# Diagrams — Deep-Dive DD-10: LangGraph — Graph-Based State Machines

**Deep-Dive**: DD-10 — LangGraph: Graph-Based State Machines
**Diagram count**: 5
**Tool**: Mermaid (primary). Each diagram validated in [Mermaid Live Editor](https://mermaid.live).

---

## Diagram 1 — The Graph IS the Loop (the architectural divergence)

**Type**: State-machine flow
**Purpose**: The single orientation diagram. Shows LangGraph's defining thesis: the loop is an explicit graph (nodes as functions, edges as transitions) rather than an implicit while-true. Every other harness hides its loop; LangGraph makes the loop the entire product. This is the largest architectural divergence in the roster.
**Reading the diagram**: Five nodes (plan, execute, verify, interrupt, done). Conditional edges express branching in code. The `interrupt()` node is structural HITL — the model cannot route around it because the model does not control the edges. Compare to Pi (DD-01), where the loop is minimal and implicit.

```mermaid
flowchart TB
  START((START)) --> PLAN[Node: PLAN<br/>function returns state update]
  PLAN -->|checkpoint| EXEC[Node: EXECUTE<br/>function returns state update]
  EXEC -->|checkpoint| VERIFY[Node: VERIFY<br/>function returns state update]

  VERIFY -->|conditional edge: pass| DONE[Node: DONE]
  VERIFY -->|conditional edge: fail| EXEC
  VERIFY -->|conditional edge: needs human| HITL["interrupt()<br/>pause INDEFINITELY"]

  HITL -->|human approves| DONE
  HITL -->|human rejects| RECOVER[Node: RECOVER]

  THESIS["THE LOOP IS THE GRAPH.<br/>Every transition is explicit in code.<br/>Every node boundary is a checkpoint.<br/>The model cannot route around interrupt()<br/>because the model does not control the edges."]

  DONE --> THESIS

  style START fill:#14141f,stroke:rgba(94,234,212,0.5),color:#e4e4e8
  style PLAN fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style EXEC fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style VERIFY fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style DONE fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style HITL fill:#08080c,stroke:rgba(240,168,104,0.5),color:#f0a868
  style RECOVER fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style THESIS fill:#08080c,stroke:#5eead4,stroke-width:1.5px,color:#5eead4
```

---

## Diagram 2 — Super-Step Checkpoints (Module 8 reference, the finest granularity)

**Type**: Linear pipeline with crash-resume
**Purpose**: Shows why LangGraph earns 5/5 on Module 8 (State/Checkpointing). State is serialized at EVERY node boundary — these boundaries are super-steps. A crash resumes from the last completed super-step, not from scratch. This is the finest-grained, most resumable state model in the roster.
**Reading the diagram**: Four nodes, each followed by a checkpoint. The crash between nodes 2 and 3 resumes from the node-2 checkpoint — not from the start, not from a session-level approximation. The annotation names the Module 8 reference property.

```mermaid
flowchart LR
  N1[Node 1<br/>plan] --> CK1((super-step<br/>checkpoint))
  CK1 --> N2[Node 2<br/>execute]
  N2 --> CK2((super-step<br/>checkpoint))
  CK2 --> N3[Node 3<br/>verify]
  N3 --> CK3((super-step<br/>checkpoint))
  CK3 --> N4[Node 4<br/>done]

  CRASH["CRASH between<br/>N2 and N3"]
  CRASH -.->|resume from N2 checkpoint| CK2

  NOTE["Module 8 reference (5/5).<br/>Every node boundary is a checkpoint.<br/>Granularity = the STEP, not the session.<br/>Resume from where you were,<br/>not approximately where you were."]

  CK3 --> NOTE

  style N1 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style N2 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style N3 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style N4 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style CK1 fill:#14141f,stroke:#5eead4,color:#5eead4
  style CK2 fill:#14141f,stroke:#5eead4,color:#5eead4
  style CK3 fill:#14141f,stroke:#5eead4,color:#5eead4
  style CRASH fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
  style NOTE fill:#08080c,stroke:#5eead4,stroke-width:1.5px,color:#5eead4
```

---

## Diagram 3 — Structural interrupt(): HITL the model cannot route around

**Type**: Two-track comparison
**Purpose**: The load-bearing HITL diagram. Shows why `interrupt()` is the cleanest HITL primitive in the roster: it is structural (in the graph topology), not behavioral (a system-prompt instruction). A prompt-injected model cannot route around the approval because the model does not control the edges — the graph definition does.
**Reading the diagram**: Top = the naive approach (system-prompt instruction). The model can forget, defer, or be injected into skipping the ask — the guard is behavioral, inside the agent's trust boundary (the same NemoClaw lesson from DD-09). Bottom = LangGraph's structural approach. The edge from `propose` to `execute` goes THROUGH an `interrupt()` node. The model cannot skip the node because the model does not choose the path; the graph does.

```mermaid
flowchart TB
  subgraph NAIVE["NAIVE HITL — system-prompt instruction (behavioral)"]
    direction LR
    A1[Agent proposes] --> A2["System prompt:<br/>'ask for approval'"]
    A2 --> A3{"Model asks?"}
    A3 -->|forgets / defers / injected| A4["APPROVAL SKIPPED<br/>action executes ungoverned"]
    A3 -->|remembers| A5[Human approves]
  end
  subgraph STRUCT["LANGGRAPH HITL — interrupt() node (structural)"]
    direction LR
    S1[Agent proposes] --> S2["Node: PROPOSE"]
    S2 --> S3["Node: interrupt()<br/>EDGE GOES THROUGH HERE"]
    S3 --> S4{Human decides}
    S4 -->|approves| S5[Node: EXECUTE]
    S4 -->|rejects| S6[Node: RECOVER]
  end

  VERDICT["STRUCTURAL beats BEHAVIORAL.<br/>The naive guard is inside the agent's trust boundary<br/>(the model can skip it). The structural guard is in the<br/>graph topology — the model does not control the edges.<br/>Same principle as NemoClaw (DD-09): enforcement<br/>outside the agent's reach."]

  A4 --> VERDICT
  S5 --> VERDICT

  style NAIVE fill:#14141f,stroke:rgba(240,128,128,0.5),color:#e4e4e8
  style STRUCT fill:#14141f,stroke:rgba(94,234,212,0.5),color:#e4e4e8
  style A1 fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style A2 fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style A3 fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style A4 fill:#08080c,stroke:rgba(240,128,128,0.4),color:#f08080
  style A5 fill:#08080c,stroke:rgba(240,168,104,0.3),color:#f0a868
  style S1 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style S2 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style S3 fill:#08080c,stroke:#5eead4,color:#5eead4
  style S4 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style S5 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style S6 fill:#08080c,stroke:rgba(240,168,104,0.3),color:#f0a868
  style VERDICT fill:#08080c,stroke:#5eead4,stroke-width:1.5px,color:#5eead4
```

---

## Diagram 4 — When the graph is right vs wrong (the decision rule)

**Type**: Two-column decision matrix
**Purpose**: The load-bearing judgment diagram. Shows the decision rule for when LangGraph is the right choice vs the wrong choice. The graph is right when the process is the product (regulated, auditable, multi-approval). The graph is wrong when the model could figure out the process (the "graph fights the model" anti-pattern). Match the framework to the use case.
**Reading the diagram**: Left column = use cases where the graph is right (process is the product). Right column = use cases where the graph is wrong (process is emergent). The footer is the decision rule: if you can draw the workflow as a flowchart before you run it, use a graph. If the workflow is "the model figures it out," use a loop.

```mermaid
flowchart TB
  subgraph RIGHT["GRAPH IS RIGHT — the process is the product"]
    R1["Regulated workflows<br/>(every step auditable)"]
    R2["Compliance pipelines<br/>(auditor traces the path)"]
    R3["Multi-approval flows<br/>(different humans, different stages)"]
    R4["Declared multi-agent<br/>(LangGraph subgraphs)"]
  end
  subgraph WRONG["GRAPH IS WRONG — the process is emergent"]
    W1["Open-ended coding<br/>(model figures out the steps)"]
    W2["Exploratory research<br/>(path discovered at runtime)"]
    W3["Creative tasks<br/>(no fixed workflow exists)"]
    W4["Emergent multi-agent<br/>(CrewAI-style handoffs)"]
  end

  RULE["DECISION RULE:<br/>If you can draw the workflow as a flowchart<br/>BEFORE you run it, use a graph.<br/>If the workflow is 'the model figures it out',<br/>use a loop (Pi, DD-01) or an implicit-graph harness."]

  RIGHT --> RULE
  WRONG --> RULE

  style RIGHT fill:#14141f,stroke:rgba(94,234,212,0.5),color:#e4e4e8
  style WRONG fill:#14141f,stroke:rgba(240,128,128,0.5),color:#e4e4e8
  style R1 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style R2 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style R3 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style R4 fill:#08080c,stroke:rgba(94,234,212,0.3),color:#5eead4
  style W1 fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style W2 fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style W3 fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style W4 fill:#08080c,stroke:rgba(240,128,128,0.3),color:#f08080
  style RULE fill:#08080c,stroke:#5eead4,stroke-width:1.5px,color:#5eead4
```

---

## Diagram 5 — LangGraph as n8n (the graph literally IS a node graph)

**Type**: n8n workflow JSON
**Purpose**: The executable expression of the thesis. Shows the LangGraph pattern as an n8n workflow you can import and run — nodes as functions, edges as transitions, super-step checkpoints between every node, and a conditional edge routing to an `interrupt()` HITL node. This is the artifact for students who want to see the graph as a runnable thing.
**Reading the diagram**: Read the n8n workflow left-to-right. Each "Set" node is a graph node (a function that returns a state update). Between nodes, the super-step checkpoint fires (Module 8). The conditional edge (IF node) routes to DONE or to the interrupt() HITL node. The interrupt() node pauses indefinitely — Module 6.2 realized structurally. Paste the JSON into n8n to import.

```json
{
  "name": "LangGraph State Machine — DD-10",
  "nodes": [
    { "parameters": { "assignments": { "assignments": [{ "id": "s", "name": "state", "value": "planning", "type": "string" }]}, "options": {} }, "id": "plan", "name": "Node: PLAN", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [160, 200], "notesInFlow": true, "notes": "The graph IS the loop. Each node is a function; each edge is a transition. Explicit, testable. Module 1: 5/5 — the loop made visible." },
    { "parameters": { "assignments": { "assignments": [{ "id": "ck1", "name": "checkpoint_after_plan", "value": true, "type": "boolean" }]}, "options": {} }, "id": "ckpt1", "name": "Super-step Checkpoint", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [380, 200], "notesInFlow": true, "notes": "Module 8 reference (5/5): state serialized at EVERY node boundary. Crash resumes from the last completed super-step, not from scratch. Finest-grained in the roster." },
    { "parameters": { "assignments": { "assignments": [{ "id": "s", "name": "state", "value": "executing", "type": "string" }]}, "options": {} }, "id": "exec", "name": "Node: EXECUTE", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [600, 200] },
    { "parameters": { "assignments": { "assignments": [{ "id": "ck2", "name": "checkpoint_after_exec", "value": true, "type": "boolean" }]}, "options": {} }, "id": "ckpt2", "name": "Super-step Checkpoint", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [820, 200] },
    { "parameters": { "conditions": { "combinator": "any", "conditions": [{ "id": "hitl", "leftValue": "={{ $json.body.needs_human || false }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true" } }] } }, "id": "branch", "name": "Conditional Edge: needs human?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [1040, 200], "notesInFlow": true, "notes": "Conditional edge = branching in code. verify → pass? done. verify → fail? back to execute. verify → needs human? interrupt(). The model does NOT control these edges — the graph definition does." },
    { "parameters": { "content": "={{ JSON.stringify({ action: 'interrupt() — paused indefinitely for human', node: 'execute→verify boundary', note: 'LangGraph interrupt(): the structural HITL primitive (Module 6.2). The model cannot route around this because the model does not control the edges.' }) }}", "options": {} }, "id": "hitl", "name": "interrupt() — HITL", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "position": [1260, 300], "notesInFlow": true, "notes": "interrupt() pauses here INDEFINITELY. Human decides; graph resumes from checkpoint. Module 6.2 (HITL) + Module 8 (checkpointing) combined. Structural, not behavioral — same principle as NemoClaw (DD-09): enforcement outside the agent's reach." },
    { "parameters": { "content": "={{ JSON.stringify({ state: 'done', checkpoints: '3 super-steps', graph: 'plan→execute→verify→done' }) }}", "options": {} }, "id": "done", "name": "Node: DONE", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "position": [1260, 100] }
  ],
  "connections": { "Node: PLAN": { "main": [ [ { "node": "Super-step Checkpoint", "type": "main", "index": 0 } ] ] }, "Super-step Checkpoint": { "main": [ [ { "node": "Node: EXECUTE", "type": "main", "index": 0 } ] ] }, "Node: EXECUTE": { "main": [ [ { "node": "Super-step Checkpoint", "type": "main", "index": 0 } ] ] }, "Super-step Checkpoint": { "main": [ [ { "node": "Conditional Edge: needs human?", "type": "main", "index": 0 } ] ] }, "Conditional Edge: needs human?": { "main": [ [ { "node": "Node: DONE", "type": "main", "index": 0 } ], [ { "node": "interrupt() — HITL", "type": "main", "index": 0 } ] ] } },
  "settings": { "executionOrder": "v1" }, "meta": { "templateCreatedBy": "Harness Engineering Master Course — DD-10 LangGraph" }
}
```

---

## Validation notes

- All diagrams use the course design system colors: `#14141f` panel fill, `#08080c` deep background for nested nodes, `#5eead4` teal accent for the graph/capability surface and principle nodes, `#f0a868` warn for the HITL/interrupt transitions and recover paths, `#f08080` danger for the naive/ungoverned surface (the skipped-approval anti-pattern). `#e4e4e8` / `#9494a0` for text.
- Paste each Mermaid block into [Mermaid Live Editor](https://mermaid.live) to render. All use stable Mermaid syntax (`flowchart TB/LR`, `subgraph`) supported in current Mermaid (v10.4+).
- The teal/warn/danger color coding is consistent: teal = the governed/explicit-graph surface (LangGraph's contribution); warn = the HITL/interrupt transitions and recover paths; danger = the naive/ungoverned surface (the skipped-approval anti-pattern, the graph-is-wrong use cases). This mapping is load-bearing — it lets the reader track which side of the explicit-graph axis a node belongs to.
