Is your feature request related to a problem? Please describe.
When a deployed Foundry hosted agent is exposed over AG-UI with the native
add_agent_framework_fastapi_endpoint(FoundryAgent(...)), the human-in-the-loop
(HITL) approve step never re-executes the gated tool.
The AG-UI agent adapter resolves approvals locally:
agent_framework_ag_ui/_agent_run.py → _resolve_approval_responses()
(main, line ~455) calls _try_execute_function_calls() (line ~568) — i.e. it
tries to run the approved tool in-process. A hosted/remote agent has no local
tool bodies, so nothing runs: the tool never re-executes server-side, and state is
unchanged. The approval request surfaces correctly (with allow_preview=True),
but the response has nowhere to go — it is effectively dropped.
The hosting side already supports this round-trip
(#5666 / #5654 / #4054 — the deployed agent emits/consumes
mcp_approval_request / mcp_approval_response). The missing half is the client /
AG-UI agent-adapter side forwarding the approval response to the remote agent.
This is the direct analog of #5054 → #5070, where FoundryAgent (Responses API)
did not surface oauth_consent_request as a CUSTOM AG-UI event and it was fixed by
surfacing that Responses event through the Foundry client. Here it's the
mcp_approval_request → mcp_approval_response round-trip that needs the same
treatment on the agent path.
Describe the solution you'd like
When the agent wrapped by add_agent_framework_fastapi_endpoint is a
Responses-backed remote/hosted agent (e.g. FoundryAgent), the AG-UI agent adapter
should forward an incoming function_approval_response to the agent (translated
to an mcp_approval_response on the Responses request) so the hosted runtime
re-executes the gated tool — instead of executing it locally via
_try_execute_function_calls. (Symmetric to how _workflow_run.py was taught to
honour approval responses in #4546 / #6360, and to the oauth_consent_request
surfacing in #5070.)
Versions
agent-framework-core 1.9.0
agent-framework-foundry 1.8.2
agent-framework-ag-ui 1.0.0rc5
- Confirmed unchanged on
main (_agent_run.py _resolve_approval_responses →
_try_execute_function_calls).
Minimal reproduction
1. The hosted agent (agent.py) — one read tool + one approval-gated tool. Deploy
it as a Foundry hosted agent (or run it locally with azd ai agent run):
# agent.py
import json
from agent_framework import Agent, tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import DefaultAzureCredential
STATE = {"value": 100}
@tool
async def get_value() -> str:
"""Return the current value."""
return json.dumps(STATE)
@tool(approval_mode="always_require")
async def apply_delta(delta: float) -> str:
"""Change the value by `delta`. Requires human approval."""
STATE["value"] = round(STATE["value"] + float(delta), 4)
return json.dumps({"status": "ok", **STATE})
def build_hosted_agent():
client = FoundryChatClient(
project_endpoint="https://<acct>.services.ai.azure.com/api/projects/<proj>",
model="gpt-4.1",
credential=DefaultAzureCredential(),
)
return Agent(client=client, name="approval_demo",
instructions="Call get_value to read. Call apply_delta to change it.",
tools=[get_value, apply_delta], default_options={"store": False})
2. The native AG-UI bridge (native_bridge.py) — points at the DEPLOYED agent:
# native_bridge.py -> uvicorn native_bridge:app --port 8080
from fastapi import FastAPI
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
from agent_framework.foundry import FoundryAgent
from azure.identity import DefaultAzureCredential
agent = FoundryAgent(
project_endpoint="https://<acct>.services.ai.azure.com/api/projects/<proj>",
agent_name="approval_demo", # the DEPLOYED hosted agent
credential=DefaultAzureCredential(),
allow_preview=True, # required to reach the hosted-agent endpoint
name="approval_demo", id="approval_demo",
)
app = FastAPI()
add_agent_framework_fastapi_endpoint(app, agent, "/")
3. Drive it over AG-UI:
# Turn 1: ask to change the value -> the agent PAUSES with a function_approval_request
curl -sN http://localhost:8080/ -H 'Content-Type: application/json' -d '{
"threadId":"t1","runId":"r1","state":{},"tools":[],"context":[],"forwardedProps":{},
"messages":[{"id":"m1","role":"user","content":"Apply a delta of 10."}]
}' # -> emits a function_approval_request for apply_delta (good)
# Turn 2: APPROVE it (replay history + the approval response)
curl -sN http://localhost:8080/ -H 'Content-Type: application/json' -d '{
"threadId":"t1","runId":"r2","state":{},"tools":[],"context":[],"forwardedProps":{},
"messages":[ ...prior turn..., {"id":"a1","role":"tool","content":"{\"accepted\":true}"} ]
}' # -> RUN_FINISHED, but get_value still returns {"value": 100}
Observed (test matrix, against a real deployed agent)
| Configuration |
HITL approve re-executes the hosted tool? |
Native FoundryAgent (no preview) |
❌ 400 "Hosted agents can only be called through the agent endpoint" |
Native FoundryAgent(allow_preview=True) |
❌ approval request surfaces, but approve leaves state unchanged |
Native + allow_preview + disabling ag-ui local approval interception |
❌ still unchanged |
A subsequent get_value shows the value never changed; no mcp_approval_response
is ever sent to the hosted agent.
Expected
After approve, the hosted runtime receives an mcp_approval_response, re-executes
apply_delta, and get_value returns {"value": 110.0}.
Current workaround
A hand-rolled SupportsAgentRun proxy that streams the hosted agent's Responses,
surfaces mcp_approval_request as the AG-UI approval, and on approve POSTs an
mcp_approval_response to .../agents/<name>/endpoint/protocols/openai/responses.
It works, but it duplicates translation the framework already does for the
non-approval path — hence this request to support it natively on the agent adapter.
Additional context
Happy to provide the full proxy + a deterministic repro script if useful.
Is your feature request related to a problem? Please describe.
When a deployed Foundry hosted agent is exposed over AG-UI with the native
add_agent_framework_fastapi_endpoint(FoundryAgent(...)), the human-in-the-loop(HITL) approve step never re-executes the gated tool.
The AG-UI agent adapter resolves approvals locally:
agent_framework_ag_ui/_agent_run.py→_resolve_approval_responses()(
main, line ~455) calls_try_execute_function_calls()(line ~568) — i.e. ittries to run the approved tool in-process. A hosted/remote agent has no local
tool bodies, so nothing runs: the tool never re-executes server-side, and state is
unchanged. The approval request surfaces correctly (with
allow_preview=True),but the response has nowhere to go — it is effectively dropped.
The hosting side already supports this round-trip
(#5666 / #5654 / #4054 — the deployed agent emits/consumes
mcp_approval_request/mcp_approval_response). The missing half is the client /AG-UI agent-adapter side forwarding the approval response to the remote agent.
This is the direct analog of #5054 → #5070, where
FoundryAgent(Responses API)did not surface
oauth_consent_requestas a CUSTOM AG-UI event and it was fixed bysurfacing that Responses event through the Foundry client. Here it's the
mcp_approval_request→mcp_approval_responseround-trip that needs the sametreatment on the agent path.
Describe the solution you'd like
When the agent wrapped by
add_agent_framework_fastapi_endpointis aResponses-backed remote/hosted agent (e.g.
FoundryAgent), the AG-UI agent adaptershould forward an incoming
function_approval_responseto the agent (translatedto an
mcp_approval_responseon the Responses request) so the hosted runtimere-executes the gated tool — instead of executing it locally via
_try_execute_function_calls. (Symmetric to how_workflow_run.pywas taught tohonour approval responses in #4546 / #6360, and to the
oauth_consent_requestsurfacing in #5070.)
Versions
agent-framework-core1.9.0agent-framework-foundry1.8.2agent-framework-ag-ui1.0.0rc5main(_agent_run.py_resolve_approval_responses→_try_execute_function_calls).Minimal reproduction
1. The hosted agent (
agent.py) — one read tool + one approval-gated tool. Deployit as a Foundry hosted agent (or run it locally with
azd ai agent run):2. The native AG-UI bridge (
native_bridge.py) — points at the DEPLOYED agent:3. Drive it over AG-UI:
Observed (test matrix, against a real deployed agent)
FoundryAgent(no preview)FoundryAgent(allow_preview=True)+ allow_preview +disabling ag-ui local approval interceptionA subsequent
get_valueshows the value never changed; nomcp_approval_responseis ever sent to the hosted agent.
Expected
After approve, the hosted runtime receives an
mcp_approval_response, re-executesapply_delta, andget_valuereturns{"value": 110.0}.Current workaround
A hand-rolled
SupportsAgentRunproxy that streams the hosted agent's Responses,surfaces
mcp_approval_requestas the AG-UI approval, and on approve POSTs anmcp_approval_responseto.../agents/<name>/endpoint/protocols/openai/responses.It works, but it duplicates translation the framework already does for the
non-approval path — hence this request to support it natively on the agent adapter.
Additional context
Happy to provide the full proxy + a deterministic repro script if useful.