Command.PARENT handoffs, and building a mini-wave
A node inside a subgraph can route in the parent graph by targeting Command.PARENT.
import { Command } from "@langchain/langgraph";
const reviewNode = (state) => {
if (state.verdict === "reject") {
return new Command({
update: { feedback: state.notes },
goto: "implement", // a node in the PARENT graph
graph: Command.PARENT,
});
}
return new Command({ goto: "done", graph: Command.PARENT });
};That is how a rejection loops back to the implementer: the reviewer subgraph reaches up and routes the parent. Without Command.PARENT, goto only sees nodes inside the same subgraph.
Because the reviewer routes with a Command, its destinations must be declared in an ends array, and the placement is the part that catches people: the array goes on the parent’s addNode for the subgraph, naming nodes in the parent’s namespace.
const supervisor = new StateGraph(SupervisorState)
.addNode("implement", implementer)
.addNode("review", reviewer, { ends: ["implement", "done"] })
.addNode("done", doneNode)
.addEdge(START, "implement")
.addEdge("implement", "review")
.compile();Putting ends on the reviewer subgraph’s own addNode instead fails differently, with Found edge ending at unknown node \implement`, because implementis not a node inside the reviewer. Omitting it entirely fails at the parent's.compile()withUnreachableNodeError: Node `done` is not reachable`.
Try it yourself
Why Command.PARENT is required for the loop-back
Why does the reviewer subgraph need Command.PARENT to loop back to the implementer, rather than a plain goto: "implement"?
Show answer
Correct answer: D — goto only resolves nodes in the reviewer's subgraph, but "implement" is in the parent graph
goto resolves within the current graph's own node namespace by default. "implement" doesn't exist inside the reviewer subgraph -- it's a node in the parent supervisor graph. Command.PARENT tells the runtime to resolve and route within the parent's graph instead.
A bug subgraph isolation prevents
Answer from the mini-wave rather than in the abstract. A named safety property is only worth anything if you can name what it stops.
Name a specific bug that subgraph state isolation prevents.
Reveal answer
Without isolation, the implementer's internal scratch state (e.g. draft code, intermediate reasoning, retry counters specific to implementation) could leak into and pollute the reviewer's state, or vice versa -- for example the reviewer might accidentally see or be influenced by the implementer's internal retry count, conflating it with its own review-rejection count, corrupting the "loop back exactly once" guard logic.
Build the mini-wave: supervisor, implementer, reviewer
Build a hand-rolled supervisor dispatching an implementer subgraph to a reviewer subgraph with a bounded fix loop.
- Create the build under ~/dev/groundschool/builds/m7-mini-wave.
- Build an implementer subgraph (its own StateGraph, compiled) that produces some output artifact.
- Build a reviewer subgraph that inspects the implementer's output and returns a verdict (approve/reject) plus feedback notes.
- Build a supervisor StateGraph that adds both compiled subgraphs as nodes, dispatches work to the implementer first, then routes its output to the reviewer.
- In the reviewer's reject path, use Command.PARENT to goto the implementer node in the supervisor graph, carrying feedback.
- Declare the reviewer's destinations in an ends array on the SUPERVISOR's addNode for the reviewer subgraph, not on the subgraph's own node: naming a parent node inside the subgraph fails with 'Found edge ending at unknown node', and omitting ends fails with UnreachableNodeError.
- Add a retry-count guard so a second rejection escalates instead of looping again.
- Capture a transcript showing the Command.PARENT handoffs, and assert the implementer's internal keys never appear in the reviewer's state.
Reject loop is bounded and state stays isolated
Confirm the fix loop and isolation properties.
A reviewer rejection loops back to the implementer exactly once before escalating to a human, Command.PARENT handoffs are visible in the transcript, and the implementer's internal state keys never appear in the reviewer's state.