> ## Documentation Index
> Fetch the complete documentation index at: https://tawkitai-alem-extract-zod-from-core.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Interrupts

> Human-in-the-loop pauses and resumes in the Agent User Interaction Protocol

Agents sometimes need to pause: to get human approval before executing a
sensitive action, to request structured input, to wait on an out-of-band
policy decision. AG-UI exposes this as an **interrupt-aware run lifecycle** —
a terminal model where the run ends with an interrupt outcome, and the client
starts a new run carrying per-interrupt responses.

## Lifecycle

```mermaid theme={null}
sequenceDiagram
  participant Agent
  participant Client as Client App

  Note over Agent,Client: Run 1 begins
  Agent-->>Client: RunStarted (runId: r1)
  Agent-->>Client: ...ToolCall* / TextMessage* / StateSnapshot...
  Note over Agent,Client: Agent needs user input — emit snapshot, then interrupt
  Agent-->>Client: RunFinished { outcome: { type: "interrupt", interrupts: [...] } }

  Note over Agent,Client: User resolves interrupts
  Client-->>Agent: RunAgentInput { threadId, resume: [{interruptId, status, payload?}, ...] }

  Note over Agent,Client: Run 2 begins; resume[].interruptId links back to run 1's interrupts
  Agent-->>Client: RunStarted (runId: r2)
  Agent-->>Client: ...continue / ToolCallResult / ...
  Agent-->>Client: RunFinished { outcome: { type: "success" }, result }
```

## Run outcomes

`RunFinished` carries an optional `outcome` field — a discriminated union with
the variant-specific data nested inside:

* **omitted** — legacy/back-compat. Treated as a normal completion. Pre-existing
  AG-UI clients that did not yet know about interrupts still emit this shape,
  and new readers should accept it.
* `{ type: "success" }` — the run completed normally. The optional `result`
  stays at the root of the event for back-compat.
* `{ type: "interrupt", interrupts: [...] }` — the run paused for user input.
  `interrupts` is a non-empty array, and it lives inside the outcome so it
  travels with the variant that needs it.

```typescript theme={null}
type RunFinishedOutcome =
  | { type: "success" }
  | { type: "interrupt"; interrupts: Interrupt[] }

type RunFinishedEvent = {
  type: "RUN_FINISHED"
  threadId: string
  runId: string
  result?: unknown
  outcome?: RunFinishedOutcome
}
```

Because `outcome` is optional, an old producer that has never heard of
interrupts (no `outcome` field) still validates as a `RunFinished` event under
the new schema — clients only need to inspect `outcome` when they care about
the interrupt-aware variant.

## The Interrupt type

```typescript theme={null}
type Interrupt = {
  id: string
  reason: string
  message?: string
  toolCallId?: string
  responseSchema?: JsonSchema
  expiresAt?: string
  metadata?: Record<string, any>
}
```

| Field            | Purpose                                                             |
| ---------------- | ------------------------------------------------------------------- |
| `id`             | Correlation key across interrupt, resume, idempotency, and audit.   |
| `reason`         | Categorical routing hint — see [Reason taxonomy](#reason-taxonomy). |
| `message`        | Human-readable prompt. Universal fallback UI content.               |
| `toolCallId`     | Binds the interrupt to a prior `ToolCall*` sequence.                |
| `responseSchema` | JSON Schema for the expected `resume.payload`.                      |
| `expiresAt`      | Optional ISO-8601 TTL. Stale resumes produce `RunError`.            |
| `metadata`       | Free-form framework-specific data.                                  |

## Resuming a run

The next `RunAgentInput` on the same thread carries a `resume` array:

```typescript theme={null}
type RunAgentInput = {
  // ... existing fields
  resume?: Array<{
    interruptId: string
    status: "resolved" | "cancelled"
    payload?: any
  }>
}
```

* `resolved` — the user responded. `payload` carries the response, validated
  against the interrupt's `responseSchema`. Denials are expressed inside the
  payload (for example, `{ approved: false }`), not as a separate status.
* `cancelled` — the user abandoned without providing meaningful input.
  `payload` should be omitted.

## Contract rules

1. **Same thread.** Resume requests must use the same `threadId` as the
   interrupted run.
2. **Resume linkage.** `resume[].interruptId` must reference an `id` from the
   interrupted run's `interrupts[]`. `parentRunId` is orthogonal — it retains
   its existing AG-UI branching/time-travel semantics.
3. **Cover all open interrupts.** A single `resume` array must address every
   open interrupt from the interrupted run. Partial resumes are not supported.
4. **Pending interrupts block new input.** If a thread has unresolved
   interrupts, any `RunAgentInput` on that thread must include a `resume`
   addressing them. Agents receiving a non-conforming input must emit
   `RunError`.
5. **Idempotency.** A resume with the same `(threadId, interruptId, status,
   payload)` must be safe to replay.
6. **Payload validation.** If an interrupt declares a `responseSchema`, the
   agent may validate the corresponding resume `payload` and emit `RunError`
   on mismatch. Clients should validate before submitting.
7. **Expiry enforcement.** Clients must not submit a resume past an
   interrupt's `expiresAt`. Stale resumes produce `RunError`.
8. **Graceful handling.** Agents should handle missing or invalid resume
   payloads via `RunError`, not silent failures.

## State at the interrupt boundary

At the moment of interrupt, the agent must emit any state required for resume
via `StateSnapshot` and `MessagesSnapshot` events **before** the
`RunFinished` event that carries the interrupt.

This rule makes the protocol resume-mode-agnostic: both replay-style
continuations (rebuild context from messages + state) and checkpoint-style
continuations (restore a suspended coroutine) must produce identical
observable behavior on resume. Framework-native checkpointing is an
implementation optimization, not a protocol contract.

## Error handling

`RunError` is the sole error event. The `outcome` enum does not carry an
`"error"` value. Interrupt-specific error conditions that produce `RunError`:

* A resume arrives past an interrupt's `expiresAt`.
* A resume payload fails validation against its `responseSchema`.
* A resume references an `interruptId` the agent cannot correlate.
* A resume fails to address every open interrupt (violates rule 3).
* A `RunAgentInput` on a thread with pending interrupts omits `resume`
  (violates rule 4).

## Reason taxonomy

`reason` is a required string. A small set of core values is spec-defined; any
other string is a valid extension.

### Core values

| Value            | Semantics                                                  | Typical companion fields                    |
| ---------------- | ---------------------------------------------------------- | ------------------------------------------- |
| `tool_call`      | Interrupt bound to a specific tool call awaiting decision. | `toolCallId` must be set.                   |
| `input_required` | Agent needs structured input to continue.                  | `responseSchema` should be set.             |
| `confirmation`   | Free-standing yes/no decision not bound to a tool.         | `responseSchema` optional; boolean default. |

### Custom reasons

Any other string is valid. Agents should namespace custom reasons as
`<framework>:<name>` (for example, `langgraph:database_modification`,
`mastra:workflow_suspend`). The `core:` prefix is reserved for future spec
additions.

### Client routing

* Clients should switch on known core values for dedicated UI.
* For unknown reasons, clients must not error. Render from `message`,
  `responseSchema`, and `metadata`.

## Tool-bound interrupts

When an interrupt carries `reason: "tool_call"` and a `toolCallId`, the tool
call and its resolution span two runs. The full audit trail is:

1. `ToolCallArgs` from the interrupted run (the agent's proposal).
2. `RunAgentInput.resume` payload from the resumed run (user decision and
   edits).
3. `ToolCallResult` from the resumed run (actual execution outcome).

The agent does **not** re-emit `ToolCallStart`/`ToolCallArgs`/`ToolCallEnd`
in the resumed run — it emits `ToolCallResult` against the original
`toolCallId`.

### Approve with edits

The recommended `responseSchema` pattern for tool-bound interrupts that
support approve-with-edits:

```json theme={null}
{
  "type": "object",
  "properties": {
    "approved": { "type": "boolean" },
    "editedArgs": {
      "type": "object",
      "description": "Full replacement of the tool args. Not merged."
    }
  },
  "required": ["approved"]
}
```

`editedArgs` is a full replacement, not a partial merge. Its presence in the
schema is the **capability signal** that the client may offer edit UI.

## Examples

### Minimal tool approval

The agent interrupts after proposing `sendEmail`:

```json theme={null}
{
  "type": "RUN_FINISHED",
  "threadId": "thread-1",
  "runId": "run-1",
  "outcome": {
    "type": "interrupt",
    "interrupts": [
      {
        "id": "int-abc123",
        "reason": "tool_call",
        "message": "Send email to a@b.com with subject 'Hi'?",
        "toolCallId": "tc-001",
        "responseSchema": {
          "type": "object",
          "properties": { "approved": { "type": "boolean" } },
          "required": ["approved"]
        }
      }
    ]
  }
}
```

The client submits the resume:

```json theme={null}
{
  "threadId": "thread-1",
  "runId": "run-2",
  "resume": [
    { "interruptId": "int-abc123", "status": "resolved", "payload": { "approved": true } }
  ]
}
```

The agent continues in `run-2`, emits `ToolCallResult` against `tc-001`, then
`RunFinished { outcome: { type: "success" } }`.

### Approve with edits (full audit)

```json theme={null}
{
  "type": "RUN_FINISHED",
  "threadId": "thread-2",
  "runId": "run-10",
  "outcome": {
    "type": "interrupt",
    "interrupts": [
      {
        "id": "int-email-edit",
        "reason": "tool_call",
        "message": "Send email to a@b.com? You can edit the body before approving.",
        "toolCallId": "tc-42",
        "responseSchema": {
          "type": "object",
          "properties": {
            "approved": { "type": "boolean" },
            "editedArgs": {
              "type": "object",
              "properties": {
                "to": { "type": "string", "format": "email" },
                "subject": { "type": "string" },
                "body": { "type": "string" }
              }
            }
          },
          "required": ["approved"]
        },
        "metadata": {
          "langgraph": {
            "checkpointId": "ckpt-xyz",
            "nodeId": "tool_executor"
          }
        }
      }
    ]
  }
}
```

Client resume with edits:

```json theme={null}
{
  "threadId": "thread-2",
  "runId": "run-11",
  "resume": [
    {
      "interruptId": "int-email-edit",
      "status": "resolved",
      "payload": {
        "approved": true,
        "editedArgs": {
          "to": "a@b.com",
          "subject": "Hi",
          "body": "Hi (revised per my note)"
        }
      }
    }
  ]
}
```

Audit trail for `tc-42`:

* `run-10` `ToolCallArgs` — original proposal.
* `run-11` `RunAgentInput.resume[0].payload.editedArgs` — user edits.
* `run-11` `ToolCallResult` — actual outcome.

### Parallel interrupts

```json theme={null}
{
  "type": "RUN_FINISHED",
  "threadId": "thread-3",
  "runId": "run-20",
  "outcome": {
    "type": "interrupt",
    "interrupts": [
      { "id": "i-1", "reason": "tool_call", "toolCallId": "tc-a", "message": "Approve sendEmail to x@y.com?" },
      { "id": "i-2", "reason": "tool_call", "toolCallId": "tc-b", "message": "Approve sendEmail to y@z.com?" },
      { "id": "i-3", "reason": "tool_call", "toolCallId": "tc-c", "message": "Approve sendEmail to z@w.com?" }
    ]
  }
}
```

Client approves two, cancels one:

```json theme={null}
{
  "threadId": "thread-3",
  "runId": "run-21",
  "resume": [
    { "interruptId": "i-1", "status": "resolved", "payload": { "approved": true } },
    { "interruptId": "i-2", "status": "resolved", "payload": { "approved": true } },
    { "interruptId": "i-3", "status": "cancelled" }
  ]
}
```

In `run-21` the agent emits `ToolCallResult` for `tc-a` and `tc-b` and treats
`tc-c` as not-executed.

### Non-tool input request

```json theme={null}
{
  "type": "RUN_FINISHED",
  "threadId": "thread-4",
  "runId": "run-30",
  "outcome": {
    "type": "interrupt",
    "interrupts": [
      {
        "id": "int-form",
        "reason": "input_required",
        "message": "Please provide the quarterly filing details.",
        "responseSchema": {
          "type": "object",
          "properties": {
            "quarter": { "type": "string", "enum": ["Q1", "Q2", "Q3", "Q4"] },
            "year": { "type": "integer", "minimum": 2000 },
            "revenue": { "type": "number" }
          },
          "required": ["quarter", "year", "revenue"]
        },
        "expiresAt": "2026-04-20T17:00:00Z"
      }
    ]
  }
}
```

Client response:

```json theme={null}
{
  "threadId": "thread-4",
  "runId": "run-31",
  "resume": [
    {
      "interruptId": "int-form",
      "status": "resolved",
      "payload": { "quarter": "Q1", "year": 2026, "revenue": 4200000 }
    }
  ]
}
```

## Framework integrations

| Framework   | Package                                | Interrupt support                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ----------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| LangGraph   | `@ag-ui/langgraph` / `ag-ui-langgraph` | ✅ Accepts `RunAgentInput.resume[]`. Can emit `RunFinishedEvent.outcome = {type:"interrupt"}` — opt-in via `emitInterruptOutcome` / `emit_interrupt_outcome` (default off; legacy clients that resume via `command.resume` stop resuming once they see the structured outcome). Legacy `CustomEvent(name="on_interrupt")` emitted by default; disable via `enableLegacyOnInterruptEvent: false`. Subclass hooks available for custom HITL translation. |
| AWS Strands | `@ag-ui/aws-strands`                   | ✅ Forwards native Strands interrupts via `RunFinishedEvent.outcome`.                                                                                                                                                                                                                                                                                                                                                                                  |

## Related

* [Events](/concepts/events) — how `RunFinished` fits into the broader event stream.
* [Capabilities](/concepts/capabilities) — the `humanInTheLoop.interrupts` and
  `humanInTheLoop.approveWithEdits` flags agents declare.
