LangGraph, the useful 20%

Your first agent, line by line: the sixty lines in 01-step-drafter/agent.py, and what each part is actually for.
PrimerSoftwareSeptember 20, 2026
The one confusion. LangGraph is not an AI library. It is a small state-machine library: nodes are plain functions, edges say which function runs next, and a shared state object gets passed along. Exactly one line in the whole file talks to a model. Once you see that, the framework stops being mysterious — you are wiring up a loop, and one of the boxes in the loop happens to be Claude.

The moves that matter

MoveThink of it asWhat actually happens
StateThe clipboard passed aroundA TypedDict. Every node gets it and returns a partial update — never the whole thing.
ReducerHow to merge two clipboardsAnnotated[list, add_messages] means "append, don't replace". Without it, each node would wipe the history.
NodeOne box in the flowchartA function: state in, dict out. think and act are the only two here.
EdgeAn arrowadd_edge("act", "think"). Always goes there next.
Conditional edgeA diamond in the flowchartA function returning the name of the next node. The only decision the graph makes.
@toolA job the model can ask forTurns a Python function's name, arguments and docstring into a schema the model can see.
bind_toolsHanding over the menuAttaches those schemas to the request. It grants no power — the model can only ask.
compile / invokeBuild it, then run itcompile() freezes the graph; invoke() runs from START until END.

The file, in the order it makes sense

  1. State — one field, messages. The agent's entire memory is the message list, and it only exists during the run.
  2. The tool — read_card, a normal function. Its docstring is the prompt: that text is what the model reads when deciding whether to call it. Write it for the model, not for a code reviewer.
  3. The model — one line: ChatAnthropic(...).bind_tools(TOOLS). The only AI in the file.
  4. think() — call the model with everything so far, append what comes back. Three lines.
  5. act() — run whatever the model asked for, append each result as a ToolMessage carrying the id of the request it answers. That id is how the model knows which answer is which.
  6. next_move() — the whole decision: did the last message carry tool_calls? Go to act. Otherwise END.
  7. The graph — four lines of wiring. The edge from act back to think is the loop. Remove it and you have a single API call with extra ceremony.
What to notice. There is no "agent" object with goals or memory. There is a list of messages, a function that grows it, and an arrow that goes back. Every agent framework you meet is this, plus conveniences.

Safe to try

None of these can cost anything or break anything — the tool in this sample is one hard-coded card.

pip install -U "langchain[anthropic]"    # langchain 1.4, langgraph 1.2 — it pulls both
python agent.py                  # no key: it fails at the model call, everything else is readable
print(agent.get_graph().draw_ascii())   # the flowchart, from the compiled graph itself
for m in final["messages"]: print(m.type, m.content[:80])   # the whole run, turn by turn

Swap model for a fake object with an .invoke() that returns a canned message and the entire loop runs offline. That is how you test an agent's plumbing without a key or a bill.

The everyday loop

Write the tool. Write its docstring as if the model were a new colleague who can only read that one sentence. Run it and read every message, not just the last one — the tool calls are where you see whether it understood. Then fix the docstring, not the model.

Practice, start to finish

  1. Run it as-is. You should see a draft that mentions the other steps on the billing card — proof it called the tool.
  2. Break the docstring: change it to "Returns data." The model stops calling it, or calls it at the wrong moment. That is how much the docstring is doing.
  3. Add a second tool that returns today's date. Two tools in the list, nothing else to change: the loop already handles any number.
  4. Set recursion_limit to 2. You get an error instead of an answer — which is what a stop condition feels like when it fires.
  5. Print the messages after the run. System, human, AI-with-tool-call, tool, AI-with-answer. Five messages is a whole agent run.

When it goes wrong

SymptomWhat it really isWay out
It never calls the toolThe docstring does not describe a situation the model recognisesRewrite the docstring as "use this when…". Name the trigger, not the return type.
It calls it foreverThe tool answers without resolving anything, so nothing changes turn to turnMake the tool's answer conclusive, and keep recursion_limit low.
GraphRecursionErrorThe stop condition fired — working as designedRead the messages to see what it kept retrying. Do not just raise the limit.
Answers drift after a few turnsThe message list grows, and every turn re-reads all of itKeep runs short. Summarise and restart rather than appending forever.
Tool raises, run diesAn exception is not something a model can readCatch it and return the text, as act() does here.
Wrong argument typesThe model is filling in a schema it half-understandsType the parameters properly and validate inside the tool. The schema is a contract, not a suggestion.

Leave this alone for now: giving a sample agent a tool that writes to anything real. Read-only tools until you have watched a full run and read every message it produced.

Words you will see

Reducer
The merge rule for a state field. add_messages appends; the default replaces.
ToolNode
LangGraph's prebuilt version of act(). Same job, already written.
tools_condition
The prebuilt version of next_move().
create_agent
The whole file in three lines, in langchain.agents. Was create_react_agent before the 1.0 releases.
Checkpointer
Saves state between runs, so a graph can pause and resume. Not in this sample.
ReAct
Reason, act, look, repeat. The pattern this graph implements.
tool_call_id
The receipt number pairing an answer with the request that asked for it.
Interrupt
Stopping mid-graph for a human to approve. The feature you will want next.
Middleware
Hooks around a prebuilt agent — before_model, wrap_tool_call, after_model. Replaced the old pre/post hooks.

Deliberately not here

Checkpointers and resumable runs, streaming tokens, human-in-the-loop interrupts, subgraphs, multi-agent handoffs, and LangSmith tracing — every one of them is a good idea and none of them is legible until the loop above is. Also skipped: retrieval, which is just another tool, and deployment, which is a different problem entirely. When you want a human gate in the middle of a run, that is the moment to come back for interrupts.

Names moved in 2026. The 1.0 releases renamed the prebuilt agent to create_agent (in langchain.agents, taking system_prompt=), moved imports to langchain.messages / langchain.tools / langchain.chat_models, replaced the pre/post model hooks with middleware, and pushed the legacy chains into a separate langchain-classic package. ToolNode and tools_condition stayed in langgraph.prebuilt. Anything written before 2026 — including the notebooks that ship with Gulli’s Agentic Design Patterns — uses the old names and will not run as written.

LangGraph · primer · September 20, 2026 · The file it explains: @ Singularity/Forge/! Main/agent-samples/01-step-drafter/agent.py