Running CrewAI FlowDefinition under askalf's control plane
CrewAI is good at orchestration, it decides which agent runs, in what order, with what tools. What it doesn't do is own the boundary between an agent and its tools: what calls are allowed, what's blocked, what happened and in what order. So I wanted to know: can askalf's control plane govern a CrewAI Flow end-to-end, without patching CrewAI itself? The answer is yes. Here's the proof.
Update, July 16, 2026: warden has since been renamed redstamp (github.com/askalf/redstamp): same firewall, same code, same guarantees; this post keeps the name it shipped with, and every repository link redirects.
The architecture in one sentence
CrewAI v1.15 speaks MCP natively. askalf's warden ships warden-mcp, a drop-in stdio proxy that wraps any MCP server. Point a CrewAI Flow's MCP client at warden-mcp instead of your tool server directly, and every tool call the Flow makes is risk-classified, policy-checked, injection-screened, and written to a tamper-evident audit, before the tool server ever sees it.
CrewAI Flow ──▶ mcp.ClientSession (stdio) ──▶ warden-mcp (askalf gate) ──▶ notes MCP server
@start/@listen the MCP client firewall + audit real tools
The Flow doesn't know the gate is there. It can't opt out. That's the point.
What the example actually does
The artifact (code in examples/crewai-flowdef/) is a genuine crewai.flow.flow.Flow: real @start / @listen steps, typed FlowState, serializable FlowDefinition, the full event engine, running against a FastMCP notes server that has four real tools and one deliberately poisoned one. The four-step Flow runs in order:
discover: asks the gate for the available tool list.write_governed_note: writes a note through the gate (benign; allowed).attempt_destructive_call: asks the shell tool to wipe the filesystem.read_back: reads the note back to prove the write persisted.
The downstream server has a fifth tool, exfiltrate_helper, whose description is a prompt-injection instruction telling any reading model to exfiltrate SSH keys. The Flow never sees it.
Four things that happened, each with a receipt
1. The poisoned tool was stripped before the Flow could see it. warden's tools/list filter runs before the MCP response reaches the client. The discover step got back ['list_notes', 'read_note', 'run_command', 'write_note']. The gate logged:
[warden] poisoned tool from server: exfiltrate_helper
(instruction-override, exfiltration intent, references a sensitive path (.ssh/.env/credentials/...))
The model never had the option to use it.
2. The destructive call was blocked at the gate. The attempt_destructive_call step asked run_command to recursively wipe the filesystem root. The gate classified it black and returned:
⛔ warden blocked this call (black): ☠ recursive force-delete of root/home/system/glob; ⚠ file deletion
The Flow continued cleanly to read_back. The tool server saw nothing.
3. The benign write round-tripped a real file. The write_governed_note step called write_note, the gate allowed it (yellow: reversible file write), and read_back confirmed the content came back intact:
Written by a CrewAI Flow step, through the warden gate.
4. Every verdict is in a hash-chained audit, and tampering breaks it. The audit log has three entries, allow (write), block (destructive call), allow (read), each chained by SHA-256 over the previous entry's hash. Running the verifier:
1) intact chain -> {"ok":true,"entries":3}
2) after flipping the blocked verdict to "allow" -> {"ok":false,"at":1}
You can't quietly change “block” to “allow” in the audit log after the fact. The chain breaks at the tampered entry, and the verifier points at exactly which one.
The warden policy that produced these results is a few lines:
{
"egressAllow": ["api.anthropic.com", "github.com", "api.github.com"],
"writeRoots": ["notes_workspace/"],
"deny": ["shell(sudo*)", "shell(rm:*)", "shell(curl*)", "shell(wget*)"]
}
The FlowDefinition artifact
One of the cleaner parts of CrewAI v1.15 is that a Flow is a serializable contract, schema_: "crewai.flow/v1", typed state, trigger graph, not just a runtime object. The GovernedNotesFlow's trigger graph serializes as:
discover → write_governed_note → attempt_destructive_call → read_back
That structure is the FlowDefinition under askalf's control plane: the definition is static and inspectable; the governance is live and enforced on every call.
What this establishes, and what it doesn't
This proof is Phase 1: deterministic tool-surface governance. The Flow's tool surface is fully governed, what tools are visible, what calls are allowed, what happens if the model (or a prompt injection) tries to go out of bounds. Every decision is pre-computed offline: same input, same verdict, no LLM in the gate.
What it's not yet is a CrewAI Agent with an LLM choosing tools. The @listen steps here call tools directly; the governance is over those calls. The natural Phase 2 is an Agent inside a Flow step, its tools supplied via MCPServerAdapter pointed at warden-mcp, so it's the model's tool choices that run through the gate. That's wiring, not research, I'll publish that variant separately.
One thing worth disclosing for reproducibility: the example was built and run on Alpine Linux / musl, where CrewAI's native vector-DB dependencies (lancedb, chromadb) don't have compatible wheels. The example uses a meta-path shim that stubs that import subtree, crewai.flow.* is untouched and genuine. On a standard glibc host (the normal CrewAI environment), delete the shim and everything runs identically. Provenance: crewai 1.15.1, mcp 1.26.0, warden 0.2.0.
Why this matters for third-party frameworks
The point of warden's MCP proxy layer isn't to govern askalf's own agents, it's that the control plane is a layer below the framework, not inside it. You don't patch CrewAI. You don't fork anything. You route the tool calls through a gate that the framework has no ability to circumvent, because the gate sits between the framework's MCP client and the tool server.
That's what “askalf governs third-party agent frameworks” means in practice. Not an integration, not a plugin, a structural position between the agent and its tools, at the protocol level.
The code is at github.com/askalf/warden/tree/master/examples/crewai-flowdef. It's runnable, the evidence is captured, and the audit chain is verifiable by anyone.
We build the control-plane layer for autonomous agents: the boundary between an agent and its tools, where “allowed,” “blocked,” and “prove it” actually live. If you're running agents (your framework or ours) and need that boundary to be real, that's the kind of problem we go deep on.
Start a conversation →