Agent interrupts
Interrupts let a tool pause execution and return a tool request to the client. The client can approve, reject, provide missing data, refresh credentials, or ask the user a question, then resume the turn.
Use interrupts when the model can decide that outside input is needed but the tool should not proceed automatically. Common cases include human approval, missing user choices, risky operations, payments, external auth, and actions that need a fresh environment check.
Define an interrupt
Section titled “Define an interrupt”ai.defineInterrupt()for an interrupt-only tool that never performs work by itself and only asks the client for information.ctx.interrupt(metadata)inside a normal tool that can either finish immediately or pause based on runtime conditions.
const askUser = ai.defineInterrupt({ name: 'ask_user', description: 'Ask the user a clarification question.', inputSchema: z.object({ question: z.string(), options: z.array(z.string()).min(2).max(5), }), outputSchema: z.object({ answer: z.string(), }),});A normal tool can pause conditionally:
const runShell = ai.defineTool( { name: 'run_shell', description: 'Run a shell command after a safety check.', inputSchema: z.object({ command: z.string() }), }, async (input, ctx) => { if (isRisky(input.command) && !ctx.resumed?.toolApproved) { ctx.interrupt({ command: input.command, reason: 'The command can modify files.', }); }
return execute(input.command); },);Receive interrupts
Section titled “Receive interrupts”Interrupted tool requests surface on res.interrupts and as tool request chunks while streaming.
const res = await chat.send('Run the migration.');
for (const interrupt of res.interrupts) { console.log(interrupt.name); console.log(interrupt.input);}The agent finish reason is interrupted when the model turn pauses on one or more interrupts.
Respond pattern
Section titled “Respond pattern”Use respond() when the client has the final tool output. The method builds a tool response part. Send that part with chat.resume().
const res = await chat.send('Transfer $50 to Robin.');const approval = res.interrupts.find((i) => i.name === 'userApproval');
if (approval) { const continued = await chat.resume({ respond: [ approval.respond({ approved: true, approver: 'alex@example.com', }), ], });
console.log(continued.text);}This pattern is useful for approvals where the tool does not need to run again. The response you provide becomes the tool result.
Restart pattern
Section titled “Restart pattern”Use restart() when the original tool should execute again after the app changes environment or metadata. The method builds a tool request part. Send it with chat.resume().
const res = await chat.send('Run the deployment command.');const command = res.interrupts.find((i) => i.name === 'run_shell');
if (command) { const continued = await chat.resume({ restart: [command.restart()], });
console.log(continued.text);}The AgentInterrupt.restart() convenience preserves the original tool input.
Use respond for approvals where the client supplies the final answer. Use restart when the server-side tool should run again after approval, refreshed credentials, or updated environment state.
Resume validation
Section titled “Resume validation”The runtime validates resume payloads against session history. A respond entry must match an interrupted tool request by name and ref. A restart entry must match the original tool request, and its input must not be modified. This protects server tools from forged client resume payloads.
Streaming interrupts
Section titled “Streaming interrupts”const turn = chat.sendStream('Book the hotel.');
for await (const chunk of turn.stream) { for (const request of chunk.toolRequests) { if (request.metadata?.interrupt) { showApproval(request); } }}
const res = await turn.response;Wait for the final response before treating the turn as durably interrupted, because the final response carries the normalized interrupt helpers.