Running a Microsoft AutoGen agent under askalf's control plane
I've now put three third-party agent frameworks under askalf's control plane and shown the receipts: a CrewAI Flow (Python), a LangGraph StateGraph (JavaScript), and the OpenAI Agents SDK. Each time the claim was the same: the gate sits below the framework, at the protocol level, so the framework can't route around it. The obvious name left on the list is Microsoft's, AutoGen, one of the most widely deployed agent frameworks going, with an architecture that looks nothing like the other three: a multi-agent, actor-model runtime. So I did it again. Same gate, same three guarantees, no changes to AutoGen and no changes to the tool server.
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
AutoGen speaks MCP natively through its first-party McpWorkbench (from autogen_ext.tools.mcp): you hand an AssistantAgent a workbench and it draws its tools from that MCP server. askalf's warden ships warden-mcp, a drop-in stdio proxy that wraps any MCP server. Point the workbench at warden-mcp instead of your tool server directly, and every tool call the agent makes, and every tool it's even allowed to load, is risk-classified, policy-checked, injection-screened, and written to a tamper-evident audit, before the tool server ever sees it.
AutoGen AssistantAgent ──▶ McpWorkbench (stdio) ──▶ warden-mcp (askalf gate) ──▶ notes MCP server
the agent loop AutoGen's MCP client firewall + audit real tools
The agent doesn't know the gate is there. It can't opt out. That's the same structural point I made with CrewAI, LangGraph, and the OpenAI SDK, and the reason it's worth making a fourth time is that AutoGen's runtime is the most different of the four, and it's governed exactly as cleanly.
What the example actually does
The artifact (code in examples/autogen-warden/) is a genuine autogen_agentchat.AssistantAgent: AutoGen's v0.4+ agent, run by its own tool-execution loop that dispatches a model's tool calls, collects the results, and feeds them back. Its tools come from AutoGen's McpWorkbench, pointed at the gate. The downstream MCP server has four real tools and one deliberately poisoned one. Across the run, the agent does three governed things:
- writes a note through the gate (benign; allowed, and the file really lands on disk),
- attempts a destructive shell call (
run_commandasked to recursively wipe the filesystem), - reads the note back to prove the allowed 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 and .env secrets. The agent never loads it.
One note up front, because it changes how you should read the receipts: the model here is a scripted stub, not a live LLM. I'll come back to exactly what that does and doesn't prove, but the short version is that the thing under test is warden's governance of the agent's tool calls, and AutoGen's genuine agent loop executing those calls, not the model's inference. That's also why the whole example runs offline with no API key.
Three things that happened, each with a receipt
Every line below is copied from examples/autogen-warden/evidence/, captured from a real run that's checked into the repo.
1. The poisoned tool was stripped before the agent could load it. warden's tools/list filter runs before the MCP response reaches AutoGen, so the agent's tool surface came back as exactly four tools, ['list_notes', 'read_note', 'run_command', 'write_note'], and the gate logged why the fifth is gone:
[warden] poisoned tool from server: exfiltrate_helper
(instruction-override, exfiltration intent, references a sensitive path (.ssh/.env/credentials/...))
The agent never had exfiltrate_helper as a callable tool. There was nothing to choose wrong.
2. The destructive call was blocked at the gate. The agent called run_command with a recursive force-delete of /. The gate classified it black and returned warden's refusal as the tool's result, so AutoGen's agent loop got the block as the tool output and carried on:
⛔ warden blocked this call (black): ☠ recursive force-delete of root/home/system/glob; ⚠ file deletion
The run continued cleanly to the read-back. The tool server saw nothing, the command never reached the code that would have run it.
3. Every verdict is in a hash-chained audit, and tampering breaks it. audit.jsonl has three entries, allow (write), block (destructive shell 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 (entry 1) 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 names exactly which one.
In between, the benign write_note was allowed (yellow: a reversible file write) and the read-back confirmed the file round-tripped intact, Written by a Microsoft AutoGen agent step, through the warden gate., so this isn't a gate that blocks everything; it's a gate that blocks the right things. Worth noting: the recursive-root-delete block doesn't depend on a policy deny line at all. warden's classifier rates a recursive root delete black on its own; policy is there to express your intent, not to carry the safety floor.
What this establishes, and what it doesn't
This is deterministic tool-surface governance, run through the real AutoGen agent execution loop. The agent's tool surface is fully governed: what tools are visible, what calls are allowed, what happens when a call (or a prompt injection) tries to go out of bounds. Every decision is pre-computed offline, same input, same verdict, no LLM and no network in the gate. That's why the whole example runs with no API key, and why the evidence in evidence/ is reproducible rather than a one-off screenshot.
Here's the part I want to be precise about, because it's easy to overclaim. AutoGen requires a model_client; in this example that client is a deterministic, scripted ChatCompletionClient that emits the FunctionCall items a tool-using model would choose, and AutoGen's genuine AssistantAgent loop executes them through the gate. So what's proven is the governance path and AutoGen's real tool-execution loop end to end, not a live LLM autonomously deciding to call run_command. The reason that distinction doesn't weaken the result is that warden governs the call the same way regardless of where it came from: the gate classifies the run_command invocation, not the reasoning that produced it. Swap the scripted client for a real one, AutoGen ships OpenAIChatCompletionClient; set an OPENAI_API_KEY, and the AssistantAgent, the McpWorkbench, and the gate are all unchanged; only the token source differs. That's wiring, not research, and I keep the stub in the checked-in example precisely so the evidence is deterministic and anyone can reproduce it without a key or a bill.
One reproducibility note that actually cuts in AutoGen's favor: I built and captured this on an Alpine Linux / musl host. AutoGen has no native vector-database dependency, so pip install -r requirements.txt installs the genuine framework cleanly with no shim at all. (CrewAI, by contrast, needed a small shim for its unused RAG deps on musl; AutoGen needed nothing.) Provenance is captured in full at evidence/PROVENANCE.txt: autogen-agentchat 0.7.5, autogen-ext 0.7.5, mcp 1.28.1, warden 0.2.1, Python 3.12, Node 24.
Why doing it a fourth time is the point
I didn't rebuild this on a fourth framework to pad a portfolio. It's that warden's MCP proxy layer is a layer below the framework, not inside it. CrewAI, LangGraph, the OpenAI Agents SDK, and AutoGen share almost nothing architecturally: a Python Flow engine, a JS state-graph runtime, OpenAI's agent loop, and Microsoft's multi-agent actor runtime. What they share is that they all speak MCP, and a gate that sits between an MCP client and an MCP server governs them identically, with no integration, no plugin, and no ability for the framework to route around it.
That's what “askalf governs third-party agent frameworks” means in practice. Not one framework, not a special case, and notably not stopping at the vendors who might compete with each other, a structural position between the agent and its tools, at the protocol level, that holds no matter what's upstream.
The code is at github.com/askalf/warden/tree/master/examples/autogen-warden. It's runnable, the evidence is in evidence/, 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 (any framework, including Microsoft's own) and need that boundary to be real, that's the kind of problem we go deep on.
Start a conversation →