feat(extensions): add MongoDB session backend#2902
Conversation
Implements MongoDBSession under src/agents/extensions/memory/ following the extensions directory structure established in issue openai#1328. Uses pymongo>=4.13's native AsyncMongoClient (pymongo.asynchronous) rather than Motor. - Two-collection schema: agent_sessions (metadata) + agent_messages (items) - Chronological ordering via ObjectId; compound index on (session_id, _id) - Idempotent one-shot index creation with per-key asyncio.Lock - from_uri() factory with owned-client lifecycle tracking - Supports SessionSettings.limit and explicit per-call limit overrides - Gracefully skips corrupted/missing message_data documents - ping() / close() lifecycle helpers; close() only touches owned clients Adds 26 tests in tests/extensions/memory/test_mongodb_session.py using in-process fake pymongo types injected via sys.modules — no real MongoDB or pymongo installation required to run the suite. Install: pip install openai-agents[mongodb] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements both client metadata patterns per the add-client-metadata skill: - Pattern A (from_uri): passes driver=_DRIVER_INFO to AsyncMongoClient via client_kwargs.setdefault(), so caller-supplied driver values are preserved. - Pattern B (injected client): calls client.append_metadata(_DRIVER_INFO) in __init__ guarded by hasattr, compatible with PyMongo <4.14. _DRIVER_INFO is a module-level DriverInfo(name="openai-agents", version=...) where version is resolved at runtime via importlib.metadata. Adds three new tests covering both patterns and the no-overwrite guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c2a083d9f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- P1: Use client.close() (sync) instead of aclose() when closing an owned AsyncMongoClient; PyMongo does not expose aclose(). - P1: Include id(client) in _init_key so that different AsyncMongoClient instances pointing at different clusters each run their own index- creation pass rather than sharing a single guard. - P2: Add TypeError to the except tuple in get_items() and pop_item() so non-string BSON message_data values (e.g. int/object) are silently skipped rather than aborting history retrieval. Adds two new tests: test_non_string_message_data_is_skipped (P2) and test_different_clients_each_run_index_init (P1). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 436289f769
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
With pymongo now installed in dev deps, mypy resolves the real async types so several ignore codes were wrong or unused: - Make FakeAsyncMongoClient.close() async (MongoDBSession awaits it) - Change method-assignment ignores from [attr-defined] to [method-assign] - Add [assignment] to admin.command monkey-patch (overloaded function) - Remove ignores on _docs access and create_index reads that mypy now handles via the Any type parameter on AsyncCollection[Any] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 054d391340
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Replace asyncio.Lock process-wide guard with threading.Lock so _get_or_create_init_lock() is safe to call from any event loop - Replace id(client)-keyed dicts with WeakKeyDictionary so entries are pruned when clients are GC'd, preventing stale id reuse from skipping index creation on a new client - Add explicit seq field (monotonic per-session counter via $inc) to every message document and sort by seq instead of _id; ObjectId is only second-level accurate across processes and not reliably monotonic - Update FakeAsyncCollection in tests with find_one_and_update + $inc support; replace _initialized_keys/_init_locks clears with _init_state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b166de716
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…only guard The per-entry asyncio.Lock stored in _init_state was still loop-bound: a second event loop reusing the same AsyncMongoClient would hang or raise RuntimeError trying to acquire it. create_index is idempotent on the server side, so no async coordination is needed — concurrent first-time callers may each issue a redundant create_index round-trip, but that is harmless. Replace the asyncio.Lock with a plain bool guarded by the existing threading.Lock, removing the last asyncio.Lock from the process-wide registry entirely. Also removes the now-unused asyncio import. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks for putting this together. I think we should resolve one compatibility issue before merging.
Right now we advertise pymongo>=4.13, but the constructor calls client.append_metadata(...) behind a hasattr() check. append_metadata() is a 4.14-era API, and on older PyMongo clients attribute access is not a safe feature check here because valid attribute names can resolve to databases. In practice, that means the injected-client path can fail on the minimum version we currently declare.
Can we either:
- raise the MongoDB extra floor to 4.14, or
- keep 4.13 support and switch this to a callable/version-safe guard, then add coverage for that real compatibility path instead of relying only on the fully mocked
pymongotest setup?
If there is no specific reason to support 4.13, my preference is explicitly supporting 4.14+
…pend_metadata append_metadata was added in PyMongo 4.14. Now that the declared minimum matches, the hasattr compatibility guard is unnecessary and misleading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e399dd331c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
@seratch bumped to 4.14+ and removed hasattr() check. The 4.13 check is more of a legacy test in the event you're mixing PyMongo with AsyncPyMongo, which was introduced in 4.13. It's not needed here. |
…qual clients AsyncMongoClient defines __eq__/__hash__ based on connection properties, so WeakKeyDictionary lookups matched on value equality rather than object identity. Two distinct clients pointing to the same host/port would share a cache entry, causing the second client to skip index creation entirely. Switch _init_state to a plain dict[int, ...] keyed on id(client) and register a weakref.finalize callback to remove the entry on GC, preventing stale id() reuse from poisoning a future client. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f412b9870a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@alexbevi Thanks for updating this PR. The part looks good to me now. Can you continue working on this PR until Codex says 👍? The Codex reviews will be triggered every time you push new commits in this repo. |
…r session Previously weakref.finalize was called in __init__, so N sessions sharing one long-lived AsyncMongoClient would register N finalizers, causing unbounded memory growth and redundant teardown callbacks. Move the registration into _mark_init_done where the _init_state entry is first created — guarded by _init_guard — so the finalizer fires exactly once per client identity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Adds
MongoDBSessiontosrc/agents/extensions/memory/, a MongoDB-backed session store. I followed theextensions/memorydirectory structure established in #1328 as this was suggested as a path forward on the previous (now closed) PR #1364.Notes:
agent_sessions(metadata) +agent_messages(individual conversation items), with a compound index on(session_id, _id)for efficient per-session chronological queries.(id(client), database, sessions_collection, messages_collection)key, guarded by a per-keyasyncio.Lock. The client identity is included so that differentAsyncMongoClientinstances (e.g. pointing at different clusters) each get their own init pass.from_uri()factory owns the client lifecycle; injected clients are left unmanaged (caller's responsibility).SessionSettings.limitand explicit per-calllimitoverrides, consistent with all other session backends.Install:
pip install openai-agents[mongodb]Issue number
Relates to #1364, #1328
Checks
make lintandmake format