Budgets and cancellation
Grant bounded work and preserve cancellation and deadlines through delegation.
Root turn
{ steps: 8, timeMs: 30_000 }Child grant
{ steps: 4, timeMs: 25_000 }
effective timeMs: 20_000A tool loop needs an execution limit even when every individual call is valid. A delegated child also needs its own grant, without gaining more time than its caller has left.
Choose the right limit
| Setting | What it bounds |
|---|---|
budget.steps | Ordinary model-loop iterations for this turn. |
budget.timeMs | Optional time ceiling for this turn; otherwise turn.wallClock.limitMs applies. |
budget.outputTokens | Cumulative output-token grant across the whole turn. |
context.outputReserveTokens | Output reserved and passed to shipped adapters on each request. |
continuation.max | Extra recovery rounds after a length-limited model response. |
Deliverable nudges | Extra rounds to recover a missing required submission. |
Tool or delegation retries | Repeated execution attempts after failure. |
The runtime's manifest declares exactly two ceilings — turn.wallClock
(with an optional under naming the platform limit it must sit below) and
transport.envelopeBytes, measured in UTF-8 bytes. Both are required; a
missing entry refuses boot.
The model-loop step grant is not a cap on every operation. One response can
contain several tool calls, a chain may retry or fall back, and recovery
rounds are separately bounded. budget.outputTokens is a cumulative grant:
each model request's output reserve is clamped to what remains of it, and an
exhausted grant settles the turn failed with budget-cut — including a
continuation or nudge round it would otherwise have funded — never a
synthetic completion.
Grant work explicitly
const controller = new AbortController();
const pending = runtime.runTurn({
agent: assistant,
ambient,
envelope,
budget: { steps: 8, timeMs: 30_000 },
hooks: { signal: controller.signal },
});
// Your UI stop handler calls controller.abort().
const outcome = await pending;hooks carries two fields: signal for cooperative cancellation, and
onEvent for observing the turn's wire. A user stop settles as stopped.
Exhausting ordinary steps without a clean finish produces failed with
budget-cut. A cut outcome names its cause in by: "timeout" (the time
ceiling), "watchdog" (a silent tool), or "stream-error" (a provider
stream that failed after content).
Preserve the caller's deadline
A child receives the smaller of its own time grant and the caller's remaining time. A child with a shorter local timeout can return failure feedback while its caller still has time to continue. Cancellation is inherited by descendants and propagates back to callers without retrying a stopped child.
Child steps are a separate grant, not automatically deducted from the parent's step count. Keep delegation depth, grants, and retries deliberate. See Delegation.
Keep tools alive or cut them
Every tool executes with a context — execute(input, ambient, ctx) where
ctx is { signal, progress } — and the executor holds both ends:
ctx.progress(data)emits a progress part on the wire and resets the liveness window. It is the heartbeat: a tool that declaresliveness: { maxSilenceMs }and then goes silent past that wall-clock ceiling hasctx.signalaborted, and the turn settlescutwithby: "watchdog"instead of hanging.- The turn's remaining time additionally caps every tool call, liveness
declared or not — a tool cannot out-sleep the wall-clock budget; exceeding
it cuts the turn with
by: "timeout".
The abort frees the turn even when the tool's own promise ignores the signal,
but a well-behaved long-running tool passes ctx.signal to its I/O so the
underlying work actually stops. Model requests remain cooperative: the runtime
observes cancellation and deadlines at execution boundaries rather than
forcibly terminating an in-flight provider call.
At boot, a declared turn.wallClock.under requires the runtime ceiling to sit
below the platform limit — the app answers before the platform kills. This
validates configuration; it cannot guarantee a response before an
uninterruptible external operation finishes.
Inspect in Studio
Compare the granted steps with steps used in TURN RECORDS. Recovery can add rounds beyond the ordinary loop grant. Trigger stop and timeout separately, then check that descendants and the root have the expected outcomes — and fire a deliberately silent tool to watch the watchdog cut land in EVENTS.
Next: Failure and recovery.
