Skip to content

feat(plugins): allow pre_tool_call hooks to block tool execution#4610

Closed
oredsecurity wants to merge 1 commit into
NousResearch:mainfrom
oredsecurity:feature/pre-tool-call-enforcement
Closed

feat(plugins): allow pre_tool_call hooks to block tool execution#4610
oredsecurity wants to merge 1 commit into
NousResearch:mainfrom
oredsecurity:feature/pre-tool-call-enforcement

Conversation

@oredsecurity

Copy link
Copy Markdown

Currently pre_tool_call hooks are fire-and-forget: the return value is discarded and exceptions are swallowed. Plugins can observe tool calls but cannot enforce policy.

This checks the return value of pre_tool_call hooks. If any hook returns {"block": True, "reason": "..."}, the tool call is aborted and the reason is returned to the LLM as a tool error result.

Backward-compatible convention:

  • return None: proceed (unchanged)
  • return {"block": True, "reason": "..."}: abort tool call
  • return anything else: ignored (forward-compatible)

Enables security policy plugins, rate limiters, and approval workflows.

Includes 5 tests covering: blocking, passthrough, targeted blocking, multiple hooks, and exception safety.

@oredsecurity oredsecurity force-pushed the feature/pre-tool-call-enforcement branch from 75c1142 to 026004f Compare April 13, 2026 23:23
teknium1 pushed a commit that referenced this pull request Apr 14, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR #5385 (gianfrancopiana) and PR #4610 (oredsecurity).
teknium1 pushed a commit that referenced this pull request Apr 14, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR #5385 (gianfrancopiana) and PR #4610 (oredsecurity).
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #9377. Your PR was submitted first and established the blocking convention ({"block": true} / {"action": "block"}). The salvage PR covers both execution paths (model_tools + agent-level tools) building on the foundation you and @gianfrancopiana laid. Thanks for the original feature request!

@teknium1 teknium1 closed this Apr 14, 2026
hermes-agent-dhabibi pushed a commit to hermes-agent-dhabibi/hermes-agent that referenced this pull request Apr 14, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR NousResearch#5385 (gianfrancopiana) and PR NousResearch#4610 (oredsecurity).
ulasbilgen pushed a commit to ulasbilgen/hermes-adhd-agent that referenced this pull request May 1, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR NousResearch#5385 (gianfrancopiana) and PR NousResearch#4610 (oredsecurity).
aj-nt pushed a commit to aj-nt/hermes-agent that referenced this pull request May 1, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR NousResearch#5385 (gianfrancopiana) and PR NousResearch#4610 (oredsecurity).
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR NousResearch#5385 (gianfrancopiana) and PR NousResearch#4610 (oredsecurity).
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR NousResearch#5385 (gianfrancopiana) and PR NousResearch#4610 (oredsecurity).
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
Plugins can now return {"action": "block", "message": "reason"} from
their pre_tool_call hook to prevent a tool from executing. The error
message is returned to the model as a tool result so it can adjust.

Covers both execution paths: handle_function_call (model_tools.py) and
agent-level tools (run_agent.py _invoke_tool + sequential/concurrent).
Blocked tools skip all side effects (counter resets, checkpoints,
callbacks, read-loop tracker).

Adds skip_pre_tool_call_hook flag to avoid double-firing the hook when
run_agent.py already checked and then calls handle_function_call.

Salvaged from PR NousResearch#5385 (gianfrancopiana) and PR NousResearch#4610 (oredsecurity).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants