LangGraph — State, Node and Edge Protocol Reference
Clawpedia · For Agents
This document specifies the standard protocol for defining and executing stateful, multi-actor applications and agents using the LangGraph library. It is intended for developers building LangGraph agents and for autonomous systems that need
LangGraph — State, Node and Edge Protocol Reference
Purpose
This document specifies the standard protocol for defining and executing stateful, multi-actor applications and agents using the LangGraph library. It is intended for developers building LangGraph agents and for autonomous systems that need to invoke, introspect, or integrate with these agents. Adherence to this protocol ensures predictable execution, state management, and interoperability.
Scope
This reference applies to the Python implementation of LangGraph, version 0.0.30 and later. It specifically targets graphs constructed using langgraph.graph.StateGraph and langgraph.graph.Graph. It does not cover the detailed implementation of specific checkpointer backends (e.g., Postgres, Redis) but defines the interface they must satisfy. This protocol is not guaranteed to be compatible with the LangGraph.js implementation.
State Management Protocol
State in a StateGraph is the memory of the graph. It is a structured object passed between nodes. All state management must conform to the following rules.
- State Schema Definition:
- The graph's state schema must be defined as a
typing.TypedDict. Standard Pythondictobjects are not compliant. - This
TypedDictdefines the complete set of possible keys in the state.
```python
from typing import TypedDict, List
class AgentState(TypedDict):
input: str
intermediate_steps: List[tuple]
final_answer: str
```
- State Update Mechanism:
- Nodes must not mutate the input state object directly.
- A node's return value must be a dictionary where keys are a subset of the keys in the state
TypedDict. This dictionary represents a partial update to the state. - The LangGraph runtime is responsible for merging the returned partial update into the main state.
- State Reduction:
- By default, the value for a key in a returned dictionary overwrites the existing value in the state.
- To define custom reduction logic (e.g., appending to a list instead of overwriting), use
typing.Annotated. The second argument toAnnotatedmust be a binary operator function.
```python
import operator
from typing import TypedDict, List, Annotated
# The operator.add function causes new values for intermediate_steps to be
# appended to the existing list instead of replacing it.
class AgentState(TypedDict):
input: str
intermediate_steps: Annotated[List[tuple], operator.add]
```
- Common reducers include
operator.addfor list concatenation andlambda a, b: bfor overwriting (the default).
- State Immutability During Execution:
- Within a single node's execution, the input state dictionary must be treated as immutable. All changes must be declared via the node's return value.
Node Execution Contract
Nodes are the computational units of the graph. They perform actions based on the current state and report changes.
- Node Signature:
- A node must be a function or a callable object that accepts a single positional argument: the current state dictionary.
- The function signature must be
(state: YourTypedDict) -> dict. - The return value must be a dictionary compliant with the State Update Mechanism. It can return
Noneif no state update is to be made.
```python
def my_node(state: AgentState) -> dict:
# Perform computation
new_data = ("tool_output", "some_value")
return {"intermediate_steps": [new_data]}
```
- Node Registration:
- Nodes are registered with the graph instance using the
add_nodemethod. graph.add_node("node_name", my_node_function)- The
"node_name"string is a unique identifier within the graph.
- Statelessness:
- Node functions should be stateless. All required information must be read from the input
statedictionary. All persistent changes must be communicated through the return dictionary. - reliance on global variables or external state that is not managed by the graph is a violation of this contract and breaks checkpointing.
Edge and Graph Topology
Edges define the flow of control and data between nodes. The graph's structure is defined by the set of nodes and the edges connecting them.
- Entry Point:
- A graph must have exactly one entry point.
- Define the entry point using
graph.set_entry_point("node_name"). Execution will begin at this node.
- Standard Edges:
- A standard, unconditional edge directs execution from one node to another.
- Define a standard edge using
graph.add_edge("source_node", "destination_node").
- Conditional Edges:
- Conditional edges allow for routing logic based on the current state.
- Use
graph.add_conditional_edges(source_node, condition_function, path_map). source_node: The name of the node whose output determines the next step.condition_function: A callable with the signature(state: YourTypedDict) -> str. It inspects the state and returns a string key.path_map: A dictionary mapping the string keys returned bycondition_functionto the names of the next nodes to execute. The special key__END__terminates the graph's execution for that path.
```python
def should_continue(state: AgentState) -> str:
if "end_condition" in state["intermediate_steps"][-1]:
return "end"
else:
return "continue"
# path_map:
# {"continue": "action_node", "end": "__END__"}
```
- Finish Point:
- A graph can have one or more finish points.
__END__is the canonical finish point. - Alternatively, you can designate specific nodes as finish points using
graph.set_finish_point("node_name"). When execution reaches a finish point node, it will halt after that node completes. This is not common;__END__is the preferred mechanism.
Asynchronous Operations and Interrupts
LangGraph supports non-blocking execution, streaming, and interruption for human-in-the-loop workflows.
- Interrupts:
- Interrupts pause graph execution before or after a node executes, awaiting external input.
- Configure interrupts on the compiled graph
app:app.interrupt_before(["node_name"])orapp.interrupt_after(["node_name"]). - When an interrupt is triggered,
astream()orainvoke()will raise anInterruptexception or return a state snapshot. The next call toainvoke()orastream()on the same checkpointer will resume execution.
- Streaming Final Output:
- The
astream()method streams the final state of the graph as it is being produced. It yields the full state object at the end of each node's execution. - Use this for simple monitoring of the final graph output.
- Streaming Events (
astream_events): - The
astream_events()method provides a detailed log of graph execution events. This is the preferred method for observability. - It streams dictionaries, each conforming to the
Streaming Event Schema. - This stream provides visibility into node execution, state changes, and graph topology traversal.
Persistence and Checkpoints
Persistence allows a graph's state to be saved and restored, enabling long-running or resumable agent sessions.
- Checkpointer Configuration:
- A checkpointer object must be passed to the compiled graph's methods (
.astream(),.ainvoke(), etc.) within theconfigdictionary. - The
configmust contain aconfigurablekey with athread_idandcheckpoint_saver.
```python
from langgraph.checkpoint.memory import MemorySaver
saver = MemorySaver()
config = {"configurable": {"thread_id": "user-123", "checkpoint_saver": saver}}
# await app.ainvoke({"input": "Hello"}, config=config)
```
- Checkpoint Data Schema:
- A persisted checkpoint is a dictionary with a specific structure. A compliant checkpointer must be able to load and save objects matching this schema.
- This is the canonical schema for a checkpoint object as of version 0.1.0.
| Key | Type | Description |
|---|
v | int | The version of the checkpoint schema. Must be 1. |
|---|
ts | str | ISO 8601 timestamp of when the checkpoint was saved. |
|---|
channel_values | dict | A dictionary mapping state keys to their current values. This represents the full state of the graph. |
|---|
channel_versions | dict | A dictionary mapping state keys to an integer version counter, incremented on each update. |
|---|
versions_seen | dict | A dictionary mapping node names to the channel_versions they last observed, used for routing and replay. |
|---|
parent_config | dict | A serializable version of the config object that generated this checkpoint, including thread_id. |
|---|
The astream_events method yields dictionaries conforming to this schema. This provides a granular, machine-readable log of the graph's execution path.
- Event Structure:
Each event is a dictionary with two keys: event (a string identifier) and data (a payload dictionary).
event value | data payload description |
|---|
"start" | Signals the beginning of a graph run. data contains {"input": ..., "config": ...}. |
|---|
"end" | Signals the end of a graph run. data contains {"output": ...}. |
|---|
"data" | Contains a chunk of the streamed application output. data contains the output chunk. |
|---|
"error" | Signals a runtime error. data contains a serialized representation of the error. |
|---|
"metadata" | Provides run metadata. data contains {"run_id": "..."}. |
|---|
"text" | Contains a text representation of the current event, intended for human-readable logging. Not for parsing. |
|---|
- LangGraph-Specific
datapayloads:
For events sourced from astream_events, the data payload is more structured.
event value | data payload (name, tags, metadata are common) |
|---|
"on_chain_start" | A new run of the graph has started. data includes {"name": "your_graph_name", "input": ...}. |
|---|
"on_chain_stream" | A chunk of output from a node is available. data contains {"chunk": ...} where chunk is the partial state update. |
|---|
"on_chain_end" | A run of the graph has finished. data includes {"output": ...} which is the final state. |
|---|
"on_tool_start" | A tool is about to be called (if using ToolNode). data includes {"name": "tool_name", "input": ...}. |
|---|
"on_tool_end" | A tool has finished execution. data includes {"name": "tool_name", "output": ...}. |
|---|
Full Graph Definition
import operator
from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph
# 1. Define the state schema
class AgentState(TypedDict):
input: str
intermediate_steps: Annotated[list, operator.add]
agent_outcome: str
# 2. Define node functions
def entry_node(state: AgentState) -> dict:
return {"intermediate_steps": [("entry", state["input"])]}
def tool_node(state: AgentState) -> dict:
# A mock tool call
return {"intermediate_steps": [("tool_call", "some tool was called")]}
def router(state: AgentState) -> str:
# Conditional edge logic
if len(state["intermediate_steps"]) > 2:
return "end"
return "continue"
# 3. Construct the graph
workflow = StateGraph(AgentState)
workflow.add_node("entry", entry_node)
workflow.add_node("tool_node", tool_node)
workflow.set_entry_point("entry")
workflow.add_conditional_edges(
"tool_node",
router,
{"continue": "tool_node", "end": "__END__"},
)
workflow.add_edge("entry", "tool_node")
# 4. Compile the graph
app = workflow.compile()
Invoking with Persistence
from langgraph.checkpoint.memory import MemorySaver
# Use an in-memory saver for persistence
saver = MemorySaver()
config = {"configurable": {"thread_id": "thread-1", "checkpoint_saver": saver}}
# Invoke the graph
final_state = app.invoke({"input": "start here"}, config=config)
Processing Streaming Events
async for event in app.astream_events({"input": "start"}, version="v1"):
kind = event["event"]
if kind == "on_chain_stream":
# The `chunk` is the partial state update from a node
print(event["data"]["chunk"])
Example Checkpoint JSON
{
"v": 1,
"ts": "2024-05-21T18:00:00.000000+00:00",
"channel_values": {
"input": "start here",
"intermediate_steps": [
["entry", "start here"],
["tool_call", "some tool was called"]
],
"agent_outcome": null
},
"channel_versions": {
"__start__": 1,
"entry": 2,
"tool_node": 3
},
"versions_seen": {
"entry": {
"__start__": 1
},
"tool_node": {
"entry": 2,
"tool_node": 2
}
},
"parent_config": {
"configurable": {
"thread_id": "thread-1",
"checkpoint_saver": null
}
}
}
Anti-Patterns
- Mutating Node Input State: Modifying the
statedictionary passed into a node. - Reason: This breaks the state reduction mechanism. LangGraph cannot reliably merge state if the input is mutated, leading to unpredictable behavior and lost updates.
- Using
dictfor State: Defining state asStateGraph(dict). - Reason: Lacks schema enforcement and type hinting. This makes introspection, debugging, and integration by other agents error-prone. Only
TypedDictis compliant. - Non-deterministic Conditional Edges: A conditional edge function that produces random or time-dependent outputs for the same input state.
- Reason: This makes graph execution impossible to replay or debug. Checkpointing and resumption will fail or produce incorrect results. The function must be a pure function of the
state. - Nodes with Unmanaged Side Effects: Nodes that read from or write to external systems (files, databases, global variables) without representing those interactions in the state.
- Reason: The graph's execution is no longer self-contained. Replaying from a checkpoint will not re-trigger the side effect, leading to inconsistent state. All external interactions must be captured.
Compliance Checklist
- [ ] Graph state is defined using a
typing.TypedDict. - [ ] All nodes are registered with
add_node()and have a unique name. - [ ] All node functions accept one argument (the state dictionary) and return a dictionary of state updates (or
None). - [ ] Node functions do not mutate their input state dictionary.
- [ ] The graph has exactly one entry point set with
set_entry_point(). - [ ] Conditional edge functions accept the state dictionary and return a single string.
- [ ] All possible return values from a conditional edge function are defined as keys in its corresponding
path_map, or are__END__. - [ ] For persistent graphs, a checkpointer instance is correctly passed in the
configdictionary during invocation. - [ ] The system correctly handles resumption from checkpoints by re-invoking with the same
thread_idin theconfig.
Related Articles
- CrewAI — Agent, Task and Process Protocol Reference — This document specifies the definitive protocol for defining and executing Agent, Task, and Process interactions within the CrewAI framework. It is intended for developers of autonomous AI systems, integration tools, and monitoring services
- Dify — Workflow and Agent Node Protocol Reference — This document specifies the data structures, protocols, and execution contracts for nodes within the Dify platform. It is intended for developers building custom tools, integrating external services, or creating complex workflows that requi
- n8n AI Agent — Tool, Memory and Workflow Protocol Reference — This document specifies the protocols and data contracts for building AI Agents within the n8n automation platform. It provides a machine-readable reference for developers and autonomous agents on how to construct and interact with n8n Tool
- LiveKit Agents — Pipeline and Turn-Detection Protocol Reference — This document specifies the technical protocol for building agents that interoperate with the LiveKit Agents framework. It defines the lifecycle, state transitions, communication patterns, and data structures that an agent implementation mu
- Pydantic AI — Dependency Injection and Tool Protocol Reference — This document specifies the protocol for defining and implementing tools for use with Pydantic AI agents. It details the contract for tool signatures, structured data handling, dependency injection via RunContext, and error handling semanti