CrewAI Integration
PyHall WCP — CrewAI Governance Integration
Add WCP governance to any CrewAI crew. Define pyhall decision checks as @tool-decorated
functions and assign them to your Agent instances. Every governed step is authorized before
execution and produces an immutable audit record in the pyhall registry.
What you can do
- Gate crew tasks — require a pyhall ALLOW before any crew agent executes a capability
- Per-agent worker IDs — bind each CrewAI agent to its own registered pyhall worker identity
- Audit every task — decisions produce
decision_idandartifact_hashstored in the registry - Block banned workers — ban list check is automatic on every routing decision
- Mix governed and ungoverned tools — add the pyhall tool alongside your existing tools
Installation
pip install pyhall-wcp crewai crewai-toolsEnvironment variables
PYHALL_API_KEY=your-api-key # registry authenticationHALL_SESSION_TOKEN=your-session-tok # local Hall Server auth (if self-hosted)OPENAI_API_KEY=your-openai-key # CrewAI default LLMPYHALL_REGISTRY=https://api.pyhall.dev # default; override for self-hostedCore pattern — @tool decorated governance check
Use the crewai.tools @tool decorator to define a pyhall governance check as a CrewAI
tool. The docstring is what the agent reads to decide when to call the tool.
import osfrom crewai.tools import toolfrom pyhall import make_decision
WORKER_ID = os.environ["MY_WORKER_ID"]TENANT_ID = os.environ.get("TENANT_ID", "org.default")
@tool("pyhall_governance_check")def pyhall_governance_check(capability_id: str) -> str: """ Check WCP governance before executing a capability. Use this tool before any data access, report generation, or privileged action. Pass the capability ID string (e.g., 'cap.data.read.v1'). Returns authorization proof on ALLOW; returns a denial message on DENY. """ decision = make_decision( capability_id=capability_id, worker_id=WORKER_ID, env="prod", data_label="INTERNAL", tenant_id=TENANT_ID, ) if decision.denied: return ( f"DENIED. capability='{capability_id}' worker='{WORKER_ID}' " f"reason='{decision.reason}' decision_id='{decision.decision_id}'. " f"Do not proceed with this action." ) return ( f"ALLOWED. capability='{capability_id}' " f"decision_id='{decision.decision_id}' " f"worker_species='{decision.selected_worker_species_id}' " f"artifact_hash='{decision.artifact_hash}'" )Per-capability tool factories
For stricter enforcement, define one tool per capability so agents cannot pass arbitrary capability IDs:
import osfrom crewai.tools import toolfrom pyhall import make_decision
WORKER_ID = os.environ["MY_WORKER_ID"]TENANT_ID = os.environ.get("TENANT_ID", "org.default")
@tool("check_data_read_authorization")def check_data_read_authorization(context: str) -> str: """ Verify WCP authorization for reading internal data (cap.data.read.v1). Call this before any database query or file read operation. context: brief description of the data access being performed. """ decision = make_decision( capability_id="cap.data.read.v1", worker_id=WORKER_ID, env="prod", data_label="INTERNAL", tenant_id=TENANT_ID, ) if decision.denied: return f"DENIED: data read not authorized. Reason: {decision.reason} [{decision.decision_id}]" return f"ALLOWED: data read authorized. proof={decision.artifact_hash}"
@tool("check_report_generation_authorization")def check_report_generation_authorization(report_type: str) -> str: """ Verify WCP authorization for generating reports (cap.report.generate.v1). Call this before creating any report or summary document. report_type: the type of report to be generated (e.g., 'quarterly', 'audit'). """ decision = make_decision( capability_id="cap.report.generate.v1", worker_id=WORKER_ID, env="prod", data_label="INTERNAL", tenant_id=TENANT_ID, ) if decision.denied: return f"DENIED: report generation not authorized. Reason: {decision.reason} [{decision.decision_id}]" return f"ALLOWED: report generation authorized for '{report_type}'. proof={decision.artifact_hash}"Full crew example — Crew + Agent + Task with pyhall governance
import osfrom crewai import Agent, Task, Crew, Processfrom crewai.tools import toolfrom pyhall import make_decision
WORKER_ID = os.environ["MY_WORKER_ID"]TENANT_ID = os.environ.get("TENANT_ID", "org.default")
# --- Governance tools ---
@tool("pyhall_data_access_check")def pyhall_data_access_check(data_description: str) -> str: """ Check WCP governance before accessing any data source. ALWAYS call this tool first before reading data or querying databases. data_description: what data you intend to read. """ decision = make_decision( capability_id="cap.data.read.v1", worker_id=WORKER_ID, env="prod", data_label="INTERNAL", tenant_id=TENANT_ID, ) if decision.denied: return ( f"ACCESS DENIED. You are not authorized to read data. " f"Reason: {decision.reason}. Stop and report this to the crew." ) return f"ACCESS GRANTED. proof={decision.artifact_hash} [{decision.decision_id}]"
@tool("pyhall_report_check")def pyhall_report_check(report_description: str) -> str: """ Check WCP governance before generating any report or analysis output. ALWAYS call this before producing a report. report_description: what report you intend to generate. """ decision = make_decision( capability_id="cap.report.generate.v1", worker_id=WORKER_ID, env="prod", data_label="INTERNAL", tenant_id=TENANT_ID, ) if decision.denied: return ( f"REPORT DENIED. You are not authorized to generate reports. " f"Reason: {decision.reason}. Stop and report this." ) return f"REPORT AUTHORIZED. proof={decision.artifact_hash} [{decision.decision_id}]"
# --- Real execution tools (add your own) ---
@tool("fetch_sales_data")def fetch_sales_data(period: str) -> str: """Retrieve sales data for the specified period. Requires governance check first.""" # real implementation return f"Sales data for {period}: [records would appear here]"
@tool("write_report")def write_report(content: str, title: str) -> str: """Write a formatted report with the given title and content. Requires governance check first.""" # real implementation return f"Report '{title}' written successfully."
# --- Agents ---
data_analyst = Agent( role="Data Analyst", goal="Retrieve and analyze sales data under WCP governance", backstory=( "You are a governed data analyst. Before accessing any data, you MUST call " "pyhall_data_access_check to verify your authorization. Never skip this step." ), tools=[pyhall_data_access_check, fetch_sales_data], verbose=True,)
report_writer = Agent( role="Report Writer", goal="Generate authorized reports from analyzed data", backstory=( "You are a governed report writer. Before generating any report, you MUST call " "pyhall_report_check. Only proceed if the check returns AUTHORIZED." ), tools=[pyhall_report_check, write_report], verbose=True,)
# --- Tasks ---
data_task = Task( description=( "Check governance authorization for data access, then fetch Q1 2026 sales data. " "If governance is denied, stop and report the denial." ), expected_output="Authorized sales data for Q1 2026 with governance proof.", agent=data_analyst,)
report_task = Task( description=( "Check governance authorization for report generation, then write a summary report " "from the Q1 sales data. Include the governance proof hash in the report header." ), expected_output="A formatted Q1 2026 sales summary report with governance proof.", agent=report_writer, context=[data_task],)
# --- Crew ---
crew = Crew( agents=[data_analyst, report_writer], tasks=[data_task, report_task], process=Process.sequential, verbose=True,)
result = crew.kickoff()print(result)Multi-agent crew with per-agent worker IDs
Bind different pyhall worker IDs to different agents for finer access control:
import osfrom crewai.tools import toolfrom pyhall import make_decision
def make_governed_tool(tool_name: str, capability_id: str, worker_id: str, tenant_id: str): """Factory: produce a @tool for a specific worker + capability pair."""
@tool(tool_name) def governed_tool(action_context: str) -> str: f""" WCP governance check for {capability_id}. action_context: describe the action about to be performed. """ decision = make_decision( capability_id=capability_id, worker_id=worker_id, env="prod", data_label="INTERNAL", tenant_id=tenant_id, ) if decision.denied: return f"DENIED [{worker_id}] {capability_id}: {decision.reason}" return f"ALLOWED [{worker_id}] {capability_id} proof={decision.artifact_hash}"
return governed_tool
analyst_governance = make_governed_tool( "analyst_governance_check", "cap.data.read.v1", os.environ["ANALYST_WORKER_ID"], "org.acme",)
writer_governance = make_governed_tool( "writer_governance_check", "cap.report.generate.v1", os.environ["WRITER_WORKER_ID"], "org.acme",)Using the local Hall Server (self-hosted)
import osimport requestsfrom crewai.tools import tool
HALL_URL = os.environ.get("HALL_SERVER_URL", "http://localhost:8765")HALL_TOKEN = os.environ["HALL_SESSION_TOKEN"]WORKER_ID = os.environ["MY_WORKER_ID"]
@tool("pyhall_local_governance_check")def pyhall_local_governance_check(capability_id: str) -> str: """ Check WCP governance via local Hall Server before executing a capability. capability_id: the capability to authorize (e.g., 'cap.data.read.v1'). """ resp = requests.post( f"{HALL_URL}/api/route", json={ "capability_id": capability_id, "worker_id": WORKER_ID, "env": "dev", "data_label": "PUBLIC", "tenant_id": "org.default", }, headers={"Authorization": f"Bearer {HALL_TOKEN}"}, timeout=5, ) resp.raise_for_status() result = resp.json() if result.get("denied"): return f"DENIED: {capability_id}. decision_id={result['decision_id']}" return f"ALLOWED: {capability_id}. proof={result.get('artifact_hash')} [{result['decision_id']}]"WCP governance chain (every decision)
- Manifest hash verification — worker binary matches registered hash
- Worker attestation check — worker is attested and not expired
- WCP policy evaluation — capability is in worker’s declared set
- Ban list check — worker hash not on global or tenant ban list
- ALLOW or DENY → immutable audit record written
Deny-by-default. No silent fallbacks.
Getting started
pip install pyhall-wcp crewai crewai-toolspyhall auth login— authenticatepyhall worker register— get yourworker_id(one per agent role if needed)- Set
PYHALL_API_KEY,MY_WORKER_ID,OPENAI_API_KEYin your environment - Add governed tools to each
Agentusing the patterns above pyhall decision query --worker <id>— audit your crew’s decision history
Full documentation: https://pyhall.dev/introduction/ WCP specification: https://workerclassprotocol.dev/spec/ Registry API: https://api.pyhall.dev