LangGraph clicked when I stopped thinking in chains
In late February a client asked me to automate first-pass triage on their support queue. Around 900 tickets on a weekday, one SaaS product, four support people visibly drowning. I'd built plenty of LangChain pipelines by then, so I did the obvious thing and reached for a chain.
The happy path took two days. Classify the ticket, draft a reply, run it past a policy check, send. Four runnables piped together, and honestly the LCEL read well.
Then we got to escalation. Anything touching billing, and anything the classifier scored under 0.62 confidence, had to go to a human. The human's decision had to flow back in so the ticket could still close through the normal path. I spent the better part of two weeks trying to express that with RunnableBranch and increasingly nested conditionals. It demoed fine. It collapsed the first time a ticket needed to bounce from the policy check back to drafting a second time.
A chain can't loop back, and no amount of clever nesting changes that.
a chain moves data, a graph moves control
What clicked for me was seeing the two as different species, not different sizes of the same thing. A chain is a pipeline: data enters one end, transformations happen in a fixed order, a result falls out. A graph is a state machine: there's one typed state object, every node reads it and returns a partial update, and edges decide who runs next based on what the state says right now.
That last clause is the whole trick. In a chain, control flow got decided once, when I wrote the pipe. In a graph, control flow is re-decided after every node: exactly what triage needed, because the same ticket can take a different route at step four depending on what step three learned.
class TriageState(TypedDict):
ticket: str
category: str
confidence: float
draft: str
attempts: int
That schema is doing real work. It's the contract every node has to honor, and designing it is now the first thing I do on any graph, not an afterthought.
the paper test
Before any graph code, I drew the flow on the back of a sprint printout. Five boxes: classify, draft, policy_check, escalate, close. One branch out of classify for billing and low confidence. One arrow from policy_check back to draft, labeled max two retries. Twenty minutes, most of them spent arguing with myself about where the human hand-off belonged.
If you can't draw your agent as boxes and arrows on one page, LangGraph won't rescue you — and if you can, the code is mostly transcription.
Turning the drawing into code took one afternoon: add_node five times, add_conditional_edges twice, compile. The two weeks of chain contortions produced nothing I kept. The paper drawing survived into the project docs, and four months later it's still how new people learn the system.
where the loop-with-tools model breaks
I want to be fair to the model I abandoned, because a hand-rolled while loop with tool calls is a genuinely good architecture: the shape I described in my agent architecture notes, with the model picking tools the way LangChain exposes them. It breaks down on three specific things.
- branching that's a business rule, not a model choice: billing goes to a human whether or not the model agrees
- cycles that need limits enforced outside the model, like my two-retry drafting loop
- resume: a run that pauses for a human on Friday and continues Tuesday can't live inside one process
Triage hit all three. The chain fight was doomed before it started; I just didn't have the vocabulary yet.
when a while loop still wins
The honest counterweight: for a single conversation with tools that finishes in one process, a while loop is about sixty lines and I can step through it in a debugger with nothing between me and the stack trace. LangGraph puts a runtime between you and your code, and that costs real debugging comfort. My breakpoints land inside framework frames more often than I'd like. I've written separately about where it's overkill.
My rule since March: if the drawing has no branch and no arrow pointing backwards, I don't build a graph. The triage system shipped in April with the same five boxes from February, and that drawing is most of why I trust it.