From a63e043d6c544373eb3b0f43f355aace9414d204 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Thu, 15 Oct 2026 09:39:01 -0300 Subject: [PATCH] docs: maintain v5.2.1 runtime and operator documentation Post-release documentation maintenance only. Preserve runtime, configuration, VERSION, changelog, tags, releases and policies. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 588 +++++------------------------------------- docs/runtime.md | 478 ++++++++++++++++++++++++++++++++++ evaluations/README.md | 34 +-- infra/README.md | 13 +- 4 files changed, 564 insertions(+), 549 deletions(-) create mode 100644 docs/runtime.md diff --git a/README.md b/README.md index 0a6fbcbe..994dee0b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - # Agent Landing Zone Orchestrator -Part of the [Agent Landing Zone](https://github.com/Azure/agent-landing-zone) solution. +The Python 3.12 service for conversational agent execution in +[Agent Landing Zone](https://github.com/Azure/agent-landing-zone). It selects a +configured strategy, streams answers, and integrates models, retrieval, and +tools. Classic execution uses FastAPI and Cosmos DB conversation storage. +The separate Microsoft Foundry hosted entrypoint is stateless. -The **Agent Landing Zone Orchestrator** service is an agentic orchestration layer built on Azure AI Foundry Agent Service and the Microsoft Agent Framework. It powers conversational agent experiences by coordinating specialized agents—each with a defined role—that combine grounded knowledge retrieval, NL2SQL, and MCP tools to produce accurate, context-aware responses. +## Documentation and release scope -### Available Strategies +- [Product documentation](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/) + covers architecture, deployment, and cross-component setup. +- [Canonical documentation source](https://github.com/Azure/AI-Landing-Zones/tree/main/docs/agent-landing-zone) + is on `Azure/AI-Landing-Zones` `main`, not a legacy documentation branch. +- [Service configuration, API, identity, and operations](docs/runtime.md) + describes this component's implementation and limitations. +- [Audit schemas and integrity pins](contracts/) define the shared event shapes. -| Key | Strategy | Description | -|-----|----------|-------------| -| `single_agent_rag` | Single Agent RAG | RAG strategy using Azure AI Foundry Agent Service v2 with dynamic routing and direct LLM bypass. | -| `maf_agent_service` | MAF + Agent Service | Microsoft Agent Framework with Azure AI Foundry Agent Service v2 for server-side threads and tool orchestration, with request-scoped client lifecycle for stable async cleanup. | -| `maf_lite` | MAF Lite | Microsoft Agent Framework with direct Azure OpenAI model access (no Agent Service dependency). | -| `mcp` | MCP | Request-scoped Microsoft Agent Framework strategy supporting legacy SSE and streamable HTTP MCP servers. | -| `nl2sql` | NL2SQL | Natural language to SQL translation strategy for structured data queries. | +Agent Landing Zone `4.3.0` pins orchestrator +[`v5.2.1`](https://github.com/Azure/agent-app-orchestrator/tree/v5.2.1) +at `27f952fcf8d48a2ba6267c237ea6bea8361fedd2`. These runtime notes describe +that revision. Do not treat unreleased `develop` behavior as shipped. -### MCP strategy configuration +## Run this service locally -The `mcp` strategy now runs on Microsoft Agent Framework instead of Semantic -Kernel. Existing SSE deployments remain compatible: keep `AGENT_STRATEGY=mcp` -and the existing MCP configuration keys. The default transport is still `sse`, -and the streamed response contract is unchanged. +First provision or obtain compatible infrastructure through Agent Landing Zone. +This repository does not provision a standalone resource set. Use a development +App Configuration store, appropriate Azure RBAC, and network access to its +dependencies. A successful server start is not an end-to-end readiness test. -Configure these values in Azure App Configuration. Use the -`agent-app-orchestrator` label for an orchestrator-specific override, or the -shared `agent-lz` label when every component should use the same value: - -| Key | Default | Purpose | -| --- | --- | --- | -| `MCP_APP_ENDPOINT` | `http://localhost:80` in Azure; local runs use `http://localhost:5000` | Base URL of the trusted MCP server. HTTPS should be used outside local development. | -| `MCP_SERVER_TRANSPORT` | `sse` | MCP transport. Supported values are `sse` and `streamable_http`. | -| `MCP_CLIENT_TIMEOUT` | `600` | Connection and read timeout in seconds. It must be an integer greater than zero. | -| `MCP_APP_APIKEY` | Unset | Optional API key sent to the MCP server as `X-API-KEY`. Store it as a Key Vault reference, not as plain text. | -| `AGENT_ID` | Unset | Optional agent identifier passed to the request-scoped Microsoft Agent Framework agent. | - -The orchestrator resolves the transport endpoint from `MCP_APP_ENDPOINT`: - -| Transport | Endpoint suffix | Example resolved endpoint | -| --- | --- | --- | -| `sse` | `/sse` | `https://mcp.contoso.com/sse` | -| `streamable_http` | `/mcp` | `https://mcp.contoso.com/mcp` | - -You can configure the base URL or include the matching suffix. The orchestrator -adds the suffix once, so both `https://mcp.contoso.com` and -`https://mcp.contoso.com/sse` resolve to the same SSE endpoint. A conflicting -suffix fails during strategy initialization. For example, -`MCP_SERVER_TRANSPORT=streamable_http` with an endpoint ending in `/sse` is -rejected and the error tells the operator to use `/mcp`. - -> [!IMPORTANT] -> The orchestrator forwards the caller's `user-context` header and, when -> configured, `X-API-KEY` to this endpoint for every request. Use only a trusted -> MCP server, require HTTPS outside local development, restrict network access, -> and keep `MCP_APP_APIKEY` in Key Vault. - -For a controlled rollout, leave existing deployments on `sse`, deploy the new -orchestrator revision, and verify that a representative request discovers and -calls the expected tools. To adopt streamable HTTP, confirm that the server -exposes `/mcp`, then change the transport and endpoint together. Monitor startup -and request logs for invalid transport, conflicting endpoint suffix, timeout, -or connection errors. Because the configuration names and SSE default are -unchanged, you can roll back to the previous orchestrator image without -rewriting the existing SSE configuration. - -### Foundry hosted-agent Toolbox identity passthrough - -When the orchestrator runs as a Microsoft Foundry hosted agent -(`api.hosted_entrypoint`, canonical `POST /responses`) with -`AGENT_STRATEGY=mcp`, document-security identity is established solely through -the Foundry hosted-agent protocol 2.0 call context — per -[Azure/agent-landing-zone ADR-0001](https://github.com/Azure/agent-landing-zone/blob/main/docs/adr/ADR-0001-hosted-agents.md), -Toolbox OAuth identity passthrough is the required native path and a manual -group-filter fallback is never the default. - -`POST /responses` is hosted by Microsoft's -`azure-ai-agentserver-responses` implementation of the Foundry Responses v2 -protocol. The orchestrator adapter accepts a plain string `input` or an -ordered array of text-only `role`/`content` message objects, streaming or -synchronous (`stream`) execution, `metadata`, and the platform-injected -`agent_reference`. Every other top-level request field — including the -`conversation`, `model`, and `session_id` routing selectors and any parameter -a future Foundry client adds — is logged and dropped before the SDK parses the -body, so a caller-supplied value can never select routing or state, nor -override the server-side agent definition. Unknown fields are ignored rather -than rejected: a strict allowlist turned every newly injected platform field -into a hosted-agent outage. Optional parameters serialized as explicit `null` -carry no caller intent and are likewise treated as absent. -This preserves the -ADR-0004 stateless contract while remaining compatible with the Foundry -Playground and `azd ai agent invoke`. -A non-null `previous_response_id` is rejected outright with HTTP 422 — silently -dropping it would answer from truncated history instead of failing loudly, so -the authenticated caller must instead send the complete, ordered turn history -as `input` on every request. `store` is -overridden to `False` unconditionally, regardless of what the caller sends or -omits, because the hosted container holds zero managed-Conversations -data-plane RBAC and must never depend on Foundry's managed persistence — see -`CHANGELOG.md`. `background: true` requires `store: true` in the underlying -SDK and is therefore also rejected with HTTP 422. Non-message-object array -items and multimodal (non-text) content are rejected rather than silently -losing role semantics or dropping content. Every rejection is logged with its -reason before the response is returned, so a refused request is diagnosable -from telemetry alone. The protocol host still exposes -response retrieval, input-item listing, cancellation, and deletion, but since -every response is created with `store: False` none of those routes ever -return a previously created response. -`POST /invocations` remains a separate compatibility protocol for the existing -`messages` request schema. Both protocols reuse the same hosted strategy -execution and identity guard after parsing their distinct request contracts. - -The runtime image pre-caches its tokenizer during the build so hosted requests -do not require public Blob Storage egress in network-isolated environments. - -The hosted Responses server disables generative-AI prompt and completion -capture in OpenTelemetry by default. Set -`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true` only when the -deployment's data-handling policy explicitly permits sensitive content -telemetry. - -The hosted entrypoint exposes `GET /readiness` for Microsoft Foundry readiness -probes. The compatibility `GET /health` route returns the same immutable image -version and hosted-eligible strategy set. - -The platform injects two headers on every hosted-agent request: - -| Header | Purpose | Forwarded to Toolbox? | -| --- | --- | --- | -| `x-agent-user-id` | Per-user container partition key. | No — container-side only. | -| `x-agent-foundry-call-id` | Opaque per-request call identifier. | Yes — the only supported identity passthrough correlation token. | - -Both `POST /responses` and `POST /invocations` capture and strictly validate -`x-agent-foundry-call-id` -(printable ASCII, no spaces or control characters, 256 characters max) -whenever the resolved strategy is Toolbox-backed (currently only `mcp`). A -missing or malformed value is rejected with HTTP 401 before the Responses -storage provider, strategy, or Toolbox client is reached; the same guard covers -response retrieval, input-item listing, cancellation, and deletion. There is no -fallback to service identity or to the legacy `metadata_security_id` manual -filter. The validated call id -then flows unchanged through the runtime-neutral `TurnRequest` into the -strategy and is echoed as the sole outbound identity header on every Toolbox -MCP HTTP call, so Toolbox can resolve the signed-in user and mint per-user -credentials server side. - -The container never reads, stores, or forwards the `Authorization` header -(the platform gateway strips it before the request reaches the container), -and it never trusts caller- or model-supplied identity fields or `x-client` -group claims for retrieval security decisions. Neither the call id nor any -authorization material is ever written to application logs. Non-Toolbox -hosted strategies (`maf_lite`, `maf_agent_service`, `single_agent_rag`) are -unaffected and continue to run without a call id. - -> [!IMPORTANT] -> Toolbox-side OAuth consent and RBAC grants for the signed-in user are an -> external, platform-managed prerequisite. This orchestrator-side change -> propagates the correlation identity Toolbox needs to resolve per-user -> credentials; it does not itself grant, request, or manage that consent — -> the Toolbox/ingestion service and the Foundry hosted-agent platform must be -> configured and consented for the target user independently. - -### Audit event configuration - -The orchestrator can emit a versioned, metadata-only-by-default activity trail through its -existing OpenTelemetry and Application Insights connection. Audit events are -disabled by default and use the `gptrag.audit` logger namespace. If regular log -export is disabled with `AZURE_MONITOR_DISABLE_LOGGING=true`, enabling audit -events exports only that namespace. - -When `AUDIT_EVENTS_ENABLED=false`, no `agentlz.audit.*` events are emitted, no -audit-only log exporter is enabled, no HMAC key is required, and other -`AUDIT_*` settings are ignored. Existing response bodies, SSE streams, -retrieval results, cache behavior, application logs, traces, and metrics remain -unchanged. The additive server-generated `X-Correlation-ID` response header and -documented Cosmos correlation metadata are independent of audit emission. - -Configure these values in Azure App Configuration. Store `AUDIT_HMAC_KEY` as a -Key Vault reference and keep it out of the admin dashboard: - -| Key | Default | Purpose | -| --- | --- | --- | -| `AUDIT_EVENTS_ENABLED` | `false` | Enables audit-event-v2 custom events (`agentlz.audit.`, `service_name` `agent-app-orchestrator`). Enabling without a valid 256-bit HMAC key fails startup. | -| `AUDIT_HMAC_KEY` | Unset | Base64, Base64URL, or hexadecimal encoding of exactly 32 random bytes. Used only to pseudonymize source, conversation, question, thread, tool, and optional actor identifiers. | -| `AUDIT_HMAC_KEY_ID` | `v1` | Non-secret key version recorded with events to support rotation. Change it together with the key. | -| `AUDIT_SENSITIVE_CONTENT_ENABLED` | `false` | Allows approved sensitive fields to be considered for capture. The allowlist must also be non-empty. | -| `AUDIT_SENSITIVE_CONTENT_FIELDS` | Empty | Comma-separated allowlist from `prompt`, `response`, `source_excerpt`, `tool_arguments`, and `tool_result`. Empty captures none. | -| `AUDIT_ACTOR_PSEUDONYM_ENABLED` | `false` | Adds a keyed pseudonym for the authenticated actor. Raw identity is never placed in audit events, trace context, or baggage. | -| `AUDIT_SOURCE_EVENT_LIMIT` | `25` | Per-request source event limit. Accepted range is 1 through 25. | -| `AUDIT_ADDITIONAL_REDACTED_KEYS` | Empty | Additional comma-separated nested key names that must always be redacted. | - -Total and tool budgets are fixed safety limits rather than configuration: - -| Budget | Limit | -| --- | --- | -| Audit events per request | 64, including one reserved request terminal and at most one `audit.emission.failed` | -| Tool invocations per request | 16 complete started/terminal pairs | -| Grounding-source events per request | 25 maximum, optionally lowered by `AUDIT_SOURCE_EVENT_LIMIT` | - -Tool pairs are reserved atomically, so a started event is not emitted unless -capacity also exists for its terminal event. Detail events are suppressed when -a budget is exhausted, while the request terminal remains reserved. Request -terminal events report `audit_events_omitted`, `source_events_omitted`, and -`tool_invocations_omitted`. - -Generate a key locally, write it to Key Vault without displaying it, and clear -the temporary shell variable: +From this repository's root, with Python 3.12 and Azure CLI installed, run: ```powershell -$auditKey = python -c "import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())" -az keyvault secret set --vault-name --name audit-hmac-key --value $auditKey --query id --output tsv -$auditKey = $null -``` - -Once the endpoint handler runs, successful orchestrator and feedback responses -return a new server-generated `X-Correlation-ID`; an inbound value is ignored. -Dependency failures and request-body validation failures that occur before the -handler may not include this header. The identifier is recorded in audit events -only when auditing is enabled. W3C `traceparent` remains the distributed tracing -context and is preserved independently. `X-Correlation-ID` is not an -authentication, authorization, idempotency, proof-of-delivery, or immutability -mechanism. - -> [!IMPORTANT] -> Sensitive-content capture can include prompts, responses, retrieved excerpts, -> tool arguments, or tool results. It increases privacy, retention, and access -> control obligations. Tokens, credentials, authorization material, cookies, -> connection strings, SAS values, and private keys are filtered before export -> even when sensitive capture is enabled. This filtering is defense in depth, -> not a data-loss-prevention boundary. Operators must still prevent secrets from -> entering content approved for capture. - -To roll back, set `AUDIT_EVENTS_ENABLED=false` and restart the orchestrator. -This stops new audit events and does not delete telemetry already exported. -These best-effort operational events can support customer-owned governance, -security, and incident processes, but they are not an immutable ledger and do -not establish legal or regulatory compliance. - -Audit settings are loaded at startup. For HMAC rotation, create a new 256-bit -key, update `AUDIT_HMAC_KEY` and `AUDIT_HMAC_KEY_ID` together, and restart the -orchestrator. Pseudonyms generated before and after rotation cannot be directly -correlated. - -Audit serialization is bounded before export: - -| Limit | Value | -| --- | --- | -| Serialized logical event | 16 KiB UTF-8 | -| Producer attributes | 60 | -| Metadata strings and nested keys | 512 characters | -| Allowlisted sensitive fields | 2,048 characters | -| Nested depth | 6 | -| Mapping entries inspected | 65, including one truncation lookahead | -| Sequence items inspected | 33, including one truncation lookahead | -| Total nested values inspected | 256 | -| `omitted_fields` / `truncated_fields` entries | 32 | - -Optional attributes are removed as needed to satisfy the event-size limit. -`truncated_fields` identifies shortened strings or collections, while -`omitted_fields` identifies unsupported, cyclic, over-depth, unknown, or -size-dropped values. The wire schema permits 64 properties because Azure -Monitor adds transport attributes around the producer's 60-attribute payload. -If a bounded logical event still cannot be serialized safely, the original -event is discarded and one payload-free, constant-safe -`audit.emission.failed` event is attempted for the request. - -The reusable v2 JSON Schema for orchestrator and ingestion producers is -[`contracts/audit-event-v2.schema.json`](contracts/audit-event-v2.schema.json). -Version 2 renames the Application Insights event prefix from `gptrag.audit.` -to `agentlz.audit.` and the producer `service_name` to -`agent-app-orchestrator`; field names and semantics are unchanged. The -`audit-event-v1` files remain in [`contracts/`](contracts/) as hash-pinned -history for reading events exported before this change. -The ingestion component reserves exactly these event names, with no aliases: - -| Scope | Events | -| --- | --- | -| Run lifecycle | `ingestion.run.started`, `ingestion.run.completed`, `ingestion.run.failed`, `ingestion.run.cancelled` | -| Document lifecycle | `ingestion.document.indexed`, `ingestion.document.rejected`, `ingestion.document.deleted` | - -They are part of both the logical and Application Insights wire schemas but are -not emitted by this repository. - -Foundry IQ exposes MCP activity only after completion and provides no -pre-invocation callback. For `foundry_iq.mcp_tool`, the producer accepts only -finite, nonnegative elapsed durations of at most 24 hours and reconstructs the -start as the observation time minus that duration. The started event uses the -reconstructed `event_time_utc`; both events set -`timing_source=reconstructed`. These timestamps are approximate and do not -prove when the remote tool began execution. Invalid timing omits the activity -pair and attempts the bounded constant-safe emission-failure event without -failing retrieval. - -Application Insights stores custom-event property values as strings. The -corresponding exported shape is documented and tested in -[`contracts/audit-event-v2.application-insights.schema.json`](contracts/audit-event-v2.application-insights.schema.json). -Consumers should parse those properties into the logical v2 types before -validating them against the reusable contract. Logical root events use -`parent_event_id=null`. Because the pinned Azure Monitor exporter drops null -custom properties, the wire adapter encodes logical null as -`evt_00000000000000000000000000000000`; consumers must decode that reserved -sentinel to null and must never join it as an event. Contract artifact SHA-256 -digests are recorded in -[`contracts/audit-event-v2.sha256`](contracts/audit-event-v2.sha256). - -Query orchestrator audit events in Application Insights: - -```kusto -customEvents -| where name startswith "agentlz.audit." -| where tostring(customDimensions.service_name) == "agent-app-orchestrator" -| project timestamp, name, tostring(customDimensions.event_id), - tostring(customDimensions.correlation_id), - tostring(customDimensions.parent_event_id), - tostring(customDimensions.status), tostring(customDimensions.reason_code) -| order by timestamp asc -``` - -During the transition, include history exported under v1 with -`name startswith "agentlz.audit." or name startswith "gptrag.audit."`. - -### NL2SQL datasource security - -> [!IMPORTANT] -> When using the `nl2sql` strategy, configure every SQL Server, Azure SQL, or Fabric SQL datasource with a least-privilege read-only principal. Grant only the `SELECT` permissions needed for approved schemas, tables, or views, and do not use admin, owner, contributor, ingestion, or write-capable identities for NL2SQL query execution. The orchestrator validates generated SQL before execution, but database permissions remain the primary security boundary. - -### Retrieval backends - -The `rag` and `multimodal_rag` strategies use Foundry IQ's Knowledge Base retrieve API. Alongside the existing native `azureBlob` and `searchIndex` (Pattern B) knowledge sources, the orchestrator can optionally query a Microsoft 365 **Work IQ** knowledge source for grounded answers over the caller's Outlook mail, Teams chats, and SharePoint / OneDrive files. - -Work IQ is opt-in and off by default: - -- Set `WORK_IQ_ENABLED=true` and `WORK_IQ_KNOWLEDGE_SOURCE_NAME=` to enable it. -- Work IQ requires a per-user on-behalf-of token. When the OBO token is missing the Work IQ source is skipped with a warning; managed-identity fallback is never used for remote knowledge source kinds. -- ACL is enforced natively by Microsoft 365 via the forwarded user token — no `filterAddOn` is emitted for Work IQ. -- Remote kinds can take 40–60 seconds end-to-end; set `FOUNDRY_IQ_MAX_RUNTIME_SECONDS` (default `120`) to control the retrieve runtime ceiling. The value is only emitted when a remote kind is enabled, so Pattern A / Pattern B requests stay byte-identical. - -Work IQ is currently a gated preview and requires admin consent plus a Work IQ knowledge source provisioned on the same Azure AI Search service. See the enablement guide in the [Azure/agent-landing-zone](https://github.com/Azure/agent-landing-zone) repo for the end-to-end setup ([issue #543](https://github.com/Azure/agent-landing-zone/issues/543)). - -#### Generic MCP Server knowledge sources (Preview) - -Foundry IQ can optionally call one or more preprovisioned generic MCP Server -knowledge sources. The feature is disabled by default. When disabled, the -orchestrator keeps the existing minimal-reasoning `intents` request and does not -add MCP runtime, activity, reasoning, or credential headers. - -Enable it with these non-secret App Configuration settings: - -```dotenv -RETRIEVAL_BACKEND=foundry_iq -FOUNDRY_IQ_MCP_ENABLED=true -FOUNDRY_IQ_MCP_REASONING_EFFORT=low -FOUNDRY_IQ_MCP_TRUSTED_HOSTS=mcp.contoso.com -FOUNDRY_IQ_MCP_LOG_TOOL_ARGUMENTS=false -FOUNDRY_IQ_MCP_SOURCES_JSON=[{"name":"monitor-mcp","description":"Read-only Azure Monitor MCP source.","serverURL":"https://mcp.contoso.com/mcp","failOnError":true,"maxOutputDocuments":5,"tools":[{"name":"query_logs","outputParsing":{"kind":"json","jsonParameters":{"documentsPath":"$.results[*]","includeContext":true}},"inclusionMode":"reranked","maxOutputTokens":2048}],"queryHeaders":[{"name":"Authorization","valueFrom":{"kind":"managedIdentity","scope":"api://monitor-mcp/.default"}}]}] -``` - -`serverURL`, tools, and output parsing are registration metadata. Retrieve -requests send only each registered knowledge source name. `queryHeaders` is -non-secret runtime credential metadata and is never rendered into the -top-level Search knowledge-source registration. Its values are resolved per -request and forwarded with Azure AI Search's paired -`-header-name[N]` and -`-header-value[N]` control headers. Supported `valueFrom.kind` -values are: - -- `managedIdentity`, with an explicit downstream `scope` -- `obo`, with an explicit downstream `scope` and an authenticated user request -- `keyVaultSecret`, with a Key Vault `secretName` -- `none`, which forwards no credential - -Literal header values and secrets are rejected. MCP hosts require an exact -trusted-host match and HTTPS; query strings, localhost, IP literals, userinfo, -fragments, and reserved hosts are rejected. The Search service `Authorization` -header and document-security `x-ms-query-source-authorization` header remain -separate from MCP credentials. - -Enabling MCP switches retrieval to one user `messages` entry with low or medium -reasoning, `extractiveData`, activity diagnostics, and the existing bounded -Foundry IQ runtime/document limits. Required-source activity failures and -credential errors fail closed. Optional-source failures can return successful -references as a partial result and are logged without raw results, credentials, -query strings, or tool arguments by default. - -This is a preview contract on API `2026-05-01-preview`. MCP-generated tool -arguments are not guaranteed to be semantically safe. Use read-only tools, -least-privilege scopes, bounded time ranges and row counts, and server-side -argument validation and auditing. - -Tool arguments are omitted from normal telemetry. When -`FOUNDRY_IQ_MCP_LOG_TOOL_ARGUMENTS=true`, debug telemetry writes only a -bounded, recursively redacted representation. Credential, authorization, -token, cookie, and header values are always redacted, including paired control -header values. Keep this setting disabled in production unless the remaining -argument data is approved for diagnostic logging. - -The source JSON schema is: - -| Field | Requirement | -| --- | --- | -| `name` | Required, unique registered knowledge source name. | -| `serverURL` | Required HTTPS registration metadata. Its exact host must appear in `FOUNDRY_IQ_MCP_TRUSTED_HOSTS`; it is never sent in retrieve bodies. | -| `tools` | Required non-empty list with unique tool `name` values. Each tool requires `outputParsing`, `inclusionMode`, and `maxOutputTokens`. `outputParsing` is discriminated by `kind`: `auto` and `none` accept no parameter object; `json` requires `jsonParameters` with `documentsPath` and optional `includeContext`; `split` optionally accepts `splitParameters` with only `textSplitMode` (`pages` or `sentences`), positive `maximumPageLength`, non-negative `pageOverlapLength`, positive `maximumPagesToTake`, and non-empty `defaultLanguageCode`. Parameter objects cannot be mixed across kinds. `inclusionMode` only accepts `reranked` or `always`; `maxOutputTokens` must be 1–8192. | -| `failOnError` | Optional, defaults to `true`. A selected source failure fails the request. Set `false` only when partial answers without that source are acceptable. | -| `maxOutputDocuments` | Optional per-source limit from 1–50. The top-level `FOUNDRY_IQ_MAX_OUTPUT_DOCUMENTS` still limits final grounding documents. | -| `queryHeaders` | Optional ordered runtime-only metadata. Each entry has a safe `name` and non-secret `valueFrom` metadata; it is not rendered into Search registration. The first resolved header uses the unnumbered pair; later headers use deterministic numeric suffixes. | - -For `managedIdentity` and `obo`, `valueFrom.scope` is required. -`keyVaultSecret` requires `valueFrom.secretName`; `none` accepts neither field -and sends no header. MCP retrieval also validates -`FOUNDRY_IQ_MAX_RUNTIME_SECONDS` in the 30–600 range. Invalid enabled -configuration stops retrieval instead of skipping a source. - -Track cross-repository provisioning and canonical documentation work in -[Azure/agent-landing-zone#567](https://github.com/Azure/agent-landing-zone/issues/567). - -## Documentation - -For comprehensive information about the Agent Landing Zone, including architecture details, configuration guides, best practices, troubleshooting resources, deployment guidance, customization options, and advanced usage scenarios, please refer to the [official project documentation](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/). - -## Dashboard - -The orchestrator ships with an optional admin dashboard mounted at `/dashboard`. It exposes three tabs: - -- **Overview**: conversation counts for today, the last 7 days, and the last 30 days; a conversations-over-time chart; average user turns per conversation; and the number of active users. -- **Conversations**: a paginated, newest-first list of conversations across all users, with a detail view that renders the full message history. -- **Configuration**: an allowlisted editor for supported orchestrator settings in Azure App Configuration. - -The data comes from the existing conversation/history Cosmos DB container used by the orchestrator (`CONVERSATIONS_DATABASE_CONTAINER` in `DATABASE_NAME`). The dashboard is read-only. - -**Enabling the dashboard.** It is disabled by default. Set the App Configuration value `ENABLE_DASHBOARD=true` to mount it. When `ENABLE_DASHBOARD=false` (the default), the `/dashboard` HTML page and every `/api/dashboard/*` route are not registered at all. - -**Access control.** When authentication is on (`OAUTH_AZURE_AD_TENANT_ID` is configured), the entire `/api/dashboard/*` surface — except the small `/api/dashboard/version` and `/api/dashboard/auth-config` endpoints used by the SPA at bootstrap — requires the caller's bearer token to include the `Admin` app role. The `/dashboard` HTML page itself is served openly so the SPA can load, call `/api/dashboard/auth-config`, and either sign the user in via MSAL (Authorization Code + PKCE) or render an access-denied state on a 403 response. When authentication is off, the dashboard is open like the rest of the app in development. - -**Sign-in configuration.** The SPA reads its runtime auth configuration from `GET /api/dashboard/auth-config`, which is derived from these App Configuration keys under the `agent-app-orchestrator` label: - -| Key | Required | Purpose | -| --- | --- | --- | -| `OAUTH_AZURE_AD_TENANT_ID` | Yes, to enable the gate | Entra tenant id. When unset, the SPA renders without MSAL and `require_admin` is a no-op. | -| `OAUTH_AZURE_AD_CLIENT_ID` | Yes when tenant is set | Application (client) id of the orchestrator API app registration. | -| `OAUTH_AZURE_AD_API_SCOPE` | Optional | Override for the scope the SPA requests. Defaults to `api:///access_as_user`. | - -Use a single Microsoft Entra app registration for both the dashboard SPA and the orchestrator API. The same application (client) id is the MSAL `clientId`, the backend token audience, and the `` segment in the default scope `api:///access_as_user`. Using separate SPA and API app registrations requires a different configuration model. - -The App Registration also needs a **Single-page application** redirect URI pointing at `https:///dashboard/` (trailing slash), an exposed `access_as_user` scope, and an `Admin` app role assigned to every user who should see the dashboard. See the Agent Landing Zone docs: [Operations](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/operations/). - -Define the role under **Microsoft Entra ID > App registrations > > App roles**. Create exactly one app role with value `Admin` and allowed member type `Users/Groups`. The backend checks the token's `roles` claim for the exact case-sensitive value `Admin`; other role names or casing are rejected. Assign users under **Enterprise applications > > Users and groups** by selecting the `Admin` role. - -Keep the Enterprise Application setting **Assignment required?** set to **No** when validating the app-level 403 path. With **No**, a signed-in user without the `Admin` app role reaches the dashboard API and receives the dashboard's access-denied state. With **Yes**, Entra blocks the sign-in earlier with `AADSTS50105`, which is useful for strict production gating but does not validate the dashboard's own 403 handling. - -After adding or removing the `Admin` role assignment, sign out completely and sign in again. Existing access tokens do not gain or lose `roles` claims retroactively. - -Setting only `OAUTH_AZURE_AD_TENANT_ID` is rejected as a server misconfiguration. The dashboard never skips audience validation or silently falls back to unauthenticated mode when auth is partially configured. - -**Token scope.** The frontend must request an access token with the orchestrator's own API scope (`api:///access_as_user` by default), not a Microsoft Graph scope. App roles are issued in the `roles` claim of an access token only when the token is requested for the application that defines those roles, so a Graph-scoped token will not surface the `Admin` role and every dashboard call will return 403. - -**Building the dashboard bundle.** Production builds happen automatically as part of the `Dockerfile` (an MCR base image stage using Node.js 20 runs `npm run build` and copies the static assets into `src/static`). For local development you can run the Vite dev server with hot reload: - -```bash -cd frontend -npm install -npm run dev -``` - -The dev server proxies `/api` to `http://localhost:9000`, which is where the orchestrator listens locally. - -## Prerequisites - -Before deploying the application, you must provision the infrastructure as described in the [Agent Landing Zone](https://github.com/Azure/agent-landing-zone) repo. This includes creating all necessary Azure resources required to support the application runtime. - -
-Click to view software prerequisites -
-The machine used to customize and or deploy the service should have: - -* Azure CLI: [Install Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) -* Azure Developer CLI (optional, if using azd): [Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) -* Git: [Download Git](https://git-scm.com/downloads) -* Python 3.12: [Download Python 3.12](https://www.python.org/downloads/release/python-3120/) -* Docker CLI: [Install Docker](https://docs.docker.com/get-docker/) -* VS Code (recommended): [Download VS Code](https://code.visualstudio.com/download) -
- -## How to deploy the orchestrator service - -Make sure you're logged in to Azure before anything else: - -```bash +python -m venv .venv +.\.venv\Scripts\python -m pip install -r requirements.txt az login +$env:APP_CONFIG_ENDPOINT = "https://.azconfig.io" +.\.venv\Scripts\python -m uvicorn main:app --app-dir src --host 127.0.0.1 --port 9000 ``` -### Deploying the app with azd (recommended) - -Initialize the template: -```shell -azd init -t Azure/agent-app-orchestrator -``` -> [!IMPORTANT] -> Use the **same environment name** with `azd init` as in the infrastructure deployment to keep components consistent. - -Update env variables then deploy: -```shell -azd env refresh -azd deploy -``` -> [!IMPORTANT] -> Run `azd env refresh` with the **same subscription** and **resource group** used in the infrastructure deployment. - -Aqui está uma versão mais clara, direta e consistente da instrução: - -### Deploying the app with a shell script - -To deploy using a script, first clone the repository, set the App Configuration endpoint, and then run the deployment script. - -##### PowerShell (Windows) - -```powershell -git clone https://github.com/Azure/agent-app-orchestrator.git -$env:APP_CONFIG_ENDPOINT = "https://.azconfig.io" -cd agent-app-orchestrator -.\scripts\deploy.ps1 -``` - -## Found an Issue? - -Encountered an error or bug? Help us improve the quality of this accelerator by reporting issues or suggesting enhancements on our **[GitHub Issues page](https://github.com/Azure/agent-landing-zone/issues)**. Your feedback helps make Agent Landing Zone better for everyone! - -## Previous Releases - -> [!NOTE] -> For earlier versions, use the corresponding release in the GitHub repository (e.g., v1.0.0 for the initial version). - -## 🤝 Contributing - -We appreciate contributions! See [CONTRIBUTING](https://github.com/Azure/agent-landing-zone/blob/main/CONTRIBUTING.md) for guidelines on submitting pull requests. - -## Trademarks - -This project may contain trademarks or logos. Authorized use of Microsoft trademarks or logos must follow [Microsoft’s Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Modified versions must not imply sponsorship or cause confusion. Third-party trademarks are subject to their own policies. +Use actual configured resource and model values, not the placeholders in +[`.env.sample`](.env.sample). The service does not automatically load that file. +The [runtime guide](docs/runtime.md#configuration-and-identity) explains required +settings and the explicit environment-variable opt-in. SQL strategies also +need the ODBC dependencies installed by the [Dockerfile](Dockerfile). + +Classic requests use `POST /orchestrator`. User bearer authentication and +service authentication are separate requirements. See the +[API and security boundaries](docs/runtime.md#classic-api-and-conversation-state) +before calling it. The image's default command serves classic mode on port +`8080`. Hosted execution requires a different command and the Foundry platform +prerequisites described in the [hosted section](docs/runtime.md#hosted-execution). + +## Retrieval and optional ingestion + +`RETRIEVAL_BACKEND` defaults to `ai_search` in this service. Platform deployment +defaults can supply a different value. Foundry IQ uses separately configured +knowledge sources. Deploying the optional ingestion Container App does not +change this backend or select sources. + +Fresh supported native Foundry IQ deployments default the ingestion Container +App deployment control to `false`, independently of Container Apps or hosted +orchestration. Existing and custom selections are preserved. Native Blob +ingestion is not a replacement for conversation attachments or every source +type. See [retrieval boundaries](docs/runtime.md#retrieval-and-ingestion-boundaries) +before choosing a topology. + +## Contributing + +Use the repository's [engineering contract](AGENTS.md) and +[contribution guidelines](https://github.com/Azure/agent-landing-zone/blob/main/CONTRIBUTING.md). +The [deprecated evaluations folder](evaluations/README.md) links to external +evaluation guidance. No evaluation gate is enabled merely by following that link. diff --git a/docs/runtime.md b/docs/runtime.md new file mode 100644 index 00000000..e5ca1ebd --- /dev/null +++ b/docs/runtime.md @@ -0,0 +1,478 @@ +# Orchestrator runtime guide + +Return to the [service README](../README.md). Product deployment and shared +configuration are owned by the +[central documentation](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/) +and its [main-branch source](https://github.com/Azure/AI-Landing-Zones/tree/main/docs/agent-landing-zone). +This guide describes orchestrator `v5.2.1`, the Agent Landing Zone `4.3.0` pin. + +Operator setup: [dashboard Entra registration](#dashboard-entra-setup), +[classic MCP](#classic-mcp-strategy), +[Work IQ for Microsoft 365](#work-iq-microsoft-365), and +[generic MCP knowledge sources](#generic-mcp-knowledge-sources-preview). + +## Configuration and identity + +Set `APP_CONFIG_ENDPOINT` in the process environment. Runtime values are loaded +from Azure App Configuration and Key Vault references. The credential chain +tries managed identity, then Azure CLI. `AZURE_CLIENT_ID` selects a +user-assigned identity when present. Local `az login` still needs data-plane +RBAC and connectivity to the configured services. + +The [configuration client](../src/connectors/appconfig.py) supplies these +selectors in order: + +1. `orchestrator` +2. `agent-app-orchestrator` +3. `gpt-rag-orchestrator` +4. `agent-lz` +5. No label, represented by the SDK selector `label_filter="\0"` + +The provider merges later selections over earlier duplicates. The no-label +selection is not a fallback used only when `agent-lz` is absent. +A service-label +value is therefore not necessarily the effective value if `agent-lz` or an +unlabelled duplicate exists. Dashboard writes use `agent-app-orchestrator`. +Read back the effective value after a write. Key lookup uses the exact key +name, without automatic `GPT_RAG_` or `AGENTLZ_` aliases. + +Direct environment reads for runtime settings are disabled by default. +`allow_environment_variables=true` enables them ahead of provider values. +Bootstrap variables such as `APP_CONFIG_ENDPOINT` and `AZURE_CLIENT_ID` are +read directly regardless of that setting. The classic entrypoint loads a +local `.env` through `python-dotenv`, but does not load `.env.sample`. +The hosted entrypoint expects process environment configuration. +Remote authentication failure +causes classic startup to exit. Other remote-load failures disable the +provider, but required missing values can still fail consumers. This is not +an offline mode for the complete service. + +Configure at least the endpoints and model required by the shared strategy +base: `AI_FOUNDRY_PROJECT_ENDPOINT`, `AI_FOUNDRY_ACCOUNT_ENDPOINT`, and +`CHAT_DEPLOYMENT_NAME`. Classic conversation storage also needs +`DATABASE_ACCOUNT_NAME` and `DATABASE_NAME`. +`CONVERSATIONS_DATABASE_CONTAINER` defaults to `conversations`. +Retrieval, MCP, SQL, telemetry, and prompt storage need their own settings +and permissions when used. The sample environment file is not a complete +deployment contract. + +Prefer managed identity and least-privilege RBAC for Azure service access. +Store secrets as Key Vault references rather than plaintext configuration. +Keyless Azure SDK access does not remove inbound API authentication or +downstream user authorization requirements. + +### TLS compatibility + +`APP_CONFIG_TLS_COMPATIBILITY` is a process-environment opt-in, default `false`. +Only the strings `true` and `false` are accepted, ignoring case and surrounding +spaces. When enabled, `KEY_VAULT_URI` is also required in the environment. +Both endpoints must be plain HTTPS origins without credentials, query, +fragment, or a non-root path. + +The compatibility transport applies to the App Configuration provider and +Key Vault reference resolution for that configured vault. It uses a TLS 1.2 +minimum, the `prime256v1` curve, and TCP maximum segment size `1200`. +Certificate and hostname verification remain enabled. It does not force TLS +1.2 as a maximum, disable certificate checks, replace credentials, or change +every outbound client. In particular, this is not a general transport setting +for Search, models, MCP, identity token endpoints, configuration writes, or +unrelated vault clients. Custom CA handling is preserved by the adapter. +Platform and network compatibility still require validation in the target +environment. See the [offline transport tests](../tests/test_appconfig_transport.py). + +## Strategies + +`AGENT_STRATEGY` defaults to `maf_lite` in the execution paths, although +`.env.sample` illustrates `single_agent_rag`. The +[factory](../src/strategies/agent_strategy_factory.py) registers: + +| Key | Execution | +| --- | --- | +| `maf_lite` | Microsoft Agent Framework with direct Azure OpenAI model access. The shared base still reads Foundry project/account settings. | +| `maf_agent_service` | Microsoft Agent Framework with Foundry Agent Service. | +| `single_agent_rag` | Foundry Agent Service v2 with intent routing and direct-model paths. | +| `mcp` | Microsoft Agent Framework with request-scoped MCP tools. | +| `multimodal` | Retrieval and model flow with image-aware grounding on the classic Search path. | +| `nl2sql` | Natural-language queries over configured SQL or Fabric datasources. | + +`multiagent` exists in the enum but is not registered. `rag` and +`multimodal_rag` are not strategy keys. Hosted eligibility is narrower than +the classic registry. + +## Classic API and conversation state + +Run `main:app` with `--app-dir src`. `POST /orchestrator` accepts +[`OrchestratorRequest`](../src/schemas.py). A normal request supplies `ask` +and can supply a `conversation_id`. Answers stream over SSE. The same route +handles `type=feedback` payloads rather than starting a new answer. + +The [service-auth dependency](../src/dependencies.py) accepts a validated +Entra token in `X-Service-Authorization`, or the existing Dapr/API-key +credentials. Keyless service auth needs `ORCHESTRATOR_AUTH_AUDIENCE` and +`ORCHESTRATOR_ALLOWED_CALLER_IDS` plus tenant configuration. The user +`Authorization` bearer is a separate boundary. `ALLOW_ANONYMOUS` defaults +to `true` for user context, not for bypassing service authentication. +Use `DISABLE_AUTH=true` only for controlled development. + +Conversation list, detail, rename, and soft-delete routes are +`GET /conversations`, `GET /conversations/{conversation_id}`, +`PATCH /conversations/{conversation_id}`, and +`DELETE /conversations/{conversation_id}`. They require authenticated +ownership checks. Classic orchestration stores message state and feedback in +Cosmos DB. Some conversation writes run in background tasks, so an emitted +answer is not a durable-write acknowledgement. Automatic profile access is +suspended by the shared strategy base. Do not equate conversation history +with enabled cross-conversation profile personalization. + +`ENABLE_DASHBOARD=false` by default. Enabling it mounts dashboard API routes +and serves `/dashboard` when built static assets are available. The dashboard +reads conversation data and can **write allowlisted configuration**. It is +not wholly read-only. With Entra configured, protected dashboard APIs require +the exact `Admin` app role. Version and auth-bootstrap endpoints are exceptions. +Without a tenant, the development dashboard gate is open. A configured tenant +without a client ID is a server misconfiguration, not a safe anonymous fallback. +See [dashboard configuration](../src/api/dashboard_config.py). + +### Dashboard Entra setup + +The dashboard SPA and API use **one Entra app registration**. Its client ID +is both the MSAL client ID and the backend token audience. Separate SPA and +API registrations are not supported by this configuration model. + +1. In **Microsoft Entra ID > App registrations**, select the orchestrator app. + Under **Authentication**, add the **Single-page application** platform + with redirect URI `https:///dashboard/`, including the + trailing slash. Add the corresponding local-origin URI only for local + development. The SPA uses its current origin plus `/dashboard/` for + sign-in and post-logout redirects, with Authorization Code and PKCE. +2. Under **Expose an API**, use application ID URI `api://` and + expose the delegated scope `access_as_user`. Grant the required consent + for that scope. If using another scope on this same API, configure its + complete value through `OAUTH_AZURE_AD_API_SCOPE`. +3. Under **App roles**, define an enabled role with the exact value `Admin` + and allowed member type **Users/Groups**. Under **Enterprise applications > + the orchestrator app > Users and groups**, assign that role to approved + dashboard operators. Other roles or different casing do not satisfy the gate. +4. Set the effective App Configuration values below, restart the backend, + and reload `/dashboard/`. Follow the label precedence described above. + Sign out and sign in again after role-assignment changes so a new token + reflects the assignment. + +| Setting | Default | Required setup | +| --- | --- | --- | +| `ENABLE_DASHBOARD` | `false` | Set `true` to mount the dashboard APIs and available static bundle. | +| `OAUTH_AZURE_AD_TENANT_ID` | Unset | Set the Entra tenant ID to enable dashboard authentication. Leaving it unset opens the development gate. | +| `OAUTH_AZURE_AD_CLIENT_ID` | Unset | Set the app registration's application client ID. Required when the tenant is set. | +| `OAUTH_AZURE_AD_API_SCOPE` | `api:///access_as_user` | Optional complete API scope override. It changes the requested scope, not the backend's expected audience. | + +`GET /api/dashboard/auth-config` supplies the browser's tenant, client, +authority, and scope at runtime. Request the orchestrator API's access token, +not a Microsoft Graph token. Missing or invalid bearer tokens return `401`. +A valid token without `Admin` in its `roles` array returns `403`. +Tenant configuration without a client ID returns `500`. + +The SPA needs no client secret. `OAUTH_AZURE_AD_CLIENT_SECRET` is used by +backend OBO flows when those features are needed, not by dashboard sign-in. +Keep it server-side as a Key Vault reference. + +For a controlled negative-role test, **Enterprise application > Properties > +Assignment required? = No** lets a signed-in non-Admin reach the API's `403` +gate. With **Yes**, Entra can reject an unassigned user earlier with +`AADSTS50105`. Choose the production assignment policy deliberately rather +than weakening it just to test the API. + +## Hosted execution + +Run `api.hosted_entrypoint:app` with `--app-dir src`, for example: + +```powershell +.\.venv\Scripts\python -m uvicorn api.hosted_entrypoint:app --app-dir src --host 127.0.0.1 --port 8001 +``` + +This is an entrypoint selection, not a standalone Foundry deployment recipe. +The platform owns hosted registration, ingress, identity passthrough, consent, +and RBAC. `POST /responses` is the canonical protocol. +`POST /invocations` is a compatibility protocol with a different schema. +`GET /readiness` and `GET /health` report the image version and eligible +strategies, not a live dependency certification. + +Only `maf_lite`, `maf_agent_service`, `single_agent_rag`, and `mcp` are +eligible. The hosted path does not use classic Cosmos state or perform managed +Foundry Conversations data-plane operations. It requires file-backed prompts +and disables profile memory. The authenticated caller must provide complete, +ordered history on every turn. Responses `input` supports a plain string or +text-only message objects. Multimodal content is rejected. +`store` is forced to `false`. Non-null `previous_response_id` and +`background: true` are rejected with `422`. Caller routing fields such as +`conversation`, `model`, and `session_id` do not select state or the server's +agent definition. + +Hosted `mcp` requires a valid platform `x-agent-foundry-call-id`, including +for the protocol's response-management routes. Missing or malformed context +is rejected with `401`. The validated opaque call ID is passed to Toolbox, +which resolves per-user identity. The orchestrator does not convert it into +a user token or grant consent. There is no manual group-filter or service +identity fallback for this Toolbox guard. This does not prove identical +authorization semantics for every non-Toolbox retrieval path. + +Hosted generative-AI message capture is disabled by default. Set +`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true` only after approving +the telemetry data-handling implications. + +## Retrieval and ingestion boundaries + +`RETRIEVAL_BACKEND` accepts `ai_search` and `foundry_iq`. The +[resolver](../src/util/retrieval_backend.py) caches the selection and falls +back to `ai_search` for empty or unknown values. Restart after changing it. +The selector is used by the MAF Lite, MAF Agent Service, and multimodal +context providers and the shared knowledge-search connector. It is not a +switch that changes every tool or datasource in every strategy. + +Foundry IQ needs `KNOWLEDGE_BASE_NAME` and a knowledge-base or Search endpoint. +Its default retrieve API version is `2026-05-01-preview`. +`azureBlob` native sources and `searchIndex` sources are distinct paths. +The multimodal strategy's Foundry IQ provider supplies text grounding, not +the classic image-grounding path. [Work IQ](#work-iq-microsoft-365) and +[generic MCP knowledge sources](#generic-mcp-knowledge-sources-preview) are +additional opt-ins, not default retrieval. They are separate from +`AGENT_STRATEGY=mcp`. + +The optional ingestion Container App is a platform deployment choice, +independent of Container Apps or Foundry hosted orchestration. In the current +platform deployment contract, fresh supported native Foundry IQ deployments +default that choice to `false`. Existing and custom selections remain intact. +Deploying ingestion does not select `RETRIEVAL_BACKEND`, register all knowledge +sources, or switch a configured source automatically. + +Native Foundry IQ Blob indexing covers its configured corpus. It does not +replace conversation attachment processing, SharePoint synchronization, +NL2SQL metadata ingestion, or every custom format. The connector has a +separate opt-in conversation-upload `searchIndex` source for native Blob +topologies. Keep the required attachment ingestion path when enabling it. +Choose topology in the central deployment guide, not by extrapolating from a +runtime feature flag. + +### Classic MCP strategy + +Select `AGENT_STRATEGY=mcp` for the request-scoped MCP agent. +Configure these settings through the configuration provider: + +| Setting | Default | Behavior | +| --- | --- | --- | +| `MCP_APP_ENDPOINT` | `http://localhost:80` in detected Azure environments | Base MCP server URL. Non-Azure execution overrides even a configured URL with `http://localhost:5000`. | +| `MCP_SERVER_TRANSPORT` | `sse` | Accepts `sse` and `streamable_http`. | +| `MCP_CLIENT_TIMEOUT` | `600` | Connection/read timeout in positive integer seconds. | +| `MCP_APP_APIKEY` | Unset | Optional value sent as `X-API-KEY`. Store it as a Key Vault reference. | +| `AGENT_ID` | Unset | Optional ID passed to the request-scoped agent. | + +Azure detection checks for the process variable `CONTAINER_APP_NAME` or +`WEBSITE_INSTANCE_ID`. Outside that detection, run the local MCP server on +port `5000`. In Azure, set `MCP_APP_ENDPOINT` to the actual trusted server. +Use HTTPS outside controlled local development. + +The client appends `/sse` for `sse` or `/mcp` for `streamable_http` exactly +once. A conflicting suffix is rejected. For example, an Azure endpoint +`https://mcp.contoso.com` resolves to `https://mcp.contoso.com/sse` by default. +The client sends `user-context` and any configured API key. Hosted execution +also forwards its validated platform call ID as described above. +This client does not enforce the generic Foundry IQ MCP trusted-host policy. + +### Work IQ Microsoft 365 + +Work IQ adds Microsoft 365 grounding through an already registered `workIQ` +knowledge source on the selected Foundry IQ knowledge base. The shipped +settings catalog describes it as a gated preview requiring admin consent on +the Foundry connector. Confirm service availability, provision the source, +and complete that consent before enabling it. A runtime flag does not perform +registration or grant Microsoft 365 permissions. + +| Setting | Default | Setup | +| --- | --- | --- | +| `RETRIEVAL_BACKEND` | `ai_search` | Select `foundry_iq` for the retrieval paths described above. | +| `WORK_IQ_ENABLED` | `false` | Set `true` to opt in to Work IQ. | +| `WORK_IQ_KNOWLEDGE_SOURCE_NAME` | Empty | Set the registered Work IQ source name on that knowledge base. | +| `FOUNDRY_IQ_MAX_RUNTIME_SECONDS` | `120` | Retrieve runtime ceiling emitted when a remote or generic MCP source is enabled. | + +Work IQ requires per-user OBO authorization. The backend delegation helper +uses `OAUTH_AZURE_AD_TENANT_ID`, `OAUTH_AZURE_AD_CLIENT_ID`, and +`OAUTH_AZURE_AD_CLIENT_SECRET`, plus delegated permissions and consent for the +requested resource. Keep the secret in Key Vault. The Foundry IQ caller +forwards the Search-audience OBO token through +`x-ms-query-source-authorization`. Microsoft 365 enforces the source's user +ACLs natively, so the Work IQ entry has no `filterAddOn`. + +At the connector level, an empty source name or absent OBO token skips the +Work IQ entry with a warning. The MAF context provider additionally requires +a retrieval user token when Work IQ is enabled and can fail before retrieve. +Neither path substitutes managed identity for the user. A Toolbox call ID +alone is not an OBO token. `FOUNDRY_IQ_FORWARD_SOURCE_AUTH` is deprecated and +ignored, not a way to enable service-identity fallback. + +### Generic MCP knowledge sources preview + +This is a separate, default-off Foundry IQ preview, not the classic MCP +strategy or a source-provisioning API. Register each `mcpServer` knowledge +source on the knowledge base first. Its name, server, and tools must match +the metadata you configure here. + +| Setting | Default | Setup or constraint | +| --- | --- | --- | +| `RETRIEVAL_BACKEND` | `ai_search` | Select `foundry_iq` for the applicable retrieval paths. | +| `FOUNDRY_IQ_API_VERSION` | `2026-05-01-preview` | This exact version is required when generic MCP is enabled. | +| `FOUNDRY_IQ_MCP_ENABLED` | `false` | Set `true` to enable conversational MCP planning. | +| `FOUNDRY_IQ_MCP_SOURCES_JSON` | `[]` | A non-empty JSON array of source objects is required when enabled. | +| `FOUNDRY_IQ_MCP_REASONING_EFFORT` | `low` | Only `low` and `medium` are accepted. | +| `FOUNDRY_IQ_MCP_TRUSTED_HOSTS` | Empty | Required exact hostname allowlist. Accepts comma/newline-separated hostnames or a JSON array of hostnames. | +| `FOUNDRY_IQ_MCP_LOG_TOOL_ARGUMENTS` | `false` | Keep off unless debug argument logging and its data-handling risks are approved. | +| `FOUNDRY_IQ_MAX_RUNTIME_SECONDS` | `120` | Must be an integer from `30` to `600` for this preview. | + +For example, set the trusted hosts to `mcp.contoso.com` and the source JSON +to the complete array below. Replace the illustrative name, host, tool, and +resource scope with the values of your registered source. Do not paste the +JSON into the process environment unless runtime environment overrides are +explicitly enabled. + +```json +[ + { + "name": "monitor-mcp", + "description": "Read-only Azure Monitor MCP source.", + "serverURL": "https://mcp.contoso.com/mcp", + "failOnError": true, + "maxOutputDocuments": 5, + "tools": [ + { + "name": "query_logs", + "outputParsing": { + "kind": "json", + "jsonParameters": { + "documentsPath": "$.results[*]", + "includeContext": true + } + }, + "inclusionMode": "reranked", + "maxOutputTokens": 2048 + } + ], + "queryHeaders": [ + { + "name": "Authorization", + "valueFrom": { + "kind": "managedIdentity", + "scope": "api://monitor-mcp/.default" + } + } + ] + } +] +``` + +Each source requires a unique `name`, HTTPS `serverURL`, and non-empty +`tools` list. Tool names must be unique within a source. Each tool requires +`name`, `outputParsing`, `inclusionMode` (`reranked` or `always`), and +`maxOutputTokens` from `1` to `8192`. `outputParsing.kind` accepts `auto`, +`none`, `json`, or `split`. Only `json` requires `jsonParameters`, including +`documentsPath` and optional boolean `includeContext`. `split` optionally +accepts `splitParameters` with `textSplitMode` (`pages` or `sentences`), +positive `maximumPageLength`, non-negative `pageOverlapLength`, positive +`maximumPagesToTake`, and non-empty `defaultLanguageCode`. When both lengths +are set, overlap must be smaller than the page length. +Do not mix parsing parameters across kinds. + +`failOnError` defaults to `true`, so a reported failure from that source +fails the request. Set it to `false` only if partial answers are acceptable. +Optional `maxOutputDocuments` accepts `1` to `50` per source. The separate +top-level `FOUNDRY_IQ_MAX_OUTPUT_DOCUMENTS` can limit final grounding records. +`description` and `queryHeaders` are optional. Unknown fields are rejected. +Server URLs and tool definitions are registration metadata, not sent in the +retrieve body. The request references registered source names and relays +resolved query headers through paired source-specific control headers. + +Trusted-host entries are bare hostnames without scheme, port, or path. +Matching is exact after lowercasing and removing a trailing dot, not wildcard +or subdomain matching. Server URLs reject HTTP, embedded credentials, query +strings, fragments, IP literals, single-label names, and reserved/local host +names such as `localhost` or names ending in `.internal` or `.test`. +The hostname allowlist does not replace network egress or DNS controls. + +Each query header needs a valid, unique HTTP field name and `valueFrom` +metadata. Reserved and hop-by-hop headers such as `Host` and `Connection` +are rejected. +Literal tokens, API keys, and other credential-value fields are rejected in +the source JSON. Use one of these credential kinds: + +| `valueFrom.kind` | Required metadata | Runtime behavior | +| --- | --- | --- | +| `managedIdentity` | `scope`, such as `api://monitor-mcp/.default` | Requests a token through the configured service credential chain, which can use Azure CLI locally. Grant that identity the required access. | +| `obo` | Explicit target-resource `scope` | Exchanges the original incoming user token for that scope. Requires the backend OBO settings described for Work IQ and resource consent. It does not reuse the Search-audience token. | +| `keyVaultSecret` | `secretName` | Reads the named secret from `KEY_VAULT_URI` using the Key Vault credential chain. Grant secret-read access and store the complete header value there. | +| `none` | Neither `scope` nor `secretName` | Omits that header. An empty `queryHeaders` list also adds no credentials. | + +These choices do not disable Foundry IQ service authentication or the +registered MCP source's authorization requirements. + +`scope` is forbidden for `keyVaultSecret` and `none`. `secretName` is forbidden +for all kinds except `keyVaultSecret`, where it must be `1` to `127` letters, +digits, or hyphens. Missing credentials or a failed OBO exchange fail the +request rather than downgrading to `none`. Normal telemetry omits tool +arguments. The debug opt-in applies bounded recursive redaction, not a +guarantee that arbitrary arguments contain no sensitive data. + +For NL2SQL, a least-privilege read-only database principal is the primary +security boundary. Generated-SQL validation is not a replacement for database +permissions. + +## Audit and recovery + +Orchestrator audit emission defaults to off with `AUDIT_EVENTS_ENABLED=false`. +Enabling it requires `AUDIT_HMAC_KEY`, encoded as exactly 32 random bytes in +Base64, Base64URL, or hexadecimal form. Keep it in Key Vault and out of the +dashboard. `AUDIT_HMAC_KEY_ID` defaults to `v1`. Change the key and ID together +and restart. Pseudonyms across rotations are not directly correlatable. + +Events use the existing telemetry pipeline, the `gptrag.audit` logger, and +`agentlz.audit.*` custom-event names. Sensitive capture defaults to off and +also requires an explicit `AUDIT_SENSITIVE_CONTENT_FIELDS` allowlist. +Audit sanitation is defense in depth, not a guarantee that arbitrary content +is safe to export. Audit events are best-effort operational evidence, not an +immutable ledger or a compliance certification. + +The [logical v2 schema](../contracts/audit-event-v2.schema.json), +[Application Insights wire schema](../contracts/audit-event-v2.application-insights.schema.json), +and [digests](../contracts/audit-event-v2.sha256) define event compatibility. +Application Insights custom properties are strings. Decode the root +`parent_event_id` sentinel `evt_00000000000000000000000000000000` to null +before correlating events. The v1 files are retained for historical events. + +Set `AUDIT_EVENTS_ENABLED=false` and restart to stop new audit events. +This does not remove previously exported telemetry. Reverting an image does +not undo persisted configuration or Cosmos changes. Use the platform's +compatible release pins and separately authorized data/configuration recovery. + +## Component image build selection + +The shipped `v5.2.1` [PowerShell](../scripts/deploy.ps1) and +[Bash](../scripts/deploy.sh) deployment hooks accept `BUILD_MODE=local` or +`BUILD_MODE=acr-task`, normalized for case and surrounding whitespace. +An unset value does **not** unconditionally mean remote ACR Tasks: + +1. An explicit non-empty `BUILD_MODE` takes precedence. +2. Otherwise recognized legacy `USE_DOCKER` values select `local` + (`true`, `1`, `yes`) or `acr-task` (`false`, `0`, `no`). +3. If still unset, `NETWORK_ISOLATION=true` or a non-empty + `ACR_TASK_AGENT_POOL` selects `acr-task`. +4. Otherwise a usable Docker command and successful `docker info` select + `local`. If Docker is unavailable, select `acr-task`. + +PowerShell also rejects the Docker Desktop manually paused diagnostic. +It trims the legacy/boolean selectors, while Bash only lowercases those +selectors. Prefer exact values without whitespace for portable configuration. +Unknown legacy `USE_DOCKER` values fall through to automatic selection. +Unknown explicit `BUILD_MODE` values fail. + +Explicit `local` fails when Docker is unavailable rather than switching to +remote. Local builds use `linux/amd64`, then push to ACR. `acr-task` uses +`az acr build`. Network-isolated remote builds require +`ACR_TASK_AGENT_POOL`, otherwise selection fails. This documents component +behavior, not a standalone deployment recipe or an assertion that a parent +hook sets `BUILD_MODE`. diff --git a/evaluations/README.md b/evaluations/README.md index 7c58e713..84c3f057 100644 --- a/evaluations/README.md +++ b/evaluations/README.md @@ -1,22 +1,14 @@ -# Evaluations (deprecated) - -The standalone evaluation scripts that used to live in this folder have been -removed. Evaluation for Agent Landing Zone agents now runs through the AgentOps Accelerator, -which scores the live orchestrator endpoint with Foundry's built-in evaluators and -turns the result into a pull request gate. - -## Where evaluation lives now - -- End-to-end evaluation workflow (dataset, evaluators, PR and dev gates): - [AgentOps HTTP agent tutorial](https://azure.github.io/agentops/tutorial-http-agent/). -- Retrieval tuning with the Document Retrieval evaluator and ranking metrics: - [Agent Landing Zone retrieval configuration](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/configuration/#retrieval). - -## Why it changed - -The old folder ran a one-off batch evaluation against a golden dataset and printed -a Foundry Studio link. AgentOps replaces that with a repeatable workflow: it builds -its own evaluation dataset, runs the evaluators against the deployed endpoint, and -fails the build when scores drop. Keeping a second, divergent harness here only -added drift, so evaluation is consolidated in one place. +# Evaluations (deprecated folder) +This folder contains no maintained standalone evaluation runner. +The repository's Python tests are under [`tests/`](../tests/). + +For optional evaluation workflow setup, see the external +[AgentOps HTTP agent tutorial](https://azure.github.io/agentops/tutorial-http-agent/). +Adopting that workflow requires its own dataset, endpoint, credentials, and +gate configuration. This repository does not enable an AgentOps pull-request +gate merely by linking to the tutorial. + +Product guidance belongs in the +[Agent Landing Zone documentation](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/), +maintained in the [canonical main-branch source](https://github.com/Azure/AI-Landing-Zones/tree/main/docs/agent-landing-zone). diff --git a/infra/README.md b/infra/README.md index c488bbbc..98ca2e95 100644 --- a/infra/README.md +++ b/infra/README.md @@ -1,3 +1,12 @@ -The infrastructure for this project is defined in: https://github.com/Azure/agent-landing-zone +# Infrastructure ownership -After deploying the infrastructure, you can use `azd env refresh` and `azd deploy` to deploy the data ingestion. +This component's `main.bicep` declares a resource-group scope but creates no +resources. [Agent Landing Zone](https://github.com/Azure/agent-landing-zone) +owns the deployment topology, resources, identities, configuration, and runtime +version pins. + +Follow the [central deployment documentation](https://azure.github.io/AI-Landing-Zones/agent-landing-zone/) +from its [main-branch source](https://github.com/Azure/AI-Landing-Zones/tree/main/docs/agent-landing-zone). +The component's `azure.yaml` and deployment scripts assume an existing compatible +environment. They are not a standalone infrastructure template. +For local startup only, return to the [orchestrator README](../README.md).