<?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: Guillermo Leyendeker</title>
    <description>The latest articles on DEV Community by Guillermo Leyendeker (@gleyendeker).</description>
    <link>https://dev.to/gleyendeker</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4076418%2F91162074-74e5-4be3-b493-4c97fd80ca29.jpg</url>
      <title>DEV Community: Guillermo Leyendeker</title>
      <link>https://dev.to/gleyendeker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gleyendeker"/>
    <language>en</language>
    <item>
      <title>Everything that breaks once you stop watching the screen</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:31:11 +0000</pubDate>
      <link>https://dev.to/gleyendeker/everything-that-breaks-once-you-stop-watching-the-screen-44dj</link>
      <guid>https://dev.to/gleyendeker/everything-that-breaks-once-you-stop-watching-the-screen-44dj</guid>
      <description>&lt;p&gt;An agent that works fine in a ten-minute demo and one that runs on its own for months are not the same system. The difference isn't architectural: it's dozens of edge cases that only surface with time on the clock.&lt;/p&gt;

&lt;p&gt;Clocks drifting out of sync. A machine that suspends overnight. A parser that breaks on an unexpected prefix. A browser that grows without a ceiling until it eats the memory. A rhetorical question, written by the agent itself, that stalled the entire pipeline waiting for an answer nobody was going to give.&lt;/p&gt;

&lt;p&gt;None of these appears in a diagram, and together they consumed as much time as every design decision before them. If you're about to leave something running unattended, this is the list you'd want to have read first.&lt;/p&gt;

&lt;h2&gt;Clocks&lt;/h2&gt;

&lt;p&gt;My favourite, because it's the hardest to suspect. For a while, the same task could read as alive or dead depending on which part of the system was looking at it.&lt;/p&gt;

&lt;p&gt;The cause: the log file's name was generated in one timezone and its status was evaluated in another. Neither was "wrong" — they were simply different. The result was that a perfectly active run showed up as stopped, or the reverse, depending on the time of day.&lt;/p&gt;

&lt;p&gt;Timezone mismatches are the kind of bug you don't find by reading code, because each piece looks reasonable on its own. You find it by watching the screen and noticing it says one thing while the system does another.&lt;/p&gt;

&lt;h2&gt;The machine that goes to sleep&lt;/h2&gt;

&lt;p&gt;The watchdog kills processes that have been running too long. Simple, until the machine suspends overnight.&lt;/p&gt;

&lt;p&gt;On waking, the watchdog computed elapsed time in wall-clock hours and found processes with eight or ten hours of "activity" that hadn't executed a single cycle. And it killed them, correctly from its own point of view.&lt;/p&gt;

&lt;p&gt;It had to be taught to &lt;strong&gt;subtract host suspension time&lt;/strong&gt; from the calculation. It's a three-line adjustment that only occurs to you after losing work to it.&lt;/p&gt;

&lt;h2&gt;Fragile parsers&lt;/h2&gt;

&lt;p&gt;The end-to-end test progress bar sat frozen at zero. The suite ran fine, the tests passed, but the indicator never moved.&lt;/p&gt;

&lt;p&gt;The reason: the test runner includes the project name on each output line when more than one project is configured, and my parser expected the format without that prefix. One extra token and the counter stayed at zero forever.&lt;/p&gt;

&lt;p&gt;It's a reminder of something I badly underestimated early on: &lt;strong&gt;almost all integration with external tools runs through parsing their text output&lt;/strong&gt;, and that output changes without notice, through configuration or version.&lt;/p&gt;

&lt;h2&gt;Memory that grows on its own&lt;/h2&gt;

&lt;p&gt;The browser running the interface reviews grew in memory without a ceiling. It wasn't a leak in my code: it was the normal behaviour of a browser held open for hours, accumulating tabs, contexts and cache.&lt;/p&gt;

&lt;p&gt;The fix wasn't elegant and didn't need to be: recycle the containers when they cross a RAM threshold. Long-lived processes don't get fixed, they get restarted periodically.&lt;/p&gt;

&lt;h2&gt;States that lie on screen&lt;/h2&gt;

&lt;p&gt;There was an entire family of bugs around the interface showing things that weren't so.&lt;/p&gt;

&lt;p&gt;Rows spinning indefinitely with an activity indicator, because the active task's identifier was compared incorrectly and no match ever switched it off. Live runs reading as stopped. Steps that did nothing but claimed the run button.&lt;/p&gt;

&lt;p&gt;They all look cosmetic and none of them are, because the state on screen is what you use to decide whether to step in. An indicator that lies makes you wait for something already finished, or interrupt something that was working fine.&lt;/p&gt;

&lt;h2&gt;Operations that can't tell empty from broken&lt;/h2&gt;

&lt;p&gt;Read-only steps — the ones that explore code without modifying anything — still triggered the automatic push on completion. Since there was nothing to push, the operation failed, and that failure was reported as though the task had gone wrong.&lt;/p&gt;

&lt;p&gt;It's the same pattern as the exhausted turns from the previous entry: &lt;strong&gt;the system couldn't distinguish "there was nothing to do" from "something went wrong"&lt;/strong&gt;. And that confusion generates noise which eventually teaches you to ignore the warnings, the worst possible outcome.&lt;/p&gt;

&lt;h2&gt;The rhetorical question that jammed everything&lt;/h2&gt;

&lt;p&gt;One skill ended its runs with a question along the lines of "shall we continue with the next part?". It wasn't expecting an answer: it was a closing formula.&lt;/p&gt;

&lt;p&gt;But the orchestrator detects runs left waiting on user input, so they don't hang indefinitely. A question at the end, however rhetorical, triggers that detection and aborts the whole sequence.&lt;/p&gt;

&lt;p&gt;The fix was to explicitly forbid closing on a question. It's also the case that gave rise to the self-improvement loop from the fourth entry: it was the canonical example of "I caught this by hand, the system should be catching it".&lt;/p&gt;

&lt;h2&gt;Other people's infrastructure falling over&lt;/h2&gt;

&lt;p&gt;When the provider's API returned a server error, the entire queue stopped. A thirty-second blip left the afternoon's work standing still.&lt;/p&gt;

&lt;p&gt;Now those errors retry the step instead of killing the sequence. It's the same idea as in the entry on failure policy: &lt;strong&gt;you have to separate failures of the work from failures of the environment&lt;/strong&gt;, because they call for different responses.&lt;/p&gt;

&lt;h2&gt;The pattern behind all of them&lt;/h2&gt;

&lt;p&gt;Looking at the full list, half of these cases weren't discovered by reading code. They were discovered by watching the screen and noticing something said one thing while the system did another.&lt;/p&gt;

&lt;p&gt;And almost none of them are agent problems. They're old distributed-systems problems: desynchronised clocks, miscalculated timeouts, long-lived processes accumulating state, fragile parsers, external dependencies failing. They reappear intact because &lt;strong&gt;an agent running for hours is, for all practical purposes, an unreliable long-lived process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If I had one piece of advice for someone about to build something like this, it would be: the interesting part — the prompts, the architecture, the retry policy — is the part you'll solve quickly. The part that eats your time is what keeps the system truthful when nobody is watching it.&lt;/p&gt;

&lt;h2&gt;The full arc&lt;/h2&gt;

&lt;p&gt;That's the path of these four months. It started as a screen with buttons for launching agents, with two commits devoted to one button's icon, and ended as a control system with distrust, budgets and auditing.&lt;/p&gt;

&lt;p&gt;Today it has closed &lt;strong&gt;2,095 fixes and features, plus 654 audits&lt;/strong&gt;, on real projects.&lt;/p&gt;

&lt;p&gt;The screen is still there. It's no longer the important part.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>debugging</category>
      <category>softwareengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your agent ran out of turns. Your system logs it as an error.</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Wed, 30 Sep 2026 18:00:30 +0000</pubDate>
      <link>https://dev.to/gleyendeker/your-agent-ran-out-of-turns-your-system-logs-it-as-an-error-30io</link>
      <guid>https://dev.to/gleyendeker/your-agent-ran-out-of-turns-your-system-logs-it-as-an-error-30io</guid>
      <description>&lt;p&gt;An agent that runs out of budget and an agent that gets it wrong end the same way in your terminal: exit code 1.&lt;/p&gt;

&lt;p&gt;For two months my system treated them as the same case, which is wrong, because they call for opposite responses: &lt;strong&gt;one resumes, the other gets fixed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Behind that confusion sits a number almost nobody calibrates and everybody leaves as it came: how many turns to give the agent before cutting it off. With no limit, it can spend hours circling something it won't solve. With too low a limit, it abandons work that was two steps from done.&lt;/p&gt;

&lt;p&gt;That number isn't a guess. Only in August, with 30 days of real runs in the database, could I build a table of budgets per task type instead of using the same one for everything.&lt;/p&gt;

&lt;h2&gt;How it was before: no limit and a hammer&lt;/h2&gt;

&lt;p&gt;At first there was no turn cap at all. Every step ran with the global default configuration, and the only brake was a watchdog that kills stuck processes after an hour.&lt;/p&gt;

&lt;p&gt;A time-based watchdog is a blunt instrument. It can't tell an agent working well on something hard from one stuck in an unproductive loop: both burn an hour the same way. All it guarantees is that nothing runs forever.&lt;/p&gt;

&lt;p&gt;That watchdog also had its own operational lesson, which I cover in the final entry: the machine suspends overnight, so suspended time had to be subtracted from the calculation. Otherwise it killed healthy runs over hours that had never actually elapsed.&lt;/p&gt;

&lt;p&gt;In June I added a global turn cap as a backstop against runaway runs. Better than nothing, but still a single number for tasks that differ wildly from one another.&lt;/p&gt;

&lt;h2&gt;The table the data produced&lt;/h2&gt;

&lt;p&gt;In August the cap became per task type, calibrated by looking at how the last 30 days of runs had actually behaved.&lt;/p&gt;

&lt;p&gt;What I like about the result is that the table has a logic you can explain without looking at the numbers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;strong&gt;verifier is the most generous, at 160 turns&lt;/strong&gt;. That makes sense: it reviews someone else's work, and reviewing properly means reading far more than you write.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;fix implementer follows at 150&lt;/strong&gt;. A fix usually starts without knowing where the problem is, so a good share of the budget goes into finding it.&lt;/li&gt;
  &lt;li&gt;The frontend implementers sit around &lt;strong&gt;90 to 110&lt;/strong&gt;, depending on whether they work on the admin or the public application.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;explorer gets 60&lt;/strong&gt;. It only has to map the terrain and return the touch points: if it needs more than that, something is wrongly framed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Above all of it sits a global safety ceiling of 200 turns, which no task should ever reach and is there just in case.&lt;/p&gt;

&lt;h2&gt;The mistake that took me two months to understand&lt;/h2&gt;

&lt;p&gt;And now the part I consider most important in this entry, because it isn't about the number but about what running out of it means.&lt;/p&gt;

&lt;p&gt;For the first two months, an agent that exhausted its turns showed up in the system as &lt;strong&gt;"exit code 1"&lt;/strong&gt;. That is: exactly like any other error.&lt;/p&gt;

&lt;p&gt;That's a serious diagnostic problem. Running out of budget and failing are completely different things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A real failure means something went wrong and you need to investigate what.&lt;/li&gt;
  &lt;li&gt;Exhausting the budget means the work &lt;em&gt;was in progress&lt;/em&gt; and got interrupted. Nothing is broken: something is unfinished.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confusing them sent me hunting for bugs that didn't exist, more than once. And the inverse, which is worse: assuming certain failures were "the turn limit again" when they were legitimate errors.&lt;/p&gt;

&lt;p&gt;In August I separated them. Running out of budget now has its own message, states clearly what happened, and &lt;strong&gt;no longer kills the sequence&lt;/strong&gt;: the step goes back to the queue instead of dragging the whole task into failure. Which is correct, because the work done up to that point still stands.&lt;/p&gt;

&lt;h2&gt;Budgets are editable without touching code&lt;/h2&gt;

&lt;p&gt;A small decision with a large impact: the per-task caps are editable from the interface, with no restart.&lt;/p&gt;

&lt;p&gt;The reason is that calibrating them isn't a one-time job. The model changes, project complexity changes, the skills change. A number you adjust by editing code and redeploying is a number that in practice never gets adjusted.&lt;/p&gt;

&lt;h2&gt;The same criterion, applied to the model&lt;/h2&gt;

&lt;p&gt;There's a related decision I made at the same time with the same shape: not every step deserves the same model.&lt;/p&gt;

&lt;p&gt;The verifier — the most expensive step in the system, that 29% of the bill I described in the entry about measurement — deliberately runs on &lt;strong&gt;a cheaper model&lt;/strong&gt;. It verifies against explicit criteria, and that doesn't require the most capable model available.&lt;/p&gt;

&lt;p&gt;Meanwhile, the component making autonomous decisions about what to do next runs on &lt;strong&gt;the highest tier with maximum reasoning effort&lt;/strong&gt;. It's the one that executes least often and has the worst consequences when it gets things wrong.&lt;/p&gt;

&lt;p&gt;It's the same idea as the turn budgets, applied along another dimension: &lt;strong&gt;budget should follow consequence, not volume&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;What I learned&lt;/h2&gt;

&lt;p&gt;A turn budget looks like a technical parameter and is a product decision: it defines how much autonomy each task gets before someone interrupts it.&lt;/p&gt;

&lt;p&gt;And the expensive mistake isn't getting the number wrong — that's corrected with data, and the data shows up on its own if you're measuring. The expensive mistake is failing to distinguish "ran out of budget" from "failed", because the first is resumed and the second is investigated, and confusing them wastes your time in both directions.&lt;/p&gt;

&lt;h2&gt;What's left to tell&lt;/h2&gt;

&lt;p&gt;Everything I've covered so far is design decisions: things you think about, plan and implement.&lt;/p&gt;

&lt;p&gt;The final entry is about the other half of the work, the half that appears in no architecture diagram and consumed half the time: everything that breaks when you leave the system actually running, for months, on a machine that suspends, restarts and runs out of memory.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>You restart the server and the agent loses all ten tasks</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Mon, 28 Sep 2026 12:30:19 +0000</pubDate>
      <link>https://dev.to/gleyendeker/you-cant-restart-the-system-thats-running-your-work-3ig</link>
      <guid>https://dev.to/gleyendeker/you-cant-restart-the-system-thats-running-your-work-3ig</guid>
      <description>&lt;p&gt;You leave the agent working through a queue of ten tasks. You need to deploy a server improvement, you restart it, and all ten are gone.&lt;/p&gt;

&lt;p&gt;Not just the pending work: also any trace of which one was running and what state it was left in. The queue lived in the process's memory, so any restart — a deploy, an update, a power cut — wiped it entirely.&lt;/p&gt;

&lt;p&gt;And the server you're restarting is the very one you're improving every day. Every improvement forced a choice: wait for the queue to drain, or lose the plan.&lt;/p&gt;

&lt;p&gt;The fix isn't "put the queue in a database" and done. What you actually have to decide is what a half-finished task means, and who gets to change its state when the process comes back up.&lt;/p&gt;

&lt;h2&gt;How it used to work&lt;/h2&gt;

&lt;p&gt;The original queue was an in-memory list. You ticked the tasks you wanted to run on screen, the order was the order you'd selected them in, and the system processed them one by one.&lt;/p&gt;

&lt;p&gt;It worked as long as nothing interrupted it. With two serious problems.&lt;/p&gt;

&lt;p&gt;The first is the one above: every time I wanted to deploy an improvement to the orchestrator, I had to choose between waiting for the queue to drain or losing the plan.&lt;/p&gt;

&lt;p&gt;The second, quieter one: &lt;strong&gt;the first failure aborted the whole queue&lt;/strong&gt;. An error in the third of ten tasks cancelled the remaining seven. And those seven had nothing to do with the one that failed: they were simply behind it in line.&lt;/p&gt;

&lt;h2&gt;The redesign&lt;/h2&gt;

&lt;p&gt;The queue moved to the database, with one design decision I particularly like: &lt;strong&gt;position is a real number, not an integer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That allows inserting between two elements without renumbering the whole list. If you want to slot something between positions 3 and 4, you assign it 3.5. It's an old technique and it solves at the root the problem of reordering a persisted list without writing fifty rows every time someone drags one.&lt;/p&gt;

&lt;p&gt;The order is curated by hand, mixing fixes, features and audits into whatever sequence makes sense that day. You reorder by dragging, as long as tasks are still waiting. Whatever is running is pinned and locked, which is the bare minimum: you can't reorder something that already started.&lt;/p&gt;

&lt;h2&gt;What to do when a task fails&lt;/h2&gt;

&lt;p&gt;There was a product decision disguised as a technical detail here, and I resolved it by not resolving it: &lt;strong&gt;I made it a per-run option&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can choose for the queue to carry on with the rest, flagging the failed one, or to stop and leave everything else waiting. Both positions are defensible depending on the case. If you're processing ten independent fixes, you want it to continue. If the third failed because the environment broke, you want it to stop before the remaining seven fail for the same reason.&lt;/p&gt;

&lt;p&gt;Later I added a stricter rule that turned out to be the most useful in practice: &lt;strong&gt;the queue doesn't resume past an unresolved failure&lt;/strong&gt;. You can continue, but someone has to have looked at that failure and decided what to do about it. Without that, the queue stays put.&lt;/p&gt;

&lt;p&gt;It's deliberately inconvenient. The alternative — always continue — lets failures pile up unseen, which is exactly the problem behind the 1,771 unprocessed incidents I described a few entries back.&lt;/p&gt;

&lt;h2&gt;A single execution mechanism&lt;/h2&gt;

&lt;p&gt;A side effect of the redesign I hadn't anticipated: the "run everything" and "repair everything" buttons stopped being separate mechanisms.&lt;/p&gt;

&lt;p&gt;Before, each was its own pipeline, with its own advancement logic and its own bugs. Now they're simply shortcuts that bulk-enqueue with a preset order and start the queue. One execution mechanism, all of it visible and reorderable.&lt;/p&gt;

&lt;p&gt;It's the kind of simplification that appears on its own once the right abstraction finally exists. While the queue was ephemeral, having parallel pipelines seemed reasonable. With a persistent, curatable queue, it makes no sense at all.&lt;/p&gt;

&lt;h2&gt;Actually surviving a restart&lt;/h2&gt;

&lt;p&gt;Persisting the queue solves half the problem. The other half is what happens to whatever was running &lt;em&gt;at the moment&lt;/em&gt; of the restart.&lt;/p&gt;

&lt;p&gt;That task lands in limbo: the database says it's in progress, but the process executing it no longer exists. If nobody does anything, it sits there forever, occupying the head of the queue and blocking everything behind it.&lt;/p&gt;

&lt;p&gt;In August I added the two missing pieces: &lt;strong&gt;graceful shutdown&lt;/strong&gt;, so in-flight runs are marked before dying, and &lt;strong&gt;startup reconciliation&lt;/strong&gt;, which checks what's declared as running with no process behind it and resolves it.&lt;/p&gt;

&lt;p&gt;And a rule that seems obvious and took me a while to find: &lt;strong&gt;re-read the task's status before launching its row&lt;/strong&gt;. Because between enqueuing and its turn arriving, anything could have happened — the task might have been completed by another path, or postponed. Launching blind against a stale snapshot is a constant source of duplicated work.&lt;/p&gt;

&lt;h2&gt;Why this sums up the whole project&lt;/h2&gt;

&lt;p&gt;Of everything I built over these months, the persistent queue is what best captures the thesis: &lt;strong&gt;in a system with agents, the state of in-flight work matters as much as the work itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An in-memory queue works perfectly until the first restart, and the first restart always arrives at the worst moment. It isn't an optimisation: it's the difference between a tool you use while you're watching and a system that runs when you're not.&lt;/p&gt;

&lt;p&gt;Today the queue is the system's only execution path. There's no way to run a task outside it, and that — which felt like an annoying restriction at first — turned out to be what makes everything else observable.&lt;/p&gt;

&lt;p&gt;With the queue surviving restarts, the system could run on its own for hours with me nowhere near it. And that raised a question I'd never asked, because I'd never let it run that long: how long to let it think before interrupting. That's what the next entry is about.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>sqlite</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>Verifying two lines from the agent cost me an hour</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Wed, 23 Sep 2026 18:01:19 +0000</pubDate>
      <link>https://dev.to/gleyendeker/when-every-check-blocks-verifying-a-small-change-costs-an-hour-2an0</link>
      <guid>https://dev.to/gleyendeker/when-every-check-blocks-verifying-a-small-change-costs-an-hour-2an0</guid>
      <description>&lt;p&gt;The agent closes a two-line task. Verifying it hadn't broken anything took me close to an hour.&lt;/p&gt;

&lt;p&gt;And not one check was superfluous. I'd added the end-to-end tests because they caught a real bug. The security review, because it caught another one. Each had earned its place, and for exactly that reason none of them could be skipped.&lt;/p&gt;

&lt;p&gt;It's a natural slope: you add a check because it helps; because it helps, you don't want it skipped; so you make it mandatory. Repeat that five times over three months and you end up with an hour of checks hanging off every task, however small.&lt;/p&gt;

&lt;p&gt;What unblocked it wasn't deleting checks or making them faster. It was changing the question: not &lt;em&gt;is this check useful?&lt;/em&gt; — they all were — but &lt;strong&gt;what decision does it block?&lt;/strong&gt;. Some have to stop the task. For others, stopping the deploy is enough.&lt;/p&gt;

&lt;h2&gt;The expensive part was the interface review&lt;/h2&gt;

&lt;p&gt;Within the gate, the slowest check was the model-assisted interface review: an agent that opens the browser, walks the application and judges whether what was implemented looks and behaves as it should.&lt;/p&gt;

&lt;p&gt;It's valuable. It catches things no automated test detects. And it's enormously expensive in time, because it means bringing up the environment, navigating, waiting on every screen and reasoning about what it sees.&lt;/p&gt;

&lt;p&gt;Running that on every task was the equivalent of asking a designer to review the entire application every time someone changes a line of code.&lt;/p&gt;

&lt;h2&gt;The right question isn't whether the check is worth it&lt;/h2&gt;

&lt;p&gt;The change of question reordered everything, and the reason is economic before it is conceptual: a control that stops a task from closing and one that stops a deploy to production are not the same thing. Their waiting costs are completely different. The first is paid dozens of times a week; the second, once per release. Mixing them is what leads a team to eventually disable both.&lt;/p&gt;

&lt;p&gt;That's where the two tiers came from.&lt;/p&gt;

&lt;h2&gt;Tier 1: what blocks each task&lt;/h2&gt;

&lt;p&gt;Fast, deterministic and per task. Three things: the full backend suite, the end-to-end tests &lt;em&gt;belonging to that task&lt;/em&gt; running headless, and a security trace.&lt;/p&gt;

&lt;p&gt;The key part is "belonging to that task". Not the full end-to-end suite: only the specs covering what was just touched. That's what takes the time from an hour down to minutes.&lt;/p&gt;

&lt;h2&gt;Tier 2: what blocks the deploy&lt;/h2&gt;

&lt;p&gt;Slow, expensive and batched. That's where the full end-to-end suite and the model-driven interface review went, the latter running over tasks Tier 1 didn't cover.&lt;/p&gt;

&lt;p&gt;That batch runs at two moments: before deploying, blocking, and every night as a safety net. If something fails, it doesn't jam the day's work — it jams the release, which is where that control actually matters.&lt;/p&gt;

&lt;p&gt;On screen the split was made visible with two separate columns, one per tier, each with its own state. Visibility matters: if a check doesn't block, it has to be crystal clear that its result still exists and that someone is going to look at it.&lt;/p&gt;

&lt;h2&gt;The delicate part: changing something everything depends on&lt;/h2&gt;

&lt;p&gt;This is the part that gave me the most trouble and the one least often written about.&lt;/p&gt;

&lt;p&gt;The quality gate is a load-bearing piece. The completion steps refuse to mark a task as done if the gate didn't pass. And that pass, as it was written, &lt;strong&gt;explicitly required the interface review's result&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Which means: if I did the intuitive thing — pull the interface review out of the per-task gate — the entire system would stop being able to close tasks. Not some of them: all of them. Every completion would start failing while looking for data nobody produced anymore.&lt;/p&gt;

&lt;p&gt;That's why the plan carries a rule written in capitals: &lt;strong&gt;if you stop producing the signal before you stop requiring it, every completion breaks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The implementation order was fixed in five stages, and the first is the counterintuitive one:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;First, decouple the requirement.&lt;/strong&gt; Make the pass stop requiring the interface review. It's a backward-compatible change: if the signal is present it's ignored, if it's absent it no longer blocks. At this point the system behaves exactly as before.&lt;/li&gt;
  &lt;li&gt;Then add the new Tier 1 checks, which is purely additive.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Only at the end&lt;/strong&gt;, make the skill stop producing the per-task signal. It's safe now, because nothing has required it for two steps.&lt;/li&gt;
  &lt;li&gt;The screen columns and the batch runner, which are net-new and don't touch the existing path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the kind of sequence that looks bureaucratic when you write it and saves your day when you execute it. Had I started with the obvious step — removing the interface review — I would have broken task completion across every managed project simultaneously.&lt;/p&gt;

&lt;h2&gt;A detail that avoided a lot of pain&lt;/h2&gt;

&lt;p&gt;Every new check &lt;strong&gt;reads the project's configuration and skips itself gracefully&lt;/strong&gt; when the project doesn't declare what it needs.&lt;/p&gt;

&lt;p&gt;Some managed projects have no end-to-end tests at all. If the gate demanded them, those projects would be permanently stuck behind a requirement that doesn't apply. The rule that stuck is: the gate can't demand what the project doesn't declare it has.&lt;/p&gt;

&lt;p&gt;It sounds obvious written down, but it's the kind of thing you discover when the same mechanism runs across projects of different shapes.&lt;/p&gt;

&lt;h2&gt;The outcome and what followed&lt;/h2&gt;

&lt;p&gt;The goal stated in the plan was to bring the per-task gate down from roughly an hour to minutes, across every project at once. That was achieved.&lt;/p&gt;

&lt;p&gt;And with the system finally running fast, a problem the slowness had been masking came into view: every time I restarted the server, I lost the entire work plan. That's what the next entry is about.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>playwright</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I used my agent's memory 1,112 times and I don't know if it helped</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Mon, 21 Sep 2026 12:31:15 +0000</pubDate>
      <link>https://dev.to/gleyendeker/inyecte-memoria-1112-veces-y-no-se-si-sirvio-para-algo-3f9n</link>
      <guid>https://dev.to/gleyendeker/inyecte-memoria-1112-veces-y-no-se-si-sirvio-para-algo-3f9n</guid>
      <description>&lt;p&gt;Your agent has memory: it stores what went well and what went wrong, and injects it into itself when it starts a new task. It's one of those features you switch on once and never look at again.&lt;/p&gt;

&lt;p&gt;The uncomfortable question is simple: &lt;strong&gt;if you turned it off tomorrow, would you notice?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mine ran &lt;strong&gt;1,112 times in 30 days&lt;/strong&gt;. When I sat down to evaluate it I couldn't say whether it helped, and not for lack of instrumentation. Memory is the one component in the system that never complains, and that's why more than a month went by without my looking at it once.&lt;/p&gt;

&lt;h2&gt;What it actually does&lt;/h2&gt;

&lt;p&gt;The system stores two kinds of thing. Patterns that worked — ways of solving something that produced good results — and known failures, so they aren't repeated. At the time of measuring there were &lt;strong&gt;127 patterns and 180 failures&lt;/strong&gt; on record.&lt;/p&gt;

&lt;p&gt;When an agent starts a task, the system searches that store for whatever resembles the work about to be done and injects it into the context. The search is by trigram similarity, with a threshold of 0.30 and a cap of three results per query.&lt;/p&gt;

&lt;p&gt;It's simple, it's cheap and it runs without errors. That last point turned out to be the problem.&lt;/p&gt;

&lt;h2&gt;The component that never complains&lt;/h2&gt;

&lt;p&gt;Everything else in the system makes it onto my review list because it makes noise. The quality gate fails and stops a task. The watchdog kills a process. An agent runs out of turns. The queue jams. Each of those events produces a signal that forces me to look at it.&lt;/p&gt;

&lt;p&gt;Memory doesn't. It gets injected, the agent works, the task closes. No error, no alert, nothing to review. &lt;strong&gt;It became invisible through good behaviour.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And so a good month went by with over a thousand accumulated injections, without anyone — me — asking whether those injections were improving anything or simply taking up room in every agent's context.&lt;/p&gt;

&lt;h2&gt;The textbook technical answer, and why I didn't take it&lt;/h2&gt;

&lt;p&gt;When I finally sat down to look at it, my first reaction was the obvious one: &lt;em&gt;trigram search is primitive, this should move to embeddings&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It's true that it's primitive. It compares character sequences, not meaning. It's the kind of retrieval anyone would replace today with a vector search, and there are libraries to do it in an afternoon.&lt;/p&gt;

&lt;p&gt;I didn't, and I think that was the right call. &lt;strong&gt;Improving retrieval without an impact metric is betting blind, and paying for the privilege&lt;/strong&gt;: embeddings carry a per-query cost, so I'd be spending more to improve something I don't even know contributes anything.&lt;/p&gt;

&lt;p&gt;The right question wasn't "how do I make retrieval better?" but "is retrieval making any difference?". And I had never asked that second one.&lt;/p&gt;

&lt;h2&gt;The infrastructure to measure it already existed&lt;/h2&gt;

&lt;p&gt;Here's what surprised me most: I could measure it, and I didn't know.&lt;/p&gt;

&lt;p&gt;Every memory injection records the identifier of the session it happened in. That, which I'd added without thinking about it much, enables a natural experiment: &lt;strong&gt;there are sessions with memory injected and sessions without&lt;/strong&gt;, on the same system, over the same period, doing comparable work.&lt;/p&gt;

&lt;p&gt;It's an A/B that's been running for months. All that was missing was looking at it.&lt;/p&gt;

&lt;p&gt;The cross-reference to make is against two things also on record: each session's incidents and the quality gate's outcome. The questions are concrete. Do sessions with memory injected repeat fewer reads of the same file? Do they run fewer searches that find nothing? Do they pass on the first attempt more often?&lt;/p&gt;

&lt;p&gt;If the answer is yes, memory helps, and only then does investing in better retrieval make sense — with the cost justified by a measured effect.&lt;/p&gt;

&lt;h2&gt;The question almost nobody asks&lt;/h2&gt;

&lt;p&gt;But there's a third possible outcome, and it's the one I find most interesting: &lt;strong&gt;that memory doesn't move the needle at all&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If that were the case, the correct conclusion isn't to improve it. It's the opposite: raise the similarity threshold, lower the result cap, inject less. Because every injection carries a cost that is certain — it consumes context, and context is finite — against a benefit that would be zero.&lt;/p&gt;

&lt;p&gt;That's the uncomfortable part of measuring seriously: one of the possible outcomes is having to dismantle something that took work to build. I suspect the resistance to measuring comes largely from there, not from technical difficulty.&lt;/p&gt;

&lt;h2&gt;The criterion I took away&lt;/h2&gt;

&lt;p&gt;A component that never fails isn't the same as a component that helps. They're two different things and they're easy to confuse, because day to day you operate by exception: you look at whatever complains.&lt;/p&gt;

&lt;p&gt;Now, before adding any new piece to the system, I try to answer up front: &lt;strong&gt;what would the world look like if this piece didn't exist?&lt;/strong&gt; If I can't answer that, it isn't that the piece is wrong — it's that I'll be stuck with it, unable to ever justify it.&lt;/p&gt;

&lt;p&gt;With memory I arrived at that question late. I'm 1,112 injections in and still don't have the answer.&lt;/p&gt;

&lt;p&gt;By July the system was running fast and verifying well, but every task still dragged close to an hour of checks behind it before it could close. The problem was no longer what to check, but what should block the work and what shouldn't: that's what the next entry is about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>rag</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your agent says the tests pass. Did it run them?</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Mon, 14 Sep 2026 12:31:16 +0000</pubDate>
      <link>https://dev.to/gleyendeker/cachear-la-suite-de-tests-sin-certificar-un-verde-falso-3pjf</link>
      <guid>https://dev.to/gleyendeker/cachear-la-suite-de-tests-sin-certificar-un-verde-falso-3pjf</guid>
      <description>&lt;p&gt;Your agent finishes a task and tells you the tests pass. It's worth asking whether it ran them or is reading the result from the previous run.&lt;/p&gt;

&lt;p&gt;Caching that result when the code hasn't changed is an obvious optimisation: an afternoon's work and the pipeline flies. I did it the same day it occurred to me.&lt;/p&gt;

&lt;p&gt;What nobody writes down is what you're asserting when you do: that the code that was tested is the code you're about to ship. When that doesn't hold, the system doesn't get slow. It signs off on code that was never tested, and you don't find out.&lt;/p&gt;

&lt;p&gt;What looked like a trivial change ended up resting on two conditions, plus a marker that makes it auditable afterwards. Miss any of the three and the cache stops being an optimisation.&lt;/p&gt;

&lt;h2&gt;The starting point&lt;/h2&gt;

&lt;p&gt;The gate ran its checks serially, one after another. The full suite, with a 40-minute timeout. Then each frontend build, at 5 minutes apiece. And finally mutation analysis, at 20.&lt;/p&gt;

&lt;p&gt;That 40-minute timeout says a lot: back in May I had raised it from 10 to 40, because the suite no longer fit. The curve pointed in one direction only.&lt;/p&gt;

&lt;p&gt;But duration wasn't the worst of it. This was: when a task failed the gate and had to be retried touching only the frontend, the re-verification &lt;strong&gt;ran the entire backend suite again&lt;/strong&gt;, which hadn't been modified at all. Minutes of compute to confirm something we already knew.&lt;/p&gt;

&lt;h2&gt;Two changes, one obvious and one delicate&lt;/h2&gt;

&lt;p&gt;The obvious one: run the suite and the frontend builds &lt;strong&gt;in parallel&lt;/strong&gt; instead of serially. There's no reason for them to wait on each other beyond the fact that's how it was written.&lt;/p&gt;

&lt;p&gt;The delicate one: &lt;strong&gt;cache the green result&lt;/strong&gt; of the suite by the combination of project, repository and commit hash. If we already ran the tests against exactly this code and they passed, there's no need to run them again.&lt;/p&gt;

&lt;p&gt;Put that way it sounds harmless. This is exactly where the cache stops being an optimisation: from this point on, the green the system reports doesn't come from running the tests. It comes from trusting that the code is the same.&lt;/p&gt;

&lt;h2&gt;The two conditions holding the promise up&lt;/h2&gt;

&lt;p&gt;That's why the cache has two locks, and neither is optional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The working tree has to be clean.&lt;/strong&gt; If there are uncommitted changes, the commit hash no longer describes what's on disk. The code about to be evaluated isn't the code that was tested. In that case the cache isn't even consulted: the suite runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The result is valid for 24 hours.&lt;/strong&gt; Not because code changes on its own, but because the environment does: dependencies, containers, test databases, external services. A green from a week ago says very little about today's state.&lt;/p&gt;

&lt;p&gt;There's a third, smaller detail I like: the suite's exclusion lock — the one preventing two runs from stepping on each other — &lt;strong&gt;is only acquired if something is actually going to run&lt;/strong&gt;. When the cache hits, the function returns before reaching that line. Without it, we'd be serialising runs that will never happen.&lt;/p&gt;

&lt;h2&gt;The marker that makes auditing possible&lt;/h2&gt;

&lt;p&gt;This is the part I consider most important in the whole implementation, and the one I see written down least often elsewhere.&lt;/p&gt;

&lt;p&gt;Every result coming from the cache &lt;strong&gt;travels flagged as such&lt;/strong&gt; inside the quality gate's data. It's on record that this task didn't run the tests: it leaned on an earlier run.&lt;/p&gt;

&lt;p&gt;What's that for? For the day a green shows up that shouldn't have. On that day I'll be able to reconstruct exactly which tasks leaned on the cache and which genuinely ran, and check whether that's where the problem was.&lt;/p&gt;

&lt;p&gt;Without that marker, a false green is indistinguishable from a legitimate one. You have a bug you can't even scope, because you don't know which universe of tasks to search.&lt;/p&gt;

&lt;p&gt;It's a principle I now apply to any optimisation replacing a real check with an inference: &lt;strong&gt;every automated promise needs to leave the marker that lets you audit it when it breaks&lt;/strong&gt;. Because it will break eventually, and on that day the marker is all you have.&lt;/p&gt;

&lt;h2&gt;What was deliberately left out&lt;/h2&gt;

&lt;p&gt;Mutation analysis — which checks whether the tests actually detect changes in the code, not merely whether they pass — was kept out of the cache on purpose.&lt;/p&gt;

&lt;p&gt;The reason is that it measures something different. The suite is evaluated against the state of the repository; mutations are evaluated against &lt;strong&gt;the specific task's diff&lt;/strong&gt;. Two tasks on the same base commit can have completely different diffs, so caching by commit hash would make no sense: they're orthogonal.&lt;/p&gt;

&lt;p&gt;What I did add was a short-circuit from the other side: if the task's diff contains no mutable code, the analysis simply doesn't run. There's nothing to mutate. That resolved a large share of cases without touching the underlying logic.&lt;/p&gt;

&lt;h2&gt;The outcome&lt;/h2&gt;

&lt;p&gt;Between the parallelism and the cache, the quality gate stopped being the obvious bottleneck it had been. It's still the most expensive step in the system — that 29% of the bill didn't move as much as I expected — but the wait per task dropped noticeably.&lt;/p&gt;

&lt;p&gt;What stayed with me above all was a criterion I've reused several times since: speeding up a check is fine as long as you can prove afterwards that the check existed. If the speed-up leaves you unable to audit what was verified and what wasn't, you're not optimising — you're loosening the check and calling it something else.&lt;/p&gt;

&lt;h2&gt;The front left open&lt;/h2&gt;

&lt;p&gt;That same June review left another item on the table, one that wasn't about speed and was considerably more uncomfortable.&lt;/p&gt;

&lt;p&gt;There was a component I'd been using for over a month, running more than a thousand times a month, and I couldn't say whether it improved anything. It never failed. That was exactly the problem.&lt;/p&gt;

&lt;p&gt;That's what the next entry is about.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>devops</category>
      <category>cicd</category>
      <category>programming</category>
    </item>
    <item>
      <title>29% of my bill was in the step I never looked at</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Mon, 07 Sep 2026 12:30:35 +0000</pubDate>
      <link>https://dev.to/gleyendeker/optimice-la-parte-del-pipeline-que-no-habia-que-optimizar-3nal</link>
      <guid>https://dev.to/gleyendeker/optimice-la-parte-del-pipeline-que-no-habia-que-optimizar-3nal</guid>
      <description>&lt;p&gt;If you use agents daily you already have an intuition about where the money goes. Mine pointed at the retry: the agent fails a check, starts over from scratch, and you watch it on screen redoing something it had already done.&lt;/p&gt;

&lt;p&gt;I wrote a five-point optimisation plan based on that intuition. Then I went to the database for the baseline I was going to measure the improvement against, and found the retry had fired &lt;strong&gt;exactly once&lt;/strong&gt; in the system's entire history.&lt;/p&gt;

&lt;p&gt;The spend was somewhere else: in the verification that runs before each task closes, which took &lt;strong&gt;$3,463 of the $11,968&lt;/strong&gt; the system spent in total. 29% of the bill in the dullest step there is, the one I never looked at because it always worked.&lt;/p&gt;

&lt;p&gt;The lesson holds for anything you automate with models: what annoys you are the exceptions, because they interrupt you. What costs you is the routine, because it happens every time.&lt;/p&gt;

&lt;h2&gt;The obvious suspect&lt;/h2&gt;

&lt;p&gt;All five points of the plan aimed at the same place: the retry loop.&lt;/p&gt;

&lt;p&gt;The reasoning was solid. When work fails the quality gate and has to be retried, you pay for everything again: another agent spun up, another exploration of the code, another implementation, another gate run. It's by far the most expensive operation in the system. And on top of that it was badly implemented: it discarded the previous session, so the second attempt started with no memory of the first.&lt;/p&gt;

&lt;p&gt;All of that was true. The five diagnostic points were correctly identified. The plan opened with making retries smarter and continued with minor optimisations.&lt;/p&gt;

&lt;p&gt;Then I went to the database to find the baseline number I'd be measuring the improvement against.&lt;/p&gt;

&lt;h2&gt;What the data said&lt;/h2&gt;

&lt;p&gt;I found three things that fit nothing I had written:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;A single item&lt;/strong&gt;, across the system's entire history, had ever produced retry feedback.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Zero surrenders.&lt;/strong&gt; The give-up path, carefully designed with its structured incident report, had never once fired.&lt;/li&gt;
  &lt;li&gt;The first-attempt pass rate was effectively &lt;strong&gt;100%&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The retry loop I was about to optimise barely ran. I had written an improvement plan for the least-travelled path in the system.&lt;/p&gt;

&lt;p&gt;The explanation, once I looked for it, made sense: errors weren't being corrected between orchestrator steps. They were corrected &lt;em&gt;inside&lt;/em&gt; each agent's own loop, before the work ever reached the quality gate. The agent iterated on its own output, caught its mistakes and fixed them before handing anything over. To the orchestrator that's invisible: all it sees is a step that finished cleanly.&lt;/p&gt;

&lt;p&gt;So my intuition wasn't just wrong about the size of the problem. I was looking at a layer of the system where the problem simply didn't happen.&lt;/p&gt;

&lt;h2&gt;The number that reordered the plan&lt;/h2&gt;

&lt;p&gt;The same query surfaced the figure that changed everything. The quality gate — the step that verifies each task before closing it — was running &lt;strong&gt;around 157 times a week, averaging 5 to 8 minutes each time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That was the dominant cost. Not retries, which barely happened: verification, which happened every time.&lt;/p&gt;

&lt;p&gt;And the finding held up over time. Today, looking at the cumulative figures, the quality gate has consumed &lt;strong&gt;3,463 dollars out of the 11,968&lt;/strong&gt; the system has spent in total. Twenty-nine percent of the bill concentrated in one step.&lt;/p&gt;

&lt;p&gt;The plan was rewritten that same afternoon. First, parallelise the gate and cache the test suite. Retry work was demoted to a cheap robustness improvement, for when the worst case actually occurs.&lt;/p&gt;

&lt;h2&gt;Why intuition pointed the wrong way&lt;/h2&gt;

&lt;p&gt;I've thought a fair bit about why I got it so clearly wrong, because the error wasn't analytical: it was perceptual.&lt;/p&gt;

&lt;p&gt;Retries &lt;strong&gt;hurt&lt;/strong&gt;. You see them on screen, they show up as a loop repeating, they feel like waste while they happen. They're visible, noisy and frustrating.&lt;/p&gt;

&lt;p&gt;The quality gate didn't hurt because it was routine. It ran every time, it was part of normal operation, and for exactly that reason it was invisible. Nobody looks twice at a step doing what it's supposed to do.&lt;/p&gt;

&lt;p&gt;Which is why what annoys you most and what costs you most are almost never the same thing. The exception interrupts you and never shows up on the invoice. The routine never bothers you and is the whole invoice.&lt;/p&gt;

&lt;h2&gt;A note on how I recorded the mistake&lt;/h2&gt;

&lt;p&gt;When I rewrote the plan, I didn't delete the original diagnosis. I added a dated note above it with the recalibration: what I had believed, what the data showed, and how that changed the priority.&lt;/p&gt;

&lt;p&gt;I could have rewritten the document cleanly, with the correct diagnosis from the start. I didn't, for a practical reason: the wrong diagnosis still has value. It explains why the system is built the way it is, and it reminds me that the reasoning behind it was sound — reading the code, anyone would have reached the same conclusion.&lt;/p&gt;

&lt;p&gt;A plan that hides its own diagnostic errors becomes an internal marketing document. One that leaves them visible, with the date attached, is a record of how something was learned.&lt;/p&gt;

&lt;h2&gt;Where to start in your own system&lt;/h2&gt;

&lt;p&gt;The order isn't guessed, it's queried. Before writing an optimisation plan, list your pipeline's steps with two columns — what each run costs and how many times it runs per week — and sort by the product of the two. Frequency is what surprises you, almost never unit cost: the wildly expensive step that runs once a month isn't your problem.&lt;/p&gt;

&lt;p&gt;The first task in the reordered plan sounded trivial: stop running the full suite when the code hadn't changed. It had a trap that nearly had me certify as passing a codebase that was never tested, and that's what &lt;a href="https://leyendeker.com/en/blog/caching-your-test-suite-is-easy-caching-it-without-lying-is-not/" rel="noopener noreferrer"&gt;the next entry&lt;/a&gt; is about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>performance</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>I asked the AI to fix its own instructions and it barely helped</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Mon, 31 Aug 2026 12:30:30 +0000</pubDate>
      <link>https://dev.to/gleyendeker/le-pedi-al-sistema-que-se-mejorara-solo-junte-1771-incidentes-y-13-propuestas-3nnm</link>
      <guid>https://dev.to/gleyendeker/le-pedi-al-sistema-que-se-mejorara-solo-junte-1771-incidentes-y-13-propuestas-3nnm</guid>
      <description>&lt;p&gt;Every time the system gave up on a task, it left a record of what it had tried and why it failed. One day I looked at the pile of those reports and thought the obvious thing: &lt;strong&gt;the AI can read this and fix its own instructions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I built the whole thing, with one rule from day one: the routine edits nothing on its own. It proposes the change, I approve it with a click.&lt;/p&gt;

&lt;p&gt;It works. I collected 1,771 recorded failures over three months, it produced 13 proposed fixes, and the system barely improved through that path.&lt;/p&gt;

&lt;p&gt;The model didn't fail and neither did the human approval. Something far more boring did, and it's a mistake that repeats in any system that tries to learn from its own logs.&lt;/p&gt;

&lt;h2&gt;The case that started it&lt;/h2&gt;

&lt;p&gt;It wasn't an abstract idea: it came from a concrete, repeated problem.&lt;/p&gt;

&lt;p&gt;One of the system's skills had a habit of ending its runs with a question. Something along the lines of "shall I continue with the next part?". To the orchestrator, a run that ends waiting for input is a stuck run: it aborted the whole sequence.&lt;/p&gt;

&lt;p&gt;I fixed it by hand, editing that skill's instructions. And while doing it I thought the obvious thing: this pattern is on record. The system knows this skill failed for this reason, several times over. Why do I have to be the one who notices?&lt;/p&gt;

&lt;p&gt;The goal ended up written like this: replace the cycle &lt;em&gt;human detects, human edits, human commits&lt;/em&gt; with &lt;em&gt;the system detects, proposes a patch, the human approves with one click&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;The constraint I set on day one&lt;/h2&gt;

&lt;p&gt;Before writing a line, I decided something I still consider the best decision in the whole subsystem: &lt;strong&gt;the routine never edits files or commits on its own&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Its output isn't a change, it's a &lt;em&gt;proposal&lt;/em&gt;: the diff plus the reasoning behind it. It's stored in the database and waits. A human approves it and only then is it applied, or rejects it and it's archived.&lt;/p&gt;

&lt;p&gt;The temptation to skip that is strong, because manual approval is precisely the bottleneck you're trying to remove. But a system that modifies its own instructions without supervision has no brake at all: if the criteria it judges by are wrong, every iteration makes them worse, and there's nothing outside the loop to catch it.&lt;/p&gt;

&lt;h2&gt;Four phases, all implemented&lt;/h2&gt;

&lt;p&gt;The design ended up in four parts, and all four are running:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A &lt;strong&gt;unified incidents table&lt;/strong&gt;, consolidating signals that previously lived scattered: orchestrator surrenders, skills that stall waiting for input, pre-flight checks that fail.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Recurring-pattern detection&lt;/strong&gt;: it groups by skill and reason, and flags anything appearing three or more times in seven days.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;routine that proposes the patch&lt;/strong&gt;, which decides whether the fix belongs in the skill's instructions or in code, computes the diff and persists it.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;approval interface&lt;/strong&gt;, with a 24-hour limit so the same proposal doesn't come back over and over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is broken. It all runs.&lt;/p&gt;

&lt;h2&gt;The numbers, three months later&lt;/h2&gt;

&lt;p&gt;When I went to measure the outcome, this is what I found:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;1,901 incidents recorded&lt;/strong&gt; in total.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;1,771 of them still open&lt;/strong&gt;, unprocessed.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;13 proposals&lt;/strong&gt; generated across the system's entire history. Seven approved, six rejected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thirteen. In three months, with nearly two thousand incidents of raw material.&lt;/p&gt;

&lt;p&gt;And what stings most is that the most frequent incidents are exactly the ones you'd want to turn into rules. Over fourteen days: &lt;strong&gt;351 repeated reads of the same file&lt;/strong&gt;, &lt;strong&gt;154 searches that found nothing&lt;/strong&gt; and &lt;strong&gt;151 failed commands&lt;/strong&gt;. All actionable patterns. All sitting there, unprocessed.&lt;/p&gt;

&lt;h2&gt;Where it failed, which isn't where you'd expect&lt;/h2&gt;

&lt;p&gt;My first reaction was to assume the model wasn't up to the task. That's the comfortable explanation, and it was wrong.&lt;/p&gt;

&lt;p&gt;The loop doesn't fail at the intelligent part. It fails in the plumbing. The hypotheses I still have open — and haven't finished ruling out — are three, and none of them involve the model's capability:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;That the proposal routine is barely firing at all, because of how its scheduling ended up configured.&lt;/li&gt;
  &lt;li&gt;That the recurring-pattern view isn't grouping correctly, so it finds nothing to report.&lt;/li&gt;
  &lt;li&gt;That the threshold of three occurrences in seven days is unreachable — not because patterns don't repeat, but because there's so much background noise that the real cases get diluted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one interests me most, because it implies the problem isn't detection but &lt;strong&gt;signal quality&lt;/strong&gt;. If the inefficiency detector produces false positives at scale, it doesn't matter how good the next stage is: you're asking it to find patterns inside a pile of noise.&lt;/p&gt;

&lt;p&gt;So the next step isn't improving the routine that proposes patches. It's triaging the 1,771 open incidents and answering something more basic: &lt;strong&gt;how many are real signal and how many are noise from the detector itself&lt;/strong&gt;. If the bulk is noise, detection has to be tightened before asking anyone for proposals.&lt;/p&gt;

&lt;h2&gt;What I took from this&lt;/h2&gt;

&lt;p&gt;A self-improvement loop doesn't fail for lack of intelligence. It fails in the plumbing, which is the part nobody shows in demos.&lt;/p&gt;

&lt;p&gt;Recording signals is easy and cheap, which is why it's the first thing you build. It feels productive: the numbers go up, the table fills, it looks like the system is learning. Turning that signal into action is the real work, and it's exactly where the project stalled.&lt;/p&gt;

&lt;p&gt;Accumulating 1,771 incidents without processing them isn't telemetry. It's an expensive log that looks like a dashboard.&lt;/p&gt;

&lt;p&gt;I now apply the lesson before instrumenting anything new: if I can't say what decision someone — or something — will make with that data, the instrumentation can wait.&lt;/p&gt;

&lt;h2&gt;The second time the data contradicted me&lt;/h2&gt;

&lt;p&gt;This was the first result that forced me to go look at the database instead of trusting what I thought was happening.&lt;/p&gt;

&lt;p&gt;A month later I did the same with the rest of the system, in a general review aimed at improving pipeline speed. I had a diagnosis put together and a plan written on top of it.&lt;/p&gt;

&lt;p&gt;The data made me throw that plan out the same day. That's what the next entry is about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Your agent fails and sits there waiting for you</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Sun, 23 Aug 2026 09:00:59 +0000</pubDate>
      <link>https://dev.to/gleyendeker/cuando-el-agente-falla-corregir-cambiar-de-enfoque-o-rendirse-1dcl</link>
      <guid>https://dev.to/gleyendeker/cuando-el-agente-falla-corregir-cambiar-de-enfoque-o-rendirse-1dcl</guid>
      <description>&lt;p&gt;You hand the agent a task, go do something else, and when you come back it's sitting there waiting for you: something failed and it didn't know what to do.&lt;/p&gt;

&lt;p&gt;That moment decides whether the agent saves you time or eats it. If the answer when work comes out wrong is "it stops and waits for you", you can leave it alone for ten minutes, never for an afternoon.&lt;/p&gt;

&lt;p&gt;That's what I had by early May. The system could already detect that a piece of work was wrong, and what it did next was nothing. The problem wasn't technical: I had never decided what should happen after a failing verdict.&lt;/p&gt;

&lt;p&gt;And there the options aren't two: &lt;strong&gt;fix what it did, start over with a different approach, or give up and leave a record of what happened&lt;/strong&gt;. Having it written down when each one applies is what separates an experiment from something you can leave running alone.&lt;/p&gt;

&lt;h2&gt;Separating the one who does from the one who judges&lt;/h2&gt;

&lt;p&gt;The first change was structural: explicitly separating two roles. One generates the work, the other evaluates it. This isn't an organisational nicety — it changes what information travels between steps.&lt;/p&gt;

&lt;p&gt;As long as the evaluator returns "passed" or "failed", the only thing you can do with a failure is retry blindly. If instead it returns &lt;strong&gt;structured feedback&lt;/strong&gt;, the system can decide what to do with it.&lt;/p&gt;

&lt;p&gt;Evaluation became multidimensional. Instead of a single verdict, four axes: correctness, security, user experience and completeness, each with its own score. And something that turned out more useful than I expected: &lt;strong&gt;regression detection&lt;/strong&gt; against previous attempts, to tell "still wrong" apart from "worse than before", which are different problems.&lt;/p&gt;

&lt;h2&gt;Three paths, not one&lt;/h2&gt;

&lt;p&gt;With that information in hand, the system picks between three exits. This is the part I think is missing from most orchestrators, where failure handling boils down to retrying.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Refine&lt;/strong&gt;: the work is on track but incomplete or has specific defects. It goes back to the same implementer with feedback about exactly what to fix.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Pivot&lt;/strong&gt;: the work is wrongly framed. Fixing on top of it won't help, because the starting point is the problem. The task is handed back a step up the chain.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Surrender&lt;/strong&gt;: after three attempts, the system stops pushing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Surrendering isn't failing silently, which would be the worst of both worlds. It writes a &lt;strong&gt;structured incident report&lt;/strong&gt;: what was attempted, with what feedback, which dimensions failed each time. That report is stored and becomes material to review later.&lt;/p&gt;

&lt;p&gt;In fact, looking at the pile of those accumulated reports is how I came up with the most ambitious idea of the project, which is the subject of the next entry and turned out considerably worse than I expected.&lt;/p&gt;

&lt;h2&gt;The mistake that took me a month to see&lt;/h2&gt;

&lt;p&gt;There's a detail in this implementation that was wrong for a full month, and it illustrates how easy it is to waste context without noticing.&lt;/p&gt;

&lt;p&gt;When the system decided to &lt;em&gt;refine&lt;/em&gt;, it relaunched the implementer. But in doing so it &lt;strong&gt;discarded the previous session&lt;/strong&gt;. The agent started from scratch: no memory of which files it had touched, which paths it had ruled out, or why it made the decisions it made. All it received was a block of text with the evaluator's feedback.&lt;/p&gt;

&lt;p&gt;Put another way: I was asking it to fix work it didn't remember doing.&lt;/p&gt;

&lt;p&gt;The fix, once I finally saw it, was one line: &lt;strong&gt;keep the session when the decision is to refine&lt;/strong&gt;, and discard it only when pivoting, which is the one case where forgetting is deliberate — there you want the model starting without the bias of the failed attempt.&lt;/p&gt;

&lt;p&gt;What makes that safe is a limit that already existed: since surrender cuts in at the third attempt, a refine with a live session can happen at most twice. Context doesn't grow indefinitely; it has a structural ceiling.&lt;/p&gt;

&lt;h2&gt;Telling apart failures that aren't the agent's&lt;/h2&gt;

&lt;p&gt;Something else I learned while operating this: not every failure is the worker's fault.&lt;/p&gt;

&lt;p&gt;When the provider's API returned a network error, the system counted it as a failed attempt. The agent had done nothing wrong — it hadn't even started — but one of its three attempts was consumed. With two consecutive network blips, a perfectly healthy task reached surrender without anyone having evaluated anything.&lt;/p&gt;

&lt;p&gt;Today those errors don't consume an attempt and are retried with backoff. Penalising the model for an infrastructure outage is, quite simply, measuring the wrong thing.&lt;/p&gt;

&lt;h2&gt;The fourth path&lt;/h2&gt;

&lt;p&gt;In August I added one more exit, for a case I hadn't foreseen: the impossible pivot.&lt;/p&gt;

&lt;p&gt;Sometimes the evaluator determines the work is wrongly framed, but handing it a step up makes no sense because that previous step doesn't exist or is already exhausted. Before, that fell straight through to surrender. Now, instead of giving up, the system &lt;strong&gt;re-explores the terrain&lt;/strong&gt;: it relaunches the code reconnaissance to rebuild context and retries from there.&lt;/p&gt;

&lt;p&gt;It's an example of something that recurs throughout the project: failure policies aren't designed well up front. They're discovered by operating, when the case appears that doesn't fit any of the exits you'd anticipated.&lt;/p&gt;

&lt;h2&gt;What this buys you&lt;/h2&gt;

&lt;p&gt;A system with agents needs a failure policy as explicit as its happy path. And that policy has to distinguish three things that look alike at first: work that's close and deserves another pass, work that's wrongly framed and needs to go back, and work that has to be handed to a person.&lt;/p&gt;

&lt;p&gt;Retrying without that distinction is burning money: you repeat the same mistake with the same information and expect a different outcome. And failing to separate surrender from the rest is worse still, because you lose the one case that actually deserved your attention.&lt;/p&gt;

&lt;p&gt;With the three exits implemented, the system started resolving most of its own stumbles on its own. And what settled at the bottom were the reports of the ones it couldn't.&lt;/p&gt;

&lt;p&gt;That pile of reports was the starting point for what comes next: asking the system to improve itself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your agent can exit clean without writing a single line</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Sun, 16 Aug 2026 09:00:36 +0000</pubDate>
      <link>https://dev.to/gleyendeker/como-saber-si-el-agente-hizo-lo-que-dice-que-hizo-50dc</link>
      <guid>https://dev.to/gleyendeker/como-saber-si-el-agente-hizo-lo-que-dice-que-hizo-50dc</guid>
      <description>&lt;p&gt;Try this with whatever agent you use: give it a concrete task and, when it reports back that it's done, don't believe it. Go read the diff.&lt;/p&gt;

&lt;p&gt;An agent can explore the repository, reason about the problem, write a convincing summary of what it would do, and exit with status zero without touching a file. It isn't lying: as far as it's concerned the run went fine. What lies is the criterion you're measuring it with.&lt;/p&gt;

&lt;p&gt;That criterion — &lt;strong&gt;if the process returned no error, the task is done&lt;/strong&gt; — is the one my system had in April, when it was already closing tasks one after another. Written out like that it sounds reasonable. In practice it's taking the suspect's word for it and filing it as evidence.&lt;/p&gt;

&lt;p&gt;Four months and 2,095 tasks later it's still the hardest problem in the system, and I have no reason to think it's only mine.&lt;/p&gt;

&lt;h2&gt;The evidence is in the repository, not in the summary&lt;/h2&gt;

&lt;p&gt;The idea was simple and it changed the whole system: &lt;strong&gt;to mark something as done, you have to find evidence in the repository&lt;/strong&gt;. Not read a claim in the agent's output. Find commits.&lt;/p&gt;

&lt;p&gt;It sounds like a minor detail. It isn't, because it flips the burden of proof. Before, the system assumed work was done unless something failed. From that day on, it assumed work wasn't done until a real change showed up in the code.&lt;/p&gt;

&lt;p&gt;Three days later I tightened the criterion further: the quality gate, until then a step you could skip, became mandatory between implementation and completion. It was no longer an optional check that ran when I remembered. Without a passing gate, the task didn't close.&lt;/p&gt;

&lt;h2&gt;The holes that showed up afterwards&lt;/h2&gt;

&lt;p&gt;What's interesting about this problem is that it wasn't solved by those two changes. It was solved — partially — over four months, and every hole I plugged taught me something about the difference between verifying and appearing to verify.&lt;/p&gt;

&lt;p&gt;In early May I added a check comparing &lt;strong&gt;the files the spec declared it would touch against the ones that actually changed&lt;/strong&gt; in the commits. Because finding "a commit" isn't enough: the agent might have committed something, somewhere, with nothing to do with what was asked.&lt;/p&gt;

&lt;p&gt;That same day a case I hadn't anticipated appeared: an old gate verdict certifying new work. The task had passed the gate on an earlier attempt, was then modified, and the stored verdict still said everything was fine. The rule I added was to require &lt;strong&gt;a fresh gate&lt;/strong&gt; before marking anything as done.&lt;/p&gt;

&lt;p&gt;And there was a scoping problem, duller but just as important: the commit search only looked at the main repository. Since changes are spread across the backend and the various frontends, there were perfectly real implementations the system couldn't find, and tasks that got stuck for no reason. The search had to be extended to every repository in the workspace.&lt;/p&gt;

&lt;h2&gt;Four months on, the problem still surfaces&lt;/h2&gt;

&lt;p&gt;This is the part I least expected when I started.&lt;/p&gt;

&lt;p&gt;On August 3rd — nearly four months after the first check — I found a new case: &lt;strong&gt;the gate was certifying tasks it had never actually looked at&lt;/strong&gt;. It wasn't that verification failed; it was that on a certain code path verification never ran, and the result defaulted to passed.&lt;/p&gt;

&lt;p&gt;That's the kind of bug that worries me most, because it's invisible. A check that fails makes noise. A check that doesn't run and returns green is indistinguishable from one that ran correctly, unless you go looking for it specifically.&lt;/p&gt;

&lt;p&gt;Nine days later I took the most recent step: each task's implementation stage &lt;strong&gt;stopped being declared by what the system believes&lt;/strong&gt; and started being derived from already-verified change evidence. In other words, state is no longer something written when a step finishes; it's something computed from what can be proven.&lt;/p&gt;

&lt;p&gt;Looking at the project's full history, this is the only line of work running end to end across all four months. Everything else had a beginning and an end. This didn't.&lt;/p&gt;

&lt;h2&gt;Why it's the hardest problem in this space&lt;/h2&gt;

&lt;p&gt;I think there's an underlying reason, and it's that trusting is the default behaviour of almost any system orchestrating agents.&lt;/p&gt;

&lt;p&gt;When you wire a model into a pipeline, what's naturally available to you is the process outcome: it finished, it didn't, it errored. Building real verification — going to the repository, finding commits, cross-referencing files against the spec, confirming the gate ran against this version and not another — is extra work nobody asks for and that goes unnoticed when it works.&lt;/p&gt;

&lt;p&gt;It gets noticed when it's missing. And it gets noticed late, which is the worst part: a wrongly closed task doesn't produce an error, it produces a false foundation for everything built on top of it. The cost shows up weeks later, when something that depended on it doesn't work and nobody understands why.&lt;/p&gt;

&lt;p&gt;So what I took from these four months isn't a technique but a criterion: &lt;strong&gt;trust in an agent isn't configured, it's built from evidence external to the agent&lt;/strong&gt;. And useful evidence isn't that the process finished without error. It's that commits exist, with specific files, matching what was asked, verified after the last modification.&lt;/p&gt;

&lt;p&gt;Everything else is taking the suspect's word for it.&lt;/p&gt;

&lt;h2&gt;What this made possible&lt;/h2&gt;

&lt;p&gt;Once the system could detect that a piece of work was wrong, the next question was exposed, and it was considerably more uncomfortable: &lt;em&gt;now what do I do about it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At that point, a failing verdict simply stopped the chain and waited for me. The agent had failed, the gate had caught it, and the system sat still until I showed up to look at what happened.&lt;/p&gt;

&lt;p&gt;A pipeline that needs you watching isn't worth much. That's what the next entry is about: what the system does when an agent fails, and how to decide between pushing on, changing approach, or giving up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>You're paying a model to work as an if statement</title>
      <dc:creator>Guillermo Leyendeker</dc:creator>
      <pubDate>Thu, 13 Aug 2026 15:53:47 +0000</pubDate>
      <link>https://dev.to/gleyendeker/el-mayor-ahorro-del-sistema-fue-sacarle-trabajo-al-agente-2a5b</link>
      <guid>https://dev.to/gleyendeker/el-mayor-ahorro-del-sistema-fue-sacarle-trabajo-al-agente-2a5b</guid>
      <description>&lt;p&gt;When I built my agent orchestrator, the first step in the chain was this: an agent read a list of tasks and picked which one came next.&lt;/p&gt;

&lt;p&gt;Sounds reasonable until you look at the rule up close. Take the first pending task with all its prerequisites met, mark it in progress. There's nothing ambiguous about that: it's an &lt;code&gt;if&lt;/code&gt;, and I was asking a model to run it.&lt;/p&gt;

&lt;p&gt;I moved it to a script three days in and noted the saving in the commit: around $0.25 and 40 seconds per run. I did the same with the closing step, which decides nothing either: another $0.30 and 20 seconds. Sounds like nothing.&lt;/p&gt;

&lt;p&gt;Four months and 2,095 tasks later, those two scripts add up to more than a thousand dollars and more than thirty hours never spent. And the saving didn't come from a cheaper model or better prompts: it came from giving it fewer things to decide.&lt;/p&gt;

&lt;h2&gt;The first two days were all interface&lt;/h2&gt;

&lt;p&gt;Before that chain there was a screen. I started on April 7th, 2026, and the first thing I built was a server managing several projects at once, with a dashboard in the middle showing which stage everything was in. Looking back at that first week's history, it's almost funny. The width of the sidebar. Tooltips showing full dates on hover. "In progress" badges on each stage. Colours per pipeline stage, so they'd be distinguishable at a glance.&lt;/p&gt;

&lt;p&gt;There's a pair of consecutive commits I particularly like as a portrait of that moment. The first sets an emoji as the icon for the loop button. The second replaces it with a Unicode character, because the emoji ignored the colour I was setting in CSS and always looked the same, regardless of state.&lt;/p&gt;

&lt;p&gt;I don't bring this up to mock myself. I bring it up because it's exactly what a project looks like when you don't yet know what the problem is. I was polishing the system's surface very carefully because the surface was the only thing in front of me. The underlying question — which parts of this flow need a model to decide, and which don't — hadn't even occurred to me.&lt;/p&gt;

&lt;h2&gt;Two steps that became scripts&lt;/h2&gt;

&lt;p&gt;By then the pipeline had taken shape: a chain of steps where an agent picked the next pending task, implemented it, and then marked it complete. All three steps ran through the model, because all three were written as instructions inside the skills I handed it.&lt;/p&gt;

&lt;p&gt;The uncomfortable question came from looking at the first of the three. There's an index of items, each with a status and a list of prerequisites, and the whole rule is: take the first one that's pending with all prerequisites met, mark it in progress, and record the change.&lt;/p&gt;

&lt;p&gt;That day I turned it into a deterministic script. It parses the index, finds the first pending item with its prerequisites in order, marks it in progress and commits the change. It also handles resuming an item that was already underway, which was the only part with any subtlety to it.&lt;/p&gt;

&lt;p&gt;The same day I did the same at the other end of the chain. The step that closes a task — updating the status, recalculating the summary counters, deleting the spec file and committing — also stopped going through the model. It's a sequence of operations over files and state. There's nothing to interpret.&lt;/p&gt;

&lt;h2&gt;The numbers I wrote down at the time&lt;/h2&gt;

&lt;p&gt;What keeps this from being an anecdote is that I measured before and after, and left it written in those commit messages.&lt;/p&gt;

&lt;p&gt;Automatic picking cost roughly &lt;strong&gt;0.25 dollars and 40 seconds&lt;/strong&gt; every time it ran. Completion, roughly &lt;strong&gt;0.30 dollars and 20 seconds&lt;/strong&gt;. As a script, picking became step zero of the pipeline: instant and free, running before any agent was spun up.&lt;/p&gt;

&lt;p&gt;A little under sixty cents and a minute per task. Seen one at a time, it's negligible. That's precisely the problem with this kind of waste: it never hurts enough for you to look at it.&lt;/p&gt;

&lt;p&gt;Four months on, the system has completed &lt;strong&gt;2,095 features and fixes&lt;/strong&gt;. At that rate, those two scripts add up to more than a thousand dollars and over thirty hours of waiting that were never paid. And not because the model was doing its job badly — it was doing it well. It was simply doing a job that didn't require a model.&lt;/p&gt;

&lt;h2&gt;You pay for the model where there's ambiguity&lt;/h2&gt;

&lt;p&gt;The conclusion I drew from those two days is still the criterion I apply to every new step I add to the pipeline. A model earns its keep where something has to be interpreted: understanding a spec written in prose, deciding how to structure a change, judging whether a result is acceptable. Where the rule can be written down, the rule wins.&lt;/p&gt;

&lt;p&gt;And it doesn't win on price alone. It wins for three reasons that weigh more heavily as the system grows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;It's cheaper&lt;/strong&gt;, which is the obvious point and the least important one.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;It's faster&lt;/strong&gt;: a script takes milliseconds where an agent takes tens of seconds, and that gap multiplies across every task in the backlog.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;It's deterministic&lt;/strong&gt;, which is what actually matters. A pipeline that has to run unattended can't have steps that sometimes do one thing and sometimes another. Every point where a model intervenes is a point where the outcome can vary, and that variance compounds along the chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put differently: every deterministic step you take away from the agent isn't just savings, it's a source of uncertainty you remove from the system.&lt;/p&gt;

&lt;h2&gt;Telemetry is what made it visible&lt;/h2&gt;

&lt;p&gt;There's a detail without which none of this would have happened: to claim a step cost twenty-five cents, I first had to measure it.&lt;/p&gt;

&lt;p&gt;That instrumentation — recording cost and duration for every single step — was among the first things I built, and today the system holds &lt;strong&gt;5,698 skill runs and 9,109 recorded steps&lt;/strong&gt;, each with what it cost and how long it took. It's the foundation under nearly every decision I'll describe in this series.&lt;/p&gt;

&lt;p&gt;Without those numbers, the reasoning of that April 10th would have been one more intuition, of the same kind as the ones that later turned out to be wrong. With the numbers, it was a ten-minute decision.&lt;/p&gt;

&lt;h2&gt;Where yours are&lt;/h2&gt;

&lt;p&gt;The exercise requires building nothing: list the steps in your flow and mark the ones with a single correct answer given the input. Picking the next item, moving a state, recalculating a counter, checking whether the prerequisites are met. Every one of those a model resolves today you're paying for twice — in tokens and in variance — to get something a conditional returns identically every time.&lt;/p&gt;

&lt;p&gt;With the deterministic steps out of the way the pipeline started producing at a good pace, and the opposite problem showed up, the one that occupied me for the next four months: the agent would finish, report that it had implemented what was asked, and the only criterion for believing it was that the step hadn't returned an error. That's what &lt;a href="https://leyendeker.com/en/blog/your-agent-can-exit-clean-without-writing-a-single-line/" rel="noopener noreferrer"&gt;the next entry&lt;/a&gt; is about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>architecture</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
