When LangGraph is overkill (a love letter and a warning)
I like LangGraph more than a principal engineer should probably admit in public. The ticket triage graph I keep writing about has survived six weeks of production traffic, a schema change, and one inverted edge, and I'd build it the same way again tomorrow. Which is exactly why the warning half of this piece took longer to write than the love-letter half.
The warning: most agent code doesn't need a graph.
After the triage launch went well, I got graph-happy. For about a month everything I touched turned into nodes and edges, the way everything turns into classes the year you discover object-orientation. This article is the correction.
the four questions
Before the graph comes out now, I ask four questions about the requirements as they exist today, not as I imagine them in some richer future:
- Does state need to survive a process restart, or a human coming back tomorrow?
- Does anyone need to pause a run mid-flight and approve something?
- Does the flow genuinely loop back on itself with an exit condition?
- Do parallel branches need to join and reconcile their results?
Four nos means a while-loop with function calls, not as a compromise but as the better design. It's build-vs-buy-thin-harness applied to one specific framework, and the same instinct the ponytail rule tries to force: the laziest structure that actually works.
the one I ported off
Now the confession. Our internal release-notes generator (fetch the week's merged PRs, cluster them, summarize each cluster, format the digest) ran on LangGraph for close to a month, purely because I built it while the triage hammer was still warm. It's strictly linear. It runs for roughly 90 seconds in CI every Thursday morning and either finishes or gets rerun. No persistence, no approvals, no loops. The fan-out I imagined for the summarization step never materialized; eight clusters summarized sequentially take about 40 seconds and nobody has ever cared.
On a Tuesday in late June I ported it to plain Python in one afternoon. It's now 118 lines, four functions and a for-loop. Stack traces end in my code instead of threading through a runtime. The dev loop is import, run, read. Our June intern read the entire file in one sitting and then, unprompted, fixed a bug in it. The dependency footprint dropped from the whole langchain constellation to two packages.
Nothing got worse. That stung a little.
The framework tax is real, but so is the artisanal-infrastructure tax, and the second one compounds quietly.
the one I regret
The mirror image, for balance. Last autumn, before I'd internalized the langgraph-mental-model, I built a supplier-compliance crawler as a while-loop: fetch documents, retry failures, flag anything ambiguous for a human. The requirements then arrived one at a time. Resume after a crash: I hand-rolled JSON snapshots to disk. Pause for approval: I invented a "waiting" status column and a cron job that polls it. An audit trail: an append-only log file with its own little parser.
By December I'd spent three weekends building a bad checkpointer, a bad interrupt, and a bad event stream. Every one of the four questions was a yes, and I'd answered each with duct tape. That project should have been a graph from the first commit, and my while-loop purism cost real Saturdays.
what the graph costs when it's idle
The tax, itemized honestly: a dependency constellation that moves fast enough that upgrades cost a morning; debugging that sometimes routes through runtime internals before it reaches your function; onboarding that requires teaching state, reducers, checkpoints, and thread IDs before anyone can review a diff. When the graph is earning (durable state, interrupts, controlled cycles), that tax is cheap. When it's idle, you pay all of it for nothing.
The checklist has one real weakness: requirements drift. The release-notes generator may someday need an approval gate, and then I'll port it back. I accept that risk, because porting 118 lines is an afternoon and carrying an unused framework is forever.
Love the graph. Ask the questions first.