Core concepts

Interrupt and resume

Use native LangChain approvals and continue through a later authorized request.

Approval policy remains inside the graph. LangChain's humanInTheLoopMiddleware can interrupt a selected tool before it executes. Use an ordinary graph schema with a LangGraph checkpointer.

import { Agentdock, validateToolApprovalResume } from "@agentdock-ai/agentdock";
import { createAgent, humanInTheLoopMiddleware } from "langchain";

const graph = createAgent({
  model,
  tools: [sendEmail],
  checkpointer,
  middleware: [humanInTheLoopMiddleware({ interruptOn: { send_email: true } })],
}).graph;

const runtime = new Agentdock(graph, {
  interruptFormat: Agentdock.HITL,
  validateResume: validateToolApprovalResume,
});

Agentdock.HITL selects LangChain HITL presentation. Use Agentdock.OPAQUE to keep interrupts generic; that is the default. The equivalent strings ("langchain-hitl" and "opaque") remain supported. The optional validator checks the response envelope and the number of actions. It does not enforce the user's permissions. Supply your own validator when current application context changes the approval batch or policy.

When the stream emits interrupt.required, present the pending action to the authorized user. Make a new authorized request with the same server-derived threadId and the resume payload expected by the graph:

await runtime.pipe(response, {
  threadId: authorizedThreadId,
  context: trustedContext,
  resume: { decisions: [{ type: "approve" }] },
});

For multiple pending interrupts, target native IDs:

await runtime.pipe(response, {
  threadId: authorizedThreadId,
  context: trustedContext,
  resume: { [interruptId]: { decisions: [{ type: "approve" }] } },
});

Native checkpoint tasks hold pending work. No serving state fields are required. Every invocation gets a fresh run ID; reduce continuation events into the existing client state, or use runtime.getResumeState(threadId) to hydrate a fresh client. Load conversation history separately with runtime.getMessages(threadId).

Continue a static interruptBefore or interruptAfter breakpoint with { threadId: authorizedThreadId, continue: true }. Dynamic interrupts require resume. The run.paused event marks a static breakpoint or a continuation that still has pending interrupts.

Authorize every approval in the application. Use a durable LangGraph saver for process restarts or multiple server instances, and manage its lifecycle yourself.