feat: Add crew-level before_tool_call and after_tool_call governance hooks#5890
feat: Add crew-level before_tool_call and after_tool_call governance hooks#5890devin-ai-integration[bot] wants to merge 1 commit into
Conversation
…hooks Adds optional before_tool_call and after_tool_call callback parameters to the Crew class, enabling per-crew governance middleware for tool call authorization. - before_tool_call(agent, tool_name, tool_input): Runs before each tool execution. Raise an exception to block the call. - after_tool_call(agent, tool_name, tool_input, tool_output): Runs after each tool execution for audit/logging. These crew-level hooks complement the existing global hook system, scoping governance policies to individual crew instances. Closes #5888 Co-Authored-By: João <joao@crewai.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
📝 WalkthroughWalkthroughAdded optional ChangesTool Call Hooks for Crew Governance
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lib/crewai/tests/hooks/test_tool_hooks.py (1)
825-1233: ⚡ Quick winAdd async-path hook coverage.
The new crew-level tests validate only
execute_tool_and_check_finality(sync). Please add at least one blocking and one post-call assertion foraexecute_tool_and_check_finalityto protect the async path.Example test shape
`@pytest.mark.asyncio` async def test_crew_before_tool_call_blocks_execution_async(): from crewai.utilities.tool_utils import aexecute_tool_and_check_finality # setup mocks + monkeypatch parse_tool_calling/ause # assert blocked message and no tool execution🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/crewai/tests/hooks/test_tool_hooks.py` around lines 825 - 1233, Tests only cover the synchronous path; add equivalent async tests that exercise the crew-level hooks for aexecute_tool_and_check_finality: create two new pytest.mark.asyncio tests—one where mock crew.before_tool_call raises (use aexecute_tool_and_check_finality and monkeypatch ToolUsage.parse_tool_calling and ToolUsage.ause) and assert the returned ToolResult contains the blocking message and that the tool use was not called, and another where crew.after_tool_call records the tool_output (monkeypatch parse_tool_calling and ToolUsage.ause to return a value) and assert after_tool_call received that output; reference functions aexecute_tool_and_check_finality, ToolUsage.parse_tool_calling and ToolUsage.ause to locate where to patch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/crewai/src/crewai/utilities/tool_utils.py`:
- Around line 118-123: The crew hook invocation is passing the raw tool name
(tool_calling.tool_name) which can desync governance checks; change the calls to
use the canonical sanitized_tool_name instead—specifically update the
crew_before_hook/before_tool_call invocation (and any similar crew hook calls)
to pass sanitized_tool_name (the variable used for resolved/canonical names) so
hooks receive the normalized identifier; ensure return behavior (ToolResult on
exception) remains unchanged and update the other occurrences mentioned (around
the other ranges) to the same sanitized_tool_name parameter.
---
Nitpick comments:
In `@lib/crewai/tests/hooks/test_tool_hooks.py`:
- Around line 825-1233: Tests only cover the synchronous path; add equivalent
async tests that exercise the crew-level hooks for
aexecute_tool_and_check_finality: create two new pytest.mark.asyncio tests—one
where mock crew.before_tool_call raises (use aexecute_tool_and_check_finality
and monkeypatch ToolUsage.parse_tool_calling and ToolUsage.ause) and assert the
returned ToolResult contains the blocking message and that the tool use was not
called, and another where crew.after_tool_call records the tool_output
(monkeypatch parse_tool_calling and ToolUsage.ause to return a value) and assert
after_tool_call received that output; reference functions
aexecute_tool_and_check_finality, ToolUsage.parse_tool_calling and
ToolUsage.ause to locate where to patch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d33e98b6-17bd-4458-abd6-82bc9d5a7727
📒 Files selected for processing (3)
lib/crewai/src/crewai/crew.pylib/crewai/src/crewai/utilities/tool_utils.pylib/crewai/tests/hooks/test_tool_hooks.py
| crew_before_hook = getattr(crew, "before_tool_call", None) if crew else None | ||
| if crew_before_hook: | ||
| try: | ||
| crew_before_hook(agent, tool_calling.tool_name, tool_input) | ||
| except Exception as e: | ||
| return ToolResult(str(e), False) |
There was a problem hiding this comment.
Use canonical tool names when invoking crew hooks.
Line 121/253 and Line 130/262 pass raw tool_calling.tool_name instead of sanitized_tool_name. This can desync governance checks from actual tool resolution and allow bypasses when names differ only by formatting/casing.
Proposed fix
- crew_before_hook(agent, tool_calling.tool_name, tool_input)
+ crew_before_hook(agent, sanitized_tool_name, tool_input)
...
- crew_after_hook(agent, tool_calling.tool_name, tool_input, tool_result)
+ crew_after_hook(agent, sanitized_tool_name, tool_input, tool_result)
...
- crew_before_hook(agent, tool_calling.tool_name, tool_input)
+ crew_before_hook(agent, sanitized_tool_name, tool_input)
...
- crew_after_hook(agent, tool_calling.tool_name, tool_input, tool_result)
+ crew_after_hook(agent, sanitized_tool_name, tool_input, tool_result)Also applies to: 127-132, 250-255, 259-264
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/crewai/src/crewai/utilities/tool_utils.py` around lines 118 - 123, The
crew hook invocation is passing the raw tool name (tool_calling.tool_name) which
can desync governance checks; change the calls to use the canonical
sanitized_tool_name instead—specifically update the
crew_before_hook/before_tool_call invocation (and any similar crew hook calls)
to pass sanitized_tool_name (the variable used for resolved/canonical names) so
hooks receive the normalized identifier; ensure return behavior (ToolResult on
exception) remains unchanged and update the other occurrences mentioned (around
the other ranges) to the same sanitized_tool_name parameter.
Summary
Adds optional
before_tool_callandafter_tool_callcallback parameters to theCrewclass, enabling per-crew governance middleware for tool call authorization as requested in #5888.These crew-level hooks complement the existing global hook system (
register_before_tool_call_hook/register_after_tool_call_hook), scoping governance policies to individual crew instances.before_tool_call(agent, tool_name, tool_input)— Runs before each tool execution. Raise any exception to block the call; the exception message is returned to the agent as the tool result.after_tool_call(agent, tool_name, tool_input, tool_output)— Runs after each tool execution for audit/logging. Exceptions in this callback are caught and logged to avoid disrupting the agent loop.Usage example
Changes
lib/crewai/src/crewai/crew.py: Addedbefore_tool_callandafter_tool_calloptionalSerializableCallablefieldslib/crewai/src/crewai/utilities/tool_utils.py: Invoke crew-level hooks in bothexecute_tool_and_check_finalityandaexecute_tool_and_check_finalitylib/crewai/tests/hooks/test_tool_hooks.py: 8 new tests inTestCrewLevelToolCallHooksclassCloses #5888
Review & Testing Checklist for Human
before_tool_callblocks tool execution when the callback raises — run a crew with a hook that raises on a specific tool and confirm the tool is never invokedafter_tool_callreceives the correcttool_outputafter executionNotes
Crew-level hooks run after global hooks (
register_before_tool_call_hook) but before tool execution. If either blocks, the tool is not executed.Link to Devin session: https://app.devin.ai/sessions/b76e0c6156684df4a930dfb1373c1b94
Summary by CodeRabbit
before_tool_call(invoked before each tool runs with agent, tool name, and input; can block execution by raising an exception) andafter_tool_call(invoked after each tool completes with the tool output).