Skip to content

feat(tools): implement A2A client tools and server (closes #514 phases 1 & 2)#4952

Closed
balaji-embedcentrum wants to merge 20 commits into
NousResearch:mainfrom
balaji-embedcentrum:feat/a2a-client-server-implementation
Closed

feat(tools): implement A2A client tools and server (closes #514 phases 1 & 2)#4952
balaji-embedcentrum wants to merge 20 commits into
NousResearch:mainfrom
balaji-embedcentrum:feat/a2a-client-server-implementation

Conversation

@balaji-embedcentrum

Copy link
Copy Markdown

Summary

Closes #514 — implements Phases 1 and 2 of the A2A (Agent-to-Agent) protocol support.

Phase 1 — A2A Client (tools/a2a_tool.py)

Three new tools in the a2a toolset:

  • a2a_discover — fetch an Agent Card from any A2A endpoint; results cached for 5 min to avoid redundant fetches
  • a2a_call — send a task to a remote A2A agent; auto-detects streaming capability from the cached Agent Card and uses tasks/sendSubscribe (SSE) when supported, falling back to tasks/send
  • a2a_local_scan — scan a localhost port range (default 9000–9010) to discover running A2A agents without any pre-configuration; closes the discovery gap identified in the issue

Named agents can be pre-configured in ~/.hermes/config.yaml:

a2a_agents:
  researcher:
    url: http://192.168.1.100:9000
    bearer_token: "optional"

Phase 2 — A2A Server (a2a_adapter/)

Exposes Hermes as a standards-compliant A2A agent over HTTP:

  • HermesAgentExecutor wraps AIAgent.run_conversation() with full SSE streaming support
  • Bearer token auth via A2A_KEY env var
  • Agent Card built from env vars (AGENT_NAME, AGENT_SKILLS, AGENT_DESCRIPTION, etc.)
  • Runs as a separate uvicorn process on configurable port (default 9000)
pip install 'hermes-agent[a2a]'
AGENT_NAME=myagent AGENT_SKILLS="coding,analysis" hermes-a2a

Supporting changes

  • pyproject.toml[a2a] optional extra, hermes-a2a CLI entry point, a2a_adapter in packages
  • toolsets.pya2a toolset definition + all 3 tools registered in _HERMES_CORE_TOOLS

Docs & Tests

  • docs/a2a-user-guide.md — end-user guide covering server setup, client tools, multi-agent workflows, auth, and troubleshooting
  • docs/a2a-architecture.md — internal design, data flow diagrams, extension points
  • tests/tools/test_a2a_tool.py — unit tests for all 3 tools using mocks (no real servers needed); covers caching, SSE streaming, named agent resolution, error handling

What's not in this PR (Phase 3)

Multi-agent orchestration patterns (agent registry, automatic agent selection, fan-out workflows) remain open per the original issue roadmap.

How to test manually

1. Start the A2A server:

pip install 'hermes-agent[a2a]'
AGENT_NAME=myagent AGENT_SKILLS="coding,analysis,reasoning" hermes-a2a

2. Verify the Agent Card:

curl http://localhost:9000/.well-known/agent.json | jq

3. Send a test task:

curl -s -X POST http://localhost:9000 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"tasks/send","params":{"id":"t1","message":{"role":"user","parts":[{"type":"text","text":"what is 2+2?"}]}}}' \
  | jq .result.artifacts[0].parts[0].text

4. Test client tools in chat:

hermes chat --toolsets hermes-core,a2a
# "find all local agents"                              → a2a_local_scan
# "discover the agent at http://localhost:9000"        → a2a_discover
# "ask the agent at http://localhost:9000 what is 2+2" → a2a_call

5. Run unit tests:

pytest tests/tools/test_a2a_tool.py -v

Platforms tested

  • macOS (Darwin 25.3.0) — server started, Agent Card served correctly, tasks/send returned completed response, SSE streaming verified end-to-end against live deployment

Test plan

  • pip install 'hermes-agent[a2a]' installs without errors
  • hermes-a2a starts and serves Agent Card at /.well-known/agent.json
  • tasks/send returns a completed response
  • tasks/sendSubscribe streams SSE events correctly
  • a2a_discover, a2a_call, a2a_local_scan available in chat with a2a toolset enabled
  • pytest tests/tools/test_a2a_tool.py passes in full dev environment
  • Tested on Linux

Related issues

Closes #514 — related: #344, #342, #413

…ch#514 phases 1 & 2)

Phase 1 — A2A Client (tools/a2a_tool.py):
- Add a2a_discover: fetch and cache Agent Cards from remote A2A endpoints
- Add a2a_call: send tasks via tasks/send (non-streaming) or
  tasks/sendSubscribe (SSE streaming), auto-detected from Agent Card
- Add a2a_local_scan: scan localhost port range to discover running A2A
  agents without pre-configuration
- Agent card caching (5 min TTL) shared across discover and call
- Named agent resolution from ~/.hermes/config.yaml a2a_agents section

Phase 2 — A2A Server (a2a_adapter/):
- HermesAgentExecutor wrapping AIAgent.run_conversation()
- Streaming via SSE (tasks/sendSubscribe) with delta queue bridge
- Bearer token auth via A2A_KEY env var
- Agent Card built from env vars (AGENT_NAME, AGENT_SKILLS, etc.)
- Separate uvicorn process on configurable port (default 9000)
- CLI entry point: hermes-a2a / python -m a2a_adapter

Supporting changes:
- pyproject.toml: [a2a] extra, hermes-a2a entry point, a2a_adapter package
- toolsets.py: a2a toolset definition + all 3 tools in _HERMES_CORE_TOOLS

Docs & tests:
- docs/a2a-user-guide.md: end-user guide for server and client usage
- docs/a2a-architecture.md: internal design, data flow, extension points
- tests/tools/test_a2a_tool.py: unit tests for all 3 tools using mocks
@balaji-embedcentrum balaji-embedcentrum force-pushed the feat/a2a-client-server-implementation branch from bf88439 to f858348 Compare April 6, 2026 20:13
balaji-embedcentrum and others added 19 commits April 6, 2026 18:40
- Slim Dockerfile: drops playwright/nodejs/ffmpeg, adds filebrowser binary
  and supervisor; installs only a2a+mcp extras (~60% smaller image)
- supervisord manages 3 processes per container: hermes gateway (8642),
  A2A server (9000), filebrowser UI (8080)
- entrypoint: persona selection via AGENT_PERSONA env var, filebrowser
  first-run init with FB_PASSWORD, A2A_KEY warning if unset
- docker/config.yaml: api_server platform + a2a toolset + all 30 agents
  pre-wired with bearer tokens for cross-agent A2A calls
- docker-compose.yml: 30 services (HP/Marvel/Office), individual port
  triplets (80xx/90xx/70xx), 768MB/0.25CPU limits per container
- 30 persona SOUL.md files: Harry Potter x10, Marvel x10, Office x10
  (Maxwell/Toby/Claire/Alex/Frank/Maya/Linda/Raj/Derek/Eleanor)
- .env.fleet.example: template for 60 secrets (30 A2A keys + 30 FB passwords)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- hermes gateway needs --platform api_server flag to serve the
  OpenAI-compatible HTTP API on :8642; without it only runs
  messaging gateway (Telegram/Discord) with nothing on the port
- GATEWAY_ALLOW_ALL_USERS=true required so API server accepts
  requests without an allowlist configured

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- hermes gateway does not accept --platform flag; API server is
  enabled via API_SERVER_ENABLED=true env var instead
- correct subcommand is 'hermes gateway run' not 'hermes gateway'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The agent card was hardcoding 'http://localhost:{port}' which breaks
orchestrators that discover the agent and call back using the URL in
the card. A2A_PUBLIC_URL env var now sets the correct external URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A2A SDK 0.3.x renamed the event id field to taskId and added a
required contextId field on TaskStatusUpdateEvent and
TaskArtifactUpdateEvent. Updated all event emissions to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A2A SDK 0.3.x requires artifactId inside the artifact dict on
TaskArtifactUpdateEvent. Added to both streaming and non-streaming
artifact emissions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire tool_gen_callback to push tool call names into the delta queue as
dict events. These are forwarded to the A2A event queue as data artifact
parts so the Akela orchestrator can display tool steps in real time.
Add _extract_message_content() that returns a list of Anthropic-format
content blocks when image file parts are present. Text-only messages
still return a plain string. Images from Akela attachment parts are
forwarded to run_conversation as multimodal content so the agent can
see them directly.
Tenants need to browse all agent data (sessions, logs, config, memories)
not just the empty files/ upload directory.
- Walks workspace directory for all 21 Sylang file extensions
- Returns all file contents in one JSON response (batch delivery for hermes web app)
- 5-min per-repo cache, auto-invalidated on file writes
- POST /ws/{repo}/symbols/invalidate for manual cache bust
Needed so hermes can run git commands inside the container (used by
the new /ws/* workspace API routes and git/log, git/files endpoints).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r-implementation

# Conflicts:
#	Dockerfile
#	docker/entrypoint.sh
#	pyproject.toml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: A2A (Agent-to-Agent) Protocol Support — Remote Agent Discovery, Communication & Interoperability

1 participant