NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #446 most downloaded on PyPI
Building stateful, multi-actor applications with LLMs
Last release 13 days ago
21 Sep 2026
Ships fairly regularly
a new release about every 2 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
10 versions withdrawn
withdrawn after publishing
3 years old
277 releases · first in 2024
chore(deps): bump soupsieve from 2.8.4 to 2.9 in /libs/langgraph
Changes since 1.2.11
One column per month.
langgraph==1.2.12
Compare
feat(langgraph): expose trace_policy on add_node
Changes since 1.2.10
trace_policy on add_node (#8523)chore(deps): bump jupyterlab from 4.5.9 to 4.5.10 in /libs/langgraph
Changes since 1.2.9
trace_policy on add_node (#8362)fix: updateState metadata/counters for delta channel
fix: delta channel bug with updateState on fresh thread will force snapshot instead of stub checkpoint
Changes since 1.2.7
fix(langgraph): snapshot DeltaChannel overwrite supersteps
Changes since 1.2.6
DeltaChannel overwrite supersteps (#8125)Overwrite survive JSON roundtrips (#8127)fix: nested subgraph inherits parent checkpoint_ns (regression in 1.2.3)
Changes since 1.2.5
README.md structure (#8064)fix(langgraph): merge lc_versions config metadata
Changes since 1.2.4
lc_versions config metadata (#8052)test(sdk-py): add factory-graph integration test exercising the server factory path
Changes since 1.2.3
feat(langgraph): wire RemoteGraph.interleave to sdk-py interleave_projections
Changes since 1.2.2
chore(langgraph): bump version to 1.2.2
Changes since 1.2.1
feat(langgraph): add before_builtins opt-in for stream transformers
Changes since 1.2.0
before_builtins opt-in for stream transformers (#7882)release: bump alpha packages to official versions
Changes since 1.2.0a7
release: alpha bump (a4) for langgraph, checkpoint, checkpoint-postgres
Changes since 1.2.0a6
This release adds finer-grained control over node execution — timeouts, error recovery, and graceful shutdown — a new channel type that cuts checkpoin
langgraph v1.2 (alpha)This release adds finer-grained control over node execution — timeouts, error recovery, and graceful shutdown — a new channel type that cuts checkpoint overhead for long-running threads, and a new content-block-centric streaming API (v3) with typed, per-channel projections.
DeltaChannel (beta)A new channel type that stores only the incremental delta at each step rather than re-serializing the full accumulated value. Most useful for channels that grow large over time — for example, a message list in a long-running thread. Without it, the full message list is written into every checkpoint; with DeltaChannel, only the new messages from each step are stored.
Use snapshot_frequency=K to write a full snapshot every K steps and bound read latency:
from typing import Annotated, Sequence
from typing_extensions import TypedDict
from langgraph.channels import DeltaChannel
# batch reducer: receives the current value and all writes from the superstep at once
def list_reducer(messages: list[str], writes: Sequence[list[str]]) -> list[str]:
return [*messages, *(item for write in writes for item in write)]
class State(TypedDict):
messages: Annotated[list[str], DeltaChannel(list_reducer, snapshot_frequency=5)]
Pass timeout= to add_node to cap how long a single attempt may run. You can set a hard wall-clock limit (run_timeout), an idle limit that resets on progress (idle_timeout), or both. When the limit fires, LangGraph raises NodeTimeoutError, clears any writes from that attempt, and hands off to the retry policy. Timeouts apply to async nodes only; sync nodes with timeout= are rejected at compile time.
from langgraph.types import RetryPolicy, TimeoutPolicy
# Hard wall-clock limit: abort after 2 minutes regardless of progress
builder.add_node(
"call_model",
call_model,
timeout=TimeoutPolicy(run_timeout=120),
retry_policy=RetryPolicy(max_attempts=3),
)
# Idle limit: abort if no output is yielded for 30 seconds —
# useful for streaming nodes where partial progress should keep the clock reset
builder.add_node(
"stream_response",
stream_response,
timeout=TimeoutPolicy(idle_timeout=30),
)
# Both: cap total duration and require steady progress
builder.add_node(
"fetch_and_parse",
fetch_and_parse,
timeout=TimeoutPolicy(run_timeout=120, idle_timeout=30),
)
# Heartbeat: yield sentinel values to reset the idle clock during long async work,
# without writing anything meaningful to state
async def slow_tool(state: State) -> AsyncIterator[dict]:
async for chunk in call_external_api():
process(chunk)
yield {} # heartbeat — resets idle_timeout, writes nothing
builder.add_node(
"slow_tool",
slow_tool,
timeout=TimeoutPolicy(idle_timeout=10),
)
Pass error_handler= to add_node to run a recovery function after all retries are exhausted. The handler receives a typed NodeError with the failing node name and exception, and can return a Command to update state and route to a different node — useful for Saga/compensation patterns where you want to recover gracefully rather than abort the graph. Handler failures propagate up normally; interrupt() inside a node bypasses the handler as expected.
from langgraph.errors import NodeError
from langgraph.types import Command, RetryPolicy
def payment_error_handler(state: State, error: NodeError) -> Command:
return Command(
update={"status": f"compensated: {error.error}"},
goto="finalize",
)
builder.add_node(
"charge_payment",
charge_payment,
retry_policy=RetryPolicy(max_attempts=3, retry_on=ConnectionError),
error_handler=payment_error_handler,
)
Stop an in-flight run cooperatively — after the current superstep completes — and save a resumable checkpoint. Unlike timeouts, which fire mid-node, graceful shutdown waits for running work to finish before halting.
import signal
from langgraph.runtime import RunControl
from langgraph.errors import GraphDrained
control = RunControl()
signal.signal(signal.SIGTERM, lambda *_: control.request_drain("sigterm"))
try:
result = graph.invoke(inputs, config, control=control)
except GraphDrained as e:
# Stopped cleanly after the current superstep. Resume later with the same config.
log.info("graph drained: %s", e.reason)
# Resume on next startup:
result = graph.invoke(None, config)
Read the graceful shutdown docs →
All five features require langgraph>=1.2. Timeouts and error handlers are Python-only; retry policies continue to work in both Python and TypeScript.
A new content-block-centric streaming protocol that replaces dict-shaped events with typed, per-channel streams. version="v3" returns a GraphRunStream (sync) or AsyncGraphRunStream (async) — a handle whose typed projections the caller drives by iterating, rather than an iterator of StreamEvent dicts that callers must filter and reassemble.
run = graph.stream_events(input, version="v3")
for state in run.values: # one snapshot per superstep
print(state)
print(run.output) # final state
print(run.interrupted, run.interrupts)
run.output, run.interrupted, and run.interrupts are populated regardless of which transformers are registered, so the "run to completion and read the result" path stays one line.
run.messages yields one ChatModelStream (or AsyncChatModelStream) per LLM call. Each exposes typed sub-projections — text tokens, reasoning, tool calls, usage — so consumers can pick the slice they care about without filtering chunks by hand:
async for chat in graph.astream_events(input, version="v3").messages:
async for token in chat.text:
print(token, end="", flush=True)
tool_calls = await chat.tool_calls.collect()
final = await chat.output # finalized AIMessage
This is the recommended path for token-level streaming to a UI in 1.2. Content-block streaming requires v3 end-to-end, including any inner graphs.
| Projection | What it carries |
|---|---|
run.values |
full state snapshot per superstep (also drives run.output etc.) |
run.messages |
one ChatModelStream per LLM call |
run.lifecycle |
subgraph started / completed / failed payloads |
run.subgraphs |
navigation handles for direct-child subgraphs |
Other channels are opt-in — register them via compile(transformers=[...]) or the call-site transformers= on stream_events:
| Transformer | Adds |
|---|---|
UpdatesTransformer |
run.updates |
CustomTransformer |
run.custom |
CheckpointsTransformer |
run.checkpoints |
TasksTransformer |
run.tasks |
DebugTransformer |
run.debug |
from langgraph.stream import UpdatesTransformer, CustomTransformer
graph = builder.compile(transformers=[UpdatesTransformer, CustomTransformer])
run = graph.stream_events(input, version="v3")
for update in run.updates:
print(update)
Projections are single-consumer by default — iterating run.values twice raises. Use projection.tee(n) (or atee(n) async) for fan-out, or run.interleave("messages", "values") to consume multiple projections in arrival order.
Compile with transformers=[...] to register custom projections that propagate to subgraph scopes automatically. Each transformer declares the stream modes it needs; the runtime builds the union and subscribes to exactly that — no hardcoded defaults, no overhead for modes nobody consumes.
from typing import Any
from langgraph.stream import StreamTransformer, StreamChannel
class TokenCounter(StreamTransformer):
"""Surface per-call token usage for chat models at the run scope."""
required_stream_modes = ("messages",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._scope_list = list(scope)
self.usage: StreamChannel[dict[str, int]] = StreamChannel()
def init(self) -> dict[str, Any]:
return {"token_usage": self.usage}
def process(self, event) -> bool:
if event["method"] != "messages":
return True
if event["params"]["namespace"] != self._scope_list:
return True
payload, _meta = event["params"]["data"]
if not isinstance(payload, dict) or payload.get("event") != "message-finish":
return True
if "usage" in payload:
self.usage.push(payload["usage"])
return True
graph = builder.compile(transformers=[TokenCounter])
run = graph.stream_events(input, version="v3")
run.output # drive to completion
usages = list(run.extensions["token_usage"])
total_in = sum(u.get("input_tokens", 0) for u in usages)
total_out = sum(u.get("output_tokens", 0) for u in usages)
version="v3"; version="v1" and version="v2" are unchanged.release: alpha bump prebuilt 1.1.0a2, langgraph 1.2.0a5
Changes since 1.2.0a4
release: alpha bump prebuilt 1.1.0a1, langgraph 1.2.0a4
Changes since 1.2.0a3
feat(langgraph): dispatch stream_events(version='v3') on Pregel
Changes since 1.2.0a2
DeltaChannel: store sentinel in blobs, reconstruct from checkpoint_writes (#7586)fix(langgraph): make NodeTimeoutError retryable by default
Changes since 1.2.0a1
feat: allow graph to graceful shutdown/drain by request
Changes since 1.1.10
DeltaChannel: store sentinel in blobs, reconstruct from checkpoint_writes (#7586)release(prebuilt): 1.0.12, langgraph 1.1.10
Changes since 1.1.9
chore(langgraph): bump version 1.1.8 -> 1.1.9
Changes since 1.1.8
fix(langgraph): remove strict add_handler type check that breaks OTel instrumentation
Changes since 1.1.7
fix: time travel when going back to interrupt node
Changes since 1.1.7a2
chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/langgraph
Changes since 1.1.7a1
test(langgraph): use monotonic clock in flaky streaming test
Changes since 1.1.6
Changes since 1.1.5 * release: langgraph 1.1.6 (#7407) * fix: execution info patching
Changes since 1.1.5
release: prebuilt 1.0.9 and langgraph 1.1.5
Changes since 1.1.4
fix(langgraph): avoid recursion limit default sentinel collision
Changes since 1.1.3
release(checkpoint-postgres): 3.0.5
Changes since 1.1.2
feat: add context for remote graph api
Changes since 1.1.1
fix: replay bug, direct to subgraphs
Changes since 1.1.0
…under the hood. However, this is deprecated and emits a LangGraphDeprecatedSinceV11 warning. It will be removed in v3.0 — migrate to result.value and…
Changes since 1.0.10
LangGraph 1.1 introduces version="v2" — a new opt-in streaming format that brings full type safety to stream(), astream(), invoke(), and ainvoke().
v1 (default, unchanged): stream() yields bare tuples like (stream_mode, data) or just data. invoke() returns a plain dict. Interrupts are mixed into the output dict under "__interrupt__".
v2 (opt-in): stream() yields strongly-typed StreamPart dicts with type, ns, data, and (for values) interrupts fields. invoke() returns a GraphOutput object with .value and .interrupts attributes. When your state schema is a Pydantic model or dataclass, outputs are automatically coerced to the correct type.
invoke() / ainvoke() with version="v2"from langgraph.types import GraphOutput
result = graph.invoke({"input": "hello"}, version="v2")
# result is a GraphOutput, not a dict
assert isinstance(result, GraphOutput)
result.value # your output — dict, Pydantic model, or dataclass
result.interrupts # tuple[Interrupt, ...], empty if none occurred
With a non-"values" stream mode, invoke(..., stream_mode="updates", version="v2") returns list[StreamPart] instead of list[tuple].
stream() / astream() with version="v2"for part in graph.stream({"input": "hello"}, version="v2"):
if part["type"] == "values":
part["data"] # OutputT — full state
part["interrupts"] # tuple[Interrupt, ...]
elif part["type"] == "updates":
part["data"] # dict[str, Any]
elif part["type"] == "messages":
part["data"] # tuple[BaseMessage, dict]
elif part["type"] == "custom":
part["data"] # Any
elif part["type"] == "tasks":
part["data"] # TaskPayload | TaskResultPayload
elif part["type"] == "debug":
part["data"] # DebugPayload
Each stream mode has its own TypedDict — ValuesStreamPart, UpdatesStreamPart, MessagesStreamPart, CustomStreamPart, CheckpointStreamPart, TasksStreamPart, DebugStreamPart — all importable from langgraph.types. The union type StreamPart is a discriminated union on part["type"], enabling full type narrowing in editors and type checkers.
When your graph's state schema is a Pydantic model or dataclass, version="v2" automatically coerces outputs to the declared type:
from pydantic import BaseModel
class MyState(BaseModel):
answer: str
count: int
graph = StateGraph(MyState)
# ... build graph ...
compiled = graph.compile()
result = compiled.invoke({"answer": "", "count": 0}, version="v2")
assert isinstance(result.value, MyState) # not a dict!
version="v1" — existing code works without changes.GraphOutput supports old-style best-effort access to graph values and interrupts. Dict-style access (result["key"], "key" in result, result["__interrupt__"]) still works and delegates to result.value / result.interrupts under the hood. However, this is deprecated and emits a LangGraphDeprecatedSinceV11 warning. It will be removed in v3.0 — migrate to result.value and result.interrupts at your convenience.result = graph.invoke({"input": "hello"}, version="v2")
# Old style — still works, but deprecated
result["input"] # delegates to result.value["input"]
result["__interrupt__"] # delegates to result.interrupts
"input" in result # delegates to "input" in result.value
# New style — preferred
result.value["input"]
result.interrupts
version="v1" remains the default. All existing code continues to work.version="v2" to individual invoke()/stream() calls to get typed outputs.GraphOutput, StreamPart, and individual part types from langgraph.types for type-safe code.chore(deps-dev): bump the all-dependencies group across 1 directory with 3 updates
Changes since 1.0.10rc1
chore: add tests to confirm expected subgraph persistence behavior
Changes since 1.0.9
make type target for type checking (#6748)fix: sequential interrupt handling w/ functional API
Changes since 1.0.8
fix: pydantic messages double streaming
Changes since 1.0.7
Runtime and ToolRuntime class descriptions for clarity (#6689)thread_id (#6515)add_node overloads (#6514)fix: aiosqlite's breaking change
Changes since 1.0.6
uv lock --upgrade (#6671)fix: flip default on base cache
Changes since 1.0.5
release(langgraph): bump to 1.0.5
Changes since 1.0.4
chore: pop thread ID from configurable fields in remote graph
Changes since 1.0.3
feat(docs): warn that StateGraph is a builder class
Changes since 1.0.2
StateGraph is a builder class (#6417)PartialState rendering in MkDocs (#6416)invoke and ainvoke docstrings (#6415)stream and astream docstrings (#6414)StateGraph (#6308)pyproject.toml links (#6364)chore: bump prebuilt dep for lg
Changes since 0.6.11
python.langchain links with new docs.langchain (#6352)chore: Restrict "json" type deserialization
Changes since 1.0.0
release: langgraph + langgraph-prebuilt v1.0.0
Changes since 1.0.0rc1
chore: release rcs for prebuilt + langgraph
Changes since 0.6.10
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
chore: Allow checkpoint 3.0 in 0.6.*
Changes since 0.6.10
chore(langgraph): bump langgraph version
Changes since 0.6.9
chore(checkpoint): bump patch version
Changes since 1.0.0a4
fix(langgraph): handle multiple annotations w/ BaseChannel detection
Changes since 1.0.0a3
BaseChannel detection (#6210)CheckpointTask.state can be a StateSnapshot (#6201)AsyncPregelLoop (#6167)defer=True (#6130)RemoteGraph (#6140)get_graph generates unexpected conditional edge (#6122)fix: Unwrap Required/NotRequired special forms before resolving channel/reducer annotations
Changes since 1.0.0a2
chore(langgraph): Add passthrough params/headers to invoke/stream/etc.
Changes since 0.6.5
feat(langgraph): implement redis node level cache
Changes since 0.6.4
chore(langgraph): deprecate MessageGraph
Changes since 0.6.3
updated_channels to checkpoint (#5828)MessageGraph (#5843)fix(langgraph): fix up deprecation warnings
Changes since 0.6.2
AgentState (#5801)config param (#5798)invoke and ainvoke (#5771)fix(prebuilt): assign context_schema to config_schema with correct condition
Changes since 0.6.1
context_schema to config_schema with correct condition (#5746)Your coding agent can read these notes before it upgrades. Set up the MCP server →