CrewAI
Governing CrewAI agents in Python.
CrewAI's agent engine never calls your tool's _run directly — ToolUsage._use converts every
crewai.tools.BaseTool via to_structured_tool() into a CrewStructuredTool and calls
.invoke(...) on that wrapper. That one method is the point every real tool call flows
through, so that's what raxit run patches: launch your existing CrewAI agent under it and
every tool call is checked against policy — permit / defer / deny / modify — before the tool
body runs, with zero raxit imports in your code. Python only (CrewAI has no JS/TS SDK).
Run it yourself
For your own CrewAI agent, install the SDK and launch through raxit run:
pip install raxit
raxit run --agent-id my-crew -- python my_crew_agent.pyRun the same file without raxit run and it runs ungoverned — governance is a deploy-time
choice, not a code change.
The demo, step by step
A support-ops bot with five mock tools exercises every decision the engine can make. Every terminal line below is from the captured run:
Five ordinary crewai.tools.BaseTool subclasses. Nothing in the agent imports raxit; each tool’s name is the policy resource it will be governed as.
from crewai.tools import BaseTool
class SearchDocsTool(BaseTool):
name: str = "search_docs"
description: str = "Search internal support documentation (read-only)."
def _run(self, query: str) -> str:
...
class DeleteCustomerRecordTool(BaseTool):
name: str = "delete_customer_record"
description: str = "Permanently delete a customer's record (policy forbids this)."
def _run(self, customer_id: str) -> str:
...What the demo drives
The scripted demo calls raxit.autopatch.install() directly — the exact function raxit run
triggers under the hood — then dispatches each tool via to_structured_tool().invoke(...),
the same path CrewAI's own orchestration uses. Deterministic and CI-able, minus the LLM.
How it works
The tools are ordinary crewai.tools.BaseTool subclasses with no governance code. The only
"wiring" is each tool's name, which is the policy resource it's governed as in
.raxit/security.yaml:
apiVersion: raxit.security/v1
agent: crewai-bot
default: block
rules:
- effect: allow
resource: search_docs
- effect: allow
resource: update_ticket
- resource: send_customer_email # MODIFY: redact PII in the body before sending
modify: { redact: [body] }
- effect: review # unconditional review == defer.policy
resource: issue_refund
# delete_customer_record is intentionally UNLISTED -> default-deny.Three things worth noticing:
default: block— anything not listed is denied.delete_customer_recordhas no rule, and that's the point: least privilege by omission.modify: { redact: [body] }— thesend_customer_emailcall runs, but on the engine's rewritten arguments. The agent passedbody: "card 4242424242424242"; the tool body received'card [REDACTED:pii_credit_card]'. The demo proves this from the tool's own call log, not from log output.effect: review—issue_refundalways routes to a human. In an interactiveraxit runsession that blocks waiting forraxit approvals approve <token>(see Approvals); the scripted demo sets the defer timeout to zero so the raw defer surfaces immediately.
What got denied and why
Both refused calls raise raxit.autopatch.RaxitDenied from inside .invoke(), each carrying a
named reason code — and in both cases the demo asserts the tool body never executed:
| Tool call | Decision | Reason code | Why |
|---|---|---|---|
issue_refund | defer | defer.policy | Matches an effect: review rule — needs human approval. |
delete_customer_record | deny | deny.default_or_forbid | Unlisted under default: block. |
Catch RaxitDenied like any other tool exception, or let it propagate to CrewAI's own error
handling. It carries the reason code and a call_id for raxit explain <call_id> — the full
decision record, same as described in LangChain (Python).
The reason-code vocabulary is covered in Decisions.
Honest scope: tool calls, not delegation
What this does not govern
raxit governs each agent's tool calls via the SDK autopatch. It does not govern
CrewAI's inter-agent delegation between multiple Agents in a Crew — that surface is not
reachable from the SDK tier. The demo deliberately runs a single-agent setup and claims
nothing about multi-agent governance. Likewise, the autopatch proves raxit's known CrewAI
dispatch point is live and governed — not that no bypass is possible.
Related
- Decisions — the permit / defer / deny vocabulary and reason codes.
- Approvals — what happens after a
defer.policy. - Audit — the hash-chained, Ed25519-signed Decision Provenance log the demo verifies in its finale.
- LangChain (Python) — the reference patcher; same experience, different interception point.

