Tracing graphs: watching state mutate is the real debugger
For a normal program the debugger is a breakpoint and a stack trace. For a LangGraph app the debugger is a diff: the change in state from one node to the next, read like a git history of a single request. Once I started thinking that way, a class of bug that used to eat afternoons started costing minutes. One in particular cost me a full afternoon before the lesson landed, so let me start there.
the afternoon I blamed the model
A ticket in the triage pipeline kept coming out under-researched. The final draft cited one piece of evidence when the enrichment steps had plainly gathered three. My first instinct was that the model had gone lazy, so I did the usual: sharpened the prompt, swapped a stronger model into the drafting node, added an instruction to use every piece of evidence available. Nothing moved. Three hours gone. Then I stopped editing prompts and actually diffed the state between nodes, and the evidence list was length three going into node four and length one coming out. The model was innocent. My state plumbing had been eating its lunch.
reducers are where the bodies are buried
The bug was a reducer. In LangGraph every state field has one, a function that decides how a node's output merges into the running state. Message fields usually get an append reducer; forget to set one and the field silently overwrites instead. My evidence field had the default, so each node that touched it clobbered the previous list rather than extending it. No exception, no warning, just a list that quietly reset to whatever the last writer put there. You can't see that in the output. You can only see it in the diff, which is why node-level state tracing isn't a nice-to-have on a graph. It's the primary instrument.
A stack trace tells you where the code stopped; a state diff tells you where the meaning went wrong.
LangSmith or roll your own
Two ways to get those diffs. LangSmith is the batteries-included route: point two environment variables at it and every node run shows up as a span in a waterfall, with inputs, outputs, and token counts, plus the eval and dataset tooling stacked on top. It's the fastest path to seeing state move, and the trade is per-trace pricing and your request payloads leaving your own network. The other route is OpenTelemetry-style: wrap each node in a span yourself, ship to a collector you run, keep everything in-house. You give up the model-shaped views LangSmith hands you for free and rebuild them, but nothing leaves your infrastructure and no per-trace meter is running. I've used both on the same graph in different quarters, and the choice came down to data residency and budget, not features.
what retention actually costs
Full traces are fat. Each one carries whole prompts, whole responses, and a complete state snapshot at every hop, and at a few thousand threads a day that piles up faster than you'd guess. It's the same write-amplification story as the checkpointer, aimed at your trace store instead. So I sample. Every errored thread traces at 100%, because those are the ones I'll actually open, and successful threads trace at roughly 10%. Large payloads get truncated before storage. That keeps the bill and the retention window honest without blinding me to the failures.
what I put alerts on
Traces are for reading after the fact; alerts are for the three things I never want to read about the morning after. Loop counts, because a cyclic graph that blows past its recursion limit is usually a routing bug spinning in place, and I page on any thread that crosses about twenty steps. Token spikes, because a single thread burning ten times the median spend means a prompt got poisoned with something huge. And dead-letter arrivals (any thread that lands on the error path), because one is a fluke and a cluster is an incident. That short list is the whole of my alerting surface, and it has caught more real production problems than every dashboard I've built; it pairs with the tests that catch the structural bugs before they ship. Tracing catches the ones that only surface on real traffic. Watch the state move; the model is usually telling the truth.