Framework Guide
EU AI Act Compliance for CrewAI Agents
CrewAI builds multi-agent crews from BaseTool instances. Fuze instruments those tools — not the crew or the delegation graph. Mix in FuzeToolMixin and every tool call emits a hash-chained audit record.
Your role under the EU AI Act
You are the Deployer of any AI system you build with CrewAI, and you may also be the Provider if you place that system on the EU market. The Provider of the underlying model (OpenAI, Anthropic, etc.) carries provider-side obligations for the model itself; the system you assemble is yours. See deployer vs provider.
Fuze is a component supplier under Article 25(4). Fuze tooling does not certify your AI system — see Terms §6.
1. Install
pip install fuze-ai crewai2. Mix the tool
Put FuzeToolMixin first in the MRO. CrewAI sees the same BaseTool interface; Fuze sees every invocation.
from crewai import BaseTool
from fuze_ai.adapters.crewai import FuzeToolMixin
# Mix FuzeToolMixin in front of BaseTool. Same interface,
# instrumented run loop.
class SearchTool(FuzeToolMixin, BaseTool):
name = "search"
description = "Search the vector database"
# Per-run limits. Tokens, steps, wall-clock — never USD.
fuze_config = {
"max_retries": 3,
"resource_limits": {
"max_tokens_per_run": 200_000,
"max_steps": 25,
"max_wall_clock_ms": 60_000,
},
}
def _run(self, query: str) -> str:
return vector_db.search(query)3. Mark side effects with a compensation
Anything irreversible — emails, payments, calendar bookings — gets side_effect: True and a _compensate method. If the run is killed mid-step, Fuze calls _compensate with the original result so you can roll back.
class SendEmailTool(FuzeToolMixin, BaseTool):
name = "send_email"
description = "Send an email"
# Mark irreversible actions as side effects. If the run is killed
# mid-step, Fuze invokes _compensate with the original result.
fuze_config = {
"side_effect": True,
}
def _run(self, to: str, body: str) -> dict:
return ses.send_email(to, body)
def _compensate(self, result: dict) -> None:
ses.recall_email(result["message_id"])4. Or batch-wrap existing tools
from fuze_ai.adapters.crewai import fuze_crew_tools
# Already have un-instrumented BaseTool instances? Batch-wrap them.
tools = [SearchTool(), CalculatorTool(), EmailTool()]
guarded = fuze_crew_tools(
tools,
# Per-tool side-effect compensations.
side_effects={"send_email": recall_email},
)5. Wire it into a Crew
from crewai import Agent, Task, Crew
from fuze_ai.adapters.crewai import FuzeToolMixin, fuze_crew_tools
tools = fuze_crew_tools(
[SearchTool(), EmailTool()],
side_effects={"send_email": recall_email},
)
researcher = Agent(
role="Recruitment Researcher",
goal="Find and summarise candidates",
tools=tools,
)
crew = Crew(
agents=[researcher],
tasks=[Task(description="Shortlist candidates for role X", agent=researcher)],
)
result = crew.kickoff()6. Verify the audit trail
Every tool invocation writes one JSONL record. Records are HMAC-SHA256 hash-chained — tampering with one breaks the rest.
from fuze_ai import verify_chain, read_audit
records = read_audit() # ~/.fuze/audit.jsonl by default
result = verify_chain(records)
print(result.ok, result.first_mismatch_index)
# Each record carries: step_id, run_id, step_number, tool_name,
# args_hash, tokens_in, tokens_out, latency_ms, error,
# prev_hash, hash, signature, sequence.Arguments are SHA-256 hashed (first 16 chars), not stored raw, so the audit trail doesn't leak PII even when crews handle personal data.
What Fuze actually does, and what it doesn't
Fuze sees the tool-call boundary. Each instrumented tool call emits a span; spans chain. Honest scope:
- Tool-call audit log — every
FuzeToolMixin._runinvocation recorded with step_id, run_id, tool_name, args_hash, tokens_in, tokens_out, latency_ms, error. - 3-layer loop detection — iteration cap, repeated tool-call dedup (sliding window on tool_name + args_hash), no-progress detection. Detects loops at the tool-call level.
- Per-run resource limits — tokens, steps, wall-clock, shared across all tool calls within one run.
- Side-effect compensation via
_compensateon irreversible tools. - Kill switch — the daemon can halt before the next step; the tool refuses to run and logs a guard event.
Fuze does notunderstand CrewAI's delegation graph, agent hierarchy, or task structure. It does not provide per-agent or per-task or per-crew budgets — only per-run, where a "run" means "one continuous instrumented invocation chain." It does not track cost in dollars, by design (see the budget docs).
If your auditor needs to see "agent A delegated to agent B who called tool X," that information has to come from CrewAI's own logs, not from FuzeToolMixin.
Common high-risk CrewAI use cases
Use the risk classifier for a real determination — these are heuristics:
- Recruitment crews — Annex III category 4 (employment), almost always high-risk.
- Underwriting / credit-screening crews — Annex III category 5 (essential services), high-risk.
- Content moderation crews — limited risk if user-facing; transparency obligations under Art. 50.
- Research / market-analysis crews — usually minimal risk.
Mapping to AI Act articles
- Article 12 (record-keeping): the JSONL hash-chained log per tool call.
- Article 14 (human oversight):
side_effect+_compensategive a defensible kill-and-rollback path. Human approval before a high-impact tool runs is your responsibility — wrap the tool call in your own approval check. - Article 15 (robustness): per-run resource limits and 3-layer loop detection.
When to consider Fuze Agent instead
If you're starting fresh and want compliance baked into the runtime — typed tools with explicit data classification, Ed25519-signed run-roots, replay-protected HITL approvals — see Fuze Agent. Fuze Agent is TypeScript only; if your team needs Python, stay on CrewAI + FuzeToolMixin.