This document describes implementation-map slice 15. Normative requirements are persistence, interfaces, resilience, security, and testing. ADRs 0012, 0018, and 0009 record rationale; their proposed status does not override the specifications.
agent_framework_mongodb.MongoDBSessionStore derives only from the public
agent_framework.SessionStore in Agent Framework Core 1.13. The inherited
get(session_id), set(session_id, session), and delete(session_id) seam is
preserved. Serialization calls the public AgentSession.to_dict() and
AgentSession.from_dict() methods. Agent Framework's public
register_state_type() registry therefore preserves registered provider-owned
state without this package inspecting framework internals.
The additional concurrency surface is:
get_versioned()returnsMongoDBVersionedSession(session, version, expires_at);create()is create-only and returns version 1;compare_and_set(..., expected_version=...)returns the winning version; andcompare_and_delete(..., expected_version=...)returns whether it deleted.
An identical create retry returns version 1 only while the stored document is
still version 1. An identical compare-and-set retry returns only
expected_version + 1; matching payload at any later version is still stale.
Different create payloads, stale updates, and stale deletes raise
MongoDBConcurrencyError.
Unconditional set() resolves bounded CAS races rather than issuing a
last-writer update that could silently lose a concurrent write. Unconditional
delete() remains idempotent.
MongoDBSessionStoreOptions is frozen. Tenant, application, and agent scope
cannot change after construction, and at least one is required. Injected async
collections and AsyncMongoClient instances remain caller-owned. A store built
from connection settings owns its client; close() and the async context
manager close it exactly once. Construction performs no I/O or index mutation.
Every database filter includes _id, _kind, the canonical
scope_discriminator, every raw scope dimension (including BSON null),
and session_id. The identifier is a SHA-256 digest of a versioned canonical
scope and the opaque session-store key. A document ID or session ID alone is
never used as authorization. There is no bulk or empty-filter deletion API.
One current snapshot is stored per authorized key:
{
"_id": "<scoped sha-256>",
"_kind": "agent_session",
"schema_version": 1,
"framework_version": "agent-framework-core/1:AgentSession.to_dict/v1",
"scope_discriminator": "<scope sha-256>",
"tenant_id": "tenant-1",
"application_id": "application-1",
"agent_id": "agent-1",
"session_id": "opaque-store-key",
"version": 2,
"created_at": "<UTC BSON datetime>",
"updated_at": "<UTC BSON datetime>",
"expires_at": "<optional UTC BSON datetime>",
"session": {"type": "session", "session_id": "...", "state": {}},
"payload_hash": "<idempotency sha-256>"
}schema_version gates this MongoDB envelope. framework_version gates the
verified public AgentSession dictionary format. Unknown versions, malformed
versions, payloads, and expiration values raise MongoDBMappingError with
migration guidance; they are never interpreted best-effort. Python/.NET
physical collection interoperability is not claimed.
Updates replace the complete document only when the scoped current version
matches. created_at is stable, updated_at advances, and versions are positive
monotonic integers. expires_at must be future, timezone-aware input and is
normalized to UTC and truncated to BSON's millisecond precision before document
construction, persistence, retry comparison, and returned snapshot metadata.
This makes create and compare-and-swap retries stable across an actual BSON
round trip. PyMongo's default timezone-naive BSON datetimes are restored as UTC;
non-datetime values remain invalid. options.ttl only computes a default
expires_at independently from Memory and Chat History and uses the same
millisecond normalization.
ensure_indexes() is the only provisioning path; validate_indexes() is
read-only. Both identity indexes and the TTL index use a partial filter for
_kind: "agent_session" and string scope_discriminator.
The identity and version indexes explicitly use simple binary collation so
opaque session IDs retain case-sensitive identity under any collection default.
| Name | Keys | Options |
|---|---|---|
session_store_scope_identity |
scope_discriminator, session_id |
unique |
session_store_scope_version |
scope_discriminator, session_id, version |
regular |
session_store_expiration |
expires_at |
required, expireAfterSeconds: 0 |
The expiration index is always created and required by validation because
create() and compare_and_set() permit per-record expiration even when no
default ttl is configured. MongoDB TTL deletion is asynchronous, so
applications must not depend on immediate physical deletion at the expiration
instant.
Runtime privileges are find, insert, replace/update, and targeted delete on the
session collection. Index provisioning additionally requires createIndex;
validation requires index-list access. Use TLS, network controls, and MongoDB
encryption at rest. Client-side field-level encryption is deployment-owned and
is not configured automatically.
Direct operations fail to callers. Driver failures preserve the original
exception as __cause__ and map to authorization, retrieval, persistence, or
transient categories. Cancellation propagates without translation. Logs contain
only feature, operation, outcome, bounded result count, duration, and error
category. They never contain IDs, scopes, session payloads, database or
collection names, hosts, filters, or driver messages.
Public-seam unit tests are in
python/tests/unit/test_session_store.py. Language-neutral schema, index, and
concurrency outcomes are in
python/tests/contracts/fixtures/session_store_contract.json.
Credential-gated deployment coverage is in
python/tests/integration_persistence/test_session_store_integration.py and
uses a unique test-session- collection prefix with targeted cleanup.
From python, run:
uv run pytest tests\unit\test_session_store.py tests\contracts\test_session_store_contract.py
uv run pytest tests\integration_persistence -m integration_persistence
uv run ruff check src tests samples
uv run ruff format --check src tests samples
uv run mypy
uv run pyrightThe integration command skips cleanly without MONGODB_URI.