---
title: Pausing the Agent for Input
icon: "lucide/CirclePause"
description: Pause an agent run mid-tool, hand control to a custom React component, and resume with the user's answer.
hideTOC: true
snippet_cell: gen-ui-interrupt
---

## What is this?

`useInterrupt` lets your agent pause mid-run, hand control to the user
through a custom React component, and resume with whatever the user
returns. How that pause is implemented depends on the framework's
runtime.

<WhenFrameworkHas flag="interrupt_pattern" equals="native">

LangGraph ships a first-class
[`interrupt()`](https://docs.langchain.com/oss/python/langgraph/interrupts)
primitive that lets a running node suspend itself and hand control to
the client. The run is frozen server-side until the client resolves the
interrupt with a payload, at which point the node resumes as if
`interrupt()` had simply returned that payload.

CopilotKit's `useInterrupt` is the frontend half of that contract: it
subscribes to the paused run, renders whatever component you give it,
and calls the agent back with the user's answer.

</WhenFrameworkHas>

<WhenFrameworkHas flag="interrupt_pattern" equals="promise-based">

The Microsoft Agent Framework runtime can't pause a run mid-tool the
way LangGraph's `interrupt()` does, so this demo uses `useFrontendTool`
with a Promise-based handler instead. The agent calls `schedule_meeting`
like any other tool; the client-side handler renders the picker, holds
the request open, and only resolves the Promise once the user picks a
slot or cancels. Same UX from the reader's perspective — agent pauses,
user answers, agent resumes — different mechanism underneath.

</WhenFrameworkHas>

<WhenFrameworkHas flag="interrupt_pattern" absent>

> **Not available on this framework.** `useInterrupt` is only meaningful
> when the underlying runtime exposes either a native `interrupt(...)`
> primitive (LangGraph) or a Promise-resolving frontend tool path
> (Microsoft Agent Framework). For all other integrations, use
> [`useHumanInTheLoop`](../human-in-the-loop) instead — it's the
> standard hook for tool-call-based pause/resume flows and works on
> every framework that supports tool calls.

</WhenFrameworkHas>

<InlineDemo demo="gen-ui-interrupt" />

## When should I use this?

Reach for `useInterrupt` when the pause is a **graph-enforced
checkpoint** where the code path _must_ stop and wait for a human,
not an LLM-initiated tool call. Typical cases:

- A sensitive action (payments, irreversible writes) must be approved
- A required piece of state isn't known and can only be collected from the user
- The agent explicitly reaches an approval node in a longer workflow
- You want the server-side contract to be `interrupt(...)` and resume with a payload

For LLM-initiated pauses where the model decides on the fly to ask
the user, prefer [`useHumanInTheLoop`](../human-in-the-loop).

<WhenFrameworkHas flag="interrupt_pattern" equals="native">

## The backend: `interrupt()` inside a tool

The example agent exposes a `schedule_meeting` tool. When the model
calls it, the tool issues a `langgraph.interrupt(...)`
with the meeting context. The run freezes here until the client
resolves; the resolution becomes the return value of `interrupt()`,
which the tool then turns into a final string for the model:

<Snippet region="backend-interrupt-tool" title="backend/agent.py — schedule_meeting interrupt tool" />

Two things to note:

- The payload (`{"topic": topic, "attendee": attendee}`) is what the
  frontend receives as `event.value`. Keep it a plain, serializable
  object. It's the "pause-time context" the UI needs to render.
- The return-side contract (`{chosen_label, chosen_time}` or
  `{cancelled: true}`) is entirely yours. The client can send anything
  as the resolve payload; the tool is the one that gives it meaning.

## The frontend: `useInterrupt` render prop

On the client you register a `useInterrupt` hook per agent. When the
paused run arrives, its payload is handed to `render` as `event.value`,
and `resolve(...)` is how you resume the run:

<Snippet region="frontend-useinterrupt-render" title="frontend/src/app/page.tsx — useInterrupt" />

Whatever you pass to `resolve` is round-tripped back to the agent as
the return value of the matching `interrupt(...)` call.

</WhenFrameworkHas>

<WhenFrameworkHas flag="interrupt_pattern" equals="promise-based">

## The frontend: `useFrontendTool` with a Promise-resolving handler

The handler stores its `resolve` callback in a ref, returns a Promise
that the user's pick eventually resolves, and renders the picker
inline in the chat. This is the MS Agent equivalent of
`useInterrupt`'s `event` / `resolve` pair:

<Snippet region="frontend-promise-handler" title="frontend/src/app/page.tsx — useFrontendTool (Promise-based)" />

## The backend: agent instructed to call the frontend tool

The agent has no local `schedule_meeting` implementation — the tool is
registered entirely on the frontend. The backend's only job is to
instruct the model to call `schedule_meeting` whenever the user wants
to book a meeting. AG-UI routes the tool call to the client, where
the Promise-returning handler takes over:

<Snippet region="backend-tool-call" title="backend — agent instructed to call schedule_meeting" />

</WhenFrameworkHas>

<WhenFrameworkHas flag="interrupt_pattern" equals="native">

### Key props

- **`agentId`** — must match a runtime-registered agent. If omitted, the
  hook assumes `"default"`. A mismatch means the interrupt never fires.
- **`render`** — receives `{ event, resolve }`. `event.value` is the
  payload you passed to `interrupt(...)` on the server.
- **`renderInChat`** — when `true` (as above), the picker appears inline
  in the chat transcript, between the paused assistant turn and the
  still-pending continuation.

## Multiple interrupts? Add a type and gate with `enabled`

If your graph issues more than one kind of interrupt (e.g. `"ask"` vs
`"approval"`), tag each with a `type` field on the payload and install
one `useInterrupt` per shape, each gated by an `enabled` predicate:

```tsx
useInterrupt({
  agentId: "gen-ui-interrupt",
  enabled: ({ eventValue }) => eventValue.type === "ask",
  render: ({ event, resolve }) => (
    <AskCard question={event.value.content} onAnswer={resolve} />
  ),
});

useInterrupt({
  agentId: "gen-ui-interrupt",
  enabled: ({ eventValue }) => eventValue.type === "approval",
  render: ({ event, resolve }) => (
    <ApproveCard content={event.value.content} onAnswer={resolve} />
  ),
});
```

## Preprocess with `handler`

For cases where the interrupt can sometimes be resolved _without_ user
input (e.g. the current user already has permission), pass a `handler`
that runs before `render`. The handler can call `resolve(...)` itself
to short-circuit the UI, or return a value that `render` receives as
`result`:

```tsx
useInterrupt({
  agentId: "gen-ui-interrupt",
  handler: async ({ event, resolve }) => {
    const dept = await lookupUserDepartment();
    if (event.value.accessDepartment === dept || dept === "admin") {
      resolve({ code: "AUTH_BY_DEPARTMENT" });
      return; // skip render
    }
    return { dept };
  },
  render: ({ result, event, resolve }) => (
    <RequestAccessCard
      dept={result.dept}
      onRequest={() => resolve({ code: "REQUEST_AUTH" })}
      onCancel={() => resolve({ code: "CANCEL" })}
    />
  ),
});
```

</WhenFrameworkHas>

## Going further

- [Tool-based HITL with `useHumanInTheLoop`](../human-in-the-loop) — for
  LLM-initiated pauses.
- [Headless interrupts](./headless) — compose the lower-level primitives
  (`useAgent`, `agent.subscribe`, `copilotkit.runAgent`) to resolve
  interrupts outside a chat surface.
