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"