Skip to main content

CrewAI

The CrewAI adapter wraps a Crew so every task execution is recorded through ProofRail. Both native callback hooks and execute_task monkey-patching are supported, depending on your CrewAI version.

Install

pip install "proofrail[crewai]"
This installs crewai if not already present.
Supported CrewAI versions: 1.10.x - 2.0.x. Install with a CrewAI version in this range; the version constraint is pinned in sdk/pyproject.toml.

Quick start

import asyncio
import proofrail
from proofrail.crewai import govern

from crewai import Agent, Task, Crew

proofrail.init(api_key="prail_...")

# Build your crew as usual
researcher = Agent(role="Researcher", ...)
writer = Agent(role="Writer", ...)

research_task = Task(description="Research the topic", agent=researcher)
write_task = Task(description="Write the report", agent=writer)

crew = Crew(agents=[researcher, writer], tasks=[research_task, write_task])

# Wrap with govern()
governed = govern(crew, chain_name="research-crew")

# Use governed exactly like the original crew
result = await governed.kickoff_async(inputs={"topic": "AI safety"})
governed has the same interface as the underlying Crew. kickoff_async is preferred; kickoff (synchronous) works but raises if called from inside a running event loop.

How it works

The adapter tries two strategies depending on what your CrewAI version exposes: Strategy A: Native callbacks (CrewAI ≥ 0.28) If your Crew has task_callback or before_task_callback attributes, the adapter wraps the existing values and inserts governance recording without breaking your callbacks. Strategy B: Monkey-patch Agent.execute_task (older CrewAI) For each agent in the crew, the instance-level execute_task method is replaced with a wrapper that fires governance events around the original call. Patches are removed in a finally block, so the crew is always restored to its original state after the run. You don’t have to choose. The adapter detects and uses whichever works.

What gets recorded

For each task execution:
  • A task_execution event when the task starts, with the task description and expected output as payload
  • A task_result event when the task completes, with the task’s output
  • A task_error event if the task raises, with the exception details
agent_name in each event is the agent’s role label (e.g., “Researcher”, “Writer”).

Important limitation: deny can’t halt mid-task

CrewAI’s architecture has a real constraint that affects deny decisions. CrewAI executes tasks synchronously in a way that doesn’t expose a pre-execution hook the adapter could use to block. ProofRail records task events after each task completes — which means: A deny decision cannot prevent a task from executing. It surfaces as an ActionDeniedError that halts the next stage of the workflow, not the task itself. This is different from LangGraph and MCP, where deny can stop execution at the exact point of the violation. For CrewAI:
  • allow and allow_with_flag work normally — task runs, event recorded, workflow continues
  • deny records the violation and raises after the task completes — the task already ran, but subsequent tasks are halted
  • require_approval may behave unexpectedly because the approval gate triggers after the task already completed
If you need strict pre-execution governance on CrewAI tasks, two options:
  1. Use proofrail.Chain manually inside your task callbacks — call record_agent_action before invoking any tool, getting a real pre-execution check
  2. Wait for native CrewAI middleware support — this is on CrewAI’s roadmap and we’ll update the adapter when it lands
For audit purposes (recording what happened), the CrewAI adapter is complete. For strict prevention, it’s better suited to workflows where most decisions are allow and deny is a rare exception caught for post-hoc review.

Policy enforcement caveat

Given the limitation above, error handling looks like:
from proofrail.exceptions import ActionDeniedError

try:
    result = await governed.kickoff_async(inputs={"topic": "AI safety"})
except ActionDeniedError as exc:
    print(f"Workflow halted after task: {exc.policy_name}")
    print(f"Reason: {exc.condition}")
    # Note: the task that triggered the deny has already executed
    # Subsequent tasks did NOT run
The audit trail will show the denied task’s task_execution and task_result events. The ActionDeniedError records WHY it was denied. But the side effects of that task — emails sent, records created — already happened. This is documented; it’s not a bug. CrewAI’s architecture requires either accepting post-hoc governance or moving the governance check into your task implementations.

Configuring the chain

governed = govern(
    crew,
    chain_name="customer-onboarding-crew",
    metadata={
        "department": "platform",
        "version": "1.2",
        "environment": "production",
    },
)
chain_name appears in your dashboard. metadata is arbitrary key/value pairs attached to every chain run.

Sync invocation

result = governed.kickoff(inputs={"topic": "AI safety"})
Raises RuntimeError if called from inside a running event loop. In that case use await governed.kickoff_async(...).

Edge cases

Hierarchical processes. CrewAI’s hierarchical process (where a manager agent delegates to others) works with the adapter. Each delegated task becomes a separate task_execution event. Tools used by agents. If an agent uses CrewAI tools, those tool calls are NOT separately recorded by the CrewAI adapter — only the task-level events are. To record individual tool calls, you’d need to instrument them yourself. Custom agent classes. If you’ve subclassed Agent with a custom execute_task, Strategy B’s monkey-patch still works — it captures whatever you’ve defined. Strategy A’s native callback approach is independent of execute_task implementation. Tasks with human_input=True. CrewAI’s built-in human-in-the-loop mechanism is separate from ProofRail’s approval gate. Both can coexist; CrewAI’s handles intra-task input, ProofRail’s handles policy-driven approvals.

When NOT to use this adapter

If your use case requires hard pre-execution prevention (a financial transaction that must NEVER execute without approval), CrewAI’s architecture makes this difficult to enforce at the framework level. Consider:
  • Adding the check inside your task’s tool calls (so the agent can’t even initiate the dangerous action)
  • Using a different orchestration framework where pre-execution hooks are exposed
  • Using ProofRail’s framework-agnostic Chain directly, controlling governance points precisely
For most CrewAI workflows — research, content generation, data analysis — post-execution governance is sufficient. The audit trail is complete; only strict prevention is constrained.

Where to go next

LangGraph adapter

For pre-execution governance with full control.

MCP adapter

Govern tool calls at the MCP protocol level.

Manual chain instrumentation

Use proofrail.Chain directly for precise governance control.

Policies

How decisions get made on the events the adapter records.