Skip to content

Python: Fix AG-UI approval thread aliases - #6908

Merged
moonbox3 merged 2 commits into
microsoft:mainfrom
godququ5-code:godququ5/issue-6894
Jul 10, 2026
Merged

Python: Fix AG-UI approval thread aliases#6908
moonbox3 merged 2 commits into
microsoft:mainfrom
godququ5-code:godququ5/issue-6894

Conversation

@godququ5-code

Copy link
Copy Markdown
Contributor

Summary

  • Register AG-UI pending approval requests under both the original client thread id and the provider-advertised conversation id.
  • Store aliases on one shared pending-approval entry so a successful approval consumes every alias and prevents replay.
  • Add a two-turn regression covering both client-thread and provider-conversation resume ids.

Fixes #6894

Root cause

For stateful providers such as Foundry, AG-UI can receive a request with a stable client thread id, then stream a provider conversation id back to the caller. Pending approval requests were registered only under the post-stream provider id, so clients that resumed with the original client thread id could not match the pending approval and the tool was never executed.

Test plan

  • uv run pytest packages/ag-ui/tests/ag_ui/test_approval_thread_id_mismatch.py packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py -k "approval" -q
  • cd packages/ag-ui && uv run poe test
  • cd packages/ag-ui && uv run poe syntax
  • cd packages/ag-ui && uv run poe pyright

Copilot AI review requested due to automatic review settings July 3, 2026 19:40
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 3, 2026
@github-actions github-actions Bot changed the title Fix AG-UI approval thread aliases Python: Fix AG-UI approval thread aliases Jul 3, 2026

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 fixes AG-UI approval resolution for stateful providers (e.g., Foundry) where the provider-advertised conversation_id can differ from the client-supplied thread_id, causing pending approvals to be unresolvable on resume and leading to missing tool outputs.

Changes:

  • Register each pending approval under both the original client thread id and the provider conversation id (as aliases to the same registry entry).
  • Consume/remove all aliases when an approval response is validated to prevent replay across either id.
  • Add a regression test that resumes approvals using either id and verifies replay prevention.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Adds alias-aware pending-approval registration/removal and updates eviction + approval consumption to operate on shared alias entries.
python/packages/ag-ui/tests/ag_ui/test_approval_thread_id_mismatch.py New regression test covering approval resolution via client thread id vs provider conversation id and preventing replay via the alternate id.

@godququ5-code

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@godququ5-code
godququ5-code marked this pull request as ready for review July 3, 2026 19:53
Comment thread python/packages/ag-ui/tests/ag_ui/test_approval_thread_id_mismatch.py Outdated
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/ag-ui/agent_framework_ag_ui
   _agent_run.py106912388%180–187, 234–235, 242, 351, 355, 357, 374, 401–402, 515, 529, 533, 537, 540, 545, 550, 559, 562, 569–575, 607, 619, 630, 633, 668, 722–726, 791, 806, 809, 811, 837, 863–865, 923, 925, 927, 930–934, 948, 956–961, 970–971, 1020–1023, 1034, 1042, 1074, 1089, 1103, 1115, 1145, 1149, 1152, 1154, 1194–1196, 1260, 1266–1267, 1272, 1276–1277, 1327–1329, 1341–1343, 1441, 1449, 1466, 1470, 1515, 1574, 1604–1605, 1783, 1834, 1851, 1871–1872, 1879, 1982, 2007, 2015, 2017, 2020, 2026, 2081, 2084, 2094–2095, 2102, 2148
TOTAL44111527488% 

Python Unit Test Overview

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

@godququ5-code
godququ5-code force-pushed the godququ5/issue-6894 branch from 165a16b to 6911161 Compare July 7, 2026 13:14
# Conflicts:
#	python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py
@moonbox3
moonbox3 added this pull request to the merge queue Jul 10, 2026
Merged via the queue into microsoft:main with commit 01ec3b7 Jul 10, 2026
37 checks passed
RudrenduPaul added a commit to RudrenduPaul/toolgovern that referenced this pull request Jul 18, 2026
Today's require-approval gate is a synchronous, in-process callback with a
30s fail-closed timeout and no way to resume a decision out of band. Add
PendingApprovalRegistry (TS + Python) as a keyed, server-generated-id store
for require-approval decisions, wired into governTool()/govern_tool()
alongside the existing synchronous onApprovalRequired path (unchanged for
callers who don't opt in).

- registerPending()/get()/resolvePending() plus alias tolerance
  (registerAlias) so a rewritten thread/conversation id still resolves to
  the same pending entry -- modeled on the bug fixed in
  microsoft/agent-framework#6908.
- resolvePending() never creates a new entry for an unrecognized id --
  closes the resume-token bypass Corridor's security bot found in
  langchain-ai/langgraph#8169's human_approval() helper.
- Edited arguments supplied to resolvePending() are re-run through the
  classifier before an "allow" is accepted; a still-risky edit is denied
  even after approval, with a real test proving it on both sides.
- governTool()/govern_tool() persist a pending approval BEFORE invoking
  the synchronous handler. A genuine handler answer (allow/deny) closes
  the entry out as terminal; a fail-closed default (no handler, timeout,
  or a throwing handler) leaves it open for later resolution -- otherwise
  the async path would be permanently unreachable.
- resumePendingApproval()/resume_pending_approval() closes the loop: given
  the resolved outcome, it actually invokes the tool and appends one trace
  entry with approvedBy populated end-to-end, not only on the sync path.

60 new TypeScript tests, 53 new Python tests, all existing suites green.
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: [Bug]: AG-UI: 'No tool output found' on Foundry provider

4 participants