feat: add pre_tool_call rewrite support for plugin tool arg transformation - #19305
elasticdotventures wants to merge 6 commits into
Conversation
…ation Adds get_pre_tool_call_directives() that fires pre_tool_call hook ONCE and returns both block_message and rewritten_args. Existing get_pre_tool_call_block_message() kept as backward-compat alias. New get_pre_tool_call_rewrite() alias added. Updates all 3 call sites (_invoke_tool, concurrent loop, sequential loop) and handle_function_call in model_tools.py to use the combined function.
There was a problem hiding this comment.
Pull request overview
This PR adds a richer pre_tool_call plugin hook contract so plugins can rewrite tool arguments before execution, not just block the call. It extends the plugin layer and updates the agent/tool dispatch paths that consume pre-tool directives.
Changes:
- Added
hermes_cli.plugins.get_pre_tool_call_directives()to collect block and rewrite directives from a singlepre_tool_callhook invocation. - Updated
run_agent.pytool execution paths to consume block/rewrite directives before dispatching tools. - Updated
model_tools.pydispatch to honor rewritten args when the pre-tool hook is evaluated there.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
run_agent.py |
Swaps pre-tool block checks over to the new directives helper in _invoke_tool, concurrent execution, and sequential execution. |
model_tools.py |
Uses the new directives helper in handle_function_call so registry-dispatched tools can be blocked or rewritten. |
hermes_cli/plugins.py |
Introduces the single-pass directive helper and keeps compatibility wrappers for legacy block-only callers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| except Exception: | ||
| block_message = None | ||
| pass | ||
|
|
||
| if block_message is not None: | ||
| block_result = json.dumps({"error": block_message}, ensure_ascii=False) | ||
| else: | ||
| if block_result is None and _block_msg is None: |
| elif _rewritten is not None: | ||
| function_args = _rewritten | ||
| # Re-check guardrails with rewritten args | ||
| guardrail_decision = self._tool_guardrails.before_call(function_name, function_args) |
| elif action == "rewrite" and rewritten_args is None: | ||
| new_args = result.get("args") | ||
| if isinstance(new_args, dict): | ||
| rewritten_args = new_args | ||
|
|
||
| return block_message, rewritten_args |
1 similar comment
Routes terminal commands through b00t hive run --dry-run guards. Intercepts pre_tool_call hook to block, warn, or rewrite commands. Handles pip->uv, docker->podman, main-branch protection, and more.
…ite patch Vendor submodule pointing to PromptExecution/hermes-agent-b00t on feat/pre-tool-rewrite-hook branch. Contains the get_pre_tool_call_directives() patch required for b00t guard interposition via Hermes plugin hooks. Upstream PR: NousResearch/hermes-agent#19305 Internal PR: PromptExecution/hermes-agent-b00t#1
…ite patch Vendor submodule pointing to PromptExecution/hermes-agent-b00t on feat/pre-tool-rewrite-hook branch. Contains the get_pre_tool_call_directives() patch required for b00t guard interposition via Hermes plugin hooks. Upstream PR: NousResearch/hermes-agent#19305 Internal PR: PromptExecution/hermes-agent-b00t#1
…tocol (#369) * chore: remove plantuml-server embedded repo from git index * feat: guard escalation, parser stages, b00t-ast CLI, b00t-py bindings, violation persistence - Guard violation counter with JSONL persistence (~/.b00t/guard-violations.jsonl) - 🦨→💩 escalation: Warn→Block when violation_count >= repeat_threshold - check_guards() auto-persists violations on every match - K0mmand3rStage guards: pattern = { stage = "pre_parse" } in hive-guards.hive.toml - parser_stages wired into KmdLine::parse() at 7 phases - b00t-ast CLI binary: b00t-ast dir <path> [--format json|mcp|counts] - b00t-py: guard_check, emoji_lookup, register_stage_guard bindings - KmdLine fields made pub for serde serialization - Schema datums moved to _b00t_/schema/ (uppercase convention) - k0mmand3r crate edition 2024, clean lints - Rust 2024: #![allow]→removed, set_var unsafe wrappers - b00t_env_backend.py promoted from DESIGN to working Python backend - Hermes backend symlinked: just hermes-backend-enable * chore: add hermes-agent-b00t vendor submodule with pre_tool_call rewrite patch Vendor submodule pointing to PromptExecution/hermes-agent-b00t on feat/pre-tool-rewrite-hook branch. Contains the get_pre_tool_call_directives() patch required for b00t guard interposition via Hermes plugin hooks. Upstream PR: NousResearch/hermes-agent#19305 Internal PR: PromptExecution/hermes-agent-b00t#1 * feat: add SCM convention guards — branch naming, main protection, conventional commits New hive guards block or warn before git commands reach the shell: - BLOCK: git checkout main/master — use feature branches - BLOCK: git push origin main — use PRs instead - BLOCK: git merge main — use gh pr merge - WARN: git checkout -b without type/ — use feat/fix/chore/ prefix - WARN: git commit -m without colon — use Conventional Commits format * feat: add regex_match() to Rhai engine + SCM convention guards - Registered regex_match(cmd, pattern) on Rhai engine in hive.rs for future guard pattern matching - Added 5 SCM guards to hive-guards.hive.toml: BLOCK: git checkout main/master, git push origin main, git merge main WARN: branch without type/ prefix, commit without conventional format - All guards use simple cmd.contains() — readable, no escaping hell * feat: add b00t guard interposition Hermes plugin Bumps vendor/hermes-agent-b00t to include the new plugins/b00t/ directory with pre_tool_call hook that routes terminal commands through b00t hive run --dry-run guard evaluation. * crypto-sign: ed25519 signing for peer_facts in IrontologyPeerStore * hive-peers: gossip, mDNS discover, list --health, peer GC * docs: initial review plan Agent-Logs-Url: https://github.com/elasticdotventures/_b00t_/sessions/b13588c4-07c5-4b06-8575-5be55c579fb1 Co-authored-by: elasticdotventures <35611074+elasticdotventures@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
#373) * feat: guard escalation, parser stages, b00t-ast CLI, b00t-py bindings, violation persistence - Guard violation counter with JSONL persistence (~/.b00t/guard-violations.jsonl) - 🦨→💩 escalation: Warn→Block when violation_count >= repeat_threshold - check_guards() auto-persists violations on every match - K0mmand3rStage guards: pattern = { stage = "pre_parse" } in hive-guards.hive.toml - parser_stages wired into KmdLine::parse() at 7 phases - b00t-ast CLI binary: b00t-ast dir <path> [--format json|mcp|counts] - b00t-py: guard_check, emoji_lookup, register_stage_guard bindings - KmdLine fields made pub for serde serialization - Schema datums moved to _b00t_/schema/ (uppercase convention) - k0mmand3r crate edition 2024, clean lints - Rust 2024: #![allow]→removed, set_var unsafe wrappers - b00t_env_backend.py promoted from DESIGN to working Python backend - Hermes backend symlinked: just hermes-backend-enable * chore: add hermes-agent-b00t vendor submodule with pre_tool_call rewrite patch Vendor submodule pointing to PromptExecution/hermes-agent-b00t on feat/pre-tool-rewrite-hook branch. Contains the get_pre_tool_call_directives() patch required for b00t guard interposition via Hermes plugin hooks. Upstream PR: NousResearch/hermes-agent#19305 Internal PR: PromptExecution/hermes-agent-b00t#1 * feat: add SCM convention guards — branch naming, main protection, conventional commits New hive guards block or warn before git commands reach the shell: - BLOCK: git checkout main/master — use feature branches - BLOCK: git push origin main — use PRs instead - BLOCK: git merge main — use gh pr merge - WARN: git checkout -b without type/ — use feat/fix/chore/ prefix - WARN: git commit -m without colon — use Conventional Commits format * feat: add regex_match() to Rhai engine + SCM convention guards - Registered regex_match(cmd, pattern) on Rhai engine in hive.rs for future guard pattern matching - Added 5 SCM guards to hive-guards.hive.toml: BLOCK: git checkout main/master, git push origin main, git merge main WARN: branch without type/ prefix, commit without conventional format - All guards use simple cmd.contains() — readable, no escaping hell * crypto-sign: ed25519 signing for peer_facts in IrontologyPeerStore * chore: update vendor/l3dg3rr submodule to ledgrrr (rebranded upstream) - Submodule URL: https://github.com/PromptExecution/l3dg3rr → git@github.com:PromptExecution/ledgrrr - Submodule pointer: 1ed3b3d → 2168595 (includes PR #80, dashboard-generated-panels-51-rebased) - Remote changed from HTTPS to SSH for consistent auth
Adds b00t Integration category to COMMAND_REGISTRY: /b00t routes any command to b00t-cli, /hive is an alias for /b00t hive. Adds h3rmes-capability plugin that checks subsystem health (b00t-cli, b00t-mcp, irontology-mcp, codebase-memory, guard-plugin) on session start and auto-remediates critical/high gaps.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Brian Horakh <35611074+elasticdotventures@users.noreply.github.com>
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the concrete single-fire design and the guard-interposition consumer.
Problems
- Current main already exposes the behavior-changing seam this implementation needs:
PluginContext.register_middleware()(hermes_cli/plugins.py:1175-1192) andtool_requestrewriting (hermes_cli/middleware.py:120-162). The active executor applies it before plugin blocking, guardrails, and checkpoint preflight (agent/tool_executor.py:388-447,:1021-1057). The PR'srun_agent.pydispatch edits therefore target superseded execution paths. - The added
plugins/b00tandplugins/h3rmes_capabilitydirectories are third-party product integrations. Repository policy requires those to ship as standalone user-installed plugins (AGENTS.md:797-813).
Suggested changes
- Split the b00t/h3rmes integrations and CLI commands into a standalone plugin repository.
- If a distinct
pre_tool_callrewrite API remains necessary beyondtool_request, redesign it on the currentagent/tool_executor.pypaths and add coverage for checkpoint, guardrail, sequential, concurrent, and direct-dispatch ordering.
Automated hermes-sweeper review.
| @@ -0,0 +1,9 @@ | |||
| name: b00t | |||
There was a problem hiding this comment.
This introduces an in-tree integration for an external product. Repository policy requires new third-party integrations to ship as standalone plugins installed under ~/.hermes/plugins/ or via a pip entry point; please move this integration (and h3rmes_capability) out of the core tree.
|
Closing in favor of the same capability landing on main via PR #87482 (#87482), salvaged from #28953. Your PR was submitted first — 16 days ahead — and proposed the identical design point (single-fire pre_tool_call hook returning both block and rewrite directives). #28953's implementation was chosen as the merge vehicle because it had been kept current against main, carried tests + docs, and had already been split per the #41457 triage — but you are credited in the merged PR body as the earliest submitter of this capability. Thanks for the contribution, and sorry it sat as long as it did. |
Summary
Adds
get_pre_tool_call_directives()— a single-hook function that firespre_tool_callonce and returns both block and rewrite directives. Plugins can now return:{"action": "block", "message": "Reason"} # existing {"action": "rewrite", "args": {...}} # new — transform tool argsMotivation
Guard interposition (command sanitization, path normalization, security middleware) needs the ability to transform tool arguments before execution, not just block them. Rather than forking
terminal_tool.pyto add custom backends, this provides a general plugin extension point.Changes
3 files, +85 / -35 lines:
hermes_cli/plugins.pyget_pre_tool_call_directives()— fires hook once, returns(block_msg, rewritten_args). Backward-compat aliases preserved.run_agent.py_invoke_tool, concurrent, sequential) to use directivesmodel_tools.pyhandle_function_callto use directivesBackward Compatibility
get_pre_tool_call_block_message()kept as an exact alias — zero breakage for existing plugins and tests. All 111 plugin/shell-hook tests pass.Rationale
The hook fires once per tool call instead of N times (once per directive type). This is both more efficient and semantically cleaner — a plugin only needs one callback pass to express its intent.