Most simple large language model setups handle single-turn interaction well: take a question, call a model, return an answer. The harder cases show up quickly after that, when an agent needs to query a database, remember earlier context, or give a developer visibility into exactly why it made a given decision. LangGraph offers a clean structural answer to these problems by representing an agent as a graph, where nodes are units of work, edges define what runs next, and a shared state object carries the full message history through every step.
At the center of any LangGraph graph is its state, a typed structure that acts as shared memory for the whole system. Every node reads from that state and writes updates back into it, and nothing passes between nodes any other way. Nodes themselves are plain Python functions that take the current state and return a dictionary of the fields they want to change; anything a node doesn't return stays untouched. Edges define execution order, either directly connecting one node to the next, or routing through a conditional function that decides where to go based on the current state.
By default, when a node returns a value for a state field, it replaces whatever was there before. For fields meant to accumulate, such as a running log or conversation history, developers annotate the field with a reducer function; using Python's operator.add on a list field, for instance, appends new entries instead of overwriting the existing ones. LangGraph ships a built-in state type called MessagesState specifically for conversational agents, which uses a specialized message reducer that appends new messages while also handling deduplication and ordering automatically, sparing developers from stitching together conversation history by hand.
The core node of a conversational agent simply passes the current message list to a model and appends whatever it returns. Because the pattern is just a Python function wrapping a model call, swapping providers, from one closed model to another, or to a local model, only requires changing the import and model string; the rest of the node's logic is unaffected.
Tools extend what a model can answer beyond its training data. A tool in LangGraph is a plain Python function decorated to expose its docstring and signature to the model, which the model reads when deciding whether to call it and what arguments to pass, making precise docstrings important since vague ones lead to missed calls or malformed arguments. Binding tools to a model sends their schemas along with every request; when the model decides to use one, its response comes back with a populated tool-calls field rather than plain text.
LangGraph provides a prebuilt ToolNode that reads the requested tool call from the last message, executes the matching function, and wraps the result in a message appended back to state, alongside a conditions function that checks whether the last message contains a tool call and routes accordingly. An edge from the tool node back to the model-calling node closes the loop, sending the tool's result back so the model can produce a final answer grounded in it. Every tool use therefore costs two model calls in this pattern: one to decide what to look up, and one to interpret the result once it's back, a detail worth keeping in mind when reasoning about latency and cost as more tools get added.
Finally, a checkpointer lets a graph persist conversations across separate invocations by restoring a thread's saved state before execution and saving the updated state afterward, keyed to a thread identifier passed in configuration. An in-memory checkpointer works well for development, while production systems typically swap in a database-backed one without needing to change any of the surrounding graph code. The same primitives, state, nodes, and conditional edges, scale directly from a simple chatbot up to a multi-agent coordinator that routes requests to specialist agents, with the underlying architecture staying essentially unchanged as complexity grows.