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
80 changes: 80 additions & 0 deletions docs/devel_doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10874,6 +10874,74 @@
},
"components": {
"schemas": {
"A2AAgentEndpointConfiguration": {
"properties": {
"name": {
"type": "string",
"title": "Agent name",
"description": "Unique identifier for this agent, used by the delegation tool."
},
"url": {
"type": "string",
"minLength": 1,
"format": "uri",
"title": "Agent URL",
"description": "Base URL of the external A2A agent."
},
"auth_token": {
"anyOf": [
{
"type": "string",
"format": "password",
"writeOnly": true
},
{
"type": "null"
}
],
"title": "Auth token",
"description": "Static bearer token for authenticating with this agent."
},
"timeout": {
"type": "integer",
"exclusiveMinimum": 0.0,
"title": "Request timeout",
"description": "Timeout in seconds for requests to this agent. Default is 30 seconds.",
"default": 30
},
"max_retries": {
"type": "integer",
"minimum": 0.0,
"title": "Maximum retries",
"description": "Maximum number of retry attempts on transient failures. Set to 0 to disable retries.",
"default": 3
}
},
"additionalProperties": false,
"type": "object",
"required": [
"name",
"url"
],
"title": "A2AAgentEndpointConfiguration",
"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."
},
"A2AAgentsConfiguration": {
"properties": {
"agents": {
"items": {
"$ref": "#/components/schemas/A2AAgentEndpointConfiguration"
},
"type": "array",
"title": "A2A agent endpoints",
"description": "External A2A agents available for task delegation."
}
},
"additionalProperties": false,
"type": "object",
"title": "A2AAgentsConfiguration",
"description": "Configuration for external A2A agent connections.\n\nAttributes:\n agents: List of external A2A agent endpoint configurations."
},
"A2AStateConfiguration": {
"properties": {
"sqlite": {
Expand Down Expand Up @@ -12322,6 +12390,18 @@
"title": "A2A state configuration",
"description": "Configuration for A2A protocol persistent state storage."
},
"a2a_agents": {
"anyOf": [
{
"$ref": "#/components/schemas/A2AAgentsConfiguration"
},
{
"type": "null"
}
],
"title": "A2A clients configuration",
"description": "External A2A agents available for task delegation."
},
"quota_handlers": {
"$ref": "#/components/schemas/QuotaHandlersConfiguration",
"title": "Quota handlers",
Expand Down
95 changes: 91 additions & 4 deletions docs/user_doc/a2a_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ This document describes the A2A (Agent-to-Agent) protocol implementation in Ligh

## Overview

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.
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:

- **A2A Server**: Other agents can call LCS to leverage its AI capabilities, MCP tools, and RAG
- **A2A Client**: LCS can discover and delegate tasks to external A2A-compliant agents during inference

### Key Concepts

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

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

### Event Flow
Expand Down Expand Up @@ -786,8 +789,92 @@ protocolVersion: "0.3.0"

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

## A2A Client: Delegating to Remote Agents

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.

### Architecture

```text
┌──────────────────────────────────────────────────────┐
│ LCS Orchestrator │
│ │
│ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │ build_agent()├─▶│ A2ADelegationCapability │ │
│ └──────────────┘ │ - list_agents() │ │
│ │ - delegate_to_agent(name, │ │
│ │ task) │ │
│ └──────────────┬───────────────┘ │
└──────────────────────────────────┼───────────────────┘
│ A2A protocol
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌──────────────┐
│ Remote │ │ Remote │ │ Remote │
│ Agent A │ │ Agent B │ │ Agent ... │
│ (any A2A │ │ │ │ │
│ server) │ │ │ │ │
└────────────┘ └──────────┘ └──────────────┘
```

### Configuration

Add an `a2a_agents` section to `lightspeed-stack.yaml`:

```yaml
a2a_agents:
agents:
- name: "openshift-agent"
url: "http://openshift-ls.example.com:8082"
timeout: 30 # seconds (default: 30)
max_retries: 3 # retry on transient failures (default: 3)

- name: "ansible-agent"
url: "http://ansible-ls.example.com:8081"
auth_token: "${ANSIBLE_AGENT_TOKEN}" # optional bearer token

- name: "rhel-agent"
url: "http://rhel-ls.example.com:8083"
```

#### Configuration Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | string | required | Identifier used by the LLM to select this agent |
| `url` | string | required | Base URL of the remote A2A agent |
| `auth_token` | string | none | Static bearer token for authentication |
| `timeout` | int | 30 | Request timeout in seconds |
| `max_retries` | int | 3 | Retry attempts on connection failures |

### How It Works

1. **Startup**: LCS connects to each configured agent, fetches its agent card, and caches its capabilities.

2. **Tool registration**: An `A2ADelegationCapability` is added to the pydantic-ai agent (used by query, streaming, and A2A endpoints) with two tools:
- `list_agents()` — returns agent names and descriptions
- `delegate_to_agent(agent_name, task)` — sends a task to a remote agent via A2A and returns its response

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.

4. **Parallel delegation**: If the LLM emits multiple `delegate_to_agent` calls in one response, they run concurrently.

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.

### Authentication

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.

| Scenario | Configuration |
|----------|---------------|
| No auth (internal network) | Omit `auth_token` |
| Static bearer token | Set `auth_token` in YAML |
| Environment variable | `auth_token: "${MY_AGENT_TOKEN}"` |

## References

- [A2A Protocol Specification](https://github.com/google/A2A)
- [A2A Protocol Specification](https://a2a-protocol.org/latest/)
- [a2a-sdk (Python)](https://pypi.org/project/a2a-sdk/)
- [A2A Samples & Test Suite](https://github.com/a2aproject/a2a-samples)
- [Llama Stack Documentation](https://llama-stack.readthedocs.io/)
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
41 changes: 41 additions & 0 deletions examples/lightspeed-stack-a2a-agents.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Example: A2A remote agent delegation
#
# Configures LCS as an orchestrator that can delegate tasks to external
# A2A-compliant agents. The LLM decides when to delegate based on agent
# descriptions from their agent cards.
#
# See docs/a2a_protocol.md for details.

name: Lightspeed Orchestrator
service:
host: 0.0.0.0
port: 8080
auth_enabled: false
llama_stack:
url: http://localhost:8321
api_key: ${env.LLAMA_STACK_API_KEY}
user_data_collection:
feedback_enabled: false
authentication:
module: "noop"
inference:
default_provider: openai
default_model: gpt-4o-mini

# Remote A2A agents available for task delegation.
# At startup, LCS connects to each agent, fetches its agent card,
# and registers delegation tools with the pydantic-ai agent.
a2a_agents:
agents:
- name: "openshift-agent"
url: "http://openshift-ls:8082"
timeout: 30
max_retries: 3

- name: "ansible-agent"
url: "http://ansible-ls:8081"
auth_token: "${env.ANSIBLE_AGENT_TOKEN}"
timeout: 60

- name: "rhel-agent"
url: "http://rhel-ls:8083"
9 changes: 9 additions & 0 deletions src/a2a_client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""A2A client module for discovering and delegating tasks to external agents."""

from a2a_client.capability import A2ADelegationCapability
from a2a_client.manager import A2AClientManager

__all__ = [
"A2AClientManager",
"A2ADelegationCapability",
]
Comment on lines +3 to +9

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep this package initializer description-only.

Move these imports to their consumers and remove __all__.

As per coding guidelines, package __init__.py files must be limited to brief package descriptions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/a2a_client/__init__.py` around lines 3 - 9, Update the package
initializer by removing the A2ADelegationCapability and A2AClientManager imports
and deleting __all__; move those imports into each consuming module so this
__init__.py contains only a brief package description.

Source: Coding guidelines

Loading
Loading