Your CrewAI crew kicks off a task. Agents delegate to each other, call tools, return results. But can you prove what each agent actually did?
CrewAI's built-in logs capture what happened. Cryptographic receipts prove it. The difference matters when an auditor, a customer, or a regulator asks "show me exactly what the agent did and prove it wasn't altered after the fact."
This tutorial adds Ed25519-signed, hash-chained audit trails to a CrewAI crew in under 5 minutes. Signet itself needs no external service, no signing API, no infrastructure — receipts verify offline with a public key. (Your CrewAI agent and any tools it uses still need their own keys; that part is unchanged.)
What you'll build
A CrewAI crew where every tool call produces a signed receipt containing:
- What: which tool was called, with what parameters
- Who: the agent's Ed25519 public key
- When: timestamp
- Proof: Ed25519 signature over JCS-canonicalized (RFC 8785) payload
- Chain: SHA-256 hash linking to the previous receipt (tamper-evident ordering)
If anyone modifies a receipt after the fact, the signature breaks. If anyone deletes or reorders receipts, the hash chain breaks.
Install
pip install signet-auth[crewai] crewai-tools
Requires a CrewAI release that exposes the crewai.hooks global tool-hook API (tested with the current PyPI release). crewai-tools ships the SerperDevTool used below; it is not bundled into the [crewai] extra.
SerperDevTool calls Serper's web-search API and the CrewAI agent itself calls an LLM provider, so this exact demo also wants:
export OPENAI_API_KEY="sk-..."
export SERPER_API_KEY="..."
If you'd rather run zero-key, swap SerperDevTool() for any local tool — Signet signs whatever flows through crewai.hooks regardless of what the tool does.
Step 1: Create a signing identity
from signet_auth import SigningAgent, KeyNotFoundError
# Load an existing Ed25519 identity, or create one on first run.
# Keys live at ~/.signet/keys/ — the private key stays local.
try:
agent = SigningAgent("my-crewai-agent")
except KeyNotFoundError:
agent = SigningAgent.create("my-crewai-agent", owner="acme-corp")
print(f"Public key: {agent.public_key}")
No key server, no certificate authority. The private key stays on disk, the public key is what verifiers use.
Step 2: Install the signing hooks
CrewAI exposes global tool hooks. Signet plugs into them with one call:
from signet_auth.crewai import install_hooks
install_hooks(agent)
That's it. Every tool call across every agent in every crew is now signed automatically.
The hooks cover the full lifecycle: before_tool_call (what was called, signed before the tool runs) and after_tool_call (what was returned, hashed and signed). Recoverable signing errors (SignetError — bad payload, audit-log IO trouble) are caught and logged as a warning. Programmer errors that imply your code is in a bad state (e.g. calling agent.close() and then continuing to use the agent) still raise — by design, so you find them at dev time rather than silently dropping receipts in production.
Step 3: Run your crew normally
from crewai import Agent, Crew, Task
from crewai_tools import SerperDevTool
researcher = Agent(
role="Research Analyst",
goal="Find information on a given topic",
backstory="Experienced analyst with attention to detail",
tools=[SerperDevTool()],
)
task = Task(
description="Research the weather in Tokyo",
expected_output="A summary of today's weather",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[task])
result = crew.kickoff()
No changes to your agents, tools, or crew. The signing happens transparently through the hooks.
Step 4: Inspect the receipts
import json
from signet_auth.crewai import get_receipts
for i, receipt in enumerate(get_receipts()):
data = json.loads(receipt.to_json())
print(f"\nReceipt #{i+1}")
print(f" Tool: {data['action']['tool']}")
print(f" Params hash: {data['action']['params_hash']}")
print(f" Signature: {data['sig'][:40]}...")
print(f" Timestamp: {data['ts']}")
Output:
Receipt #1
Tool: serper_dev
Params hash: sha256:a1b2c3...
Signature: ed25519:Mz4xNTk2NjQ0NDgw...
Timestamp: 2026-04-20T14:30:00Z
Receipt #2
Tool: _tool_end
Params hash: sha256:d4e5f6...
Signature: ed25519:Nk5yODk3MjE1Njg4...
Timestamp: 2026-04-20T14:30:02Z
Notice the start/end pair: the first receipt captures the tool call, the second captures a SHA-256 hash of the output. Together they prove what was called and the hash of what it returned. (The full output stays in your application; the receipt only signs the hash, which is enough to detect any after-the-fact tampering of an output you've stored elsewhere.)
Step 5: Verify a receipt
Anyone with the public key can verify, offline:
from signet_auth import verify
receipts = get_receipts()
receipt = receipts[0]
is_valid = verify(receipt, agent.public_key)
print(f"Valid: {is_valid}") # True
Tamper with any field and verification fails:
data = json.loads(receipt.to_json())
data["action"]["tool"] = "evil_tool" # tamper
from signet_auth import Receipt
tampered = Receipt.from_json(json.dumps(data))
print(verify(tampered, agent.public_key)) # False
Step 6: Verify the audit chain
The audit log is a hash-chained JSONL file. Each entry's hash covers the previous entry, so deleting or reordering receipts breaks the chain:
from signet_auth import audit_verify_chain, default_signet_dir
signet_dir = default_signet_dir()
chain_status = audit_verify_chain(signet_dir)
print(f"Chain intact: {chain_status.valid}")
print(f"Entries: {chain_status.total_records}")
Step 7: Clean up (optional)
When you're done signing, uninstall the hooks:
from signet_auth.crewai import uninstall_hooks
uninstall_hooks()
Why this matters for CrewAI specifically
CrewAI's strength is agent-to-agent delegation. A researcher agent delegates to a writer agent, who calls tools, who returns results that feed back into the chain. When something goes wrong, "which agent did what" becomes a real question.
Signed receipts answer that question independently of CrewAI's own logs. CrewAI's ToolCallHookContext does expose agent to the hook today, but Signet's current install_hooks() binding ties one signing identity to the global hook registration; per-call routing to a per-agent key is on the roadmap. For now, if you need separate keys per agent, the practical pattern is: install with one agent's key, run that agent's task, call uninstall_hooks(), then re-install with the next agent's key.
Either way, the audit trail proves not just what happened but who signed it.
What this gives you
| Without Signet | With Signet |
|---|---|
| "The research agent called serper_dev" (log entry) | Ed25519 signature proving it, verifiable by anyone with the public key |
| Logs can be edited after the fact | Signature breaks if any field is modified |
| No ordering proof | Hash chain breaks if receipts are deleted or reordered |
| Trust CrewAI's logs | Verify independently, offline |
When you need this
- Regulated industries: EU AI Act Article 12 requires "automatic recording of events" for high-risk systems. Tamper-evident signed receipts can support that traceability + log-integrity requirement (the legal sufficiency check is your auditor's call, not Signet's — but they need something better than rotatable plaintext logs to point at).
- Enterprise deployments: When the question is "can you prove what the agent did?", signed receipts are the answer.
- Agent-to-agent: CrewAI's core pattern — when one agent verifies another's work, signatures make it cryptographic, not just log-based.
- Incident response: After something goes wrong in a multi-agent crew, tamper-evident receipts let you reconstruct exactly what happened without trusting anyone's claim.
Next steps
-
Bilateral co-signing: Have both the agent and the tool server sign each interaction independently. Neither party can fabricate receipts. See
signet proxyfor MCP integration. - Policy attestation: Evaluate YAML policy rules and include the decision (allow/deny/require_approval) inside the signed receipt.
- Delegation chains: Prove that Agent A was authorized by Human B to perform a specific action with scoped constraints. Useful when CrewAI agents are acting on behalf of specific users.
All of these are in signet-auth today.
pip install signet-auth[crewai]
GitHub: Prismer-AI/signet
Signet is open source (Apache-2.0 OR MIT). Rust core with Python and TypeScript bindings. The signing layer needs no external service or signing API — receipts verify offline with the public key.
Top comments (0)