git:20260709.c7babcf to git:20261007.581d809

101 added, 65 removed. Audit A to A.

---
name: foundry-hosted-agent-copilotkit
- description: 'Ongoing development guidance for agentic web apps that pair a CopilotKit frontend with Microsoft Agent Framework agents on Azure AI Foundry hosted agents over the AG-UI protocol - add and gate agent tools, wire human-in-the-loop approvals, build generative UI and shared state, debug the event stream, upgrade pre-1.0 packages safely, and deploy hosted agent updates.'
+ description: 'Build and evolve agentic web apps that pair a CopilotKit React frontend with a Microsoft Agent Framework agent running as a Microsoft Foundry hosted agent, connected over the AG-UI protocol. Covers choosing the wiring, using Foundry-native primitives instead of hand-rolled plumbing, agent tools, human-in-the-loop approvals, generative UI and shared state, local run and azd deploy, debugging the event stream, and safe upgrades.'
---
- # Developing with CopilotKit + AG-UI + Azure AI Foundry Hosted Agents
+ # CopilotKit + AG-UI + Microsoft Foundry hosted agents
- Use this skill for development work inside an EXISTING application built on this stack: a React/Next.js frontend using CopilotKit, connected over the AG-UI protocol to a Microsoft Agent Framework (MAF) agent (Python or .NET) that runs as — or is being developed against — an Azure AI Foundry hosted agent (paid Azure service; usage may incur costs).
+ Use this skill when a React/Next.js app uses CopilotKit and talks over AG-UI to a Microsoft Agent Framework (MAF) agent hosted in Microsoft Foundry. This covers new builds and changes to existing apps. Foundry hosted agents are a paid Azure service.
- Do NOT use this skill to scaffold a new project. Dedicated scaffolders exist (the CopilotKit CLI, `azd ai agent init`); use those, then return here for everything that follows: adding tools, gating them behind approvals, generative UI, shared state, debugging, dependency upgrades, and deploying agent updates.
+ Scaffold the agent with `azd ai agent init` and the frontend with your usual React/Next.js tooling. Then follow this skill for everything that connects them.
## Mental model
```text
- CopilotKit hooks (React) useFrontendTool / useHumanInTheLoop /
- │ useRenderToolCall / useCoAgent
- ▼
- CopilotKit Runtime (route handler) agents: { <name>: new HttpAgent({ url }) }
- │ AG-UI events over SSE
- ▼
- AG-UI endpoint ← WHERE this lives defines your architecture
- │
- ▼
- MAF Agent (tools, approval modes) → model deployment
+ React (CopilotKit v2) CopilotKitProvider / CopilotChat threadId=<conv_ id> / useInterrupt
+ │ HTTP
+ CopilotKit runtime CopilotRuntime({ agents: { default: new HttpAgent({ url }) } })
+ │ AG-UI (SSE)
+ AG-UI gateway (FastAPI) add_agent_framework_fastapi_endpoint(app, AgentFrameworkAgent(FoundryAgent(...),
+ use_service_session=True, service_session_id_from_thread_id=True)) ← keeps no thread state
+ │ OpenAI Responses + Entra token (https://ai.azure.com/.default)
+ Foundry hosted agent ResponsesHostServer(Agent(tools=[...])) ← deployed with azd
```
- The single most important fact: **a deployed Foundry hosted agent endpoint does not speak AG-UI by default.** It exposes an OpenAI Responses endpoint (`.../protocols/openai/responses`) and/or a raw `.../protocols/invocations` endpoint. AG-UI must be produced somewhere, and where it is produced determines how every feature (especially human-in-the-loop) behaves. The three wirings are described in [references/architecture.md](references/architecture.md).
+ **A deployed hosted agent does not speak AG-UI.** It exposes `.../agents/<name>/endpoint/protocols/openai/responses` (Responses protocol) or `.../protocols/invocations`. Translate with MAF's own adapter (`FoundryAgent` + `agent-framework-ag-ui`) instead of writing an SSE translator by hand. Browsers cannot hold the Entra token, so the gateway is always server-side.
- ## Workflow
+ ## Use the platform first
- Follow these steps for every task on this stack:
+ Before you write any plumbing, check this table. Each row is something older codebases built by hand.
- 1. **Identify the wiring first.** Inspect the codebase before changing anything:
- - `add_agent_framework_fastapi_endpoint(...)` (Python) or `MapAGUI(...)` (.NET) wrapping an in-process agent → Architecture A (in-process AG-UI endpoint).
- - A hosted agent whose own container serves AG-UI, declared with `protocol: invocations` in `agent.yaml` → Architecture B.
- - A separate service translating between the AG-UI endpoint and a hosted agent's `/responses` endpoint (look for `previous_response_id`, `mcp_approval_response`, or a Foundry `conversation` object in the code) → Architecture C (translation bridge).
- - Confirm the frontend agent name: the key in the runtime `agents` config, the `agent` prop on the `<CopilotKit>` provider, and the hosted agent name in `agent.yaml` must all agree.
- 2. **Ground in live documentation.** Every layer here is pre-1.0 or preview and moves between minor versions. Never trust memorized APIs:
- - MAF and Foundry hosted agents: use the Microsoft Docs MCP tools when available, otherwise learn.microsoft.com (`/agent-framework/integrations/ag-ui/`, `/azure/foundry/`).
- - CopilotKit: docs.copilotkit.ai (Microsoft Agent Framework section). Verify hook and runtime API names against the TypeScript declarations bundled in the installed `@copilotkit/*` packages — names have churned (`useCopilotAction` is legacy; current names include `useFrontendTool`, `useHumanInTheLoop`, `useRenderToolCall`, `useCoAgent`).
- - AG-UI protocol: docs.ag-ui.com (event reference, dojo patterns).
- 3. **Execute the task** using the matching reference below.
- 4. **Verify adversarially.** A compiling build, a started dev server, or one successful chat reply is NOT proof. Apply the completion criteria at the end of this skill.
+ | Need | Native primitive (don't hand-roll) |
+ | --- | --- |
+ | Conversation history across turns and restarts | Foundry **conversations**: create one per chat, use its `conv_…` ID as the AG-UI thread ID, and the gateway passes it through (no snapshot store) |
+ | Durable tool approvals | `ResponsesHostServer` persists approval requests by default (`FoundryFunctionApprovalStore`). The AG-UI adapter turns them into AG-UI interrupts and forwards decisions back to the hosted agent |
+ | Runs that survive browser disconnects | Responses `background: true` + `store: true`, then poll or reconnect (see [wiring](references/wiring.md#background-runs-gateway)) |
+ | Durable multi-step workflows with gates | Invocations hosted agent + `@multi_turn_task` + `FoundryStateStore` |
+ | Per-user files for the agent | Session Files API; the agent reads `$HOME` with its own tools. Never inline large documents into the prompt |
+ | Long-term user memory | Foundry Memory Store (user scope) or `FoundryMemoryProvider` |
+ | Tool catalogs, web search, code interpreter | Foundry **Toolbox** (`FoundryToolbox`) with Tool Search, instead of listing every tool up front |
+ | Live docs while coding | The Microsoft Learn and Foundry MCP servers (bundled with this skill's plugin) |
- ## References
+ ## Choose the wiring
- Load on demand; each is self-contained:
+ | If the app needs… | Use | Gateway size |
+ | --- | --- | --- |
+ | Hosted agent + chat + tools + approvals (**default**) | **A. AG-UI gateway over a Responses hosted agent** | ~50 lines of Python |
+ | Every state pattern, local prototype, no Foundry compute | **B. In-process AG-UI** (agent runs inside the FastAPI app) | ~15 lines |
+ | Background runs that keep going after disconnect, reconnect, per-user conversation lists | **C. Background-runs gateway** (custom AG-UI agent over Foundry conversations) | ~150 lines of TypeScript |
+ | Multi-step plan → approve → execute, irreversible steps that must not run twice | **D. Invocations hosted agent** alongside A or C | REST proxy |
- | Reference | Load when |
- | --- | --- |
- | [references/architecture.md](references/architecture.md) | Choosing or understanding the wiring; local-vs-deployed modes; why a translation bridge exists and what it must handle |
- | [references/patterns.md](references/patterns.md) | Implementing any of the 7 AG-UI interaction patterns (frontend tools, backend tool rendering, HITL, generative UI, shared state, predictive state) |
- | [references/hitl.md](references/hitl.md) | Adding or debugging human-in-the-loop approvals, including the known duplicate-execution hazard |
- | [references/troubleshooting.md](references/troubleshooting.md) | Any failure: symptom → root cause → fix tables for every layer |
- | [references/upgrading.md](references/upgrading.md) | Bumping any dependency; version compatibility rules; tracked upstream issues |
- | [references/deploy-loop.md](references/deploy-loop.md) | Running the agent locally with `azd ai agent run`, deploying updates, deployment gotchas |
+ Code for each is in [references/wiring.md](references/wiring.md). In an existing codebase, identify the wiring before you change anything:
+ - `FoundryAgent(...)` wrapped by `add_agent_framework_fastapi_endpoint` means A.
+ - `Agent(client=...)` wrapped directly means B.
+ - A custom `AbstractAgent` calling `/responses` with `background` means C.
+ - `protocol: invocations` in `azure.yaml` means D.
- ## Task playbooks
+ ## Workflow
- ### Add or modify an agent tool
+ 1. **Pick or identify the wiring** (table above).
+ 2. **Use the latest release of every package** (`@copilotkit/*`, `@ag-ui/*`, `agent-framework-*`, `azd` and its `azure.ai.agents` extension). Then ground on live sources:
+ - the Microsoft Learn MCP server for MAF and Foundry
+ - the Foundry MCP server for project state
+ - docs.copilotkit.ai and the `.d.ts` files bundled in the installed `@copilotkit/*` packages
+ - docs.ag-ui.com
- 1. Define the tool on the agent (`@tool` in Python; `AIFunctionFactory.Create` in .NET) with typed, described parameters.
- 2. Keep docstrings grounding-safe: do not put concrete example values in parameter descriptions for fields the model must derive from real data — models copy literal examples. Use placeholders and validate inside the tool.
- 3. Return compact, model-consumable values; rich formatting belongs in the UI render, not the tool result.
- 4. Decide the approval mode now: side-effecting tools get `approval_mode="always_require"` (see [references/hitl.md](references/hitl.md)); read-only tools stay unrestricted.
- 5. If the tool call should render in the UI, add a `useRenderToolCall`/render entry for it ([references/patterns.md](references/patterns.md)).
- 6. Verify live: trigger the tool through the chat UI, confirm the call and result stream as `TOOL_CALL_*` events, and confirm renamed or re-typed parameters did not break any frontend component that parses the arguments.
+ APIs move between minor versions, so don't trust memorized names.
+ 3. **Build or change it** with a playbook below.
+ 4. **Verify adversarially** against the completion criteria. A compiling build, a started server, or one good chat reply is not proof.
- ### Wire human-in-the-loop onto an existing tool
+ ## Playbooks
- Follow [references/hitl.md](references/hitl.md) end to end. Summary: mark the tool (`approval_mode="always_require"` / `ApprovalRequiredAIFunction`), enable confirmation on the AG-UI wrapper, register the approval UI hook on the frontend, and make the response payload shape match what the server detection expects. Then test approve AND reject AND a follow-up turn after approval (see the duplicate-execution hazard).
+ ### Build a new app end to end (wiring A)
- ### Build generative UI or shared state
+ 1. In a **fresh directory**, run `azd ai agent init --no-prompt ...` against the project. Confirm `azd env get-values` shows `FOUNDRY_PROJECT_ENDPOINT`, then write tools in `main.py` ([wiring §1](references/wiring.md#1-hosted-agent)).
+ 2. Run it locally with `azd ai agent run` (port 8088) and smoke-test it with `azd ai agent invoke --local "hi"`.
+ 3. `azd deploy` (each deploy creates a new agent version), then `azd ai agent invoke "hi"`.
+ 4. Add the AG-UI gateway exactly as in [wiring §2](references/wiring.md#2-ag-ui-gateway-wiring-a), including the thread-ID-is-conversation-ID flags and the interop shim. Create a conversation, then test chat **and** an approve resume with `curl -N` using its `conv_…` ID as `threadId` before touching the UI.
+ 5. Add the conversation route, the CopilotKit runtime route and the React provider ([wiring §3](references/wiring.md#3-copilotkit-runtime-and-react)).
+ 6. Add `useInterrupt` approval UI and a tool renderer ([hitl.md](references/hitl.md#frontend-copilotkit-v2)).
+ 7. Verify every completion criterion, first locally and then against the deployed agent.
- Follow the pattern table in [references/patterns.md](references/patterns.md). Know the honesty caveat: state synchronization patterns are native when the AG-UI adapter wraps an in-process agent (Architecture A/B); through a Responses-protocol bridge (Architecture C) they require explicit synthesis work — check what the codebase actually implements before promising the feature.
+ ### Add or change an agent tool
+ 1. Define it with `@tool` (Python) or `AIFunctionFactory.Create` (.NET), using typed and described parameters.
+ 2. Keep parameter descriptions free of concrete example values for data the model must derive. Models copy literal examples.
+ 3. Return compact values. Rich formatting belongs in the UI renderer.
+ 4. Side-effecting tools get `approval_mode="always_require"` ([hitl.md](references/hitl.md)).
+ 5. Redeploy the hosted agent. Tools live in the hosted agent, not the gateway.
+ 6. Verify through the UI that `TOOL_CALL_*` events stream, and that any component parsing the arguments still works.
+
+ ### Generative UI and shared state (CopilotKit v2 hooks)
+
+ | Pattern | Agent side | Frontend hook |
+ | --- | --- | --- |
+ | Frontend tool (agent calls the UI) | none; arrives in `RunAgentInput.tools` | `useFrontendTool` |
+ | Render a backend tool call | normal `@tool` | `useRenderTool` / `useRenderToolCall` / `useDefaultRenderTool` |
+ | Human approval of a tool call | `approval_mode="always_require"` | `useInterrupt` → `resolve({approved})` |
+ | Agent asks the user for input (frontend-resolved tool) | none | `useHumanInTheLoop` → `respond(...)` |
+ | Shared or predictive state | `state_schema` / `predict_state_config` on `AgentFrameworkAgent` | `useAgent` (read `agent.state`) |
+
+ State patterns emit `STATE_SNAPSHOT`/`STATE_DELTA` only when the adapter can see the agent's state:
+ - **Wiring B** gets them natively.
+ - **Wiring A** needs them declared on the gateway's `AgentFrameworkAgent`. Test this before you promise the feature.
+ - **Wiring C** has no state events unless you synthesize them.
+
### Debug a broken flow
- 1. Reproduce at the lowest layer first: `curl -N` the AG-UI endpoint with a minimal `RunAgentInput` JSON body and read the raw SSE events. If the bug reproduces there, the frontend is innocent.
- 2. For hosted agents, go one layer lower: call the agent's `/responses` endpoint directly. This is how the known re-execution bug was isolated to the framework rather than the UI stack.
- 3. Match the symptom against [references/troubleshooting.md](references/troubleshooting.md) — exact error strings are listed.
- 4. Restart a locally running hosted agent (`azd ai agent run`) between verification passes if the agent holds in-memory state; stale state makes tests pass or fail for the wrong reason.
+ Work from the lowest layer upward:
+ 1. Call the hosted agent with `azd ai agent invoke` or `curl` against `/responses`.
+ 2. Call the gateway with a real conversation ID. A made-up thread ID returns HTTP 500 with this wiring:
- ### Upgrade dependencies
+ ```bash
+ CONV=$(curl -s -X POST "$FOUNDRY_PROJECT_ENDPOINT/agents/$FOUNDRY_AGENT_NAME/endpoint/protocols/openai/conversations?api-version=v1" \
+ -H "Authorization: Bearer $(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)" \
+ -H 'content-type: application/json' -d '{}' | jq -r .id)
+ curl -N -X POST <gateway>/ -H 'content-type: application/json' -d '{"threadId":"'$CONV'","runId":"r1","messages":[{"id":"m1","role":"user","content":"hi"}],"tools":[],"context":[],"state":{},"forwardedProps":{}}'
+ ```
+ 3. Test the CopilotKit route.
+ 4. Test the browser.
- Follow [references/upgrading.md](references/upgrading.md). Never bump a single package in isolation: the version relationship rules there (runtime ↔ AG-UI client, agent-framework line consistency, hosting protocol ↔ manifest version) must hold simultaneously, and any local workaround must be re-validated against its tracked upstream issue before removal.
+ The first layer that fails owns the bug. Then match the symptom in [troubleshooting.md](references/troubleshooting.md). Use `azd ai agent monitor` for hosted-agent logs.
- ### Deploy an agent update
+ ### Upgrade dependencies
- Follow [references/deploy-loop.md](references/deploy-loop.md): iterate locally against the real agent with `azd ai agent run`, then `azd deploy` (each deploy creates a new agent version), then verify the deployed agent — including the approval pause — before declaring success.
+ Follow the version rules in [troubleshooting.md](references/troubleshooting.md#upgrades). Upgrade all packages together, then re-run the completion criteria live.
## Completion criteria
- A change on this stack is done only when ALL of these hold:
-
- 1. The read/query path works through the real UI (not only via curl).
- 2. Every approval-gated tool was tested both ways: approve → the tool executes server-side and state visibly changes; reject → the tool does not run and the agent acknowledges.
- 3. At least one follow-up turn was sent in the same thread after an approval, and the gated tool did NOT silently execute again ([references/hitl.md](references/hitl.md), duplicate-execution hazard).
- 4. Tool calls render correctly at stream end, not just during streaming (message snapshots can differ from live events).
- 5. For deployed changes: the checks above were run against the deployed endpoint, not only locally — deployment success is not proof of behavior.
+ A change is done only when all of these hold:
+ 1. The chat path works through the real UI, not only through curl.
+ 2. Every approval-gated tool was tested both ways:
+ - Approve: the tool runs once on the server and the side effect is observable.
+ - Reject: the tool does not run, and the agent acknowledges it.
+ 3. After an approval, a follow-up turn in the same thread does **not** re-run the gated tool.
+ 4. Tool and approval cards still render after `RUN_FINISHED`, not only while streaming.
+ 5. For deployed changes, checks 1–4 passed against the deployed agent and gateway. A successful deployment proves nothing about behavior.