<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Within Constraints]]></title><description><![CDATA[Within Constraints]]></description><link>https://kaliupin.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/69c2189a30a9b81e3aedf814/67ae77d0-cc69-452c-bc3e-5a5c475bf675.png</url><title>Within Constraints</title><link>https://kaliupin.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 21 Oct 2026 20:31:22 GMT</lastBuildDate><atom:link href="https://kaliupin.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Train the Agent, Not the Model]]></title><description><![CDATA[Make one coding agent reliable before you chain it. A concept for training prompts skills and tools from real developer sessions with replayable tests]]></description><link>https://kaliupin.hashnode.dev/train-the-agent-not-the-model</link><guid isPermaLink="true">https://kaliupin.hashnode.dev/train-the-agent-not-the-model</guid><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Prompt Engineering]]></category><category><![CDATA[ecommerce]]></category><dc:creator><![CDATA[Roman Kaliupin]]></dc:creator><pubDate>Sat, 17 Oct 2026 18:07:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c2189a30a9b81e3aedf814/8cc77f43-5be6-4321-af2f-66f6e7a31217.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>The short version</strong></h2>
<p>Stop building agent swarms on top of agents that still need a human. Make one agent reliable first. You can do that by training it, not the model, but the agent itself: its prompts, skills and tools. And the training data already exists. It's the sessions your developers have with coding agents every day.</p>
<p>This is a concept, not a finished result. I'm sharing it to start a discussion and hear how others are approaching the same problem.</p>
<h2><strong>Why now, and how I got here</strong></h2>
<p>Core models are now smart enough to generalize. They don't need pages of instructions anymore. So a small amount of training on the right parts of the setup should get you 80% of the result for 20% of the effort.</p>
<p>I learned this by hand. On an e-commerce migration project I set up a lean agent for my team. I split the migration into its most common tasks. Then I ran the agent, watched where it went wrong, tweaked the prompts and skills, and ran it again. By the end we had templated prompts for everyday tasks, and the agent needed far less human help.</p>
<p>It worked. But I was the training loop. This post is about automating it.</p>
<h2><strong>Reliable nodes first</strong></h2>
<p>Automation is built from tested, reliable blocks. Agents should be no different. An agent in your chain should already work without a human before it joins the system.</p>
<p>The industry does the opposite. We wire agents to Jira, browsers, MCP servers and each other while each one still needs a developer steering it. If every step is 95% reliable, ten in a row land at about 60%. More plumbing won't fix that. Better nodes will.</p>
<p>And nodes will get smaller. I expect a Shopify developer agent, a Salesforce Commerce Cloud agent, each narrow, each with its own prompts, skills and tools. Narrow, repeatable work is exactly what you can train for. E-commerce is a great place to start, but any team on a long-running project fits.</p>
<h2><strong>The training loop</strong></h2>
<p>If you've trained a neural network, you already know this shape. Each step maps to a familiar idea.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c2189a30a9b81e3aedf814/7c894c6f-387b-4fe2-af5f-ff2d23b42a4d.png" alt="Every closed ticket makes the agent a little better" style="display:block;margin:0 auto" />

<p>training loop for one agent · 6 steps, closes on itself</p>
<p>The loop never stops. Developers use the better agent, and their new sessions become the next batch.</p>
<h2><strong>What gets trained</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/69c2189a30a9b81e3aedf814/59ed5aed-d8da-44c4-89e2-eb362a19833b.png" alt="Every part of the agent becomes trainable clusters" style="display:block;margin:0 auto" />

<p>four parameter groups, each split into clusters</p>
<p>Agents are non-deterministic. Parametrization is how you make them predictable. <strong>Each part of the agent is split into pieces, and every piece is its own parameter. The whole part is a parameter too.</strong> So training can rewrite one rule inside a skill, drop the whole skill, or rewrite the system prompt from scratch.</p>
<p>Start from a structure that already works, with a few clusters per part. The cluster count can be learned too, but gently. Tests will tell.</p>
<p>Three things fall out of this. Skills that never help get pruned, judged by impact, not usage. The developer's raw prompt can be reshaped into a clearer task before the agent starts. And you learn what a good agent looks like: how many skills and tools it needs and how its system prompt is shaped. Those principles carry over to other agents, even ones you never train.</p>
<h2><strong>Where the data comes from</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/69c2189a30a9b81e3aedf814/a0f76d49-49e9-4ab6-99e8-eb8afc386595.png" alt="Once closed ticket becomes a replayable test case" style="display:block;margin:0 auto" />

<p>Every developer session is a training example: ticket in, PR out. The corrections are the gold. They're free, expert labels showing exactly where the agent went wrong. The data is noisy, but a PR a developer was willing to open is a fair sign that human and agent agreed on a solution. Each correction is also a cut point, so one ticket gives several examples.</p>
<p>Replay turns those sessions into a benchmark. Snapshot the repo, replay the original prompt, then score the run two ways:</p>
<ul>
<li><p><strong>Smoke tests</strong> written from the final PR, for example in Playwright. Keep only tests that fail on the old code and pass on the new one. Test behavior, not implementation.</p>
</li>
<li><p><strong>A rubric from the corrections</strong>, like "reuse the existing cart service", scored by an LLM judge.</p>
</li>
</ul>
<h2><strong>Where this could break</strong></h2>
<ul>
<li><p><strong>Overfitting and noisy replays.</strong> A project may have only a hundred tickets. A generalization filter at the end of each step rewrites accepted edits into general rules and adds variation, like data augmentation.</p>
</li>
<li><p><strong>Clusters that split meaning.</strong> Starting from a proven structure with few clusters keeps this risk small.</p>
</li>
<li><p><strong>The optimizer.</strong> An LLM critique isn't a real gradient. That's fine for now. The core idea is the training setup, and the optimizer can later be swapped for something more deterministic, even a model trained for this job.</p>
</li>
</ul>
<h2><strong>What exists, and what's new</strong></h2>
<p>The optimizer side is already proven, which is good news.</p>
<table>
<thead>
<tr>
<th></th>
<th><a href="https://arxiv.org/abs/2605.23904">SkillOpt</a> (Microsoft)</th>
<th><a href="https://www.amazon.science/publications/trace-trajectory-attribution-for-automated-context-engineering">TRACE</a> (Amazon)</th>
<th>This proposal</th>
</tr>
</thead>
<tbody><tr>
<td>What changes</td>
<td>One skill document</td>
<td>The context source that failed</td>
<td>Input prompt, system prompt, skills, tools</td>
</tr>
<tr>
<td>Training data</td>
<td>Benchmark tasks</td>
<td>Historical sessions</td>
<td>Real developer sessions, ticket to PR</td>
</tr>
<tr>
<td>How success is judged</td>
<td>Benchmark score</td>
<td>Simulated faults</td>
<td>Replay with smoke tests and a corrections rubric</td>
</tr>
<tr>
<td>Scope</td>
<td>One skill</td>
<td>One agent</td>
<td>Every node in a pipeline, always training</td>
</tr>
</tbody></table>
<p>The new part is everything around the optimizer: real work as data, corrections as labels, replay as the benchmark.</p>
<h2><strong>Open questions</strong></h2>
<p>The ones I find most interesting:</p>
<ul>
<li><p>How cheap can replay get?</p>
</li>
<li><p>How do you separate a real mistake from one developer's taste?</p>
</li>
<li><p>In a chain of agents, which node caused the failure?</p>
</li>
</ul>
<p>I build with coding agents for e-commerce clients every day. At a Meta event this summer, talks with engineers there confirmed this is still an open problem. I'd love to hear from anyone exploring this space, whether you've tried something similar or see a hole in the idea.</p>
<h2><strong>Sources</strong></h2>
<ul>
<li><p><a href="https://arxiv.org/abs/2605.23904">SkillOpt: Executive Strategy for Self-Evolving Agent Skills</a>, Microsoft, May 2026</p>
</li>
<li><p><a href="https://www.amazon.science/publications/trace-trajectory-attribution-for-automated-context-engineering">TRACE: TRajectory Attribution for Automated Context Engineering</a>, Amazon, August 2026</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Microsoft Just Shipped Conductor. I Built a Side Project With the Same Idea. Here's the Gap.]]></title><description><![CDATA[I built a deterministic agent pipeline two months before Microsoft shipped Conductor. Here's the gap—and where this is heading for ecommerce.]]></description><link>https://kaliupin.hashnode.dev/dagent-and-microsoft-conductor</link><guid isPermaLink="true">https://kaliupin.hashnode.dev/dagent-and-microsoft-conductor</guid><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Microsoft]]></category><category><![CDATA[ecommerce]]></category><dc:creator><![CDATA[Roman Kaliupin]]></dc:creator><pubDate>Wed, 20 May 2026 00:49:22 GMT</pubDate><content:encoded><![CDATA[<p><em>Third in a series. Previously:</em> <a href="https://dagent.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing"><em>the vision post</em></a> <em>and</em> <a href="https://dagent.hashnode.dev/my-ai-pipeline-burned-50-minutes-on-correct-code-the-infrastructure-just-didn-t-exist-yet"><em>the infrastructure post</em></a><em>, both from March 2026.</em></p>
<hr />
<h2>The parallel</h2>
<p>In March I published two posts about DAGent — a deterministic agentic pipeline that routes specialized AI agents through a fixed topology, produces tested pull requests from a spec, and spends zero tokens on orchestration decisions. On May 14, <a href="https://www.linkedin.com/in/jasonrobert/">Jason Robert</a> — Principal Software Engineer in Microsoft's Azure Core CTO office — shipped <a href="https://opensource.microsoft.com/blog/2026/05/14/conductor-deterministic-orchestration-for-multi-agent-ai-workflows/">Conductor: Deterministic orchestration for multi-agent AI workflows</a>, Jinja2-template routing, isolated agent sessions with explicit context flow.</p>
<p>He shipped a framework. I'd built a learning prototype that validated the idea inside one ecom project and one sample Azure app. Those aren't the same achievements, and I'm not going to pretend they are. But the underlying read on the category was the same — a Principal Engineer at Microsoft and an ecommerce solution architect on nights-and-weekends landed on the same architectural intuition from completely different vantage points. That's a category signal, not a coincidence.</p>
<hr />
<h2>What was right</h2>
<p>The core insight behind DAGent was simple: most useful multi-agent workflows have known structure. A code-review pipeline is always plan → implement → test → review. A feature delivery pipeline is always spec → dev → unit-test → e2e → PR. You know the topology before anything runs.</p>
<p>If the topology is known, the orchestrator shouldn't be an LLM making routing decisions at runtime. It should be a state machine. Routing consumes zero tokens. You get deterministic execution, reproducible failures, and cost you can predict before you press enter.</p>
<p>DAGent was never a shipped product. It was tightly coupled to one ecommerce project and one sample Azure app — never extracted into a standalone tool anyone else could install. What I was building was a prototype to test whether the deterministic-DAG idea actually worked in practice — and it did.</p>
<p>Concretely, the demo branch (<a href="https://github.com/RomanKaliupinMelonusa/DAGent-t/tree/demo/single-file-pipeline">demo/single-file-pipeline</a>) — the stripped-down version I eventually published — encoded this as a 6-node linear pipeline with index-jump failure routing and a try/catch/finally finalizer:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c2189a30a9b81e3aedf814/9b3ec34c-7647-4b4d-9228-c1d68e8d07d5.png" alt="" style="display:block;margin:0 auto" />

<p>Each node has its own tool allowlist (RBAC), write-path restrictions, timeout budget, and retry policy. The e2e-debug agent could patch both application code and test code; the e2e-author couldn't read the application source at all. Bounded agents with explicit capabilities. No shared conversation state bleeding between nodes.</p>
<p>This was the right category to build in. Conductor's announcement post opens with the same pain: "We kept building multi-agent workflows—code review pipelines, design document generation, research assistants—and writing the same glue code every time." Same problem. Same answer.</p>
<hr />
<h2>What broke</h2>
<p>I scaled before I stabilized. That's the short version.</p>
<p>The long version: in late April I introduced <a href="https://temporal.io">Temporal</a> for orchestration. The <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t/tree/project/temporal-migration">project/temporal-migration</a> branch carried a Temporal worker, a DAG kernel, an <a href="https://github.com/microsoft/apm">APM compiler</a> (Microsoft solution), telemetry, a triage LLM, lifecycle hooks, an archive subsystem, and an admin CLI. Roughly 20× the surface area of the demo pipeline. I wanted a production-grade distributed system because the architecture docs in my head said "production-grade distributed system."</p>
<p>The problem is I never shipped a clean, stable core first. There was no finalized plug-and-play interface for external users. The Temporal integration introduced real bugs — workflow replay compatibility, signal handling edge cases, state serialization issues — and I was debugging infrastructure instead of demonstrating the thesis.</p>
<p>I know the theory here. I've given feedback on roadmaps and scope creep in delivery planning for years. Ship the smallest thing that proves the idea, get feedback, then add infrastructure. I broke every one of those rules on my own project.</p>
<p>Eventually I pulled back. The <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t/tree/demo/single-file-pipeline">demo/single-file-pipeline</a> branch stripped everything to ~1,200 lines of TypeScript: six nodes, JSON state on disk, index-jump failure routing, and a finalizer that always runs. No Temporal, no DAG kernel, no distributed system. It worked. It demonstrated the thesis. It should have been the first thing I built.</p>
<p>The specific product-mindset failures:</p>
<ul>
<li><p><strong>Built for scale before proving value.</strong> Temporal is the right tool for a production multi-tenant pipeline. It's the wrong tool for a weekend project that needs to convince people the category exists.</p>
</li>
<li><p><strong>Confused architecture ambition with shipping.</strong> A well-scoped architecture doc is not progress.</p>
</li>
<li><p><strong>Let complexity debt accumulate on nights-and-weekends time.</strong> When you have 8 hours a week, every line of infrastructure you add is a line you'll debug later at 11pm. I added thousands.</p>
</li>
<li><p><strong>Built an LLM-powered triage router I didn't need.</strong> One node used RAG search plus an LLM call to classify failure types and dynamically reroute the pipeline — a semi-deterministic classifier that decided whether a test failure was a code bug, a test bug, or a platform flake, then jumped to the appropriate recovery node. It was clever. It was also spending tokens on orchestration decisions, which was the exact problem deterministic routing was supposed to eliminate. The demo branch replaced it with a static onFailureRoutes map keyed by fault domain strings that the failing agent self-reports. Zero tokens. Same outcomes.</p>
</li>
</ul>
<p>This was a side project built on nights and weekends, which makes the product-discipline failure worse, not better. Limited time demands ruthless scope control. I did the opposite.</p>
<hr />
<h2>What Conductor showed me about the framework layer</h2>
<p>I mapped <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t/blob/demo/single-file-pipeline/conductor-workflow.yaml">DAGent's pipeline to Conductor's YAML format</a> the week it launched. The exercise made visible what a real framework looks like when someone with the right specialty actually builds one.</p>
<h3>1. The CLI as distribution surface</h3>
<p>DAGent's Temporal branch already had YAML-defined workflows and a CLI — you ran <code>dagent run workflows.yml</code> and it executed the pipeline. An 11-node DAG in <code>workflows.yml</code>, a node pool in <code>apm.yml</code>, agents in separate folders. The topology was declarative and the invocation was terminal-native. What I never solved was extraction: the CLI, the runtime, and the workflow definitions all lived inside the project repo. You couldn't install DAGent separately and point it at a different codebase. Conductor ships as <code>curl | sh</code>, runs against any workflow file in any directory, and requires zero knowledge of the underlying project. That's the difference between having a CLI and having a distributable product.</p>
<h3>2. Context modes as first-class primitives</h3>
<p>DAGent always accumulated — every agent saw all prior outputs. Conductor ships three modes: <code>accumulate</code>, <code>last_only</code>, and <code>explicit</code> (only named dependencies). Their blog post notes: "Being deliberate about what each agent sees turned out to matter more than we expected."</p>
<p>I hit this problem in practice. The e2e-debug agent received all prior node outputs including the full dev context, which inflated token consumption and occasionally confused the model. I worked around it with prompt engineering. Conductor solved it at the framework level.</p>
<h3>3. Pub/sub event system</h3>
<p>DAGent bolted on a <code>live-logger.ts</code> that printed to stdout. Conductor built a pub/sub event bus from day one — the terminal renderer, web dashboard, and any future consumer subscribed independently. Adding their web dashboard required zero changes to the workflow engine.</p>
<p>This is the kind of upfront investment I skipped because "I'll add observability later." Later never comes on a side project.</p>
<h3>4. Parallel execution</h3>
<p>DAGent was strictly linear. Conductor supports static parallel groups with configurable failure modes (<code>fail_fast</code>, <code>continue_on_error</code>, <code>all_or_nothing</code>) and dynamic <code>for_each</code> groups that process arrays in parallel with batched concurrency.</p>
<p>For my 6-node pipeline, linear was fine. For production workflows with independent research or validation steps, parallel execution is table stakes.</p>
<h3>5. Human gates and plugin registries</h3>
<p>DAGent's admin interface was — quoting my own README — "Ctrl+C." That's not entirely fair to past-me though. On the main branch, when the pipeline produced infrastructure-as-code changes, I needed a human approval step before deploying to a live environment. My solution: the pipeline posted a GitHub comment with a deployment command, a human reviewed the IaC diff and triggered a GitHub Actions workflow via comment, then you ran a CLI command in your terminal to restore the agent session — it would check whether the deployment succeeded, pull back the artifact status, and resume the pipeline from where it left off.</p>
<p>It worked. It was also a duct-tape integration across three surfaces (GitHub comments, Actions workflows, a CLI restore command) that I knew at the time was a temporary fix. The "long-aimed decision" was always to make approval gates a first-class node type in the graph itself. I just never got there.</p>
<p>Conductor ships exactly that: human gate steps that pause execution, present options with Markdown-rendered prompts, route on the response, and resume — all within the workflow graph. No external surfaces, no session restore commands.</p>
<hr />
<h2>The ecommerce thesis</h2>
<p>Here's the part I think matters most for the people I work with every day.</p>
<p>Ecommerce is <strong>constraint-oriented development</strong>. Every platform — Salesforce Commerce Cloud, Shopify, Adobe Commerce, commercetools, SAP Hybris — ships with a framework, a set of operating rules, and a solution kit that defines what's buildable. You don't start from scratch. You extend within boundaries. The platform's architecture decisions are your architecture decisions whether you agreed to them or not.</p>
<p>I spent years inside this constraint space. Migrating a server-rendered storefront to a PWA architecture inside Salesforce's framework. Writing session-alignment logic across two REST APIs with different auth models. Building extension mechanisms that don't fork the base layer. The work is always: how do you ship features inside someone else's constraints?</p>
<p>Here's the strong opinion: <strong>the next move for these platforms is to ship their own curated pool of deterministic pipelines.</strong> Not "AI features." Not chatbots bolted onto checkout. Deterministic agentic workflows that encode the platform's own conventions, APIs, and architectural rules.</p>
<p>What this looks like in practice:</p>
<ul>
<li><p>A Salesforce pipeline that knows PWA Kit architecture, SCAPI/OCAPI patterns, and the Page Designer content model. You give it a spec; it produces a PR that follows your platform's rules.</p>
</li>
<li><p>A Shopify pipeline that understands theme architecture, Liquid templates, and the Storefront API. It authors extensions within Shopify's boundaries, not around them.</p>
</li>
<li><p>A commercetools pipeline that speaks its API-first model, knows the projection/event pattern, and builds integrations that fit the platform's extension points.</p>
</li>
</ul>
<p>The underlying orchestrator — whether it's Conductor, a DAGent descendant, or something else — is the execution layer. But the value is in the <strong>platform-aware pipeline library</strong>: curated workflows that encode domain constraints so the agents operate within the same rules a human architect would enforce.</p>
<p><strong>So why hasn't this happened yet?</strong> Three reasons.</p>
<p><strong>First</strong>, the mainstream bet is on a different architecture: the silver-bullet agent plus MCP servers. Platforms are shipping MCP servers that bolt platform capabilities onto Copilot, Cursor, and other popular AI tools — one agent, better context, ship it. That's the dominant mental model right now, and it doesn't produce pipeline infrastructure because it doesn't think in pipelines.</p>
<p><strong>Second</strong>, the pipeline idea comes from a different lineage. I arrived at DAGent through two principles that aren't mainstream AI-product thinking: (1) no matter how well you configure a single agent with MCP and prompts, you're still manually testing its output — that's frustrating and doesn't scale, I wanted at least an 80%-working feature before a human touches it; and (2) DevOps. If you've ever worked with a Jenkins setup you know it's a workflow pipeline with nodes and artifacts — one builds the project, another runs unit tests, another analyzes code quality, the next deploys and runs integration tests, and through the pipeline you get notified on failure. All the pieces are on the table. You just need to connect them for AI agents the same way CI/CD connected them for builds.</p>
<p><strong>Third</strong>, until May there was no Enterprise ready orchestrator on the market. Which meant every company that wanted this had to ship both — the orchestrator <em>and</em> the platform-aware pipeline on top of it. That's two hard problems for the price of one, and nobody's going to invest in a pipeline library if they also have to invent the execution engine. But now we have Conductor. It's a genuinely promising product, and it removes the harder half of the problem.</p>
<p>Whoever first ships a platform-aware pipeline library backed by a deterministic orchestrator wins the next chapter of ecommerce extensibility. The platforms that move slowest will watch their SI partners build it anyway — with worse integration points.</p>
<hr />
<h2>What's next</h2>
<p>DAGent is done. I don't see a future for it as a standalone project — and that's fine. It was a great learning exercise. I learned how to scope agent boundaries, how context flow between nodes matters more than individual agent quality, how deterministic routing eliminates an entire class of cost and reliability problems, and how badly I need to apply product discipline to my own side projects. It validated the thesis. That was always the point.</p>
<p>My next goal is applying Conductor to ecommerce. The <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t/blob/demo/single-file-pipeline/conductor-workflow.yaml">conductor-workflow.yaml</a> in this repo is the starting point — a functional mapping of DAGent's pipeline to Conductor's format, targeting a Salesforce PWA Kit storefront. From here I'm building the platform-aware pipeline library I described above: curated workflows that encode ecommerce framework constraints so the agents operate within the same rules a human architect would enforce.</p>
<p>If I hit gaps — missing features, edge cases, things that don't work cleanly in constraint-heavy delivery — I'll contribute upstream. Conductor is early enough that contributions from people running real workloads against it will shape the product.</p>
<p>Jason — thank you for shipping Conductor. Seeing a Principal-level engineer converge on the same architecture from the infrastructure side told me the category is real. Your implementation made the orchestration layer credible to people who would never have looked at my weekend project.</p>
<p>If you're an engineer who's been stitching together agent pipelines with glue code inside platform-constrained delivery: <a href="https://github.com/microsoft/conductor">Conductor</a> is worth your afternoon. And if you're at Salesforce, Shopify, or Adobe thinking about what "AI-native extensibility" actually means — the platform-aware deterministic pipeline pool is the answer nobody's shipping yet.</p>
<p>The pipeline is the product. The orchestrator is infrastructure.</p>
<hr />
<p><a href="https://github.com/RomanKaliupinMelonusa/DAGent-t"><em>DAGent repo</em></a> <em>·</em> <a href="https://github.com/microsoft/conductor"><em>Conductor repo</em></a> <em>·</em> <a href="https://dagent.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing"><em>First post</em></a> <em>·</em> <a href="https://dagent.hashnode.dev/my-ai-pipeline-burned-50-minutes-on-correct-code-the-infrastructure-just-didn-t-exist-yet"><em>Second post</em></a></p>
]]></content:encoded></item><item><title><![CDATA[My AI Pipeline Burned 50 Minutes on Correct Code. The Infrastructure Just Didn't Exist Yet.]]></title><description><![CDATA[AI agents retried correct code for 50 min because infrastructure didn't exist yet. A two-wave DAG with a human approval gate fixed it — 55% faster.]]></description><link>https://kaliupin.hashnode.dev/my-ai-pipeline-burned-50-minutes-on-correct-code-the-infrastructure-just-didn-t-exist-yet</link><guid isPermaLink="true">https://kaliupin.hashnode.dev/my-ai-pipeline-burned-50-minutes-on-correct-code-the-infrastructure-just-didn-t-exist-yet</guid><category><![CDATA[AI]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[devtools]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[architecture]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[software development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Roman Kaliupin]]></dc:creator><pubDate>Mon, 30 Mar 2026 17:10:17 GMT</pubDate><content:encoded><![CDATA[<p><strong>TL;DR:</strong> My deterministic AI pipeline burned 50 minutes retrying correct code because infrastructure hadn't been provisioned yet. I restructured from a 12-item single-wave DAG to an 18-item two-wave architecture with a human-approved infrastructure gate — cutting total runtime by 55% and eliminating an entire class of failure. <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t">The repo is open.</a></p>
<hr />
<p>Three of four failure cycles in my agentic pipeline were the same bug class: the backend agent wrote correct Azure Functions code, CI deployed it, integration tests hit <code>404</code>, the triage engine routed the failure back to <code>backend-dev</code>, and the agent re-read its own code, found nothing wrong, added defensive checks, pushed again. Another <code>404</code>. Same error, different attempt, zero progress.</p>
<p>The circuit breaker caught it after 50 minutes and 3 wasted cycles. The root cause wasn't a code bug. The APIM route hadn't been provisioned. The Function App setting didn't exist. The infrastructure wasn't there.</p>
<p>The pipeline was working as designed. <strong>The design was wrong.</strong></p>
<p>This post covers how I restructured the pipeline from a 12-item, 4-phase single-wave DAG to an 18-item, 6-phase two-wave architecture with a human-approved infrastructure gate — and why that gate eliminated an entire category of failure. <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t">Repo is open.</a> <a href="https://dagent.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing">First blog post</a> for context.</p>
<hr />
<h2>The Failure Pattern, Precisely</h2>
<p>Here's the v1 timeline from <a href="https://dagent.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing">the first post</a>:</p>
<pre><code class="language-plaintext">00:37 — schema-dev           ✅  (4m)
00:41 —┬— backend-dev        ✅  (7m)     ← writes Terraform + Azure Functions
       └— frontend-dev       ✅  (11m)
00:52 —┬— backend-unit-test  ✅  (2m)
       └— frontend-unit-test ✅  (1m)
00:54 — push-code            ❌  (15m TIMEOUT — agent went rogue)
01:09 — push-code            ✅  (2s, deterministic retry)
01:09 — poll-ci              ✅  (10m)
01:19 — integration-test     ❌  → redev cycle 1   ← "404 on /api/generate"
       ... 3 more cycles ...                         ← same 404, same root cause
02:20 — integration-test     ✅
02:47 — create-pr            ✅
</code></pre>
<p>140 minutes total. 53 of those minutes — 38% of the run — were spent in post-deploy reroute cycles caused by the same problem: <strong>application code deployed before its infrastructure existed.</strong></p>
<p>The v1 architecture treated Terraform as a <code>backend-dev</code> concern. The agent wrote <code>.tf</code> files <em>and</em> Azure Functions code in the same session, committed both with the same scope, and the deploy pipeline handled them as one unit. When the feature required a new APIM operation or Function App setting, the infrastructure changes and the code that depended on them raced through CI together.</p>
<p>Sometimes infra landed first. Sometimes it didn't. When it didn't: <code>404</code>.</p>
<hr />
<h2>Why Better Triage Doesn't Fix This</h2>
<p>The immediate instinct is: make the triage engine smarter. Teach it to recognize "infrastructure not provisioned" as a distinct fault domain. Route to <code>infra-architect</code> instead of <code>backend-dev</code>.</p>
<p>I actually tried this. The problem is deeper — it's <em>temporal</em>, not taxonomic. The triage engine can correctly classify <code>fault_domain: "infra"</code> and route to the right agent. But in a single-wave pipeline, re-running the infra agent means re-running the deploy, re-running CI, and re-running the integration test. Each cycle burns 10–20 minutes. And if the fix requires a <code>terraform apply</code> that needs elevated permissions, the loop is infinite.</p>
<p>Triage tells you <em>what</em> broke. It can't fix the fact that the ordering was wrong from the start.</p>
<hr />
<h2>Infrastructure and Application Code Are Different Trust Domains</h2>
<p>This is the table that changed how I think about the pipeline:</p>
<table>
<thead>
<tr>
<th>Dimension</th>
<th>Infrastructure</th>
<th>Application Code</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Blast radius</strong></td>
<td>Entire environment — networking, IAM, DNS, billing</td>
<td>Feature-scoped — one endpoint, one component</td>
</tr>
<tr>
<td><strong>Reversibility</strong></td>
<td>Often irreversible (state files, DNS propagation, IAM bindings)</td>
<td>Always reversible (<code>git revert</code>, redeploy)</td>
</tr>
<tr>
<td><strong>Validation</strong></td>
<td><code>terraform plan</code> — requires human review</td>
<td>Unit tests + CI — fully automated</td>
</tr>
<tr>
<td><strong>Cost of error</strong></td>
<td>Resource leaks, security exposure, cost spikes</td>
<td>Broken feature — rollback and retry</td>
</tr>
<tr>
<td><strong>Feedback loop</strong></td>
<td>Minutes to hours (provisioning)</td>
<td>Seconds (test execution)</td>
</tr>
<tr>
<td><strong>Who approves</strong></td>
<td>Platform engineer or security reviewer</td>
<td>The pipeline itself</td>
</tr>
</tbody></table>
<p><em>"Yeah, but couldn't you just run</em> <code>terraform apply</code> <em>automatically?"</em></p>
<p>For greenfield resources in a dev environment, sure. But the moment you touch IAM role assignments, OIDC federation, network security groups, or APIM policies — the blast radius is environment-wide. A wrong <code>terraform apply</code> can lock out your own CI pipeline (ask me how I know). The enterprise answer is: plan automatically, apply with approval.</p>
<p>The fix isn't better error classification. It's <strong>never letting application agents run until infrastructure is proven to exist.</strong></p>
<hr />
<h2>The Two-Wave DAG</h2>
<img src="https://cdn.hashnode.com/uploads/covers/69c2189a30a9b81e3aedf814/e18ea8b9-9623-48c5-a267-9c969eea76da.png" alt="" style="display:block;margin:0 auto" />

<p>18 items. 6 phases. The two waves never overlap.</p>
<p>The key dependency edges — and this is worth staring at — are:</p>
<pre><code class="language-javascript">// pipeline-state.mjs — the actual dependency map
export const ITEM_DEPENDENCIES = {
  // Wave 1
  "schema-dev":           [],
  "infra-architect":      ["schema-dev"],
  "push-infra":           ["infra-architect"],
  "create-draft-pr":      ["push-infra"],
  "poll-infra-plan":      ["create-draft-pr"],
  "await-infra-approval": ["poll-infra-plan"],
  "infra-handoff":        ["await-infra-approval"],

  // Wave 2 — gated behind infra-handoff
  "backend-dev":          ["schema-dev", "infra-handoff"],  // ← the gate
  "frontend-dev":         ["schema-dev", "infra-handoff"],  // ← the gate
  "backend-unit-test":    ["backend-dev"],
  "frontend-unit-test":   ["frontend-dev"],
  "push-app":             ["backend-unit-test", "frontend-unit-test"],
  "poll-app-ci":          ["push-app"],
  "integration-test":     ["poll-app-ci"],
  "live-ui":              ["poll-app-ci", "integration-test"],
  "code-cleanup":         ["integration-test", "live-ui"],
  "docs-archived":        ["code-cleanup"],
  "publish-pr":           ["docs-archived"],
};
</code></pre>
<p><code>backend-dev</code> and <code>frontend-dev</code> both depend on <code>infra-handoff</code>. That's the structural guarantee. No amount of prompt engineering or triage logic can bypass it — the DAG scheduler won't make the item available until <code>infra-handoff</code> is <code>done</code>.</p>
<p>Six new items vs. v1. <code>push-code</code> split into <code>push-infra</code> + <code>push-app</code>. <code>poll-ci</code> split into <code>poll-infra-plan</code> + <code>poll-app-ci</code>. Not naming gymnastics — each triggers different CI workflows with different permissions and different failure modes.</p>
<hr />
<h2>The <code>infra-architect</code> Agent: Validate Before You Commit</h2>
<p>Most agent systems trust CI to catch errors. The <code>infra-architect</code> agent runs <code>terraform validate</code> + <code>terraform plan</code> locally before anything leaves the session. The hard constraint: don't mark complete until <code>terraform plan</code> succeeds.</p>
<p>Why validate locally instead of trusting CI? Terraform plan is slow in CI (~2 min), plan failures cascade (a missing variable blocks everything), and the plan itself is an artifact — it gets posted to the Draft PR for human review. Catching errors in 10-second local cycles beats 10-minute push → CI → poll cycles.</p>
<p>The agent never runs <code>terraform apply</code>. Apply is irreversible and environment-scoped — a wrong apply can delete resources, corrupt state, or create security holes. The agent writes and validates; CI generates the plan; a human approves; an elevated workflow applies.</p>
<hr />
<h2>The Approval Gate: <code>agent: null</code></h2>
<p>This is the simplest and most important piece of code in the pipeline:</p>
<pre><code class="language-javascript">// pipeline-state.mjs
{ key: "await-infra-approval", agent: null, phase: "approval" }
</code></pre>
<pre><code class="language-typescript">// watchdog.ts — the orchestrator loop
const approvalGateItems = available.filter((i) =&gt; i.agent === null);
if (approvalGateItems.length &gt; 0 &amp;&amp; available.every((i) =&gt; i.agent === null)) {
  console.log("  ⏸  Awaiting human approval — comment /dagent approve-infra");
  break;  // clean exit — ChatOps re-triggers on approval
}
</code></pre>
<p><code>agent: null</code> means no LLM session gets created. Zero tokens. The orchestrator logs a message, exits cleanly, and waits. A GitHub Actions ChatOps workflow (<code>dagent-chatops.yml</code>) watches for PR comments:</p>
<ul>
<li><p><code>/dagent approve-infra</code> — Triggers <code>elevated-infra-deploy.yml</code>, which runs <code>terraform apply</code> with OIDC credentials scoped to a <code>secops-elevated</code> GitHub Environment (requires reviewer approval). On success, calls <code>pipeline:complete</code> for <code>await-infra-approval</code>, then re-triggers the orchestrator.</p>
</li>
<li><p><code>/dagent hold</code> — Cancels all running agentic + elevated workflows. Full stop.</p>
</li>
<li><p><code>/dagent apply-elevated -target=azurerm_resource.x</code> — Targeted apply for when you need to apply specific resources with elevated permissions.</p>
</li>
</ul>
<p>The approval gate costs ~0 seconds of compute and ~0 tokens. The human review typically takes 2–5 minutes. The 50 minutes of wasted agent cycles it eliminates make it the best ROI of any change in the pipeline.</p>
<hr />
<h2>The Infra-Handoff Contract</h2>
<p>After approval and apply, <code>infra-handoff</code> bridges the two waves. It reads <code>terraform output -json</code> from the applied infrastructure and writes a structured contract (with sensitive values masked):</p>
<pre><code class="language-markdown"># Infrastructure Interfaces — my-feature

## Endpoints
- Function App: https://func-tb-dev.azurewebsites.net
- APIM Gateway: https://apim-tb-dev.azure-api.net
- Static Web App: https://swa-tb-dev.azurestaticapps.net

## API Routes
- POST /api/auth/login → fn-demo-login
- GET  /api/hello → fn-hello

## Auth Config
- CORS Origins: ["https://swa-tb-dev.azurestaticapps.net"]
</code></pre>
<p>When <code>backend-dev</code> and <code>frontend-dev</code> start their sessions, they read this file. They know every endpoint, every URL, every resource name. They don't guess. They don't assume. They read a contract that was generated from real <code>terraform output</code> against applied infrastructure.</p>
<p>This is why application development time dropped 22% — agents spend less time exploring and more time implementing, because the infrastructure contract removes an entire class of unknowns.</p>
<hr />
<h2>Graceful Degradation: Knowing When to Stop</h2>
<p>Not every infrastructure error is retryable. The triage engine maintains a list of signals that no agent can fix:</p>
<pre><code class="language-typescript">// triage.ts
const UNFIXABLE_SIGNALS = [
  "authorization_requestdenied",       // Azure AD
  "aadsts700016", "aadsts7000215",     // Entra ID
  "insufficient privileges",
  "does not have authorization",
  "subscription not found",
  "error acquiring the state lock",    // Terraform
  "resource already exists",
  "state blob is already locked",
] as const;
</code></pre>
<p>When one of these appears, <code>triageFailure()</code> returns an empty array — "blocked, don't retry." The pipeline doesn't halt. It preserves all valid work, marks untestable items as <code>n/a</code>, skips to documentation and PR creation, and opens a Draft PR with the error documented.</p>
<p><em>"Why not just halt and throw an error?"</em></p>
<p>Because partial work has value. If <code>schema-dev</code>, <code>infra-architect</code>, and <code>backend-dev</code> all completed successfully but <code>integration-test</code> hit an IAM denial — that's 80% of a feature. Throwing it away because of an environment config issue is wasteful. Ship the draft, let the human fix the IAM binding, re-run the pipeline from where it stopped.</p>
<hr />
<h2>Before/After: The Numbers</h2>
<p>Same feature (full-stack deployment with new APIM routes), v1 vs. two-wave:</p>
<table>
<thead>
<tr>
<th>Phase</th>
<th>v1</th>
<th>Two-Wave (v2)</th>
<th>Δ</th>
</tr>
</thead>
<tbody><tr>
<td>Schema</td>
<td>4 min</td>
<td>4 min</td>
<td>—</td>
</tr>
<tr>
<td>Infrastructure + approval</td>
<td>N/A (in backend-dev)</td>
<td>12 min + ~3 min review</td>
<td><strong>New</strong></td>
</tr>
<tr>
<td>App dev (parallel)</td>
<td>18 min</td>
<td>14 min</td>
<td>−22%</td>
</tr>
<tr>
<td>Unit tests</td>
<td>3 min</td>
<td>2 min</td>
<td>−33%</td>
</tr>
<tr>
<td>Deploy + CI</td>
<td>12 min</td>
<td>10 min</td>
<td>—</td>
</tr>
<tr>
<td>Post-deploy verification</td>
<td><strong>53 min</strong> (4 reroute cycles)</td>
<td><strong>11 min</strong> (0 cycles)</td>
<td><strong>−79%</strong></td>
</tr>
<tr>
<td>Finalize</td>
<td>8 min</td>
<td>7 min</td>
<td>—</td>
</tr>
<tr>
<td><strong>Total wall clock</strong></td>
<td><strong>140 min</strong></td>
<td><strong>~63 min</strong></td>
<td><strong>−55%</strong></td>
</tr>
</tbody></table>
<p>The 79% reduction in post-deploy time is the headline. Those 4 reroute cycles were structurally impossible in v2 — the DAG dependency on <code>infra-handoff</code> prevents the failure class from occurring.</p>
<p>The 22% app-dev speedup is an unexpected bonus: agents producing fewer exploratory commands because the infra contract removes guesswork about which endpoints exist.</p>
<p>The total now includes ~3 minutes of human Terraform plan review. That's a net trade of 3 minutes of human attention for 50 minutes of wasted compute. I'd take that tradeoff every time.</p>
<hr />
<h2>The Mental Model: Your DAG Is Your Trust Model</h2>
<p>Every dependency edge in a DAG is a trust statement.</p>
<p><code>backend-dev → backend-unit-test</code> means: "I trust <code>backend-dev</code>'s output enough to run tests against it."</p>
<p><code>infra-architect → push-infra → poll-infra-plan → await-infra-approval → infra-handoff → backend-dev</code> means: "I don't trust infrastructure changes until CI validates the plan, a human reviews it, an elevated workflow applies it, and the outputs are captured into a contract."</p>
<p>That's 5 edges encoding a trust policy. The old pipeline had 0.</p>
<p>When you add a new agent to any pipeline, the question isn't just "what does it depend on?" It's: <strong>"what am I trusting when I let this agent run?"</strong> If the answer includes "infrastructure that hasn't been validated" or "permissions that haven't been approved" — add a gate.</p>
<h3>Trust Taxonomy for AI Agents</h3>
<p>Based on running this pipeline across multiple features, here's where I've landed:</p>
<p><strong>Trust the agent to:</strong></p>
<ul>
<li><p>Reason about code. Write functions, debug type errors, design APIs. This is what LLMs are genuinely good at.</p>
</li>
<li><p>Diagnose failures. Parse error logs, emit structured <code>{"fault_domain": "backend", "diagnostic_trace": "..."}</code> JSON. Agents are surprisingly reliable at classification.</p>
</li>
<li><p>Follow bounded workflows. Numbered steps with clear completion criteria. The key word is <em>bounded.</em></p>
</li>
</ul>
<p><strong>Don't trust the agent to:</strong></p>
<ul>
<li><p>Orchestrate itself. (The v1 <code>push-code</code> agent ran 63 shell commands in 15 minutes, including <code>git reset --hard</code>. Deterministic orchestration is non-negotiable.)</p>
</li>
<li><p>Judge blast radius. (An agent doesn't distinguish "this affects one endpoint" from "this affects the entire network layer." The DAG must encode this.)</p>
</li>
<li><p>Know when to stop. (Without circuit breakers, agents retry until timeout with decreasing output quality.)</p>
</li>
<li><p>Approve infrastructure. Not because they'd get it wrong — the <code>infra-architect</code> agent writes <em>correct</em> Terraform. But the consequence of a wrong apply is categorically different from a wrong endpoint, and the organization needs a human audit trail.</p>
</li>
</ul>
<hr />
<h2>Updated Stripe Comparison</h2>
<p>The <a href="https://dagent.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing">first post</a> mapped my v1 against <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2">Stripe's Minions</a>. The biggest delta in v2: infrastructure handling. Stripe likely doesn't have this problem — their infra is managed by a dedicated platform team, not by the same agents that write application code. For everyone else building on cloud primitives, the infrastructure trust boundary needs to be explicit in the execution graph.</p>
<hr />
<h2>Trade-Offs and Open Questions</h2>
<p>This architecture isn't free. Honest accounting:</p>
<p><strong>Added latency.</strong> Wave 1 is strictly sequential: <code>schema-dev → infra-architect → push-infra → create-draft-pr → poll-infra-plan → await-infra-approval → infra-handoff</code>. That's 7 items in series before any application code runs. For features with no infrastructure changes, this is pure overhead. The pipeline mitigates this with workflow types — <code>Backend</code> and <code>Frontend</code> workflow types still skip infra items via N/A marking — but <code>Full-Stack</code> features pay the full serial cost.</p>
<p><strong>Human dependency.</strong> The approval gate blocks on a human. If no one reviews the Terraform plan for 2 hours, the pipeline sits idle for 2 hours. In practice: we use Slack notifications on Draft PR creation. Average review time is 3 minutes. But this is a process dependency, not a technical one, and it doesn't self-heal.</p>
<p><strong>State complexity.</strong> 18 items × 6 phases × 4 workflow types = a combinatorial state space. Each workflow type has an N/A map that marks irrelevant items — get one entry wrong and an item either runs when it shouldn't (wasted tokens) or gets skipped when it shouldn't (missing validation). Tests cover this (<code>apm-parity.test.ts</code>), but the surface area is real.</p>
<p><strong>Concurrent Terraform state.</strong> Two feature branches can't run <code>terraform plan</code> against the same state backend simultaneously without state locking conflicts. The pipeline currently handles this by detecting <code>"error acquiring the state lock"</code> as an unfixable signal (graceful degradation), but the correct solution is either a queueing mechanism or environment-per-branch isolation. Neither is implemented.</p>
<hr />
<h2>The Principle</h2>
<p><strong>Separate concerns by trust level, not by technology.</strong></p>
<p>The v1 pipeline separated by phase: pre-deploy, deploy, post-deploy, finalize. The v2 pipeline separates by trust boundary: infrastructure (human-approved) vs. application (auto-verified). The phases still exist within each wave — but the trust model governs the top-level architecture.</p>
<p>This applies beyond AI pipelines. Any system where autonomous agents interact with infrastructure should encode trust boundaries in its execution graph, not in its prompts. Prompts can be ignored. DAG edges can't.</p>
<hr />
<h2>What's Next</h2>
<p>The <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t">full pipeline is open source</a> — orchestrator, APM compiler, triage engine, ChatOps workflows, and the two-wave DAG. If you're adapting this to your own stack, the files to start with are <code>.apm/instructions/</code> (your project's rules) and the workflow type configs in <code>pipeline-state.mjs</code>.</p>
<p>I'm most interested in feedback on:</p>
<ul>
<li><p><strong>The approval gate pattern</strong> — is <code>/dagent approve-infra</code> via ChatOps the right UX, or should this be a GitHub Environment protection rule?</p>
</li>
<li><p><strong>Graceful degradation</strong> — shipping a Draft PR with partial work vs. halting entirely. Which would your team trust?</p>
</li>
<li><p><strong>Terraform state contention</strong> — environment-per-branch isolation vs. a queue. What's worked for you?</p>
</li>
</ul>
<hr />
<p><em>If you're building deterministic agent pipelines or figuring out where human gates belong in AI-assisted workflows, I'd genuinely like to hear what you've learned.</em> <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t"><em>The repo</em></a> <em>is open and actively developed. The</em> <a href="https://dagent.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing"><em>first post</em></a> <em>covers the v1 architecture and the Stripe convergence. I'm</em> <a href="https://www.linkedin.com/in/roman-kaliupin-74994b158/"><em>Roman Kaliupin</em></a> <em>— I build agentic developer tooling and always enjoy connecting with people working on similar problems.</em></p>
]]></content:encoded></item><item><title><![CDATA[I Built a Deterministic Agentic Coding Pipeline. Then I Found Out Stripe Built the Same Thing.]]></title><description><![CDATA[12 AI agents, one DAG, zero human interaction. Open-source pipeline that writes code, deploys, self-heals, and delivers tested PRs autonomously.]]></description><link>https://kaliupin.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing</link><guid isPermaLink="true">https://kaliupin.hashnode.dev/i-built-a-deterministic-agentic-coding-pipeline-then-i-found-out-stripe-built-the-same-thing</guid><category><![CDATA[AI]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[devtools]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[architecture]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Roman Kaliupin]]></dc:creator><pubDate>Tue, 24 Mar 2026 06:15:45 GMT</pubDate><content:encoded><![CDATA[<p><strong>TL;DR:</strong> I built a headless, DAG-scheduled AI coding pipeline — 12 specialist agents, self-healing recovery, real browser testing — that takes a feature spec and delivers a tested PR with zero human interaction. Then I read Stripe's Minions blog post and realized we independently converged on the same architectural pattern. <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t">The repo is open.</a></p>
<hr />
<h2>The 80/20 Insight</h2>
<p>We have incredible AI coding tools — Copilot, Cursor, Claude Code. They're transformative for the 20% of work that requires judgment, creativity, and architectural thinking.</p>
<p>But what about the other 80%? The work where every step is well-understood, the patterns exist in your codebase, the rules are documented, and the tests define success. This work doesn't need a human co-pilot. It needs a <strong>factory</strong>.</p>
<p>So I built one. Then I found out <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2">Stripe built the same thing</a> — and ships 1,300+ PRs per week with it.</p>
<hr />
<h2>The Architecture: Deterministic Control + LLM Execution</h2>
<p>The core insight — and one that Stripe arrived at independently — is this: <strong>LLMs are great reasoners but unreliable orchestrators.</strong> Give them a focused task with clear boundaries, and they excel. Ask them to coordinate a 12-step pipeline with failure recovery and CI/CD integration, and they hallucinate steps, skip phases, and burn tokens in loops.</p>
<p>The system separates two concerns that most agentic tools conflate:</p>
<ul>
<li><p><strong>Control plane (deterministic)</strong> — A TypeScript <code>while</code> loop reads a DAG state machine, resolves dependencies, and spawns agent sessions. No LLM decides what happens next. The state machine does.</p>
</li>
<li><p><strong>Execution plane (LLM)</strong> — Each specialist agent receives a bounded context (rules, MCP tools, skills) and reasons about its domain. Backend-dev writes backend services. Frontend-dev writes React components. Live-ui runs Playwright against the deployed app. Each agent is trusted to <em>think</em>, but not to <em>orchestrate</em>.</p>
</li>
</ul>
<h3>The Stripe Convergence</h3>
<p>After reading <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2">Stripe's Minions blog post</a>, I mapped my design decisions against theirs:</p>
<table>
<thead>
<tr>
<th>Design Decision</th>
<th>My Pipeline</th>
<th>Stripe Minions</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Orchestration</strong></td>
<td>Deterministic TypeScript loop with DAG state machine</td>
<td>"Blueprints" — state machines with interwoven deterministic and agentic nodes</td>
</tr>
<tr>
<td><strong>Agent specialization</strong></td>
<td>12 domain-specific agents with per-agent prompts</td>
<td>Task-specific agents with curated tool subsets</td>
</tr>
<tr>
<td><strong>Context management</strong></td>
<td>APM compiler with token budgets + modular rules</td>
<td>Scoped rules (Cursor format) + MCP tools via "Toolshed" (~500 tools)</td>
</tr>
<tr>
<td><strong>CI integration</strong></td>
<td>Deterministic deploy bypasses (no LLM) → poll CI → auto-fix → re-push (bounded cycles)</td>
<td>Push → CI run → autofix → agent fix → second CI run (bounded to 2 iterations)</td>
</tr>
<tr>
<td><strong>Failure recovery</strong></td>
<td>Structured triage → compound fault domains → targeted reroute + dedup circuit breakers</td>
<td>CI failures route back to agent nodes for local remediation</td>
</tr>
<tr>
<td><strong>Safety boundary</strong></td>
<td>Circuit breakers: 10 retries, 5 reroute cycles, session timeouts</td>
<td>2 CI iteration limit; quarantined devboxes with no production access</td>
</tr>
</tbody></table>
<p>Deterministic orchestration wrapping LLM execution, configured per project, with bounded failure recovery and CI/CD as a first-class pipeline phase. Two teams, working independently, arrived at the same pattern.</p>
<hr />
<h2>The Pipeline DAG</h2>
<p>12 items across 4 phases, scheduled by an explicit dependency graph — not inferred by an LLM at runtime. Items within a batch run in parallel:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c2189a30a9b81e3aedf814/a1a5494d-1254-4eb2-919d-0799985f83c8.png" alt="" style="display:block;margin:0 auto" />

<p>The dashed arrows show self-healing reroutes with <strong>compound fault domains</strong> — a CORS failure resets only frontend-dev, not the entire pipeline. Test agents <strong>write new tests</strong> for the code that dev agents just created. The deploy phase (<code>push-code</code>, <code>poll-ci</code>) runs as <strong>deterministic bypasses</strong> — no LLM session, no tokens spent on mechanical operations. Four workflow types (<code>Backend</code>, <code>Frontend</code>, <code>Full-Stack</code>, <code>Infra</code>) prune irrelevant items at init.</p>
<hr />
<h2>What Makes It Work</h2>
<h3>1. APM: Agent Package Manager</h3>
<p>Context pollution kills agent quality silently. When you stuff a single system prompt with every rule for your entire project, the agent's attention degrades. <a href="https://github.com/microsoft/apm">Microsoft's APM</a> solves this — you declare agents, their instruction includes, MCP servers, and skills in a single <code>apm.yml</code> manifest:</p>
<table>
<thead>
<tr>
<th>Without APM (monolithic prompt)</th>
<th>With APM (compiled per-agent context)</th>
</tr>
</thead>
<tbody><tr>
<td>One giant <code>.cursorrules</code> or <code>CLAUDE.md</code> for all agents</td>
<td>28 modular <code>.md</code> files organized by domain</td>
</tr>
<tr>
<td>Backend agent sees frontend rules it can't use</td>
<td>Backend-dev gets <code>[always, backend, infra, tooling/roam-tool-rules.md]</code> — 4,800 tokens of relevant context</td>
</tr>
<tr>
<td>No limit on prompt size — degrades silently</td>
<td>6,000-token budget enforced at compile time — <code>ApmBudgetExceededError</code> fails the build</td>
</tr>
<tr>
<td>Adding a new convention bloats every agent</td>
<td>Adding <code>infra/cors-rules.md</code> only affects agents that include <code>infra</code></td>
</tr>
<tr>
<td>Switching projects requires rewriting everything</td>
<td>Point <code>--app</code> at a new directory with its own <code>apm.yml</code> — same engine, different project</td>
</tr>
</tbody></table>
<pre><code class="language-yaml"># apm.yml — each agent declares exactly which rules it needs
agents:
  backend-dev:
    instructions: [always, backend, infra, tooling/roam-tool-rules.md, tooling/roam-efficiency.md]
    mcp: [roam-code]
    skills: [test-backend-unit]
  frontend-dev:
    instructions: [always, frontend, tooling/roam-tool-rules.md, tooling/roam-efficiency.md]
    mcp: [roam-code]
    skills: [test-frontend-unit, build-frontend]
  poll-ci:
    instructions: [always]    # minimal context — this agent just watches CI
    mcp: []
    skills: []
</code></pre>
<p>If any agent's assembled rules exceed the token budget, compilation fails — the same way type systems prevent runtime errors by failing at compile time.</p>
<h3>2. Self-Healing Recovery with Live Browser Testing</h3>
<p>When post-deploy tests fail, the pipeline doesn't retry blindly. The failing agent emits a structured diagnostic:</p>
<pre><code class="language-json">{ "fault_domain": "frontend+infra", "diagnostic_trace": "CORS 403 on /api/generate — APIM not routing to backend origin" }
</code></pre>
<p>The triage engine maps the fault domain to the correct development agents, resets them to <code>pending</code>, injects the error context into their next prompt, and re-enters the loop. Circuit breakers prevent infinite loops: 5 redevelopment cycles, 10 retries per item, hard session timeouts.</p>
<p>The <code>live-ui</code> agent doesn't execute a pre-written test suite — it <strong>creates Playwright E2E scenarios</strong> tailored to the feature it just built, runs them against the deployed app with headless Chromium. It authenticates through the real auth flow, validates CORS headers, routing, and rendered DOM state — all against production-like infrastructure, not mocked endpoints.</p>
<h3>3. Structural Code Intelligence (Roam-Code)</h3>
<p><a href="https://github.com/Cranot/roam-code">Roam-code</a> pre-indexes the codebase into a <strong>semantic AST graph</strong> using tree-sitter (27 languages, 102 MCP tools). Instead of text search, agents ask structural questions:</p>
<table>
<thead>
<tr>
<th>What the agent needs</th>
<th>Text search (grep)</th>
<th>Graph query (roam-code)</th>
</tr>
</thead>
<tbody><tr>
<td>Find all callers of <code>generateSku</code></td>
<td>String match — comments, imports, and calls indiscriminately</td>
<td><code>roam_context</code> — only call sites, with file paths and line numbers</td>
</tr>
<tr>
<td>"What breaks if I change this type?"</td>
<td>No answer</td>
<td><code>roam_preflight</code> — blast radius: affected files, consumers, test coverage</td>
</tr>
<tr>
<td>"Which tests cover this code path?"</td>
<td><code>grep -r</code> — misses indirect coverage</td>
<td><code>roam_affected_tests</code> — follows the call graph</td>
</tr>
<tr>
<td>"Is this change safe to commit?"</td>
<td>No answer</td>
<td><code>roam_check_rules</code> — deterministic SEC/PERF/COR audit against the AST diff</td>
</tr>
</tbody></table>
<p>Agents are forbidden from using grep for code exploration. Before any agent can mark its work done, it must pass <code>roam_check_rules</code> — a deterministic governance gate, not a prompt-based suggestion.</p>
<h3>4. Agents Never Touch Git or State</h3>
<table>
<thead>
<tr>
<th>What the agent wants to do</th>
<th>What it actually calls</th>
<th>Why it matters</th>
</tr>
</thead>
<tbody><tr>
<td>Commit code</td>
<td><code>agent-commit.sh &lt;scope&gt; "&lt;msg&gt;"</code></td>
<td>Scoped staging — <code>backend</code> scope only stages <code>backend/</code>, <code>packages/</code>, <code>infra/</code>. Can't accidentally commit <code>.env</code> files</td>
</tr>
<tr>
<td>Push to remote</td>
<td><code>agent-branch.sh push</code></td>
<td>Validates branch is not <code>main</code>. Retries once on network failure</td>
</tr>
<tr>
<td>Deploy phase</td>
<td>Deterministic bypass — no LLM session</td>
<td>Orchestrator runs scripts directly. No tokens spent on mechanical operations</td>
</tr>
<tr>
<td>Mark work done</td>
<td><code>npm run pipeline:complete</code></td>
<td>Phase-gated — rejects if prior phase has incomplete items. State mutation is atomic and logged</td>
</tr>
<tr>
<td>Record failure</td>
<td><code>npm run pipeline:fail</code></td>
<td>Appends to error log. Auto-halts after 10 failures (circuit breaker)</td>
</tr>
</tbody></table>
<p>Every side-effect goes through deterministic wrappers that provide the same auditability enterprises expect from their human CI/CD pipelines. Trust the LLM to reason, but don't trust it to operate.</p>
<hr />
<h2>What 140 Minutes and 4 Failure Cycles Taught Me</h2>
<p>Architecture diagrams are easy to draw. Running them against real infrastructure is where the learning happens. I ran the pipeline on a full-stack deployment feature — provisioning Azure infra, deploying backend Functions + frontend SWA, wiring APIM, and validating end-to-end with Playwright. Here's what actually happened:</p>
<pre><code class="language-plaintext">00:37 — schema-dev (4m)
00:41 —┬— backend-dev (7m)        ←── parallel
       └— frontend-dev (11m)      ←── parallel
00:52 —┬— backend-unit-test (2m)  ←── parallel
       └— frontend-unit-test (1m) ←── parallel
00:54 — push-code ❌ (15m TIMEOUT — agent went rogue)
01:09 — push-code ✅ (2s, deterministic retry)
01:09 — poll-ci ✅ (10m)
01:19 — integration-test ❌ → redev cycle 1
       ... 3 more cycles on the same bug ...
02:20 — integration-test ✅ (all 7 tests pass)
02:25 — live-ui ✅ (8 Playwright tests)
02:47 — create-pr ✅ (PR #2)
</code></pre>
<p><strong>Final result:</strong> 140m 30s, 33 steps (28 pass / 5 fail), 4 redevelopment cycles. The pipeline shipped a working PR.</p>
<p><strong>What went right:</strong> The happy-path pre-deploy phase completed in 15 minutes with full DAG parallelism. The backend agent independently identified that <code>tsc</code> doesn't bundle dependencies and migrated to esbuild. The CI agent proactively fixed a workflow bug. The finalize phase removed 56 lines of dead code using roam-code's analysis.</p>
<p><strong>What went wrong — and what I learned:</strong></p>
<p>The first <code>push-code</code> agent went rogue. It was supposed to do one thing: push the branch. Instead it executed 63 shell commands in 15 minutes — including <code>git reset --hard</code>, cherry-pick operations, and <code>pipeline:complete</code> calls on items owned by other agents. It timed out. The deterministic fallback (just <code>git push</code>) worked in 2 seconds. Lesson: mechanical operations should never get an LLM session.</p>
<p>The bigger bug: <code>agent-commit.sh</code> scopes commits by agent domain — <code>backend</code> scope only stages files under <code>backend/</code>. The backend-dev agent correctly identified a CI workflow fix and applied it to the working tree, but the commit script wouldn't stage <code>.github/workflows/deploy-backend.yml</code>. The fix existed in the working tree for 3 cycles but was never committed. The pipeline burned ~50 minutes on the identical unfixed bug because the agent could edit the file but not commit it through the sanctioned path.</p>
<p>The triage diagnostics from cycle 3 onward explicitly reported: <em>"A working-tree fix exists but was NEVER COMMITTED."</em> The system saw the problem clearly — it just couldn't act on it.</p>
<p><strong>What I fixed after:</strong> Added a <code>cicd</code> commit scope for cross-cutting workflow files. Added duplicate-error detection to the circuit breaker — if an agent fails with the same error and no code has changed, the pipeline halts immediately instead of burning tokens. Made <code>push-code</code> always deterministic.</p>
<p>This is the feedback loop that makes deterministic pipelines valuable: every failure is observable, triageable, and fixable. The audit trail is the training data.</p>
<hr />
<h2>Try It</h2>
<p>The <a href="https://github.com/rkaliupin/DAGent">entire pipeline is open source</a>. It includes the orchestrator, the APM system, 6 CI/CD workflows, and comprehensive documentation. Every run produces a deterministic audit trail — state files, transition logs, per-step metrics, and Playwright logs.</p>
<h3>It's a Blueprint — Not a Finished Product</h3>
<p>The orchestration engine is generic — the watchdog, state machine, APM compiler, triage engine, and DAG scheduler don't know anything about Azure. But the sample app's instruction fragments and CI workflows are Azure-specific. Adapting this to your stack means rewriting the files in <code>.apm/instructions/</code> and <code>.github/workflows/</code> — exactly the files designed to be swapped per project.</p>
<h3>Quick Start</h3>
<pre><code class="language-bash"># 1. Clone and open in DevContainer (VS Code / Codespaces)

# 2. Create a feature spec
mkdir -p apps/sample-app/in-progress
vim apps/sample-app/in-progress/my-feature_SPEC.md

# 3. Initialize pipeline state
export APP_ROOT=apps/sample-app
npm run pipeline:init my-feature Full-Stack

# 4. Run the orchestrator
npm run agent:run -- --app apps/sample-app my-feature

# 5. Review the PR when the pipeline completes
</code></pre>
<p>Prerequisites: GitHub CLI auth (<code>gh auth status</code>), Azure CLI auth (for live deploy/test), DevContainer with <code>--shm-size=2gb</code>, and an <code>apm.yml</code> config.</p>
<p>I'd love technical feedback — especially on gaps in the architecture or if you've tried similar approaches.</p>
<hr />
<p><em>If you're building deterministic agent orchestration or evaluating this pattern for your org, I'd love to compare notes.</em> <a href="https://github.com/RomanKaliupinMelonusa/DAGent-t"><em>The repo</em></a> <em>is open and I'm actively developing it. I'm</em> <a href="https://www.linkedin.com/in/roman-kaliupin-74994b158/"><em>Roman Kaliupin</em></a> <em>— I build agentic developer tooling and always enjoy connecting with people working on similar problems.</em></p>
]]></content:encoded></item></channel></rss>