Skip to content

feat(tool-search): lazy-load core tools via defer_core/defer_always_core - #53193

Open
chrisribe wants to merge 10 commits into
NousResearch:mainfrom
chrisribe:feat/deferrable-core-tools
Open

feat(tool-search): lazy-load core tools via defer_core/defer_always_core#53193
chrisribe wants to merge 10 commits into
NousResearch:mainfrom
chrisribe:feat/deferrable-core-tools

Conversation

@chrisribe

@chrisribe chrisribe commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Adds opt-in lazy loading for core tools through the existing tool_search / tool_describe / tool_call bridge.

New config flags:

tools:
  tool_search:
    enabled: "on"
    defer_core: true          # defer deferrable core tools
    defer_always_core: true   # fully lazy mode (implies defer_core)
  • defer_core: keeps an always-visible safety set and defers the rest of core tools.
  • defer_always_core: defers even always-visible core tools (bridge-only visible set).
  • Defaults remain false -> no behavior change for existing installs.

Motivation

Reduce per-call tool schema payload for token and latency efficiency, while preserving access to full capability via the bridge.

Changes

  • toolsets.py
    • split core tool handling into always-visible vs deferrable groups
    • preserve _HERMES_CORE_TOOLS as combined list for compatibility
    • fixed startup import/runtime issue by restoring _HERMES_DEFERRABLE_CORE_TOOLS definition (prevents NameError during toolsets import)
  • tools/tool_search.py
    • add defer_core and defer_always_core config handling
    • propagate both flags through deferrable classification logic
    • defer_always_core auto-promotes defer_core
  • model_tools.py, agent/tool_executor.py
    • pass config through resolve/scoped call sites
  • hermes_cli/config.py
    • document both flags in defaults
  • docs
    • update tool-search feature docs for both flags
  • tests
    • expanded defer-core and defer-always-core coverage, including E2E bridge paths

Validation

  • python3 -m pytest tests/tools/test_tool_search.py -q
    • result: 56 passed
  • runtime smoke:
    • hermes --version starts successfully
    • hermes chat -q "ping" returns pong

Maintainer notes

  • Backward compatibility preserved (_HERMES_CORE_TOOLS remains available as flat combined list).
  • Prompt caching behavior preserved (schema remains stable within a session).
  • Deferred terminal and other tools still route through existing approval/middleware paths (no bypass).

@chrisribe
chrisribe marked this pull request as ready for review June 26, 2026 17:50
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/tools Tool registry, model_tools, toolsets comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jun 26, 2026
@chrisribe

chrisribe commented Jun 26, 2026

Copy link
Copy Markdown
Author

Overlaps with #40993 and #42551 — key differences:

Ours wins: only working e2e implementation (both others miss config threading through resolve_underlying_call / scoped_deferrable_names → silent bridge failure), only PR with tests (5 cases).

Ours lacks: per-platform support (#42551), user-configurable list (#40993/#42551).

Happy to port the invocation fix + tests to whichever PR maintainers prefer, or add a defer_core_tools: [list] override if hardcoded tiers are the concern.

Measured on real install: 54% payload reduction, 8,199 tokens saved/call.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for threading the proposed core-deferral classification through the bridge gates. The underlying schema-overhead premise still exists on current main: tools/tool_search.py:150-174 protects every _HERMES_CORE_TOOLS member from deferral.

Problems

  • defer_core is not represented in hermes_cli/config.py:2785-2804 and is absent from website/docs/user-guide/features/tool-search.md:74-88; the same document currently states at :18-24 that built-ins never defer. The public config contract must be updated with the implementation.
  • The added TestDeferCoreTrue tests cover helper classification/parsing, but not the runtime assembly plus bridge dispatch paths modified in model_tools.py and agent/tool_executor.py.
  • tools/voice_mode.py adds paplay, which is unrelated to this feature and is untested here.

Suggested changes

  • Add the default, docs, and an end-to-end scoped bridge test using a temporary config and real registry dispatch.
  • Split out the audio-player fallback change.

Automated hermes-sweeper review.

Comment thread tools/voice_mode.py Outdated
@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 15, 2026
@chrisribe
chrisribe force-pushed the feat/deferrable-core-tools branch from 0add293 to 8d1c6f0 Compare July 17, 2026 19:10
@chrisribe

Copy link
Copy Markdown
Author

Thanks @teknium1 — didn't catch those missing items. Added defer_core to the config DEFAULTS in hermes_cli/config.py, updated the stale comment, added defer_core to the docs table in tool-search.md, added an e2e bridge dispatch test, and dropped the unrelated paplay change. Also rebased onto latest main — resolved conflicts in toolsets.py (picked up close_terminal, browser_cdp, browser_dialog, kanban_attach family). 45/45 tests passing.

@chrisribe chrisribe changed the title feat(tools): lazy-load non-essential core tools via defer_core flag feat(tools): lazy-load core tools via defer_core + defer_always_core flags Jul 17, 2026
@chrisribe

chrisribe commented Jul 17, 2026

Copy link
Copy Markdown
Author

Added defer_always_core: true flag (commit 102bc7e) — defers all built-in tools including terminal/memory/delegate_task behind the bridge. Only 3 bridge tools visible on every call (~1.6 KB). defer_core is auto-promoted so only one flag needed. 51/51 tests pass.

@chrisribe

chrisribe commented Jul 17, 2026

Copy link
Copy Markdown
Author

Code review done (commit 3ecaa501). Logic correct, no security bypass. Fixed: defer_always_core now auto-promotes defer_core in from_raw(). Added 3 missing tests: E2E assemble_tool_defs, scoped_deferrable_names, and auto-promotion. 54/54 tests pass.

@chrisribe

Copy link
Copy Markdown
Author

@teknium1 please review

@dbenn8

dbenn8 commented Jul 21, 2026

Copy link
Copy Markdown

This looks very promising, especially for those if us using local inference. Excited to see this released!

@doom9ra

doom9ra commented Jul 22, 2026

Copy link
Copy Markdown

release planned?

@alt-glitch alt-glitch added area/config Config system, migrations, profiles needs-decision Awaiting maintainer decision before any implementation comp/cli CLI entry point, hermes_cli/, setup wizard and removed sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) labels Jul 22, 2026
@chrisribe

Copy link
Copy Markdown
Author

Hello trying to get this PR more visibility... I know there are a lot of open PRs (over 17k!!)
@kshitijk4poor or @alt-glitch I saw you where involved in a recent PR merge.... hoping you can review

Thanks

@chrisribe chrisribe changed the title feat(tools): lazy-load core tools via defer_core + defer_always_core flags feat(tool-search): lazy-load core tools via defer_core/defer_always_core Jul 24, 2026
…hema reduction

Split _HERMES_CORE_TOOLS into two tiers:
- _HERMES_ALWAYS_CORE_TOOLS (8): memory, clarify, todo, session_search,
  web_search, web_extract, vision_analyze, delegate_task
- _HERMES_DEFERRABLE_CORE_TOOLS (26+): terminal, file, browser_*, skills,
  execute_code, cronjob, tts, ha_*, kanban_*, computer_use

Add defer_core: bool to ToolSearchConfig. When true (default with enabled=on),
deferrable core tools enter the tool_search catalog and are fetched on demand
via tool_search → tool_describe → tool_call bridge. Always-core tools never
defer regardless of config.

Result: 62,752 → 22,417 bytes per call (64% reduction) for conversational
sessions. Full capability preserved — model discovers and calls any tool in
one turn via the bridge.

All 39 existing tool_search tests pass.
chrisribe and others added 8 commits July 28, 2026 07:43
Usage data shows these 3 tools account for 77% of all tool calls:
  terminal:     175 calls (62%)
  search_files:  29 calls (10%)
  read_file:     14 calls ( 5%)

Deferring them behind tool_search adds a 2-turn discovery overhead
on nearly every non-trivial task, which outweighs the schema savings.
They stay always-core; write_file, patch, and heavier tools remain
deferrable.
…es + add tests

- Remove terminal/read_file/search_files duplicates from DEFERRABLE list
- Thread config through resolve_underlying_call so deferred core tools
  can actually be invoked via tool_call bridge when defer_core=True
- Thread config through scoped_deferrable_names for the same reason
- Update stale module docstring (core tools invariant is now conditional)
- Add tests covering the defer_core=True code paths (was: zero coverage)
- Remove unrelated paplay addition from voice_mode.py
- Add defer_core key to DEFAULTS in hermes_cli/config.py with comment
- Fix stale 'core tools are NEVER deferred' comment in config
- Update tool-search.md: revise info box, add defer_core to config table
- Add e2e bridge dispatch test for defer_core=True path
…nly mode

When defer_always_core: true is set (alongside defer_core: true), all
built-in tools including terminal, memory, and delegate_task are deferred
behind the bridge.  Only tool_search / tool_describe / tool_call remain
visible on every call (~1.6 KB vs ~15 KB with defer_core alone, ~94%
reduction from stock Hermes).

Changes:
- Add defer_always_core field to ToolSearchConfig dataclass
- _core_tool_names() returns frozenset() when defer_always_core=True
- is_deferrable_tool_name(): None registry entry returns defer_always_core
  instead of hardcoded False (built-in tools not in registry are now
  correctly deferrable in fully-lazy mode)
- 6 new tests in TestDeferAlwaysCoreTrue covering config parsing,
  always-core deferral, bridge tool protection, and classify_tools

Tested: fresh hermes session with defer_always_core=true sees exactly
3 tools (tool_search, tool_describe, tool_call). All 51 tool_search
tests pass.
…dd missing tests

Fixes three issues raised in code review:

1. Comment said 'Requires defer_core=True' but it wasn't enforced — could
   confuse users. Now from_raw() auto-promotes defer_core=True whenever
   defer_always_core=True, so users only need one flag in config.yaml.

2. Added E2E test: assemble_tool_defs() with defer_always_core=True activates
   and leaves exactly the 3 bridge tools visible.

3. Added test: scoped_deferrable_names() with defer_always_core=True correctly
   includes always-core tools (the runtime security gate path).

4. Added test: defer_always_core=True alone (without defer_core=True) correctly
   auto-promotes defer_core and works as expected.

All 54 tool_search tests pass.
@alt-glitch alt-glitch removed the comp/cli CLI entry point, hermes_cli/, setup wizard label Jul 30, 2026
@chrisribe

Copy link
Copy Markdown
Author

@alt-glitch @teknium1 friendly bump ;)

@chrisribe
chrisribe requested a review from teknium1 July 30, 2026 18:48
@chrisribe

chrisribe commented Jul 31, 2026

Copy link
Copy Markdown
Author

@OutThisLife maybe ? Sorry expanding pokes, do not want to be a nuisance but.
"Ah sorry did not see this comment, someone saw this PR! So you can ignore I guess.... have a nice day ;)"
#60181 (comment)

@chrisribe

Copy link
Copy Markdown
Author

Quick technical refresh on current HEAD 9c74a0ac5 (rebased/merged with latest main):

  • Runtime path is now threaded for deferred-core calls (classification + scoped bridge dispatch): tools/tool_search.py (is_deferrable_tool_name, classify_tools, scoped_deferrable_names) plus model_tools.py / agent/tool_executor.py.
  • Added runtime-focused tests: test_resolve_underlying_call_allows_deferrable_core_tool_with_defer_core_true, test_scoped_deferrable_names_defer_core_true, test_assemble_tool_defs_defer_always_core_e2e, test_scoped_deferrable_names_defer_always_core.
  • Docs include both flags in website/docs/user-guide/features/tool-search.md (defer_core, defer_always_core).

If maintainers prefer defer_toolsets over defer_core/defer_always_core, I can port these runtime/test fixes to that shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/tools Tool registry, model_tools, toolsets needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants