The philosophy

Walls that make sense

What ~3,900 recorded agent runs taught me about getting a deterministic outcome from a stochastic machine.

Dylan · August 2026 · the evidence behind every number here

The first overnight autonomous run was June 9, 2026: a 27-billion-parameter open model, served on my own hardware, left alone with a build loop for a night. It built a 28 KB website from scratch, and it built it perfectly. Then it tried to edit the thing it had just built, and couldn’t. It read the file. It read it again. It kept reading, politely, while making no progress at all.

The obvious conclusion was that the model wasn’t good enough. The correct conclusion was that I had handed it an edit tool that demanded exact character-for-character matches, file reads that silently trimmed long content, and a loop that counted reading as progress. I made the edit tool forgiving, added line-targeted operations, paginated the reads, and added a nudge that fires when reads streak without a change. Same model. Suddenly it edits the way it builds.

Four months and roughly 3,900 recorded runs later, the number of failures where the model itself turned out to be the root cause is zero. Not “few.” Zero. Every run-ending failure traced upstream to a boundary I owned: the product, the harness, or the infrastructure. That statistic is the whole philosophy. The rest of this essay is the mechanics of believing it.

The Plinko problem

When you build an agentic loop, you are entrapping a stochastic probability machine inside a deterministic system that wants a particular output. Think of Plinko. Toss the ball in and it falls, and depending on the rungs, the physics, the air, and the spin you gave it on release, you get one outcome, sampled from all the outcomes that arrangement makes possible.

So how do you get a specific outcome, every time? You funnel. Something like the opposite of progressive disclosure: you structurally remove the ability to land in the wrong hole. You put a wall there, placed so it blocks that hole and no other, angled so the ball bounces back toward the outcome you want. More instructions just adds more rungs; the ball still goes where the geometry sends it. The craft is walls that make sense.

Errors are upstream problems

The same idea applies to failure. When an error shows up, the tempting question is “what do I do to fix this error?” The productive questions are: what caused this upstream? Why would this error occur at all? How do we make it recoverable when it happens? And how do we cut the possibility of it happening out of existence? Over those four months, every fix we shipped was applied at the boundary that owned the failure, one root-caused fix per iteration of the product, and never a patch over the symptom.

The model is not being a jackass

When a model fails outright, or keeps trying to do the thing you don’t want it to do, the model is almost never being a jackass. Most of the time you gave it bad instructions. The rest of the time it ran into an error, and the error just put up a wall with no geometry. An opaque wall doesn’t funnel; it scatters. The ball bounces off in a random direction, and you call the randomness “model error.”

Radiant errors, radiant everything

Tool results are the model’s only sensory organ. In an agentic loop, errors desperately need to not be opaque. And not just errors: every tool result should radiate the context the next step needs. Here is what writing a file returns in Disco:

wrote 1659 bytes to src/engine/state.js

applied — lines 1-40 now read:
 1  // Game state machine — pure logic, no DOM/three.js.
 2
 3  export const GameState = {
 4    MENU: 'menu',
 5    PLAYING: 'playing',
    ⋮
[total lines: 55]
[complete — 1659 bytes, sha256 e9f03157248c. The file now
contains EXACTLY the content you supplied, verbatim; only the
head is echoed above to save context. A read-back to confirm
this write is unnecessary.]

Byte count, the file’s actual first lines with line numbers, the total length, a hash, and an explicit “a read-back is unnecessary.” That last line is a wall. It blocks the verification-read loop before it starts. The bad version of this tool says changes written and nothing else. The model cannot see the file, so it does the only reasonable thing and reads it back to check. Every time. You built that loop, not the model.

Refusals should radiate too. Disco gates risky work behind a plan the user approves before the agent executes it. If the user asks for a change while the agent is mid-step, the approved plan is now stale, so the agent’s next action gets refused. Here is what that refusal looks like from the model’s side:

REFUSED: `shell` was not applied. A change request arrived
while you were mid-step, so this action would have landed on
the OLD, now-stale plan. The build has re-entered PLANNING.
Fold the new request into a REVISED plan and call
`submit_plan`; once it is approved you can apply the change.
You may also read (file_read/file_list/search/extract) or
ask/questions_v2 first.

Why it was refused, what state the system is now in, exactly what to do next, and what remains allowed meanwhile. Compare the version that helps nothing: error: file previously read 2 times, do not attempt to read file again. That error names the behavior and offers no way out. It is a wall with no angle, in front of a model that was reading for a reason.

One caution: radiance has to be relevant. If the model was doing something specific and legitimate, and the error it gets back is generic boilerplate, you have not built a wall. You have created a fresh occasion for thrash. The specificity of the error must match the specificity of the work.

So when thrash occurs, the question is never “what did the model do wrong?” The question is: what can we change to make the model’s job even easier?

A good tool is not niche

Walls are also about what you hand the model in the first place. A good tool fits multiple places and is relevant everywhere, not built for one narrow situation. We made this mistake in an egregious way once: making a plan, rendering a preview, and checking the work were spread across a pile of narrow tools that mostly had to be called in a particular order for the run to succeed. That is a maze wearing a funnel’s name tag. The model’s real job had become guessing our choreography, and guessing is exactly what the loop exists to remove. A good tool is not niche, but it can fill the niche where needed. A handful of wide tools with radiant results beats a drawer full of specialized ones every time.

The kitchen sink is a Plinko board

Which leads to the mistake currently being industrialized: throwing everything at the context window. Thousands of lines of skill files, hundreds of tools and MCP servers. If you want to ruin a model’s reasoning, that is the recipe. Attention is a relevance contest with a fixed budget: every token in the window scores against every other, and the shares have to sum to one. Nothing you put in context is inert. Every schema and every skill file competes for the same finite attention, and sits there as a live option while it does.

Loading in the kitchen sink reintroduces the Plinko problem inside the transformer. Every irrelevant tool is a hole you drilled yourself, and the model can now “reasonably” route into it. It looks relevant precisely because you put it in the window. Presence implies affordance. This all boils down to minimalism. Keep what you actually need; everything else is the bullshit on top.

Incidents come before failures

The last piece I brought from a previous life. In the Navy I maintained an autonomous weapon system that is a close relative of aerospace systems, and it inherits aviation’s safety doctrine: incidents are the precursors of failures. A failure is usually a few incidents chaining together until the sum is catastrophic. So you start with the incidents, because the answers usually live there. Fix the incidents, make them structurally impossible, and the failure rate follows them down.

In an agent loop, an incident is a small oddity in a run that still passed: the model acknowledging a system message as if the user had said it, narrating something confusing out loud, a short burst of tool errors it recovered from. While the final test waves ran, a separate read-only audit collected exactly these oddities from passing runs, and we traced each one upstream and removed its cause. Across three consecutive stretches of the same test suite in August, recorded failures fell from 59 to 8, then to 4. We rarely fixed a failure directly. We fixed its precursors.

Where this lands

The final test wave passed 300 runs out of 300, and the follow-up wave passed 100 out of 100, under a counting rule with no mercy in it: one genuine product failure and the count restarts from zero, on fresh test seeds. Every run was recorded, the results are tied by hash to the sealed evidence that produced them, and the distilled history, with every run indexed and all 253 failures reconstructed, is public.

I built Disco on the belief that people who run models locally are the future, and the evidence kept agreeing with a stronger claim than I started with: capable local models do not need a bigger babysitter. They need better boundaries. Toss the ball in as many times as you like. If it keeps missing, stop blaming the ball. Build a wall that makes sense.

← agenticdisco.app