Compute & AI Services
Where the harness actually runs, and how it talks to large language models. The agent client is provider-agnostic — the same code path works against OpenRouter, Azure OpenAI, plain OpenAI, or any OpenAI-compatible gateway, selected by a single configuration switch.
Hosting options
The harness is a standard ASP.NET Core application, so it can run on any Azure compute service that supports .NET 10 containers. The choice comes down to how much operational overhead you want to take on, what scaling model fits your workload, and whether you need Docker-in-Docker for the sandbox executor.
| Factor | Container Apps | App Service | AKS |
|---|---|---|---|
| Best for | Most deployments | Simple / single-app | Enterprise at scale |
| Scaling | Event-driven, 0 → N | Fixed / auto rules | Full Kubernetes |
| Cost model | Consumption or dedicated | Always-on tiers | Node pools |
| SignalR | Built-in WebSocket | Needs ARR affinity | Ingress controller |
| Sandbox (Docker.DotNet) | Needs dedicated plan | Not supported | Native Docker socket |
| Complexity | Low–Medium | Low | High |
Container Apps for most teams. It handles the SignalR WebSocket requirement natively, scales to zero in dev, and supports the dedicated compute plan needed for Docker.DotNet sandbox isolation.
Container Apps layout
The harness deploys as three Container Apps, each with a distinct responsibility. Splitting them lets you scale the API and SignalR hub independently and isolate the MCP server for external consumers.
harness-mcp: don't point them at an authed endpoint
The MCP server is fail-closed: when authentication is configured, a
fallback authorization policy (RequireAuthenticatedUser) protects
every endpoint that lacks explicit authorization metadata. An unauthenticated
liveness/readiness probe hitting the MCP endpoint (or any other route) therefore gets a
401, which Container Apps reads as an unhealthy replica and will restart
in a crash loop. Configure the probe as a TCP probe against the
container port, or expose a dedicated anonymous /health
endpoint (map it with .AllowAnonymous()) and point the probe there. Do not
relax the fallback policy to make an authed route probe-friendly.
Bicep example
A representative Container App resource definition for the main API. Note how secrets reference Key Vault via managed identity rather than embedding values.
resource harnessApi 'Microsoft.App/containerApps@2024-03-01' = {
name: 'harness-api'
location: location
properties: {
environmentId: containerAppEnv.id
configuration: {
ingress: {
external: true
targetPort: 8080
transport: 'auto'
}
secrets: [
{
name: 'openai-key'
keyVaultUrl: '${keyVault.properties.vaultUri}secrets/AzureOpenAI-ApiKey'
identity: managedIdentity.id
}
]
}
template: {
containers: [
{
name: 'harness-api'
image: '${containerRegistry}/harness-api:${imageTag}'
resources: { cpu: json('1.0'), memory: '2Gi' }
env: [
{
name: 'AI__AgentFramework__AzureOpenAI__Endpoint'
value: openAiEndpoint
}
{
name: 'AI__AgentFramework__AzureOpenAI__ApiKey'
secretRef: 'openai-key'
}
]
}
]
scale: {
minReplicas: 1
maxReplicas: 10
rules: [
{
name: 'http-rule'
http: { metadata: { concurrentRequests: '50' } }
}
]
}
}
}
}
AI providers
The harness needs two model surfaces: a chat-completion model for the agent
runtime, and an embedding model for the RAG pipeline. The chat surface is
provider-agnostic — one configuration block (AI:AgentFramework) selects
between OpenRouter, Azure OpenAI, plain OpenAI, Anthropic, or any OpenAI-compatible gateway.
Embeddings use a separate block (AI:Embedding) and currently expect an OpenAI
or Azure OpenAI endpoint.
Agent model (chat completion)
Powers conversation, tool selection, and reasoning — the model that decides when to call
tools, how to interpret results, and what to respond. The ClientType switch picks
the SDK path; everything else is just an endpoint, a model identifier, and credentials.
| Provider | ClientType |
Endpoint |
Notes |
|---|---|---|---|
| OpenRouter (default) | OpenAI |
https://openrouter.ai/api/v1 |
OpenAI-compatible gateway. DefaultDeployment is a slug like
anthropic/claude-sonnet-4.6. Single key unlocks ~100 model families. |
| Azure OpenAI | AzureOpenAI |
Your Azure OpenAI resource URL | DefaultDeployment is the deployment name from Azure AI Foundry, not
the model name. Supports managed identity. |
| OpenAI (direct) | OpenAI |
https://api.openai.com/v1 |
DefaultDeployment is the model id (gpt-4o etc.). |
| Anthropic / Claude (via Azure Foundry) | Anthropic |
AppConfig:AI:AgentFramework:Endpoint (your Foundry resource) |
DefaultDeployment is the model id (e.g.
claude-sonnet-4-6). Claude ships through Azure AI Foundry, not a
direct connection: the Anthropic SDK builds api.anthropic.com URLs, and
AzureFoundryRewritingHandler rewrites each request to the configured
Foundry endpoint (native Messages API at /anthropic/v1/messages),
keeping the x-api-key header. |
| Azure AI Foundry (inference) | AzureAIInference |
Your Foundry project inference endpoint | Non-OpenAI models (Claude, Mistral, …) deployed via Foundry, called through the Azure AI Inference SDK. |
| Azure AI Foundry (hosted agent) | PersistentAgents |
Foundry project endpoint | Pre-configured, server-side-stateful agents managed by Foundry. Entra ID auth
from AppConfig:AI:AIFoundry. |
| Azure AI Foundry (responses agent) | FoundryResponses |
Foundry project endpoint | Direct-inference Foundry agent built via AIProjectClient.AsAIAgent(...)
with harness-composed model/instructions/tools. Yields an AIAgent
(not an IChatClient), so it wires in at the agent-factory level. |
The harness ships Microsoft.Agents.AI.Foundry support and a dedicated
Presentation.FoundryHost host. The PersistentAgents and
FoundryResponses client types let you run agents backed by Azure AI
Foundry — either server-managed (persistent state) or built at runtime from a
Foundry project endpoint — while keeping the full harness middleware pipeline
(OpenTelemetry, content safety, function-invocation limits) via the client-factory hook.
Embedding model
Generates vector representations for document chunks during ingestion and queries during retrieval. Configured separately from the chat client and currently expects an OpenAI or Azure OpenAI endpoint — OpenRouter does not yet expose an embeddings surface, so even with OpenRouter selected as the chat provider you still need an OpenAI-style embedding endpoint if you want RAG or the knowledge graph.
Configuration mapping
Each code-level config path maps to a specific resource or setting. Use this table to trace
from appsettings.json (or user-secrets) to the upstream provider.
| Code Config Path | Upstream | What It Controls |
|---|---|---|
AI:AgentFramework:ClientType |
Selector — OpenAI / AzureOpenAI /
Anthropic |
Which SDK path the chat client takes |
AI:AgentFramework:Endpoint |
Provider URL | Where chat completions are sent |
AI:AgentFramework:DefaultDeployment |
Model identifier or deployment name | Which model is invoked |
AI:AgentFramework:ApiKey |
Provider API key (or managed identity for Azure OpenAI) | Authentication |
AI:Embedding:Endpoint |
OpenAI or Azure OpenAI resource endpoint | Embedding generation |
AI:Embedding:DeploymentName |
Embedding deployment / model id | Embedding model |
AI:Embedding:Dimensions |
N/A (client-side config) | Vector dimensions (1536 / 3072) |
When credentials aren't configured, the harness exposes the gap in three places: the
/api/config/status endpoint (consumed by the WebUI banner), the
/health/ai health check, and — in Development only — a
surfaced chat error with the offending config keys. See
AiProviderStatus and AiProviderNotConfiguredException.
Most chat models have large context windows, but the harness uses a context budget system (see the Developer Guide) to stay well under. Plan your provider quota (TPM — Tokens Per Minute) based on expected concurrent users. A single agent turn can consume 4K–20K tokens depending on tool use and RAG context. OpenRouter exposes a single account-wide credit pool; Azure OpenAI quotas are per-deployment.
Azure-specific patterns
When the chat provider is Azure OpenAI (directly or via Azure AI Foundry), two Azure-native controls apply that the third-party gateways don't expose.
For Azure OpenAI in production, managed identity beats API keys. Set
AI:AgentFramework:UseManagedIdentity: true and assign the
Cognitive Services OpenAI User role to your Container App's managed
identity. The ApiKey field can then be left empty.
In Azure OpenAI, a deployment is a named instance of a specific
model. The harness's DefaultDeployment takes that deployment name —
not the model name. If your Azure portal shows no deployments, create one in Azure AI
Foundry first.