Build pipeline · decision

Two paths, and neither one survives a restart

The LangGraph engine was adopted to get crash recovery and time-travel debugging. A full read of both pipelines, every node and every phase, found neither engine has either one.

This page is what the code actually does, what LangGraph actually buys, and the four phases we would run.

01 What we found

Three defects, and one of them is hitting production today

All three sit on the default engine. LangGraph is switched off and defaults to off, so none of this is a LangGraph problem.

A build waiting for approval hangs forever production

The build loop waits on a human by polling the database once a second. If the process dies while it is waiting, the row stays awaiting_approval and nothing is left polling it.

Approving later writes the flag into a row no one is watching. The build never moves. The stale-build repair cannot rescue it either, because that repair deliberately skips awaiting_approval. The user sees a progress bar that never advances.

The progress stream lives in one process structural

Build events go into an in-memory queue. The server runs two workers, so a stream request can land on the worker that is not running the build.

When that happens the client attaches to an empty queue and receives nothing but keepalives until it times out. A redeploy does the same thing to every connected stream.

Neither engine can resume both engines

The build loop always starts at phase zero. Completed phase text is written to the database after every phase, and nothing ever reads it back.

Today the only recovery is terminal. A build whose process died gets marked failed with “Build process lost — server was restarted.”

The part that reframes everything

Crash recovery was the headline reason to adopt LangGraph. It is not a LangGraph feature. The data needed to resume is already persisted after every phase — the position in the sequence and the text of everything completed. The resume point is roughly thirty lines of reading it back, and that is true of either engine.

02 What the other engine is missing

The LangGraph engine is a prompt-level clone

It reuses the prompts and two parsers. It reimplements almost nothing else. The graph itself has eight nodes, two conditionals, and both conditionals mean “stop” — there are no loops. It is a straight line wearing a graph’s clothes.

CapabilityDefault engineLangGraph engine
Prompt cachingpresentmissing
Fitness approval gatepauses on failskips the gate
Deploy gatespresentmissing
Dev-spec output typepresentmissing
Gap verdict cross-checkpresentmissing
Pattern matchingpresentmissing
Organization contextpresentmissing
Cancel mid-buildpresentmissing
Phase state saved per phaseafter each phaseonly at the end
Approval writes to the databasepresentnever does
Tests that can failcore pathsnone execute
On the tests

Twenty-four tests exist for the LangGraph engine. Every one that executes a node stubs out the model call, so the line that raises on a missing credential is never reached. All twenty-four pass against an engine that cannot complete a single phase. A test suite that cannot fail is worse than no test suite, because it reads as coverage.

03 What LangGraph actually buys

One of the three things is real, and it is not the one we bought it for

Split the pitch into its parts. They have different answers.

Either engine

Resume

Survive a restart and continue. Presented as the framework’s gift. It is not.

The state is already persisted. Reading it back is about thirty lines, and it is the same thirty lines whether the orchestrator is a loop or a graph.

Genuinely the framework’s

Time travel

Replay from any historical checkpoint, change an input, run forward. Writing that yourself means versioning every phase output and building a replay path.

The honest question is whether you would use it. For a ten-minute build, “re-run from phase three with a better prompt” is also what the rebuild button does badly.

Real, but not needed here

Expressiveness

Typed state, branching edges, loops, parallel fan-out with partial-failure handling. This is where the framework earns its keep.

The build pipeline has seven phases in a straight line and two conditionals that both mean stop. A loop is the correct tool for a straight line.

Where it flips

The governance archetype is the one place a graph is right. Its meeting monitor runs for the life of a project: it ingests transcripts, extracts proposed changes, routes each one through a human, applies what is approved, and loops. That is a long-running, resumable process with human gates in the middle of it — the exact shape the current pipeline cannot express. The strongest argument for keeping the engine is a plan that already assumes it and never says so.

04 The plan

Fix the seam first, then decide with a number

Four phases. The first one does not touch LangGraph at all.

THE DECISION RUN Fix the default engine Legacy only. ~30 lines. Make the comparison Thread the key. Branch the stream. Decide Cost and completion rate. Close the gap Graph wins. Eleven capabilities. Delete the engine Graph loses. Toggle and migration go. survives a restart one build each graph wins graph loses
The first phase does not touch LangGraph. The decision waits until there is a number to make it with.
0

Make the default engine survivable

Legacy engine only · no LangGraph changes

Three small changes to the engine that is actually running in production.

  • Resume the build loop from the phase text already in the database
  • Make approval start a runner when the original one is gone
  • Catch the progress stream up from what is already saved
Done when A build killed at phase three finishes after a reconnect, and an approval on an abandoned build moves it forward instead of hanging.
1

Make the comparison possible

Both engines · small changes

Right now there is nothing to compare, because one engine cannot run.

  • Hand the graph the credentials it is currently dropping
  • Route the progress stream by which engine ran the build
  • Fix the event payloads so the interface can read them
  • Make the tests fail when the engine is broken
Done when One identical build has completed on each engine, with completion rate, wall-clock, and token cost recorded for both.
2

Decide, with a number

The fork

Does the graph complete builds at comparable cost, and does a durable checkpoint deliver something the thirty lines do not?

If yes, close the gap in phase three. If no, delete the engine, the toggle, and its migration — and correct the published pages that promise a capability neither engine had.

Done when One engine is left, or one engine is clearly better. Not two half-engines.
3

Close the gap — only if the graph wins

Conditional

Eleven capabilities, ordered by blast radius. Caching first, because without it the graph costs roughly a third more in input tokens than the engine it is replacing.

Done when The graph engine does everything the default engine does, and the default engine can be retired.
The plan does not touch

Scoring, the phase prompts, the interface contract, the database enums, or the route surface. The first three phases are small, independently revertible changes to paths that already work.

Scoring formula Phase prompts Event vocabulary Status enums HTTP routes Shared parsers
© 2025–2026 Charlie Fuller All AESOP explainers →