feat(prompt): Platform Capabilities preamble at top of system prompt

Closes #2332 item 1 (workspace awareness — agents don't surface
platform-native tools up front).

The dogfooding session surfaced that agents weren't using A2A
delegation, persistent memory, or send_message_to_user. The tools
were registered AND documented in the system prompt — but only in
sections #8 (Inter-Agent Communication) and #9 (Hierarchical Memory),
which agents read AFTER they've already started reasoning about a
plan from earlier sections.

This adds a tight inventory at section #1.5 (immediately after
Platform Instructions, before role-specific prompt files) — every
tool name + its short description in a bulleted block. Detailed
when_to_use docs in sections #8/#9 stay; this preamble is the
elevator pitch ("you have these"), the later sections are the
manual ("here's when and how").

Generated from `platform_tools.registry` ToolSpecs — every tool's
`name` + `short` flow through automatically, no manual sync. A new
`get_capabilities_preamble(mcp: bool)` helper in executor_helpers
mirrors the existing get_a2a_instructions / get_hma_instructions
pattern.

CLI-runtime agents (mcp=False) get an empty preamble — they see
_A2A_INSTRUCTIONS_CLI's hand-written subcommand vocabulary further
down, and the registry's MCP tool names would conflict.

Tests:
  - test_capabilities_preamble_appears_in_mcp_prompt: header present
  - test_capabilities_preamble_lists_every_registry_tool: every
    a2a + memory tool from registry shows up (drift catches at test
    time — adding a new tool to registry surfaces here automatically)
  - test_capabilities_preamble_precedes_prompt_files: ordering
    invariant (toolkit before role docs)
  - test_capabilities_preamble_skipped_for_cli_runtime: empty when
    mcp=False

All 40 prompt + platform_tools tests pass.
This commit is contained in:
Hongming Wang 2026-04-29 21:31:13 -07:00
parent 856ff89973
commit 4299475746
3 changed files with 144 additions and 1 deletions

View File

@ -370,6 +370,59 @@ def _render_section(heading: str, specs, footer: str = "") -> str:
return "\n".join(parts).rstrip() + "\n"
def get_capabilities_preamble(mcp: bool = True) -> str:
"""Return a top-of-prompt one-glance summary of platform-native tools.
Shipped 2026-04-30 (#2332): the dogfooding session surfaced that
agents weren't using A2A delegation, persistent memory, or
send_message_to_user these capabilities WERE documented further
down in the system prompt (## Inter-Agent Communication, ## HMA),
but agents tend to read top-down and commit to a plan before
reaching that section.
The preamble is the elevator pitch: every tool name + its short
description in a tight bulleted block, immediately after Platform
Instructions. The detailed when_to_use docs further down still
apply this is "you have these tools; consult the dedicated
section for usage details."
Generated from the same `platform_tools.registry` ToolSpecs as the
detailed sections, so renames/additions in registry.py flow through
automatically. Returns "" for CLI-runtime agents (mcp=False) they
get a different overall prompt shape and the registry's MCP-named
tools wouldn't match the CLI command vocabulary.
"""
if not mcp:
# CLI-runtime agents see _A2A_INSTRUCTIONS_CLI's hand-written
# command list instead. Skip the preamble to avoid confusing
# agents with two name vocabularies (MCP tool names vs CLI
# subcommand keywords).
return ""
from platform_tools.registry import a2a_tools, memory_tools
parts = [
"## Platform Capabilities",
"",
(
"You have native access to these platform tools. Use them "
"proactively — they're how multi-agent collaboration, "
"persistent memory, and user communication actually work. "
"Detailed usage guidance for each lives in the dedicated "
"sections below; this preamble is just the inventory."
),
"",
"**Inter-agent collaboration (A2A):**",
]
for spec in a2a_tools():
parts.append(f"- `{spec.name}` — {spec.short}")
parts.append("")
parts.append("**Persistent memory (HMA):**")
for spec in memory_tools():
parts.append(f"- `{spec.name}` — {spec.short}")
return "\n".join(parts).rstrip() + "\n"
def get_a2a_instructions(mcp: bool = True) -> str:
"""Return inter-agent communication instructions for system-prompt injection.

View File

@ -4,7 +4,11 @@ import logging
import os
from pathlib import Path
from executor_helpers import get_a2a_instructions, get_hma_instructions
from executor_helpers import (
get_a2a_instructions,
get_capabilities_preamble,
get_hma_instructions,
)
from skill_loader.loader import LoadedSkill
from shared_runtime import build_peer_section
@ -92,6 +96,17 @@ def build_system_prompt(
parts.append("# Platform Instructions\n")
parts.append(platform_instructions)
# Platform Capabilities preamble (#2332): tight inventory of every
# native tool agents have access to, generated from the registry.
# Goes BEFORE prompt files so the role-specific docs read against
# a known toolkit, not a discovery problem. Detailed when_to_use
# docs still appear later in the A2A and HMA sections — this
# preamble is the elevator pitch ("you have these"); the later
# sections are the manual ("here's when and how").
capabilities = get_capabilities_preamble(mcp=a2a_mcp)
if capabilities:
parts.append(capabilities)
# Load prompt files in order
files_to_load = list(prompt_files or [])
if not files_to_load:

View File

@ -469,3 +469,78 @@ def test_tool_instructions_precede_peer_section(tmp_path):
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"
# --- Capabilities preamble (#2332) ---
def test_capabilities_preamble_appears_in_mcp_prompt(tmp_path):
"""MCP-runtime agents see the Platform Capabilities preamble at top."""
(tmp_path / "system-prompt.md").write_text("Role-specific content.")
result = build_system_prompt(
config_path=str(tmp_path),
workspace_id="ws-1",
loaded_skills=[],
peers=[],
)
assert "## Platform Capabilities" in result
def test_capabilities_preamble_lists_every_registry_tool(tmp_path):
"""Every tool in the registry appears in the preamble — drift catches at test time."""
(tmp_path / "system-prompt.md").write_text("Base.")
result = build_system_prompt(
config_path=str(tmp_path),
workspace_id="ws-1",
loaded_skills=[],
peers=[],
)
from platform_tools.registry import a2a_tools, memory_tools
preamble_start = result.index("## Platform Capabilities")
# Detailed sections come later — only check the slice between the
# preamble heading and the next ## heading after it.
next_section = result.index("\n## ", preamble_start + 1)
preamble_block = result[preamble_start:next_section]
for spec in a2a_tools() + memory_tools():
assert f"`{spec.name}`" in preamble_block, (
f"tool {spec.name!r} from registry missing from capabilities preamble"
)
def test_capabilities_preamble_precedes_prompt_files(tmp_path):
"""Preamble lands before role-specific prompt files so agents see the
toolkit before reading their role docs."""
(tmp_path / "system-prompt.md").write_text("ROLE_MARKER_SENTINEL")
result = build_system_prompt(
config_path=str(tmp_path),
workspace_id="ws-1",
loaded_skills=[],
peers=[],
)
cap_idx = result.index("## Platform Capabilities")
role_idx = result.index("ROLE_MARKER_SENTINEL")
assert cap_idx < role_idx, "Capabilities preamble must precede role prompt files"
def test_capabilities_preamble_skipped_for_cli_runtime(tmp_path):
"""CLI-runtime agents see _A2A_INSTRUCTIONS_CLI's hand-written commands
instead the preamble's MCP tool names would conflict."""
(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 "## Platform Capabilities" not in result