Skip to content
Merged

a2a #17

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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- workflows for publishing develop and stable versions of documentation
- Orchestration configuration layer and behavior selection through a mode-specific strategy, preserves existing CLI, Gradio, and OpenAI API interfaces.
- `orchestration.py` , `orchestrator.py` large updates to support the new pattern selection layer
- Adds A2A capabilities, enabling the agent to connect with other agents and be accessed by them through A2A.

### Changed
- re-architected the test suite into unit/integration/e2e tests
Expand Down
37 changes: 37 additions & 0 deletions configs/agent_cards/mada_orchestrator_card.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "MADAOrchestrator",
"description": "MADA multi-agent orchestrator that coordinates local reasoning agents and delegates to remote A2A agents when their capabilities match the task.",
"version": "0.2.0",
"supportedInterfaces": [
{
"url": "http://localhost:9120",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"capabilities": {
"streaming": true
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
Comment thread
bgunnar5 marked this conversation as resolved.
{
"id": "mada-orchestration",
"name": "MADA orchestration",
"description": "Coordinate configured local MADA agents and remote A2A agents to answer delegated tasks.",
"tags": ["mada", "orchestration", "multi-agent", "a2a"]
},
{
"id": "csv-table-routing",
"name": "CSV table routing",
"description": "Route CSV table-reading and column-average requests to the appropriate remote A2A specialist.",
"tags": ["csv", "table", "average", "routing"]
},
{
"id": "answer-review",
"name": "Answer review",
"description": "Use local critique behavior to identify gaps, risky assumptions, and concrete improvements.",
"tags": ["review", "critique", "reasoning"]
}
]
}
51 changes: 51 additions & 0 deletions configs/example_a2a_agents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"model": {
"provider": "livai",
"model": "gpt-5.4",
"api_key": "${API_KEY}",
"base_url": "${API_BASE_URL:-https://livai-api.llnl.gov/v1}"
},
"agents": [
{
"agent_name": "LocalCoordinatorAgent",
"description": "Coordinates local reasoning and delegates to remote A2A agents when useful.",
"domain": "coordination",
"mcp_servers": [],
"instructions": "You are LocalCoordinatorAgent, a MADA specialist that handles local reasoning and helps the planning agent decide when to use remote A2A agents."
},
{
"agent_name": "LocalCritiqueAgent",
"description": "Reviews proposed answers, identifies gaps, and recommends improvements.",
"domain": "critique",
"mcp_servers": [],
"instructions": "You are LocalCritiqueAgent, a rigorous reviewer. Identify flaws, risky assumptions, missing context, and concrete improvements."
}
],
"a2a": {
"agents": {
"LangChainAgent": {
"url": "http://localhost:9111/",
"card_url": "http://localhost:9111/.well-known/agent-card.json"
},
"GoogleADKAgent": {
"url": "http://localhost:9112/",
"card_url": "http://localhost:9112/.well-known/agent-card.json"
}
},
"self": {
"card_path": "agent_cards/mada_orchestrator_card.json",
"url": "http://localhost:9120"
}
},
"orchestration": {
"mode": "agent-as-tool",
"participants": ["LocalCoordinatorAgent", "LocalCritiqueAgent"]
},
"interface": {
"title": "MADA A2A Agent Orchestrator",
"description": "Coordinate local MADA agents with remote A2A agents",
"chat_placeholder": "Ask MADA to solve a task or delegate to a remote A2A agent...",
"port": 7862,
"share": false
}
}
115 changes: 115 additions & 0 deletions docs/user_guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Additionally, there are optional configuration options:
- [Database Configuration](#optional-database-configuration)
- [Gradio Interface Configuration](#optional-gradio-interface-configuration)
- [Orchestration Configuration](#optional-orchestration-configuration)
- [A2A Configuration](#optional-a2a-configuration)

## Agent Configuration

Expand Down Expand Up @@ -229,6 +230,120 @@ it is the only supported mode.
If `participants` is omitted, MADA includes every configured agent except
`PlanningAgent`.

## (Optional) A2A Configuration

MADA can participate in Agent-to-Agent (A2A) workflows in two directions:

- `a2a.agents` is the client-side configuration. It lists remote A2A agents
that MADA can call as tools from the orchestrator.
- `a2a.self` is the server-side configuration. It describes MADA's own A2A
identity when you run MADA with `mada-a2a` so other agents can discover and
call MADA.

These settings do not replace CLI or Gradio. CLI and Gradio are interactive
interfaces for users. A2A mode starts an HTTP service so other A2A agents can
discover MADA and delegate tasks to it.

In code, the same split is reflected by the modules: `mada.core.a2a_client`
handles outbound calls from MADA to remote A2A agents, while
`mada.interfaces.a2a.main` exposes MADA itself as an inbound A2A service.

### Remote A2A Agents

Use `a2a.agents` when the MADA orchestrator should delegate work to other A2A
agents. Each configured remote agent is exposed to the planning agent as a tool,
using the remote agent card for routing context. MADA fails startup if a
configured remote A2A agent card cannot be fetched.

#### Fields

| Field Name | Description | Required? | Default |
| ------------- | --------------------------------------------------------------------------- | --------- | ------- |
| `url` | JSON-RPC endpoint for the remote A2A agent. | Yes | N/A |
| `card_url` | Explicit URL for the remote agent card. If omitted, MADA tries standard A2A card paths derived from `url`. | No | None |
| `timeout` | HTTP timeout in seconds for calls to the remote agent. | No | `180` |
| `api_key` | Optional API key sent as `x-api-key`. | No | None |
| `headers` | Additional HTTP headers to send to the remote agent. | No | `{}` |

#### Example

```json
"a2a": {
"agents": {
"LangChainAgent": {
"url": "http://localhost:9111/",
"card_url": "http://localhost:9111/.well-known/agent-card.json"
},
"GoogleADKAgent": {
"url": "http://localhost:9112/",
"card_url": "http://localhost:9112/.well-known/agent-card.json"
}
}
}
```

The example MCP servers are used inside the remote A2A agents, not as local
MADA MCP servers. The MADA orchestrator should report `0 MCP Servers` and `2
remote A2A agents` for this config. The remote agent card endpoints must be
reachable so MADA can discover each remote agent's skills. Install optional
dependencies and launch the MCP servers and A2A agents with the config path:

```bash
pip install -e ".[a2a-examples]"
python examples/a2a/a2a_table_mcp_server.py --port 9101
python examples/a2a/a2a_average_mcp_server.py --port 9102
python examples/a2a/a2a_langchain_agent.py --port 9111 --config configs/example_a2a_agents.json --mcp-url http://localhost:9101/mcp
python examples/a2a/a2a_google_adk_agent.py --port 9112 --config configs/example_a2a_agents.json --mcp-url http://localhost:9102/mcp
```

Use each example agent's `--model`, `--api-key`, and `--base-url` flags when
you want that remote agent to use a different model endpoint from MADA. The
Google ADK example also accepts `--provider`.

### MADA's A2A Agent Card

Use `a2a.self` when you want MADA itself to be discoverable by other A2A agents.
This block is used by `mada-a2a` and `mada a2a`; it is not used by CLI or Gradio
Comment thread
bgunnar5 marked this conversation as resolved.
mode. These are commands within this repo and not actual MADA repos like `mada-tools`.

The `card_path` value points to a standalone A2A agent card JSON file. Relative
paths are resolved relative to the configuration file. When the card is served,
MADA overwrites the card's `url` field with the runtime public URL from
`a2a.self.url` or `--public-url`, and advertises A2A protocol `1.0.0`.

#### Fields

| Field Name | Description | Required? | Default |
| ----------- | --------------------------------------------------------------------------- | --------- | ------- |
| `card_path` | Path to MADA's standalone A2A agent card JSON file. | No | None |
| `url` | Public URL advertised in the served agent card. | No | Runtime host and port |
| `name` | Name used by the generated card fallback when no `card_path` is supplied. | No | `MADA` |
| `description` | Description used by the generated card fallback when no `card_path` is supplied. | No | `MADA multi-agent orchestration service` |
| `skills` | Skills used by the generated card fallback when no `card_path` is supplied. | No | Derived from configured agents |

#### Example

```json
"a2a": {
"self": {
"card_path": "agent_cards/mada_orchestrator_card.json",
"url": "http://localhost:9120",
}
}
```

Launch MADA as an A2A service with:

```bash
mada-a2a --port 9120 configs/example_a2a_agents.json
```

Other A2A agents can then discover MADA at:

```text
http://localhost:9120/.well-known/agent-card.json
```

## (Optional) Database Configuration

If you want to customize your database settings, you can set this in the configuration file. There are two database options:
Expand Down
96 changes: 96 additions & 0 deletions examples/a2a/a2a_average_mcp_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Copyright 2026, Lawrence Livermore National Security, LLC and MADA contributors
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

"""
CSV column-average MCP server used by the Google ADK A2A example agent.
Comment thread
bgunnar5 marked this conversation as resolved.
This is separate from MADA as this only used for A2A examples.
"""

from __future__ import annotations

import argparse
import csv
from io import StringIO

from fastmcp import FastMCP


SAMPLE_CSV = """experiment,temperature_c,pressure_kpa
alpha,21.2,101.3
beta,24.8,99.8
gamma,19.6,103.1
delta,22.4,100.6
"""


def create_server() -> FastMCP:
mcp = FastMCP(name="A2A Column Average MCP Server")

@mcp.tool()
def calculate_column_averages(columns: str = "all") -> str:
"""
Calculate averages for numeric columns in a built-in CSV table.
"""
rows = list(csv.DictReader(StringIO(SAMPLE_CSV)))
numeric_columns = []
for column in rows[0]:
try:
for row in rows:
float(row[column])
except ValueError:
continue
numeric_columns.append(column)

if columns.strip().lower() != "all":
requested = [
column.strip()
for column in columns.split(",")
if column.strip() in numeric_columns
]
if not requested:
return (
"No requested numeric columns were found. "
f"Available numeric columns: {', '.join(numeric_columns)}."
)
numeric_columns = requested

lines = ["Column averages from the sample experiment table:"]
for column in numeric_columns:
values = [float(row[column]) for row in rows]
value = sum(values) / len(values)
lines.append(f"- {column}: {value:.2f}")
return "\n".join(lines)

return mcp


def main() -> None:
parser = argparse.ArgumentParser(
description="Run the A2A column-average MCP server"
)
parser.add_argument("--host", default="0.0.0.0", help="Host interface to bind")
parser.add_argument("--port", type=int, default=9102, help="Port to bind")
parser.add_argument(
"--transport",
choices=["stdio", "streamable-http"],
default="streamable-http",
help="MCP transport",
)
args = parser.parse_args()

server = create_server()
if args.transport == "stdio":
server.run(transport="stdio")
return

server.run(
transport="streamable-http",
host=args.host,
port=args.port,
stateless_http=True,
uvicorn_config={"access_log": False},
)


if __name__ == "__main__":
main()
Loading
Loading