Skip to content

tool_hooks cannot access the Function object (tool_definition) they intercept #7687

@wuji3

Description

@wuji3

Problem

tool_hooks middleware cannot access the Function object of the tool they intercept. This makes it impossible for hooks to dynamically modify tool attributes — most notably stop_after_tool_call — at runtime based on execution context.

Concrete use case: Review/Advisor middleware

Consider a hook that reviews an agent's decision before allowing a tool to execute:

async def review_hook(function_name, function_call, arguments, run_context):
    if function_name != "submit_decision":
        return await function_call(**arguments)
    
    decision = await ask_advisor(run_context.messages, arguments)
    
    if decision["approve"]:
        return await function_call(**arguments)  # Execute normally
    else:
        # Problem: we want the agent to CONTINUE the loop and retry,
        # but stop_after_tool_call=True on the Function object means
        # the agent will stop regardless.
        return "REJECTED: please reconsider and try again"

When a tool has stop_after_tool_call=True, the agent terminates after execution regardless of whether the hook approved or rejected. The hook has no way to override this because it has no access to the Function object.

Current workaround

There is none within the tool_hooks API. Users must either:

  • Avoid stop_after_tool_call entirely (losing the auto-stop feature)
  • Use pre_hook to raise AgentRunException (but this doesn't support async advisor calls or conditional logic cleanly)

Proposed solution

Add tool_definition to _build_hook_args parameter injection, following the existing pattern for agent, team, run_context, and arguments:

# In FunctionCall._build_hook_args():
if "tool_definition" in signature(hook).parameters:
    hook_args["tool_definition"] = self.function

This is a 2-line change. Hooks opt in by declaring tool_definition in their signature — fully backward-compatible.

With this fix, the review hook becomes:

async def review_hook(function_name, function_call, arguments, run_context, tool_definition):
    if function_name != "submit_decision":
        return await function_call(**arguments)
    
    decision = await ask_advisor(run_context.messages, arguments)
    
    if decision["approve"]:
        tool_definition.stop_after_tool_call = True
        return await function_call(**arguments)
    else:
        tool_definition.stop_after_tool_call = False  # Agent continues loop
        return "REJECTED: please reconsider"

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions