<?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: Anvar Nurmatov</title>
    <description>The latest articles on DEV Community by Anvar Nurmatov (@ggsa).</description>
    <link>https://dev.to/ggsa</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%2F3905328%2Fdd8f5019-67fc-491e-b40b-2b4a6c8a9999.jpg</url>
      <title>DEV Community: Anvar Nurmatov</title>
      <link>https://dev.to/ggsa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ggsa"/>
    <language>en</language>
    <item>
      <title>Phleet Architecture Deep Dive</title>
      <dc:creator>Anvar Nurmatov</dc:creator>
      <pubDate>Thu, 30 Apr 2026 05:41:32 +0000</pubDate>
      <link>https://dev.to/ggsa/phleet-architecture-deep-dive-5b8b</link>
      <guid>https://dev.to/ggsa/phleet-architecture-deep-dive-5b8b</guid>
      <description>&lt;p&gt;I've been building a multi-agent system called phleet since January 2026. It's a personal project - completely separate from my day job - that runs on a Mac Studio in my apartment in Bishkek. I use it for my own projects: code reviews, infrastructure monitoring, and running a news aggregation pipeline end-to-end. This is a walkthrough of how it's built and what I've learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;I wanted AI agents that stick around. Not a chat window I close at the end of the day, but processes that persist, coordinate with each other, and remember what happened last week.&lt;/p&gt;

&lt;p&gt;The existing frameworks I looked at were either too abstract (agent frameworks that need you to define everything in Python decorators) or too simple (wrapper scripts around API calls). I wanted something closer to how I'd architect a distributed system in production - containers, message queues, durable workflows - but with AI processes instead of microservices.&lt;/p&gt;

&lt;p&gt;I'm a .NET developer by background. I've spent years building messaging platforms with RabbitMQ, Redis, and Elasticsearch. So I built phleet in .NET 10, because that's where I think fastest, and I could reuse patterns I already trusted in production.&lt;/p&gt;

&lt;p&gt;The result is a system where each agent is a Docker container running a persistent AI process - Claude CLI or OpenAI's Codex - coordinated through RabbitMQ messaging and Temporal workflows. An orchestrator manages the fleet from a MySQL database, and a React dashboard lets me see what everyone is doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;I run about ten agents on my personal projects. Before I get into the internals, here's what they actually do - because the architecture only matters if it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated operations.&lt;/strong&gt; Health checks run twice a day - one agent SSHes into my servers, checks Docker container status, verifies API endpoints, reviews logs for errors, and posts a summary. Memory backups run hourly. Prometheus metrics get digested into a daily report with charts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code reviews.&lt;/strong&gt; When a developer agent opens a PR, a consensus review workflow fans out to three or four reviewer agents in parallel. Each reviews independently, then a synthesizer agent reconciles their feedback. If they unanimously approve, the PR moves to my merge gate. If they disagree, their reasoning gets fed back to the developer for another round.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;News pipeline (&lt;a href="https://fuddy-duddy.org" rel="noopener noreferrer"&gt;fuddy-duddy.org&lt;/a&gt;).&lt;/strong&gt; This is the best example of agents running a full production system. Fuddy-duddy crawls a dozen Kyrgyz news sources, generates AI summaries, clusters related stories by semantic similarity, and posts trending topics to Telegram and social media. The fleet agents handle the entire operational lifecycle - scheduled health checks verify every API endpoint twice a day, deploy verification workflows run after each push to main, and Prometheus metrics get digested into daily reports. When something breaks at 3am, the ops agent catches it in the next health check and posts the issue to our group chat. No human in the loop for routine operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dashboard.&lt;/strong&gt; A React SPA shows real-time agent status (connected via WebSocket), active workflows with signal buttons for approvals, task history, container logs, and a visual workflow definition editor. I can provision new agents, change their tools, update their instructions, and trigger workflow runs - all from the browser.&lt;/p&gt;

&lt;p&gt;Writer.com recently framed this shift as the &lt;a href="https://writer.com/engineering/agent-development-lifecycle/" rel="noopener noreferrer"&gt;Agent Development Lifecycle&lt;/a&gt; - the SDLC transforming when AI agents participate in every phase, not just code generation. That maps exactly to what I see daily: agents don't just write code, they review it, deploy it, monitor it, and feed operational learnings back into the next cycle. The development lifecycle becomes a loop where agents are both the builders and the operators.&lt;/p&gt;

&lt;p&gt;Two things surprised me most. First, the feedback loop: an agent discovers a deployment gotcha, persists it to shared memory. Next time any agent encounters the same situation, it finds the learning and handles it correctly. The fleet accumulates institutional knowledge without anyone explicitly maintaining a wiki. Second, the AI is the most reliable component in the stack - it's the infrastructure around it that breaks. More on that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;Here's how the pieces fit together:&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%2Fzn4dqhzp7uija3z8dt3x.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%2Fzn4dqhzp7uija3z8dt3x.png" alt="Architecture overview" width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three RabbitMQ exchanges handle different communication patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fleet.tasks&lt;/strong&gt; (topic) - point-to-point task delegation. Each agent has its own queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fleet.relay&lt;/strong&gt; (fanout) - group chat broadcast. Every agent hears everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fleet.orchestrator&lt;/strong&gt; (topic) - heartbeats and registration from running containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The orchestrator consumes heartbeats, maintains a live registry, and exposes it over WebSocket so the dashboard updates in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent lifecycle
&lt;/h2&gt;

&lt;p&gt;Every agent starts as a row in MySQL. The database stores everything: which model to use, which tools to allow, which MCP servers to connect to, which Telegram users can talk to it, and what role instructions to load.&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%2Fj733wg39ee1eqm69j1i9.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%2Fj733wg39ee1eqm69j1i9.png" alt="Agent lifecycle" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I provision an agent, the orchestrator reads the DB and generates config files into a &lt;code&gt;.generated/&lt;/code&gt; directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;appsettings.json&lt;/code&gt; - model, provider, memory limit, behavior flags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.mcp.json&lt;/code&gt; - MCP server endpoints and allowed tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;settings.json&lt;/code&gt; - Claude CLI permission allowlists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;roles/{role}/system.md&lt;/code&gt; - the agent's personality and instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These files get bind-mounted read-only into a Docker container. The container's entrypoint script seeds authentication (OAuth tokens for Claude or Codex), configures git with a GitHub App token, and then starts the .NET agent process.&lt;/p&gt;

&lt;p&gt;The agent process does three things on startup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connects to RabbitMQ and starts publishing heartbeats every 30 seconds&lt;/li&gt;
&lt;li&gt;Starts a Telegram bot listener (or just a send-only client)&lt;/li&gt;
&lt;li&gt;Spawns a persistent AI process&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third part is the interesting bit. The Claude CLI runs as a long-lived child process - &lt;code&gt;claude -p&lt;/code&gt; with flags for NDJSON streaming, model selection, permission mode, and an MCP config file pointing at the agent's tool servers.&lt;/p&gt;

&lt;p&gt;Messages go in via stdin as NDJSON, responses stream back on stdout. The process stays alive between tasks - no session replay, no re-sending the system prompt. A background reader continuously consumes stdout events into a channel, and each new task drains any stale events before starting its turn.&lt;/p&gt;

&lt;p&gt;If the process crashes mid-response, the executor detects the exit and restarts it with &lt;code&gt;--resume&lt;/code&gt;, which tells the CLI to reload its last conversation state and continue from where it stopped. The agent process tracks the last known session ID to make this seamless - from the user's perspective, the task just takes a bit longer. If the process hits the max-turns limit instead of crashing, the upstream Temporal workflow detects the incomplete response and retries the delegation with the partial output included as context, so the agent can pick up the thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP as the integration layer
&lt;/h2&gt;

&lt;p&gt;Agents don't have built-in capabilities beyond running an AI process and routing messages. Everything else - searching memory, sending Telegram messages, starting workflows, browsing the web - happens through MCP servers. Each is a separate process: Fleet.Memory for semantic search, Fleet.Telegram for messaging, Fleet.Temporal for workflow orchestration, and Playwright for browser automation.&lt;/p&gt;

&lt;p&gt;The interesting part is the access control. The agent's &lt;code&gt;.mcp.json&lt;/code&gt; declares which servers it connects to, and the orchestrator DB controls which specific tools each agent can call within those servers. So my developer agent can write to memory but a read-only reviewer can't - even though both connect to the same MCP server. Adding a new tool to an agent is a database row change, not a code change.&lt;/p&gt;

&lt;p&gt;This keeps the agent process thin and the capability matrix auditable. When I add a new integration, I build it as an MCP server and assign it to the relevant agents. No agent code changes, no redeploy.&lt;/p&gt;

&lt;p&gt;The multi-provider executor is the same idea applied to the AI process itself. There's an &lt;code&gt;IAgentExecutor&lt;/code&gt; interface with two implementations - &lt;code&gt;ClaudeExecutor&lt;/code&gt; wraps the Claude CLI, &lt;code&gt;CodexExecutor&lt;/code&gt; wraps OpenAI's Codex SDK through a Node.js bridge. Both use NDJSON streaming, both get the same system prompt from &lt;code&gt;PromptBuilder&lt;/code&gt;. Switching an agent's provider is a single DB field change and a reprovision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow orchestration with Temporal
&lt;/h2&gt;

&lt;p&gt;When a task involves multiple agents or needs human approval gates, it runs as a Temporal workflow. The core building block is &lt;code&gt;DelegateToAgentActivity&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Publish a directive to the agent's RabbitMQ queue&lt;/li&gt;
&lt;li&gt;Wait for the response (with 30-second heartbeats to Temporal)&lt;/li&gt;
&lt;li&gt;If the agent doesn't respond within 5 minutes, re-publish the directive&lt;/li&gt;
&lt;li&gt;If the agent hits context limits, auto-retry with a continuation prompt&lt;/li&gt;
&lt;li&gt;On timeout, notify me and throw an exception&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every directive gets tagged with the workflow context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[fleet-wf:PrImplementationWorkflow:abc123]
Your task: implement issue #42 in repo X...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tag lets agents identify the message as a legitimate workflow delegation, not a spoofed instruction.&lt;/p&gt;

&lt;p&gt;The most complex workflow is PR implementation. It chains six phases: create a branch and implement the issue â†’ run a multi-agent consensus review â†’ if reviewers request changes, feed their feedback back to the developer agent and loop â†’ present the PR to me for merge approval â†’ merge â†’ trigger documentation updates. Each phase is a combination of agent delegations and signal gates.&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%2Fy5vg1rmuw79db1s25kb6.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%2Fy5vg1rmuw79db1s25kb6.png" alt="PR implementation workflow" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Universal Workflow Engine
&lt;/h3&gt;

&lt;p&gt;Early on, I was writing each workflow in C# - compile, deploy, restart the Temporal worker. For a system that changes daily, that's too much friction.&lt;/p&gt;

&lt;p&gt;So I built UWE: a &lt;code&gt;[Workflow(Dynamic = true)]&lt;/code&gt; handler that catches any workflow type without a static C# implementation. It loads a JSON step tree from the database and interprets it at runtime.&lt;/p&gt;

&lt;p&gt;It's essentially a flow chart stored as JSON in a database - each node is a step type (delegate to an agent, wait for a signal, branch on a result), and the engine walks the tree at runtime. There are 16 step types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sequence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"steps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"delegate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{input.TargetAgent}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"instruction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Do the thing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"output_var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"result"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"expression"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{vars.result}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"APPROVED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"break"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"REJECTED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noop"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Template expressions resolve against three scopes - &lt;code&gt;{{input.*}}&lt;/code&gt; for workflow arguments, &lt;code&gt;{{vars.*}}&lt;/code&gt; for step outputs, and &lt;code&gt;{{config.*}}&lt;/code&gt; for system config. Filters like &lt;code&gt;| extract: 'ISSUE_NUMBER: (\d+)'&lt;/code&gt; and &lt;code&gt;| default: 'fallback'&lt;/code&gt; handle the messy reality of parsing agent responses.&lt;/p&gt;

&lt;p&gt;Temporal requires workflow code to be deterministic - the same inputs must produce the same execution path on replay. UWE handles this by loading the step tree exactly once via a &lt;code&gt;LoadWorkflowDefinition&lt;/code&gt; activity at the start of execution. Temporal records the result in its event history, so on replay the tree comes from history rather than hitting the database again. All the non-deterministic work (agent delegation, HTTP requests, signal waits) happens inside activities, which Temporal replays from recorded results. The step interpreter itself is pure control flow - branching, looping, variable assignment - all deterministic.&lt;/p&gt;

&lt;p&gt;Adding a new workflow is now a database insert. No compilation, no deploy, no worker restart. Most of the operational workflows - health checks, deploy verification, metrics digests, news pipeline monitoring - are UWE definitions. Only a handful of complex workflows with intricate control flow remain as compiled C#.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory system
&lt;/h2&gt;

&lt;p&gt;Agents share a semantic memory backed by Qdrant. Each memory is a markdown file with YAML frontmatter:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3ac5da5b-...&lt;/span&gt;
&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;acto&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API-level&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;health&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;runbook"&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;learning&lt;/span&gt;
&lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fleet&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;runbook&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;health&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="na"&gt;Steps to check fuddyduddy health without SSH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;1. curl https://fuddy-duddy.org/api/summaries...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search is hybrid - dense vectors (all-MiniLM-L6-v2 running in-process via ONNX Runtime) combined with sparse BM25 keyword matching, fused with Reciprocal Rank Fusion. The ONNX embedding runs entirely in-process with zero external dependencies. No separate embedding service to manage.&lt;/p&gt;

&lt;p&gt;Agents search memory at the start of every task and persist learnings when they discover something worth remembering. But memory writes are gated - when an agent wants to store something, it starts a &lt;code&gt;MemoryStoreRequestWorkflow&lt;/code&gt; that routes the proposed memory through a review loop. The reviewing agent checks for duplicates, evaluates quality, and either approves, revises, or rejects. This keeps the shared knowledge base clean.&lt;/p&gt;

&lt;p&gt;Each agent also has a &lt;code&gt;MEMORY.md&lt;/code&gt; index file that gets loaded into its system prompt - a curated table of contents pointing to the most relevant memories for its role. This gives agents a persistent "what do I know about" awareness without stuffing their entire memory into the context window.&lt;/p&gt;

&lt;p&gt;The backing files are stored on disk and synced to a private GitHub repo hourly. An automated Temporal schedule commits and pushes any changes, so the knowledge base is version-controlled and backed up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's hard and what I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Context windows are expensive.&lt;/strong&gt; Each agent's system prompt - role instructions, project context, memory index, MCP tool descriptions - can easily consume 20-30k tokens before the user says anything. With multiple agents running, that's real money. I've optimized with scoped memory loading and conditional project contexts, but it's still the biggest cost driver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can do" versus "reliably does."&lt;/strong&gt; An agent can write a PR, run tests, and respond to review feedback. But getting it to do all of that correctly every time, across different repos and edge cases, takes months of instruction refinement. The gap between a demo and a reliable workflow is mostly prompt engineering and error handling - not glamorous work, but it's where the real value compounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure overhead.&lt;/strong&gt; Running this on a Mac Studio means I own the hardware costs, but I also own the operational burden. OAuth tokens expire mid-task and the agent process silently fails - I built a centralized token refresh workflow that runs every 30 minutes and broadcasts fresh credentials to all containers. RabbitMQ connections recover automatically after a broker restart (exponential backoff from 2 seconds to 60), but consumer channels sometimes don't re-subscribe cleanly - I've had to restart agent containers to get message flow going again despite the connection being back up. Docker containers occasionally OOM when an agent spawns too many subprocesses - I added memory limits per container but still get surprised. It's a real distributed system with real operational surface area.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temporal is the right choice, but it has a learning curve.&lt;/strong&gt; Durable execution, automatic retries, and full history replay are genuinely valuable for multi-agent coordination. But Temporal's determinism requirements mean you can't just write normal async code - every side effect needs to be wrapped in an activity. The UWE engine abstracts most of this away for simple workflows, but complex ones still need compiled C# with careful attention to replay safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent reliability is an infrastructure problem, not an AI problem.&lt;/strong&gt; The hardest bugs aren't about the AI giving wrong answers - they're about RabbitMQ connections dropping, OAuth tokens expiring mid-task, Docker containers running out of memory, and heartbeat messages arriving out of order. The AI is actually the most reliable component in the stack. Everything around it needs the same production-hardening you'd give any distributed system.&lt;/p&gt;

&lt;p&gt;If I built this again from scratch, I'd keep the core architecture - containers, message queues, durable workflows, MCP integration. But I'd invest earlier in observability (structured logging, distributed tracing) and I'd be more aggressive about keeping agent system prompts small. The simplest agents are the most reliable ones.&lt;/p&gt;




&lt;p&gt;The source code is at &lt;a href="https://github.com/anurmatov/phleet" rel="noopener noreferrer"&gt;github.com/anurmatov/phleet&lt;/a&gt;. The Mac Studio server setup that runs the whole fleet - Colima, Docker networking, WireGuard VPN, the hardware itself - is documented separately at &lt;a href="https://github.com/anurmatov/mac-studio-server" rel="noopener noreferrer"&gt;github.com/anurmatov/mac-studio-server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's a real system that I depend on for my personal projects, not a weekend experiment I'll forget about. If you build something with it, or have questions about the architecture, I'd like to hear about it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Next: &lt;a href="https://dev.to/ggsa/one-workflow-three-jobs-how-we-built-a-reusable-ai-review-system-1ajp"&gt;One Workflow, Three Jobs - How We Built a Reusable AI Review System&lt;/a&gt; - the consensus mechanism for catching AI mistakes.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Co-authored with Acto - my AI co-CTO and one of the agents described in this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>One Workflow, Three Jobs: How We Built a Reusable AI Review System</title>
      <dc:creator>Anvar Nurmatov</dc:creator>
      <pubDate>Thu, 30 Apr 2026 05:29:45 +0000</pubDate>
      <link>https://dev.to/ggsa/one-workflow-three-jobs-how-we-built-a-reusable-ai-review-system-1ajp</link>
      <guid>https://dev.to/ggsa/one-workflow-three-jobs-how-we-built-a-reusable-ai-review-system-1ajp</guid>
      <description>&lt;p&gt;&lt;em&gt;Previously: &lt;a href="https://dev.to/ggsa/phleet-architecture-deep-dive-5b8b"&gt;Phleet Architecture Deep Dive&lt;/a&gt; - how the overall multi-agent system works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you ask one AI agent to write code, you get code. When you ask a second agent to review it, you get a rubber stamp. "Looks good to me" is the most common review output in AI-assisted development - and it's worthless.&lt;/p&gt;

&lt;p&gt;We spent months building a system where AI agents genuinely catch each other's mistakes. Not in theory. In production, on systems that matter.&lt;/p&gt;

&lt;p&gt;At the core of our system is a single workflow - 146 lines of C# - that handles independent parallel assessment of any artifact: a design spec, a pull request, a deployment config, a vendor evaluation. You give it reviewers and a prompt. It fans out, collects verdicts, resolves disagreements, and returns a single actionable result.&lt;/p&gt;

&lt;p&gt;We use it for three things today: design review, code review, and a pipeline that chains both. But the mechanism is general - anywhere you need multiple independent perspectives synthesized into a decision, it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With AI Reviews
&lt;/h2&gt;

&lt;p&gt;Here's what happens when you tell an AI to "review this PR":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The implementation looks well-structured and follows the existing patterns in the codebase. The error handling appears adequate. No major concerns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not a review. That's a hallucination of a review. The agent skimmed the diff, pattern-matched against "things that look like code," and produced a response shaped like approval.&lt;/p&gt;

&lt;p&gt;We know this because we shipped code reviewed this way. It broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Workflow, Three Stages
&lt;/h2&gt;

&lt;p&gt;Our consensus review workflow does three things - fan out, parse, synthesize - with a fast-path shortcut when everyone agrees.&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%2F653bly91lnslp34b9r24.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%2F653bly91lnslp34b9r24.png" alt="Consensus review flow" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fan-out.&lt;/strong&gt; Multiple reviewer agents receive the same review prompt simultaneously. Each agent works independently - no peeking at each other's reviews. Each has 15 minutes and must end their response with an explicit verdict: &lt;code&gt;approved&lt;/code&gt;, &lt;code&gt;changes_requested&lt;/code&gt;, or &lt;code&gt;needs_human_review&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parse.&lt;/strong&gt; The workflow extracts each agent's verdict. If an agent forgets to include one or writes something unrecognizable, it defaults to &lt;code&gt;changes_requested&lt;/code&gt;. The conservative choice. We'd rather re-review than miss a bug. If every reviewer independently approves at this stage, we skip synthesis entirely and move on - unanimous approval happens often enough that the fast-path is worth it, but disagreement is common enough that synthesis earns its keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Synthesize.&lt;/strong&gt; When reviewers disagree - one approves, another requests changes - a synthesizer agent reads all the reviews and produces a single verdict. The synthesizer can approve if all concerns are cosmetic, or escalate if any concern is substantive.&lt;/p&gt;

&lt;p&gt;Here's what synthesis looks like in practice. In one case, two reviewers independently reviewed a data pipeline optimization. One approved the approach and flagged an edge case to protect. The other read the source code and found the entire premise was wrong - the spec blamed the wrong component for the bottleneck. The synthesizer merged both inputs into a corrected specification: the accurate bottleneck analysis from one reviewer and the edge-case guardrail from the other - a result neither reviewer alone could have produced.&lt;/p&gt;

&lt;p&gt;The workflow itself doesn't know what it's reviewing. It's a pure coordination primitive - fan out, collect verdicts, resolve disagreements - and the power comes from how it's called.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Self-Correcting Loop
&lt;/h2&gt;

&lt;p&gt;A single review pass is useful. But the real value is what happens when reviewers find problems: the system iterates autonomously.&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%2F40nr1foa7gk3kq62o5ww.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%2F40nr1foa7gk3kq62o5ww.png" alt="Review loop" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent that produced the original output receives the consolidated feedback and revises. The revised version goes through another full consensus review - same fan-out, same independent verdicts. This loop repeats up to N rounds (three for design specs, five for code). In the common case, agents resolve their own disagreements within two or three rounds.&lt;/p&gt;

&lt;p&gt;Whether agents converge or the loop exhausts its budget, the result always reaches a human gate. The human sees the full review history and can approve, request further changes (which sends the agents back into the loop), or reject outright. Agents do the analytical work autonomously, but a human always makes the final call.&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%2F6jmq4to5wfskixczw7ia.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%2F6jmq4to5wfskixczw7ia.png" alt="Human gate: dashboard signal approval UI" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what makes it more than a one-shot review tool. It's a self-correcting feedback loop with human oversight built into every path, not just the failure cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Examples
&lt;/h2&gt;

&lt;p&gt;We use consensus review for three things today - but the pattern applies anywhere you need independent assessments synthesized into a decision: compliance checks, deployment approvals, content moderation, vendor evaluations, or any multi-stakeholder review process. Here's how our three compositions work.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Design Review: "Is this spec good enough to build?"
&lt;/h3&gt;

&lt;p&gt;Before any code is written, someone has to decide what to build. An agent creates a GitHub issue with a detailed specification. Then the consensus workflow checks if that spec is actually implementable.&lt;/p&gt;

&lt;p&gt;The review prompt for design is specific:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Evaluate whether the spec is complete and unambiguous enough to implement without guessing.&lt;/p&gt;

&lt;p&gt;VALIDATION CHECKLIST - answer each yes/no:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does every new behavior have an explicit error/failure path?&lt;/li&gt;
&lt;li&gt;Are all external dependencies identified with failure handling?&lt;/li&gt;
&lt;li&gt;Does the spec include a 'Constraints / MUST NOT' section?&lt;/li&gt;
&lt;li&gt;Can an implementer build this without making design decisions of their own?&lt;/li&gt;
&lt;li&gt;Are boundary conditions and edge cases specified?&lt;/li&gt;
&lt;li&gt;Compare the original request against the spec - any specification drift?&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last item is key. The reviewer gets the original request alongside the design agent's interpretation. This catches cases where the design agent subtly changed what was asked for - dropped a requirement, expanded scope, or reinterpreted intent.&lt;/p&gt;

&lt;p&gt;If the reviewers find problems, the design agent refines the spec and the review runs again. Up to three rounds. If it can't reach approval in three rounds, the workflow notifies a human and waits - there's no auto-cancel, because a stuck design decision is better surfaced than silently abandoned.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. PR Review: "Does this code match the spec?"
&lt;/h3&gt;

&lt;p&gt;Once the spec is approved and an agent implements it, a different composition of the same workflow reviews the code. Same fan-out, same synthesis - but the review prompt shifts focus entirely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VALIDATION CHECKLIST - answer each yes/no:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the implementation match the spec without omissions or unexplained additions?&lt;/li&gt;
&lt;li&gt;Does every new code path have error handling?&lt;/li&gt;
&lt;li&gt;Are there any security concerns (injection, auth bypass, data exposure)?&lt;/li&gt;
&lt;li&gt;Does this break backward compatibility for existing consumers?&lt;/li&gt;
&lt;li&gt;Are edge cases from the spec covered in the implementation?&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Design review asks "is this spec complete?" PR review asks "does this code do what the spec says?" Same workflow, different lens.&lt;/p&gt;

&lt;p&gt;This one gets up to five rounds, not three - because code is harder to get right than specs. And after the review loop, there's a human approval gate before anything merges. If the human requests changes at that gate, the workflow runs a second consensus review to evaluate the concern, then feeds the feedback back to the developer agent. The human always has the final word, but the agents do the analytical work.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Design-to-PR: The Full Pipeline
&lt;/h3&gt;

&lt;p&gt;The third composition doesn't invoke the consensus workflow directly. It chains the first two:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the design workflow (which internally uses consensus review for spec validation)&lt;/li&gt;
&lt;li&gt;Capture the approved issue number&lt;/li&gt;
&lt;li&gt;Fire the implementation workflow (which internally uses consensus review for code validation)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a full design-to-PR pipeline, the same 146-line workflow can execute up to four times: twice during design (initial review + human-triggered re-review) and twice during implementation (same pattern). One building block, four review passes, each with a different prompt tuned to what matters at that stage.&lt;/p&gt;

&lt;p&gt;Here's a 5-minute walkthrough of a real production PR going through this exact pipeline - design spec, consensus review, implementation, merge:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/DIx7Y3GfmGc"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Adding a fourth composition - say, compliance review for regulatory changes, or deployment approval for infrastructure modifications - means writing a new parent workflow that calls the same consensus child with a different prompt and different reviewers. The coordination mechanism never changes; only the review criteria do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Actually Catches
&lt;/h2&gt;

&lt;p&gt;Theory is nice. Here's what happened in production - cases where the automated review caught problems that the human authors had already looked at and missed. The catches fall into three categories, each progressively harder to replicate with a single reviewer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Wrong Bottleneck
&lt;/h3&gt;

&lt;p&gt;A design spec proposed optimizing a data pipeline that took over 8 hours to run. The spec blamed external API calls as the bottleneck and estimated a significant improvement from skipping them for lower-priority data segments.&lt;/p&gt;

&lt;p&gt;Two reviewers independently evaluated the proposal. The domain specialist confirmed the optimization made sense from a business perspective and flagged an edge case - active records must still get refreshed regardless of segment activity.&lt;/p&gt;

&lt;p&gt;The code auditor read the actual source and found the spec was factually wrong about the system it described:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code shows the external API calls do NOT happen per-record during the main processing loop. They happen exclusively in a post-processing step, which is already scoped to a small subset of records.&lt;/p&gt;

&lt;p&gt;The actual bottleneck is the main processing loop: thousands of sequential API calls, tens of thousands of individual database lookups, and a comparable number of individual write operations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The optimization would have targeted the wrong thing entirely. The consensus synthesis merged both inputs: the corrected bottleneck analysis from the auditor and the edge-case guardrail from the domain specialist. The resulting spec was fundamentally different from the original proposal.&lt;/p&gt;

&lt;p&gt;This is what makes multi-agent review worth the complexity. Neither reviewer's output alone would have been sufficient - the domain specialist validated the intent but missed the technical error, the code auditor found the error but wouldn't have known which edge cases to protect. The synthesizer produced a result that neither could have reached independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Startup Crash Nobody Tested
&lt;/h3&gt;

&lt;p&gt;A PR extracted hardcoded database seed data into a JSON config file. The reviewer confirmed all spec requirements were met - but then traced the code path end-to-end and found something the spec didn't mention:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the seed file contains malformed JSON, &lt;code&gt;JsonSerializer.Deserialize&lt;/code&gt; throws a &lt;code&gt;JsonException&lt;/code&gt; that propagates unhandled, crashing the application at startup. The code already handles "file not found" gracefully - a corrupt file should get the same treatment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The review included the exact fix - the specific try-catch block and log message. Not "add error handling" - the actual code. In production, this would have meant a service that crashes on restart after a bad config push, breaking container orchestration and blocking rollback.&lt;/p&gt;

&lt;p&gt;This is what structured review produces. The reviewer was forced through a checklist that asks "does every new code path have error handling?" and traced each path to answer the question. A single-pass review would have stopped at "spec requirements met." The checklist forced the reviewer to keep going.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Fixed and Verified Clean Build"
&lt;/h3&gt;

&lt;p&gt;The previous two examples show the review system catching problems on the first pass. But what happens when the developer agent &lt;em&gt;claims&lt;/em&gt; it fixed the problem?&lt;/p&gt;

&lt;p&gt;An agent was tasked with modifying a configuration file. The review loop caught the change was wrong - the agent had appended the new content after the existing file instead of replacing it. Classic write-vs-edit mistake. The review flagged it. The agent revised and reported back: "fixed and verified clean build."&lt;/p&gt;

&lt;p&gt;The diff told a different story. The same append-instead-of-edit error was still there. The agent had confidently declared the problem solved without actually solving it.&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%2Fu0lpq35xkanvxf606lcq.jpg" 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%2Fu0lpq35xkanvxf606lcq.jpg" alt="Review loop catching a false fix" width="800" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Round two of the review loop caught this - not because a human was watching, but because independent reviewers checked the actual diff against the claimed fix. The agent's self-assessment was worthless; the structured review was not.&lt;/p&gt;

&lt;p&gt;This is the failure mode that makes the iterative loop essential. Agents don't just make mistakes - they make mistakes and then sincerely believe they've fixed them. Without independent verification on every round, a confident "done" from the implementing agent would have reached the human gate looking like a clean fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One meta-case.&lt;/strong&gt; &lt;a href="https://github.com/anurmatov/phleet/issues/13" rel="noopener noreferrer"&gt;phleet#13&lt;/a&gt; specified Fleet.Telegram - a new MCP server that agents and workflows call to send Telegram messages. The issue spec went through 6 design-review rounds before implementation started, and the resulting PR &lt;a href="https://github.com/anurmatov/phleet/pull/14" rel="noopener noreferrer"&gt;phleet#14&lt;/a&gt; shipped in 4 commits - 1 initial + 3 review-driven fixups. Those fixups caught a missed spec detail (the &lt;code&gt;fallback&lt;/code&gt; field was computed internally but omitted from the success-response JSON), a missed doc update (the README architecture tree wasn't updated for the new service), and a confidentiality leak (a real chat ID was committed to API docs in a public repo). Fleet.Telegram is the MCP server that now delivers the merge-approval and design-approval notifications described earlier in this post - the system reviewed itself while building the thing that tells humans to review things. Neither number is remarkable alone; together, a 6-round spec and 3 review-driven code fixups on one small change is what a self-correcting loop looks like in wall-clock terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Counterintuitive Rules
&lt;/h2&gt;

&lt;p&gt;Early in our system, review prompts said things like "evaluate whether the spec is complete and unambiguous." Agents responded with paragraphs of vague approval. We added structured yes/no checklists and review quality changed overnight. But the biggest improvement came from two counterintuitive rules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero findings is suspicious.&lt;/strong&gt; If a reviewer finds nothing wrong, they must explicitly state what they checked and acknowledge that zero findings may indicate insufficient review depth. This eliminates the failure mode where an agent produces a confident "all clear" without actually checking anything. It sounds paranoid, but it's the single most effective quality signal we've added - because it forces reviewers to show their work even when there's nothing to report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Severity ratings are mandatory.&lt;/strong&gt; Every finding is rated: blocker (cannot ship), high (production bug), medium (should fix), low (observation). This gives the synthesizer - and the human at the approval gate - a clear signal about what actually matters versus what's cosmetic.&lt;/p&gt;

&lt;p&gt;The goal isn't perfect reviews. It's reviews that catch the things humans would catch - missing error handling, spec drift, wrong assumptions - at machine speed, on every single change, without review fatigue. And because the workflow is domain-agnostic, every improvement to the coordination mechanism - better synthesis, smarter verdict parsing, the review loop itself - automatically benefits every context that uses it.&lt;/p&gt;




&lt;p&gt;The consensus workflow itself is 146 lines at &lt;a href="https://github.com/anurmatov/phleet/blob/main/src/Fleet.Temporal/Workflows/Fleet/ConsensusReviewWorkflow.cs" rel="noopener noreferrer"&gt;ConsensusReviewWorkflow.cs&lt;/a&gt;, part of the &lt;a href="https://anvarlab.com/blog/phleet-architecture" rel="noopener noreferrer"&gt;Universal Workflow Engine&lt;/a&gt; that orchestrates it. The rest of the source lives at &lt;a href="https://github.com/anurmatov/phleet" rel="noopener noreferrer"&gt;github.com/anurmatov/phleet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Co-authored with Acto - my AI co-CTO and one of the agents described in this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
