> ## Documentation Index
> Fetch the complete documentation index at: https://tryagentai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Use LangGraph interrupts

> Pause a LangGraph workflow and resume it from a TryAgent decision.

# Use LangGraph Interrupts

Use LangGraph interrupts when a graph reaches a decision that needs a human
before it can continue. The graph pauses with `interrupt()`, TryAgent routes the
decision to the right reviewer, and your callback resumes the same graph thread
with `new Command({ resume })`.

This guide follows LangGraph's official
[interrupts](https://docs.langchain.com/oss/javascript/langgraph/interrupts)
pattern:

* Compile the graph with a checkpointer.
* Invoke and resume with the same `{ configurable: { thread_id } }`.
* Call `interrupt()` inside the node that needs external input.
* Read the interrupt payload from `__interrupt__`.
* Resume with `new Command({ resume: ... })`.

## 1. Interrupt with a TryAgent payload

Keep the graph node deterministic up to the interrupt. Build the TryAgent
escalation payload inside the node, then let the caller publish it to TryAgent
after LangGraph has paused.

```typescript theme={null}
import {
  Annotation,
  END,
  MemorySaver,
  START,
  StateGraph,
  interrupt,
} from "@langchain/langgraph";
import type { EscalateInput } from "@tryagent/sdk";

type ApprovalResume = {
  escalationId: string;
  choice: "approve" | "reject";
  answeredBy?: string;
};

type TryAgentInterruptPayload = {
  kind: "tryagent_approval";
  policy: string;
  tryagentInput: EscalateInput;
};

const State = Annotation.Root({
  runId: Annotation<string>(),
  orderId: Annotation<string>(),
  status: Annotation<"approved" | "rejected" | undefined>(),
  escalationId: Annotation<string | undefined>(),
});

const graph = new StateGraph(State)
  .addNode("approval", (state) => {
    const decision = interrupt<TryAgentInterruptPayload, ApprovalResume>({
      kind: "tryagent_approval",
      policy: "orders.auth_doc",
      tryagentInput: {
        agentId: "order-review-agent",
        runId: state.runId,
        subject: {
          type: "order",
          id: state.orderId,
          label: `Order #${state.orderId}`,
        },
        question: "Approve this order authorization document?",
        evidence: ["Signature date is missing.", "Customer identity matched."],
        choices: [
          { id: "approve", label: "Approve" },
          { id: "reject", label: "Reject" },
        ],
        resume: {
          mode: "webhook",
          url: "https://api.example.com/tryagent/resume",
        },
      },
    });

    return {
      status: decision.choice === "approve" ? "approved" : "rejected",
      escalationId: decision.escalationId,
    };
  })
  .addEdge(START, "approval")
  .addEdge("approval", END)
  .compile({ checkpointer: new MemorySaver() });
```

<Warning>
  `MemorySaver` is useful for local examples. Use a durable checkpointer in
  production so paused graph state survives process restarts.
</Warning>

## 2. Publish the interrupt to TryAgent

Use one durable value for both systems:

* TryAgent `runId`
* LangGraph `thread_id`

When the graph pauses, the payload you passed to `interrupt()` is returned under
`__interrupt__`. Publish that payload to TryAgent.

```typescript theme={null}
import { INTERRUPT, isInterrupted } from "@langchain/langgraph";
import { TryAgent } from "@tryagent/sdk";

const tryagent = new TryAgent({
  apiKey: process.env.TRYAGENT_API_KEY!,
});

const runId = "order-review-ord_4821";
const config = { configurable: { thread_id: runId } };

const result = await graph.invoke(
  { runId, orderId: "ord_4821" },
  config,
);

if (isInterrupted(result)) {
  const activeInterrupt = result[INTERRUPT][0];
  const payload = activeInterrupt.value as TryAgentInterruptPayload;

  const escalation = await tryagent.escalate(
    payload.policy,
    payload.tryagentInput,
  );

  console.log({
    interruptId: activeInterrupt.id,
    escalationId: escalation.id,
    runId,
  });
}
```

## 3. Resume from the signed callback

When TryAgent calls your `resume.url`, verify the callback signature first with
[`webhooks.constructEvent`](/sdk/webhooks). Then resume the same LangGraph thread
with `new Command({ resume })`.

```typescript theme={null}
import { Command } from "@langchain/langgraph";

export async function POST(request: Request) {
  const event = await tryagent.webhooks.constructEvent({
    payload: await request.text(),
    signature: request.headers.get("x-tryagent-signature"),
    secret: process.env.TRYAGENT_WEBHOOK_SECRET!,
  });

  await graph.invoke(
    new Command({
      resume: {
        escalationId: event.escalationId,
        choice: event.choice,
        answeredBy: event.answeredBy,
      },
    }),
    {
      configurable: {
        thread_id: event.runId,
      },
    },
  );

  return Response.json({ ok: true });
}
```

The object passed as `resume` becomes the return value of the original
`interrupt()` call. In the first example, that means `decision.choice` and
`decision.escalationId` are available inside the `approval` node after resume.

## Rules for TryAgent integrations

* Use `runId` as the LangGraph `thread_id`.
* Keep interrupt payloads JSON-serializable.
* Do not perform non-idempotent side effects before `interrupt()` in the same
  node. If the node is re-entered on resume, that code can run again.
* Prefer publishing the TryAgent escalation from the caller after LangGraph has
  returned an interrupt.
* For sequential approvals, resume each interrupt as it appears. For parallel
  interrupts, pair resume values with the interrupt IDs returned by LangGraph.

See the runnable [examples](/examples) for complete stock research and refund
approval workflows using this pattern.
