forked from molecule-ai/molecule-core
Merge pull request #2340 from Molecule-AI/auto/issue-2332-capabilities-preamble
feat(prompt): Platform Capabilities preamble at top of system prompt (#2332 item 1)
This commit is contained in:
commit
21ed74c76a
@ -370,6 +370,59 @@ def _render_section(heading: str, specs, footer: str = "") -> str:
|
|||||||
return "\n".join(parts).rstrip() + "\n"
|
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:
|
def get_a2a_instructions(mcp: bool = True) -> str:
|
||||||
"""Return inter-agent communication instructions for system-prompt injection.
|
"""Return inter-agent communication instructions for system-prompt injection.
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
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 skill_loader.loader import LoadedSkill
|
||||||
from shared_runtime import build_peer_section
|
from shared_runtime import build_peer_section
|
||||||
|
|
||||||
@ -92,6 +96,17 @@ def build_system_prompt(
|
|||||||
parts.append("# Platform Instructions\n")
|
parts.append("# Platform Instructions\n")
|
||||||
parts.append(platform_instructions)
|
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
|
# Load prompt files in order
|
||||||
files_to_load = list(prompt_files or [])
|
files_to_load = list(prompt_files or [])
|
||||||
if not files_to_load:
|
if not files_to_load:
|
||||||
|
|||||||
@ -469,3 +469,78 @@ def test_tool_instructions_precede_peer_section(tmp_path):
|
|||||||
a2a_idx = result.index("## Inter-Agent Communication")
|
a2a_idx = result.index("## Inter-Agent Communication")
|
||||||
peers_idx = result.index("## Your Peers")
|
peers_idx = result.index("## Your Peers")
|
||||||
assert a2a_idx < peers_idx, "A2A instructions must come before the peer list"
|
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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user