symfonic.agent.checkpointer.factory¶
factory ¶
CheckpointerFactory protocol + three concrete implementations (v7.1.1).
Closes the §2 Item 2 carryover from the 7.1.x roadmap. The agent runtime
no longer peeks at MemoryLayer.SEMANTIC._graph.backend._pool to build
its LangGraph checkpointer; instead it picks one of the factories below
and treats the returned BaseCheckpointSaver as opaque.
Two-phase lifecycle¶
LangGraph's workflow.compile(checkpointer=...) runs in sync code and
requires the saver object up front. Production Postgres setup, however,
needs async I/O (pool open + idempotent DDL). The factory protocol
therefore exposes a two-phase lifecycle:
build_saver_sync()— constructs the saver with zero I/O. Safe from any sync constructor; the engine calls this in__init__.astart()— performs the async startup (open the pool, runsaver.setup()). Idempotent. The engine calls this once per agent lifetime from the top ofrun/stream/stream_typed.
For convenience async def build() runs both phases in order and
returns the saver — used by tests and callers that already live in an
async context.
Why dual pools?¶
langgraph-checkpoint-postgres only accepts psycopg connections.
The rest of the symfonic memory stack uses asyncpg for tenant SQL
(graph/vector backends via PostgresPoolManager). The two protocols
are incompatible inside langgraph.checkpoint.postgres._ainternal,
so PostgresCheckpointerFactory builds its own
psycopg_pool.AsyncConnectionPool from the same DSN. The pools
share a database; they do not share connections.
CheckpointerFactory ¶
Bases: Protocol
Constructs and owns a LangGraph checkpointer.
Implementations own their own connection lifecycle. The agent
Runtime instantiates the factory once, calls build_saver_sync
to obtain the saver for graph.compile(checkpointer=...), then
calls astart() once before the first real invocation. No
knowledge of memory-layer internals leaks across this boundary.
MemoryCheckpointerFactory ¶
Returns a MemorySaver (in-process, non-durable).
Use for unit tests and local development where pause/resume durability
is not required. astart and close are no-ops.
Source code in src/symfonic/agent/checkpointer/factory.py
MongoCheckpointerFactory ¶
MongoCheckpointerFactory(
uri: str,
database: str = "symfonic",
*,
checkpoint_collection_name: str = "checkpoints",
writes_collection_name: str = "checkpoint_writes",
)
Returns a MongoDBSaver backed by a pymongo.MongoClient.
The durable checkpointer for Mongo-backed deployments. Before this
factory existed, a Mongo graph backend had no matching checkpointer, so
the engine fell through to an ephemeral MemorySaver — checkpoints
(interactive resume, durable transcript) died on restart.
Like PostgresCheckpointerFactory's dual-pool design, this owns a
connection distinct from the graph/vector backend: MongoGraphBackend
uses motor's AsyncIOMotorClient for tenant I/O, while
langgraph-checkpoint-mongodb's MongoDBSaver requires a synchronous
pymongo.MongoClient. Its async methods (aput / aget_tuple /
alist) offload the blocking pymongo calls to a thread pool, so it drives
the async graph without blocking the event loop. The two clients share a
database; they do not share connections.
Lifecycle:
build_saver_syncconstructs the client withconnect=False(defers all network I/O and server monitoring) and wraps it inMongoDBSaver— no I/O.astartis a no-op:MongoDBSaverneeds nosetup()(its collections/indexes are created lazily on first write).closecloses the client.
Raises:
| Type | Description |
|---|---|
SymfonicAgentError(code='bad_config')
|
when no URI is provided. |
ImportError
|
when |
Source code in src/symfonic/agent/checkpointer/factory.py
PostgresCheckpointerFactory ¶
Returns an AsyncPostgresSaver backed by psycopg_pool.
Builds and owns its own AsyncConnectionPool. This pool is
DISTINCT from PostgresPoolManager (which is asyncpg-backed and
used for graph/vector I/O); the two pools share a database but do
not share connections.
Lifecycle:
build_saver_syncconstructs the pool withopen=Falseand wraps it inAsyncPostgresSaver— no I/O.astartopens the pool and runssaver.setup()(idempotent CREATE-IF-NOT-EXISTS DDL).closecloses the pool.
Raises:
| Type | Description |
|---|---|
SymfonicAgentError(code='bad_config')
|
when no DSN is provided. |
ImportError
|
when |
Source code in src/symfonic/agent/checkpointer/factory.py
astart
async
¶
Open the pool and run AsyncPostgresSaver.setup() once.
Source code in src/symfonic/agent/checkpointer/factory.py
SqliteCheckpointerFactory ¶
Returns an AsyncSqliteSaver backed by aiosqlite.
Accepts a filesystem path (":memory:" also valid). Holds the
underlying aiosqlite.Connection so close() can release it.
Requires the ask-user extra (aiosqlite +
langgraph-checkpoint-sqlite). A missing dependency raises an
ImportError that explicitly names the extra.