Skip to content

Python: add public TypedDict for AgentExecutor checkpoint state (#8201) - #8284

Open
lsmlhi_25 (FOWEPJF255) wants to merge 3 commits into
microsoft:mainfrom
FOWEPJF255:feat/agent-executor-checkpoint-typeddict-8201
Open

Python: add public TypedDict for AgentExecutor checkpoint state (#8201)#8284
lsmlhi_25 (FOWEPJF255) wants to merge 3 commits into
microsoft:mainfrom
FOWEPJF255:feat/agent-executor-checkpoint-typeddict-8201

Conversation

@FOWEPJF255

Copy link
Copy Markdown
Contributor

Motivation & Context

Applications that inspect, migrate, or associate AgentExecutor checkpoint data currently rely on an undocumented dict[str, Any] shape. A public TypedDict contract improves static analysis and makes checkpoint persistence safer to evolve.

Fixes #8201.

Description & Review Guide

  • What are the major changes?
    • Add public AgentExecutorCheckpointState and AgentSessionCheckpointState TypedDicts.
    • Type on_checkpoint_save / on_checkpoint_restore against that schema.
    • Validate known field types on restore (WorkflowCheckpointException); accept missing keys and ignore unknown keys for compatibility.
    • Export the types from agent_framework and add unit tests.
  • What is the impact of these changes?
    • Additive public typing + clearer restore errors; existing checkpoints remain readable.
  • What do you want reviewers to focus on?
    • TypedDict field set vs current save payload, restore validation strictness, and backward/forward compatibility notes in the docstring.

Related Issue

Fixes #8201

No other open PR targets this issue.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add [BREAKING] to the title prefix, before or after any language prefix) 鈥?a workflow keeps the label and title prefix in sync automatically.

…osoft#8201)

Expose AgentExecutorCheckpointState / AgentSessionCheckpointState, validate restore field types, and cover partial/malformed/forward-compatible payloads.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The root stub is unsynchronized, and validation does not fully enforce the published checkpoint schema.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds public checkpoint-state schemas for Python’s AgentExecutor, including restore validation and compatibility guidance.

Changes:

  • Adds and exports two checkpoint TypedDict contracts.
  • Validates checkpoint fields during restoration.
  • Adds schema, compatibility, and malformed-state tests.
File summaries
File Description
test_agent_executor.py Tests checkpoint schemas and restoration behavior.
_agent_executor.py Defines schemas and restore validation.
__init__.py Exports the new public types.
Review details

Suppressed comments (3)

python/packages/core/agent_framework/_workflows/_agent_executor.py:87

  • Checking only list does not validate the declared field types. For example, {"cache": ["bad"]} passes restore and is later sent to run_agent as though it contained Message objects; malformed pending responses similarly fail later when .type is accessed instead of producing the promised checkpoint error. Validate each list element as Message or Content, as appropriate.
    list_keys = ("cache", "full_conversation", "pending_responses_to_agent")
    for key in list_keys:
        if key in state and state[key] is not None and not isinstance(state[key], list):

python/packages/core/agent_framework/_workflows/_agent_executor.py:93

  • The mapping's declared dict[str, Content] shape is not validated. A checkpoint with non-string keys or non-Content values is accepted into _pending_agent_requests, which can leave pending requests that normal string request IDs can never remove. Validate both keys and values before restoring this field.
    if "pending_agent_requests" in state and state["pending_agent_requests"] is not None:
        if not isinstance(state["pending_agent_requests"], dict):

python/packages/core/agent_framework/_workflows/_agent_executor.py:100

  • A dictionary is not sufficient for a valid AgentSessionCheckpointState: session_id is required and must be a string. Payloads such as {} or {"session_id": 1} pass this check; the former is then caught and silently replaced with a new session, while the latter creates a session with an invalid ID. Validate the required nested field and raise WorkflowCheckpointException.
    if "agent_session" in state and state["agent_session"] is not None:
        if not isinstance(state["agent_session"], dict):
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +321 to +324
"AgentExecutorCheckpointState",
"AgentExecutorRequest",
"AgentExecutorResponse",
"AgentSessionCheckpointState",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up: synced agent_framework/__init__.pyi with the new root exports (AgentExecutorCheckpointState, AgentSessionCheckpointState, AgentSessionDict). See FOWEPJF255#1 (f016657).


type: NotRequired[str]
session_id: str
service_session_id: NotRequired[str | None]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up: AgentSessionDict.service_session_id is now str | ServiceSessionId | None, matching AgentSession.service_session_id / to_dict(). See FOWEPJF255#1.

Comment on lines +34 to +44
class AgentSessionCheckpointState(TypedDict):
"""Serialized :class:`~agent_framework.AgentSession` payload (``AgentSession.to_dict()``).

``state`` holds session-local data. When the session uses service-side storage,
local ``state`` may be incomplete relative to the remote conversation.
"""

type: NotRequired[str]
session_id: str
service_session_id: NotRequired[str | None]
state: NotRequired[dict[str, Any]]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we get this shape from the session itself, seems like a bad idea to maintain that twice

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — moved the shape onto AgentSession as AgentSessionDict (to_dict() return type) so AgentExecutor does not maintain a second copy. AgentSessionCheckpointState is an alias for the existing public name. Proposed in FOWEPJF255#1.


@override
async def on_checkpoint_restore(self, state: dict[str, Any]) -> None:
async def on_checkpoint_restore(self, state: AgentExecutorCheckpointState | dict[str, Any]) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just type this as the TypedDict, that's kind of the point, no?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated: on_checkpoint_restore(self, state: AgentExecutorCheckpointState) — dropped the | dict[str, Any] union. Proposed in FOWEPJF255#1.

LI (ktz03) added a commit to ktz03/agent-framework that referenced this pull request Sep 11, 2026
…wnership

- Define AgentSessionDict on AgentSession (to_dict return) so the shape is
  not duplicated in AgentExecutor; keep AgentSessionCheckpointState as alias.
- Include ServiceSessionId mapping in service_session_id.
- Type on_checkpoint_restore as AgentExecutorCheckpointState only.
- Sync root stub exports (__init__.pyi) with runtime __all__.
@ktz03

Copy link
Copy Markdown

Left a follow-up PR into your branch addressing the open review threads:

Changes:

  1. Eduard van Valkenburg (@eavanvalkenburg) — session payload shape now lives on AgentSession as AgentSessionDict (to_dict() return); AgentSessionCheckpointState is kept as an alias so we do not maintain two schemas.
  2. Eduard van Valkenburg (@eavanvalkenburg)on_checkpoint_restore is typed as AgentExecutorCheckpointState only (dropped | dict[str, Any]).
  3. Copilot — service_session_id includes the ServiceSessionId mapping form; __init__.pyi synced with the new root exports.

Feel free to merge #1 into your branch (or cherry-pick f016657) if this matches what you want.

…wnership (#1)

- Define AgentSessionDict on AgentSession (to_dict return) so the shape is
  not duplicated in AgentExecutor; keep AgentSessionCheckpointState as alias.
- Include ServiceSessionId mapping in service_session_id.
- Type on_checkpoint_restore as AgentExecutorCheckpointState only.
- Sync root stub exports (__init__.pyi) with runtime __all__.
@FOWEPJF255

Copy link
Copy Markdown
Contributor Author

Merged the TypedDict ownership follow-up on the PR branch (FOWEPJF255#1). Ready for another look when you have a moment.

…oft#8284)

Enforce Message/Content element types, string pending-request keys, and
required agent_session.session_id so malformed checkpoints fail at restore.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: Add a typed schema for AgentExecutor checkpoint state

5 participants