Skip to content

feat: add before_tool_call_hooks for pre-execution tool-call gating (fixes #2557)#2560

Open
kuangmi-bit wants to merge 1 commit into
huggingface:mainfrom
kuangmi-bit:feat/before-tool-call-hooks
Open

feat: add before_tool_call_hooks for pre-execution tool-call gating (fixes #2557)#2560
kuangmi-bit wants to merge 1 commit into
huggingface:mainfrom
kuangmi-bit:feat/before-tool-call-hooks

Conversation

@kuangmi-bit

Copy link
Copy Markdown

Summary

Adds a supported before_tool_call_hooks extension point to MultiStepAgent, enabling pre-execution policy gates before tool calls with side effects. Fixes #2557.

Problem

smolagents has step_callbacks but they fire after tool execution via _finalize_step(). There is no way to intercept and potentially block a tool call before it runs — only post-hoc observation of what already happened.

This is a gap compared to CrewAI (before_tool_call hook) and LangChain (wrap_tool_call middleware), both of which let a host application enforce a policy boundary before tools with real-world side effects execute.

Solution

Three changes across 2 files:

  1. AgentToolCallBlockedError (new exception in utils.py) — raised when a hook blocks execution.

  2. before_tool_call_hooks parameter on MultiStepAgent.__init__() — a list of callables, each receiving (tool_name, arguments, agent) and returning False to block or True/truthy to allow.

  3. Hook invocation in process_single_tool_call() before execute_tool_call() — hooks are iterated before any tool runs. Exceptions from hooks (other than AgentToolCallBlockedError) are caught and logged as warnings so one faulty hook doesn't break the agent loop.

Usage

def security_gate(tool_name: str, arguments: dict, agent) -> bool:
    if tool_name == "install_package":
        return False  # block
    return True

agent = CodeAgent(
    tools=[...],
    before_tool_call_hooks=[security_gate],
)

Design decisions

  • Follows the existing step_callbacks / final_answer_checks pattern (simple callable list, no registry class)
  • Minimal API surface — just a list, no dict/registry needed since there's only one event type (before tool call)
  • Fault-tolerant: broken hooks warn instead of crashing the agent
  • Backward-compatible: default is None, no behavior change for existing users

Add a supported extension point that lets host applications enforce
policy before a tool with side effects executes — block, allow, or
log — without subclassing or overriding internal methods.

Changes:
- Add AgentToolCallBlockedError to utils.py
- Add before_tool_call_hooks parameter to MultiStepAgent.__init__()
- Invoke hooks in process_single_tool_call() before execute_tool_call()
- Each hook receives (tool_name, arguments, agent) and returns False
  to block or True/truthy to allow. Hooks that raise exceptions are
  caught and logged as warnings so one faulty hook doesn't break the
  agent loop.

This mirrors CrewAI's before_tool_call hook and LangChain's
wrap_tool_call middleware pattern, giving smolagents a first-class
security boundary for MCP server connections, package installs,
and other side-effecting tools.

Fixes huggingface#2557
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.

Feature request: a supported pre-execution tool-call hook (no equivalent to CrewAI's before_tool_call / LangChain's wrap_tool_call today)

1 participant