scalable/ai/langgraph agent teams/lesson 07 cph / /
lesson 07 / 08 · 12 min · updated ·

human gates — interrupts and checkpoints

interrupt() pauses the graph mid-run; a checkpointer makes the pause survivable. approval gates as first-class graph citizens.

tl;dr. interrupt() pauses a run mid-node and checkpoints everything; a Command with resume continues it — minutes or days later, in a different process. that makes human approval a first-class node in the graph instead of a slack message and a prayer. if you take one thing from this course into production, take this lesson.

the pause button

everything so far ran start to finish. real agent systems can't: some actions are too expensive to be wrong about — merging code, sending a letter to a debtor, deleting anything. the naive fix is to have the agent ask in chat, which works until the run is unattended, which is the whole point of a team. the structural fix is a gate: a node where the graph stops, durably, until a human answers.

interrupt() — pause as a node, not a hack
import { interrupt, Command, MemorySaver } from "@langchain/langgraph";

// a gate is a node that pauses the run and waits for a human.
async function approvalGate(state: typeof PipelineState.State) {
  // interrupt() STOPS here. state is checkpointed; the run ends.
  // whatever you pass out is what the approver sees.
  const decision = interrupt({
    question: "ship this change?",
    summary: state.reviewSummary,
    diffUrl: state.prUrl,
  });

  // ...and when resumed, execution continues HERE, with the
  // human's answer as the return value.
  if (decision.approved) return { status: "approved" };
  return { status: "rejected", notes: [decision.reason] };
}

const graph = builder.compile({
  checkpointer: new MemorySaver(),   // postgres in production
});

resume is a second invoke

same thread_id, Command with resume — a day later
// day 1, 09:14 — the run hits the gate and stops.
const config = { configurable: { thread_id: "issue-482" } };
await graph.invoke(input, config);
// graph state: paused at approvalGate. process can exit.

// day 2, 21:37 — you open the laptop and approve.
// same thread_id is the whole trick: the checkpointer
// rehydrates the run exactly where it stopped.
await graph.invoke(
  new Command({ resume: { approved: true } }),
  config,
);
// approvalGate's interrupt() returns { approved: true },
// the node finishes, the graph continues to the next edge.

the part that feels like a trick until you trust it: nothing was running in between. no process waited, no websocket held. the run is rows in a checkpoint table, and the resume call rehydrates it. this is what makes the evening-review routine possible at all — agents work in the morning, gates accumulate during the day, and the human clears the queue at night. the pause is load-bearing for the whole operating model, not a safety afterthought.

where the gates go. the discipline i argued in the agentic sdlc post: two of them. what's worth building — a gate before the pipeline starts spending money — and what ships — a gate before anything irreversible. every gate between those two should be an agent gate (a reviewer that fails the run), not a human one. humans gate judgment; agents gate quality. a human gate on every step means you've built a very elaborate way to do the work yourself.

gates change time, and time changes design

a graph with human gates runs on human time — hours and days, not seconds. that has consequences. state must be resume-legible: a node that stashed context in a local variable instead of state loses it at the pause, so the annotation is the only memory you get to keep. the world moves while you wait — the pr the gate summarized on monday may not match main by wednesday, so re-validate staleness after the gate for anything that waited long. and rejection needs a path, not an exception: route it back into the pipeline with notes, the way a reviewer sends back a pr. approve-or-crash is not a review process.

when it breaks

all the pieces exist now: agents, teams, fan-out, and a pause button. the capstone puts them to work: the sdlc as a graph.

scalable labs·cvr 30091604·github·linkedin·hello@scalable.dk