Skip to content

feat: add pre_tool_call rewrite support for plugin tool arg transformation - #19305

Closed
elasticdotventures wants to merge 6 commits into
NousResearch:mainfrom
PromptExecution:feat/pre-tool-rewrite-hook
Closed

elasticdotventures wants to merge 6 commits into
NousResearch:mainfrom
PromptExecution:feat/pre-tool-rewrite-hook

Conversation

@elasticdotventures

Copy link
Copy Markdown

Summary

Adds get_pre_tool_call_directives() — a single-hook function that fires pre_tool_call once and returns both block and rewrite directives. Plugins can now return:

{"action": "block", "message": "Reason"}   # existing
{"action": "rewrite", "args": {...}}         # new — transform tool args

Motivation

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.py to add custom backends, this provides a general plugin extension point.

Changes

3 files, +85 / -35 lines:

File Change
hermes_cli/plugins.py get_pre_tool_call_directives() — fires hook once, returns (block_msg, rewritten_args). Backward-compat aliases preserved.
run_agent.py Updated all 3 call sites (_invoke_tool, concurrent, sequential) to use directives
model_tools.py Updated handle_function_call to use directives

Backward 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.

…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.

Copilot AI 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.

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 single pre_tool_call hook invocation.
  • Updated run_agent.py tool execution paths to consume block/rewrite directives before dispatching tools.
  • Updated model_tools.py dispatch 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.

Comment thread run_agent.py Outdated
Comment on lines +9452 to +9455
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:
Comment thread run_agent.py
Comment on lines +9445 to +9448
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)
Comment thread hermes_cli/plugins.py
Comment on lines +1211 to +1216
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
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels May 3, 2026
@alt-glitch

Copy link
Copy Markdown

Implements #18988. Related to #18148 (runtime extension hooks) and #11816 (pre_tool_call approve action).

1 similar comment
@alt-glitch

Copy link
Copy Markdown

Implements #18988. Related to #18148 (runtime extension hooks) and #11816 (pre_tool_call approve action).

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.
elasticdotventures added a commit to elasticdotventures/_b00t_ that referenced this pull request May 4, 2026
…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
elasticdotventures added a commit to elasticdotventures/_b00t_ that referenced this pull request May 4, 2026
…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
elasticdotventures added a commit to elasticdotventures/_b00t_ that referenced this pull request May 4, 2026
…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>
elasticdotventures added a commit to elasticdotventures/_b00t_ that referenced this pull request May 4, 2026
#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.
elasticdotventures and others added 2 commits May 30, 2026 18:02
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 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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) and tool_request rewriting (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's run_agent.py dispatch edits therefore target superseded execution paths.
  • The added plugins/b00t and plugins/h3rmes_capability directories 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_call rewrite API remains necessary beyond tool_request, redesign it on the current agent/tool_executor.py paths and add coverage for checkpoint, guardrail, sequential, concurrent, and direct-dispatch ordering.

Automated hermes-sweeper review.

Comment thread plugins/b00t/plugin.yaml
@@ -0,0 +1,9 @@
name: b00t

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@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-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 12, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

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.

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

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/plugins Plugin system and bundled plugins 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 sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants