CrewAI + ACP — Governance Install Guide
CrewAI is a popular Python framework for building multi-agent systems. Out of the box, a CrewAI deployment shares one backend API key across every end user’s request — no per-user policy, no per-user audit trail, no way to tell downstream systems which human triggered which action.
acp-crewai closes that gap. Wrap tools with @governed; before each runs, ACP decides allow / deny / redact based on your workspace’s policy, the end user’s identity, rate limits, and PII detection. Same governance model as Claude Code — same /govern/tool-use endpoint, same workspace policies.
Starter · 5-minute install.
pip install acp-crewai, wrap tools with@governed, bind the end user’s JWT per request, and optionally callinstall_crew_hooks(crew)to audit inter-agent handoffs. See the governance model for the shared concepts across every framework, or the frameworks index for other options.
Install
pip install acp-crewai
Minimal governed crew
from crewai import Agent, Crew, Task
from crewai.tools import tool
from fastapi import FastAPI, Header
from acp_crewai import governed, install_crew_hooks, set_context
app = FastAPI()
# Define tools however you want. Stack @governed under @tool — the
# governance check runs inside CrewAI's tool dispatch.
@tool("web_search")
@governed("web_search")
def web_search(query: str) -> str:
"""Search the web."""
return my_search(query) # your code, your credentials
@tool("send_email")
@governed("send_email")
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email on behalf of the user."""
return sendmail(to, subject, body)
@app.post("/run")
def run(topic: str, authorization: str = Header(...)):
# Bind the end user's JWT to this request's context.
set_context(user_token=authorization.removeprefix("Bearer ").strip())
researcher = Agent(role="researcher", goal=f"Research {topic}",
tools=[web_search, send_email])
task = Task(description=f"Research {topic} and email me a summary.",
agent=researcher)
crew = Crew(agents=[researcher], tasks=[task])
# Capture inter-agent handoffs too (sequential task passes, coworker
# delegation) as synthetic Agent.Handoff audit events.
install_crew_hooks(crew)
return {"result": str(crew.kickoff())}
What happens on every tool call
@governedPOSTs{ tool_name, tool_input, session_id }+Authorization: Bearer <user-jwt>to ACP’s/govern/tool-use.- ACP evaluates workspace policy, the user’s scopes, rate limits, and PII.
- Deny → the wrapped function returns
"tool_error: <reason>". CrewAI treats this as the tool’s output; the model sees it and adapts. - Allow → your tool runs.
- Post-audit: ACP scans the output for PII. If policy says
redact, the redacted version replaces the tool’s output. Audit row written, rooted in the end user’s identity.
Inter-agent handoffs
CrewAI has two delegation paths that don’t cross a tool boundary, so @governed alone doesn’t see them:
- Sequential task handoffs — Task N’s output feeds Task N+1’s context
- Hierarchical delegation — CrewAI’s built-in “Delegate work to coworker” / “Ask question to coworker” tools
install_crew_hooks(crew) attaches task_callback and step_callback so inter-agent messages emit synthetic Agent.Handoff audit events. PII scanning applies; audit rows are written; existing callbacks are chained, not overwritten.
Fail-open
If /govern/tool-use times out (5s default) or is unreachable, the tool proceeds. Matches Claude Code hook behavior. Governance is never a single point of failure for the agent.
Configure your ACP workspace
Before the crew can usefully call tools, your ACP workspace needs:
- An IdP configured — ACP verifies the end user’s JWT against your identity provider (Firebase, Auth0, any OIDC). Dashboard → Settings → Identity Provider.
- Tools listed — the names in
@governed("...")must match tools enabled in your workspace. Dashboard → Policies → Tools. - Policy per tier — set allow/deny, rate limits, PII mode per agent tier. Dashboard → Policies.
If the dev themselves is the end user (single-user test), Firebase login to cloud.agenticcontrolplane.com gives a valid JWT out of the box.
What shows up in the dashboard
Every governed call — tool or inter-agent handoff — appears in cloud.agenticcontrolplane.com/activity with:
- Actor — the end user’s sub (not your service account)
- Tool name — whatever you passed to
@governed(...) - Decision — allow / deny / redact, with reason
- Session — groups all tool calls from one request
- Findings — PII detected in input or output, if any
CrewAI tool calls sit alongside Claude Code and Cursor calls from the same user. One audit log across every agent surface.
Add or replace tools
from crewai.tools import tool
from acp_crewai import governed
@tool("my_tool")
@governed("my_tool")
def my_tool(arg: str) -> str:
"""Description the LLM sees."""
# your code: your API, your credentials
return "..."
@tool (CrewAI) and @governed (ACP) stack — @governed must sit closer to the function so the governance check runs inside CrewAI’s tool dispatch. Tools outside this pattern are not governed.
Comparison: CrewAI native vs. ACP
CrewAI’s own features and what acp-crewai adds:
| Concern | CrewAI native | acp-crewai |
|---|---|---|
| Workflow (agents, tasks, crew composition) | ✅ | — |
| Workspace-level RBAC (who can edit crews) | ✅ (Enterprise) | — |
| Tracing / observability (what ran, latency) | ✅ (Enterprise) | Audit is narrower but enforced |
| Per-tool-call policy enforcement | ❌ | ✅ |
| Per-end-user rate limits / budgets | ❌ | ✅ |
| PII detection on tool I/O | ❌ | ✅ |
| Per-end-user audit trail across tool calls | ❌ (workspace-level) | ✅ (per human) |
| Cross-framework unified audit (Claude Code + CrewAI + …) | ❌ (framework-specific) | ✅ |
Complementary, not duplicative. Use CrewAI Enterprise for what it does; add acp-crewai where CrewAI doesn’t reach.
Limitations
- Only tools routed through
@governedare covered. If you write a plain Python function as a CrewAI tool and don’t decorate it, ACP doesn’t see the call. This is intentional — the decorator makes governance an explicit choice — but worth flagging. - LLM calls go direct to your provider. ACP governs tools and actions, not tokens. If you need per-user LLM cost attribution, pair with Portkey or LiteLLM virtual keys.
- Pre-release.
acp-crewai@0.1.xis the initial release. Pin exact versions.
Troubleshooting
Crew runs but nothing appears in the dashboard. Confirm the end user’s JWT is being sent as Authorization: Bearer ... and that ACP has the IdP configured for its issuer. Check the Activity log’s workspace dropdown.
401 from /govern/tool-use. The JWT is invalid, expired, or signed by an IdP ACP doesn’t trust. Check Settings → Identity Provider in the dashboard.
Tools run but decisions always show allow with reason fail-open. The gateway request is erroring. Check network (is api.agenticcontrolplane.com reachable from your runtime?). Raise timeout via configure(timeout_s=10) if latency is the issue.
Policy says deny, but the tool still runs. Verify the tool is wrapped with @governed (not just @tool). Inspect the decorator order — @governed must be closer to the function than @tool.