Repository navigation
Description
Description
When a tool is gated by human-in-the-loop approval over AG-UI using the confirm_changes flow (AgentFrameworkAgent(require_confirmation=True), as CopilotKit uses), the tool's executed result is emitted only as a transient TOOL_CALL_RESULT event and is never recorded in flow.tool_results. As a result, the end-of-turn MESSAGES_SNAPSHOT omits the tool-result message.
Clients that reconcile rendered state from MESSAGES_SNAPSHOT (e.g. CopilotKit) then revert the already-completed tool call back to "in progress": the tool chip flips from done → running right after approval, and again on the next turn's snapshot.
Package Versions
agent-framework-core: 1.9.0, agent-framework-ag-ui:1.0.0rc5, agent-framework-ollama:1.0.0b260521, ollama: 0.5.3
Python Version
Python 3.12
Additional Context
Minimal reproduction
MAF advisor exposed over AG-UI (add_agent_framework_fastapi_endpoint, require_confirmation=True) with a tool requiring approval (e.g. the Microsoft Learn MCP with require_approval=True), driven by a CopilotKit useHumanInTheLoop({ name: "confirm_changes" }) client. Ask a question that calls the gated tool, approve it: the tool chip stays "Running" after the result returns, and remains "Running" after the next turn.
Root cause
_make_approval_tool_result_events (agent_framework_ag_ui/_agent_run.py) builds the TOOL_CALL_RESULT event but does not append to flow.tool_results, which is what _build_messages_snapshot uses to attach tool-result messages.
_clean_resolved_approvals_from_snapshot does patch the direct approval_mode flow — there the approval-response tool message is keyed by the original call_id, so it gets replaced with the executed result (covered by test_approval_resume_snapshot_replaces_approval_payload_with_tool_result). It does not cover the confirm_changes flow, where the HITL tool call carries its own id, so the original call's result never lands in the snapshot.
Expected vs actual
- Expected: after approval, the tool chip shows a completed/result state and stays there across subsequent snapshots.
- Actual: the chip reverts to "in progress" / "Running" after the tool has already produced its result.
Additional info
Regression-style: the direct approval_mode flow is fine; only the confirm_changes flow is affected. A fix PR follows.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
lordlinus commentedon Jul 1, 2026
Confirming this is real, and flagging a more severe variant: for a hosted/remote agent driven through this same
confirm_changesflow (add_agent_framework_fastapi_endpoint+ a customSupportsAgentRunthat forwards to a Foundry hosted agent overprevious_response_id-chained Responses calls — the patternagent-framework-ag-uiitself recommends for #6652), the missing snapshot cleanup isn't just a UI rendering glitch — it causes the approved tool to be genuinely re-executed a second time on the very next unrelated turn, with a real, duplicated side effect.Reproduction (isolates this to the raw Responses API /
previous_response_idchaining, no CopilotKit, no bridge code involved)Packages:
agent-framework-core==1.9.0,agent-framework-ag-ui==1.0.0rc5,agent-framework-foundry==1.8.2— identical core/ag-ui versions to this issue.A minimal demo agent with a
get_valueread tool and anapply_delta(delta)tool decorated@tool(approval_mode="always_require"), served viaResponsesHostServer/FoundryChatClient. Four turns, chained purely viaprevious_response_idagainst the raw/responsesendpoint (no AG-UI/CopilotKit involved at all in this specific reproduction — just curl):This reproduces consistently (verified across multiple independent runs). Turn 4 sends nothing approval-shaped in its own input — the only way
apply_deltacould have executed again is if the chained conversation state on theResponsesHostServer/FoundryChatClientside still considers the turn-2 tool call "pending" and resolves/executes it again once a new turn arrives, which lines up with this issue's root cause (the approval's tool result never gets durably recorded/cleaned up for theconfirm_changes-style flow,_clean_resolved_approvals_from_snapshotonly covers the directapproval_modeflow).I also verified via the AG-UI bridge itself (
add_agent_framework_fastapi_endpoint+ a custom hosted-agent-forwardingSupportsAgentRun, same package versions) that the bridge's own approval-detection logic correctly identifies turn 4 as a normal (non-approval) turn and does not re-send anmcp_approval_response— yet the hosted agent still re-executes the tool, confirming the duplication happens upstream of any AG-UI-layer bridge/proxy code, in the core Responses/HITL resolution path itself.Given the tool executed was gated specifically because it's consequential (side-effecting), a second, unrequested execution triggered purely by an unrelated later message is a correctness/safety issue beyond the reported UI "chip reverts to running" symptom — worth confirming whether the fix PR for the missing tool-result snapshot entry also addresses (or needs to separately address) the underlying re-execution, not just the display state.
Happy to share the full repro script / logs if useful.
Isolate root cause of the multi-turn approval re-execution bug
lordlinus commentedon Jul 1, 2026
Filed #6851 to track the more severe duplicate-execution consequence of this same root cause separately, since it's a safety issue (real side-effect duplication) beyond the UI display symptom reported here — cross-linking for visibility.
lordlinus commentedon Jul 1, 2026
Update: I posted a correction on the root-cause attribution over on #6851 (which I'd cross-linked here) — it's not
agent_framework_ag_ui, the bug reproduces with that package not even installed. The actual trigger is chainingprevious_response_idthrough a response that resolved anmcp_approval_response. See #6851 for the isolation details and a client-side workaround we shipped in the meantime.Mitigate HITL duplicate-execution bug in the bridge (DIRECT mode)
antsok commentedon Jul 2, 2026
Hi Evan Mattson (@moonbox3), could you help tag this issue as a Python bug, since I failed to open it properly in the first place?
github-actions commentedon Jul 7, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
Repro: In
python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py,_make_approval_tool_result_events(line 390) buildsToolCallResultEventobjects but never appends corresponding dicts toflow.tool_results; additionally_clean_resolved_approvals_from_snapshot(line 650) cannot match theconfirm_changestool'stoolCallId(theconfirm_id) againstresult_by_call_idwhich is keyed by the original tool'scall_id. Minimal repro: callrun_agent_streamwith resume messages containing an assistant tool_calls array (original tool + confirm_changes) followed by a tool result for the confirm_changes call with{"accepted": true, "steps": [...]}, then assert the MESSAGES_SNAPSHOT includes the original tool's result — it will be missing.python/packages/ag-ui/tests/ag_ui/test_confirm_changes_snapshot_bug.py1.0.0rc51.0.0rc7