| name | foundry-agent-deploy |
| description | Build, register, and wire up a Foundry Hosted Agent end-to-end on Azure: build the container to ACR, register a hosted-agent version with the azure-ai-projects SDK, grant least-privilege RBAC, configure the CopilotKit runtime App Service, and verify through the Static Web App. Use when deploying or redeploying a hosted agent + CopilotKit runtime + SWA frontend. WHEN: "deploy Foundry hosted agent", "register hosted agent version", "wire CopilotKit runtime to Foundry", "az acr build agent", "create_version hosted agent", "invocations api-version", "hosted agent RBAC", "verify agent through static web app". |
Skill: Deploy a Foundry Hosted Agent + CopilotKit runtime + SWA
Validated, end-to-end deployment flow for the three-component generative-UI
solution (agent → runtime → frontend). Assumes infra is provisioned via azd
(azd up). Pair with the foundry-hosted-agent-maf skill for the agent code.
Deployment order (matters)
- Provision infra —
azd up (ACR, App Service, SWA, Foundry account +
model, App Insights, managed identities).
- Deploy runtime + frontend —
azd deploy runtime, azd deploy web (or via
azd up). The runtime boots even before the agent exists (lazy 503).
- Build the agent image to ACR.
- Register a hosted-agent version and poll to
active.
- Grant RBAC to the agent instance identity.
- Wire the runtime to the invocations endpoint (with
api-version).
- Verify end-to-end through the SWA.
1–2. Provision + deploy
azd auth login
azd up
azd deploy runtime # redeploy after runtime/src/server.ts changes (npm run build first)
The azure.ai.agents postdeploy hook logs AZURE_AI_PROJECT_ENDPOINT is not set — cosmetic, deploy succeeds. Node App Service cold start ≈100s.
3. Build the agent image
$acr = (azd env get-values | ConvertFrom-StringData).AZURE_CONTAINER_REGISTRY_NAME.Trim('"')
$tag = "maf-agui-$(Get-Date -Format yyyyMMddHHmmss)"
az acr build --registry $acr --image "analytics-agent:$tag" --image "analytics-agent:latest" ./agent
4. Register a hosted-agent version
Use the azure-ai-projects Python SDK (see scripts/register_agent.py — update
IMAGE to the new tag, then run it):
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
HostedAgentDefinition, ProtocolVersionRecord, AgentEndpointProtocol, ContainerConfiguration,
)
from azure.identity import DefaultAzureCredential
project = AIProjectClient(endpoint=PROJECT_ENDPOINT,
credential=DefaultAzureCredential(), allow_preview=True)
agent = project.agents.create_version(
agent_name="analytics-agent",
definition=HostedAgentDefinition(
container_configuration=ContainerConfiguration(image=IMAGE),
protocol_versions=[ProtocolVersionRecord(protocol=AgentEndpointProtocol.INVOCATIONS, version="1.0.0")],
cpu="1", memory="2Gi",
environment_variables={
"AZURE_OPENAI_ENDPOINT": "https://<account>.openai.azure.com/",
"MODEL_DEPLOYMENT_NAME": "model-router",
},
),
)
PROJECT_ENDPOINT = https://<account>.services.ai.azure.com/api/projects/<project>.
- Poll
project.agents.get_version("analytics-agent", agent_version="N").status
until it equals AgentVersionStatus.ACTIVE (usually near-instant). Compare to the
enum, not str(status) — under Python 3.11+ str(status) is "AgentVersionStatus.ACTIVE".
- The agent's Entra instance identity principal is on the create response at
agent.instance_identity.principal_id.
SDK note (azure-ai-projects 2.3.0): the image lives in
container_configuration=ContainerConfiguration(image=...), the protocol list field is
protocol_versions, and the enum is AgentEndpointProtocol (formerly AgentProtocol).
See docs/hosted-agent-sdk-2.3.0-api-changes.md.
5. Grant RBAC (least privilege)
The account has disableLocalAuth: true, so the agent identity needs data-plane
roles on the Foundry account:
$scope = "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>"
az role assignment create --assignee <instance-principal-id> --role "Cognitive Services OpenAI User" --scope $scope
az role assignment create --assignee <instance-principal-id> --role "Cognitive Services User" --scope $scope
6. Wire the runtime to the invocations endpoint
The URL MUST include ?api-version=v1 (or 2025-05-15-preview). Older
api-versions return 400.
$rg = "<rg>"; $app = "<runtime-app>"
$inv = "https://<account>.services.ai.azure.com/api/projects/<project>/agents/analytics-agent/endpoint/protocols/invocations?api-version=v1"
az webapp config appsettings set -g $rg -n $app --settings FOUNDRY_AGENT_ENDPOINT="$inv"
az webapp restart -g $rg -n $app
The runtime authenticates with its App Service managed identity
(DefaultAzureCredential, scope https://ai.azure.com/.default).
7. Verify (through the SWA only)
Direct calls to the runtime App Service return 401 by design (locked SWA
linked backend). Always test via the SWA hostname.
$swa = "https://<swa-hostname>"
# a) runtime lists the agent
'{"method":"info"}' | Out-File $env:TEMP\i.json -Encoding ascii -NoNewline
curl.exe -sS -X POST $swa/api/copilotkit -H "Content-Type: application/json" --data-binary "@$env:TEMP\i.json"
# expect: {"version":"1.61.2","agents":{"analytics-agent":{...}}, ...}
# b) full run (AG-UI SSE: RUN_STARTED / TOOL_CALL_* / TOOL_CALL_RESULT / TEXT_MESSAGE_*)
@'
{"method":"agent/run","params":{"agentId":"analytics-agent"},
"body":{"threadId":"t1","runId":"r1",
"messages":[{"id":"m1","role":"user","content":"Add a todo 'Ship blog' then list all todos."}],
"tools":[],"context":[],"state":{},"forwardedProps":{}}}
'@ | Out-File $env:TEMP\run.json -Encoding ascii -NoNewline
curl.exe -sS -N -X POST $swa/api/copilotkit -H "Content-Type: application/json" --data-binary "@$env:TEMP\run.json"
Troubleshooting
| Symptom | Cause / fix |
|---|
| Agent 400 "Missing/invalid api-version" | Append ?api-version=v1 to FOUNDRY_AGENT_ENDPOINT. |
/api/copilotkit 404 (X-Powered-By: Express) | Runtime handler mounted with a path prefix; mount at root, gate on req.path. |
| 503 from runtime | FOUNDRY_AGENT_ENDPOINT not set on the App Service; set it + restart. |
| 401 calling runtime directly | Expected — locked SWA linked backend. Use the SWA hostname. |
| Agent 401 to the model | Grant the instance identity Cognitive Services OpenAI User. |
curl -d → 400 "Invalid JSON payload" | Use --data-binary "@file.json" (ASCII, no trailing newline). |
| Runtime "info" empty agents | Rebuild (npm run build) + redeploy; check CopilotRuntime({ agents }) name matches params.agentId. |
See references/verify.md for a standalone verification script.