Feature Area
Core functionality
Is your feature request related to an existing bug?
Not a bug, but multiple open issues and PRs request tool-level authorization:
CrewAI's existing guardrail system (Task.guardrail / Task.guardrails) validates output after task completion. The BeforeToolCallHook protocol in crewai.hooks.types can block tool execution by returning False. What's missing is a standard provider contract that sits between the hook system and authorization logic, so users can plug in any policy engine without writing raw hooks.
Describe the solution you'd like
A GuardrailProvider protocol that any authorization provider can implement. It plugs into the existing BeforeToolCallHook system - no changes to the tool execution pipeline.
Interface (~40 lines)
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Protocol, runtime_checkable
@dataclass
class GuardrailRequest:
"""Context passed to the provider for each tool call."""
tool_name: str
tool_input: dict
agent_role: str | None = None
task_description: str | None = None
crew_id: str | None = None
timestamp: str = "" # ISO 8601
@dataclass
class GuardrailDecision:
"""Provider's allow/deny verdict."""
allow: bool
reason: str | None = None
metadata: dict = field(default_factory=dict)
@runtime_checkable
class GuardrailProvider(Protocol):
"""Contract for pluggable tool-call authorization."""
name: str
def evaluate(self, request: GuardrailRequest) -> GuardrailDecision:
"""Evaluate whether a tool call should proceed.
Returns a GuardrailDecision. If allow is False, the tool call
is blocked and `reason` is surfaced to the agent.
"""
...
def health_check(self) -> bool:
"""Optional readiness probe. Default: True."""
...
How it plugs in
The provider registers itself as a BeforeToolCallHook. A thin adapter bridges the protocol:
from crewai.hooks.tool_hooks import register_before_tool_call_hook
def enable_guardrail(provider: GuardrailProvider, *, fail_closed: bool = True):
"""Wire a GuardrailProvider into CrewAI's hook system."""
def _hook(context) -> bool | None:
request = GuardrailRequest(
tool_name=context.tool_name,
tool_input=context.tool_input,
agent_role=getattr(context.agent, "role", None),
task_description=getattr(context.task, "description", None),
)
try:
decision = provider.evaluate(request)
except Exception:
return False if fail_closed else None
if not decision.allow:
return False # blocks tool execution
return None # allow
register_before_tool_call_hook(_hook)
Configuration (optional, for YAML-based crews)
# crew.yaml or crewai config
guardrail_provider:
enabled: true
fail_closed: true
provider: "my_package.MyGuardrailProvider"
config:
# provider-specific settings
policy_file: "./policies/default.yaml"
What this enables
Simple (no external dependencies):
- Block tools by name (e.g., deny
ShellTool in production)
- Restrict file paths per agent role
- Rate-limit tool calls per crew run
Advanced (via provider packages):
- Policy-as-code with declarative YAML rules
- Per-agent capability scoping in multi-agent crews
- Audit trails with signed decisions
- Remote policy evaluation for enterprise deployments
Describe alternatives you've considered
1. Raw BeforeToolCallHook only (status quo)
Works today - any function returning False blocks a tool. But there's no contract for what context the hook receives, no standard for deny reasons, no fail_closed behavior, and no way to swap providers without rewriting hook logic. Each implementation is ad-hoc.
2. Extend Task guardrails to cover tool calls
Task guardrails (Task.guardrail) validate output after completion - a different concern. Tool-call authorization must happen before execution, per-call, across all tasks. Mixing the two conflates output validation with access control.
3. Middleware parameter on Agent (like #4682 proposes)
The loop detection middleware proposal (#4682) adds a middleware parameter to agents. A guardrail provider could be expressed as middleware, but tool authorization is cross-cutting - it should apply to all agents in a crew, not be configured per-agent. The hook system is the right level.
Additional context
Existing hook infrastructure is sufficient. The BeforeToolCallHook protocol in crewai.hooks.types already supports returning False to block execution. The ToolCallHookContext provides tool_name, tool_input, agent, task, and crew. The GuardrailProvider protocol is a standardization layer on top of this - not a new execution path.
Proven pattern. APort Agent Guardrails implements this provider pattern for multiple agent frameworks, demonstrating that a thin protocol over existing hooks is viable without core changes.
Scope boundary. This proposal covers: the GuardrailProvider protocol, the enable_guardrail() adapter, and documentation. It does NOT propose: RBAC, multi-tenant policies, bundled providers, changes to the agent loop, or modifications to existing task guardrails.
Willingness to Contribute
Yes, I'd be happy to submit a pull request.
Feature Area
Core functionality
Is your feature request related to an existing bug?
Not a bug, but multiple open issues and PRs request tool-level authorization:
middlewareparameter on agents)CrewAI's existing guardrail system (
Task.guardrail/Task.guardrails) validates output after task completion. TheBeforeToolCallHookprotocol increwai.hooks.typescan block tool execution by returningFalse. What's missing is a standard provider contract that sits between the hook system and authorization logic, so users can plug in any policy engine without writing raw hooks.Describe the solution you'd like
A
GuardrailProviderprotocol that any authorization provider can implement. It plugs into the existingBeforeToolCallHooksystem - no changes to the tool execution pipeline.Interface (~40 lines)
How it plugs in
The provider registers itself as a
BeforeToolCallHook. A thin adapter bridges the protocol:Configuration (optional, for YAML-based crews)
What this enables
Simple (no external dependencies):
ShellToolin production)Advanced (via provider packages):
Describe alternatives you've considered
1. Raw
BeforeToolCallHookonly (status quo)Works today - any function returning
Falseblocks a tool. But there's no contract for what context the hook receives, no standard for deny reasons, nofail_closedbehavior, and no way to swap providers without rewriting hook logic. Each implementation is ad-hoc.2. Extend Task guardrails to cover tool calls
Task guardrails (
Task.guardrail) validate output after completion - a different concern. Tool-call authorization must happen before execution, per-call, across all tasks. Mixing the two conflates output validation with access control.3. Middleware parameter on Agent (like #4682 proposes)
The loop detection middleware proposal (#4682) adds a
middlewareparameter to agents. A guardrail provider could be expressed as middleware, but tool authorization is cross-cutting - it should apply to all agents in a crew, not be configured per-agent. The hook system is the right level.Additional context
Existing hook infrastructure is sufficient. The
BeforeToolCallHookprotocol increwai.hooks.typesalready supports returningFalseto block execution. TheToolCallHookContextprovidestool_name,tool_input,agent,task, andcrew. TheGuardrailProviderprotocol is a standardization layer on top of this - not a new execution path.Proven pattern. APort Agent Guardrails implements this provider pattern for multiple agent frameworks, demonstrating that a thin protocol over existing hooks is viable without core changes.
Scope boundary. This proposal covers: the
GuardrailProviderprotocol, theenable_guardrail()adapter, and documentation. It does NOT propose: RBAC, multi-tenant policies, bundled providers, changes to the agent loop, or modifications to existing task guardrails.Willingness to Contribute
Yes, I'd be happy to submit a pull request.