Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .azure-pipelines/scripts/hosted-agent-samples-ci-skiplist
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ samples/csharp/hosted-agents/agent-framework/a2a/01-delegation/caller
samples/python/hosted-agents/agent-framework/a2a/01-delegation/caller
samples/python/hosted-agents/langgraph/a2a/a2a-caller

# Activity and WebSocket protocols need dedicated orchestration/clients.
# A2A, Activity, and WebSocket protocols need dedicated orchestration/clients.
samples/python/hosted-agents/bring-your-own/a2a/echo
samples/csharp/hosted-agents/bring-your-own/invocations_ws/HelloWorld
samples/python/hosted-agents/bring-your-own/activity/echo
samples/python/hosted-agents/bring-your-own/activity/github-copilot
Expand Down
10 changes: 8 additions & 2 deletions samples/python/hosted-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ Hosted agents run any code you can put in a container. These samples cover three
| | **Agent Framework** | **LangGraph** | **Bring Your Own** |
| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Best for** | Starting fresh on Foundry — also supports AutoGen and Semantic Kernel | Already using LangChain / LangGraph | Already built with CrewAI or your own stack |
| **SDK** | `agent-framework-foundry-hosting` (includes core, openai, foundry, orchestrations) | `langchain-azure-ai[hosting]` (`ResponsesHostServer` / `InvocationsHostServer`) | `azure-ai-agentserver-responses` / `azure-ai-agentserver-invocations`, or `azure-ai-agentserver-core` for fully custom HTTP |
| **SDK** | `agent-framework-foundry-hosting` (includes core, openai, foundry, orchestrations) | `langchain-azure-ai[hosting]` (`ResponsesHostServer` / `InvocationsHostServer`) | `azure-ai-agentserver-responses` / `azure-ai-agentserver-invocations` / `azure-ai-agentserver-a2a`, or `azure-ai-agentserver-core` for fully custom HTTP |
| **Foundry integration** | Native — sessions, tools, memory, streaming all built in | Native via `langchain_azure_ai.agents.hosting` — sessions, streaming, and tool-call surfacing built in for LangGraph agents (`create_agent`) and custom `StateGraph`s | Core adapter hosts the web server and exposes `/invocations` and `/responses` endpoints; you supply the agent logic |
| **Protocols** | Responses and Invocations | Responses and Invocations | Responses and Invocations |
| **Protocols** | Responses and Invocations | Responses and Invocations | Responses, Invocations, and A2A |
| **Language support** | Python and C# | Python only | Any language (Python and C# samples provided) |
| **Start here** | [Basic Agent →](agent-framework/responses/01-basic/) | [LangGraph Chat →](langgraph/responses/01-langgraph-chat/) | [Hello World →](bring-your-own/responses/hello-world/) |

Expand Down Expand Up @@ -204,6 +204,12 @@ Already built an agent with CrewAI or your own code? The protocol SDKs (`azure-a
| **[Human-in-the-Loop](bring-your-own/invocations/human-in-the-loop/)** | Long-running agent that pauses for human approval before continuing. |
| **[Event Grid Trigger](bring-your-own/invocations/event-grid-trigger/)** | Event-driven agent: Azure Storage → Event Grid → hosted agent (direct delivery, authenticated by the system topic's system-assigned managed identity); agent summarizes the new blob and writes the summary to a sibling Storage container. |

### A2A protocol

| Sample | What it shows |
| ------ | ------------- |
| **[Echo](bring-your-own/a2a/echo/)** | Minimal native A2A v1 agent using `a2a-sdk`, hosted on Foundry with `azure-ai-agentserver-a2a`, plus a dedicated local and hosted A2A client. |

## Python dependencies

New Python Hosted Agent runtimes use `pyproject.toml` with `uv.lock` by default. A fully resolved `requirements.txt` remains accepted as a backward-compatible fallback. The selected artifact must capture the complete dependency graph; CI validates uv locks with a pinned uv version and a frozen production export.
Expand Down
61 changes: 61 additions & 0 deletions samples/python/hosted-agents/bring-your-own/a2a/echo/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Coding Agent Instructions - A2A Echo

This sample is a **Microsoft Foundry hosted agent** that implements a native
A2A v1 echo agent with `a2a-sdk` and hosts it through
`azure-ai-agentserver-a2a`. It runs in
[Foundry Agent Service](https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents).

## Deployment mode

- **Direct code deployment:** `codeConfiguration` declares the Python runtime
and entry point. A Dockerfile is not required.

## Key files

- `azure.yaml` - Foundry project and native A2A hosted-agent declaration
- `src/echo-a2a/main.py` - native A2A echo executor and server entry point
- `src/echo-a2a/test_client.py` - dedicated local or hosted A2A v1 client
- `src/echo-a2a/requirements.txt` - fully resolved runtime dependencies
- `README.md` - local, deployment, and E2E instructions

Do not commit Toolkit-generated `.vscode` files.

## Dependency workflow

Follow `samples/python/hosted-agents/DEPENDENCY_POLICY.md`.

Keep `azure-ai-agentserver-a2a` and every transitive runtime dependency pinned
to an exact published version in `requirements.txt`.

## Development workflow

```powershell
cd src\echo-a2a
python -m venv .venv
.\.venv\Scripts\python -m pip install -r requirements.txt
.\.venv\Scripts\python main.py
```

In another terminal:

```powershell
cd src\echo-a2a
.\.venv\Scripts\python test_client.py --message "hello"
```

The generic hosted-agent cloud runner does not yet support A2A Agent Card
discovery and JSON-RPC Task handling. Use `test_client.py` for local and hosted
E2E validation.

## Microsoft Foundry Skill

Install the Microsoft Foundry Skill for guided deployment and troubleshooting:

```bash
npx skills add https://github.com/microsoft/azure-skills --skill microsoft-foundry
```

## References

- [Hosted agents overview](https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents)
- [A2A protocol](https://a2a-protocol.org/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# CLAUDE.md

This project uses [AGENTS.md](./AGENTS.md) as the single source of truth for
coding agent instructions.

@AGENTS.md
123 changes: 123 additions & 0 deletions samples/python/hosted-agents/bring-your-own/a2a/echo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<!-- Begin standard disclaimer — do not modify -->
**IMPORTANT!** All samples and other resources made available in this GitHub repository ("samples") are designed to assist in accelerating development of agents, solutions, and agent workflows for various scenarios. Review all provided resources and carefully test output behavior in the context of your use case. AI responses may be inaccurate and AI actions should be monitored with human oversight. Learn more in the transparency note for [Agent Service](https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/agents/transparency-note).

Agents, solutions, or other output you create may be subject to legal and regulatory requirements, may require licenses, or may not be suitable for all industries, scenarios, or use cases. By using any sample, you are acknowledging that any output created using those samples are solely your responsibility, and that you will comply with all applicable laws, regulations, and relevant safety standards, terms of service, and codes of conduct.

Third-party samples contained in this folder are subject to their own designated terms, and they have not been tested or verified by Microsoft or its affiliates.

Microsoft has no responsibility to you or others with respect to any of these samples or any resulting output.
<!-- End standard disclaimer -->

# What this sample demonstrates

A minimal native **A2A v1 echo agent** using the Bring Your Own approach in
Python. The native `a2a-sdk` owns the Agent Card, JSON-RPC, Task, artifact, and
executor behavior. `azure-ai-agentserver-a2a` adds Foundry's canonical routes,
request context, readiness, telemetry, and lifecycle.

## How it works

The executor creates an A2A Task, adds an artifact containing
`Echo: <message>`, and completes the Task. The host exposes:

```text
POST /invocations/a2a
GET /invocations/a2a/.well-known/agent-card.json
GET /readiness
```

[`test_client.py`](src/echo-a2a/test_client.py) uses the native A2A client for
Agent Card discovery and JSON-RPC validation against either the local server or
the public Foundry A2A endpoint.

> [!NOTE]
> `InMemoryTaskStore` is used only for this deterministic echo sample. Use a
> durable native A2A `TaskStore` for production workloads that must survive
> container restarts, suspension, or replica movement.

## Prerequisites

1. Python 3.13.
2. Azure Developer CLI 1.27.1 or later with the `azure.ai.agents` extension.
3. Azure CLI authentication for hosted E2E validation.

## Run and test locally

```powershell
cd src\echo-a2a
python -m venv .venv
.\.venv\Scripts\python -m pip install -r requirements.txt
.\.venv\Scripts\python main.py
```

In another terminal:

```powershell
cd src\echo-a2a
.\.venv\Scripts\python test_client.py --message "hello"
```

Successful output includes:

```text
Echo: hello
```

## Deploy to Microsoft Foundry

From this sample directory:

```powershell
azd env new echo-a2a-dev
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
azd env set AZURE_LOCATION <supported-region>
azd provision
azd deploy
```

After deployment, get the project endpoint:

```powershell
$projectEndpoint = azd env get-value FOUNDRY_PROJECT_ENDPOINT
$endpoint = "$projectEndpoint/agents/echo-a2a/endpoint/protocols/a2a"
```

Run the dedicated hosted E2E client:

```powershell
cd src\echo-a2a
.\.venv\Scripts\python test_client.py `
--endpoint $endpoint `
--card-path agentCard/v1.0 `
--azure-auth `
--message "hosted hello"
```

The generic `azd ai agent invoke` and hosted-agent cloud test runner do not yet
implement A2A Agent Card discovery and JSON-RPC Task handling, so this sample
uses its dedicated native client.

## Troubleshooting

- **`JSON-RPC Error -32099: The requested A2A operation is not supported for
this hosted-agent target`** — the project or subscription has not been
enabled for native hosted-agent A2A passthrough. Agent Card discovery may
still work through the A2A Head, but `SendMessage` will not reach the native
container. The Vienna code references
`AgentsPlatform.A2A.EnablePassthrough`, but its reviewed threat model requires
passthrough to remain disabled pending a separate security/privacy review.
This is a service-side ECS rollout controlled by the Foundry Agents team, not
a flag customers can enable on their Azure subscription.
- **Agent Card says both `a2a` and `responses` are required** — keep both
entries under `agentEndpoint.protocols` in `azure.yaml`. The container itself
remains native A2A.
- **Agent Card is not defined** — keep the deployment-time `agentCard` metadata
in `azure.yaml`; the native container card remains available at the canonical
container route for passthrough.

## Next steps

- Replace `InMemoryTaskStore` with a durable native A2A `TaskStore`.
- Add skills and artifacts using native `a2a-sdk` types.
- Add another Agent Server protocol host to run multiple protocol paths in the
same process.
55 changes: 55 additions & 0 deletions samples/python/hosted-agents/bring-your-own/a2a/echo/azure.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json

requiredVersions:
azd: '>=1.27.1'
extensions:
azure.ai.agents: '>=1.0.0-beta.9'
name: echo-a2a
services:
ai-project:
host: azure.ai.project
echo-a2a:
host: azure.ai.agent
metadata:
tags:
- AI Agent Hosting
- A2A Protocol
- Bring Your Own
- Python
project: src/echo-a2a
language: python
codeConfiguration:
runtime: python_3_13
entryPoint: main.py
uses:
- ai-project
kind: hosted
name: echo-a2a
displayName: Echo (Python, A2A)
description: >
Minimal native A2A v1 echo agent hosted by
azure-ai-agentserver-a2a for the Foundry platform contract.
protocols:
- protocol: a2a
version: 1.0.0
agentEndpoint:
protocols:
- a2a
- responses
agentCard:
description: Echoes the user's A2A message as a completed Task artifact.
version: 1.0.0
skills:
- id: echo
name: Echo
description: Returns the supplied text with an Echo prefix.
tags:
- echo
examples:
- hello
container:
resources:
cpu: '0.5'
memory: 1Gi
infra:
provider: microsoft.foundry
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv/
__pycache__/
*.pyc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv/
__pycache__/
*.pyc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv/
__pycache__/
vendor/*.whl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright (c) Microsoft. All rights reserved.

"""Native A2A v1 echo agent hosted by Azure AI Agent Server."""

import logging
import os

from a2a.helpers import get_message_text, new_task_from_user_message, new_text_message, new_text_part
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore, TaskUpdater
from a2a.types import AgentCapabilities, AgentCard, AgentInterface, AgentSkill, TaskState

from azure.ai.agentserver.a2a import A2AAgentServerHost

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s | %(message)s")
logger = logging.getLogger("a2a-echo-agent")


class EchoAgentExecutor(AgentExecutor):
"""Echo the incoming A2A message as a completed Task artifact."""

async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
if context.message is None:
raise ValueError("A2A request did not contain a message")

task = context.current_task
if task is None:
task = new_task_from_user_message(context.message)
await event_queue.enqueue_event(task)

text = get_message_text(context.message)
reply = f"Echo: {text}"
updater = TaskUpdater(
event_queue=event_queue,
task_id=task.id,
context_id=task.context_id,
)
await updater.add_artifact(parts=[new_text_part(reply, media_type="text/plain")])
await updater.update_status(
state=TaskState.TASK_STATE_COMPLETED,
message=new_text_message("Echo completed"),
)
logger.info("A2A_ECHO_COMPLETED task_id=%s context_id=%s", task.id, task.context_id)

async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
del context, event_queue
raise NotImplementedError("Cancellation is not supported by the echo sample")


agent_url = os.environ.get("A2A_AGENT_URL", "http://localhost:8088/invocations/a2a")
card = AgentCard(
name="Foundry A2A Echo Agent",
description="Echoes the user's A2A message as a completed Task artifact.",
version="1.0.0",
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
capabilities=AgentCapabilities(streaming=False, push_notifications=False),
supported_interfaces=[
AgentInterface(
protocol_binding="JSONRPC",
protocol_version="1.0",
url=agent_url,
)
],
skills=[
AgentSkill(
id="echo",
name="Echo",
description="Returns the supplied text with an Echo prefix.",
input_modes=["text/plain"],
output_modes=["text/plain"],
tags=["echo"],
examples=["hello"],
)
],
)
handler = DefaultRequestHandler(
agent_executor=EchoAgentExecutor(),
task_store=InMemoryTaskStore(),
agent_card=card,
)
host = A2AAgentServerHost(agent_card=card, request_handler=handler)


if __name__ == "__main__":
logger.info("Starting native A2A echo agent")
host.run()
Loading
Loading