hermes-bsd/hermes_cli
teknium1 369075dc95 feat(tools): progressive tool disclosure for MCP and plugin tools
Adds Tool Search, a structured-tools progressive-disclosure layer that
replaces MCP and non-core plugin tools in the model-visible tools array
with three bridge tools (tool_search / tool_describe / tool_call) when
the deferrable surface would consume more than a configurable percentage
of the active model's context window. Core Hermes tools are never deferred.

Default mode is 'auto' with a 10% context threshold, so small toolsets
pay no overhead. Set tools.tool_search.enabled to 'on' to force or 'off'
to disable.

Design carefully reflects the OpenClaw production failure modes
documented in the openclaw-tool-search-report:

  - Core tools never defer (toolsets._HERMES_CORE_TOOLS). Addresses the
    'tools silently missing from isolated cron turns' regression class
    (openclaw#84141) by construction: there is no code path that can
    drop a core tool.
  - Catalog is stateless across turns — rebuilt from the live tool-defs
    list on every assembly. No session-keyed Map that can drift out of
    sync with the registry.
  - tool_call unwraps the bridge call before any hook fires, so plugin
    pre/post hooks, guardrails, approval flows, and the activity feed
    all see the underlying tool name, not the bridge (addresses
    openclaw#85588 and the verbose-mode complaint on openclaw#79823).
  - The unwrap happens in both the parallel and sequential paths of
    agent/tool_executor.py and also in handle_function_call, so direct
    callers (sandboxed code, eval harnesses) are covered too.
  - Bridge tools cannot invoke each other (recursion guard) and cannot
    invoke core tools (those must be called directly).
  - Tools mode only — no JS-sandbox code-mode. Keeps the surface small.
  - Token estimation via cheap char/4 heuristic; precision isn't needed
    for the threshold decision.

Files:
  - tools/tool_search.py — new module (BM25 retrieval, classification,
    threshold gate, bridge dispatch, unwrap helper).
  - tests/tools/test_tool_search.py — 35 tests including the OpenClaw
    #84141 regression guard.
  - model_tools.py — wires assembly into _compute_tool_definitions as the
    final step, adds skip_tool_search_assembly kwarg so the bridge can
    see the real catalog, dispatches the three bridge tools.
  - agent/tool_executor.py — unwraps tool_call in both parallel and
    sequential parsing loops so checkpointing, guardrails, plugin hooks,
    and tool-progress callbacks all observe the underlying tool name.
  - hermes_cli/config.py — DEFAULT_CONFIG['tools']['tool_search'] block.
  - website/docs/user-guide/features/tool-search.md — user docs.

Validation:
  - 35/35 new tests pass.
  - Existing tool/registry/model_tools/config/coercion/executor tests
    (82 + 74 + small adjacents) green.
  - Live E2E: 20 fake MCP tools registered, get_tool_definitions returns
    3 bridges, tool_search returns top 3 hits, tool_describe returns
    full schema, tool_call dispatches to the real underlying handler
    and the underlying result is what the model sees.
  - Reserved-name recursion guard verified live.
  - Core-tool refusal via tool_call verified live.
2026-05-29 02:04:12 -07:00
..
dashboard_auth fix(dashboard-auth): share /api/* public allowlist between legacy and OAuth gates 2026-05-29 12:17:12 +10:00
proxy chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
__init__.py chore: release v0.15.1 (2026.5.29) (#34222) 2026-05-28 18:11:49 -07:00
_parser.py Fix CLI verbose tool progress config fallback 2026-05-23 21:03:51 -07:00
_subprocess_compat.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
auth.py fix(auth): don't launch a text-mode browser inside the terminal for OAuth (#34479) 2026-05-29 01:23:06 -07:00
auth_commands.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
azure_detect.py
backup.py fix: limit pre-update state snapshots 2026-05-28 02:45:25 -07:00
banner.py fix(docker): bake build-time git SHA into the image 2026-05-28 15:14:05 +10:00
browser_connect.py
build_info.py fix(docker): bake build-time git SHA into the image 2026-05-28 15:14:05 +10:00
bundles.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
callbacks.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
checkpoints.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
claw.py
cli_output.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
clipboard.py
codex_models.py fix(codex): drop dead model slugs that HTTP 400 on ChatGPT Pro (#33424) 2026-05-27 12:16:15 -07:00
codex_runtime_plugin_migration.py
codex_runtime_switch.py
colors.py
commands.py fix(model picker): unify /model and hermes model lists, add disk cache (#33867) 2026-05-28 11:33:16 -07:00
completion.py
config.py feat(tools): progressive tool disclosure for MCP and plugin tools 2026-05-29 02:04:12 -07:00
container_boot.py fix(docker): make s6 lifecycle work for the unprivileged hermes user 2026-05-25 12:23:23 +10:00
copilot_auth.py
cron.py
curator.py
curses_ui.py
debug.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
default_soul.py
dep_ensure.py
dingtalk_auth.py
doctor.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
dump.py fix(docker): bake build-time git SHA into the image 2026-05-28 15:14:05 +10:00
env_loader.py fix(secrets): only apply external secrets once per HERMES_HOME per process (#32271) 2026-05-25 15:18:55 -07:00
fallback_cmd.py
fallback_config.py
gateway.py feat(docker): auto-redirect gateway run to supervised mode inside s6 image 2026-05-28 12:42:13 +10:00
gateway_windows.py fix(gateway): drain on Windows hermes gateway stop so sessions survive restart (#33798) 2026-05-28 03:25:32 -07:00
goals.py
hooks.py
inventory.py
kanban.py fix(kanban): add --reason flag to unblock for symmetry with block (#30897) 2026-05-28 23:41:44 -07:00
kanban_db.py fix(kanban): close three blocked/iteration-exhausted handling gaps (#29747) 2026-05-29 00:13:29 -07:00
kanban_decompose.py fix(kanban): close kanban.db FD after every connect() in long-lived processes 2026-05-27 22:07:49 -07:00
kanban_diagnostics.py fix(kanban): close three blocked/iteration-exhausted handling gaps (#29747) 2026-05-29 00:13:29 -07:00
kanban_specify.py fix(kanban): close kanban.db FD after every connect() in long-lived processes 2026-05-27 22:07:49 -07:00
kanban_swarm.py fix(kanban): CLI dispatch honors max_in_progress/max_spawn from config; swap missing 'avoid-ai-writing' skill for bundled humanizer (#33488, #29415) (#34337) 2026-05-28 21:00:46 -07:00
logs.py
main.py fix: improve plugins list usability 2026-05-29 00:59:42 -07:00
mcp_catalog.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
mcp_config.py feat(mcp): Nous-approved MCP catalog with interactive picker (#30870) 2026-05-26 12:48:14 -07:00
mcp_picker.py feat(mcp): Nous-approved MCP catalog with interactive picker (#30870) 2026-05-26 12:48:14 -07:00
memory_setup.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
migrate.py
model_catalog.py fix(model-catalog): fall through to raw.github when Vercel 403s; swap step-3.5-flash for step-3.7-flash on OpenRouter+Nous 2026-05-29 00:25:36 -07:00
model_normalize.py remove Vercel AI Gateway and Vercel Sandbox (#33067) 2026-05-27 00:43:32 -07:00
model_switch.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
models.py fix(model-catalog): fall through to raw.github when Vercel 403s; swap step-3.5-flash for step-3.7-flash on OpenRouter+Nous 2026-05-29 00:25:36 -07:00
nous_account.py feat(auth) normalise the way in which we check whether a user has free/paid access to nous portal so we can expose behaviour and error messages accordingly. 2026-05-28 00:19:31 -07:00
nous_subscription.py fix(auth): refresh Nous entitlement in tool menus 2026-05-28 00:19:31 -07:00
oneshot.py fix(provider): make config.yaml model.provider the single source of truth (#31222) 2026-05-23 18:18:41 -07:00
pairing.py
platforms.py
plugins.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
plugins_cmd.py fix: improve plugins list usability 2026-05-29 00:59:42 -07:00
portal_cli.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
profile_describer.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
profile_distribution.py fix(profile): reject symlinks in distributions (#25292) 2026-05-25 05:07:58 -07:00
profiles.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
providers.py remove Vercel AI Gateway and Vercel Sandbox (#33067) 2026-05-27 00:43:32 -07:00
psutil_android.py fix(android): reject unsafe tar members in psutil compatibility installer 2026-05-28 02:36:09 -07:00
pt_input_extras.py fix(cli): ignore terminal focus reports (salvage of #16780) 2026-05-29 00:31:44 -07:00
pty_bridge.py
relaunch.py
runtime_provider.py
secret_prompt.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
secrets_cli.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
security_advisories.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
security_audit.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
send_cmd.py
service_manager.py fix(docker): align HOME for dashboard and s6 gateway services (#33481) 2026-05-28 13:42:27 +10:00
session_recap.py
setup.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
skills_config.py
skills_hub.py fix(skills-hub): stop ellipsis-truncating the Identifier column (#33810) 2026-05-28 04:53:13 -07:00
skin_engine.py
slack_cli.py
status.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
stdio.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
timeouts.py
tips.py docs(auth): replace stale 'hermes login' references with 'hermes auth add' 2026-05-26 15:41:11 -07:00
tools_config.py fix: expose context engine tools with saved toolsets 2026-05-28 00:28:42 -07:00
uninstall.py
voice.py
web_server.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
webhook.py fix(state): restrict sensitive store file permissions 2026-05-24 04:55:18 -07:00
xai_retirement.py