> ## Documentation Index
> Fetch the complete documentation index at: https://docs.certior.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CrewAI

> Wrap one CrewAI tool with certior_tool_wrapper, or guard every tool in an existing crew with guard_crew_tools.

<a href="https://colab.research.google.com/github/certior/certior/blob/main/notebooks/crewai_integration.ipynb" target="_blank" rel="noreferrer"><img noZoom src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" /></a> — run a live crew refused mid-task (needs your own OpenAI key).

The CrewAI adapter ships two patterns: decorate a single tool, or wrap every tool in an existing crew at construction time. Both run `Guard.verify(...)` before each tool call.

## What the adapter does

The CrewAI wrapper runs all three gates on each tool call:

* **Capability** - pass `required_capabilities=[...]` to the wrapper (or a `capabilities` map to `guard_crew_tools`). A call whose needs aren't a subset of the agent's grant is blocked. Omit it for content-gate-only checking.
* **Content** - PII detection and policy-specific content rules on the first string argument.
* **Budget** - pass `cost_cents=...` to debit the guard's budget per call.

A blocked call **returns the string** `"[CERTIOR BLOCKED] {reason}"` rather than raising; CrewAI surfaces that as the tool's output, and the agent's reasoning loop sees it.

## Pattern 1: decorate one tool

```python theme={null}
from crewai import tool
from certior import Guard
from certior.adapters.crewai import certior_tool_wrapper

guard = Guard(policy="sox", permissions=["database:read"], budget_cents=5000)

@tool("financial_query")
@certior_tool_wrapper(guard, tool_name="financial_query",
                      required_capabilities=["database:read"], cost_cents=2)
def query_financials(query: str) -> str:
    ...
```

`certior_tool_wrapper(guard=None, policy="default", tool_name="", *, required_capabilities=None, cost_cents=0)` is the full signature. When `guard` is omitted, the wrapper builds its own `Guard(policy=policy)`. `tool_name` is what appears in the audit log (defaults to the function's `__name__`). `required_capabilities` turns on the capability gate; `cost_cents` debits the budget.

## Pattern 2: guard every tool in an existing crew

```python theme={null}
from crewai import Crew, Agent
from certior import Guard
from certior.adapters.crewai import guard_crew_tools

guard = Guard(policy="hipaa", budget_cents=5000)

crew = Crew(agents=[Agent(...), Agent(...)])
guard_crew_tools(crew, guard,                       # modifies the crew in place
                 capabilities={"db_query": ["database:read"]})

result = crew.kickoff(inputs={...})
```

`guard_crew_tools(crew, guard, capabilities=None)` walks every agent's tools list and replaces each tool's function with the guarded wrapper. Tools named in `capabilities` have the capability gate enforced; the others are content-gate-only. The crew is mutated in place and also returned for chaining.

## Raise instead of returning a blocked string

The wrapper returns `"[CERTIOR BLOCKED] {reason}"` so the agent's reasoning loop can see it. If you'd rather halt hard on a capability or budget miss, also decorate the underlying function with `@guard.wrap(...)` - that path raises `CertiorBlocked` before the body runs:

```python theme={null}
from crewai import tool
from certior import Guard
from certior.adapters.crewai import certior_tool_wrapper

guard = Guard(policy="sox", permissions=["database:read"], budget_cents=5000)

@tool("financial_query")
@guard.wrap(required_capabilities=["database:read"], cost_cents=2)   # raises on a miss (outer, runs first)
@certior_tool_wrapper(guard, tool_name="financial_query")            # returns blocked-string (inner)
def query_financials(query: str) -> str:
    ...
```

Decorator order matters here. Python applies decorators bottom-up but at call time the outer wrapper runs first. With `@guard.wrap` above `@certior_tool_wrapper`, capability + budget is checked first (raising `CertiorBlocked` on a miss); only if that passes does the inner wrapper run.

## See also

* [OpenAI guide](/guides/openai) - same gate via `verify_tool_calls()`.
* [Custom loop](/guides/custom-loop) - direct `Guard.verify()` / `@guard.wrap()`.
