Skip to content

Commit 6afc62d

Browse files
are-cesclaude
andcommitted
LCORE-2914: add A2A client support for external agent delegation
Enable LCS to discover and delegate tasks to external A2A-compliant agents. The delegation is exposed as a pydantic-ai capability, so any agent-powered endpoint (query, streaming, A2A) can use it. - A2AAgentsConfiguration: YAML config for remote agent endpoints with timeout, max_retries, and optional auth_token - A2AClientManager: singleton that resolves agent cards at startup and holds Client instances with httpx timeout/retry transport - A2ADelegationCapability: pydantic-ai tools (list_agents, delegate_to_agent) auto-injected into build_agent() - Startup/shutdown lifecycle wired into FastAPI lifespan - A2A protocol docs updated with client documentation - OpenAPI schema regenerated - Example config: examples/lightspeed-stack-a2a-agents.yaml Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 938dcac commit 6afc62d

14 files changed

Lines changed: 1104 additions & 4 deletions

File tree

‎docs/devel_doc/openapi.json‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10874,6 +10874,74 @@
1087410874
},
1087510875
"components": {
1087610876
"schemas": {
10877+
"A2AAgentEndpointConfiguration": {
10878+
"properties": {
10879+
"name": {
10880+
"type": "string",
10881+
"title": "Agent name",
10882+
"description": "Unique identifier for this agent, used by the delegation tool."
10883+
},
10884+
"url": {
10885+
"type": "string",
10886+
"minLength": 1,
10887+
"format": "uri",
10888+
"title": "Agent URL",
10889+
"description": "Base URL of the external A2A agent."
10890+
},
10891+
"auth_token": {
10892+
"anyOf": [
10893+
{
10894+
"type": "string",
10895+
"format": "password",
10896+
"writeOnly": true
10897+
},
10898+
{
10899+
"type": "null"
10900+
}
10901+
],
10902+
"title": "Auth token",
10903+
"description": "Static bearer token for authenticating with this agent."
10904+
},
10905+
"timeout": {
10906+
"type": "integer",
10907+
"exclusiveMinimum": 0.0,
10908+
"title": "Request timeout",
10909+
"description": "Timeout in seconds for requests to this agent. Default is 30 seconds.",
10910+
"default": 30
10911+
},
10912+
"max_retries": {
10913+
"type": "integer",
10914+
"minimum": 0.0,
10915+
"title": "Maximum retries",
10916+
"description": "Maximum number of retry attempts on transient failures. Set to 0 to disable retries.",
10917+
"default": 3
10918+
}
10919+
},
10920+
"additionalProperties": false,
10921+
"type": "object",
10922+
"required": [
10923+
"name",
10924+
"url"
10925+
],
10926+
"title": "A2AAgentEndpointConfiguration",
10927+
"description": "Configuration for a single external A2A agent endpoint.\n\nAttributes:\n name: Agent identifier used in delegation tool calls.\n url: Base URL of the A2A agent.\n auth_token: Optional static bearer token for authenticating with this agent.\n timeout: Request timeout in seconds for this agent.\n max_retries: Maximum retry attempts on transient failures."
10928+
},
10929+
"A2AAgentsConfiguration": {
10930+
"properties": {
10931+
"agents": {
10932+
"items": {
10933+
"$ref": "#/components/schemas/A2AAgentEndpointConfiguration"
10934+
},
10935+
"type": "array",
10936+
"title": "A2A agent endpoints",
10937+
"description": "External A2A agents available for task delegation."
10938+
}
10939+
},
10940+
"additionalProperties": false,
10941+
"type": "object",
10942+
"title": "A2AAgentsConfiguration",
10943+
"description": "Configuration for external A2A agent connections.\n\nAttributes:\n agents: List of external A2A agent endpoint configurations."
10944+
},
1087710945
"A2AStateConfiguration": {
1087810946
"properties": {
1087910947
"sqlite": {
@@ -12322,6 +12390,18 @@
1232212390
"title": "A2A state configuration",
1232312391
"description": "Configuration for A2A protocol persistent state storage."
1232412392
},
12393+
"a2a_agents": {
12394+
"anyOf": [
12395+
{
12396+
"$ref": "#/components/schemas/A2AAgentsConfiguration"
12397+
},
12398+
{
12399+
"type": "null"
12400+
}
12401+
],
12402+
"title": "A2A clients configuration",
12403+
"description": "External A2A agents available for task delegation."
12404+
},
1232512405
"quota_handlers": {
1232612406
"$ref": "#/components/schemas/QuotaHandlersConfiguration",
1232712407
"title": "Quota handlers",

‎docs/user_doc/a2a_protocol.md‎

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ This document describes the A2A (Agent-to-Agent) protocol implementation in Ligh
44

55
## Overview
66

7-
The A2A protocol is an open standard for agent-to-agent communication that allows different AI agents to discover, communicate, and collaborate with each other. Lightspeed Core Stack implements the A2A protocol to expose its AI capabilities to other agents and systems.
7+
The A2A protocol is an open standard for agent-to-agent communication that allows different AI agents to discover, communicate, and collaborate with each other. Lightspeed Core Stack implements both sides of the A2A protocol:
8+
9+
- **A2A Server**: Other agents can call LCS to leverage its AI capabilities, MCP tools, and RAG
10+
- **A2A Client**: LCS can discover and delegate tasks to external A2A-compliant agents during inference
811

912
### Key Concepts
1013

@@ -310,8 +313,8 @@ The `A2AAgentExecutor` class implements the A2A `AgentExecutor` interface:
310313

311314
1. **Receives A2A Request**: Extracts user input from the A2A message
312315
2. **Creates Query Request**: Builds an internal `QueryRequest` with conversation context
313-
3. **Calls Llama Stack**: Uses the Responses API to get streaming responses
314-
4. **Converts Events**: Transforms Responses API streaming chunks to A2A events
316+
3. **Builds Pydantic-AI Agent**: Uses `build_agent()` with the same capabilities as the query/streaming endpoints (skills, A2A delegation)
317+
4. **Streams via Agent**: Runs `agent.run_stream_events()` and converts pydantic-ai events to A2A events
315318
5. **Manages State**: Tracks task state and publishes status updates
316319

317320
### Event Flow
@@ -786,8 +789,92 @@ protocolVersion: "0.3.0"
786789

787790
The protocol version is included in the agent card response and indicates which version of the A2A protocol specification the agent implements.
788791

792+
## A2A Client: Delegating to Remote Agents
793+
794+
LCS can also act as an **A2A client**, discovering and delegating tasks to external A2A-compliant agents during inference. This enables multi-agent orchestration where a single LCS instance coordinates work across specialized agents.
795+
796+
### Architecture
797+
798+
```text
799+
┌──────────────────────────────────────────────────────┐
800+
│ LCS Orchestrator │
801+
│ │
802+
│ ┌──────────────┐ ┌──────────────────────────────┐ │
803+
│ │ build_agent()├─▶│ A2ADelegationCapability │ │
804+
│ └──────────────┘ │ - list_agents() │ │
805+
│ │ - delegate_to_agent(name, │ │
806+
│ │ task) │ │
807+
│ └──────────────┬───────────────┘ │
808+
└──────────────────────────────────┼───────────────────┘
809+
│ A2A protocol
810+
┌──────────────┼──────────────┐
811+
▼ ▼ ▼
812+
┌────────────┐ ┌──────────┐ ┌──────────────┐
813+
│ Remote │ │ Remote │ │ Remote │
814+
│ Agent A │ │ Agent B │ │ Agent ... │
815+
│ (any A2A │ │ │ │ │
816+
│ server) │ │ │ │ │
817+
└────────────┘ └──────────┘ └──────────────┘
818+
```
819+
820+
### Configuration
821+
822+
Add an `a2a_agents` section to `lightspeed-stack.yaml`:
823+
824+
```yaml
825+
a2a_agents:
826+
agents:
827+
- name: "openshift-agent"
828+
url: "http://openshift-ls.example.com:8082"
829+
timeout: 30 # seconds (default: 30)
830+
max_retries: 3 # retry on transient failures (default: 3)
831+
832+
- name: "ansible-agent"
833+
url: "http://ansible-ls.example.com:8081"
834+
auth_token: "${ANSIBLE_AGENT_TOKEN}" # optional bearer token
835+
836+
- name: "rhel-agent"
837+
url: "http://rhel-ls.example.com:8083"
838+
```
839+
840+
#### Configuration Fields
841+
842+
| Field | Type | Default | Description |
843+
|-------|------|---------|-------------|
844+
| `name` | string | required | Identifier used by the LLM to select this agent |
845+
| `url` | string | required | Base URL of the remote A2A agent |
846+
| `auth_token` | string | none | Static bearer token for authentication |
847+
| `timeout` | int | 30 | Request timeout in seconds |
848+
| `max_retries` | int | 3 | Retry attempts on connection failures |
849+
850+
### How It Works
851+
852+
1. **Startup**: LCS connects to each configured agent, fetches its agent card, and caches its capabilities.
853+
854+
2. **Tool registration**: An `A2ADelegationCapability` is added to the pydantic-ai agent (used by query, streaming, and A2A endpoints) with two tools:
855+
- `list_agents()` — returns agent names and descriptions
856+
- `delegate_to_agent(agent_name, task)` — sends a task to a remote agent via A2A and returns its response
857+
858+
3. **Runtime**: The LLM decides when to delegate based on the agent descriptions injected into the system prompt. It can delegate to one or multiple agents per turn.
859+
860+
4. **Parallel delegation**: If the LLM emits multiple `delegate_to_agent` calls in one response, they run concurrently.
861+
862+
5. **Error handling**: If a remote agent is unavailable or times out, the tool returns an error message to the LLM, which can try another agent or answer directly.
863+
864+
### Authentication
865+
866+
The orchestrator authenticates to remote agents using **service account credentials** (the `auth_token` field), not end-user tokens. Each remote agent validates the token using its own authentication module.
867+
868+
| Scenario | Configuration |
869+
|----------|---------------|
870+
| No auth (internal network) | Omit `auth_token` |
871+
| Static bearer token | Set `auth_token` in YAML |
872+
| Environment variable | `auth_token: "${MY_AGENT_TOKEN}"` |
873+
789874
## References
790875
791-
- [A2A Protocol Specification](https://github.com/google/A2A)
876+
- [A2A Protocol Specification](https://a2a-protocol.org/latest/)
877+
- [a2a-sdk (Python)](https://pypi.org/project/a2a-sdk/)
878+
- [A2A Samples & Test Suite](https://github.com/a2aproject/a2a-samples)
792879
- [Llama Stack Documentation](https://llama-stack.readthedocs.io/)
793880
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Example: A2A remote agent delegation
2+
#
3+
# Configures LCS as an orchestrator that can delegate tasks to external
4+
# A2A-compliant agents. The LLM decides when to delegate based on agent
5+
# descriptions from their agent cards.
6+
#
7+
# See docs/a2a_protocol.md for details.
8+
9+
name: Lightspeed Orchestrator
10+
service:
11+
host: 0.0.0.0
12+
port: 8080
13+
auth_enabled: false
14+
llama_stack:
15+
url: http://localhost:8321
16+
api_key: ${env.LLAMA_STACK_API_KEY}
17+
user_data_collection:
18+
feedback_enabled: false
19+
authentication:
20+
module: "noop"
21+
inference:
22+
default_provider: openai
23+
default_model: gpt-4o-mini
24+
25+
# Remote A2A agents available for task delegation.
26+
# At startup, LCS connects to each agent, fetches its agent card,
27+
# and registers delegation tools with the pydantic-ai agent.
28+
a2a_agents:
29+
agents:
30+
- name: "openshift-agent"
31+
url: "http://openshift-ls:8082"
32+
timeout: 30
33+
max_retries: 3
34+
35+
- name: "ansible-agent"
36+
url: "http://ansible-ls:8081"
37+
auth_token: "${env.ANSIBLE_AGENT_TOKEN}"
38+
timeout: 60
39+
40+
- name: "rhel-agent"
41+
url: "http://rhel-ls:8083"

‎src/a2a_client/__init__.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""A2A client module for discovering and delegating tasks to external agents."""
2+
3+
from a2a_client.capability import A2ADelegationCapability
4+
from a2a_client.manager import A2AClientManager
5+
6+
__all__ = [
7+
"A2AClientManager",
8+
"A2ADelegationCapability",
9+
]

0 commit comments

Comments
 (0)