Building Agents in LangGraph: Nodes, Edges, and State
LangGraph agents are usually learned backwards. You find an example, paste it, watch it run, change one thing, and it breaks. You cannot say why, because you never understood why it was a graph in the first place. Most LangGraph tutorials teach the syntax (add_node, add_edge, compile) and skip the model underneath. Syntax you can look up at 2 a.m. The model is what lets you debug at 2 a.m.
So this LangGraph tutorial for beginners and working engineers goes in the right order. First the mental model, in five nouns: state, nodes, edges, conditional edges, reducers. Then this step-by-step LangGraph tutorial builds one complete agent in five runnable stages, from a two-node graph to a working ReAct loop with memory and a human approval gate.
Prerequisites: Python 3.10 or later, comfort with functions and dictionaries, and an API key for any chat model.
The Mental Model: A LangGraph Agent Is a Whiteboard, Not a Script
Picture a war room with one whiteboard. That single image explains almost everything about AI agents in LangGraph, and we will use it the whole way through.
The whiteboard is the state. One shared surface. Everything anyone needs to know is written on it, and there is exactly one.
The specialists are the nodes. Each one walks up, reads the whole board, does one job, writes down only the part they changed, and sits back down. They do not talk to each other and hold no private memory between turns.
The edges are the rule for who is called up next. A plain edge is a fixed rota: after the researcher, always the writer. A conditional edge is the coordinator glancing at the board and deciding: "Unanswered tool request written here? Call the tools person. Nothing pending? We are done."
A reducer is the house rule for when two people write in the same box: overwrite, or add to the list? Get it wrong and the board silently loses information.
Here is the payoff, the thesis of this article: because every specialist reads and writes the same board, you can photograph the board after every turn. That one property is what makes LangGraph agents debuggable, resumable, pausable and rewindable, and it is exactly what a while loop with local variables can never give you. Every later section is a consequence of this sentence.
Quick orientation: LangGraph is an open-source Python and JavaScript framework from the LangChain team for building stateful agents as graphs, stable since v1.0 shipped in October 2025 (1.0 milestone post). For the full definition and history, read our explainer on what LangGraph is and why it exists; here, we build.
The five nouns you need (and nothing else yet)
This table is the article's reference card. Screenshot it.
| Noun | In the whiteboard analogy | In code | What it controls |
|---|---|---|---|
| State | The board itself | A TypedDict or Pydantic model | What every node can see |
| Node | A specialist who steps up | A Python function (state) -> dict | What actually happens |
| Edge | The fixed rota | add_edge("a", "b") | Unconditional flow |
| Conditional edge | The coordinator's decision | add_conditional_edges(...) | Branching and looping |
| Reducer | The house rule for shared boxes | Annotated[list, add_messages] | How updates merge |
Understand these five and everything else in LangGraph is convenience built on top. The rest of the article honours that by deriving every advanced feature from these nouns.
Why an agent is a loop, not a pipeline
If you are shaky on how AI agents work at all, start there; the loop argument lands much easier with the basics in hand.
A pipeline knows its steps in advance. An agent does not. The model decides at run time whether it needs a tool, and after using one it must come back and decide again. That return arrow is a cycle, and a cycle is precisely what a linear LangGraph workflow cannot express and a directed graph can.
The pattern has a name: the reason, act, observe loop, from the ReAct paper (Yao et al., 2022). The "ReAct agent" everyone builds is this three-node cycle with an exit condition. Which raises the warning: a cycle without an exit condition is an infinite loop with an API bill attached. LangGraph's recursion limit is a safety net, not a design; the debugging section shows the failure and the fix.
State: The One Object That Flows Through the Graph
What is state in LangGraph? In one sentence: state is a single typed object that every node in the graph receives and can update. Nodes never pass values to each other directly; they read from state and return partial updates, which LangGraph merges back in before calling the next node.
Three properties matter. State is typed: you declare a schema. State is shared: there is exactly one, not one per node. State is merged, not replaced: a node returns only the keys it changed. Every debugging skill later in this article is some version of "look at the board".
Defining state with TypedDict
from typing import Annotated
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
TypedDict gives you a dictionary with a declared shape, a hint to you and your editor that is not enforced at runtime. messages is the conventional key for chat history. Annotated[...] attaches the merge rule to the type itself. If Annotated is new to you, brush up on Python type hints and dictionaries first, because the runtime point causes real confusion: LangGraph will not stop you putting a string where a list belongs. That is the trade-off you accept for speed and simplicity, and it is why the next section exists.
A graph can also declare separate input and output schemas, as in StateGraph(State, input_schema=..., output_schema=...), to hide internal working keys from the caller. That one call is the complete LangGraph multiple schemas example pattern.
Build an AI-First Career, Master the Complete Skillset
Choose from our industry-leading programs designed for career success
Modern Software and AI Engineering Program
Master full-stack development with AI integration
+1000 moreModern Data Science and ML with specialisation in AI
Advanced data science techniques with AI specialization
+1000 moreAdvanced AIML with Specialisation in Agentic AI
Deep dive into AIML with focus on Agentic systems
+1000 moreDevOps, Cloud & AI Platform Engineering
Build and manage AI-powered cloud infrastructure
+1000 moreAI Engineering Advanced Certification by IIT-Roorkee
Premier AI engineering certification from IIT-Roorkee
AI Forward Deployed Engineer Program
Full-stack engineering, production AI and client-facing consulting
+1000 moreTypedDict vs Pydantic: which should you use?
| Dimension | TypedDict | Pydantic BaseModel |
|---|---|---|
| Runtime validation | None | Enforced |
| Default values | No | Yes |
| Performance | Faster, plain dict | Slight overhead |
| Error timing | Fails deep inside a node | Fails at the boundary |
| Best for | Most agents, tutorials, speed | User-facing input, strict schemas, team code |
Start with TypedDict. Move to Pydantic when a bad value should be caught at the boundary with a readable error instead of three nodes later as a KeyError. The failure that usually forces the switch: an optional key absent on the first turn, so state["plan"] raises inside a node. Both work; neither is a mistake.
Reducers: how concurrent updates merge
Back to the whiteboard: two specialists write in the same box. Without a rule, the second writer erases the first. The reducer is the rule. Default behaviour is last write wins; a key with no reducer is overwritten by whichever node wrote it last. A reducer is any function taking (current_value, update) and returning the merged value:
from operator import add
from typing import Annotated
from typing_extensions import TypedDict
operator.add concatenates lists; write your own reducer to de-duplicate or cap a list at the last 20 items. add_messages is the special-cased reducer for chat history: it appends new messages and updates existing ones by ID instead of duplicating them, which is what makes streaming partial messages and editing prior turns work. Anyone asking how add_messages in LangGraph behaves should know it is not plain append; the ID matching is the point.
Reducers become non-negotiable in one place: when two nodes run in parallel and both write the same key, LangGraph must know how to combine the writes, and without a reducer it refuses rather than silently picking a winner. Verified on langgraph 1.2.11, the exact failure is InvalidUpdateError: At key 'log': Can receive only one value per step. Use an Annotated key to handle multiple values. Remember that message; it turns an abstract concept into a rule.
The bug that catches everyone: overwriting instead of appending
The symptom, which you may already have typed into a forum: "My agent has no memory. Every turn it acts like the conversation just started." Or: "The tool result vanished before the model saw it." The cause:
The rule worth memorising, on its own line: a node returns a diff, not a document.
Nodes: Pure Functions That Read State and Return Updates
LangGraph nodes are Python functions that take the current state and return a dictionary of the keys they want to change. That is the whole interface. No base class to inherit, no decorator required, no framework object to construct:
Register with builder.add_node("summarise", summarise). The string name is the node's address: it is what edges point at, what appears in the diagram, and what shows up in traces. Name nodes after what they do (fetch_docs, call_model), not what they are (node1). You will read these names in every debug output for the life of the project.
Now the honest asterisk on "pure function". Conceptually nodes are pure: same state in, same update out. In reality your nodes call LLMs and hit APIs, so they are neither deterministic nor side-effect-free. What LangGraph actually requires is narrower: do not mutate the state object in place, and do not stash data in module-level globals between calls. Return your changes. Everything the next node needs must be on the whiteboard.
Two brief facts. Nodes can be async def for concurrent I/O. And a node can be an entire compiled graph (a subgraph), which the multi-agent section picks up. In a real system each node maps to a distinct responsibility: retrieval, reasoning, tool execution, validation. The system-level picture is in our piece on agentic AI architecture.
Edges: Control Flow You Can Actually See
In a hand-rolled agent, control flow is scattered across if statements inside functions. In LangGraph it is declared separately from the work, which is why the graph renders as a picture and why you can reason about "what can happen next" without reading any node's body.
Normal edges and the START / END markers
builder.add_edge(START, "first_node") sets the entry point; builder.add_edge("last_node", END) terminates the run. START and END are sentinel constants imported from langgraph.graph, not strings you invent. Adding two edges from the same source fans out: both branches run concurrently, then fan back in, which is the case that makes a reducer mandatory on any shared key.
Conditional edges: how an agent decides
A LangGraph conditional edge has a three-part contract most tutorials show but never name: the source node where the decision is made, the routing function (a plain function taking state and returning a string label), and the mapping (a dict translating each label to a destination):
The critical clarification: the routing function does not return the next node. It returns a label, which the mapping resolves to a node. Once you see that, the common KeyError (routing function returned a string absent from the mapping) explains itself. Small insight, outsized debugging value.
The alternative worth knowing: a node can return Command(goto="node_name", update={...}), imported from langgraph.types, to update state and route in one move. It is the mechanism behind multi-agent handoffs; more in that section.
Build It: A Complete LangGraph Agent in Five Steps
One agent, a weather agent, built across five steps: tiny, an obvious tool, and a real decision (call the tool, or answer directly). Each step is a strict superset of the last, and every stage runs on its own. Unlike most LangGraph examples online, every block below was executed against langgraph 1.2.11 before publication.
Step 0: Install and set up
pip install -U langgraph "langchain[openai]"
The LangGraph Python library needs 3.10 or higher; all LangChain packages dropped 3.9 at v1, so pip install langgraph on 3.9 refuses. Set your provider key (OPENAI_API_KEY here); any provider works. On the JavaScript side, @langchain/langgraph is the LangGraph JS port with the same concepts.
Step 1: The smallest graph that runs: two nodes, one edge
Deliberately no LLM yet. The point is to isolate the mechanics (state in, partial updates out, order controlled by edges) so that when the model arrives, nothing about the LangGraph StateGraph is new.
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
Three observations carry the whole model. Each node returned one key, not the whole state. The second node read a value the first wrote, without either function referencing the other. And compile() is a required build step that validates the graph before it can run.
Step 2: Replace a node with a real model call
Swap in messages state with the add_messages reducer (the schema from the state section, reused) and a node that calls a chat model:
One snag stops beginners cold: the node returns a list containing one message, not the message itself, because add_messages expects a list to merge.
How Scaler Transformed Careers in Different Fields
Scaler learners achieved 2.5x salary growth with average post-Scaler CTC reaching ₹23L.
Step 3: Give the agent a tool
LangGraph tools are ordinary Python functions with a docstring, plus one mechanic most readers get fuzzy:
from langchain_core.tools import tool
from langchain_core.messages import ToolMessage
The mechanic, precisely: bind_tools does not let the model run anything. It tells the model which tools exist and how to ask for them. The model replies with a message carrying tool_calls, a request. Your call_tools node executes it and writes the result back as a ToolMessage carrying the matching tool_call_id. The ID pairing is what lets the model connect answer to question.
If request versus execution still feels slippery, the Scaler lecture above (45 minutes) is entirely about what the model does with tools, independent of LangGraph, and it makes every node in the loop legible.
Now the prebuilt shortcut, earned: from langgraph.prebuilt import ToolNode, then tool_node = ToolNode(tools), does the same in one line. Use the prebuilt in production; writing it once by hand is what makes it legible when it misbehaves. Also note the @tool docstring is not a comment: it is the tool description the model reads to decide whether to call the tool. A vague docstring is a vague agent. In production, most tools arrive through a standard protocol rather than being hand-defined per project; see how agents call tools through MCP.
Step 4: Add the conditional edge that routes "call tool" vs "finish"
Wire in should_continue, then close the loop:
builder.add_conditional_edges("call_model", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "call_model") # the return arrow: this is the agent loop
Sit with that second line. That edge, pointing backwards, is the entire difference between a chain and an agent. After running a tool, control returns to the model, which decides again: another tool, or an answer. It is the reason a graph was necessary at all.
Step 5: The finished agent (full listing)
Every competing tutorial makes you reassemble fragments. Here is the whole program in one copy-pasteable block:
Walk the trace in five hops: the user message lands on the board; the model reads it and returns a tool call; the conditional edge routes to tools; the ToolMessage goes onto the board; control returns to the model, which sees the result and answers with no tool call, so the edge routes to END. Every hop was one specialist reading and writing the whiteboard. The analogy has done its job; retire it.
See the graph you just built
print(graph.get_graph().draw_ascii()) # needs: pip install grandalf
draw_ascii() prints the loop in your terminal (install grandalf first; the import error is the price of skipping it). draw_mermaid_png() renders the same LangGraph diagram as an image: the rendered output (see image notes) is our LangGraph architecture diagram and LangGraph visualization asset, with the conditional edge dashed and the return arrow highlighted. Practical tip: render the graph before you debug it. Half of "why is my agent doing nothing" is an edge pointing at the wrong node, and the picture shows it in a second.
Checkpointing: Memory That Survives a Restart
Here is where the mental model pays out. Because everything lives on one state object, saving a LangGraph agent's progress means saving one thing. Add a checkpointer and LangGraph photographs the whiteboard after every node. Everything in this section follows from that. Scope note: this is persistence mechanics, how state is saved and restored; memory strategy (what to remember, summarising, trimming context) is a design topic of its own.
Threads: giving each conversation its own history
from langgraph.checkpoint.memory import InMemorySaver
The key insight: the second call passes only the new message, yet the agent understands "tomorrow" in context, because the checkpointer reloaded the thread's state first. In our executed run, the second invocation's state held all 8 messages though only one was passed in. Change thread_id and you get a clean conversation: multi-user LangGraph memory in two lines, no database code. Freshness note that dates competing tutorials: the current class is InMemorySaver; MemorySaver in older code is the same object.
Turn Learning into Career Growth
InMemorySaver vs a durable checkpointer
The LangGraph checkpointer you pick decides whether memory survives a restart:
| Checkpointer | Import | Survives restart | Use for |
|---|---|---|---|
| InMemorySaver | langgraph.checkpoint.memory | No | Local dev and tests |
| SqliteSaver | langgraph.checkpoint.sqlite (package langgraph-checkpoint-sqlite) | Yes, single file | Single-machine apps |
| PostgresSaver / AsyncPostgresSaver | langgraph.checkpoint.postgres (package langgraph-checkpoint-postgres) | Yes | Production, multi-instance |
The trap, bluntly: InMemorySaver looks like it works. It works perfectly until the process restarts, at which point every conversation is gone. Many teams ship it by accident because the tutorial they copied used it. Details in the official persistence docs.
What persistence unlocks: replay, resume, and time travel
Three capabilities, one mechanism each. Resume after failure: a crashed run restarts from the last checkpoint, not the first token. Inspect any past state:
graph.get_state(config) returns a StateSnapshot with values and the next node queued. Time travel: graph.get_state_history(config) walks every checkpoint (our run recorded 5 for one tool-calling turn); pass a checkpoint_id in the config to re-run from that point, optionally with edited state. Rewind an agent that went wrong, change one value, fork a new branch: a debugging superpower with no equivalent in a hand-rolled loop.
for snapshot in graph.get_state_history(config):
print(snapshot.next, snapshot.values["messages"][-1].content[:60])
Human-in-the-loop: pausing for approval with interrupt
Adding a human in the loop with LangGraph is the single most adopted production feature, and almost nobody shows working code for it. The concrete case: the agent is about to issue a refund, email a customer, or run a DELETE, the kind of action across these AI agent examples you can build, and a human must approve. Without persistence this is impossible; with checkpointing the graph simply stops and the state waits. Minutes or days later, someone approves and it continues from exactly that point.
from langgraph.types import interrupt, Command
def human_approval(state: AgentState) -> dict:
last = state["messages"][-1]
decision = interrupt({
"question": "Approve this tool call?",
"tool_calls": last.tool_calls,
})
if decision != "approve":
return {"messages": [ToolMessage(
content="Tool call rejected by the user.",
tool_call_id=last.tool_calls[0]["id"],
)]}
return {}
# Insert between the model and the tools node, then resume with:
graph.invoke(Command(resume="approve"), config)
Two halves. interrupt(payload) raises out of the run, surfacing the payload to your application (verified: our run paused with the payload exposed under __interrupt__, and get_state(config).next pointing at the approval node); that payload is what you render in a UI. Command(resume=value) sends the answer back, and interrupt() returns it as though nothing stopped. One real, commonly hit caveat absent from competing articles: a node containing an interrupt re-runs from its start on resume, so keep expensive work out of it. Full semantics in the interrupts documentation.
Prebuilt vs Custom: create_agent and When to Drop to a StateGraph
The reveal: everything above is available prebuilt in three lines. A LangGraph ReAct agent, no assembly required:
from langchain.agents import create_agent
agent = create_agent("openai
agent.invoke({"messages": [{"role": "user", "content": "What's the weather in Pune?"}]})
Why build it by hand, then? Because the prebuilt agent is that graph: same state, same nodes, same conditional edge, same loop (verified: create_agent returns a CompiledStateGraph). When it surprises you in production, you now know which part surprised you. Reading create_agent as a black box is how people end up on forums at midnight. Understanding how agent frameworks are built generalises this beyond LangGraph.
Naming note, the most date-sensitive fact here. Most tutorials and searches still say create_react_agent, imported from langgraph.prebuilt. It still works, but as of LangGraph v1 it is deprecated: calling it warns create_react_agent has been moved to langchain.agents and asks you to switch to create_agent. The v1 migration guide and the create_react_agent API reference confirm both names. If you searched "create react agent" LangGraph snippets and landed on the old name, it is the same object under its old address. Use create_agent; recognise the old one everywhere.
Stay prebuilt or go custom? Decide on signals, not features:
| Signal | Prebuilt create_agent | Custom LangGraph StateGraph |
|---|---|---|
| One model + a set of tools + a loop | Yes | Overkill |
| Extra state keys beyond messages (plan, retry counter, docs) | No | Yes |
| Fixed steps before reasoning starts | No | Yes |
| Branching on more than "did the model call a tool?" | No | Yes |
| Approval on specific tools only | Yes, with middleware | Yes, finer control |
| Multiple cooperating agents | No | Yes (next section) |
| Ship today and iterate | Yes | Later |
The rule: start prebuilt; drop to a StateGraph the first time you find yourself fighting it. Fighting it means adding state the harness lacks, or wanting control flow it will not express. An engineering heuristic, not framework advocacy.
Multi-Agent Patterns in One Page: Supervisor and Handoff
The query "LangGraph multi agent" usually means one of two patterns, and one idea covers both: multi-agent systems are not a new mechanism. An "agent" becomes a node (often a compiled subgraph), and coordination is just edges and routing: the same five nouns, one level up. You already have everything you need; the rest is taste and knowing different types of AI agents conceptually.
| Pattern | How control moves | State model | Fits when | Cost |
|---|---|---|---|---|
| Supervisor | A router node picks the next specialist each turn | One shared state; supervisor sees all | Tasks decompose cleanly; one place to audit decisions | Extra model call per hop; supervisor is a bottleneck |
| Handoff / swarm | Agents transfer directly via Command(goto=..., graph=Command.PARENT) | Shared state; each agent decides when to pass on | Agents know who goes next better than a router | Harder to reason about; accidental loops |
The LangGraph supervisor pattern and the swarm pattern ship as maintained prebuilt packages, langgraph-supervisor and langgraph-swarm, installable from PyPI.
On subgraphs, the other half of LangGraph orchestration: a compiled graph can be added as a node in a parent graph, which is how specialists get composed and large graphs stay readable. Shared state keys pass through automatically; differing schemas need explicit transformation at the boundary.
Honest advice to close: most problems that look like they need three agents need one agent with three tools. Multi-agent architectures multiply cost, latency and failure modes. Reach for them when specialists genuinely need different tools, prompts or models, not because the diagram looks impressive.
Debugging LangGraph Agents: See the State, Don't Guess
Every LangGraph bug is one of three things: wrong data on the board, wrong specialist called, wrong merge rule. All three are visible if you look at the state. Debugging LangGraph agents is a looking skill, not a guessing skill, and this section exists because no top-ranking tutorial teaches it.
Inspect state between nodes with streaming
for chunk in graph.stream(
{"messages": [{"role": "user", "content": "What's the weather in Pune?"}]},
config,
stream_mode="updates",
):
print(chunk)
Picking the right stream_mode matters, and the wrong pick is its own confusion. "updates" emits only what each node changed (best for debugging; our run printed {'call_model': ...} then {'tools': ...} then {'call_model': ...}). "values" emits the full state after each step. "messages" streams LLM tokens for UX. "debug" emits everything. The point-in-time alternative: graph.get_state(config) returns the snapshot plus .next, the node about to run. If .next is not what you expect, the routing function or mapping is wrong, and you know in one line instead of an hour.
Five failure modes and what they actually mean
| Symptom | Likely cause | Fix |
|---|---|---|
| GraphRecursionError | Loop has no exit: model keeps requesting tools, or the edge never returns END | Add a turn counter to state, route to END past a threshold |
| No memory across turns | messages declared without add_messages; each write overwrites | Annotated[list[AnyMessage], add_messages] |
| State "isn't updating" | Node mutated state in place, or returned the whole state | Return {"key": value} for changed keys only |
| KeyError in routing | Routing function returned a label absent from the mapping | Type the return Literal[...]; make the mapping exhaustive |
| Parallel branches error on write | Two nodes wrote one key with no reducer (InvalidUpdateError) | Add a reducer via Annotated |
| Tool result never reaches the model | Missing or mismatched tool_call_id | Echo the exact id from the model's tool_calls |
On the limit itself: it is a safety net, configurable per invocation with {"recursion_limit": 10} in the config (verified: a runaway loop raised GraphRecursionError: Recursion limit of 5 reached without hitting a stop condition when capped at 5). Lower it while developing so runaway loops fail in seconds. Upgraders take note: langgraph 1.2.x ships a default of 10007 supersteps, not the 25 older tutorials quote, so never rely on the default as your exit strategy; design one.
A true story to close: an agent I review was looping between two nodes, burning tokens on every run. Streaming with stream_mode="updates" showed the same two node names alternating, and the cause took under a minute: the routing function checked tool_calls on messages[-2] instead of messages[-1], so it never saw the tool result land. The fix was one character. The finding was the method. Once an agent is deployed, you graduate from printing chunks to a tracing tool; that is what LangSmith is for, and the output you just learned to read is what it captures automatically.
What to Build Next
Three LangGraph projects in increasing difficulty, each specified by the new concept it forces you to learn, so each is a lesson, not just a build. All three double as generative AI projects worth putting on a resume.
First, a support triage agent: classify a ticket, route to one of three handlers, escalate to a human on low confidence. It forces conditional edges with more than two branches, plus interrupt.
Second, a research agent with a retrieval tool: search, read, judge whether the answer suffices, search again if not, then write. This is the build people mean when they search agentic RAG LangGraph tutorials, and LangGraph RAG guides usually skip what matters here: a genuine multi-turn loop with a quality gate, plus extra state keys beyond messages. It is also the project that first justifies dropping the prebuilt agent.
Third, a two-agent writer and critic loop passing work back and forth until the critic approves or a revision cap hits. It forces subgraphs, handoffs, and a counter with a reducer. Once that clicks, a LangGraph deep research agent (search, synthesis, citation tracking across many loops) is an ambitious natural extension.
One sentence of career pragmatism: "LangGraph interview questions" is now a real search trend, and these three projects cover most of what gets asked: state design, when a graph beats a chain, and how to add human approval. For the wider sequence (evaluation, deployment, observability), follow the full agentic AI roadmap.
FAQs
What is a LangGraph agent?
An agent built as a graph, where a shared state object flows between nodes (Python functions that do the work) along edges (which decide what runs next). Conditional edges let the model's output route execution, creating loops plain chains cannot express.
How do I build an agent in LangGraph?
Define a state schema with a TypedDict, write nodes as functions returning partial updates, connect them with add_edge, add a conditional edge routing between tool use and finishing, then compile() and invoke(). The full five-step build is above.
What is state in LangGraph?
A single typed object every node receives and updates. Nodes never pass values directly to each other; they return partial updates, which LangGraph merges into state before the next node runs.
What is a reducer in LangGraph?
A function deciding how a new value merges with the existing one for a state key. Without a reducer the last write wins. add_messages appends chat messages and updates them by ID instead of replacing the history.
Should I use create_react_agent or build my own StateGraph?
Start with the prebuilt agent, now create_agent in langchain.agents, for one model plus tools. Move to a custom StateGraph when you need extra state keys, custom branching, or multiple cooperating agents.
Can I learn LangGraph without learning LangChain?
Yes. LangGraph is standalone and works with direct model SDKs; LangChain's integrations are convenient but optional. If you are weighing the frameworks themselves, see our LangChain vs LangGraph comparison.
Is LangGraph a Python library?
Yes, and also a JavaScript and TypeScript one. pip install langgraph needs Python 3.10 or later; @langchain/langgraph covers the JS side with the same concepts.
Why does my LangGraph agent loop forever?
Usually the model keeps requesting tools and the conditional edge never routes to END. LangGraph raises GraphRecursionError at the recursion limit as a safety net. Add an explicit exit, such as a turn counter in state, and lower recursion_limit while debugging.
How do I add memory to a LangGraph agent?
Compile with a checkpointer and pass a thread_id. InMemorySaver suits development; SqliteSaver or PostgresSaver persist across restarts. Each thread keeps its own history automatically.
What are the 7 types of AI agents?
Classical taxonomies list simple reflex, model-based reflex, goal-based, utility-based, learning, hierarchical agents and multi-agent systems. LangGraph agents are typically goal-based or utility-based, built as tool-using loops. (There is no official "big 4 AI agents" list either; that phrase refers to whichever four frameworks a given listicle ranked that month.)
Conclusion
Recall the model, not the code. State is the whiteboard. Nodes read it and return diffs. Edges decide who is next; conditional edges decide at run time, and reducers settle who wins when two writes collide. The LangGraph agent you built is a loop between a model node and a tool node with one conditional edge, and every advanced feature here (persistence, human approval, multi-agent) is a consequence of everything living on one inspectable state object. Once the model sticks, LangGraph stops being a framework you copy from and becomes one you design with.
Building agents that hold up in production takes more than a framework; it takes structured practice in system design, evaluation and deployment. Scaler's AI & Machine Learning program covers that path with mentorship from working engineers.