When a peer_agent push lands and the agent needs context from prior
turns with that workspace ("what task did this peer assign me last
hour?", "what did I tell them?"), the only options today are
re-deriving from memory (lossy) or scrolling activity_logs in the
canvas (no agent-facing tool). Surface the platform's existing
audit log directly via a new MCP tool so agents can read both sides
of an A2A conversation in chronological order.
Implementation:
- a2a_tools.py: new tool_chat_history(peer_id, limit=20, before_ts="")
hits /workspaces/<self>/activity?peer_id=X&limit=N (the new server
filter from molecule-core#2472). Reverses the DESC response into
chronological order so the agent reads top-down. Graceful error
envelope on validation/network/non-200 — never crashes the MCP
server, agent can branch on Error: prefix.
- platform_tools/registry.py: ToolSpec wired into the A2A section so
the rendered system-prompt block automatically includes it. Same
pattern as the existing inbox_peek/inbox_pop/wait_for_message.
- a2a_mcp_server.py: dispatch in handle_tool_call.
- executor_helpers.py: _CLI_A2A_COMMAND_KEYWORDS gets a None entry
(CLI runtimes don't expose chat history today; flip to a keyword
when a2a_cli grows a `history` subcommand).
- snapshots/a2a_instructions_mcp.txt regenerated.
Tests: 10 new branches in TestChatHistory (validation / param
forwarding / limit cap / before_ts pass-through / DESC→chronological
reorder / 400 verbatim / 500 generic / network exc / non-list resp).
Mutation-verified: reverting a2a_tools.py fails 10/10. Full test
suite remains green at 1516 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| __init__.py | ||
| README.md | ||
| registry.py | ||
Platform tool registry
Single source of truth for every tool the platform exposes to agents (A2A delegation, hierarchical memory, broadcast, introspection).
Why this exists
Pre-#2240, three places independently declared each tool:
- MCP server (
workspace/a2a_mcp_server.py) — theTOOLSJSON list - LangChain
@toolwrappers (workspace/builtin_tools/{delegation,memory}.py) - Agent-facing system-prompt docs (
workspace/executor_helpers.py)
Adding a tool to one and forgetting the others happened repeatedly. The
canonical case: send_message_to_user was registered in MCP TOOLS but
the executor_helpers doc string never mentioned it, so agents saw the
tool as available but had no usage guidance — a silent capability
regression.
What the registry does
registry.py defines each tool ONCE as a frozen ToolSpec:
ToolSpec(
name="delegate_task",
short="Delegate a task to a peer workspace via A2A and WAIT for the response.",
when_to_use="Use for QUICK questions and small sub-tasks where you can afford to wait inline...",
input_schema={...}, # JSON Schema, consumed by MCP server
impl=tool_delegate_task, # the actual coroutine
section="a2a", # which prompt section it belongs to
)
Adapters consume specs; no hardcoded names anywhere else:
- MCP server builds its
TOOLSlist from_PLATFORM_TOOL_SPECSat import time - LangChain
@toolwrappers readname=spec.namefrom the registry - Doc generator (
executor_helpers._render_section()) produces the system-prompt block fromspec.short(bullet) +spec.when_to_use(heading + paragraph)
CLI subprocess block — special case
Non-MCP runtimes (ollama, custom subprocess adapters) use a separate
hand-maintained block in executor_helpers._A2A_INSTRUCTIONS_CLI because
the CLI subcommand vocabulary (peers, delegate, status, info)
differs from the MCP tool names (list_peers, delegate_task, etc.).
Auto-generation would lose the readable invocation syntax.
Alignment is enforced via _CLI_A2A_COMMAND_KEYWORDS (in
executor_helpers.py): every a2a-section spec must be keyed there with
either a CLI subcommand keyword OR an explicit None if the tool is
intentionally not exposed via subprocess (e.g.
send_message_to_user because its structured attachments field
doesn't survive positional-arg shell invocation).
Tests that catch drift
workspace/tests/test_platform_tools.py:
| Test | What it catches |
|---|---|
test_mcp_server_registers_every_registry_tool |
MCP TOOLS list out of sync with registry |
test_mcp_tool_descriptions_match_registry_short |
hand-edited MCP description that drifted |
test_mcp_tool_input_schemas_match_registry |
schema duplicated in server file |
test_a2a_instructions_text_includes_every_a2a_tool |
doc generator missed a tool |
test_old_pre_rename_names_not_present_in_docs |
stale name leaked back in |
test_a2a_mcp_instructions_match_snapshot |
rendered shape (bullet ordering, headings, footers) drifted |
test_a2a_cli_instructions_match_snapshot |
CLI block edited in a way that changes shape |
test_hma_instructions_match_snapshot |
HMA section drifted |
test_cli_keyword_mapping_covers_every_a2a_tool |
tool added to registry without a CLI mapping decision |
test_cli_keyword_substrings_appear_in_cli_block |
CLI keyword in the mapping but missing from the doc block |
The snapshot files at workspace/tests/snapshots/*.txt are LF-pinned
in .gitattributes so a Windows contributor with core.autocrlf=true
doesn't get mysterious test failures.
Adding a new tool
- Append a
ToolSpec(...)toTOOLSinregistry.py. - Add the LangChain
@toolwrapper inworkspace/builtin_tools/(the wrapper body just callsspec.impl). - Update
_CLI_A2A_COMMAND_KEYWORDSinexecutor_helpers.py— set the value to the CLI subcommand keyword, or toNoneif the tool isn't exposed via the subprocess interface. - Regenerate snapshots — see the comment block at the top of
workspace/tests/test_platform_tools.pyfor the one-liner. - Run
pytest workspace/tests/test_platform_tools.py --no-cov.
Renaming a tool
Edit name in registry.py only. Then:
- The MCP TOOLS list rebuilds automatically.
- The doc generator regenerates automatically (snapshots will fail the diff — regenerate them).
- Search
workspace/for the old literal in case a non-adapter consumer (tests, plugin code) hardcoded the old name; update those. - Update any
_CLI_A2A_COMMAND_KEYWORDSkey + the literal substring in_A2A_INSTRUCTIONS_CLIif applicable.
Removing a tool
Delete the ToolSpec and the _CLI_A2A_COMMAND_KEYWORDS key. Adapters
and doc generators stop registering it automatically; the structural
tests prevent stale references from surviving.