Docs/CrewAI Guide
CrewAI Integration

CrewAI + Tork Governance

Add PII guardrails to your CrewAI multi-agent systems. Govern task inputs, tool calls and crew outputs on-device, with a local receipt for every decision.

Crew Governance

Wrap entire crews with one call

Agent Control

Govern individual agents

Task Validation

Check inputs and outputs

Local Receipts

A receipt for every check, on-device

Installation

Install Tork with CrewAI dependencies.

bash
pip install tork-governance crewai crewai-tools

CrewAI adapters are in tork_governance.adapters.crewai. Governance runs on-device: PII detection and the allow / redact / deny decision are computed locally.

TorkCrewAIMiddleware

The main entry point for CrewAI governance.

The middleware wraps agents and crews. A wrapped agent governs the task description before execute_task() runs and the output after; a wrapped crew governs thekickoff() inputs and the final output. Each check appends a receipt summary tomiddleware.receipts.

pythonmiddleware_example.py
from crewai import Agent, Task, Crew
from tork_governance.adapters.crewai import TorkCrewAIMiddleware

# On-device governance. No api_key = zero network calls.
# Pass api_key="tork_live_..." to also report metadata-only attestations.
middleware = TorkCrewAIMiddleware(agent_id="my-crew")

# Create a standard CrewAI agent
researcher = Agent(
    role="Research Analyst",
    goal="Find accurate information about given topics",
    backstory="You are an expert researcher with attention to detail.",
    verbose=True
)

# Wrap the agent with governance
governed_researcher = middleware.wrap_agent(researcher)

# governed_researcher.execute_task(task) now:
# - governs the task description before execution (PII redacted, or the
#   task raises ValueError when the configured action is DENY)
# - governs the agent's output before it is returned
# - records a local receipt for each check in middleware.receipts

Middleware Methods

wrap_agent()Wrap a single agent (returns GovernedAgent)
wrap_crew()Wrap a crew (returns GovernedCrew)
process_input() / process_output()Govern text; returns a GovernanceResult with a receipt
check_tool_call()Govern a tool name + arguments before execution

GovernedAgent

Wrap individual CrewAI agents with governance controls.

GovernedAgent wraps a single agent to validate task inputs before execution and check outputs after. Use when you need granular control over specific agents.

pythongoverned_agent.py
from crewai import Agent, Task
from tork_governance.adapters.crewai import TorkCrewAIMiddleware, GovernedAgent

middleware = TorkCrewAIMiddleware(agent_id="content-team")

# Create your CrewAI agents
writer = Agent(
    role="Content Writer",
    goal="Write engaging and accurate content",
    backstory="Expert content creator with SEO knowledge.",
    verbose=True
)

editor = Agent(
    role="Editor",
    goal="Review and improve content quality",
    backstory="Experienced editor focused on clarity and accuracy.",
    verbose=True
)

# Wrap agents individually for granular control
governed_writer = GovernedAgent(writer, middleware)
governed_editor = GovernedAgent(editor, middleware)

# Create a task
writing_task = Task(
    description="Write a blog post about AI safety best practices",
    expected_output="A 500-word blog post with actionable tips",
    agent=governed_writer
)

# Execute with governance:
# - the task description is governed before execution
# - the output is scanned for PII and redacted before it is returned
result = governed_writer.execute_task(writing_task)

GovernedCrew

Wrap entire CrewAI crews for comprehensive governance.

GovernedCrew governs the string inputs you pass tokickoff() and the crew's final output. It does not wrap the individual agents inside the crew; wrap those with GovernedAgent if you need per-task checks.

pythongoverned_crew.py
from crewai import Agent, Task, Crew, Process
from tork_governance.adapters.crewai import TorkCrewAIMiddleware, GovernedCrew

middleware = TorkCrewAIMiddleware(agent_id="content-crew")

# Define agents
researcher = Agent(
    role="Research Analyst",
    goal="Gather comprehensive information",
    backstory="Expert at finding and synthesizing information.",
    allow_delegation=False
)

writer = Agent(
    role="Content Writer",
    goal="Create engaging content from research",
    backstory="Skilled writer who turns research into readable content.",
    allow_delegation=False
)

reviewer = Agent(
    role="Quality Reviewer",
    goal="Ensure content accuracy and quality",
    backstory="Detail-oriented reviewer with high standards.",
    allow_delegation=False
)

# Define tasks
research_task = Task(
    description="Research the topic: {topic}",
    expected_output="Comprehensive research notes with sources",
    agent=researcher
)

writing_task = Task(
    description="Write an article based on the research",
    expected_output="A well-structured article",
    agent=writer
)

review_task = Task(
    description="Review the article for accuracy and quality",
    expected_output="Final approved article with any corrections",
    agent=reviewer
)

# Create crew
crew = Crew(
    agents=[researcher, writer, reviewer],
    tasks=[research_task, writing_task, review_task],
    process=Process.sequential,
    verbose=True
)

# Wrap the crew with governance
governed_crew = GovernedCrew(crew, middleware)

# kickoff() governs every string value in inputs before the crew runs
# and governs the crew's final output before returning it.
# It does not intercept messages the agents exchange with each other.
result = governed_crew.kickoff(inputs={"topic": "AI Governance Best Practices"})

Per-Task Checks

Govern a task description, an output, or a tool call directly.

There is no task wrapper class. For fine-grained control call the middleware'sprocess_input(), process_output() andcheck_tool_call() yourself. Each returns aGovernanceResult (action, output, pii, receipt).

pythonper_task_checks.py
from crewai import Task
from tork_governance import GovernanceAction
from tork_governance.adapters.crewai import TorkCrewAIMiddleware

middleware = TorkCrewAIMiddleware(agent_id="analysis-task")

task = Task(
    description="Analyze customer feedback and summarize key themes",
    expected_output="A summary report with top 5 themes and recommendations"
)

# Govern the task description before you run it
check = middleware.process_input(task.description)
if check.action == GovernanceAction.DENY:
    raise ValueError(f"Task blocked: {check.receipt.receipt_id}")
task.description = check.output  # redacted if PII was present

# ... execute the task with your crew ...

# Govern the output after execution
output = "Customer feedback analysis shows..."
result = middleware.process_output(output)
safe_output = result.output             # PII redacted
print(result.pii.has_pii, result.pii.types)
print(result.receipt.receipt_id)

# Govern a tool call before it runs: the tool name and arguments are
# serialised and scanned for PII like any other text
tool_check = middleware.check_tool_call("send_email", {"to": "jane@example.com"})
print(tool_check.action, tool_check.pii.types)

Complete Workflow Example

A production-ready customer service crew with full governance.

pythoncustomer_service.py
from crewai import Agent, Task, Crew, Process
from tork_governance import Tork, GovernanceAction
from tork_governance.adapters.crewai import TorkCrewAIMiddleware, GovernedCrew

def create_governed_crew() -> GovernedCrew:
    """Create a fully governed CrewAI crew."""

    # DENY instead of the default REDACT: inputs containing PII raise
    # ValueError from kickoff() rather than being redacted.
    tork = Tork(default_action=GovernanceAction.DENY)
    middleware = TorkCrewAIMiddleware(
        tork=tork,
        agent_id="customer-service-crew"
    )

    # Customer service agents
    intake_agent = Agent(
        role="Intake Specialist",
        goal="Understand and categorize customer inquiries",
        backstory="Expert at understanding customer needs quickly.",
        allow_delegation=True
    )

    resolver_agent = Agent(
        role="Resolution Specialist",
        goal="Provide accurate solutions to customer issues",
        backstory="Knowledgeable support specialist with extensive training.",
        allow_delegation=False
    )

    qa_agent = Agent(
        role="Quality Assurance",
        goal="Ensure responses meet quality and compliance standards",
        backstory="Detail-oriented QA specialist focused on compliance.",
        allow_delegation=False
    )

    # Task pipeline
    intake_task = Task(
        description="Analyze the customer inquiry: {inquiry}",
        expected_output="Categorized inquiry with key details extracted",
        agent=intake_agent
    )

    resolution_task = Task(
        description="Provide a helpful response to the customer's issue",
        expected_output="Clear, actionable response for the customer",
        agent=resolver_agent
    )

    qa_task = Task(
        description="Review the response for accuracy and compliance",
        expected_output="Approved response or feedback for revision",
        agent=qa_agent
    )

    crew = Crew(
        agents=[intake_agent, resolver_agent, qa_agent],
        tasks=[intake_task, resolution_task, qa_task],
        process=Process.sequential,
        verbose=True
    )

    return GovernedCrew(crew, middleware)

def handle_customer_inquiry(inquiry: str) -> str:
    """Process a customer inquiry with governance on inputs and output."""

    crew = create_governed_crew()

    try:
        return crew.kickoff(inputs={"inquiry": inquiry})
    except ValueError as e:
        # The adapter raises ValueError when governance denies an input.
        # The message carries the receipt id, never the blocked text.
        print(f"Blocked by governance: {e}")
        return "Please remove any personal information and try again."

# Usage
response = handle_customer_inquiry(
    "I need help with my account password reset"
)
print(response)

Inter-Agent Communication

Governance for agent delegation and collaboration.

The adapter does not intercept messages that CrewAI agents exchange with each other during delegation (allow_delegation=True, hierarchical processes). It governs what enters a crew or agent (inputs, task descriptions) and what leaves it (outputs). To govern an internal hand-off, pass the text throughmiddleware.process_input() yourself before the receiving agent uses it.

Error Handling

Handle governance violations gracefully.

The adapter raises a plain ValueError when governance denies an input, and only when the engine's action is DENY. With the defaultREDACT action nothing is raised: PII is replaced in place.

pythonerror_handling.py
from tork_governance import Tork, GovernanceAction
from tork_governance.adapters.crewai import TorkCrewAIMiddleware, GovernedCrew

def safe_crew_execution(crew, inputs: dict) -> dict:
    """Execute crew with error handling."""

    # Only a DENY action raises. With the default REDACT action, PII is
    # replaced in place and no exception is thrown.
    tork = Tork(default_action=GovernanceAction.DENY)
    middleware = TorkCrewAIMiddleware(tork=tork)
    governed_crew = GovernedCrew(crew, middleware)

    try:
        result = governed_crew.kickoff(inputs=inputs)
        return {
            "success": True,
            "result": result,
            "governance_passed": True
        }

    except ValueError as e:
        # Input denied by governance (PII detected, action=DENY).
        # str(e) is "Input blocked: <receipt_id>".
        return {
            "success": False,
            "error": "input_blocked",
            "message": str(e),
            "governance_passed": False
        }

    except Exception as e:
        # Other errors (LLM, network, etc.)
        return {
            "success": False,
            "error": "execution_error",
            "message": str(e),
            "governance_passed": None  # Unknown
        }

# Usage with fallback
result = safe_crew_execution(my_crew, {"query": "Help with account"})

if not result["success"]:
    if result["error"] == "input_blocked":
        print("Please remove personal information from your request.")
    else:
        print("An error occurred. Please try again.")

Exception Types

ValueErrorRaised by execute_task() / kickoff() when an input is denied; the message carries the receipt id
(no other exceptions)There are no toxicity, jailbreak or policy exception types in this adapter

Advanced Topics

Compliance receipts and configuration

python
from tork_governance.adapters.crewai import TorkCrewAIMiddleware, GovernedCrew

middleware = TorkCrewAIMiddleware(agent_id="audited-crew")
governed_crew = GovernedCrew(crew, middleware)
result = governed_crew.kickoff(inputs={"topic": "Q4 Analysis"})

# Every check the middleware performs appends a summary to
# middleware.receipts: {'type', 'agent_id', 'receipt_id', 'action'}
for entry in middleware.receipts:
    print(f"{entry['type']}: {entry['action']} ({entry['receipt_id']})")

# For the full receipt, call the middleware directly. The receipt is
# minted on-device from SHA-256 hashes; it never contains the text.
check = middleware.process_output("Contact jane@example.com about Q4")
receipt = check.receipt
print(receipt.receipt_id)      # "rcpt_..."
print(receipt.timestamp)
print(receipt.action)          # GovernanceAction.REDACT
print(receipt.input_hash)      # sha256 of the input
print(receipt.output_hash)     # sha256 of the redacted output
print(receipt.pii_types)       # ['email']
print(receipt.policy_version)

# With an api_key, each decision is also reported to tork.network as a
# METADATA-ONLY client attestation (never the text). Check the outcome:
reporting = TorkCrewAIMiddleware(api_key="tork_live_...", agent_id="audited-crew")
res = reporting.process_input("Hello")
if res.report is not None:
    res.report.wait()
    print(res.report.attempted, res.report.succeeded, res.report.reason)

Best Practices

Use GovernedCrew for simplicity

Wrapping the entire crew is easier and ensures all agents are governed consistently.

Handle ValueError when using DENY

With Tork(default_action=GovernanceAction.DENY) the adapter raises ValueError on denied input; catch it to respond gracefully.

Set meaningful agent IDs

Use descriptive agent_id values to identify which crew/agent triggered violations in logs.

Keep the local receipts

Every check appends a summary to middleware.receipts; the full on-device receipt is on each GovernanceResult.

Start with DENY, relax to REDACT

Run with default_action=DENY in staging to see what would be blocked, then switch to REDACT for production.

Imports Reference

python
from tork_governance import Tork, TorkConfig, GovernanceAction, GovernanceResult
from tork_governance.adapters.crewai import (
    TorkCrewAIMiddleware,   # Main middleware class
    GovernedAgent,          # Agent wrapper
    GovernedCrew,           # Crew wrapper
)
# Denied inputs raise the built-in ValueError; there are no custom exceptions.

Next Steps

Configure policies in the dashboard or explore other framework integrations.

Documentation

Learn to integrate TORK

Upgrade Plan

Current: free

Support

Get help from our team