LangGraph is the orchestration framework we reach for most often when building agentic systems at Eazyware. Not because it's the only option — LlamaIndex's workflows, custom state machines, and several commercial platforms all work — but because LangGraph hits a balance of control, clarity, and ecosystem that fits how we build. This post documents the five patterns we use in essentially every non-trivial LangGraph deployment.
Pattern 1: State-based routing
LangGraph's core abstraction is a state dict that flows through nodes. Routing happens on state, not on raw model outputs. Every decision point reads state, runs logic, writes state, and returns. This makes flow reasoning tractable — you can read the graph and trace what determines each branch.
Implementation pattern: state is a typed dict with explicit fields. Each node reads specific fields, writes specific fields, and is documented with its dependencies. Routing functions return the next node name based on state. The result: graphs that are readable, testable, and debuggable.
Pattern 2: Human-in-loop checkpoints
For high-stakes actions — sending emails, making purchases, modifying records — insert an explicit human approval node. The graph pauses, surfaces the proposed action for review, and only proceeds on approval. LangGraph's checkpointer makes this trivial: interrupt before a node, resume after approval.
When to use: any action with irreversible or regulatory impact. Compliance-heavy domains (legal, medical, financial) should default to human-in-loop for all state-changing actions. Fast-iteration domains (research, drafting) can skip it for most actions.
Pattern 3: Retry with reflection
When a node fails — tool error, validation failure, low-confidence output — don't just retry with the same input. Retry with an added reflection step: the model reads the failure and proposes a changed approach for the next attempt. This catches a lot of easy-to-fix errors that plain retries would just reproduce.
Implementation: a retry node that (on failure) calls the model with the failure context and asks 'what went wrong and what should we try differently?' The retry then uses the revised plan. Cap at 2-3 retries — beyond that, escalate to human.
Pattern 4: Parallel tool execution
When the model decides to call multiple independent tools, execute them in parallel rather than sequentially. For read-only tools (API lookups, database queries, web search), this is almost always safe and cuts latency significantly.
LangGraph's Send API enables fan-out. Our rule: tools flagged as 'read-only' execute in parallel; tools flagged 'write' or 'state-changing' run sequentially. This catches issues where the LLM proposes conflicting writes in the same turn — serialized execution is safer.
Pattern 5: Guard nodes
Before the graph proceeds to a risky action, route through a guard node that validates prerequisites. 'Before sending the email, validate that the recipient address is in the user's approved list.' 'Before updating the record, check that the user has permission.' The guard is code, not LLM — it's deterministic, fast, and unambiguous.
Guards prevent a class of failures where the LLM proposes valid-looking but actually-invalid actions. Guards catch these before execution. They're boring and invisible when they work, and extremely visible when they don't exist.
Combining patterns
A production LangGraph system typically combines all five. State-based routing as the skeleton. Guard nodes before any write. Human-in-loop for irreversible writes. Retry-with-reflection wrapping flaky tools. Parallel execution where tools allow. The combination yields graphs that are readable, debuggable, and reliable.
Observability for LangGraph
LangGraph integrates with LangSmith out of the box, and with Langfuse through simple wrappers. Either gives you full per-node traces: inputs, outputs, latency, cost. See our observability stack post for choosing between them.
LangGraph antipatterns
- Unbounded loops. A graph that can loop back to itself without a step counter eventually does, forever. Always add max-iteration guards.
- State explosion. Dumping entire tool outputs into state blows up the context window. Summarize, then store.
- Leaking state between runs. Checkpointer misconfiguration causes state to persist across unrelated invocations. Test with fresh sessions.
- Hardcoded prompts in nodes. Prompts should be externalized, versioned, and testable. See prompt testing post.
Closing
LangGraph rewards discipline. The patterns above are the discipline: state-based routing for clarity, human-in-loop for safety, retry-with-reflection for robustness, parallelism for speed, guards for safety. Apply them by default. Your graphs will be more reliable and easier to maintain. See agents in production for what breaks when these patterns are missing.