Skip to content

A2A agent mesh: agent card (discovery) + A2A client bridge#82

Merged
lezama merged 4 commits into
mainfrom
feat/a2a-agent-mesh
Jun 3, 2026
Merged

A2A agent mesh: agent card (discovery) + A2A client bridge#82
lezama merged 4 commits into
mainfrom
feat/a2a-agent-mesh

Conversation

@lezama

@lezama lezama commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Phase 1 + 2 of the "A2A version of Studio" proposal — turning openclaWP from an A2A server into a peer that can both advertise itself and call other agents.

What's in here

Phase 1 — Discovery (agent card)

  • OpenclaWP_Agent_Card serves an A2A Agent Card at GET /openclawp/v1/agenttic/<slug>/.well-known/agent-card.json, derived from a registered agent: name, description, JSON-RPC endpoint, streaming capability, and skills derived from the agent's tools + subagents.

Phase 2 — A2A client + caller chain

  • OpenclaWP_A2a_Client_Transport — outbound message/send + fetch_card, emitting the canonical agents-api X-Agents-Api-* caller-chain headers (agents-api#81).
  • OpenclaWP_A2a_Client_Bridge — registers each openclawp_a2a_peers entry as an a2a/<slug> tool any local agent can call (mirrors OpenclaWP_Mcp_Client_Bridge).
  • Bridge receive side tags inbound peer calls as client_context.source = peer-agent / peer_agent_call = true, failing closed on malformed caller headers (agents-api#180).

This lets two local native-PHP sites delegate to each other over A2A with a proper caller chain — zero external infra.

Security

  • Agent card gated to manage_options by default (the card exposes the system prompt + tool inventory); opt into public discovery via openclawp_agent_card_permission. The card is never more permissive than the bridge endpoint it describes.
  • No untrusted-input SSRF: peer endpoints come from the openclawp_a2a_peers filter (admin/code-controlled); tool args control only the message body, never the URL. esc_url_raw applied.
  • No privilege escalation from spoofed caller headers: they're recorded as attribution only; authz still rests on the bridge's manage_options gate. Malformed headers fail closed.
  • Peer-tool invocation gated by manage_options (filterable).

Tests

  • tests/unit/AgentCardTest, A2aClientBridgeTest, A2aClientTransportTest (card shape + skill derivation, peer plan/validation, caller-header chain + Task parsing, including a round-trip through the real WP_Agent_Caller_Context).
  • tests/smoke.php — fetches a live card (gated → 401 unauth; 200 when opened via filter; 404 unknown agent) and registers a peer ability.

⚠️ Local PHPUnit/PHPCS PHARs emit zero bytes on this machine's PHP 8.4; verified via php -l, PHPStan, and a bootstrap-backed harness (29/29). CI runs the real gates.

🤖 Generated with Claude Code

lezama and others added 4 commits May 26, 2026 20:49
agents-api's WP_Agent_Message::toolCall() builds envelopes with
role='assistant' and content='Calling <tool_name>'. That makes sense
for inspector/debug surfaces, but for a chat channel the content is
internal-infrastructure leakage.

A Mantia user hit this on 2026-05-26 — a real WhatsApp bubble read
literally "Calling mantia__register-prediction" because the LLM
turn finished on a tool_call without a subsequent text reply.
last_assistant_text() walked the transcript backwards, found the
tool_call envelope (role=assistant), and surfaced its content as
the bot's answer.

Fix: filter by envelope \`type\`. tool_call / tool_result / delta /
error / approval_required / input_required / multimodal_part all
skip; only text envelopes (or untyped, defaulting to text) return.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds three interaction surfaces that downstream consumers (Mantia) now
need from the WhatsApp webhook + outbound payload builder:

Inbound:
- type=reaction → emit a virtual command
  whatsapp_reaction:<emoji>:<original_msg_id> so the consumer can
  route by emoji + map to message-specific actions.
- type=interactive nfm_reply → emit
  whatsapp_flow:<flow_name>:<json_payload> so the consumer can
  parse the form data and route by flow name.

Outbound:
- New type='flow' branch in build_interactive_payload — packages a
  flow_id + flow_token + flow_action_payload into the messaging
  envelope per Meta's spec.
- apply_interactive_header() centralizes header-shape resolution
  across button/list/flow payloads. Now supports:
    'header' => 'string'             → text header (capped 60)
    'header' => ['text' => '…']      → text header
    'header' => ['image' => 'url']   → image header (link)
    'header' => ['image_id' => '…']  → image header (media id)

No public API removals; consumers passing the old string-only
'header' shape stay unaffected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 1 + 2 of the "A2A version of Studio" proposal (docs/a2a-studio-proposal.md).

Phase 1 — Discovery:
- OpenclaWP_Agent_Card serves an A2A Agent Card at
  /openclawp/v1/agenttic/<slug>/.well-known/agent-card.json, derived from a
  registered agent (skills from tools + subagents, streaming capability,
  JSON-RPC endpoint). Gated to manage_options by default — the card exposes
  the system prompt + tool inventory — with openclawp_agent_card_permission
  to opt into public discovery.

Phase 2 — A2A client + caller chain:
- OpenclaWP_A2a_Client_Transport: outbound message/send + fetch_card, emitting
  the canonical agents-api X-Agents-Api-* caller-chain headers (#81).
- OpenclaWP_A2a_Client_Bridge: registers each openclawp_a2a_peers entry as an
  a2a/<slug> tool any local agent can call (mirrors the MCP client bridge).
- Agenttic bridge receive side tags inbound peer calls as client_context
  source=peer-agent (peer_agent_call=true), failing closed on malformed
  caller headers (#180).

Covered by tests/unit/ (AgentCard, A2aClientBridge, A2aClientTransport) and a
tests/smoke.php section that asserts the card is gated, serves when opened via
filter, 404s for unknown agents, and registers a peer ability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lezama lezama merged commit 74ffcb0 into main Jun 3, 2026
6 checks passed
@lezama lezama deleted the feat/a2a-agent-mesh branch June 3, 2026 20:04
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.

1 participant