Skip to content

Python: [BREAKING]: Canonicalize AG-UI interrupt and resume handling#6925

Open
moonbox3 wants to merge 11 commits into
microsoft:mainfrom
moonbox3:ag-ui-refactor-interrupt
Open

Python: [BREAKING]: Canonicalize AG-UI interrupt and resume handling#6925
moonbox3 wants to merge 11 commits into
microsoft:mainfrom
moonbox3:ag-ui-refactor-interrupt

Conversation

@moonbox3

@moonbox3 moonbox3 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

AG-UI agent and workflow interrupts need a single protocol-level pause/resume contract that matches the canonical AG-UI Interrupt and ResumeEntry models. The current integration exposes an undesired interrupt shape, and several resume/cancel paths can leave approval, workflow, or snapshot state stale.

Description & Review Guide

  • What are the major changes?

    • Emits interrupted runs as RUN_FINISHED.outcome.type == "interrupt" with canonical outcome.interrupts.
    • Handles tool approvals, confirmation prompts, and workflow request_info pauses through canonical AG-UI interrupt/resume data.
    • Serializes typed and compatible legacy client resume inputs into canonical ResumeEntry arrays.
    • Validates missing, unknown, invalid, resolved, and cancelled resume entries through streamed RUN_ERROR events.
    • Keeps snapshot hydration aligned with canonical interrupt outcomes and clears only the completed/cancelled interrupt state.
    • Updates AG-UI docs, examples, and tests, and raises the ag-ui-protocol dependency to the version that contains the canonical models.
  • What is the impact of these changes?

    • This is a release-candidate breaking wire-contract change for interrupted runs: clients should read RUN_FINISHED.outcome.interrupts instead of the legacy top-level interrupt field.
    • Resume requests should use canonical resume entries with interruptId, status, and optional payload.
    • Cancelled approvals and workflow input requests no longer leave stale pending state that wedges later requests.
  • What do you want reviewers to focus on?

    • Whether the canonical interrupt/resume wire shape is the right public AG-UI contract.
    • Approval and workflow resume validation, especially cancellation and multi-interrupt cases.
    • Snapshot-backed hydrate/resume behavior for interrupted threads.

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.

moonbox3 added 10 commits June 19, 2026 15:37
Key decisions: raise ag-ui-protocol to 0.1.19, type AGUIRequest/AGUIChatOptions with protocol Interrupt and ResumeEntry, and emit interrupted runs through RUN_FINISHED.outcome.interrupts instead of the legacy top-level interrupt field. Preserve existing internal resume/snapshot compatibility by translating legacy interruption metadata into canonical Interrupt metadata.

Files changed: packages/ag-ui pyproject, AG-UI run/type/workflow/snapshot helpers, AG-UI protocol-shape tests, and uv.lock.

Verification: uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe typing -P ag-ui; uv run poe validate-dependency-bounds-test -P ag-ui; uv run poe check -P ag-ui; git diff --cached --check.

Notes: README and local PRD/Ralph planning files were left unstaged. Follow-up slices still own richer approval/workflow response schemas and full client-side resume forwarding.
Key decisions: build Agent Framework approval pauses as canonical AG-UI Interrupt entries under RUN_FINISHED.outcome.interrupts; use reason=tool_call with toolCallId routing; advertise generic approval response schemas using the existing accepted/edited-argument payload contract; keep legacy Agent Framework approval metadata nested under metadata.agent_framework.value for internal snapshot/resume compatibility while avoiding any top-level interrupt value in emitted protocol JSON.

Files changed: packages/ag-ui/agent_framework_ag_ui/_run_common.py adds canonical approval interrupt/schema helpers and uses them for function approval requests; packages/ag-ui/agent_framework_ag_ui/_agent_run.py emits canonical interrupts for predictive confirm_changes pauses; packages/ag-ui/tests/ag_ui/test_endpoint.py covers endpoint/SSE approval pause shape; packages/ag-ui/tests/ag_ui/test_run.py covers helper and run-level confirmation interrupt behavior.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_pause_emits_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_run.py::test_emit_approval_request_populates_interrupt_metadata packages/ag-ui/tests/ag_ui/test_run.py::test_predictive_confirmation_run_finished_interrupt_links_tool_call -q; uv run pytest packages/ag-ui/tests/ag_ui/test_run.py::test_run_agent_stream_accumulates_multiple_confirm_interrupts packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_hydrates_interrupted_thread_without_invoking_agent packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_pause_emits_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_run.py::test_predictive_confirmation_run_finished_interrupt_links_tool_call -q; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe typing -P ag-ui; uv run poe check -P ag-ui; git diff --check; git diff --cached --check.

Notes: local README, PRD, .ralph, and issue planning artifacts remain unstaged. Follow-up slices still own canonical ResumeEntry approval continuation, workflow request_info canonical resume, client-side forwarding, pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.
Key decisions: translate canonical ResumeEntry approval payloads into the existing Agent Framework function approval response path at the AG-UI agent-run boundary; route by canonical interruptId while preserving pending approval registry validation; allow edited arguments only through canonical resume translation by updating the stored pending argument fingerprint before execution; emit RUN_ERROR for cancelled, unknown, or malformed approval resumes instead of proceeding.

Files changed: packages/ag-ui/agent_framework_ag_ui/_agent_run.py adds canonical approval resume translation, interrupt-id registry aliasing, explicit approval resume RUN_ERROR handling, and alias cleanup on consumption; packages/ag-ui/tests/ag_ui/test_endpoint.py adds endpoint/SSE coverage for approved, denied, edited, cancelled, and unknown canonical approval resumes.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_executes_approved_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_denial_does_not_execute_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_applies_edited_arguments packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_cancelled_resume_entry_emits_run_error packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_unknown_resume_entry_emits_run_error -q; uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_pause_emits_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_endpoint.py::test_agent_endpoint_confirm_changes_clears_persisted_interrupt packages/ag-ui/tests/ag_ui/test_approval_result_event.py -q; uv run pytest packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py::test_approval_argument_mismatch_is_blocked packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_applies_edited_arguments packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_executes_approved_tool -q; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe typing -P ag-ui; uv run poe test -P ag-ui; uv run poe check -P ag-ui; git diff --check; git diff --cached --check.

Notes: issues/agui-int-03-agent-approval-resume-entry.md was moved to issues/done locally but remains unstaged. Existing local README, PRD, and .ralph artifacts remain unstaged. Follow-up slices still own workflow request_info canonical resume, client-side forwarding, stricter pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.
Key decisions: emit workflow request_info pauses as canonical input_required interrupt outcomes with response schemas and Agent Framework metadata; normalize typed ResumeEntry model dumps through the shared resume parser while preserving status; translate resolved workflow resume payloads through the existing workflow response coercion path; emit RUN_ERROR for cancelled workflow resumes before invoking the workflow.

Files changed: packages/ag-ui/agent_framework_ag_ui/_run_common.py preserves canonical resume status/model dumps and merges interrupt metadata values; packages/ag-ui/agent_framework_ag_ui/_workflow_run.py builds canonical workflow request_info interrupts and cancellation errors; packages/ag-ui/tests/ag_ui/test_endpoint.py adds endpoint/SSE workflow pause, resolved resume, and cancelled resume coverage; packages/ag-ui/tests/ag_ui/test_run_common.py and packages/ag-ui/tests/ag_ui/test_workflow_run.py update canonical helper expectations.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_workflow_request_info_emits_canonical_interrupt_and_resumes packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_workflow_request_info_cancelled_resume_emits_run_error -q; uv run pytest packages/ag-ui/tests/ag_ui/test_workflow_run.py -q; uv run pytest packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_executes_approved_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_denial_does_not_execute_tool packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_resume_entry_applies_edited_arguments packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_cancelled_resume_entry_emits_run_error packages/ag-ui/tests/ag_ui/test_endpoint.py::test_endpoint_agent_approval_unknown_resume_entry_emits_run_error -q; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test -P ag-ui; uv run poe typing -P ag-ui; uv run poe check -P ag-ui; git diff --check; git diff --cached --check.

Notes: issue bookkeeping and local PRD files were not staged; existing unstaged packages/ag-ui/README.md remains untouched. Follow-up slices still own client-side forwarding, stricter pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.
Key decisions: normalize typed Interrupt and ResumeEntry values at the AGUIChatClient and AGUIHttpService boundaries using protocol aliases; map legacy request_info available-interrupt hints to canonical input_required reason while preserving legacy resume wrapper shapes; preserve remote RUN_FINISHED.outcome metadata and expose outcome.interrupts for Agent Framework callers without changing normal success completion handling.

Files changed: packages/ag-ui/agent_framework_ag_ui/_client.py forwards normalized available_interrupts/resume values; packages/ag-ui/agent_framework_ag_ui/_http_service.py serializes typed protocol models and compatible values to camelCase wire JSON; packages/ag-ui/agent_framework_ag_ui/_event_converters.py preserves canonical outcomes/interruption metadata; packages/ag-ui/tests/ag_ui/test_ag_ui_client.py, test_http_service.py, and test_event_converters.py cover outgoing typed JSON, canonical interrupted conversion, and success outcome behavior.

Verification: uv run pytest packages/ag-ui/tests/ag_ui/test_http_service.py::test_post_run_serializes_typed_interrupts_and_resume_with_protocol_aliases packages/ag-ui/tests/ag_ui/test_ag_ui_client.py::TestAGUIChatClient::test_typed_interrupt_options_forward_canonical_protocol_shape packages/ag-ui/tests/ag_ui/test_event_converters.py::TestAGUIEventConverter::test_run_finished_event_with_canonical_interrupt_outcome packages/ag-ui/tests/ag_ui/test_event_converters.py::TestAGUIEventConverter::test_run_finished_event_with_success_outcome_preserves_normal_completion -q; uv run pytest packages/ag-ui/tests/ag_ui/test_http_service.py packages/ag-ui/tests/ag_ui/test_ag_ui_client.py packages/ag-ui/tests/ag_ui/test_event_converters.py -q; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe test -P ag-ui; uv run poe typing -P ag-ui; uv run poe check -P ag-ui; git diff --check; git diff --cached --check.

Notes: issues/agui-int-05-chat-client-http-forwarding.md was moved to issues/done locally but not staged. Existing unstaged packages/ag-ui/README.md, .ralph, PRD, and snapshot planning artifacts remain untouched. Follow-up slices still own stricter pending interrupt contract enforcement, snapshot stale-prompt clearing, and documentation/examples.
Key decisions: validate pending AG-UI interrupts before agent or workflow execution; require resume entries to address every open interrupt exactly once; emit RUN_ERROR for missing, unknown, duplicate, malformed, cancelled, or schema-invalid resume payloads; keep successful canonical approval and workflow resume flows working while removing heuristic non-resume workflow continuation for interrupted threads.

Files changed: packages/ag-ui/agent_framework_ag_ui/_run_common.py adds strict resume parsing and exact pending-interrupt contract validation; _agent_run.py applies the contract to approval resumes, validates edited approval argument types, and considers stored canonical interrupt ids; _workflow_run.py applies the contract to request_info resumes and fails invalid response coercion explicitly; AG-UI endpoint, workflow, golden, wrapper, and subgraph tests now cover RUN_ERROR failures and canonical resume entries.

Verification: uv run pytest focused new resume-contract endpoint tests -q; uv run pytest existing approval/workflow resume endpoint tests -q; uv run pytest packages/ag-ui/tests/ag_ui/test_workflow_run.py packages/ag-ui/tests/ag_ui/test_run.py packages/ag-ui/tests/ag_ui/golden/test_scenario_workflow.py -q; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe typing -P ag-ui; uv run poe check -P ag-ui; git diff --check; git diff --cached --check.

Notes: issues/agui-int-06-resume-contract-validation.md was moved to issues/done locally but not staged. Existing unstaged packages/ag-ui/README.md and local .ralph/PRD artifacts remain untouched. Follow-up slices still own canonical snapshot stale-prompt clearing and documentation/examples.
Key decisions: treat cancelled canonical approval and workflow resumes as completion of the stored interruption for AG-UI Thread Snapshot hydration; clear only the persisted interrupt field while preserving replayable messages and Shared State; consume cancelled approval registry entries so server-side approval state does not remain open; preserve existing RUN_ERROR responses for cancelled resumes.

Files changed: packages/ag-ui/agent_framework_ag_ui/_snapshots.py adds shared persisted-interrupt clearing; _agent_run.py clears snapshots and consumes pending approvals on cancelled approval resumes; _workflow.py clears snapshots on cancelled workflow resumes; test_endpoint.py covers agent/workflow cancelled-resume stale prompt clearing; test_run_common.py covers canonical interrupt toolCallId trusted suffix filtering.

Verification: uv run pytest focused interrupted snapshot/resume tests -q; uv run poe test -P ag-ui; uv run poe syntax -P ag-ui -C; uv run poe pyright -P ag-ui; uv run poe typing -P ag-ui; uv run poe check -P ag-ui; git diff --check; git diff --cached --check.

Notes: issues/agui-int-07-thread-snapshot-interrupt-hydration.md was moved to issues/done locally but not staged. Existing unstaged packages/ag-ui/README.md and local .ralph/PRD artifacts remain untouched. Follow-up docs/examples slice still owns public guidance updates.
Key decisions: document the clean release-candidate interrupt cutover around canonical AG-UI protocol models; direct users to RUN_FINISHED.outcome.interrupts and canonical resume arrays; make clear that Interrupt and ResumeEntry come from ag_ui.core rather than an Agent Framework-specific model; retain normal RUN_FINISHED completion guidance for non-interrupted runs.

Files changed: packages/ag-ui/AGENTS.md updates package guidance; packages/ag-ui/README.md adds interrupt/resume protocol and migration notes; packages/ag-ui/agent_framework_ag_ui_examples/README.md documents canonical resume shape for examples; packages/ag-ui/getting_started/README.md teaches outcome.interrupts and ResumeEntry usage.

Verification: uv run poe markdown-code-lint failed on pre-existing packages/mistral/README.md; uv run python scripts/check_md_code_blocks.py packages/ag-ui/README.md packages/ag-ui/agent_framework_ag_ui_examples/README.md packages/ag-ui/getting_started/README.md packages/ag-ui/AGENTS.md; git diff --check; git diff --cached --check.

Notes: no issue or PRD artifacts were staged. Root AGENTS.md could not be read because access was denied; package and Python workspace guidance were applied. Follow-up docs/examples issue appears complete; no remaining AG-UI interrupt cutover tasks were found locally.
…rrupt

# Conflicts:
#	python/packages/ag-ui/pyproject.toml
@moonbox3 moonbox3 self-assigned this Jul 6, 2026
Copilot AI review requested due to automatic review settings July 6, 2026 04:53
@moonbox3 moonbox3 added python Usage: [Issues, PRs], Target: Python ag-ui Usage: [Issues, PRs], Target: AG-UI protocol integration labels Jul 6, 2026
@github-actions github-actions Bot changed the title [Python][BREAKING]: Canonicalize AG-UI interrupt and resume handling Python: [Python][BREAKING]: Canonicalize AG-UI interrupt and resume handling Jul 6, 2026
@giles17 giles17 added the documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs label Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/ag-ui/agent_framework_ag_ui
   _agent.py44295%68, 125
   _agent_run.py7919088%175–182, 229–230, 237, 346, 350, 352, 369, 396–397, 500, 509, 530, 533, 535, 550, 585, 587, 589, 592–596, 610, 618–623, 632–633, 670–673, 684, 692, 723, 738, 752, 764, 811–815, 879, 885–886, 891, 895–896, 946–948, 960–962, 1060, 1068, 1127, 1157–1158, 1325, 1371, 1388, 1405–1406, 1413, 1510, 1535, 1543, 1545, 1548, 1554, 1609, 1612, 1622–1623, 1630, 1676
   _client.py1501391%85–86, 90–94, 98–102, 262
   _event_converters.py870100% 
   _http_service.py102892%22, 26, 41–42, 56, 66, 104, 107
   _run_common.py5574891%76, 139–140, 142, 144, 147, 153, 155, 169, 176, 200–201, 214, 233, 235, 269, 291, 313, 325, 327, 329, 332–336, 739–740, 746–751, 1054–1055, 1060, 1062–1064, 1073, 1081, 1093, 1095–1098, 1161
   _snapshots.py82593%135, 218, 220, 229–230
   _types.py440100% 
   _workflow.py2041692%64, 127, 132, 171, 210–211, 293, 336, 338, 340–342, 344, 362–364
   _workflow_run.py6597688%74, 88, 90, 92, 95, 222–225, 261, 272, 277, 302, 338–341, 369, 374, 402, 412, 423, 428, 431, 444, 454, 457, 462, 465, 480–482, 487, 489–490, 494, 496, 513, 519–520, 530–531, 535–536, 560–561, 594, 602, 643, 663, 671, 674, 679, 694, 790–804, 836–837, 858–859, 930, 970, 989
TOTAL43944529887% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8579 33 💤 0 ❌ 0 🔥 2m 14s ⏱️

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.

Pull request overview

This PR updates the Python AG-UI integration to use the canonical AG-UI interrupt/resume contract, moving interrupted-run data to RUN_FINISHED.outcome.type == "interrupt" with outcome.interrupts, and requiring canonical resume entries to continue or cancel pauses (approvals and workflow request_info).

Changes:

  • Canonicalizes interrupted run completion shape to RUN_FINISHED.outcome.interrupts (removing the stable top-level interrupt field for interrupted runs).
  • Implements strict resume-contract validation (missing/unknown/invalid/cancelled/resolved) with streamed RUN_ERROR codes, and clears only completed/cancelled interrupt state (including snapshot-backed hydration).
  • Updates docs, examples, client serialization, and extensive test coverage; bumps ag-ui-protocol to >=0.1.19,<0.2.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
python/uv.lock Bumps locked dependency to ag-ui-protocol>=0.1.19,<0.2.
python/packages/ag-ui/pyproject.toml Raises ag-ui-protocol minimum version for canonical models.
python/packages/ag-ui/README.md Documents canonical interrupt/resume wire shape and breaking change.
python/packages/ag-ui/getting_started/README.md Adds canonical resume-entry documentation to the tutorial.
python/packages/ag-ui/AGENTS.md Updates contributor guidance to canonical outcome.interrupts shape.
python/packages/ag-ui/agent_framework_ag_ui/_types.py Types available_interrupts as canonical Interrupt; makes resume permissive at the HTTP trust boundary.
python/packages/ag-ui/agent_framework_ag_ui/_run_common.py Adds canonical interrupt construction, resume-entry parsing/validation helpers, and emits RunFinishedInterruptOutcome.
python/packages/ag-ui/agent_framework_ag_ui/_workflow_run.py Canonicalizes workflow request_info interrupts + strict resume validation and response coercion.
python/packages/ag-ui/agent_framework_ag_ui/_workflow.py Reads canonical outcome interrupts for snapshots; clears snapshot interrupts on cancelled workflow resumes.
python/packages/ag-ui/agent_framework_ag_ui/_agent.py Migrates pending-approval registry keys to structured (thread_id, request_id) tuples.
python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Implements canonical approval resume handling, cancellation semantics, snapshot seeding, and registry validation.
python/packages/ag-ui/agent_framework_ag_ui/_http_service.py Serializes typed and legacy interrupt/resume inputs into canonical wire JSON (aliases).
python/packages/ag-ui/agent_framework_ag_ui/_client.py Ensures chat client forwards canonical availableInterrupts/resume wire shapes.
python/packages/ag-ui/agent_framework_ag_ui/_event_converters.py Preserves canonical outcome/interrupts metadata on converted ChatResponseUpdate.
python/packages/ag-ui/agent_framework_ag_ui/_snapshots.py Adds helper to clear stored snapshot interrupt state (all or by interrupt id set).
python/packages/ag-ui/tests/ag_ui/event_stream.py Adds helpers to assert canonical RUN_FINISHED.outcome.interrupts in tests.
python/packages/ag-ui/tests/ag_ui/test_types.py Updates request typing tests for canonical Interrupt + permissive resume.
python/packages/ag-ui/tests/ag_ui/test_http_service.py Verifies canonical serialization for typed + legacy interrupt/resume inputs.
python/packages/ag-ui/tests/ag_ui/test_ag_ui_client.py Ensures client forwards canonical interrupt/resume payloads to HTTP service.
python/packages/ag-ui/tests/ag_ui/test_event_converters.py Tests converter behavior for canonical RUN_FINISHED.outcome shapes.
python/packages/ag-ui/tests/ag_ui/test_run_common.py Adds tests for canonical run-finished outcome shape and snapshot reconstruction.
python/packages/ag-ui/tests/ag_ui/test_run.py Updates/expands agent-stream tests for canonical interrupts, approvals, and resume/cancel behavior.
python/packages/ag-ui/tests/ag_ui/test_multi_turn.py Updates multi-turn round-trip tests to read canonical interrupts and validate no-outcome completion.
python/packages/ag-ui/tests/ag_ui/test_workflow_run.py Updates workflow stream tests for canonical interrupts and strict resume-required errors.
python/packages/ag-ui/tests/ag_ui/test_workflow_agent.py Updates workflow wrapper tests for canonical interrupt outcome.
python/packages/ag-ui/tests/ag_ui/test_subgraphs_example_agent.py Updates subgraphs example tests for canonical interrupts + resume-required error behavior.
python/packages/ag-ui/tests/ag_ui/test_endpoint.py Adds extensive SSE endpoint coverage for canonical pause/resume/cancel + snapshot hydration behavior.
python/packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py Updates comprehensive wrapper tests to use canonical resume entries and new registry keying.
python/packages/ag-ui/tests/ag_ui/golden/test_scenario_workflow.py Updates golden workflow scenarios for canonical outcome shape and strict resume requirements.
python/packages/ag-ui/tests/ag_ui/golden/test_scenario_subgraphs.py Updates golden subgraphs scenarios for canonical outcome shape.
python/packages/ag-ui/tests/ag_ui/golden/test_scenario_hitl.py Updates golden HITL scenarios for canonical outcome shape.
python/packages/ag-ui/agent_framework_ag_ui_examples/README.md Documents canonical interrupt/resume shape used by examples.

Comment thread python/packages/ag-ui/README.md Outdated
Comment thread python/packages/ag-ui/README.md
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_workflow_run.py Outdated

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 4 | Confidence: 74%

✓ Correctness

This is a large, well-structured breaking change that canonicalizes AG-UI interrupt/resume handling. The core logic is sound and the test coverage is comprehensive. I found one inconsistency between _coerce_responses_for_pending_requests_strict and its non-strict sibling: the strict version does not index pending events by event.request_id (only by dict key), while the non-strict version was updated in this same PR to include both lookups. When dict keys and event.request_id attributes diverge, responses may bypass type validation in the strict path.

✓ Security Reliability

The PR implements solid input validation and contract enforcement for the canonical interrupt/resume protocol. The security model (pending approval registry, resume contract validation, argument type checking) is well-designed. One reliability issue: the AGUIChatClient pre-serializes available_interrupts and resume before passing to post_run, which internally re-serializes them, creating wasteful double-validation that could surface unexpected Pydantic validation errors if the models evolve.

✓ Failure Modes

The PR canonicalizes AG-UI interrupt/resume handling with thorough validation. The primary failure-mode concern is in _serialize_resume where unrecognized Mapping structures silently pass through without canonical validation, potentially sending invalid wire data. The snapshot cleanup uses a catch-all exception handler that, while logging, could leave threads in a wedged state if the clear fails after pending-approval registry entries have already been consumed.

✗ Design Approach

I found two design-level regressions in the new pause/resume contract handling. First, the strict resume validator no longer accepts the legacy toolCallId alias even though the rest of this package still treats it as a compatibility input, so direct server callers can now fail before their resume payload is canonicalized. Second, the new strict workflow-response coercion dropped the existing alias mapping between the workflow runner’s internal dict key and request_event.request_id, which means some canonical interrupt IDs now bypass the very type/approval validation this PR is trying to enforce.

Flagged Issues

  • _run_common.py:158_strict_resume_entries rejects resume entries using the legacy toolCallId key even though _normalize_resume_interrupts (line 80-85) and _serialize_resume_entry (_http_service.py:59-64) still accept it as a compatibility alias. Direct AG-UI requests with toolCallId now fail validation instead of being canonicalized.
  • _workflow_run.py:549 – The strict coercion helper indexes pending_by_id only by the raw dict key, unlike the non-strict sibling which also indexes by event.request_id. Since _pending_workflow_interrupt_ids prefers event.request_id over the dict key (line 242), resume payloads addressed to the canonical interrupt ID bypass _coerce_response_for_request() and _approval_response_matches_request() entirely.

Suggestions

  • The client at _client.py:443-444 pre-serializes available_interrupts and resume, but post_run (_http_service.py:209,213) re-serializes them internally. Consider passing raw values to post_run and letting it handle serialization exclusively, or removing the redundant internal serialization for the client path.

Automated review by moonbox3's agents

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_run_common.py Outdated
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Flagged issue

_run_common.py:158_strict_resume_entries rejects resume entries using the legacy toolCallId key even though _normalize_resume_interrupts (line 80-85) and _serialize_resume_entry (_http_service.py:59-64) still accept it as a compatibility alias. Direct AG-UI requests with toolCallId now fail validation instead of being canonicalized.


Source: automated DevFlow PR review

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Flagged issue

_workflow_run.py:549 – The strict coercion helper indexes pending_by_id only by the raw dict key, unlike the non-strict sibling which also indexes by event.request_id. Since _pending_workflow_interrupt_ids prefers event.request_id over the dict key (line 242), resume payloads addressed to the canonical interrupt ID bypass _coerce_response_for_request() and _approval_response_matches_request() entirely.


Source: automated DevFlow PR review

@moonbox3 moonbox3 changed the title Python: [Python][BREAKING]: Canonicalize AG-UI interrupt and resume handling Python: [BREAKING]: Canonicalize AG-UI interrupt and resume handling Jul 6, 2026


def _evict_oldest_approvals(registry: dict[str, PendingApprovalEntry], max_size: int = 10_000) -> None:
def _pending_approval_request_id(entry: PendingApprovalEntry) -> str | 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.

I'm never a big fan of these kinds of helper functions that are only used once...

pending_approvals: dict[PendingApprovalKey, PendingApprovalEntry] | None,
thread_id: str,
) -> bool:
if pending_approvals is 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.

might as well cover the empty dict case early:

Suggested change
if pending_approvals is None:
if not pending_approvals:

pending_approvals: dict[PendingApprovalKey, PendingApprovalEntry] | None,
thread_id: str,
) -> set[str]:
if pending_approvals is 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.

same here


converter = AGUIEventConverter()

available_interrupts = options.get("available_interrupts")

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.

Suggested change
available_interrupts = options.get("available_interrupts")
available_interrupts = options.get("available_interrupts", options.get("availableInterrupts"))

if "outcome" in event:
outcome = event.get("outcome")
additional_properties["outcome"] = outcome
if isinstance(outcome, dict) and outcome.get("type") == "interrupt":

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.

is there ever a case where we would accept outcome not to be a dict, and what should happen? wonderign if this should just fail loudly instead of silently ignoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ag-ui Usage: [Issues, PRs], Target: AG-UI protocol integration documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants