---
title: Interrupts
icon: "lucide/CirclePause"
description: Learn how to implement Human-in-the-Loop (HITL) using a interrupt-based flow.
---
{/* TODO: swap feature-viewer URLs back to /deepagents/ once the dojo supports that route */}
<IframeSwitcher
  id="human-in-the-loop-example"
  exampleUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/human_in_the_loop?sidebar=false&chatDefaultOpen=false"
  codeUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/human_in_the_loop?view=code&sidebar=false&codeLayout=tabs"
  exampleLabel="Demo"
  codeLabel="Code"
  height="700px"
/>

<Callout type="info">
  This example demonstrates interrupt-based human-in-the-loop (HITL) in the [CopilotKit Feature Viewer](https://feature-viewer.copilotkit.ai/langgraph/feature/human_in_the_loop).
</Callout>

## What is this?

[LangGraph's interrupt flow](https://docs.langchain.com/oss/python/langgraph/interrupts) provides an intuitive way to implement Human-in-the-loop workflows.

This guide will show you how to both use `interrupt` and how to integrate it with CopilotKit.

## When should I use this?

Human-in-the-loop is a powerful way to implement complex workflows that are production ready. By having a human in the loop,
you can ensure that the agent is always making the right decisions and ultimately is being steered in the right direction.

Interrupt-based flows are a very intuitive way to implement HITL. Instead of having a node await user input before or after its execution,
nodes can be interrupted in the middle of their execution to allow for user input. The trade-off is that the agent is not aware of the
interaction, however [CopilotKit's SDKs provide helpers to alleviate this](#make-your-agent-aware-of-interruptions).

## Implementation

<Steps>
<Step>
  ### Run and connect your agent
  <RunAndConnect />
</Step>
<Step>
  ### Install the CopilotKit SDK
  <InstallSDKSnippet />
</Step>
<Step>
### Set up your agent state
We're going to have the agent ask us to name it, so we'll need a state property to store the name.

<Tabs groupId="agent_language" items={['Python', 'TypeScript']} persist>
    <Tab value="Python">
        ```python title="agent.py"
        # ...
        from copilotkit import CopilotKitState # extends MessagesState
        # ...

        # This is the state of the agent.
        # It inherits from the CopilotKitState properties from CopilotKit.
        class AgentState(CopilotKitState):
            agent_name: str
        ```
    </Tab>
    <Tab value="TypeScript">
        ```ts title="agent.ts"
        import { createMiddleware } from "langchain";
        import { copilotkitMiddleware } from "@copilotkit/sdk-js/langgraph"; // [!code highlight]
        import { z } from "zod";

        // Define the agent's custom state as a middleware.
        export const agentNameMiddleware = createMiddleware({
            name: "AgentState",
            stateSchema: z.object({
                agentName: z.string().optional(),
            }),
        });

        // createDeepAgent({ middleware: [agentNameMiddleware, copilotkitMiddleware], ... })
        ```
    </Tab>
</Tabs>

</Step>
<Step>
    ### Call `interrupt` in your Deep Agents agent
    Now we can call `interrupt` in our Deep Agents agent.

    <Callout type="info">
        Your agent will not be aware of the `interrupt` interaction by default in LangGraph.

        If you want this behavior, see the [section on it below](#make-your-agent-aware-of-interruptions).
    </Callout>

    <Tabs groupId="agent_language" items={['Python', 'TypeScript']} persist>
        <Tab value="Python">
            ```python title="agent.py"
            from typing import Any
            from copilotkit import CopilotKitState
            from langchain.agents.middleware import AgentMiddleware # [!code highlight]
            from langgraph.runtime import Runtime
            from langgraph.types import interrupt # [!code highlight]

            # add the agent state definition from the previous step
            class AgentState(CopilotKitState):
                agent_name: str

            class AgentNameMiddleware(AgentMiddleware[AgentState, Any]):
                state_schema = AgentState

                def before_model(self, state: AgentState, runtime: Runtime[Any]) -> dict[str, Any] | None:
                    if not state.get("agent_name"):
                        # Interrupt and wait for the user to respond with a name
                        name = interrupt("Before we start, what would you like to call me?") # [!code highlight]
                        return {"agent_name": name}
                    return None
            ```
        </Tab>
        <Tab value="TypeScript">
            In Deep Agents TypeScript, wire the interrupt into a middleware hook rather than a custom node. The hook below runs before the model call and asks the user for a name the first time.

            ```ts title="agent.ts"
            import { createMiddleware } from "langchain";
            import { interrupt } from "@langchain/langgraph"; // [!code highlight]
            import { z } from "zod";

            export const agentNameMiddleware = createMiddleware({
                name: "AgentState",
                stateSchema: z.object({
                    agentName: z.string().optional(),
                }),
                beforeModel: async (state) => {
                    if (!state.agentName) {
                        // Interrupt and wait for the user to respond with a name
                        const name = await interrupt("Before we start, what would you like to call me?"); // [!code highlight]
                        return { agentName: name };
                    }
                },
            });
            ```
        </Tab>
    </Tabs>
</Step>
<Step>
    ### Handle the interrupt in your frontend
    At this point, your Deep Agents agent's `interrupt` will be called. However, we currently have no handling for rendering or
    responding to the interrupt in the frontend.

    To do this, we'll use the `useInterrupt` hook, give it a component to render, and then call `resolve` with the user's response.

    ```tsx title="app/page.tsx"
    import { useInterrupt } from "@copilotkit/react-core/v2"; // [!code highlight]
    // ...

    const YourMainContent = () => {
    // ...
    // [!code highlight:15]
    // styles omitted for brevity
    useInterrupt({
        render: ({ event, resolve }) => (
            <div>
                <p>{event.value}</p>
                <form onSubmit={(e) => {
                    e.preventDefault();
                    resolve((e.target as HTMLFormElement).response.value);
                }}>
                    <input type="text" name="response" placeholder="Enter your response" />
                    <button type="submit">Submit</button>
                </form>
            </div>
        )
    });
    // ...

    return <div>{/* ... */}</div>
    }
    ```

</Step>
<Step>
### Give it a try!
Try talking to your agent, you'll see that it now pauses execution and waits for you to respond!
<video src="https://cdn.copilotkit.ai/docs/copilotkit/images/coagents/interrupt-flow.mp4" className="rounded-lg shadow-xl" loop playsInline controls autoPlay muted />
</Step>
</Steps>

## Advanced usage

### Condition UI executions

When rendering multiple `interrupt` events in the agent, there could be conflicts between multiple `useInterrupt` hooks calls in the UI.
For this reason, the hook can take an `enabled` argument which will apply it conditionally:

<Steps>
    <Step>
        ### Define multiple interrupts
        First, let's define two different interrupts. We will include a "type" property to differentiate them.
        <Tabs groupId="agent_language" items={['Python', 'TypeScript']} persist>
            <Tab value="Python">
                ```python title="agent.py"
                from typing import Any
                from langchain.agents.middleware import AgentMiddleware
                from langgraph.runtime import Runtime
                from langgraph.types import interrupt # [!code highlight]

                # ... your full state definition

                class ApprovalAndNameMiddleware(AgentMiddleware[AgentState, Any]):
                    state_schema = AgentState

                    def before_model(self, state: AgentState, runtime: Runtime[Any]) -> dict[str, Any] | None:
                        approval = interrupt({ "type": "approval", "content": "please approve" }) # [!code highlight]
                        updates: dict[str, Any] = {"approval": approval}

                        if not state.get("agent_name"):
                            # Interrupt and wait for the user to respond with a name
                            updates["agent_name"] = interrupt({ "type": "ask", "content": "Before we start, what would you like to call me?" }) # [!code highlight]

                        return updates
                ```
            </Tab>
            <Tab value="TypeScript">
                ```ts title="agent.ts"
                import { createMiddleware } from "langchain";
                import { interrupt } from "@langchain/langgraph"; // [!code highlight]
                import { z } from "zod";

                export const approvalAndNameMiddleware = createMiddleware({
                    name: "AgentState",
                    stateSchema: z.object({
                        agentName: z.string().optional(),
                        approval: z.unknown().optional(),
                    }),
                    beforeModel: async (state) => {
                        const approval = await interrupt({ type: "approval", content: "please approve" }); // [!code highlight]
                        const updates: Record<string, unknown> = { approval };

                        if (!state.agentName) {
                            updates.agentName = await interrupt({ type: "ask", content: "Before we start, what would you like to call me?" }); // [!code highlight]
                        }

                        return updates;
                    },
                });
                ```
            </Tab>
        </Tabs>
    </Step>
    <Step>
        ### Add multiple frontend handlers
        With the differentiator in mind, we will add a handler that takes care of any "ask" and any "approve" types.
        With two `useInterrupt` hooks in our page, we can leverage the `enabled` property to enable each in the right time:

        ```tsx title="app/page.tsx"
        import { useInterrupt } from "@copilotkit/react-core/v2"; // [!code highlight]
        // ...

        const ApproveComponent = ({ content, onAnswer }: { content: string; onAnswer: (approved: boolean) => void }) => (
            // styles omitted for brevity
            <div>
                <h1>Do you approve?</h1>
                <button onClick={() => onAnswer(true)}>Approve</button>
                <button onClick={() => onAnswer(false)}>Reject</button>
            </div>
        )

        const AskComponent = ({ question, onAnswer }: { question: string; onAnswer: (answer: string) => void }) => (
        // styles omitted for brevity
            <div>
                <p>{question}</p>
                <form onSubmit={(e) => {
                    e.preventDefault();
                    onAnswer((e.target as HTMLFormElement).response.value);
                }}>
                    <input type="text" name="response" placeholder="Enter your response" />
                    <button type="submit">Submit</button>
                </form>
            </div>
        )

        const YourMainContent = () => {
            // ...
            // [!code highlight:13]
            useInterrupt({
                enabled: ({ eventValue }) => eventValue.type === 'ask',
                render: ({ event, resolve }) => (
                    <AskComponent question={event.value.content} onAnswer={answer => resolve(answer)} />
                )
            });

            useInterrupt({
                enabled: ({ eventValue }) => eventValue.type === 'approval',
                render: ({ event, resolve }) => (
                    <ApproveComponent content={event.value.content} onAnswer={answer => resolve(answer)} />
                )
            });

            // ...
        }
    ```
    </Step>
</Steps>

### Preprocessing of an interrupt and programmatically handling an interrupt value

When opting for custom chat UI, some cases may require pre-processing of the incoming values of interrupt event or even resolving it entirely without showing a UI for it.
This can be achieved using the `handler` property, which is not required to return a React component.

The return value of the handler will be passed to the `render` method as the `result` argument.
```tsx title="app/page.tsx"
// We will assume an interrupt event in the following shape
type Department = 'finance' | 'engineering' | 'admin'
interface AuthorizationInterruptEvent {
    type: 'auth',
    accessDepartment: Department,
}

import { useInterrupt } from "@copilotkit/react-core/v2";

const YourMainContent = () => {
    const [userEmail, setUserEmail] = useState({ email: 'example@user.com' })
    function getUserByEmail(email: string): { id: string; department: Department } {
        // ... an implementation of user fetching
    }

    // ...
    // styles omitted for brevity
    // [!code highlight:28]
    useInterrupt({
        handler: async ({ result, event, resolve }) => {
            const { department } = await getUserByEmail(userEmail)
            if (event.value.accessDepartment === department || department === 'admin') {
                // Following the resolution of the event, we will not proceed to the render method
                resolve({ code: 'AUTH_BY_DEPARTMENT' })
                return;
            }

            return { department, userId }
        },
        render: ({ result, event, resolve }) => (
            <div>
                <h1>Request for {event.value.type}</h1>
                <p>Members from {result.department} department cannot access this information</p>
                <p>You can request access from an administrator to continue.</p>
                <button
                    onClick={() => resolve({ code: 'REQUEST_AUTH', data: { department: result.department, userId: result.userId } })}
                >
                    Request Access
                </button>
                <button
                    onClick={() => resolve({ code: 'CANCEL' })}
                >
                    Cancel
                </button>
            </div>
        )
    });
    // ...

    return <div>{/* ... */}</div>
}
```
