AgentRuntime.resume
JavaScript · package toolnexus · SPEC §10 · js/src/agents/runtime.ts
class AgentRuntime { async resume(answer: Answer): Promise<void>}Routes an Answer to the deepest suspended handle in
the runtime’s tree, resumes it from its checkpoint (a retry-with-answer of the halted tool —
turns and token usage keep accumulating, never reset), then cascades upward: each suspended
ancestor replays too, and its re-invoked task delegation call reattaches to the already-
resumed child by task key rather than spawning a duplicate. resume itself returns nothing — call
runtime.wait(handle) afterward to get the finished
TaskResult.
When to use it
Section titled “When to use it”Reach for runtime.resume(answer) any time an agents.Agent.run()
call comes back with status: "pending" and you have (or have just obtained) the
Answer to that suspension — a human approved a payment, a
login completed, a form was filled in. AgentRunResult.runtime is handed back to you specifically
for this: result.runtime.resume(answer) is the documented way back in, whether the answer arrives
a second later or after the runtime has sat idle for an hour waiting on a Slack reply.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — suspend, resume, get the final answer
Section titled “1. The smallest useful call — suspend, resume, get the final answer”import assert from "node:assert"import { agents, pending } from "toolnexus"
const approve = agents.agent("approve", { does: "Approves a payment", uses: { tools: [{ name: "charge_card", description: "Charge the card", inputSchema: { type: "object", properties: {}, additionalProperties: false }, source: "custom", execute: async (_args, ctx: any) => ctx?.answer ? { output: `charged (ok=${ctx.answer.ok})`, isError: false } : pending({ kind: "approval", prompt: "Approve $500 charge?" }), }], },})
let turn = 0const canned: typeof fetch = async () => { turn++ const body = turn === 1 ? { choices: [{ message: { content: null, tool_calls: [{ id: "c1", type: "function", function: { name: "charge_card", arguments: "{}" } }] } }] } : { choices: [{ message: { content: "Done — charged." } }] } return new Response(JSON.stringify({ ...body, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } }), { status: 200, headers: { "content-type": "application/json" } })}
const r1 = await approve.run("Charge the card.", { fetch: canned })assert.equal(r1.status, "pending")assert.equal(r1.pending?.kind, "approval")
// The handle this run spawned — resume needs it to `wait` afterward.const handle = r1.runtime.root.children[0]
await r1.runtime.resume({ id: r1.pending!.id, ok: true })const r2 = await r1.runtime.wait(handle)
assert.equal(r2.status, "done")assert.match(r2.text, /Done — charged/)
console.log("ok:", r1.status, "->", r2.status, "|", r2.text)2. A declined answer — the run finishes, it just doesn’t do the thing
Section titled “2. A declined answer — the run finishes, it just doesn’t do the thing”import assert from "node:assert"import { agents, pending } from "toolnexus"
const approve = agents.agent("approve", { does: "Approves a payment", uses: { tools: [{ name: "charge_card", description: "Charge the card", inputSchema: { type: "object", properties: {}, additionalProperties: false }, source: "custom", execute: async (_args, ctx: any) => ctx?.answer ? { output: `charge outcome ok=${ctx.answer.ok}`, isError: false } : pending({ kind: "approval", prompt: "Approve $500 charge?" }), }], },})
let turn = 0const canned: typeof fetch = async () => { turn++ const body = turn === 1 ? { choices: [{ message: { content: null, tool_calls: [{ id: "c1", type: "function", function: { name: "charge_card", arguments: "{}" } }] } }] } : { choices: [{ message: { content: "Understood — the charge was cancelled." } }] } return new Response(JSON.stringify({ ...body, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } }), { status: 200, headers: { "content-type": "application/json" } })}
const r1 = await approve.run("Charge the card.", { fetch: canned })assert.equal(r1.status, "pending")
const handle = r1.runtime.root.children[0]// §10's loop rule branches only on `ok` — a decline is data, not a thrown error.await r1.runtime.resume({ id: r1.pending!.id, ok: false, reason: "declined" })const r2 = await r1.runtime.wait(handle)
assert.equal(r2.status, "done") // the RUN still completes — it just knows the charge was declinedassert.match(r2.text, /cancelled/)
console.log("ok:", r2.text)3. Inspecting the parked tree before resuming — runtime.inspect(handle)
Section titled “3. Inspecting the parked tree before resuming — runtime.inspect(handle)”A host that stores the answer separately from the runtime (a queue, a database row) still needs a
live handle to resume — list()/inspect() give a read-only view of what’s parked, including the
pending request itself, without guessing at root.children indices.
import assert from "node:assert"import { agents, pending } from "toolnexus"
const approve = agents.agent("approve", { does: "Approves a payment", uses: { tools: [{ name: "charge_card", description: "Charge the card", inputSchema: { type: "object", properties: {}, additionalProperties: false }, source: "custom", execute: async (_args, ctx: any) => ctx?.answer ? { output: "charged", isError: false } : pending({ kind: "approval", prompt: "Approve $500 charge?" }), }], },})
let turn = 0const canned: typeof fetch = async () => { turn++ const body = turn === 1 ? { choices: [{ message: { content: null, tool_calls: [{ id: "c1", type: "function", function: { name: "charge_card", arguments: "{}" } }] } }] } : { choices: [{ message: { content: "Charged." } }] } return new Response(JSON.stringify({ ...body, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } }), { status: 200, headers: { "content-type": "application/json" } })}
const r1 = await approve.run("Charge the card.", { fetch: canned })const handle = r1.runtime.root.children[0]
// The read-only view: same Request the AgentRunResult carried, reachable from the runtime alone.const view = r1.runtime.inspect(handle)assert.equal(view.state, "suspended")assert.equal(view.pending?.kind, "approval")assert.equal(view.pending?.id, r1.pending?.id)
await r1.runtime.resume({ id: view.pending!.id, ok: true })const r2 = await r1.runtime.wait(handle)assert.equal(r2.status, "done")
console.log("ok:", view.state, "->", r2.status)Signature
Section titled “Signature”| Parameter | Type | What it is |
|---|---|---|
answer |
Answer |
Must echo the id of the pending Request — routed to the deepest suspended handle. |
| returns | Promise<void> |
Call runtime.wait(handle) afterward for the finished TaskResult. |
See also
Section titled “See also”pending— Return a Pending from a tool to park the run until someone answers.authRequired— The auth-shaped suspension: hand back a URL, resume once the user has granted access.waitFor— The single hook where the host resolves a suspension — in-process prompt or durable queue, same contract.pendingOf— Detect that a RunResult is parked rather than finished, and get the Request that parked it.agents.Agent—.run()returns theruntimethis page’sresumeis called on.