Deferred tools
A tool doesn't have to answer in the same decision. Your worker can acknowledge the call, do the work on its own schedule, and report the result later. The turn stays open, and the run resumes when the answer arrives.
Example
Answer tool.execute without settling the call: start the work, return an
empty decision, and the engine leaves the call in flight.
const tools = [
{
name: "render_report",
description: "Render a report. This takes a while.",
input: {
type: "object",
properties: { topic: { type: "string" } },
required: ["topic"]
}
}
];
function decide({ trigger, proposed }) {
if (trigger.type === "session.start") {
return { agent: { model: "claude-haiku-4-5-20251001", stream: true, tools } };
}
// Kick off the work, keyed by the call id, and leave the call pending.
if (trigger.type === "tool.execute") {
startRender(trigger.id, trigger.input.value); // runs elsewhere, on its own schedule
return {}; // no tool.result yet
}
return proposed;
}When the render finishes, settle the call out of band by its id:
{ "type": "tool.result", "id": "<toolCallId>", "result": "https://reports/42.pdf" }Deferring
Answering a tool.execute with no tool.result or tool.error leaves the
call in flight. It is the same state a client-side tool sits in while the
browser works (see Client-side tools); a deferred tool
is the worker-handled version. The declaration is an ordinary tool, run on
your own schedule.
Waiting
While a call is in flight the turn stays open. Sibling tool calls that finish first only record their results; the model is not re-prompted until every in-flight call has settled, so it never sees a half-finished turn. The wait is just persisted state: it holds no compute and survives a restart of the engine or your worker.
Settling
Report the outcome with a tool.result or tool.error, addressed by the call
id:
{ type: "tool.result"; id: string; attempt?: number; result?: unknown }
{ type: "tool.error"; id: string; error: string; retryable: boolean; attempt?: number }Your service submits it to the engine's client API, or calls settle_effect
in an embedded runtime. attempt is optional; include it to fence a settle
from a stale executor that a retry has superseded. The engine records the
result and, once nothing is in flight, re-prompts the model and the turn
continues.
Timeouts
By default a deferred call waits indefinitely. Give the tool.call a retry
policy with a timeout_secs to bound the wait: when it lapses the call fails,
then retries or settles as a terminal error per the policy. See
Retries.
Next
- Tool calls: the tool contract these build on.
- Client-side tools: the same wait, driven by the browser.
- Retries and timeouts: bounding a deferred call.
- Interrupts: pausing the conversation for a human.