Files
microsoft--agent-framework/python/packages/hosting/README.md
Eduard van Valkenburg 28389df805 Python: Move SessionStore to core and persist Foundry Responses sessions (#7306)
* Python: Move session persistence into core

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

* Python: Address session persistence review feedback

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

* Python: Preserve session snapshot compatibility

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

* Python: Simplify Foundry session isolation

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

* Python: Reduce Foundry session helper layering

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

* Python: Clarify Foundry request context validation

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

* Python: Share Foundry request context helpers

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

* Restore Foundry checkpoint storage paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Simplify Foundry session storage paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Persist Foundry sessions under hosted home

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Make hosted path test platform independent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Address session persistence review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Isolate Foundry session path handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Clarify Foundry session path terminology

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Align Foundry sessions with Responses continuity

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Finalize Foundry Responses session persistence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Add session store feature usage telemetry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

* Fix hosted per-call history persistence

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c

---------

Copilot-Session: 3e5c81ad-75e8-4e92-a883-8bbd676c6c8c
2026-07-30 13:04:08 +00:00

146 lines
6.2 KiB
Markdown

# agent-framework-hosting
Shared execution-state helpers for app-owned Agent Framework hosting.
This package keeps Agent Framework state separate from web-framework concerns:
- `AgentState` — pairs an agent target with a core `SessionStore`
(`session_id -> AgentSession`).
- `WorkflowState` — resolves a workflow target, including direct `Workflow`
instances, workflow factories, `WorkflowBuilder`, and orchestration builders.
The experimental `SessionStore` and `FileSessionStore` implementations live in
`agent-framework-core` and are imported from `agent_framework`. `SessionStore`
provides `get`/`set`/`delete` by an app-selected id. Each
successful `get` returns an independent copy, so a run works from a snapshot
instead of mutating an older continuation point in place. The store does not
know how to create a new value for an id it hasn't seen before — use
`AgentState.get_or_create_session(...)` for that, since only the state object
has both the store and the resolved target. Workflow
checkpointing should use the existing `CheckpointStorage` abstraction directly;
if an app needs per-session resume, keep a small app-owned cursor such as
`session_id -> checkpoint_id`.
`SessionStore` accepts opaque non-empty IDs so custom database and Redis
implementations can use their native key contracts. `FileSessionStore` limits
its direct keys to 128 ASCII letters, digits, `-`, and `_`. `AgentState` passes
the app-selected ID to the configured store unchanged; each store implementation
owns any validation or normalization required by its backend. This does not
replace parameterized queries or backend-specific validation in custom stores.
`FileSessionStore` uses msgspec JSON. Register custom objects placed in
`AgentSession.state` explicitly before sessions are saved or restored:
```python
from agent_framework import register_state_type
class MyState:
...
# Register at module import time, before any provider instance is created.
register_state_type(MyState, type_id="my_state")
```
Classes with `to_dict()` / `from_dict()` methods and explicitly registered
Pydantic models receive default codecs. Other classes can provide `encoder=`
and `decoder=` callbacks. Keep registration at module level so importing the
module prepares cold-start session restoration before its context provider is
instantiated. Unregistered Pydantic models still use legacy same-process
auto-registration for now, but emit `DeprecationWarning`; cold-start
deserialization is not guaranteed on that path.
JSON is the default file format. Use MessagePack for a compact binary file:
```python
store = FileSessionStore("storage/sessions", serialization_format="msgpack")
```
Use FastAPI, Starlette, Azure Functions, Django, or another framework for route
registration, auth, middleware, response construction, and background work.
> The core `SessionStore` is an in-memory `dict` with no eviction — every
> id ever stored stays resolvable for the life of the process. That is
> intentional: protocols such as OpenAI Responses'
> `previous_response_id` are designed to let a caller continue from *any*
> earlier point in a conversation, not just the latest turn, so every id
> handed out needs to stay independently resolvable. If you back the store
> with real storage (Redis, a database, ...), you are responsible for that
> store's own TTL/eviction policy; this in-memory reference implementation
> does not model that concern.
## Quickstart
```python
from agent_framework import FileSessionStore
from agent_framework.openai import OpenAIChatClient
from agent_framework_hosting import AgentState
agent = OpenAIChatClient().as_agent(name="Assistant")
state = AgentState(agent, session_store=FileSessionStore("storage/sessions"))
session = await state.get_or_create_session("conversation-1")
result = await (await state.get_target()).run("Hello", session=session)
```
If a protocol mints a new continuation id on every response, store the session
explicitly after `run(...)` returns. `run(...)` may update the session, so store
the post-run object:
```python
session = await state.get_or_create_session(previous_response_id)
result = await (await state.get_target()).run("Hello", session=session)
await state.set_session(response_id, session)
```
This response-keyed pattern supports simultaneous branches: callers may read
the same `previous_response_id`, receive independent working copies, and store
each result under a different new response id. A stable `conversation_id` is
different: explicitly write the completed session back under that same id to
advance its mutable head, and ensure only one caller advances it at a time.
`AgentState` does not provide that application-level locking or optimistic
concurrency control.
Targets can be direct instances, synchronous factories, asynchronous factories,
or awaitables:
```python
state = AgentState(create_agent) # cached by default
state = AgentState(create_agent, cache_target=False)
```
`WorkflowState` mirrors this shape for workflow targets:
```python
from agent_framework import InMemoryCheckpointStorage
from agent_framework_hosting import WorkflowState
state = WorkflowState(create_workflow)
storage = InMemoryCheckpointStorage()
result = await (await state.get_target()).run("Hello", checkpoint_storage=storage)
latest = await storage.get_latest(workflow_name=(await state.get_target()).name)
```
A `Workflow` instance allows only one active run. Hosts that need simultaneous
workflow runs should pass a factory or builder and set `cache_target=False` so
each request receives a fresh workflow instance.
`WorkflowState` also accepts an unbuilt workflow builder directly:
```python
from agent_framework import WorkflowBuilder
from agent_framework_hosting import WorkflowState
builder = WorkflowBuilder(start_executor=executor)
state = WorkflowState(builder) # calls builder.build() when the target is resolved
```
This is structural: orchestration builders from `agent_framework_orchestrations`
(`SequentialBuilder`, `ConcurrentBuilder`, `HandoffBuilder`, `GroupChatBuilder`,
and `MagenticBuilder`) also work because they expose the same zero-argument
`build() -> Workflow` method.
Cross-channel identity linking, multicast delivery, background runs,
continuation tokens, and durable delivery runners are follow-up enhancements,
not part of this v1 state surface.