Capstone: a writer-critic graph
A draft, a critique, a revision loop with a cap, a checkpoint after every step, and an approval before anything is published. Every idea in the course, once.
The shape of the workflow
The writer produces a draft. The critic scores it and writes down what is wrong. A router decides: revise, publish, or give up. The revise edge goes back to the writer with the critique in the state, and the attempt counter goes up.
That is a graph with one loop and two routes out. Everything the course covered has a place in it: state and reducers, a conditional edge, a counter, a checkpoint, and an interrupt before the action that cannot be undone.
A trace of one run: write produces draft one, attempts one. Critique scores 0.6 and says “needs an example”. The router returns revise. Write produces draft two with the note in the state, attempts two. Critique scores 0.8. The router returns publish, and the interrupt stops before it. Four steps, one loop.
The cap is the design
The cap is not a safety feature bolted on at the end; it is what makes the loop predictable. Three attempts and then a decision — publish the best draft, or escalate — is a workflow. “Revise until good” is not, because “good” may never arrive.
When the cap is reached, the state should say so and carry the best attempt. A caller can then decide to accept it, hand it to a person, or fail. The graph’s job is to make that decision visible.
A worked cap: attempts one and two score 0.6 and 0.75; attempt three scores 0.78. The router checks the cap first, so it returns give_up even though the score is close. The state carries status “gave up” and the best draft, which is the one with the highest score. A caller can publish it with a warning, send it to a person, or fail the request. The cap also makes the cost predictable: at most three writer and three critic calls per request.
The same workflow in LangGraph
builder.add_node("write", write)
builder.add_node("critique", critique)
builder.add_node("publish", publish)
builder.add_edge(START, "write")
builder.add_edge("write", "critique")
builder.add_conditional_edges("critique", route, {
"revise": "write",
"publish": "publish",
"give_up": END,
})
graph = builder.compile(checkpointer=checkpointer, interrupt_before=["publish"])The loop with a cap
Run it and read the trace. Then make the critic impossible to satisfy and confirm the graph gives up with the best attempt instead of looping.
Where the state earns its keep
The critic needs the draft. The writer, on the second pass, needs the critique and the previous draft. The router needs the attempt count and the score. All of it is state, and all of it is inspectable when something goes wrong.
The one thing that should not be in the state is the model’s reasoning. A long chain of thought re-sent on every node costs tokens and rarely helps the next step. Keep the conclusion and the critique, drop the transcript.
The state keys are the workflow: topic, draft, critique, attempts, score, and history. History lets give-up choose the best draft. The model’s reasoning is not in the state, because it is re-sent on every node and rarely changes the next step. A state with six keys is readable; one with thirty is a debugging project.
Log the attempt count and the final score for every run. The distribution of attempts tells you whether the critic is calibrated long before anyone reads a single draft.
Score the critic, not only the writer
The loop is only as good as the critic. If the critic passes everything, the writer never revises; if it passes nothing, every run hits the cap. Both look like a writer problem and are not.
Label a sample of drafts by hand and compare the critic’s scores with your labels. If the critic is too generous, the rubric is missing a clause; if it is too harsh, the bar is above what the writer can reach. Calibrate the critic before tuning the writer.
Adding the human
The approval goes before publish, not after. The checkpoint holds the final draft and the critique, a person reads both, and their decision is a key in the state. Approve, edit, or reject with a note that routes back to the writer.
The graph does not change shape. It gains one edge and one key, which is the test of whether the earlier chapters were built correctly.
The approval screen should show the draft and the critique together, because the critique is the reason the draft looks the way it does. An edit should store both versions, and a rejection should carry a note that reaches the writer. That note is the only way the next attempt can be different.
Approve before publishing
Run to the gate, inspect the draft, then resume with approval. Then run a second thread, reject it with a note, and confirm the note reaches the writer.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What are the nodes in the writer-critic graph?
