docs(a2a-mcp): list new envelope attrs in initialize instructions

The agent learns about <channel> 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 <channel> 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) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-05-01 17:49:36 -07:00
parent 0fec3d6fe4
commit 103ac09aeb
2 changed files with 49 additions and 2 deletions

View File

@ -221,8 +221,9 @@ def _build_channel_instructions() -> str:
"\n"
"PUSH PATH (Claude Code with channel push enabled):\n"
"Messages arrive as <channel source=\"molecule\" kind=\"...\" "
"peer_id=\"...\" activity_id=\"...\" ts=\"...\"> 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"

View File

@ -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 <channel> 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 <channel> 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 <channel> 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