Interrupting
A run that needs outside input ends withRUN_FINISHED whose outcome is the
interrupt outcome, carrying one or more Interrupt objects — at least one,
because an interrupt outcome with nothing to answer would leave a consumer
with nothing to do.
- A producer MUST NOT report an interrupted run as success: the interrupt
outcome is the only conforming way to end a run that stopped to ask —
where the producer names what it is waiting for and resume entries answer
it. A run that stopped by calling a
frontend tool is the other,
ordinary path: it finishes as success, naming the unanswered calls in the
success outcome’s
pendingToolCallIds, and the answer rides the next input’s messages. A run that stopped because it was told to is neither: it ends with the cancelled outcome and asks for nothing. - Each interrupt’s
idMUST be unique within the run; a resume entry answers it by this id. - An interrupt’s
reasonis an open string — the protocol does not attempt to classify every reason an agent might need input.messageis a human-readable prompt for whoever answers;toolCallIdnames the tool call an approval concerns;responseSchemadescribes the answer’s expected shape, carried opaquely so a consumer can build a form for it. - An interrupt raised inside a subagent MAY carry that subagent’s
subagentRunId; the subagent rules govern attribution and the suspended outcome that accompanies it.
RUN_ERROR reporting a failure
that surfaced after the close — and continuing means a new run.
Resuming
The run that continues carries its answers in the input’sresume list, one
ResumeEntry per interrupt answered:
- Each entry’s
interruptIdMUST name an interrupt from the run being continued — the most recent interrupted run on the thread. - An entry’s
statussays whether the interrupt was answered or abandoned;payloadcarries the answer the agent asked for, any JSON value;metadatais envelope information about the response, not part of the answer. - The resume list MUST cover every interrupt of the run being continued: each
one answered, or explicitly abandoned by an entry with the abandoned status.
Omission is not abandonment. Coverage is the consumer’s rule to enforce:
the consumer holds the interrupts the closing
RUN_FINISHEDdelivered and it assembles the resume list, so it is the one participant that can always tell whether the list is complete. A consumer MUST reject a resuming input that leaves an interrupt uncovered before the run starts — before anything is sent — and MUST NOT silently continue past an interrupt it has no entry for. The producer is not asked to check this, and needs to retain nothing from the interrupted run to be conformant; what it does when a defective consumer sends an incomplete list anyway is its error handling, below. What an abandoned interrupt means for the agent’s work is the producer’s business. expiresAtis deliberately format-unconstrained, so whether an interrupt has expired is the judging consumer’s own reading of it — the reference reads it as a date and treats now-or-earlier as expired. The rule attaches to the judgment, not to a parse the schema refuses to specify: an interrupt the consumer judges expired can no longer be answered — the consumer rejects a resume entry resolving it before the run starts, as it rejects an uncovered interrupt. It can still be — and, coverage being mandatory, must be — abandoned, which is how a thread moves past an interrupt nobody answered in time.- Thread and state continuity hold across the gap: the resuming run carries the
same
threadId, the accumulated messages, and the state the interrupted run left behind, exactly as any sequential run does. - Token usage does not carry across the gap: the resuming run’s
usagecovers only the model calls it made itself. The interrupted run already reported its own on theRUN_FINISHEDthat interrupted it.
- An unrecognised entry — one naming an interrupt the producer did not raise: the run SHOULD proceed without the entry, and the producer SHOULD surface a warning rather than fail a run over an answer it never asked for.
-
An uncovered interrupt — one the producer can tell is still open and
has no entry, because it kept the interrupted run’s checkpoint, or because
the messages carry an approval-gated tool call with neither a result nor an
entry answering it.
A producer MUST NOT perform the interrupted action on the strength of an
absent entry, and MUST NOT treat the omission as abandonment — dropping the
call with a warning and finishing as success would let a defective consumer
abandon what nobody decided to abandon, which is the outcome the coverage
rule exists to prevent. Having noticed, the producer keeps the interrupt
open. It MAY reject the input before the run starts, as any invalid input
(a stream opening with
RUN_ERROR, or the transport’s own rejection of the request); or it MAY run what the covered entries permit and end with the interrupt outcome again, carrying the still-open interrupt so the consumer gets another chance to answer it. Either way the run MUST NOT end as success while an interrupt the producer knows to be open stands unanswered. A producer that cannot tell — the stateless case above — is under no obligation here, because it has nothing to notice.
Message Flow
Data Types
RunFinishedInterruptOutcome, Interrupt and ResumeEntry are defined by the
schema reference. expiresAt, when present, conventionally
carries an ISO 8601 timestamp; the schema deliberately does not constrain its
format.