CrewAI Adapter
The `CrewAIAdapter` registers global before/after tool-call hooks with the
Right page if: you are adding Edictum to a CrewAI crew and need contract enforcement across all agents. Wrong page if: you need per-agent scoping -- CrewAI hooks are global. For per-agent contracts, consider separate Edictum instances or a different framework. For adapter comparison, see https://docs.edictum.ai/docs/guides/adapter-comparison. Gotcha: register() modifies global state -- only the last adapter registered is active. The on_postcondition_warn callback is side-effect only (cannot replace tool results). Tool names are auto-normalized (spaces/hyphens to underscores) to match contract names.
The CrewAIAdapter registers global before/after tool-call hooks with the
CrewAI framework. Every tool call across all agents in a crew passes through
these hooks.
Getting Started
Install
pip install edictum[crewai]Create adapter
from edictum import Edictum
from edictum.adapters.crewai import CrewAIAdapter
guard = Edictum.from_yaml("contracts.yaml")
adapter = CrewAIAdapter(guard=guard)Register hooks
adapter.register()The register() method calls CrewAI's register_before_tool_call_hook and
register_after_tool_call_hook to install the adapter's handlers as global
hooks. After this call, every tool invocation in the CrewAI runtime passes
through Edictum contract enforcement.
Postcondition Callback
Use on_postcondition_warn to react when postconditions flag issues. The
callback is invoked for side effects only (logging, alerting) -- it cannot
replace the tool result:
import logging
logger = logging.getLogger("edictum")
def log_pii_detected(result, findings):
for f in findings:
logger.warning("Postcondition violation: %s", f.message)
adapter.register(on_postcondition_warn=log_pii_detected)The callback receives (result, findings) where result is the tool output
and findings is a list of Finding objects describing the postcondition
violations.
Known Limitations
-
Global hooks:
register()modifies global state in the CrewAI runtime. If you create multiple adapters, only the last one registered is active. Callregister()once before any crew runs. -
Sequential execution model: CrewAI executes tools sequentially within a crew run. The adapter uses a single-pending slot (not a dict keyed by call ID) to correlate before/after events. This is correct for sequential execution but would need adaptation if CrewAI adds parallel tool calls.
-
Tool name normalization: CrewAI tool names may use spaces or hyphens (e.g., "Search Documents", "Read-Database"). The adapter normalizes them to lowercase with underscores (e.g., "search_documents", "read_database") to match contract tool names. The original name is restored after evaluation so CrewAI sees the expected name.
-
Async-to-sync bridging: CrewAI hooks are synchronous, but Edictum's pipeline is async. The adapter detects whether an event loop is running and bridges via
ThreadPoolExecutorwhen needed.
Full Working Example
from edictum import Edictum, Principal
from edictum.adapters.crewai import CrewAIAdapter
from crewai import Agent, Crew, Task
# Load contracts
guard = Edictum.from_yaml("contracts.yaml")
adapter = CrewAIAdapter(
guard=guard,
session_id="crew-session-01",
principal=Principal(user_id="deploy-crew", role="ci"),
)
adapter.register()
# Build crew as usual -- hooks are global
researcher = Agent(
role="Researcher",
goal="Find deployment status",
tools=[status_tool, log_reader_tool],
)
task = Task(
description="Check the health of the staging deployment",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[task])
result = crew.kickoff()Observe Mode
Deploy contracts without enforcement:
guard = Edictum.from_yaml("contracts.yaml", mode="observe")
adapter = CrewAIAdapter(guard=guard)
adapter.register()Denials are logged as CALL_WOULD_DENY audit events but tool calls proceed
normally.
Last updated on