Busy Thread Queueing
Send work while a thread is already running.
App code still uses thread.send().
const first = await thread.send("Draft a report");
const second = await thread.send("Then make it shorter", { queue: { mode: "queue" } });
await first.waitUntilIdle();
await second.waitUntilIdle();With queue: { mode: "queue" }, a busy thread accepts the second send but does not admit it to the transcript yet. The runtime creates a queued run, stores the message in queued_items, and returns second as a RunHandle with status "queued". Without an explicit queue mode, busy sends reject with ThreadBusyError.
The queued item becomes a transcript message only when the thread is available to admit the next turn. second.waitUntilIdle() follows that same run through queued, running, waiting, and terminal states.
Render queued state from ThreadState:
const state = thread.getState();
for (const item of state.queue.items) {
renderQueuedItem({
queueItemId: item.queueItemId,
runId: item.runId,
mode: item.mode,
messages: item.messages,
});
}state.queue.items is UI state, not transcript history. Queued items are future user messages.
Recall queued work before it is admitted:
const queued = thread.getState().queue.items.find((item) => item.runId === second.runId);
if (queued) {
await queued.recall();
}Recall removes the queued item and marks the owning run cancelled. It does not append a cancelled message to the transcript.
Editors can restore recalled queued user messages from the queued item:
if (queued) {
const draftText = queued.messages
.flatMap((message) => message.parts)
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n");
await queued.recall();
composer.setText(draftText);
}Recalled messages are no longer scheduled work. Recall preserves queued item ids, order, parts, and metadata until the item is removed, so callers that need draft restoration should read queued.messages before recalling.