Handle interrupts
An action can pause a run by throwing ActionInterrupt — to ask a human to approve a
destructive step, say. When that happens the run does not fail: it suspends and hands
control back to you. This guide shows how to detect a suspended run, inspect what it is
waiting on, and continue it.
For writing the action side of this — throwing the interrupt and consuming its resumption — see Human-in-the-loop with interrupts. For the model behind it, see Interrupts.
Detecting a suspension
Section titled “Detecting a suspension”When an action throws ActionInterrupt, run() resolves with status: 'interrupted'
rather than throwing, and the run emits an interrupted event carrying the interrupt’s
message and data:
run.on('interrupted', ({ message, data }) => { console.log('run paused:', message, data);});
const result = await run.run();
if (result.status === 'interrupted') { // the run is waiting for you}The data is the payload the action attached to describe what must be satisfied before
resuming — for example a question to put to a human.
Inspecting the suspended state
Section titled “Inspecting the suspended state”run.state tells you exactly where the run stands. It is derived from the prompt itself, so
it reports the same thing even on a run reconstructed from a persisted prompt after a
restart:
const state = run.state;
if (state.status === 'interrupted') { console.log('suspended tool:', state.toolCall.name); console.log('waiting on:', state.interrupt.message, state.interrupt.data);}When suspended, state is { status: 'interrupted', toolCall, interrupt }:
toolCall— the'tool'output that suspended, including itsnameandinput.interrupt— the pending interrupt: itsmessage,data, and the tool’s privatestatecheckpoint.
Otherwise state is { status: 'ready' }. Calling run() while suspended throws — you
must answer the interrupt first.
Continuing the run
Section titled “Continuing the run”Answer the pending interrupt with one of two calls. Both continue the loop and return an
AgentRunResult, so the run may complete, or suspend again on the next interrupt.
Resume
Section titled “Resume”resume(data) re-runs the suspended action, handing your data back to it as
context.resumption.data (alongside the tool’s own state checkpoint). Use it once the
condition the action was waiting on is met:
const result = await run.resume({ approved: true });
if (result.status === 'completed') { console.log('finished', result.prompt.output);}The action typically checks resumption to decide whether it may now proceed:
execute: async ({ input, resumption }) => { if (!resumption) { throw new ActionInterrupt('Approve deleting the record?', { recordId: input.id }); } // resumed — the human answered return deleteRecord(input.id);};Reject
Section titled “Reject”reject(error) declines the interrupt. The action is not re-run; instead the error
you pass is recorded as the tool call’s failure and fed back to the model, which continues
with the knowledge that the tool was refused:
const result = await run.reject({ name: 'Denied', message: 'The user declined the deletion.',});The model sees the error as the tool’s result and can respond accordingly — for example by explaining to the user that it could not proceed.
The transcript keeps the history
Section titled “The transcript keeps the history”Both decisions are recorded on the tool call’s interrupt history before anything else
happens, so the transcript preserves the full story: the interrupt, its resolution, and
the eventual result. If an action interrupts more than once, each entry keeps its own
message, checkpoint state, and resolution — and each resume carries the checkpoint of
the interrupt it answered. Because all of this lives on the Prompt, you can persist a
suspended run and resume it later from a fresh AgentRun over the same prompts. See
The agent run loop.