Repository navigation
[BREAKING] Python: Bind AG-UI workflow interrupts to the resolved thread id - #9365
Conversation
…ead id Workflow runs that omit threadId now resolve a single thread id up front. The generated id is the one reported in RUN_STARTED, keys the workflow_factory cache and Thread Snapshot, and owns the run's live request_info interrupts, so a continuation must send it back as threadId. Checkpoint ownership still binds only client-supplied thread ids, which keeps legacy unthreaded checkpoint resumes working.
There was a problem hiding this comment.
🟡 Changes recommended
Factory-based omitted continuations and empty-ID checkpoint ownership do not consistently implement the documented behavior.
2 open findings
What changed in this PR
Aligns generated AG-UI workflow thread IDs across events, caching, snapshots, and live interrupt ownership.
Changes:
- Resolves omitted thread IDs once and propagates them through workflow execution.
- Adds regression coverage for continuation, scope isolation, and snapshots.
- Documents the breaking continuation behavior.
| File | Description |
|---|---|
python/packages/ag-ui/agent_framework_ag_ui/_workflow.py |
Resolves IDs and binds live interrupts. |
python/packages/ag-ui/agent_framework_ag_ui/_workflow_run.py |
Accepts the pre-resolved thread ID. |
python/packages/ag-ui/tests/ag_ui/test_workflow_agent.py |
Adds wrapper-level regression tests. |
python/packages/ag-ui/tests/ag_ui/test_endpoint.py |
Updates and expands endpoint tests. |
python/packages/ag-ui/README.md |
Documents generated-ID continuation. |
python/packages/ag-ui/AGENTS.md |
Records thread ownership behavior. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (1 commit(s)): 8be122eaa148
Model: gpt-5.6-sol
Overview
The change consistently reuses one resolved Thread id for live workflow events, factory instances, snapshots, and request_info ownership, with strong regression coverage for foreign ids and Snapshot Scopes. Core workflow concurrency checks also prevent the generated-id path from creating simultaneous runs on one workflow instance. One durable-continuation gap remains: checkpoints created by an unthreaded run retain id-less ownership, so the newly advertised generated id cannot restore them.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: python/packages/ag-ui/agent_framework_ag_ui/_workflow.py
…stent - Normalize the client-supplied thread id once, so an empty id is treated as omitted for both the resolved id and checkpoint ownership. - Reject a workflow_factory resume that omits threadId before building a workflow instance. - Record the generated thread id on checkpoints of runs that omitted threadId, so the id reported in RUN_STARTED resumes them while id-less restores keep working.
mypy and zuban infer one tuple type per variable, so the two loops now use separate names.
There was a problem hiding this comment.
MAF Automated Review — Iteration 2
Result: Findings reported
Scope: 2 net-new commit(s): 791508200d0f, c415256f7770
Model: gpt-5.6-sol
Overview
The change consistently normalizes omitted IDs and adds same-scope generated-ID aliases for durable checkpoint resumes, with focused tests covering scope isolation, legacy ID-less restores, and canonical resume rejection. However, the new factory-resume guard only recognizes canonical resume interrupt IDs, so the existing message-based continuation channel can still start a new workflow under a different generated ID.
Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: python/packages/ag-ui/agent_framework_ag_ui/_workflow.py
…tory workflows A workflow_factory request that omits threadId reports a new generated id, so nothing can be pending on it. Besides resume entries, reject tool results and approval responses, as the workflow stream defines them, with WORKFLOW_RESUME_NOT_FOUND before a workflow instance is built, and document the behavior.

Motivation & Context
AG-UI workflow requests that omit
threadIdresolved a thread id in two places that did not agree:AgentFrameworkWorkflow.run()generated one, andrun_workflow_stream()generated a different one forRUN_STARTED. The id reported to the client was therefore not the id used for theworkflow_factorycache, the Thread Snapshot, or the ownership of liverequest_infointerrupts. A client could not continue a run with the id it was given, and the interrupts of runs withoutthreadIdwere not tied to the id reported for that run.Description & Review Guide
AgentFrameworkWorkflow.run()resolves the thread id once and passes it torun_workflow_stream(), so the protocol events,workflow_factorycache, Thread Snapshot, and liverequest_infoownership all use the same id. The client-supplied id is normalized in one place, so an empty id is treated as omitted everywhere.request_infointerrupts are owned by(Snapshot Scope, resolved thread id). Checkpoint ownership still binds client-supplied thread ids; a checkpoint saved by a run that omittedthreadIdalso records the generated id as an alias, so the reported id and legacy id-less restores both resume it (same Snapshot Scope only).workflow_factoryrequest that omitsthreadIdbut carries aresumeentry, a tool result, or an approval response is rejected withWORKFLOW_RESUME_NOT_FOUNDbefore a workflow instance is built. Message responses are detected with the same extraction the workflow stream uses. Checkpoint resumes are not affected.threadIdnow send one. New regression tests cover runs withoutthreadId, and the package README andAGENTS.mddocument the behavior.threadIdnow requires sending the id reported inRUN_STARTEDasthreadId. A continuation that omits it is rejected withWORKFLOW_RESUME_NOT_FOUND. Clients that already sendthreadIdare unaffected.workflow_factoryonly: a request withoutthreadIdthat includes a tool result or approval response, including replayed history, is now rejected instead of starting a new run. SendthreadIdwith such requests.workflow_factoryinstance, and hydrates the Thread Snapshot stored under that id.workflow_factoryrequests withoutthreadId, which also rejects replayed history that contains a tool result.threadId.Related Issue
N/A - no existing issue covers this change.
AI Assistance
AI assistance details: Implementation, tests, documentation, and this PR description were AI-assisted.
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.