From 103ac09aebdda3b96a3eff83ed3e9e3e80a081eb Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Fri, 1 May 2026 17:49:36 -0700 Subject: [PATCH] docs(a2a-mcp): list new envelope attrs in initialize instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent learns about tag attributes ONLY from the instructions string returned by initialize. Without this update the wheel ships peer_name / peer_role / agent_card_url on the wire but no agent ever uses them — they get printed inline in the push tag, the agent doesn't know they're there, and the UX gain from the enrichment is lost. Update _build_channel_instructions to: - List the new attrs in the tag template under PUSH PATH - Add per-attribute semantics (when present, what to do with them, what \"absent\" means — graceful-degrade vs bug) - Point at the discover endpoint for agent_card_url so the agent treats it as a follow-on URL not the body of the message Tests: structural pin asserting all three attr names appear in the instructions AND the per-field semantics phrases (\"registry resolved\", \"discover endpoint\") so a future copy-edit that shortens the prose can't silently drop the agent guidance. Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace/a2a_mcp_server.py | 15 +++++++++-- workspace/tests/test_a2a_mcp_server.py | 36 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/workspace/a2a_mcp_server.py b/workspace/a2a_mcp_server.py index 2c47655a..aaa10bcf 100644 --- a/workspace/a2a_mcp_server.py +++ b/workspace/a2a_mcp_server.py @@ -221,8 +221,9 @@ def _build_channel_instructions() -> str: "\n" "PUSH PATH (Claude Code with channel push enabled):\n" "Messages arrive as tags as a " - "synthetic user turn — no agent action needed to surface them.\n" + "peer_id=\"...\" peer_name=\"...\" peer_role=\"...\" " + "agent_card_url=\"...\" activity_id=\"...\" ts=\"...\"> tags as " + "a synthetic user turn — no agent action needed to surface them.\n" "\n" "POLL PATH (every other MCP client + Claude Code without push " "enabled — this is the universal default):\n" @@ -234,6 +235,16 @@ def _build_channel_instructions() -> str: "delegating to you).\n" "- `peer_id` is empty for canvas_user, set to the sender " "workspace UUID for peer_agent.\n" + "- `peer_name` and `peer_role` are present for peer_agent when " + "the platform registry resolved the sender — e.g. " + "`peer_name=\"ops-agent\"`, `peer_role=\"sre\"`. Surface these " + "in your reasoning so the user can tell which peer is talking " + "without having to memorise UUIDs. Absent on canvas_user and " + "on a registry-lookup failure (the push still delivers).\n" + "- `agent_card_url` is present for peer_agent and points at " + "the platform's discover endpoint for that peer — fetch it if " + "you need the peer's full capability list (skills, role, " + "runtime).\n" "- `activity_id` is the inbox row to acknowledge.\n" "\n" "Reply path:\n" diff --git a/workspace/tests/test_a2a_mcp_server.py b/workspace/tests/test_a2a_mcp_server.py index 5d625775..b9e68e80 100644 --- a/workspace/tests/test_a2a_mcp_server.py +++ b/workspace/tests/test_a2a_mcp_server.py @@ -704,6 +704,42 @@ def test_instructions_zero_timeout_means_push_only_mode(): os.environ["MOLECULE_MCP_POLL_TIMEOUT_SECS"] = saved +def test_instructions_document_envelope_enrichment_attrs(): + """The agent learns about envelope attributes ONLY from the + instructions string. PR-B added peer_name, peer_role, + agent_card_url to the wire shape; pin that the instructions list + them in the tag template AND describe each one's + semantics. Without this, the wheel ships new attributes that no + agent ever uses.""" + from a2a_mcp_server import _build_initialize_result + + instructions = _build_initialize_result()["instructions"] + + # The tag template in the PUSH PATH section must include + # the new attribute names so the agent recognises them when they + # arrive inline. + for attr in ("peer_name", "peer_role", "agent_card_url"): + assert attr in instructions, ( + f"instructions must list `{attr}` as a tag " + f"attribute — otherwise the agent sees the attr in pushes " + f"but doesn't know what to do with it" + ) + + # And the per-field semantics block must explain when each attr + # is present + what it means. These phrases are what the agent + # actually reads to decide how to surface the attrs in its turn. + assert "registry resolved" in instructions, ( + "instructions must explain peer_name/peer_role come from a " + "registry lookup that may fail — otherwise the agent treats " + "their absence as a bug instead of a graceful degrade" + ) + assert "discover endpoint" in instructions, ( + "instructions must point at the registry discover endpoint " + "for agent_card_url so the agent knows it's a follow-on URL " + "to fetch full capabilities, not the body of the message" + ) + + def test_initialize_instructions_pins_prompt_injection_defense(): """The threat-model sentence in `_CHANNEL_INSTRUCTIONS` is what tells the agent that inbound canvas-user / peer-agent message