CrewAI
CrewAI integration is audit-only because CrewAI has no pre-execution hook. For real gating, call aira.authorize() inline inside your tool bodies.
Kind: audit (not a gate) · Pre-execution gate: no · Peer dep: crewai
This integration records what your CrewAI agents did — it cannot prevent a task or step from running. CrewAI's task_callback and step_callback fire AFTER execution. If you need a real authorization gate, call aira.authorize() directly inside your tool bodies before the side effect. This page shows both patterns.
Why CrewAI is audit-only
CrewAI exposes exactly two callback points: task_callback (fires when a task completes) and step_callback (fires on each agent step). Both fire AFTER the work has already been performed. There is no upstream pre-execution hook that can abort a step or task across CrewAI versions.
That means AiraCrewHook can give you:
- A signed receipt recording what the agent did
- Policy evaluation against the completed action (which will run, but can't retroactively undo it)
- Drift detection signals based on action patterns
It cannot give you:
- A denial gate that stops a step from running
- Held-for-approval behavior that pauses the agent
For those, you have to put aira.authorize() inside the tool function your agent calls, and check auth.status before performing the side effect. That pattern is the full gate — and it works with any framework, not just CrewAI.
Install
pip install aira-sdk[crewai]Pattern 1: Audit-only (the callback handler)
from aira import Aira
from aira.extras.crewai import AiraCrewHook
from crewai import Agent, Task, Crew
aira = Aira(api_key="aira_live_...")
hook = AiraCrewHook(client=aira, agent_id="research-crew")
# Attach to the crew — callbacks fire AFTER each step/task
crew = Crew(
agents=[...],
tasks=[...],
**AiraCrewHook.for_crew(aira, "research-crew"),
# expands to: task_callback=hook.task_callback, step_callback=hook.step_callback
)
crew.kickoff()
# Every task + step is recorded in Aira with a signed receipt.
# If a policy was violated, you'll see the denied evaluation in
# the dashboard — but the step already ran.Pattern 2: Real gate (inline authorize in your tool body)
from aira import Aira, AiraError
from crewai import Agent, Task, Crew, tool
aira = Aira(api_key="aira_live_...")
@tool("send_wire_transfer")
def send_wire_transfer(amount: float, to: str) -> str:
"""Gate the side effect with aira.authorize() BEFORE running it."""
try:
auth = aira.authorize(
action_type="wire_transfer",
details=f"Send €{amount} to {to}",
agent_id="payments-crew",
)
except AiraError as e:
if e.code == "POLICY_DENIED":
return f"Denied by Aira: {e.message}"
raise
if auth.status == "pending_approval":
return f"Held for human approval — action {auth.action_uuid}"
# Only reach this point if actually authorized
try:
# real side effect
tx_id = stripe.transfers.create(...)
except Exception as e:
aira.notarize(
action_uuid=auth.action_uuid,
outcome="failed",
outcome_details=str(e)[:200],
)
raise
aira.notarize(
action_uuid=auth.action_uuid,
outcome="completed",
outcome_details=f"stripe_tx={tx_id}",
)
return f"Wire sent: €{amount} to {to}"
# Now use the gated tool in your crew — it self-enforces
crew = Crew(
agents=[Agent(role="payer", tools=[send_wire_transfer])],
tasks=[Task(description="Send €75K to vendor-x", agent=...)],
)
crew.kickoff()Pattern 3: Use both
You can attach AiraCrewHook for audit receipts AND inline aira.authorize() in high-stakes tools for real gates. The two patterns compose — the hook will see the authorize/notarize calls from your tool body and won't double-record them (they share the same action_uuid).
When to use what
| Situation | Pattern |
|---|---|
| You want a compliance trail of every step for audit purposes | Pattern 1 (callback hook) |
| You want to block specific high-stakes side effects | Pattern 2 (inline in tool body) |
| You want both an audit trail AND specific gates | Pattern 3 |
| You want to prevent a CrewAI step from running based on policy | Pattern 2 — you MUST inline because CrewAI has no pre-step hook |
Known limits
- Audit callbacks run back-to-back
authorize + notarize, which means you'll see a full action row with a receipt for every task/step even though the engine can't block it. This is the honest trade-off: you get an audit signal, but the receipt records something that already happened. - Policy denials in audit mode are recorded but not acted on. If
authorize()returnsPOLICY_DENIED, the callback logs a warning and skips the notarize step. The task already ran and its side effects are irreversible.
Proof it works
- Python:
tests/test_extras_crewai.py(97 lines) - Pinned in the
INTEGRATIONSregistry askind="audit",pre_execution_gate=False. The kind is explicit — CI would fail if the code ever started claiming to be a gate without actually becoming one.
Related
- Policies — what the policy engine checks
- LangChain — real pre-execution gate alternative
- OpenAI Agents — real pre-execution gate alternative