Skip to content

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.

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.

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 = 0
const 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 = 0
const 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 declined
assert.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 = 0
const 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)
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.
  • 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 the runtime this page’s resume is called on.