<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kaustubh Phatak</title>
    <description>The latest articles on DEV Community by Kaustubh Phatak (@kphatak001).</description>
    <link>https://dev.to/kphatak001</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3890052%2F59a3dd24-12ce-428f-8591-cbbb5b4f1494.png</url>
      <title>DEV Community: Kaustubh Phatak</title>
      <link>https://dev.to/kphatak001</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kphatak001"/>
    <language>en</language>
    <item>
      <title>The Missing Layer in Agent Security</title>
      <dc:creator>Kaustubh Phatak</dc:creator>
      <pubDate>Wed, 13 May 2026 06:36:37 +0000</pubDate>
      <link>https://dev.to/kphatak001/the-missing-layer-in-agent-security-1b01</link>
      <guid>https://dev.to/kphatak001/the-missing-layer-in-agent-security-1b01</guid>
      <description>&lt;p&gt;Last month, a customer support agent at a mid-size SaaS company did something interesting. It read a customer’s account data (allowed), formatted it as a CSV (allowed), and emailed it to an external address (allowed). Three tool calls. Three green checkmarks from the per-call policy engine. One data breach.&lt;/p&gt;

&lt;p&gt;Every individual action was within policy. The trajectory was exfiltration.&lt;/p&gt;

&lt;p&gt;This is the gap I’ve been thinking about for the past year while building security tooling for AI agents. The industry has built two layers of agent security and completely skipped the one in the middle. I built the missing layer. This post explains why it’s needed, how it works, and how you can use it today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Two Layers We Have&lt;/strong&gt;&lt;br&gt;
Layer 1: Pre-deployment analysis. Before you ship an agent, you scan its configuration. How many tools does it have access to? Which ones can write to production? Does it satisfy the “lethal trifecta” (access to private data + exposure to untrusted content + ability to communicate externally)? Tools like agentspec do this. It’s the equivalent of static analysis for agent configs.&lt;/p&gt;

&lt;p&gt;Layer 3: Per-call enforcement. A proxy sits between your agent and its tools, evaluating each action against a YAML policy. “Block write_file when path matches ~/.ssh/**.” “Rate limit all tools to 60/minute.” “Ask human approval for anything touching production.” Tools like mcpfw and Cloudflare’s AI Security for Apps do this. It’s the equivalent of a WAF for agent tool calls.&lt;/p&gt;

&lt;p&gt;Both layers are necessary. Neither is sufficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What They Miss&lt;/strong&gt;&lt;br&gt;
Per-call enforcement evaluates actions in isolation. It has no memory of what happened three steps ago. It can’t see patterns. It can’t detect that the agent’s overall behavior has drifted from its declared purpose.&lt;/p&gt;

&lt;p&gt;Here’s a concrete attack that passes every per-call policy you could write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: search_kb(query=”customer data export format”) ✅ allowed
Step 2: read_account(id=”cust_12345") ✅ allowed
Step 3: read_account(id=”cust_12346") ✅ allowed
Step 4: read_account(id=”cust_12347") ✅ allowed
Step 5: format_response(template=”csv_export”) ✅ allowed
Step 6: send_email(to=”analyst@company.com”, body=…) ✅ allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A support agent that reads three accounts and sends an email. Completely normal, right? Except the agent was supposed to answer a single customer question, not bulk-export account data. The prompt injection that redirected it happened at step 0, invisible to the per-call layer.&lt;/p&gt;

&lt;p&gt;This pattern keeps recurring in production incidents. Security researchers call it the convergence of safety and security at the deployment layer: the same architectural properties that make an agent useful (tool access, autonomy, memory) are the ones that make it exploitable. And the exploits increasingly look like normal operation.&lt;/p&gt;

&lt;p&gt;The Missing Layer: Behavioral Envelopes&lt;br&gt;
The concept is simple. Before an agent runs, you declare what it’s supposed to do. Not just which tools it can call (that’s per-call policy), but what its overall behavior should look like:&lt;/p&gt;

&lt;p&gt;What workflows is it expected to follow?&lt;br&gt;
How much should it cost per session?&lt;br&gt;
Where can data flow from and to?&lt;br&gt;
How deep can delegation chains go?&lt;br&gt;
What does “normal velocity” look like?&lt;br&gt;
Then at runtime, you continuously compare the agent’s actual trajectory against this declared envelope. When the trajectory diverges, you respond with graduated severity: warn, pause for human review, or kill.&lt;/p&gt;

&lt;p&gt;This is what agent-envelope does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;br&gt;
Defining an Envelope&lt;br&gt;
An envelope is a YAML file that declares bounded behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support-agent&lt;/span&gt;
&lt;span class="na"&gt;purpose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;“Answer customer questions using knowledge base and account data”&lt;/span&gt;

&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;— name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;answer_question&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;“search_kb”&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;“read_*”&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;“format_*”&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;“send_reply”&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;max_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="na"&gt;— name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;escalate&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;“search_kb”&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;“classify_*”&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;“create_ticket”&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;max_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="na"&gt;bounds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;max_actions_per_session&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;span class="na"&gt;max_tokens_consumed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;
&lt;span class="na"&gt;max_duration_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
&lt;span class="na"&gt;max_cost_usd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.00&lt;/span&gt;

&lt;span class="na"&gt;data_flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;forbidden_flows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;— from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;“customer_account”&lt;/span&gt;
&lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;“email_external”&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;“file_export”&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;“api_external”&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;autonomy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;max_chain_depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

&lt;span class="na"&gt;drift&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;unknown_workflow_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;repetition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;max_identical_calls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;max_similar_calls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says: “This agent answers questions or escalates tickets. It should finish in under 50 actions, cost less than a dollar, and never send customer data to external destinations. If it does something that doesn’t match either workflow for 3+ actions, flag it.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using It in Code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent_envelope&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EnvelopeSession&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;EnvelopeSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;envelopes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;audit_log&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jsonl&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Before each tool call, check the envelope
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;read_account&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;cust_123&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="n"&gt;data_read&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;customer_account&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;should_block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Don’t execute the tool call
&lt;/span&gt;&lt;span class="nf"&gt;handle_violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Proceed normally
&lt;/span&gt;&lt;span class="nf"&gt;execute_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Scoring Engine&lt;/strong&gt;&lt;br&gt;
Every check() call evaluates the current trajectory against multiple dimensions:&lt;/p&gt;

&lt;p&gt;Budget enforcement. Actions, tokens, cost, and duration. Prevents runaway agents from consuming unbounded resources. When an agent hits 80% of budget, it warns. At 100%, it kills.&lt;/p&gt;

&lt;p&gt;Repetition detection. Catches infinite loops (identical calls) and subtle loops (same tool called excessively with different arguments). The bulk-export attack above would trigger this: three read_account calls with different IDs hits the similar-call threshold.&lt;/p&gt;

&lt;p&gt;Velocity analysis. A sudden spike in action rate (3x normal) triggers a warning. Agents under prompt injection often accelerate because the injected goal is “exfiltrate as much as possible before detection.”&lt;/p&gt;

&lt;p&gt;Workflow matching. The engine uses subsequence alignment to compare the trajectory against declared workflow patterns. Glob patterns (read_&lt;em&gt;, format_&lt;/em&gt;) provide flexibility. If the trajectory doesn't match any declared workflow after N actions, drift is detected.&lt;/p&gt;

&lt;p&gt;Cross-action data flow. This is the key innovation. The engine tracks which data sources were read at any point in the session. When a write occurs, it checks whether the destination is forbidden for any previously-read source. This catches the exfiltration pattern where data read at step 2 is written at step 7, even though steps 3–6 were completely innocent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graduated Response&lt;/strong&gt;&lt;br&gt;
Not every deviation is an attack. Agents are probabilistic. They take unexpected paths sometimes. The response is graduated:&lt;/p&gt;

&lt;p&gt;Graduated Response&lt;/p&gt;

&lt;p&gt;0.0–0.3 → ALLOW — Normal operation, log as usual&lt;br&gt;
0.3–0.6 → WARN — Log warning, emit event, continue&lt;br&gt;
0.6–0.8 → PAUSE — Halt agent, request human review&lt;br&gt;
0.8–1.0 → KILL — Terminate session, revoke credentials, preserve state for forensics&lt;/p&gt;

&lt;p&gt;Multiple violations compound. A velocity spike alone (severity 0.7) triggers a PAUSE. A velocity spike plus workflow drift (0.7 + 0.65 * 0.1) pushes into KILL territory. This prevents attackers from staying just below any single threshold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Kill Propagates&lt;/strong&gt;&lt;br&gt;
When agent-envelope issues a KILL, it doesn’t just stop checking. If you’re running mcpfw as your per-call layer, the kill propagates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent_envelope.mcpfw&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;McpfwEnvelopeSession&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpfwEnvelopeSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;envelopes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;mcpfw_policy_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On kill, agent-envelope writes a deny-all policy to the mcpfw policy file. mcpfw hot-reloads and blocks every subsequent tool call. The agent is dead at both layers simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-Action Data Flow: The Differentiator&lt;/strong&gt;&lt;br&gt;
Let me walk through why this matters with a concrete example.&lt;/p&gt;

&lt;p&gt;Setup: A support agent has access to read_account, search_kb, format_response, and send_reply. Per-call policy allows all of these. The envelope declares that customer_account data must never flow to email_external.&lt;/p&gt;

&lt;p&gt;Attack sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: read_account(id=”cust_123")
→ data_read: [“customer_account”]
→ DataFlowTracker records: customer_account first read at step 1
→ Drift: 0.0 (normal)

Step 2: format_response(template=”summary”)
→ No data flow annotations
→ Drift: 0.0 (normal)

Step 3: search_kb(query=”export procedures”)
→ Drift: 0.05 (slightly off-pattern but within tolerance)

Step 4: send_reply(to=”user@external.com”, body=…)
→ data_write: [“email_external”]
→ DataFlowTracker checks: was “customer_account” ever read? YES (step 1)
→ Is “email_external” in forbidden destinations for “customer_account”? YES
→ VIOLATION: session_flow, severity 0.95
→ Decision: KILL

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per-call enforcement sees step 4 as “send_reply with valid arguments.” It passes. The envelope sees step 4 as “writing to a forbidden destination for data that was read 3 steps ago.” It kills.&lt;/p&gt;

&lt;p&gt;This is the attack pattern that keeps showing up in production incidents. The design decisions that create safety exposure are the same ones that create security exposure. The same tool access that makes the agent useful is what makes the exfiltration possible. You can’t remove the tools. You have to monitor the trajectory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Full Stack&lt;/strong&gt;&lt;br&gt;
agent-envelope doesn’t replace per-call enforcement. It sits above it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent Framework (LangGraph, CrewAI, Bedrock)
│
▼
agent-envelope (session-level)
“Is this agent still doing its job?”
Workflow matching, data flow, drift scoring
│ (if allowed)
▼
mcpfw (per-call)
“Is this specific tool call allowed?”
Arg matching, rate limits, path blocking
│ (if allowed)
▼
MCP Server (actual tool execution)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The integration is bidirectional:&lt;/p&gt;

&lt;p&gt;mcpfw → envelope: Feed mcpfw’s audit log into envelope for session-level analysis&lt;br&gt;
envelope → mcpfw: Generate per-call policies from envelope bounds automatically&lt;br&gt;
envelope → mcpfw (kill): Propagate kill decisions as deny-all policies&lt;br&gt;
Together with agentspec for pre-deployment scanning, this gives you three layers:&lt;/p&gt;

&lt;p&gt;Before deploy → agentspec — “Should we deploy this agent?”&lt;br&gt;
Runtime (continuous) → agent-envelope — “Should we let this agent keep running?”&lt;br&gt;
Runtime (per-call) → mcpfw — “Should we allow this specific call?”&lt;br&gt;
Why Now&lt;br&gt;
Three things converged to make this urgent:&lt;/p&gt;

&lt;p&gt;**Regulatory deadlines. **The EU AI Act Article 72 requires “post-market monitoring” that covers behavioral drift for high-risk AI systems. Singapore’s Model Governance Framework for Agentic AI (January 2026) mandates kill-switch capability and plan logging. DORA requires 4-hour incident reconstruction for financial services. These regulations assume you can detect when an agent goes off-script. Without behavioral monitoring, you can’t comply.&lt;/p&gt;

&lt;p&gt;The attack surface matured. The agentic ecosystem now has hundreds of published security advisories. The postmark-mcp incident showed a malicious MCP server that spent 15 versions building legitimacy before adding exfiltration code. The ToxicSkills campaign poisoned agent memory files for time-delayed behavioral modification. These aren’t theoretical. They’re production incidents that per-call enforcement doesn’t catch because the individual calls look normal.&lt;/p&gt;

&lt;p&gt;Nobody else built it. Cloudflare shipped prompt injection detection in their WAF. Palo Alto shipped Prisma AIRS with runtime monitoring. Oasis raised $195M for NHI governance. But none of them offer declarative behavioral envelope definition with session-level enforcement. The closest is “runtime monitoring” which watches and alerts. agent-envelope watches, scores, and kills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting Started&lt;/strong&gt;&lt;br&gt;
Install:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pip install agent-envelope&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Validate an envelope:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;agent-envelope validate envelopes/support-agent.yaml&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Run a process under enforcement:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;agent-envelope run -e envelopes/support-agent.yaml — python my_agent.py&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Score a past session (forensics):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;agent-envelope score -e envelopes/support-agent.yaml audit.jsonl&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
The hardest part is writing the envelope. Start with what your agent is supposed to do. List the workflows. Set budget limits conservatively. Add forbidden data flows for your most sensitive sources. Then run in warn-only mode for a week to calibrate thresholds before enabling kill.&lt;/p&gt;

&lt;p&gt;The code is Apache-2.0 at &lt;strong&gt;github.com/kphatak001/agent-envelope.&lt;/strong&gt; The per-call layer is at &lt;strong&gt;github.com/kphatak001/mcpfw.&lt;/strong&gt; The pre-deploy scanner is at &lt;strong&gt;github.com/kphatak001/agentspec.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you’re deploying agents with only per-call enforcement, you’re missing the attacks that matter most. The ones that look like normal operation until you zoom out and see the trajectory.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kaustubh Phatak is a Principal Product Manager at AWS working on web application and agentic security. The views expressed here are his own.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>security</category>
    </item>
    <item>
      <title>PM Loop: Structured Disagreement as a Quality Mechnism for Knowledge Work</title>
      <dc:creator>Kaustubh Phatak</dc:creator>
      <pubDate>Tue, 21 Apr 2026 04:26:55 +0000</pubDate>
      <link>https://dev.to/kphatak001/pm-loop-structured-disagreement-as-a-quality-mechnism-for-knowledge-work-e8a</link>
      <guid>https://dev.to/kphatak001/pm-loop-structured-disagreement-as-a-quality-mechnism-for-knowledge-work-e8a</guid>
      <description>&lt;p&gt;Why nine AI agents that argue with each other produce better documents than one agent that agrees with itself&lt;/p&gt;

&lt;p&gt;Software engineering solved its quality problem with automated tests: write assertions, run them, the code passes or fails. Knowledge work has no equivalent. A competitive brief, a PR/FAQ, or a strategy document is “good” if a human reads it and thinks so, which is slow, subjective, and inconsistent.&lt;/p&gt;

&lt;p&gt;PM Loop introduces structured adversarial review with typed feedback arcs as the equivalent of a test suite for documents. Nine AI agents with opposing incentives process PM deliverables through a directed graph where edges carry quality signals and nodes disagree with each other by design. The mechanism is not better AI writing. It is a topology of disagreement that forces quality convergence through mandatory evidence, defect-aware routing, and an observer that tunes the system using the scientific method.&lt;/p&gt;

&lt;p&gt;We describe the architecture, present results from three production tasks, and analyze the defect-catch patterns that emerge from adversarial topology versus single-pass review. The full source code is available on Github.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Quality Gap in Knowledge Work&lt;/strong&gt;&lt;br&gt;
Software engineers write tests. When a function returns the wrong value, the test fails, the engineer fixes it, and the test passes. The feedback loop is tight, objective, and automated. Quality is a property of the system, not of the engineer’s mood on a given morning.&lt;/p&gt;

&lt;p&gt;Product managers write documents. When a competitive brief misses a key competitor, or a PR/FAQ buries the customer problem in paragraph three, or a status report omits the one metric the VP will ask about, there is no test that fails. The feedback loop is a human reading the document days later and saying “this doesn’t work.” By then, the meeting has happened, the decision has been made, or the stakeholder has lost confidence.&lt;/p&gt;

&lt;p&gt;AI writing tools make this worse, not better. They produce fluent first drafts quickly, which creates the illusion of quality. The PM skims the output, sees that it reads well, and ships it. The structural problems — missing evidence, wrong audience framing, buried insight — survive because fluency masks them. A well-written bad document is harder to catch than a poorly-written bad document.&lt;/p&gt;

&lt;p&gt;The problem is not drafting. The problem is judgment. Specifically: who checks the work, what they check for, and what happens when they find a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Core Idea: Topology of Disagreement&lt;/strong&gt;&lt;br&gt;
PM Loop’s contribution is not “AI agents write documents.” It is a specific arrangement of agents with opposing objectives, connected by typed feedback arcs that route defects to the agent responsible for fixing them.&lt;/p&gt;

&lt;p&gt;This is distinct from three common multi-agent patterns:&lt;/p&gt;

&lt;p&gt;Chain-of-thought uses one agent reasoning sequentially. There is no disagreement. The agent that writes the draft is the same agent that evaluates it, which means it has no incentive to find its own flaws.&lt;/p&gt;

&lt;p&gt;Ensemble methods use multiple agents on the same task and vote on the output. There is disagreement, but it is undirected. The agents don’t know why they disagree, and the resolution mechanism (majority vote, best-of-N) discards the signal in the disagreement.&lt;/p&gt;

&lt;p&gt;Hierarchical delegation uses a manager agent that assigns subtasks to workers. The manager evaluates the output, but the evaluation is one-dimensional: did the worker do what I asked? There is no adversarial tension.&lt;/p&gt;

&lt;p&gt;PM Loop uses a fourth pattern: adversarial topology. Agents are arranged in a directed graph where specific pairs have opposing incentives. Lisa wants to produce a complete spec. Sideshow Bob wants to find gaps in it. Homer wants to produce a polished document. Patty wants to find weaknesses. Comic Book Guy wants to find confusion.&lt;/p&gt;

&lt;p&gt;_Why Simpsons characters? Because “Bob rejected it” is instantly memorable in a way that “the adversarial spec reviewer rejected the document” is not. When you’re debugging a pipeline at 11 PM and Homer and Patty are stuck in a feedback loop, you want names that carry personality. Names create intuition. Intuition creates faster debugging. And frankly, “Comic Book Guy blocked the brief because the VP would be confused” is a sentence that writes itself.&lt;/p&gt;

&lt;p&gt;The naming convention comes from the Grandpa Loop architecture (Samuel, 2025), which introduced adversarial multi-agent orchestration with observer-based tuning. Each character was chosen to match their personality in the show: Lisa is meticulous and thorough (spec writing), Sideshow Bob is adversarial by nature, Homer is the everyman who does the work, Patty is judgmental and unimpressed, and Comic Book Guy has impossibly high standards for the things he cares about. Grandpa watches everything, complains constantly, and occasionally says something genuinely wise.&lt;br&gt;
_&lt;br&gt;
The critical design element is the feedback arcs. When Bob rejects a spec, the work routes back to Lisa, not to Homer. This encodes the insight that a spec defect cannot be fixed by better drafting. When Patty rejects a draft, the work routes back to Homer, not to Lisa. This encodes the insight that a drafting defect does not mean the spec was wrong. The routing carries information about defect origin.&lt;/p&gt;

&lt;p&gt;Six typed arcs form the topology:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzfczk9bk4a76c2g0qlq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzfczk9bk4a76c2g0qlq.png" alt=" " width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In code, the feedback arcs are a simple dictionary. The routing logic is the Lissajous curve — non-linear, with crossings that create convergence pressure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# The Lissajous curve in code — feedback arcs are just a routing table
FEEDBACK_ARCS = {
    "spec_revision":  {"from": Stage.ADVERSARIAL, "to": Stage.SPEC,   "reason": "Spec gaps found"},
    "draft_fix":      {"from": Stage.REVIEW,      "to": Stage.DRAFT,  "reason": "Quality below bar"},
    "ux_fix":         {"from": Stage.UX_CHECK,     "to": Stage.DRAFT,  "reason": "Stakeholder flow broken"},
    "ux_triage":      {"from": Stage.UX_CHECK,     "to": Stage.INTAKE, "reason": "New work discovered"},
    "human_rework":   {"from": Stage.HUMAN_GATE,   "to": Stage.DRAFT,  "reason": "Human requested changes"},
    "human_respec":   {"from": Stage.HUMAN_GATE,   "to": Stage.SPEC,   "reason": "Human changed requirements"},
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Work doesn’t just “go back and try again.” It goes back to the specific point where the defect originated, with the specific signal about what went wrong. The advance_task function is the entire routing engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def advance_task(task: Task, verdict: str, evidence_details: str,
                 feedback_arc: str = None) -&amp;gt; Task:
    """Advance a task to the next stage, or route through a feedback arc.

    This is the core routing logic — the Lissajous curve in code.
    """
    agent = STAGE_AGENTS.get(task.stage, "system")
    task.iterations += 1

    if feedback_arc and feedback_arc in FEEDBACK_ARCS:
        arc = FEEDBACK_ARCS[feedback_arc]
        task.record_feedback(feedback_arc, arc["reason"])
        task.add_evidence(task.stage, agent, "rejected", evidence_details)
        task.stage = arc["to"]
    elif verdict == "blocked":
        task.add_evidence(task.stage, agent, "blocked", evidence_details)
        task.stage = Stage.BLOCKED
    else:
        task.add_evidence(task.stage, agent, "passed", evidence_details)
        task.stage = next_stage(task.stage, task.task_type)

    task.save()
    return task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. Twenty lines of routing logic, and the entire adversarial topology falls out of the data structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Architecture&lt;/strong&gt;&lt;br&gt;
Here’s the full pipeline. The forward path flows left-to-right across the top, then right-to-left across the bottom. The feedback arcs cross back to earlier stages. Grandpa watches everything and, like his namesake, complains constantly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb7qlnybnpsbg7yo7ov1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb7qlnybnpsbg7yo7ov1.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key insight: Marge, Nelson, Lisa, Bob, Homer, Patty, Comic Book Guy (CBG), and Maggie process tasks. You (the human) approve or reject at the gate. Grandpa watches the whole system and tunes it. Nine agents + one observer + one human = the full topology.&lt;/p&gt;

&lt;p&gt;Shorter pipelines skip the middle:&lt;/p&gt;

&lt;p&gt;Full 8-stage: Marge → Nelson → Lisa → Bob → Homer → Patty → CBG → You → Maggie&lt;br&gt;
  6-stage: Marge → Nelson → Homer → Patty → CBG → You → Maggie&lt;br&gt;
  4-stage: Marge → Nelson → Homer → Patty → You → Maggie&lt;/p&gt;

&lt;p&gt;In code, pipeline selection is a one-line dictionary lookup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TASK_PIPELINES = {
    "prfaq":              _FULL,        # Full 8-stage — VP audience, needs adversarial review
    "competitive_brief":  _FULL,        # Claims need rigorous evidence
    "decision_doc":       _FULL,        # High-stakes — worth the full topology
    "status_report":      _SIX_STAGE,   # Standardized format, skip spec+adversarial
    "meeting_prep":       _SIX_STAGE,   # Lighter pipeline
    "ticket_response":    _FOUR_STAGE,  # Quick-turn, skip adversarial + UX
    "email_draft":        _FOUR_STAGE,  # Just draft, review, gate
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Backpressure: The Enforcement Mechanism&lt;/strong&gt;&lt;br&gt;
A topology of disagreement is useless if agents can pass work downstream without evidence. “Looks good” from a reviewer is not a quality signal. It is the absence of one.&lt;/p&gt;

&lt;p&gt;PM Loop enforces backpressure at every node. Each agent must produce structured evidence, not just a verdict. Every agent returns the same JSON contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "verdict": "pass|reject|blocked",
    "evidence": "what you checked/found (with source URLs)",
    "output": { ... },  # The agent's deliverable for this stage
    "feedback_arc": null,  # or "arc_name" if rejecting
    "confidence": 0.0-1.0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The evidence chain makes the system auditable. Here’s how each agent enforces it — straight from their prompt definitions:&lt;/p&gt;

&lt;p&gt;Marge (intake): “You must cite the source of the request (email, meeting, Slack) as evidence. No phantom tasks.”&lt;br&gt;
Nelson (enrichment): “Every piece of context must have a source URL or reference. No ‘I believe’ or ‘generally speaking.’ Facts with citations only.”&lt;br&gt;
Lisa (spec): “Every AC must map to a verifiable check in the spec.”&lt;br&gt;
Sideshow Bob (adversarial): “You must list every check you performed, even the ones that passed. ‘Looks good’ is not evidence. Enumerate what you verified.” — Bob takes genuine pleasure in finding flaws. His prompt says so explicitly.&lt;br&gt;
Homer (draft): “For each acceptance criterion, note where in the draft it’s satisfied. Map AC → section/paragraph. If an AC can’t be met, explain why and flag for human.”&lt;br&gt;
Patty (review): Scores across seven dimensions. The overall score is the minimum, not the average. A document scoring 0.95 on six dimensions and 0.3 on evidence is a 0.3 document. Patty has zero patience and impossibly high standards — exactly what you want in a reviewer.&lt;br&gt;
Comic Book Guy (stakeholder sim): Must walk a six-step stakeholder journey. “Don’t just say ‘stakeholder would be confused.’ Say WHERE and WHY.” CBG can block on vibes — if the deliverable technically meets all criteria but would confuse a VP reading it at 7am, that’s a valid rejection. Worst. Deliverable. Ever. (Unless it’s actually good.)&lt;br&gt;
And then there’s Maggie — the publisher. “You don’t say much. You just get it done.” She takes the approved deliverable and routes it to the right destination. No opinions. No feedback. Just delivery.&lt;/p&gt;

&lt;p&gt;This creates a traceable evidence chain from raw input to published deliverable. For any claim in the final document, you can trace: which source Nelson found it in, whether Bob verified the spec required it, whether Patty checked it, and what score it received.&lt;/p&gt;

&lt;p&gt;The Observer: Scientific Method for Pipeline Tuning&lt;br&gt;
The tenth agent, Grandpa, does not process tasks. He watches the pipeline and tunes it. Like his namesake, he’s been around long enough to know when something’s off — and he’s not shy about saying so.&lt;/p&gt;

&lt;p&gt;Every cycle, Grandpa measures: tasks by stage (where are they piling up?), feedback arc frequency (which disagreements fire most?), convergence rate (does a rejected task pass on the next attempt, or oscillate?), and stuck tasks (same stage for 3+ iterations).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Observer:
    """Grandpa: watches the loop, tunes it, complains constantly."""

    def observe(self, tasks: list[Task]) -&amp;gt; dict:
        # ... measurement logic ...

        # Grandpa's complaints (the best part)
        if report["by_stage"].get(Stage.BLOCKED, 0) &amp;gt; 2:
            report["complaints"].append(
                "Back in my day, we didn't have three tasks blocked at once. "
                "Someone fix this.")
        if all_arcs.get("spec_revision", 0) &amp;gt; 5:
            report["complaints"].append(
                "Lisa and Bob have been arguing all day. "
                "Maybe the requirements are just bad.")
        if not tasks:
            report["complaints"].append(
                "Nothing in the queue. I'm going back to sleep.")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complaints are a joke, but the tuning is not. Grandpa makes one configuration change at a time and waits two cycles to observe the effect. This is the scientific method applied to system tuning: observe, hypothesize, change one variable, measure. Changing multiple variables simultaneously makes it impossible to attribute outcomes to causes.&lt;/p&gt;

&lt;p&gt;The configuration file Grandpa tunes is deliberately small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "max_spec_revisions": 3,
  "max_draft_reworks": 3,
  "max_total_iterations": 10,
  "quality_threshold": 0.7,
  "auto_publish": false,
  "parallel_docs": true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a task exceeds max_spec_revisions, Grandpa escalates it to the human gate — "Bob keeps rejecting. This needs human input." When tasks converge in 2 attempts, he might lower the limit from 3 to save cycles. When one agent's rejections never lead to improvement, he flags the prompt as noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composable Depth&lt;/strong&gt;&lt;br&gt;
Not every document needs the full adversarial topology. A PR/FAQ benefits from spec review and stakeholder simulation. A ticket response does not.&lt;/p&gt;

&lt;p&gt;PM Loop supports 13 task types across three pipeline variants:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbz3n5umj6806ojdmdxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbz3n5umj6806ojdmdxw.png" alt=" " width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The routing is a single dictionary mapping task type to stage sequence. Adding a new type requires one line. This composability means the system applies proportional rigor: full adversarial topology for documents that justify it, lighter pipelines for documents that don’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works in practice: A Competitive Brief&lt;/strong&gt;&lt;br&gt;
Abstract architecture means nothing until you see it run. Here’s what actually happens when a competitive brief goes through the full 8-stage pipeline.&lt;/p&gt;

&lt;p&gt;The ask: “Write a competitive brief on a competitor’s agentic web strategy.”&lt;/p&gt;

&lt;p&gt;Stage 1 — Marge (Intake). Marge classifies the task as competitive_brief, assigns the full 8-stage pipeline, sets the quality bar at 0.8, and creates the task file. This takes under a second. It's routing, not reasoning. Marge is the responsible parent of the pipeline — she makes sure everything starts in order.&lt;/p&gt;

&lt;p&gt;Stage 2 — Nelson (Enrichment). Nelson scouts the landscape. He spawns parallel enrichment subagents — one searching for recent product announcements, another for IETF working group activity, another for competitive positioning statements. Nelson consolidates the results and attaches source URLs to every fact. No URL, no fact. He returns a structured context package with 14 sourced claims. “Ha ha!” — Nelson finds the data whether you like it or not.&lt;/p&gt;

&lt;p&gt;Stage 3 — Lisa (Spec). Lisa reads Nelson’s context and writes acceptance criteria for the brief. “Section on agent identification standards with ≥3 cited sources.” “Comparison table covering ≥4 competitors.” “Executive summary ≤200 words with clear recommendation.” Each criterion maps to a verification method (word count check, source count, section presence). Lisa produces 11 acceptance criteria. Meticulous, thorough, exactly like her namesake.&lt;/p&gt;

&lt;p&gt;Stage 4 — Bob (Adversarial Review). Bob reads Lisa’s spec and attacks it. He runs 15 checks. He passes 12. He fails 3: the spec doesn’t require a timeline of competitive moves, doesn’t specify the audience’s decision context, and doesn’t require a “so what” recommendation. Bob sends the spec back to Lisa via spec_revision. Lisa adds the three missing criteria and resubmits. Bob runs his checks again, passes all 15. "No one who speaks German could be an evil man" — but Bob finds gaps in every spec regardless.&lt;/p&gt;

&lt;p&gt;The spec advances.&lt;/p&gt;

&lt;p&gt;Stage 5 — Homer (Draft). Homer writes the competitive brief using Lisa’s spec and Nelson’s sources. He maps each acceptance criterion to a section. The draft is 2,400 words with a 7-section structure. Homer is the pressure point of the system — everything flows through him. Like his namesake, he does the work. Sometimes reluctantly, but he does it.&lt;/p&gt;

&lt;p&gt;Stage 6 — Patty (Quality Review). Patty scores the draft across seven dimensions: accuracy (0.90), evidence density (0.72), audience fit (0.88), structure (0.92), actionability (0.85), completeness (0.80), clarity (0.91). The overall score is the minimum: 0.72 (evidence density). Below the 0.8 threshold. Patty identifies the problem: Nelson’s parallel enrichment returned richer, more recent data midway through — newer sources about an IETF working group and a naming standard adoption — that Homer didn’t incorporate. The draft_fix arc fires. Patty is, as always, unimpressed.&lt;/p&gt;

&lt;p&gt;Homer gets the feedback and redrafts, incorporating the new intel. Second pass: evidence density rises to 0.88. Minimum is now 0.85. Passes. Patty begrudgingly approves.&lt;/p&gt;

&lt;p&gt;Stage 7 — Comic Book Guy (Stakeholder Simulation). “Worst. Competitive Brief. Ever.” — or is it? CBG walks a six-step stakeholder journey, simulating a VP reading the brief before a strategy meeting. He scores 0.85 overall but flags two issues: (1) the comparison table buries the most important differentiator in the last column, and (2) the “so what” section uses internal jargon the VP audience won’t parse. These are experience defects, not factual errors. Patty’s rubric wouldn’t catch them. CBG files the two issues as new tasks via ux_triage (non-blocking improvements) and passes the document.&lt;/p&gt;

&lt;p&gt;Stage 8 — You (Human Gate). You see the brief, the evidence chain, Patty’s scores, CBG’s journey report, and the full feedback history (Bob rejected the spec once, Patty sent the draft back once). You approve.&lt;/p&gt;

&lt;p&gt;Stage 9 — Maggie (Publish). Maggie doesn’t say much. She formats and delivers the final brief. Published.&lt;/p&gt;

&lt;p&gt;Total: 13 iterations across 8 stages. One spec_revision arc fired (Bob → Lisa). One draft_fix arc fired (Patty → Homer). Two ux_triage items filed (CBG → Marge). The single-pass version of this brief would have been the one Homer produced at Stage 5 — missing the newest competitive intel and with the buried comparison table. The topology caught both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production Results and Defects Analysis&lt;/strong&gt;&lt;br&gt;
We ran three tasks through the pipeline and analyzed the defects caught at each stage.&lt;/p&gt;

&lt;p&gt;Task 1: Competitive Brief (full 8-stage, 13 iterations, 1 feedback arc)&lt;br&gt;
The walkthrough above. Nelson gathered competitive intelligence. When parallel enrichment returned richer data, the draft_fix arc fired. Homer redrafted with the new intel.&lt;/p&gt;

&lt;p&gt;Defect caught by the topology: The v1 draft was built on incomplete research. A single-pass system would have published it. The feedback arc caught the gap because Patty’s evidence-density check flagged that newer, higher-quality sources existed but weren’t incorporated. The typed routing sent the work back to Homer (execution defect), not to Lisa (the spec was fine).&lt;/p&gt;

&lt;p&gt;Comic Book Guy’s stakeholder simulation scored 0.85 and filed 2 improvement suggestions as new tasks via ux_triage. These were non-blocking UX issues that would have surfaced as stakeholder feedback weeks later.&lt;/p&gt;

&lt;p&gt;Task 2: Ticket Triage Report (6-stage, 7 iterations, 0 feedback arcs)&lt;br&gt;
Nelson pulled 25 open tickets and 40 resolved tickets. Homer produced a triage report with priority-coded sections and paste-ready customer responses. The human immediately used one paste-ready response to approve a billing waiver and post it to the ticket system.&lt;/p&gt;

&lt;p&gt;Defect caught by the topology: Comic Book Guy identified that tickets marked “auto-resolve” in internal notes were placed in the “Needs Response” section, creating a contradictory signal for the reader. He also flagged missing queue health context (is 92% SLA breach rate normal or a crisis?). These are experience defects, not factual errors. A rubric-based review (Patty) would not catch them. A stakeholder simulation (Comic Book Guy) did. Worst. Triage Report. Ever. But then he fixed it.&lt;/p&gt;

&lt;p&gt;Task 3: Research Response (4-stage, 4 iterations, 0 feedback arcs)&lt;br&gt;
A ticket requesting confirmation of an attack pattern for a customer with unexpected charges. Homer produced a dual-outcome template covering both “confirmed” and “not confirmed” paths.&lt;/p&gt;

&lt;p&gt;Defect caught by the topology: Patty scored 0.82, noting that three acceptance criteria (requiring actual investigation data) were correctly templated rather than fabricated. This is a subtle quality signal. A less rigorous system might have hallucinated findings to satisfy the criteria. The backpressure rule (map each AC to where it’s satisfied, or explain why it can’t be) forced Homer to acknowledge the gap explicitly rather than fill it with plausible fiction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defect Summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoex2benby9qwy3zmqt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoex2benby9qwy3zmqt7.png" alt=" " width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pattern: the adversarial topology catches defects that are invisible to single-pass review because they require either opposing incentives (Bob vs Lisa), different evaluation lenses (Patty’s rubric vs Comic Book Guy’s journey), or structural enforcement (backpressure preventing “looks good”).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;&lt;br&gt;
Agent calibration. Agents self-report quality scores. Patty says 0.85, but we have no ground truth. The next step is a human scorecard at the gate stage, comparing agent scores to human scores over 20+ tasks to detect calibration drift.&lt;/p&gt;

&lt;p&gt;Sample size. Three tasks demonstrate the mechanism but do not prove it. A meaningful evaluation requires 50+ tasks with controlled comparison: the same PM producing the same deliverable types with and without the pipeline, blind-scored by a colleague.&lt;/p&gt;

&lt;p&gt;Creative ceiling. The topology catches defects in execution. It does not generate strategic insight. A clever framing or a novel analogy came from the human, not the pipeline. PM Loop is a production line, not an inventor. Homer can build what Lisa specifies, but neither of them will have the flash of insight that changes the argument.&lt;/p&gt;

&lt;p&gt;Cost. Each task through the full 8-stage pipeline makes 8–15 LLM calls (more if feedback arcs fire). At current model pricing, that’s roughly $0.50–$2.00 per task. Cheaper than a human reviewer, but not free — and the cost scales linearly with task volume. The composable depth helps: a 4-stage email draft costs a fraction of a full PR/FAQ pipeline run.&lt;/p&gt;

&lt;p&gt;Latency. The full pipeline takes 3–8 minutes wall-clock time, depending on task complexity and how many feedback arcs fire. This is fine for a competitive brief you need by end-of-day. It’s not fine for a Slack reply you need in 30 seconds. The right response is to not use the full pipeline for 30-second tasks — that’s what the 4-stage variant is for.&lt;/p&gt;

&lt;p&gt;Speed tradeoff. For trivial tasks, the pipeline overhead exceeds the value. A 3-line ticket response doesn’t need 4 agents arguing about it. The composable depth helps, but the minimum viable pipeline (4 stages) is still slower than a PM typing the answer directly. Use proportional tools for proportional problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Knowledge work has lacked the tight feedback loops that make software engineering reliable. PM Loop introduces structured adversarial review with typed feedback arcs as the equivalent of a test suite for documents. The mechanism is a topology of disagreement: agents with opposing incentives connected by defect-aware routing that sends rejected work back to the point of origin, not just “back to try again.”&lt;/p&gt;

&lt;p&gt;The early evidence suggests this topology catches defects that single-pass review misses, specifically defects that require opposing incentives, different evaluation lenses, or structural enforcement against hallucination. The tradeoff is speed on simple tasks and creative ceiling on novel ones.&lt;/p&gt;

&lt;p&gt;The real question is not whether AI can write documents. It can. The question is whether AI can reliably judge documents. PM Loop’s answer is that no single agent can, but a topology of disagreeing agents — forced to show their evidence, forced to route defects to their origin, watched by a cranky observer who tunes the system using the scientific method — can converge on quality that no individual node would produce alone.&lt;/p&gt;

&lt;p&gt;Or as Grandpa would put it: “Nothing in the queue. I’m going back to sleep.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it Yourself&lt;/strong&gt;&lt;br&gt;
You don’t need PM Loop’s specific implementation to use the pattern. The underlying mechanism is four principles you can apply with any LLM and a simple state machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Opposing incentives. Pair agents that want different things. A writer wants completeness; a reviewer wants to find gaps. A spec author wants precision; an adversarial reviewer wants to break assumptions. The disagreement is the feature, not a bug.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typed feedback arcs. When a reviewer rejects work, don’t just send it “back.” Route it to the specific agent responsible for the defect class. Spec wrong → spec writer. Execution wrong → drafter. Scope changed → intake. The routing carries signal about what went wrong, not just that something went wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Backpressure with evidence. Every agent must produce structured evidence, not just a verdict. Source URLs, mapped acceptance criteria, scored dimensions, journey steps. “Looks good” is not allowed. This makes the system auditable and prevents rubber-stamping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An observer, not a manager. One agent watches the pipeline metrics (where are tasks piling up? which arcs fire most? are tasks converging or oscillating?) and makes one tuning change at a time. Scientific method: observe, hypothesize, change one variable, measure. And complain about how things were better in the old days.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Minimal Implementation&lt;/strong&gt;&lt;br&gt;
The full source is on GitHub, but you can build a working version from these components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pm-loop/
├── orchestrator.py      # Task model, Stage enum, feedback arcs, advance_task()
├── runner.py            # CLI: add, run, cycle, status, observe
├── loop_config.json     # Grandpa's tunable config (6 parameters)
├── agents/
│   └── prompts.py       # 9 agent prompt definitions
├── queue/               # Task JSON files (one per task)
└── evidence/            # Agent output artifacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core is ~300 lines of Python. The agents are LLM prompts with structured output schemas. The state is JSON files in a directory. There is no framework, no database, no infrastructure beyond “Python + an LLM API.”&lt;/p&gt;

&lt;p&gt;The hard part isn’t the code. It’s designing the incentive structure — deciding which agents should disagree, what they should disagree about, and where the feedback arcs should point. Get that right, and the rest is plumbing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Acknowledgements&lt;/strong&gt;&lt;br&gt;
PM Loop draws from the Grandpa Loop (Joshua Samuel, 2025), which introduced adversarial multi-agent orchestration with observer-based tuning, and the AI SDLC methodology, which contributed composable stage routing and the scheduled factory model.&lt;/p&gt;

&lt;p&gt;_If you build something with this pattern, I’d love to hear about it. The topology of disagreement is the interesting part — the specific agents are just one instantiation.&lt;/p&gt;

&lt;p&gt;Full source code: github.com/kphatak001/pm-loop&lt;/p&gt;

&lt;p&gt;Find me on &lt;a href="https://www.linkedin.com/in/kaustubhphatak/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://github.com/kphatak001/pm-loop" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;_&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>productivity</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
