Agentic Workforce ME Developer PortalDocs 1.10 · Widget 0.5.0

Backend integration

Connect your Azure AI Foundry project

Connect a Foundry project once, list and inspect the agents already in it, invoke or link them, import one as a draft agent, or publish a published agent back onto Foundry Agent Service — the Entra role, request and response shapes, error codes and the honest status of each capability.

A Foundry project connection is how a tenant works with everything its team already built in Azure AI Foundry Agent Service without moving model traffic off Azure. It is project-level (one connection, many agents) and complements the single-agent external agent registration on Connect an external agent — it does not replace it. Positioning: Agentic Workforce ME is the workforce control plane (hierarchy, manifests, human approvals, tenant audit); Foundry stays the Azure-native runtime plane (Entra, Purview, Defender, Monitor). Link = coexist on Azure; import = run on Agentic Workforce ME instead.

1. The four capabilities and their status

CapabilityRoutesStatus
Understand the agents already in FoundryGET …/agents (paged inventory on GA /agents, classic /assistants fallback), GET …/agents/:name (definition, tools, knowledge refs, version), …/threads, …/runs (classic surface only)Verified live on 29 Sep 2026 against a Foundry project
Integrate — use the agent where it isPOST …/invoke natively on both API generations (classic threads/runs poll, or POST {project}/openai/v1/responses with agent_reference), audited and metered; POST …/link registers it in external_agents as an A2A peerVerified live on 29 Sep 2026 (agent-endpoint Responses route, no Foundry-Features header needed; the project-scoped fallback also answers 200)
Run Agentic Workforce ME agents on Foundry Agent ServicePOST /v1/foundry/connections/:id/publish projects a published manifest to a Foundry prompt agent — admin-only, audited, dry_run, refuses draftsVerified live once on 29 Sep 2026 against a Foundry project (prompt agents only; hosted/container agents not supported)
Migrate Foundry → Agentic Workforce ME (the “Foundry Local replacement”)POST …/import (live) and POST /v1/foundry/import-definition (offline) produce a valid draft manifest with hitl.default = review and a fidelity report of unmapped toolsVerified live on 29 Sep 2026. Vector stores, Toolbox → MCP, evals and workflow graphs are referenced, not copied (M3 roadmap)

2. Quickstart — connect your Foundry project

On the Azure side

Foundry Agent Service needs a kind: AIServices account plus a project, and an Entra app registration that holds the Foundry User role on the project. The platform lists and creates agents, so Foundry Agent Consumer (the invoke-only role that is enough for an external-agent A2A peer) is not enough here. Token scope is https://ai.azure.com/.default.

Shell
# 1. A Foundry (kind AIServices) account + project. An Azure OpenAI account is not the same thing.
az cognitiveservices account create -n <account> -g <rg> -l <region> \
  --kind AIServices --sku S0 --custom-domain <account> \
  --assign-identity --allow-project-management true --yes
az cognitiveservices account project create \
  --name <account> --resource-group <rg> --project-name <project> --location <region>

# 2. An Entra app registration for the platform + the "Foundry User" role on the project.
#    (Microsoft renamed "Azure AI User" to "Foundry User" in 2026 — same role id, either label is the same grant.)
az ad app create --display-name awf-foundry-connector      # note appId, then create a client secret
az role assignment create --assignee <appId> \
  --role 53ca6127-db72-4b80-b1b0-d745d6d5456d \
  --scope $(az cognitiveservices account show -n <account> -g <rg> --query id -o tsv)

# Project endpoint — used verbatim as project_endpoint:
#   https://<account>.services.ai.azure.com/api/projects/<project>

Connect, then prove reachability

Shell
curl -sS -X POST "https://<api-host>/v1/foundry/connections" \
  -H "Authorization: Bearer hive_…" -H "X-Tenant-Id: <tenant id>" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "gov-project",
    "name": "Foundry — gov (uaenorth)",
    "project_endpoint": "https://<account>.services.ai.azure.com/api/projects/<project>",
    "residency_region": "uaenorth",
    "auth_kind": "entra_client_credentials",
    "credentials": {
      "tenant_id": "<entra tenant id>",
      "client_id": "<app registration client id>",
      "client_secret": "<client secret>"
    }
  }'
# → 201 { connection: { id, slug, …, auth_kind, credential_meta: { tenant_id, client_id } } }
#   credential_meta never carries the secret; no GET route returns it either.

project_endpoint must be https:// and its host must end in .services.ai.azure.com — anything else is 400 VALIDATION before a single byte leaves the platform (the tenant’s Entra bearer is sent to that origin). The credential bag is AES-256-GCM at rest and decrypted only inside the gateway, per call; it is never serialized into prompts, run state, checkpoints, audit rows or logs. PATCH with a new credentials bag rotates it (audited as rotated: true); enabled: false makes every live route answer 409 FOUNDRY_UNREACHABLE.

3. Use it

Understand — inventory and inspect

Shell
# Understand: the agents already in the project (GA /agents, classic /assistants fallback; pages of 100, up to 10)
curl -sS "https://<api-host>/v1/foundry/connections/<id>/agents" \
  -H "Authorization: Bearer hive_…" -H "X-Tenant-Id: <tenant id>"
# → { surface, items: FoundryAgentView[] }

# Inspect one: definition, tools, knowledge references, version
curl -sS "https://<api-host>/v1/foundry/connections/<id>/agents/<name>" \
  -H "Authorization: Bearer hive_…" -H "X-Tenant-Id: <tenant id>"
# → { agent: FoundryAgentView }

# Classic surface only — a new-agents-only project answers 409 FOUNDRY_SURFACE_UNSUPPORTED
curl -sS "https://<api-host>/v1/foundry/connections/<id>/agents/<name>/threads" …
curl -sS "https://<api-host>/v1/foundry/connections/<id>/agents/<name>/runs?thread_id=<thread>" …

Integrate — invoke natively, or link as a peer

Shell
# Integrate (a): invoke the Foundry agent natively — the run executes in Foundry, on their identity and traces
curl -sS -X POST "https://<api-host>/v1/foundry/connections/<id>/agents/<name>/invoke" \
  -H "Authorization: Bearer hive_…" -H "X-Tenant-Id: <tenant id>" \
  -H "Content-Type: application/json" \
  -d '{ "input": "Screen bid 6003 against bid 6001 for duplicate banking details." }'
# → { result: { text, status: "completed", surface: "agents", remote_response_id, model, usage? } }
#   audit_log foundry.invoke + usage_events external_agent_call (ids, status, latency, token counts — never the text)

/invoke is an operator / test path: the raw text comes back to the admin and the run executed in Foundry — its trace lands in their Application Insights; the platform records an audit row and a usage row with the remote ids (correlation, not a second copy of Azure Monitor). Once linked, the Foundry agent is one external agent among the others — tool grants, approval policies, the trust floor and the untrusted-output wrapper all apply, exactly as on Connect an external agent.

Migrate in — import to a draft

Shell
# Migrate in (a): from the live project → a DRAFT agent under node_id, HITL defaulted to "review"
curl -sS -X POST "https://<api-host>/v1/foundry/connections/<id>/agents/<name>/import" \
  -H "Authorization: Bearer hive_…" -H "X-Tenant-Id: <tenant id>" \
  -H "Content-Type: application/json" \
  -d '{ "node_id": "<org node id>" }'
# → 201 { agent, version, report: { completeness, unmapped_tools[], knowledge_refs[], suggested_slug, notes[], source } }

The fidelity report is the important screen — it never claims a lossless copy:

JSON
{
  "completeness": "partial",
  "suggested_slug": "foundry-tender-vendor-summary",
  "unmapped_tools": [
    { "type": "file_search",
      "reason": "Vector store / index stays on Foundry until an explicit KB copy (M3)." },
    { "type": "code_interpreter",
      "reason": "Enable code_execution on the draft after review; not auto-enabled." }
  ],
  "knowledge_refs": [{ "kind": "vector_store", "id": "vs_…" }],
  "notes": ["1 knowledge ref(s) stay on Foundry until an explicit copy."],
  "source": { "surface": "agents", "id": "tender-vendor-summary", "name": "tender-vendor-summary", "kind": "prompt" }
}

completeness is partial whenever a tool did not cross, a knowledge reference exists, or the Foundry kind has no portable prompt (hosted, container_app, workflow, external import as identity only). file_search / azure_ai_search map to the kb.search builtin with the corpus left on Foundry; function, openapi, mcp, azure_function, bing_grounding, a2a / connected_agent are listed with the reason and the suggested rebinding. The draft is an ordinary agent: versioned, publishable, with guardrails and audit.

Publish out — a platform agent on Foundry Agent Service

Shell
# Publish out: project a PUBLISHED platform agent onto the project as a Foundry prompt agent. Dry run first.
curl -sS -X POST "https://<api-host>/v1/foundry/connections/<id>/publish" \
  -H "Authorization: Bearer hive_…" -H "X-Tenant-Id: <tenant id>" \
  -H "Content-Type: application/json" \
  -d '{ "agent_id": "<agent id>", "dry_run": true }'
# → { dry_run: true, hive_version: 3, plan: { method: "POST", path: "/agents", api_version: "v1",
#       body: { name, description?, definition: { kind: "prompt", model, instructions, tools } },
#       omitted: ["mcp:tender-tools (register as a Foundry Toolbox / MCP tool in M2)",
#                 "builtin:kb.search (Foundry file_search needs vector_store_ids; bind a vector store in M3)",
#                 "knowledge_bases[] (no silent corpus copy; bind a Foundry vector store in M3)"] } }

# Then for real (drop dry_run):
# → { result: { remote_id: "tender-drafting-assistant:1", name, version: "1", created: true, model, omitted[] }, plan_omitted[], hive_version }
# Re-publishing the same name: Foundry answers 409 on POST /agents; the platform falls back to POST /agents/{name}/versions,
# which returns the EXISTING version for an identical definition (version: "1", created: false) and mints ":2" only when it changed.
# 404 NOT_FOUND when the agent has no published version (drafts never cross).

Only a published version may cross: published manifests are immutable, so the Foundry projection is reproducible; the platform stays the source of truth and the Foundry agent is a projection of it, never the reverse. omitted[] lists every capability that did not cross — MCP servers, A2A peers, knowledge bases and every builtin, kb.search included: Foundry rejects a bare file_search tool (400 Required property ‘vector_store_ids’ is missing), so nothing is sent for it until M3 binds a vector store. Observed live: Foundry answers POST /agents for an existing name with 409 conflict; the platform then calls POST /agents/{name}/versions, which returns the existing version for an identical definition (created: false, version: "1") and mints a new version only when the definition changed. created is derived from the returned version label. Foundry does not validate the model deployment at create time — a wrong name is accepted and fails only at invoke, so publish agents whose model exists on the project.

4. Reference — routes, shapes, roles

Base path /v1/foundry. Every route needs an API key and X-Tenant-Id (see API keys); rows are tenant-scoped by row-level security, so another tenant’s connection id is a 404. Roles: member may read, admin may write, test, invoke, import, link and publish.

RouteRoleBody → response
GET /v1/foundry/connectionsmember→ { items: FoundryConnection[] }
POST /v1/foundry/connectionsadmin{ slug, name, project_endpoint, auth_kind, credentials, api_version?, region?, residency_region?, node_id?, default_surface? } → 201 { connection }; 409 SLUG_TAKEN
GET · PATCH · DELETE /v1/foundry/connections/:idmember · admin · adminPATCH: any create field plus enabled; credentials rotates the bag → { connection }; DELETE → 204
POST …/:id/testadmin→ { ok, surface, count }; persists last_inventory + default_surface
GET …/:id/agentsmember→ { surface, items: FoundryAgentView[] }; audited foundry.inventory
GET …/:id/agents/:namemember→ { agent: FoundryAgentView }
GET …/agents/:name/threads · GET …/agents/:name/runs?thread_id=memberClassic surface only → { items }; 409 FOUNDRY_SURFACE_UNSUPPORTED on a new-agents-only project; thread_id is required for runs
POST …/agents/:name/invokeadmin{ input (≤ 16 000 chars), thread_id? } → { result: FoundryInvokeResult }
POST …/agents/:name/importadmin{ node_id, slug?, name? } → 201 { agent, version, report } (draft)
POST …/agents/:name/linkadmin{ slug?, trust_level = "partner" } → 201 { external_agent: { id, slug, name } }; 409 FOUNDRY_SURFACE_UNSUPPORTED on an api_key connection; 409 SLUG_TAKEN
POST …/:id/publishadmin{ agent_id, dry_run = false } → dry run { dry_run: true, plan, hive_version }; real { result: FoundryPublishResult, plan_omitted[], hive_version }; 404 when the agent has no published version
POST /v1/foundry/import-definitionadmin{ definition, persist = false, node_id?, slug?, name? } → dry run { dry_run: true, view, report, manifest }; with persist: true (then node_id is required) 201 { agent, version, report }

Shapes (from the API’s Zod schemas)

ObjectFields
FoundryConnectionid, slug, name, project_endpoint, api_version (default v1), region?, residency_region?, node_id? (null = tenant-wide), auth_kind, credential_meta (tenant_id / client_id / header / hint — never the secret), default_surface (agents | assistants | null = auto), last_synced_at, enabled, created_at, updated_at
credentials by auth_kindentra_client_credentials: { tenant_id, client_id, client_secret, scope? } (scope defaults to https://ai.azure.com/.default) — the production path; api_key: { api_key, header? } (sent in api-key unless header names another); bearer: { token } (a pre-minted Entra token). The last two are for local doubles; link refuses api_key.
FoundryAgentViewsurface, id, name, description?, kind (prompt | hosted | container_app | workflow | external | assistant), model?, instructions?, tools[] ({ type, name?, description? }), knowledge[] ({ kind: vector_store | azure_ai_search | file | toolbox, id, name? }), metadata?, version?, created_at?, a2a_endpoint?
FoundryInvokeResulttext, status (completed | failed | cancelled | expired | requires_action), surface, remote_thread_id?, remote_run_id?, remote_response_id?, model?, usage? ({ input_tokens?, output_tokens? }, Responses surface only)
FoundryImportReportcompleteness (full | partial), unmapped_tools[] ({ type, name?, reason }), knowledge_refs[], suggested_slug, notes[], source ({ surface, id, name, kind })
FoundryPublishPlanapi_version: "v1", method: "POST", path: "/agents", body ({ name (≤ 63, alphanumeric ends, hyphens), description?, definition: { kind: "prompt", model, instructions, tools[] } }), omitted[]
FoundryPublishResultremote_id ({name}:{version}), name, version, created (true only when Foundry reports version 1), model?, omitted[]

Error codes

CodeStatusMeaning
FOUNDRY_AUTH401 / 403Entra refused the client credentials, or the project refused the token (missing Foundry User role).
FOUNDRY_UNREACHABLE502 · 504 · 409The project did not answer or answered 5xx (one bounded retry on idempotent GETs, never on POSTs); 409 when the connection is disabled.
FOUNDRY_SURFACE_UNSUPPORTED409Classic threads / runs on a new-agents-only project; link on an api_key connection; a project that accepts neither publish path.
FOUNDRY_AGENT_NOT_FOUND404No agent with that name on either surface.
FOUNDRY_INVOKE_FAILED502 / 504Thread, message, run or Responses call failed; 504 when a classic run did not reach a terminal state in time.
VALIDATION400Body validation, a project_endpoint that is not https on *.services.ai.azure.com, or runs without thread_id.
NOT_FOUND404Unknown connection id (including another tenant’s), or a publish target with no published version.
SLUG_TAKEN409Connection or external-agent slug already exists.

5. Governance evidence

WhatWhere
Every privileged actionaudit_log: foundry.connection.create / update / delete · foundry.inventory · foundry.invoke · foundry.import · foundry.link · foundry.publish — ids, status, surface, latency, tool counts; never instructions, message bodies or credentials
Consumptionusage_events kind external_agent_call with source: foundry_project, connection id, remote ids, latency and the token counts Foundry reported
Tenant isolationfoundry_connections is row-level-secured (enable + force, USING + WITH CHECK); a second tenant asking for the same id gets 404 for reads and for publish
Secrets and egressDecryption only inside the gateway per call; the project host is pinned to *.services.ai.azure.com with the strict SSRF policy and an empty insecure-host allowlist — stricter than the A2A path
Human approvalImports default hitl.default to review; a linked agent is gated by grants, approval policies and its trust floor like any external agent. Nothing goes live silently.

6. Not true yet — say it before they ask

  • Verified live once (29 Sep 2026) against one GA project: inventory, inspect, invoke, import-to-draft and publish. Not yet observed live: a classic (Assistants-shaped) project, a project that needs the project-scoped Responses fallback (the agent-endpoint route worked with no Foundry-Features header), and hosted / container agents.
  • Publish is prompt agents only. Hosted / container publish needs an ACR and agent-identity contract that does not exist. kb.search is omitted on publish until M3 binds a vector store.
  • No corpus, Toolbox or eval copy. file_search vector stores stay on Foundry and are referenced; Toolbox → MCP server rows, eval suites and workflow graphs are M3 roadmap — copying classified corpora is an explicit decision, never a side effect.
  • No streaming of Foundry tokens; /invoke returns the completed text.
  • No traceparent producer on platform runs yet — correlate by the remote ids in usage_events.
  • No console card; the public cloud host suffix only (sovereign-cloud suffixes are not allow-listed).

7. What a demo project costs on the Azure side

Microsoft list prices as of 29 Sep 2026. Agent Service charges no per-agent or per-run platform fee for prompt agents — you pay for tokens, metered tools and the optional standard-setup resources.

ChoiceWhy
One AIServices S0 account + one project, basic agent setupThe standard setup provisions ≈ $250/month of Cosmos DB + AI Search you do not need for a demo.
One tool-less prompt agent on gpt-5-nano Global Standard$0.05 in / $0.40 out per 1M tokens — a light demo is ≈ $0.06/month; gpt-4.1-mini ≈ $0.32. gpt-4.1-nano retires 14 Oct 2026. gpt-5-nano cannot use Function / MCP / OpenAPI tools in Agent Service — use gpt-5.4-nano (≈ $0.21/month) when the agent needs tools.
No Application Insights, no metered toolsBing grounding is $14 per 1 000 calls, Code Interpreter $0.03 per session, File Search $0.10 per GB-day — none is needed to show the four capabilities.