From 448709f4b4d41a87cf4f95856f12d0bc305abca9 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Tue, 28 Apr 2026 16:43:36 -0700 Subject: [PATCH] fix(prompt): inject A2A and HMA tool instructions into system prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workers were registering platform tools (delegate_task, delegate_task_async, list_peers, check_task_status, send_message_to_user, commit_memory, recall_memory) but the build_system_prompt assembly never included documentation for any of them. The instruction-text functions get_a2a_instructions() and get_hma_instructions() exist in executor_helpers.py and have unit tests, but were not called from any production code path — workers received system-prompt.md content only and saw the tools as bare names with no usage guidance. Symptom: agents called commit_memory and delegate_task without knowing they were platform tools. They worked when the agent guessed the API correctly and silently failed when the agent didn't. Fix: build_system_prompt() now appends both instruction sets between the Skills section and the Peers section. The placement is intentional — A2A docs explain how to call delegate_task; the peer list is the data that delegate_task operates over, so the docs precede the peer table. New parameter `a2a_mcp: bool = True` lets adapters opt into the CLI subprocess variant of the A2A instructions for runtimes without MCP support (ollama, custom CLI runtimes). Default True covers the MCP-capable majority (claude-code, hermes, langchain, crewai). Adapter callers don't need to change unless they specifically need CLI mode. Tests: 4 new regression tests in test_prompt.py pin - A2A MCP variant injection (default) - A2A CLI variant injection (a2a_mcp=False, with MCP-only fields absent) - HMA instruction injection - A2A docs precede peer list ordering Full suite green: 1223 passed, 2 xfailed. --- workspace/prompt.py | 16 ++++++++ workspace/tests/test_prompt.py | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/workspace/prompt.py b/workspace/prompt.py index 70cce126..7d93152e 100644 --- a/workspace/prompt.py +++ b/workspace/prompt.py @@ -4,6 +4,7 @@ import logging import os from pathlib import Path +from executor_helpers import get_a2a_instructions, get_hma_instructions from skill_loader.loader import LoadedSkill from shared_runtime import build_peer_section @@ -68,6 +69,7 @@ def build_system_prompt( plugin_prompts: list[str] | None = None, parent_context: list[dict] | None = None, platform_instructions: str = "", + a2a_mcp: bool = True, ) -> str: """Build the complete system prompt. @@ -154,6 +156,20 @@ def build_system_prompt( parts.append(skill.instructions) parts.append("") + # Platform tool instructions: A2A (inter-agent communication) and HMA + # (persistent memory). These document how to call delegate_task, + # commit_memory, etc — without them, agents see the tools registered + # but have no instructions on when/how to use them. Placed between + # Skills and Peers so the A2A docs precede the peer list (which is + # the data shape the A2A tools operate over). + # + # a2a_mcp=True: MCP tool variant (claude-code, hermes, langchain, + # crewai). a2a_mcp=False: CLI subprocess variant (ollama, custom + # runtimes that don't speak MCP). Default True matches the + # MCP-capable majority; CLI-only adapters override at the call site. + parts.append(get_a2a_instructions(mcp=a2a_mcp)) + parts.append(get_hma_instructions()) + # Add peer capabilities with a single shared renderer. peer_section = build_peer_section(peers) if peer_section: diff --git a/workspace/tests/test_prompt.py b/workspace/tests/test_prompt.py index 133a5d7e..5f868c81 100644 --- a/workspace/tests/test_prompt.py +++ b/workspace/tests/test_prompt.py @@ -395,3 +395,77 @@ async def test_get_peer_capabilities_exception(): result = await get_peer_capabilities("http://platform:8080", "ws-abc") assert result == [] + + +# Regression tests for the A2A + HMA tool-instruction injection. Pre-fix, +# get_a2a_instructions() and get_hma_instructions() were defined in +# executor_helpers.py but never called from build_system_prompt — workers +# saw the platform's delegate_task / commit_memory tools registered but +# had no documentation telling them how to use them. + +def test_a2a_instructions_injected_default_mcp(tmp_path): + """build_system_prompt embeds A2A MCP-variant instructions by default.""" + (tmp_path / "system-prompt.md").write_text("Base.") + + result = build_system_prompt( + config_path=str(tmp_path), + workspace_id="ws-1", + loaded_skills=[], + peers=[], + ) + + assert "## Inter-Agent Communication" in result + assert "delegate_task" in result + assert "list_peers" in result + assert "send_message_to_user" in result + + +def test_a2a_instructions_cli_variant_when_disabled(tmp_path): + """a2a_mcp=False emits the CLI subprocess variant for non-MCP runtimes.""" + (tmp_path / "system-prompt.md").write_text("Base.") + + result = build_system_prompt( + config_path=str(tmp_path), + workspace_id="ws-1", + loaded_skills=[], + peers=[], + a2a_mcp=False, + ) + + assert "## Inter-Agent Communication" in result + assert "molecule_runtime.a2a_cli" in result + # MCP-only details must NOT leak into the CLI variant. + assert "send_message_to_user" not in result + + +def test_hma_instructions_injected(tmp_path): + """build_system_prompt embeds HMA persistent-memory instructions.""" + (tmp_path / "system-prompt.md").write_text("Base.") + + result = build_system_prompt( + config_path=str(tmp_path), + workspace_id="ws-1", + loaded_skills=[], + peers=[], + ) + + assert "## Hierarchical Memory (HMA)" in result + assert "commit_memory" in result + assert "recall_memory" in result + + +def test_tool_instructions_precede_peer_section(tmp_path): + """A2A docs must precede the peer list — peer IDs are operands of A2A tools.""" + (tmp_path / "system-prompt.md").write_text("Base.") + + peers = [{"id": "p1", "name": "Worker", "status": "active", "agent_card": None}] + result = build_system_prompt( + config_path=str(tmp_path), + workspace_id="ws-1", + loaded_skills=[], + peers=peers, + ) + + a2a_idx = result.index("## Inter-Agent Communication") + peers_idx = result.index("## Your Peers") + assert a2a_idx < peers_idx, "A2A instructions must come before the peer list"