To course

The agent loop

One step of an agent loopPractice: one step of the loopThe loop in codePractice: the loop in codeStopping and retryingPractice: stopping and retryingWhat only code can decidePractice: what code decides

Reliability in production

Timeout and idempotency keyPractice: timeout and idempotency keySays done, but nothing happenedPractice: false successDurable agent: checkpoints and resumePractice: durable agent

Agent designs

Workflow or agentPractice: workflow or agentReAct and plan-and-executePractice: ReAct and plan-and-executePlan-and-SolvePractice: Plan-and-SolveReflexionPractice: ReflexionOne agent or a swarmPractice: one agent or a swarm

Context and cost

Why the agent bill growsPractice: tokens and cacheContext engineeringPractice: managing the windowAgent memoryPractice: what the agent keeps

Tools and protocols

MCPPractice: MCPTool descriptions and injectionPractice: tool descriptions and injectionCode instead of a toolsetPractice: code instead of a toolsetAgent skills and A2APractice: agent skills and A2A

Frameworks

LangGraph or the raw loopPractice: LangGraph or the raw loopAgent SDKs: what you are buyingPractice: agent SDKs

Coding agents

Driving a coding agent from a specPractice: driving a coding agent from a spec
←→RUSign in
Agents › FrameworksDeep dive

LangGraph or the raw loop

Builds onDurable agent: checkpoints and resumeWorkflow or agent

This block shows what LangGraph adds to the loop you already wrote, what it leaves to you, and how to tell when it earns its place.

Refunds above €200 at Northwind now wait for a manager, who may answer tomorrow morning. The server restarts for every deploy. The loop from the earlier block keeps messages in a local variable, so a refund waiting overnight has nowhere to live.

A case folder in an office

Picture a paper folder moving through an office. Each desk reads it, adds a sheet and sends it on. A sign at each desk says where it goes next, and some signs depend on what the folder says. Each time the folder moves on, a clerk files a photocopy under the case number. If the manager is out, the folder waits in a cabinet, and nobody has to stand there holding it.

LangGraph is that office.

In the office

In LangGraph

What it is

The folder

State

Shared data that every node can read, here a TypedDict

A desk

Node

A function that reads state and returns updates

A sign

Edge

Which node runs next, fixed or decided by a function

The photocopy

Checkpoint

State saved at each step

The case number

thread_id

Which saved state to load

Your loop from the earlier block is an office with two desks. The if on the stop reason has become a sign.

The same loop as before, drawn as a graph. The decision you wrote as an if is now a conditional edge.
The same loop as before, drawn as a graph. The decision you wrote as an if is now a conditional edge.

For this plain loop you gain nothing: the logic is the same and you have new words to learn. The gain comes from the photocopies and the cabinet.

One refund through the office

Here is the folder for a refund. The model's part is left out so that the mechanism stays visible.

class Case(TypedDict):
    order_id: str
    amount_cents: int
    approved: bool
    log: Annotated[list[str], operator.add]

A node returns only what it changes. By default a new value replaces the old one. log has a reducer, operator.add, so each update is appended. Without it, every desk would throw away the sheets before it.

The desk that waits calls interrupt:

def ask_manager(case: Case):
    answer = interrupt({"order_id": case["order_id"],
                        "amount_cents": case["amount_cents"]})
    return {"approved": answer == "approve",
            "log": [f"manager said {answer}"]}

The signs are ordinary functions that name the next node:

def route(case: Case) -> Literal["ask_manager", "refund"]:
    return "ask_manager" if case["amount_cents"] > 20000 else "refund"

builder.add_conditional_edges("propose", route)

Your code starts the run with a case number, and the graph stops at the manager's desk:

config = {"configurable": {"thread_id": "case-4417"}}
result = graph.invoke({"order_id": "4417", "amount_cents": 34900,
                       "approved": False, "log": []}, config)
result["__interrupt__"]   # the order and amount for the manager's screen

The process can now exit. The next morning, a new process compiles the same graph with the same checkpointer and sends the answer:

graph.invoke(Command(resume="approve"), config)

The value in Command(resume=...) becomes what interrupt returns. LangGraph's docs now recommend driving such runs with stream_events(..., version="v3"). That mode is still marked experimental, and invoke works and is shorter here.

Checkpoints from a real run on LangGraph 1.2.20 with SqliteSaver, paused and resumed by two separate processes.
Checkpoints from a real run on LangGraph 1.2.20 with SqliteSaver, paused and resumed by two separate processes.

The picture shows five checkpoints for three nodes: one empty start, one for the input and one after each node. ask_manager ran twice, because, as the block on durable runs said, a node restarts from its top. SqliteSaver is for local work. LangGraph's docs point to PostgresSaver for production. By default a checkpoint is written in the background while the next step runs (durability="async"), so there is a small risk that a crash loses the latest one. durability="sync" waits for each write before moving on.

A new thread_id means an empty folder. A retry that invents a fresh id starts the case again and does not resume it.

The same pause in the raw loop

The raw loop handles this better than people expect, because messages is already plain data. Save it, stop, and continue when the answer comes:

def pause(case_id, messages, results, block):
    db.save(case_id, {"messages": messages, "results": results,
                      "pending": block})

def resume(case_id, decision):
    run = db.load(case_id)
    answer = apply_decision(run["pending"], decision)   # a tool_result
    run["messages"].append({"role": "user",
                            "content": run["results"] + [answer]})
    return run_agent(run["messages"])

The results of the other calls in that reply are kept, because on Anthropic's API they all go in one message. With one pause point, this is a table and two functions, and every prompt stays in your code. The comparison changes as the needs grow:

You need

Raw loop

LangGraph

One approval before one tool

A table and two functions

interrupt and a checkpointer

Pick up after a crash at any step

Save after every step, write the recovery

Checkpoint per step, and invoke(None, config) retries what did not finish

Fixed steps mixed with model steps

ifs, fine until paths multiply

Nodes and conditional edges

Parallel branches where one fails

Track yourself which ones finished

Finished nodes are not rerun

Inspect or replay an old run

Build it

get_state_history, replay from a snapshot

The last question in the picture is about durable-execution engines such as Temporal, from the block on durable runs.

A rule of thumb. Each question moves you to more machinery only when the answer forces it.
A rule of thumb. Each question moves you to more machinery only when the answer forces it.

What you pay, and what you still own

Anthropic's advice from 2024 is to start with LLM APIs directly and, if you take a framework, to understand the code under it, because wrong assumptions about it are a common source of errors. LangGraph sits low in this respect: its docs say it does not abstract prompts or force an architecture.

The price is real anyway. You learn its execution model, your state has to survive serialization, and old checkpoints pile up until you prune them or set a retention policy. Between 10 November 2025 and 10 November 2026 it had 50 releases on PyPI, patches and 10 pre-releases included, so pin the version.

A graph does not make a refund correct. The ownership check, the amount check and the idempotency key from the earlier blocks still live in your refund node.

Name the layer correctly. create_agent is imported from langchain.agents: it is LangChain's ready-made agent loop, built on top of LangGraph. LangGraph's own builder is StateGraph.

Conclusion

Go deeper

LangGraph: InterruptsHow to pause and resume, including several interrupts at once.LangGraph: CheckpointersHow super-steps and pending writes work, and how each kind of invocation continues a thread.LangChain: Runtimes, frameworks, and harnessesLangChain's own map of where LangGraph, LangChain and Deep Agents sit.Anthropic: Building effective agentsThe section on frameworks. From 2024, and the post itself warns that the tools have changed.
‹ Previous
Suggest an improvementFound a mistake