-
Type:
Task
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: ABX
-
None
TL;DR
MongoDBSaver in langgraph-checkpoint-mongodb saves the entire agent state as one MongoDB document on every step. Once the combined state passes MongoDB’s 16 MB document limit, the run fails. Store each large state channel as its own document and write only the channels that changed.
Context
LangGraph is a framework for building AI agents as graphs. A checkpointer saves the agent’s progress after each step, so a run can resume after a crash, pause for human input, or be rewound (“time travel”).
Concepts used below:
- Channel: a named slot in the agent’s state (for example, a message list or a tool result). A step may change some channels and leave the rest alone.
- Checkpoint: a snapshot of all channels at one step. Each channel has a version that changes only when its value changes.
- 16 MB limit: the maximum size of one MongoDB document. It is fixed and can’t be raised.
The problem: [MongoDBSaver.put |https://github.com/langchain-ai/langchain-mongodb/blob/main/libs/langgraph-checkpoint-mongodb/langgraph/checkpoint/mongodb/saver.py] serializes the whole checkpoint, every channel’s value, into one document and upserts all of it each step. It receives the list of changed channel versions (new_versions) but doesn’t use it. So the 16 MB limit applies to the sum of all channels, and unchanged data is rewritten every step. LangGraph’s Postgres saver already stores channel values separately and writes only changed ones.
Desired User Experience
The code the developer writes doesn’t change. Only what succeeds does.
State: a 7 MB channel “docs” and a 10 MB channel “tool_result”
checkpointer = MongoDBSaver(client)
graph = builder.compile(checkpointer=checkpointer)
graph.invoke(input, {“configurable”: {“thread_id”: “t1”}})
BEFORE: pymongo.errors.DocumentTooLarge (17 MB combined state in one document)
AFTER: succeeds. Each channel is its own document under 16 MB, and later steps write only the channels that changed.
Resuming from a brand-new saver instance also works:
fresh = MongoDBSaver(client)
builder.compile(checkpointer=fresh).invoke(None, {“configurable”: {“thread_id”: “t1”}})
A single channel over 16 MB fails early, writes nothing, and names the channel and size.
Goals
- A run with large total state works, with the 16 MB limit applying per channel.
- Each step writes only the channels that changed.
- Existing threads keep working after the upgrade with no forced migration.
Where: [langgraph/checkpoint/mongodb/saver.py|https://github.com/langchain-ai/langchain-mongodb/blob/main/libs/langgraph-checkpoint-mongodb/langgraph/checkpoint/mongodb/saver.py]
Acceptance Criteria
- The LangGraph checkpointer conformance tests (a minimal version) pass on MongoDB 7.0 and 8.x, for both the sync and async savers.
- The 7 MB + 10 MB example completes and resumes from a fresh saver instance.
- A single channel over 16 MB raises a clear error naming the channel and size, and nothing is written.
- For a 15 MB state with one changing channel, bytes written per step drop by at least half.
- A read never returns partial state: if a stored channel value is missing, it fails with an error.
- Threads created before the upgrade still read and write correctly; new threads use the new layout.
- The stored layout matches the JS saver’s, so both languages behave the same.
- Release notes say that downgrading after new-format threads exist is unsafe.
- A follow-up ticket exists to update the checkpointer documentation.
Additional Context
- Existing threads keep their original format because mixing old (integer) and new (string) channel versions inside one thread crashes the Python saver (comparing a string with an integer).
- A channel that grows without limit will still reach 16 MB eventually. This change makes that failure clear but doesn’t prevent it.
- Reference Solution Doc
- is related to
-
NODE-7993 JS MongoDB checkpointer: store each LangGraph state channel separately
-
- Needs Triage
-