Python: Move SessionStore to core and persist Foundry Responses sessions - #7306
Python: Move SessionStore to core and persist Foundry Responses sessions#7306eavanvalkenburg wants to merge 14 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Moves Python session persistence primitives into agent-framework-core and updates Foundry Responses hosting to persist/restore full AgentSession state (not just transcript history), including a new msgspec-backed file store and explicit per-user storage isolation for hosted scenarios.
Changes:
- Introduces experimental
SessionStore(in-memory) andFileSessionStore(JSON/MessagePack, atomic replace) in core, plus a stricter session-state type registry viaregister_state_type. - Switches
FileHistoryProviderdefault JSON codec to msgspec (and adds optional MessagePack), deprecating custom JSONdumps/loadshooks. - Updates Foundry Responses hosting to persist sessions across calls, partition durable storage by resolved authenticated identity, and harden isolation/scoping across sessions, checkpoints, and approvals.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| python/uv.lock | Adds msgspec to the locked dependency set. |
| python/samples/04-hosting/af-hosting/README.md | Updates sample documentation to reflect core-owned SessionStore. |
| python/samples/04-hosting/af-hosting/local_telegram/README.md | Updates Telegram session-id format description (no :). |
| python/samples/04-hosting/af-hosting/local_responses/README.md | Documents use of core FileSessionStore for session snapshots. |
| python/samples/04-hosting/af-hosting/local_responses/app.py | Wires AgentState to a FileSessionStore for local persistence. |
| python/samples/02-agents/conversations/file_history_provider.py | Removes optional orjson path; documents msgspec default. |
| python/samples/02-agents/conversations/file_history_provider_conversation_persistence.py | Removes optional orjson path; documents msgspec default. |
| python/samples/02-agents/context_providers/simple_context_provider.py | Demonstrates explicit register_state_type for persisted custom state. |
| python/packages/hosting/tests/hosting/test_state.py | Updates hosting tests for core-owned SessionStore and new ID validation. |
| python/packages/hosting/README.md | Documents new core session store APIs and ID restrictions. |
| python/packages/hosting/agent_framework_hosting/_state.py | Removes hosting-owned store implementation; uses core SessionStore and validation. |
| python/packages/hosting/agent_framework_hosting/init.py | Stops re-exporting SessionStore. |
| python/packages/hosting-telegram/tests/hosting_telegram/test_parsing.py | Updates tests for new Telegram session-id format. |
| python/packages/hosting-telegram/README.md | Updates docs to refer to core session stores and new ID format. |
| python/packages/hosting-telegram/agent_framework_hosting_telegram/_parsing.py | Changes telegram_session_id output to use underscore-delimited IDs. |
| python/packages/hosting-responses/README.md | Documents session store location in core. |
| python/packages/foundry_hosting/tests/test_responses.py | Adds/updates tests for session persistence and isolation behavior. |
| python/packages/foundry_hosting/README.md | Documents hosted persistence behavior, default storage layout, and isolation resolver. |
| python/packages/foundry_hosting/agent_framework_foundry_hosting/_session_isolation.py | Adds isolation resolver and scoped file session store wrapper. |
| python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py | Implements session persistence + isolation scoping across Responses lifecycle. |
| python/packages/foundry_hosting/agent_framework_foundry_hosting/init.py | Exposes new isolation-related public types. |
| python/packages/core/tests/core/test_sessions.py | Adds comprehensive tests for state registry + session/file stores + msgspec history changes. |
| python/packages/core/tests/core/test_harness_memory.py | Removes custom JSON codec plumbing now that msgspec is default. |
| python/packages/core/tests/core/test_feature_stage.py | Adds test ensuring internal subclasses don’t warn under experimental features. |
| python/packages/core/pyproject.toml | Adds msgspec runtime dependency. |
| python/packages/core/AGENTS.md | Updates package map docs for new session persistence APIs. |
| python/packages/core/agent_framework/_sessions.py | Adds session stores + registry hardening + msgspec-based history/session serialization. |
| python/packages/core/agent_framework/_harness/_memory.py | Marks history codec overrides as deprecated. |
| python/packages/core/agent_framework/_feature_stage.py | Avoids experimental warnings for internal framework subclasses. |
| python/packages/core/agent_framework/init.pyi | Exports SessionStore/FileSessionStore typing surface. |
| python/packages/core/agent_framework/init.py | Exports SessionStore/FileSessionStore runtime surface. |
| python/PACKAGE_STATUS.md | Adds SESSION_STORE feature-stage entry and updates staged-API listing. |
| python/AGENTS.md | Adds “Changelog Ownership” guidance for Python PRs. |
| python/.github/skills/agent-framework-py-release/SKILL.md | Documents that release workflow owns python/CHANGELOG.md. |
| docs/decisions/0033-python-session-store-serialization.md | Adds ADR for session store ownership + msgspec serialization strategy. |
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 85%
✓ Correctness
The implementation is solid overall. The session store, serialization registry, and feature-stage changes are well-tested and handle edge cases properly. One low-severity behavioral asymmetry exists in SessionStore where set() stores by reference while get() deep-copies, creating different mutation semantics than FileSessionStore.
✓ Security Reliability
The implementation is well-structured with proper security controls: path traversal protection (validate_session_id + resolve + is_relative_to), atomic file writes via os.replace, and corruption quarantining. The serialization pipeline correctly chains exceptions from custom decoders into ValueError for quarantine handling. The in-memory SessionStore stores direct references on write (no deep-copy) while deep-copying on read — this is asymetric but explicitly documented ('Reads return independent working copies'). The main reliability note is that SessionStore.set() stores a direct reference, meaning external mutation after set() silently corrupts the stored value, creating a behavioral difference from FileSessionStore which inherently provides write isolation through serialization.
✓ Test Coverage
The test coverage for this PR is generally strong. The new SessionStore, FileSessionStore, register_state_type enhancements, and FileHistoryProvider msgpack support all have meaningful tests with good edge-case coverage (corruption quarantine, concurrent writes, non-finite floats, reserved Windows filenames). Two minor test gaps exist: (1) no test for
register_state_typeraising when only one of encoder/decoder is provided, and (2) no test for the FileSessionStore quarantine-failure path where the OSError branch is hit. Both are low-priority given the scope of existing coverage. The test coverage for the new session persistence lifecycle in Foundry hosting is solid overall, with tests for success, failure, streaming, multi-turn, file store isolation, and corrupt file recovery. However, there's a notable gap: thefinallyclause in_handle_inner_agent(line 826-828) that saves the session when the async generator is abandoned mid-stream (client disconnect/cancellation) has no corresponding test. Additionally, the telegram session ID format change is a breaking change without any migration path or backward-compatibility test.
✗ Design Approach
I found one design-level correctness issue in the new in-memory SessionStore: it presents itself as a session snapshot store, but set() keeps the caller's AgentSession by reference instead of capturing a snapshot. That means post-set mutations to the original session silently alter the stored value, and the in-memory base no longer matches the write-time snapshot semantics of FileSessionStore. I found one design issue: the Telegram helper now returns a different session-ID format, which splits durable conversation state across two unrelated keys after upgrade. Because
AgentStatepasses the caller-selected session ID through unchanged to the configured store (python/packages/hosting/agent_framework_hosting/_state.py:169-175, reinforced bypython/packages/hosting/tests/hosting/test_state.py:202-226), changingtelegram_session_id()fromtelegram:...totelegram_...makes existing persisted sessions unreachable with no compatibility path. I found one non-blocking design issue in the new benchmark script: although its CLI exposes workload sizes as free-form integers, the verification path assumes at least one message and one custom class state object, so zero-sized runs crash instead of benchmarking an empty-session case or rejecting the input up front.
Flagged Issues
- python/packages/core/agent_framework/_sessions.py:1379-1384,1420-1434: SessionStore documents snapshot-style behavior and deep-copies on read, but set() stores the caller-owned AgentSession by reference. A caller can
await store.set(id, session), mutatesession.stateafterward, and laterget(id)will return the mutated data rather than the snapshot that was supposedly stored—diverging from FileSessionStore which inherently snapshots at write time via serialization. - Changing
telegram_session_id()fromtelegram:<bot_id>:<chat_id>totelegram_<bot_id>_chat_<chat_id>breaks continuity for any app already persisting Telegram sessions, because the helper output is used directly as the storage key via AgentState (python/packages/hosting/agent_framework_hosting/_state.py:169-175) and there is no migration or fallback lookup path.
Suggestions
- Consider adding a test for the
register_state_typevalidation branch at _sessions.py:308 where providing onlyencoderwithoutdecoder(or vice-versa) raises ValueError('State type encoder and decoder must be provided together.'). This is a user-facing validation that has no direct test. - Consider adding a test for the FileSessionStore quarantine-failure path (_sessions.py:1539-1543) where
_quarantine_corrupt_snapshotraises OSError, producing the 'could not be quarantined' error message. The current tests cover successful quarantine and concurrent-replacement cases but not OS-level quarantine failures.
Automated review by eavanvalkenburg's agents
|
Flagged issue python/packages/core/agent_framework/_sessions.py:1379-1384,1420-1434: SessionStore documents snapshot-style behavior and deep-copies on read, but set() stores the caller-owned AgentSession by reference. A caller can Source: automated DevFlow PR review |
|
Flagged issue Changing Source: automated DevFlow PR review |
Move SessionStore and durable msgspec-backed storage into core, restore sessions in Foundry Responses hosting with per-user isolation, and document the serialization design. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Harden scoped file paths and corruption recovery, preserve session serialization compatibility, clarify dependency placement, and add reproducible benchmark evidence. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Deep-copy in-memory session writes and retain existing Telegram session keys so stored conversations continue resolving. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Add experimental FoundrySessionStore backed by Agent Server request context, remove resolver plumbing, and centralize v2 user isolation for sessions, checkpoints, and approvals. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Inline the single-use request user accessor while keeping separate context validation, fingerprint, and directory helpers for their distinct callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Separate fail-fast request validation from context retrieval so Responses no longer appears to discard a returned context. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Move protocol validation and user-scope derivation into a dedicated request-context module, leaving the session-store module focused on storage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
4db3adb to
5a47cca
Compare
Motivation & Context
Core agents need a reusable way to persist complete
AgentSessionstate, while Foundry Responses hosting currently restores transcript history without restoring middleware state, provider state, or service continuation handles. This change moves session storage into core and uses it in Foundry hosting so state survives Responses API calls with explicit per-user isolation.Description & Review Guide
SessionStoreinto core; add an experimental msgspec-backedFileSessionStorewith JSON and MessagePack formats, atomic replacement, opaque keys encoded to portable filenames, explicit provider-owned custom-state registration, and non-destructive schema/version/decoder failures; make msgspec theFileHistoryProviderdefault while deprecating customdumps/loads; and restore/save sessions throughout the Foundry Responses lifecycle. Hosted Responses servers default to the experimentalFoundrySessionStoreat the session file API path/.sessions/<user-id>/<session-id>.json(stored under$HOME/.sessions), local servers default to the in-memorySessionStore, andsession_store=is the override for persisting MAF snapshots outside Foundry. The change also updates hosting imports, samples, package metadata, tests, and ADR-0033.AgentSessioncarries framework context state; the shared identifier correlates them without equating their semantics. Workflow checkpoints and function approvals retain their existing paths and raw-user partitioning. Consumers of the alpha hosting package now importSessionStorefromagent_framework, and core gains amsgspecruntime dependency. Foundry deployment validation used deterministic middleware that incrementsAgentSession.state["turn_count"]: two calls sharing one hosted session returnedSESSION_TURN=1thenSESSION_TURN=2; after stopping the hosted compute while preserving its filesystem, the resumed call returnedSESSION_TURN=3. The session file API also exposed the snapshot under/.sessions/<user-id>/<session-id>.json, confirming the intended user partition and filename.Related Issue
No issue is linked for this draft, as requested.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.