Runs
Track one execution of agent work.
Every accepted thread.send() creates a run and returns its RunHandle.
const run = await thread.send("Summarize this thread");The run may start immediately or remain queued while another run owns the thread's execution lease.
Reading A Run
Runs are normalized beside the transcript:
const state = thread.getState();
const runState = state.runs.find((candidate) => candidate.runId === run.runId);Messages retain their durable association in metadata.pai.runId:
const messages = state.messages.filter(
(message) => message.metadata.pai.runId === run.runId,
);This grouping is a derived view over the same PaiMessage objects, not a
parallel transcript DTO.
Waiting
await run.waitUntilIdle();
await run.waitUntilBlocked();
const state = await run.waitUntil("waiting");These helpers return the latest owning ThreadState. For the lower-level run
record, use getState() or waitUntilStatus():
const current = await run.getState();
const completed = await run.waitUntilStatus("completed");waitUntilBlocked() resolves when a run becomes waiting or terminal, which is
useful for suspension, client-tool, and human-in-the-loop flows.
Watching
for await (const state of run.watch({ stopOn: "blocked" })) {
render(state.messages, state.runs);
}run.watch() yields the same ThreadState as thread.watch() and stops at
the selected lifecycle boundary.
To locate an action after a run blocks, resolve it from its tool part:
const state = thread.getState();
for (const message of state.messages) {
for (const part of message.parts) {
if (!isPaiToolPart(part)) continue;
const action = thread.getPendingAction(message, part);
if (action) await action.submit({ approved: true });
}
}Usage
const record = await run.getState();
renderUsage(record?.usage);Run usage aggregates every model step in one accepted unit of work. Thread
totals remain on state.usage; step usage is on model message
metadata.pai.usage.
Stopping
await run.stop();Stopping is cooperative. It targets this exact running or waiting run, while
leaving sibling queued work unchanged. Recall a queue item directly to cancel
it before admission, or use thread.stop({ continueWith }) to choose what
happens to all pending work.
Regenerating
const replacementRun = await run.regenerate();Regeneration rewinds the owning turn and preserves its complete ordered input
set. Use thread.regenerate({ messageId, replacement }) for the lower-level
edit operation.
Queued Runs
const queued = await thread.send("Continue later", {
queue: { mode: "queue" },
});While queued, (await queued.getState())?.status is "queued" and its input
stays in state.queue.items. Admission moves it into the transcript without
recasting its native message parts.