Aira
Integrations

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)

audit-only-crewai.py
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)

gated-crewai.py
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

SituationPattern
You want a compliance trail of every step for audit purposesPattern 1 (callback hook)
You want to block specific high-stakes side effectsPattern 2 (inline in tool body)
You want both an audit trail AND specific gatesPattern 3
You want to prevent a CrewAI step from running based on policyPattern 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() returns POLICY_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 INTEGRATIONS registry as kind="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.

On this page