A useful AI application often needs much more than “send a prompt, receive an answer.” It may need to classify a request, retrieve trusted data, call APIs, ask for approval, remember progress, retry after failure, and decide what should happen next.
LangGraph is a low-level orchestration framework for building these stateful workflows and agents as a graph of steps. Those steps can branch, loop, persist progress, call tools, stream intermediate events, and for human input.
The simplest mental model is: state is what the system knows, nodes do the work, and edges decide what runs next.
Consider a customer asking: “My order arrived damaged. Can I get a refund?”
A production system may need to:
If all of this logic is hidden inside one large function or one opaque model loop, it becomes difficult to test, observe, resume, and control. LangGraph makes the control flow explicit while still allowing model-driven decisions where they are useful.
Imagine a parcel moving through a delivery hub:
The analogy is useful because it makes execution steps and transitions visible. It is not exact: software nodes can run in parallel, invoke probabilistic models, revisit earlier nodes, and update only selected fields of shared state.
State is the current snapshot of information available to graph steps. A state schema defines which fields exist and, in Python, usually their types.
from typing_extensions import TypedDictclass CounterState(TypedDict): count: int
If the current state is {"count": 4}, a node can read that value and return an update such as {"count": 5}. A node does not need to return every state field.
A useful rule is to keep state limited to the information required to coordinate the workflow. Treat it as shared working data, not as a dump of every temporary local variable.
A node is executable logic. It may call a model, run deterministic code, validate output, query a service, perform a tool action, or request human input.
def increment(state: CounterState): return {"count": state["count"] + 1}
If count is 4, this node returns 5. Returning an explicit update gives the runtime a clean boundary for state management.
This boundary is also valuable for software engineering: small nodes let you test routing separately from database access, tool execution, validation, or an LLM call.
An edge determines which node executes next. A normal edge always follows the same transition. A conditional edge calls routing logic and chooses a destination based on state.
from typing import Literaldef route(state: CounterState) -> Literal["again", "done"]: return "again" if state["count"] < 3 else "done"
Conditional routing is especially useful for exact business rules. If ordinary code can determine the answer precisely, there is usually no benefit in asking an LLM to guess it.
With the Graph API, StateGraph is a builder. You add nodes and edges, then call compile() to produce an executable graph.
from langgraph.graph import StateGraph, START, ENDbuilder = StateGraph(MyState)builder.add_node("work", work_node)builder.add_edge(START, "work")builder.add_edge("work", END)graph = builder.compile()
START and END are special markers rather than business-logic nodes. START represents where input enters; END represents termination.
A tool-capable model can produce a structured request containing a tool name and arguments. The application, not the model itself, performs the real operation.
messages -> model -> tool request? | no -> finish | | yes v tool -> tool result -> model -> ...
The model chooses only from the tools you expose. Tool descriptions and schemas therefore become part of the agent’s interface design. A vague tool description can lead the model to call the right capability incorrectly.
This is also where an important distinction appears: a workflow follows predetermined code paths, while an agent dynamically chooses actions and tool usage. Strong systems often combine the two: deterministic workflow structure around a smaller agentic region.
One of the most practical design lessons in the source material is that an LLM does not have to control everything.
A robust refund workflow might divide responsibility like this:
Use models where flexible judgment adds value. Keep exact rules, permissions, and irreversible actions constrained.
The section distinguishes a higher-level agent harness from lower-level orchestration. As of the current LangChain documentation, LangChain’s create_agent is the recommended higher-level option for a familiar model-plus-tools loop and is built on LangGraph.
from langchain.agents import create_agentdef get_weather(city: str) -> str: """Return weather data for a city.""" return f"Weather lookup for {city}"agent = create_agent( model="openai:gpt-5.5", tools=[get_weather], system_prompt="Use tools when needed, then answer concisely.",)result = agent.invoke({ "messages": [ {"role": "user", "content": "Weather in Kolkata?"} ]})
The expected behavior is that the model can choose the weather tool, receive its returned value, and then answer the user. Exact wording depends on the configured model and provider.
**Use **create_agent first when:
Use custom LangGraph when you need:
A reducer defines how an existing state value combines with a new update. Without a special reducer, a new update commonly replaces the previous value. Some fields instead need accumulation.
For example, if a list field uses concatenation, conceptually:
new list = old list + incoming items
Here + means list concatenation, not numeric addition. ["a"] + ["b"] becomes ["a", "b"].
For chat-style agents, MessagesState provides a standard convenience state for maintaining a chronological sequence of user messages, model replies, tool requests, and tool results.
Reducers matter even more when multiple branches update the same key. Parallel execution creates a state-design problem: simultaneous updates must have intentional merge behavior.
LangGraph separates two kinds of persistence
A checkpointer supports conversation continuity, resumption, human-in-the-loop flows, debugging or time-travel behavior, and fault recovery. A thread_id identifies which thread state to load.
from langgraph.checkpoint.memory import InMemorySavercheckpointer = InMemorySaver()graph = builder.compile(checkpointer=checkpointer)config = { "configurable": { "thread_id": "user-42-chat-1" }}result = graph.invoke(input_state, config=config)
InMemorySaver is useful for examples, but it loses data when the process restarts. A production system that needs durable recovery should use a persistence backend suited to its deployment.
A store solves a different problem: durable application information that should not be tied to only one conversation thread.
An interrupt s graph execution and surfaces a JSON-serializable payload to the caller. This is useful when the workflow needs an approval or other external decision before continuing.
from langgraph.types import interruptdef approval_node(state): approved = interrupt({ "question": "Approve refund?", "amount": state["refund_amount"], }) return {"approved": bool(approved)}
To continue, the same thread is resumed with a Command(resume=...) value.
The key implementation detail is easy to miss: when execution resumes, the interrupted node starts again from its beginning. That means side effects performed before interrupt() should be idempotent, or better, moved after approval.
Streaming lets a caller observe intermediate execution instead of waiting only for the final result. Depending on the API and stream mode, the caller may observe message chunks, state snapshots or updates, custom events, and interrupts.
Streaming does not make the underlying work faster; it makes progress visible sooner and can make long-running agent execution easier to debug.
LangGraph also provides Command, which can combine a state update with a routing decision from inside a node. It is useful when a node naturally produces both data and control flow. The source cautions against using it everywhere: ordinary edges keep simple flows easier to read.
For dynamic parallel fan-out, Send can create downstream work items with different inputs. Subgraphs can package reusable workflows, teams of agents, or specialized stages behind a clear input/output contract.
The PDF’s integrated example combines the main ideas into a refund workflow. It is presented as an original teaching example rather than an official policy.
State might include:
START -> understand_request -> load_order -> policy_check / | \ ineligible small large | | | explain refund approval | | | END reply <- refund | END
A model can turn free text such as “Please refund order A123; the item arrived damaged” into structured fields. This is a good model task because natural language varies.
A deterministic service call should fetch the real order record. Do not let the model invent price, payment status, delivery state, or whether the order exists.
def policy_check(state): order = state["order"] eligible = ( order["delivered"] and order["days_since_delivery"] <= 30 and not order["already_refunded"] ) return {"eligible": eligible}
If the order was delivered eight days ago and has not already been refunded, all three Boolean conditions are true and eligible becomes True.
In the teaching example, eligible refunds at or below Rs. 2,000 can proceed automatically, while larger refunds require human review.
def refund_route(state): if not state["eligible"]: return "reject" if state["refund_amount"] <= 2000: return "auto_refund" return "human_review"
For eligible=True and refund_amount=1500, the route is "auto_refund". For Rs. 3,500, the route is "human_review".
The graph s with an interrupt containing only the information a reviewer needs: order ID, amount, reason, and relevant policy facts. The same thread_id must be reused when execution resumes.
The refund API should accept an idempotency key such as refund:<order_id>:<case_id> when the external service supports that pattern. Idempotent means that retrying the same logical operation does not create a duplicate refund.
The returned refund_id should be stored in state so later nodes work from the verified outcome rather than from a model's assumption.
The model can turn the authoritative result into a friendly explanation. It should receive the actual status and refund identifier rather than decide whether money moved.
The agentic part is language understanding or flexible tool selection. The deterministic part should include eligibility rules, permission checks, monetary side effects, and idempotency.
The source recommends a practical implementation sequence:
It is not. LangGraph orchestrates execution. A graph can call models from different providers, or it can contain no model at all.
A node is simply a function or runnable step. It may be deterministic code, a service call, a model call, or a human-input step.
Messages are one possible state field. State is the graph’s broader working snapshot: IDs, flags, structured results, counters, approvals, trusted data, and more.
They solve different problems. A checkpointer persists thread-scoped graph snapshots; a store holds application-defined information that may be shared across threads.
create_agent is a higher-level LangChain harness built on LangGraph. Custom LangGraph remains useful when lower-level orchestration control is required.
The interrupted node starts again from its beginning when resumed. That is why side-effect placement and idempotency matter.
More freedom is not automatically better. Give the model flexibility only where it creates value; constrain exact rules, permissions, and irreversible actions.
Picture a railway map: the train carries state, stations are nodes, tracks are edges, switches are conditional routes, station logs are checkpoints, and a red signal is an interrupt.
LangGraph is useful when agent behavior must be explicit and operationally reliable. Its value is not that every step becomes “AI.” Its value is that you can combine probabilistic model decisions with deterministic software engineering in one visible execution model.
Start with the smallest abstraction that fits. If a simple model-plus-tools loop is enough, a higher-level agent harness may be the better choice. If you need custom state, nontrivial branching, durable execution, human approval, carefully controlled side effects, or multi-stage orchestration, custom LangGraph gives you the lower-level control to design those behaviors directly.
State remembers. Nodes work. Edges route. Checkpoints save. Interrupts ask humans. Tools act on the outside world.
LangGraph Agents: A Practical Guide to Building Stateful AI Workflows was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.