Skip to content

feat: add tool call release decisions#6165

Open
maxpetrusenkoagent wants to merge 6 commits into
crewAIInc:mainfrom
maxpetrusenkoagent:hermes/oss-pr-2026-06-15-crewai-6025
Open

feat: add tool call release decisions#6165
maxpetrusenkoagent wants to merge 6 commits into
crewAIInc:mainfrom
maxpetrusenkoagent:hermes/oss-pr-2026-06-15-crewai-6025

Conversation

@maxpetrusenkoagent

@maxpetrusenkoagent maxpetrusenkoagent commented Jun 15, 2026

Copy link
Copy Markdown

Summary

  • Add ToolCallDecision tri-state release-control results for before-tool hooks
  • Route PROCEED / NEEDS_REVIEW / SILENCE decisions through existing tool execution paths
  • Document release-control hook usage and add regression coverage

Closes #6025

Test Plan

  • uv run pytest lib/crewai/tests/hooks/test_tool_hooks.py::TestToolHooksIntegration::test_needs_review_decision_blocks_react_tool_execution lib/crewai/tests/hooks/test_tool_hooks.py::TestToolHooksIntegration::test_tool_call_decision_factories -q
  • uv run pytest lib/crewai/tests/hooks/test_tool_hooks.py -q
  • uv run ruff check --diff lib/crewai/src/crewai/hooks/tool_call_decision.py lib/crewai/src/crewai/hooks/tool_hooks.py lib/crewai/src/crewai/hooks/types.py lib/crewai/src/crewai/hooks/decorators.py lib/crewai/src/crewai/hooks/wrappers.py lib/crewai/src/crewai/agents/crew_agent_executor.py lib/crewai/src/crewai/experimental/agent_executor.py lib/crewai/src/crewai/utilities/agent_utils.py lib/crewai/src/crewai/utilities/tool_utils.py
  • uv run ruff format --check lib/crewai/src/crewai/hooks/tool_call_decision.py lib/crewai/src/crewai/hooks/tool_hooks.py lib/crewai/src/crewai/hooks/types.py lib/crewai/src/crewai/hooks/decorators.py lib/crewai/src/crewai/hooks/wrappers.py lib/crewai/src/crewai/agents/crew_agent_executor.py lib/crewai/src/crewai/experimental/agent_executor.py lib/crewai/src/crewai/utilities/agent_utils.py lib/crewai/src/crewai/utilities/tool_utils.py lib/crewai/tests/hooks/test_tool_hooks.py
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Before tool call hooks now support returning ToolCallDecision for release control, enabling PROCEED, NEEDS_REVIEW, or SILENCE decisions with optional reasons and review context.
  • Refactor

    • Improved hook execution logic with consistent decision resolution across agent executors.
  • Tests

    • Added comprehensive tests for new decision functionality, including edge cases and decision propagation.
  • Documentation

    • Updated tool hooks documentation with release control patterns and decision usage examples.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 58cce69b-c947-4936-977c-0bf9d166287d

📥 Commits

Reviewing files that changed from the base of the PR and between 0880481 and 0cad75e.

📒 Files selected for processing (4)
  • docs/edge/en/learn/tool-hooks.mdx
  • lib/crewai/src/crewai/hooks/tool_call_decision.py
  • lib/crewai/src/crewai/hooks/tool_hooks.py
  • lib/crewai/tests/hooks/test_tool_hooks.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/crewai/src/crewai/hooks/tool_hooks.py

📝 Walkthrough

Walkthrough

Introduces a ToolCallDecision dataclass and ToolCallDecisionType enum (PROCEED, NEEDS_REVIEW, SILENCE) as a structured return type for before-tool-call hooks. A new resolve_tool_call_decision helper normalizes hook return values into an optional block message, replacing the prior boolean flag logic across all four executor paths. Hook type contracts, decorators, and wrappers are updated accordingly.

Changes

ToolCallDecision release-control mediation

Layer / File(s) Summary
ToolCallDecision data model
lib/crewai/src/crewai/hooks/tool_call_decision.py
Defines ToolCallDecisionType enum with PROCEED, NEEDS_REVIEW, SILENCE. Adds frozen ToolCallDecision dataclass with factory classmethods, should_execute property, and block_message(tool_name) that composes agent-visible mediation messages including optional reason and serialized review context.
Hook type contracts and public API
lib/crewai/src/crewai/hooks/types.py, lib/crewai/src/crewai/hooks/wrappers.py, lib/crewai/src/crewai/hooks/decorators.py, lib/crewai/src/crewai/hooks/__init__.py
Expands BeforeToolCallHook protocol, BeforeToolCallHookType, BeforeToolCallHookCallable, BeforeToolCallHookMethod, and before_tool_call decorator overloads to use bool | ToolCallDecision | None. Exports ToolCallDecision and ToolCallDecisionType from the hooks package __all__.
resolve_tool_call_decision normalization helper
lib/crewai/src/crewai/hooks/tool_hooks.py
Adds resolve_tool_call_decision, which maps ToolCallDecision to None or block_message, treats False as a generic block, passes None/True through, and returns an invalid-type message for unrecognized hook returns. Updates register_before_tool_call_hook docstring.
Executor integration across all tool-call paths
lib/crewai/src/crewai/agents/crew_agent_executor.py, lib/crewai/src/crewai/experimental/agent_executor.py, lib/crewai/src/crewai/utilities/agent_utils.py, lib/crewai/src/crewai/utilities/tool_utils.py
Replaces hook_blocked boolean flags with hook_blocked_message: str | None in all four executor paths (crew executor, experimental executor, agent_utils native-tool path, and tool_utils sync/async paths). Each path now calls resolve_tool_call_decision and uses the returned message as the tool result when non-None.
Tests and documentation
lib/crewai/tests/hooks/test_tool_hooks.py, docs/edge/en/learn/tool-hooks.mdx
Adds integration test for needs_review hook blocking structured tool execution. Adds unit tests for factory methods, review_context copy isolation, and fail-closed behavior for invalid return types, cyclic contexts, and string decision values. Updates tool-hooks docs with expanded return type and a release_control example.

Sequence Diagram(s)

sequenceDiagram
  participant Agent as AgentExecutor
  participant Hook as BeforeToolCallHook
  participant Resolver as resolve_tool_call_decision
  participant Decision as ToolCallDecision
  participant Tool as ToolFunction

  Agent->>Hook: call(ToolCallHookContext)
  Hook-->>Agent: ToolCallDecision | bool | None
  Agent->>Resolver: resolve_tool_call_decision(hook_result, tool_name)
  alt result is ToolCallDecision
    Resolver->>Decision: should_execute?
    alt PROCEED
      Decision-->>Resolver: True
      Resolver-->>Agent: None
      Agent->>Tool: execute()
    else NEEDS_REVIEW or SILENCE
      Decision-->>Resolver: False
      Resolver->>Decision: block_message(tool_name)
      Decision-->>Resolver: formatted block message
      Resolver-->>Agent: block message (str)
      Agent-->>Agent: result = block message (skip tool)
    end
  else result is False
    Resolver-->>Agent: generic block message
    Agent-->>Agent: result = block message (skip tool)
  else result is None or True
    Resolver-->>Agent: None
    Agent->>Tool: execute()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • lorenzejay
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add tool call release decisions' accurately and concisely describes the main feature being introduced—runtime release-control decisions for tool execution.
Linked Issues check ✅ Passed The PR implements all core requirements from #6025: tri-state ToolCallDecision system (PROCEED, NEEDS_REVIEW, SILENCE), integration with before-tool-call hooks, and runtime authorization checkpoint between generation and execution across all tool execution paths.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the runtime release-control feature. Documentation, type definitions, hook decorators, executors, utilities, and tests are all focused on the tri-state decision system and its integration without unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@maxpetrusenkoagent maxpetrusenkoagent marked this pull request as ready for review June 18, 2026 15:21
@maxpetrusenkoagent

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@maxpetrusenkoagent

Copy link
Copy Markdown
Author

Review-fix update:

  • Synced the branch with current main using a clean merge commit (0880481c3).
  • Marked the PR ready for review so CodeRabbit would run instead of skipping the draft.
  • Focused local verification: uv run pytest lib/crewai/tests/hooks/test_tool_hooks.py -> 27 passed.
  • Bot state after update: GitGuardian completed neutral; CodeRabbit completed successfully after @coderabbitai review.

Remaining external blocker: repository GitHub Actions are still action_required and need maintainer approval to run.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@maxpetrusenkoagent Thanks for the thorough status update! Here's a quick summary of where things stand:

  • ✅ Branch synced with main
  • ✅ PR marked ready for review
  • ✅ Local test suite passing (27/27 in test_tool_hooks.py)
  • ✅ CodeRabbit review completed successfully
  • Remaining blocker: GitHub Actions are action_required — a repository maintainer needs to approve the workflow runs for the fork/branch before CI can execute

For the GitHub Actions blocker, you may want to ping a maintainer directly (e.g., via a comment tagging them) to request approval of the workflow runs. Once CI passes, the PR should be in a good state for merge consideration.

Is there anything else you'd like me to look into or review on this PR?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
lib/crewai/src/crewai/hooks/tool_call_decision.py (1)

41-42: ⚡ Quick win

frozen=True is undermined by aliasing mutable review_context.

On Line 41 / Line 54 / Line 67, non-empty dicts are stored by reference. External mutation after creation can still change decision payloads.

Suggested patch
 `@dataclass`(frozen=True)
 class ToolCallDecision:
@@
+    `@staticmethod`
+    def _copy_context(
+        review_context: dict[str, Any] | None,
+    ) -> dict[str, Any]:
+        return dict(review_context) if review_context is not None else {}
+
     `@classmethod`
     def proceed(
@@
         return cls(
             decision=ToolCallDecisionType.PROCEED,
             reason=reason,
-            review_context=review_context or {},
+            review_context=cls._copy_context(review_context),
         )
@@
         return cls(
             decision=ToolCallDecisionType.NEEDS_REVIEW,
             reason=reason,
-            review_context=review_context or {},
+            review_context=cls._copy_context(review_context),
         )
@@
         return cls(
             decision=ToolCallDecisionType.SILENCE,
             reason=reason,
-            review_context=review_context or {},
+            review_context=cls._copy_context(review_context),
         )

Also applies to: 54-55, 67-68

🤖 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/hooks/tool_call_decision.py` around lines 41 - 42, The
frozen=True dataclass is storing mutable dict objects by reference, allowing
external code to mutate the review_context after object creation despite the
frozen constraint. To fix this, create defensive copies of the review_context
dict in all three locations where it appears (at lines 41-42, 54-55, and 67-68)
by replacing review_context or {} with dict(review_context or {}) to ensure a
copy is stored rather than a reference to the original mutable object.
🤖 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 `@docs/v1.13.0/en/learn/tool-hooks.mdx`:
- Around line 28-55: The file docs/v1.13.0/en/learn/tool-hooks.mdx is part of a
frozen version snapshot documentation and should not be modified. Move all the
changes you made to this file (the before_hook function documentation and the
release_control example with ToolCallDecision) to the editable source
documentation location instead, typically found in the main docs folder without
the version number prefix (docs/en/learn/tool-hooks.mdx or similar). Keep the
docs/v1.13.0/ snapshot unchanged unless this is explicitly a [docs-freeze]
release-cut PR.

In `@lib/crewai/src/crewai/hooks/tool_hooks.py`:
- Around line 120-133: The function resolve_tool_call_decision currently fails
open by returning None at the end if the hook_result does not match any expected
types (ToolCallDecision instance or boolean False), which silently allows tool
execution for unexpected return values. Replace the final return None statement
with a blocking response that raises an exception or returns a block message
indicating an invalid hook return type, ensuring that any unexpected hook return
values fail closed by preventing tool execution rather than allowing it.

---

Nitpick comments:
In `@lib/crewai/src/crewai/hooks/tool_call_decision.py`:
- Around line 41-42: The frozen=True dataclass is storing mutable dict objects
by reference, allowing external code to mutate the review_context after object
creation despite the frozen constraint. To fix this, create defensive copies of
the review_context dict in all three locations where it appears (at lines 41-42,
54-55, and 67-68) by replacing review_context or {} with dict(review_context or
{}) to ensure a copy is stored rather than a reference to the original mutable
object.
🪄 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: 950aee1b-78de-4cdd-8efd-26750a092098

📥 Commits

Reviewing files that changed from the base of the PR and between c0fa66d and 0880481.

📒 Files selected for processing (12)
  • docs/v1.13.0/en/learn/tool-hooks.mdx
  • lib/crewai/src/crewai/agents/crew_agent_executor.py
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/src/crewai/hooks/__init__.py
  • lib/crewai/src/crewai/hooks/decorators.py
  • lib/crewai/src/crewai/hooks/tool_call_decision.py
  • lib/crewai/src/crewai/hooks/tool_hooks.py
  • lib/crewai/src/crewai/hooks/types.py
  • lib/crewai/src/crewai/hooks/wrappers.py
  • lib/crewai/src/crewai/utilities/agent_utils.py
  • lib/crewai/src/crewai/utilities/tool_utils.py
  • lib/crewai/tests/hooks/test_tool_hooks.py

Comment thread docs/v1.13.0/en/learn/tool-hooks.mdx Outdated
Comment on lines +28 to +55
def before_hook(context: ToolCallHookContext) -> bool | ToolCallDecision | None:
# Return False to block execution with a generic message
# Return ToolCallDecision.needs_review(...) to pause for review
# Return ToolCallDecision.silence(...) to suppress execution
# Return True, None, or ToolCallDecision.proceed(...) to allow execution
...
```

For runtime release-control, return a `ToolCallDecision` from a before-tool hook.
This lets a hook distinguish normal execution from calls that need review or should
be silenced before the tool runs:

```python
from crewai.hooks import ToolCallDecision, ToolCallHookContext, before_tool_call

@before_tool_call
def release_control(context: ToolCallHookContext) -> ToolCallDecision | None:
if context.tool_name == "send_email":
return ToolCallDecision.needs_review(
reason="Outbound email requires human approval",
review_context={"input": context.tool_input},
)

if context.tool_name == "post_to_social_media":
return ToolCallDecision.silence(reason="Public posting is disabled")

return ToolCallDecision.proceed()
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not modify frozen snapshot docs under docs/v*.

This file is under a versioned frozen-docs path. Move this update to the editable docs source and keep this snapshot unchanged (unless this is explicitly a [docs-freeze] release-cut PR).
As per coding guidelines, docs/v[0-9]*.[0-9]*.[0-9]*/**: Never modify files under docs/v*/ as they are frozen release snapshots, except in [docs-freeze] release-cut PRs.

🤖 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 `@docs/v1.13.0/en/learn/tool-hooks.mdx` around lines 28 - 55, The file
docs/v1.13.0/en/learn/tool-hooks.mdx is part of a frozen version snapshot
documentation and should not be modified. Move all the changes you made to this
file (the before_hook function documentation and the release_control example
with ToolCallDecision) to the editable source documentation location instead,
typically found in the main docs folder without the version number prefix
(docs/en/learn/tool-hooks.mdx or similar). Keep the docs/v1.13.0/ snapshot
unchanged unless this is explicitly a [docs-freeze] release-cut PR.

Source: Coding guidelines

Comment thread lib/crewai/src/crewai/hooks/tool_hooks.py Outdated
@maxpetrusenkoagent

Copy link
Copy Markdown
Author

Review-fix sweep update:

  • Fixed CodeRabbit's docs blocker by moving the ToolCallDecision docs update to docs/edge/en/learn/tool-hooks.mdx and removing the frozen docs/v1.13.0/... snapshot change from the final PR diff.
  • Fixed mutable review_context aliasing by defensively copying context in ToolCallDecision factories.
  • Fixed fail-open paths by making unexpected before-tool hook return values block tool execution instead of allowing it.
  • Autoreview found an additional fail-open edge case where malformed/string decision values or unserializable review context could raise during block message creation and get swallowed by executor hook error handling. Fixed that too.
  • Synced with current origin/main via merge commit 80a056094.

Verification run locally:

  • uv run pytest lib/crewai/tests/hooks/test_tool_hooks.py -q -> 31 passed, 37 warnings (existing deprecation warnings).
  • uv run ruff check lib/crewai/src/crewai/hooks/tool_call_decision.py lib/crewai/src/crewai/hooks/tool_hooks.py lib/crewai/tests/hooks/test_tool_hooks.py -> passed.
  • uv run ruff format --check lib/crewai/src/crewai/hooks/tool_call_decision.py lib/crewai/src/crewai/hooks/tool_hooks.py lib/crewai/tests/hooks/test_tool_hooks.py -> passed.
  • uv run mypy lib/crewai/src/crewai/hooks/tool_call_decision.py lib/crewai/src/crewai/hooks/tool_hooks.py -> passed.
  • git diff --check origin/main -> passed.
  • Final diff check confirms no docs/v*/ frozen snapshot files are changed.

Confidence: high for the review-fix scope. Remaining external state may still depend on repository Actions approval for forked PR workflows.

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] Runtime release-control mediation layer before agent/tool execution

1 participant