Skip to content

ElliotOne/nl-agent-interoperability-a2a-foundry-azure

Repository files navigation

nl-agent-interoperability-a2a-foundry-azure

An educational Azure-first .NET project showing how a small agent system can stay interoperable by combining Azure AI Foundry model access, A2A discovery and delegation, deterministic remote-agent validation, and single-image deployment to Azure Container Apps.

This version uses an Azure AI architecture guidance scenario with one remote specialist agent, one gateway agent, Foundry-backed answer generation and synthesis, A2A-compatible card and message routes, and cloud-first deployment through Azure Container Registry and Container Apps.

Overview

This repository demonstrates a practical agent-to-agent interoperability flow:

  1. Load runtime configuration for mode, Foundry endpoint, API key, model, specialist base URL, allowed agent hosts, and HTTPS enforcement.
  2. In Specialist mode, serve a remote agent card and accept delegated A2A message requests on fixed endpoints.
  3. Answer specialist questions by calling a Foundry-backed model with a fixed architecture knowledge pack.
  4. In Gateway mode, fetch the remote agent card, validate its host, scheme, and advertised skill against deterministic policy, then delegate the user question over A2A.
  5. Run a second Foundry call on the gateway to convert the specialist's prose answer into structured JSON with summary, findings, actions, and confidence.
  6. Return the structured gateway response together with the raw specialist answer and delegated conversation context.
  7. Optionally protect specialist A2A routes with API key authentication using constant-time comparison.
  8. Build one container image, then deploy it twice to Azure Container Apps with different environment-driven modes.

The result is a real cloud-hosted A2A system without local model infrastructure, local Docker builds, or hardcoded credentials in source.

What This Project Demonstrates

  • Azure AI Foundry integration through the OpenAI-compatible inference endpoint
  • Automatic resolution from Foundry project URLs to Azure OpenAI chat endpoints
  • A2A-compatible ASP.NET Core endpoints for remote agent-card discovery and delegated messaging
  • Deterministic validation of remote agent host, URL scheme, and required skill ID before delegation
  • Gateway synthesis that converts remote specialist prose into a bounded structured JSON response
  • Conditional specialist API key enforcement with constant-time comparison
  • Single-image deployment promoted to both gateway and specialist roles through environment configuration
  • Cloud-first build and deploy flow using Azure Container Registry build tasks and Azure Container Apps
  • Startup validation that refuses to run with missing or invalid runtime configuration
  • Conversation threading from gateway request through delegated A2A context

Prerequisites

  • .NET 10 SDK or later for source builds and tests
  • Azure subscription
  • Azure CLI logged into the target subscription
  • An Azure AI Foundry project endpoint such as https://<resource>.services.ai.azure.com/api/projects/<project>
  • A model deployment in that Foundry project such as gpt-4.1-mini
  • For local HTTPS specialist runs, an ASP.NET Core development certificate if you want to test the gateway flow locally

The checked-in configuration defaults to Gateway mode and expects live Foundry credentials. There is no mock-model path in this project.

Quick Start

From the project root, verify the solution first:

dotnet test AzureFoundryA2AInteroperability.slnx

For a local end-to-end run, start the specialist first in one terminal:

$env:ASPNETCORE_URLS="https://localhost:7001"
$env:A2AINT_Runtime__Mode="Specialist"
$env:A2AINT_Foundry__BaseUrl="https://<resource>.services.ai.azure.com/api/projects/<project>"
$env:A2AINT_Foundry__ApiKey="<foundry-api-key>"
$env:A2AINT_Foundry__ModelId="gpt-4.1-mini"
$env:A2AINT_SpecialistAgent__PublicBaseUrl="https://localhost:7001"
dotnet run --project AzureFoundryA2AInteroperability

Then start the gateway in a second terminal:

$env:ASPNETCORE_URLS="http://localhost:7002"
$env:A2AINT_Runtime__Mode="Gateway"
$env:A2AINT_Runtime__SpecialistBaseUrl="https://localhost:7001/a2a/specialist"
$env:A2AINT_Runtime__AllowedAgentHosts__0="localhost"
$env:A2AINT_Foundry__BaseUrl="https://<resource>.services.ai.azure.com/api/projects/<project>"
$env:A2AINT_Foundry__ApiKey="<foundry-api-key>"
$env:A2AINT_Foundry__ModelId="gpt-4.1-mini"
dotnet run --project AzureFoundryA2AInteroperability

Query the gateway:

POST http://localhost:7002/api/query
Content-Type: application/json

{
  "question": "How should I host a Foundry-backed A2A system on Azure without local dependencies?",
  "conversationId": "demo-session-1"
}

The gateway returns a structured answer containing the discovered specialist identity, a summary, delegated findings, next actions, confidence, and the raw specialist response.

Azure Deployment

This repo includes:

  • infra/main.bicep for shared Azure infrastructure
  • scripts/publish-image.ps1 to queue a cloud build in Azure Container Registry
  • scripts/deploy.ps1 to provision both Container Apps and wire the gateway to the specialist

Recommended deployment order:

  1. Provision shared infrastructure:
az deployment group create `
  --resource-group <rg> `
  --template-file .\infra\main.bicep `
  --parameters location=northeurope acrName=<acrName> containerAppsEnvironmentName=<envName>
  1. Build the image in Azure:
.\scripts\publish-image.ps1 `
  -ResourceGroupName <rg> `
  -AcrName <acrName> `
  -ImageTag v1
  1. Deploy the two Container Apps:
.\scripts\deploy.ps1 `
  -ResourceGroupName <rg> `
  -ContainerAppsEnvironmentName <envName> `
  -AcrName <acrName> `
  -ImageTag v1 `
  -FoundryBaseUrl https://<resource>.services.ai.azure.com/api/projects/<project> `
  -FoundryApiKey <foundry-api-key> `
  -FoundryModelId gpt-4.1-mini

If -SpecialistApiKey is omitted, the deploy script generates a random GUID key and injects it into both container apps automatically. Retrieve the deployed value later if needed:

az containerapp show `
  --resource-group <rg> `
  --name a2a-specialist `
  --query "properties.template.containers[0].env[?name=='A2AINT_Runtime__SpecialistApiKey'].value" `
  -o tsv

Configuration

Default settings are in AzureFoundryA2AInteroperability/appsettings.json.

Example:

{
  "Runtime": {
    "Mode": "Gateway",
    "RequestTimeoutSeconds": 45,
    "SpecialistBaseUrl": "https://specialist.contoso.com/a2a/specialist",
    "SpecialistApiKey": "replace-me",
    "A2AApiKeyHeaderName": "x-a2a-api-key",
    "RequireHttpsSpecialist": true,
    "AllowedAgentHosts": [ "specialist.contoso.com" ]
  },
  "Foundry": {
    "BaseUrl": "https://YOUR-RESOURCE.services.ai.azure.com/api/projects/YOUR-PROJECT",
    "ApiKey": "replace-me",
    "ModelId": "gpt-4.1-mini"
  },
  "SpecialistAgent": {
    "Name": "Foundry Architecture Specialist",
    "Description": "Azure-hosted AI engineering specialist for A2A interoperability, secure delegation, and production deployment patterns.",
    "Version": "1.0.0",
    "SkillId": "azure_ai_architecture_review",
    "PublicBaseUrl": "https://specialist.contoso.com"
  }
}

Environment variable overrides use prefix A2AINT_:

  • A2AINT_Runtime__Mode
  • A2AINT_Runtime__RequestTimeoutSeconds
  • A2AINT_Runtime__SpecialistBaseUrl
  • A2AINT_Runtime__SpecialistApiKey
  • A2AINT_Runtime__A2AApiKeyHeaderName
  • A2AINT_Runtime__RequireHttpsSpecialist
  • A2AINT_Runtime__AllowedAgentHosts__0
  • A2AINT_Foundry__BaseUrl
  • A2AINT_Foundry__ApiKey
  • A2AINT_Foundry__ModelId
  • A2AINT_SpecialistAgent__Name
  • A2AINT_SpecialistAgent__Description
  • A2AINT_SpecialistAgent__Version
  • A2AINT_SpecialistAgent__SkillId
  • A2AINT_SpecialistAgent__PublicBaseUrl

How It Works

  1. Program.cs loads configuration through AppConfig.Load, validates it at startup, registers the Foundry chat client, and branches service registration and route mapping based on Gateway or Specialist mode.
  2. AppConfig enforces valid runtime mode, request timeout bounds, Foundry HTTPS endpoint requirements, required model credentials, and mode-specific specialist URL rules.
  3. FoundryConfig.GetChatEndpoint() resolves either a Foundry project URL or Azure OpenAI-style URL to the /openai/v1/ chat endpoint used by the OpenAI SDK.
  4. FoundryChatClientFactory builds an IChatClient over the OpenAI client so the same chat abstraction is reused by both specialist answering and gateway synthesis.
  5. In Specialist mode, /a2a/* requests optionally pass through API key middleware, then GET /a2a/specialist/v1/card returns the configured AgentCard.
  6. POST /a2a/specialist/v1/message:stream extracts non-empty text parts from the inbound A2AMessageRequest, forwards them to SpecialistAnswerService, and returns an A2A-style agent message with a stable context ID.
  7. SpecialistInstructionFactory supplies a fixed eight-point Azure architecture knowledge pack so the remote specialist stays inside a narrow interoperability and deployment scope.
  8. In Gateway mode, POST /api/query forwards the caller request to GatewayQueryService.
  9. GatewayQueryService uses A2ARemoteClient to fetch the remote card and send the delegated question, then passes both the original question and remote answer into GatewaySynthesisService.
  10. AgentCardValidationPolicy deterministically checks card name, description, absolute URL, HTTPS requirements, allowlisted host membership, and required skill ID presence before any delegation is trusted.
  11. A2ARemoteClient normalizes the configured specialist base URL, attaches the configured API key header when present, deserializes the agent card and delegated message, and rejects empty remote text responses.
  12. GatewaySynthesisService prompts the Foundry model to return only JSON matching the GatewaySynthesis schema, extracts the outer JSON object, deserializes it, and normalizes findings, actions, and confidence values before returning the final gateway answer.
  13. Both deployment modes expose GET /healthz, and GET / returns a compact JSON description of the running role and available endpoints.

Project Structure

.
+-- AzureFoundryA2AInteroperability.slnx
+-- AzureFoundryA2AInteroperability/
|   +-- AzureFoundryA2AInteroperability.csproj
|   +-- Program.cs
|   +-- appsettings.json
|   +-- App/
|   |   +-- AppConfig.cs
|   +-- Domain/
|   |   +-- A2AModels.cs
|   +-- Properties/
|   |   +-- launchSettings.json
|   +-- Services/
|       +-- A2ARemoteClient.cs
|       +-- AgentCardValidationPolicy.cs
|       +-- FoundryChatClientFactory.cs
|       +-- GatewayQueryService.cs
|       +-- GatewaySynthesisService.cs
|       +-- SpecialistAnswerService.cs
|       +-- SpecialistInstructionFactory.cs
+-- AzureFoundryA2AInteroperability.Tests/
|   +-- AzureFoundryA2AInteroperability.Tests.csproj
|   +-- AgentCardValidationPolicyTests.cs
|   +-- AppConfigTests.cs
+-- infra/
|   +-- main.bicep
+-- scripts/
|   +-- deploy.ps1
|   +-- publish-image.ps1
+-- Dockerfile
+-- .dockerignore
+-- LICENSE
+-- README.md

Interoperability Guarantees

  • The gateway never trusts a remote specialist card without deterministic validation first
  • Remote card URLs can be forced to HTTPS and restricted to an explicit host allowlist
  • Delegation proceeds only if the remote specialist advertises the expected skill ID
  • Specialist A2A routes can be protected with a shared API key, and comparison uses CryptographicOperations.FixedTimeEquals
  • The gateway keeps control of the caller-facing API and synthesizes the remote answer into a bounded JSON contract
  • Empty remote messages and malformed synthesis payloads fail closed with exceptions instead of being silently accepted
  • One container image can be promoted across both runtime roles without changing source code

Tests

Run the test project from the repo root:

dotnet test AzureFoundryA2AInteroperability.slnx

The current test suite covers:

  • gateway configuration validation for valid HTTPS specialist settings
  • rejection of non-HTTPS specialist URLs when HTTPS is required
  • acceptance of agent cards from an allowlisted host
  • rejection of agent cards from an unexpected host

License

See the LICENSE file for details.

Contributing

Contributions are welcome for improvements within current project scope.

Suggested areas:

  • Add broader gateway and specialist endpoint tests beyond config and card validation
  • Prefer managed identity or secret references in deployment wiring where the hosting model allows it
  • Expand A2A payload handling to support richer metadata, tool results, or non-text parts
  • Add observability hooks such as structured request correlation and delegated failure classification
  • Tighten JSON synthesis parsing with stronger schema validation or explicit response contracts

About

Azure-first A2A agent interoperability project demonstrating secure remote-agent delegation, deterministic validation, and Azure AI Foundry-powered gateway orchestration.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors