[eric] agents: OSW_TOOL_MANIFEST flag prunes dead preset built-ins from the tool surface (default off, no tool loss)

This commit is contained in:
ciregenz
2026-07-17 13:14:26 -07:00
parent 68b079cc5e
commit 9b440a3c18
3 changed files with 67 additions and 7 deletions
@@ -1,5 +1,6 @@
import logging
from typing import List, Optional, Set
import os
from typing import Dict, List, Optional, Set, Union
from typeguard import typechecked
@@ -23,6 +24,22 @@ FULL_TOOLS = [
]
@typechecked
def resolve_builtin_tools_option() -> Union[List[str], Dict[str, str]]:
"""The SDK `tools` option (CLI `--tools`), the base set of built-in tools.
Default: the full claude_code preset, which ships every preset built-in's schema. With
OSW_TOOL_MANIFEST=1, an explicit FULL_TOOLS list instead, ONLY the built-ins OpenSwarm exposes,
which prunes the ~9 preset extras nothing here references (Cron*/Monitor/Task*/PushNotification/
RemoteTrigger/etc) for ~940 schema tokens/turn, cache-stable. MCP tools ride mcp_servers, so
SpawnAgent/browser/schedule/skill/web/user MCPs are untouched; ToolSearch stays in FULL_TOOLS so
deferred loading survives (live-proven: the model still ToolSearch-loads + calls MCP tools under
the manifest). Flag-gated pending a real-app soak before default-on."""
if os.environ.get("OSW_TOOL_MANIFEST") == "1":
return list(FULL_TOOLS)
return {"type": "preset", "preset": "claude_code"}
@typechecked
def get_denied_tool_names(tool: ToolDefinition) -> Set[str]:
"""Return the set of MCP sub-tool names whose permission is 'deny'."""
@@ -25,7 +25,7 @@ from backend.apps.agents.manager.configure_provider_env import configure_provide
from backend.apps.agents.manager.session.workspace_git import ensure_cwd_git_repo
from backend.apps.agents.manager.session.history_compaction import build_history_prefix, get_branch_messages
from backend.apps.agents.manager.prompt.compose_turn_system_prompt import compose_turn_system_prompt
from backend.apps.agents.manager.prompt.tool_catalog import get_all_tool_names
from backend.apps.agents.manager.prompt.tool_catalog import get_all_tool_names, resolve_builtin_tools_option
from backend.apps.agents.manager.prompt.prompt_context import resolve_mode
from backend.apps.agents.manager.run.run_options_helpers import (
pre_send_context_guard, set_framework_overhead, register_web_mcp_server,
@@ -194,11 +194,8 @@ class RunOptions(AgentManagerProtocol):
options_kwargs["mcp_servers"] = mcp_servers
mcp_json_len = len(json.dumps({"mcpServers": mcp_servers}))
logger.info(f"[MCP-DEBUG] mcp_servers passed to SDK: {list(mcp_servers.keys())}, JSON length={mcp_json_len}")
# claude_code preset for BOTH system_prompt and tools so the CLI's deferred-tools scaffolding survives. Raw string would replace it.
options_kwargs["tools"] = {
"type": "preset",
"preset": "claude_code",
}
# Built-in tool surface (preset vs pruned FULL_TOOLS manifest); see resolve_builtin_tools_option. system_prompt keeps the preset regardless.
options_kwargs["tools"] = resolve_builtin_tools_option()
# exclude_dynamic_sections=True moves cwd/git/OS grounding out of the cached prefix and into the first user message, unlocks Anthropic prompt cache (~80% input-token cut, 13-31% faster TTFT). Trade-off: grounding freezes at turn 1.
if composed_prompt:
options_kwargs["system_prompt"] = {
+46
View File
@@ -0,0 +1,46 @@
"""OSW_TOOL_MANIFEST gate: default ships the full claude_code preset; flag-on ships an explicit
FULL_TOOLS list that prunes dead preset built-ins WITHOUT dropping anything OpenSwarm uses. The
manifest MUST keep every built-in in effective_allowed's source set + ToolSearch (so deferred MCP
loading survives); MCP tools ride mcp_servers, not this list."""
import os
from backend.apps.agents.manager.prompt.tool_catalog import (
FULL_TOOLS,
resolve_builtin_tools_option,
)
def test_default_is_the_full_preset(monkeypatch):
monkeypatch.delenv("OSW_TOOL_MANIFEST", raising=False)
assert resolve_builtin_tools_option() == {"type": "preset", "preset": "claude_code"}
def test_flag_on_is_the_explicit_full_tools_manifest(monkeypatch):
monkeypatch.setenv("OSW_TOOL_MANIFEST", "1")
out = resolve_builtin_tools_option()
assert isinstance(out, list)
assert out == list(FULL_TOOLS)
# A fresh copy, never the module list itself (a caller mutation must not poison FULL_TOOLS).
assert out is not FULL_TOOLS
def test_manifest_keeps_toolsearch_so_deferred_mcp_loading_survives(monkeypatch):
monkeypatch.setenv("OSW_TOOL_MANIFEST", "1")
out = resolve_builtin_tools_option()
assert "ToolSearch" in out
def test_manifest_keeps_every_core_builtin_openswarm_exposes(monkeypatch):
# These are the built-ins the agent actually uses; none may vanish from the manifest.
monkeypatch.setenv("OSW_TOOL_MANIFEST", "1")
out = resolve_builtin_tools_option()
for core in ("Read", "Edit", "Write", "Bash", "Glob", "Grep", "AskUserQuestion"):
assert core in out
def test_only_the_exact_flag_value_flips_it(monkeypatch):
monkeypatch.setenv("OSW_TOOL_MANIFEST", "true") # not "1"
assert resolve_builtin_tools_option() == {"type": "preset", "preset": "claude_code"}
monkeypatch.setenv("OSW_TOOL_MANIFEST", "0")
assert resolve_builtin_tools_option() == {"type": "preset", "preset": "claude_code"}