Files
hermes-agent/plugins/platforms/a2a/plugin.yaml
T
Kevin (OpenClaw Bot) 41c406e1ab feat(a2a): v1.0 upgrade + full code review fixes
Fable 5 pass: 40 turns, $13.56, 109k output tokens.

## A2A v1.0 upgrade
- SCREAMING_SNAKE task states (TASK_STATE_COMPLETED etc)
- ROLE_USER/ROLE_AGENT message roles
- Unified Parts (no kind field, member-presence discrimination)
- Agent Card: supportedInterfaces[], provider, capabilities.extendedAgentCard
- SSE: member-discriminated statusUpdate/artifactUpdate, closure=terminal
- contextId inside Message (not top-level params)
- ISO 8601 millisecond timestamps, createdAt/lastModified on Task
- New operations: tasks/list, tasks/subscribe
- input-required state reachable via [INPUT_REQUIRED] hint

## Security & correctness (all must-fix from review)
- Slash-command bypass removed — remote peers can't invoke operator commands
- Per-peer token auth (A2A_PEER_TOKENS) replaces self-asserted params.peer
- _pending_replies keyed by task_id with per-context FIFO (no cross-talk)
- Timeout returns TASK_STATE_FAILED, not completed
- reset_turns uses task's context from store (was silent no-op)
- Error codes: spec codes only for spec semantics, custom -32050..-32052
- Real latency metric (was fake 0.0)

## Dead features wired
- Push notifications: inline configuration.taskPushNotificationConfig in
  message/send + tasks/pushNotificationConfig/create. HMAC-signed e2e.
- Dynamic Agent Cards: skills from live tools.registry, A2A_ADVERTISED_TOOLSETS
- Persistence: new a2a_history(context_id) tool recalls conversations
- Dead helpers cut: rate_limit_status, is_open_mode, verify_push_signature,
  turn_count, check_bearer

## Architecture
- TurnTracker/RateLimiter/TaskStore on adapter instance (was module-global)
- Handler class at module level (was untestable closure)
- on_processing_complete for failure/cancel paths
- SSE hang fix: keepalive header no longer prevents socket closure

## a2a_orchestrate kept per user instruction
- best mode: only successful replies considered (long error can't win)
- all-error case: explicit 'All peers failed' listing
- Client paths deduped into _send_task helper

## Tests
- inspect.getsource() tests replaced with behavioral coverage
- 133 total: 118 unit + 15 integration
- v1.0 spec compliance, peer-token auth, FIFO replies, timeout→FAILED,
  tasks/get-after-complete, streaming SSE parse, subscribe replay,
  anti-loop rejection, 429s, push e2e, input-required e2e, orchestrate

## Docs
- DESIGN.md out-of-scope synced with reality
- README and plugin.yaml updated

Still TODO (in DESIGN.md): file/data Parts, push-config get/list/delete,
tenant, gRPC/HTTP+JSON bindings, true mid-turn task abort.
2026-08-02 15:10:15 -07:00

60 lines
2.9 KiB
YAML

name: a2a-platform
label: A2A
kind: platform
version: 1.0.0
description: >
A2A (Agent-to-Agent) protocol v1.0 support for Hermes Agent — both directions
of the open Linux Foundation standard for inter-agent communication.
OUTBOUND (client tools): a2a_discover, a2a_call, a2a_list, a2a_history, and
a2a_orchestrate let the agent fetch another agent's Agent Card and send it
tasks over JSON-RPC — works with any A2A-compliant peer (Hermes, LangChain,
CrewAI, Google ADK, OpenClaw, ...).
INBOUND (platform adapter): exposes Hermes as an A2A-discoverable agent. An
Agent Card is served at /.well-known/agent.json and incoming tasks are routed
into the agent's live gateway session like any other platform — so the agent
that replies is the same one talking to its user, with full memory and
context, not a throwaway clone.
Security is on by default: no bearer token configured => localhost-only bind.
Inbound task text passes through prompt-injection filters; outbound text is
scrubbed of credential-shaped strings; every exchange is audit-logged and
persisted to disk outside the context-compaction pipeline so conversations
survive compaction and restarts.
Pure stdlib transport (http.server + urllib) — no a2a-sdk dependency required.
author: Nous Research
# requires_env / optional_env are surfaced in the `hermes config` UI via the
# platform-plugin env var injector in hermes_cli/config.py.
requires_env: []
optional_env:
- name: A2A_PEER_TOKENS
description: "Per-peer bearer tokens ('alice:tok1,bob:tok2'). Each remote agent gets its own credential; the matched name is the authenticated identity used for rate limiting, trust, and audit."
prompt: "A2A per-peer tokens (name:token, comma-separated; or empty)"
password: true
- name: A2A_BEARER_TOKEN
description: "Shared bearer token for inbound A2A calls (identity falls back to caller IP). With no token of any kind => bind to 127.0.0.1 only (no remote access)."
prompt: "A2A shared bearer token (or empty for localhost-only)"
password: true
- name: A2A_HOST
description: "Inbound bind host. Defaults to 127.0.0.1; only widens to 0.0.0.0 when a bearer token is set AND you opt in here."
prompt: "A2A bind host (default 127.0.0.1)"
password: false
- name: A2A_PORT
description: "Inbound A2A server port (default 9900)."
prompt: "A2A port (default 9900)"
password: false
- name: A2A_AGENT_NAME
description: "Name advertised on this agent's Agent Card (default: hostname-derived)."
prompt: "A2A agent name"
password: false
- name: A2A_ALLOW_ALL_USERS
description: "Allow any authenticated A2A peer to reach the agent (dev only)."
prompt: "Allow all A2A peers? (true/false)"
password: false
- name: A2A_HOME_CHANNEL
description: "Task/context id used as the cron / notification delivery target for deliver=a2a."
prompt: "A2A home channel (or empty)"
password: false