You ask the agent to build a feature. It produces something. It’s almost right, so you re-prompt. Still not quite, so you re-prompt again. Each pull of the lever feels like progress, and each one feels a little less like engineering. Somewhere in there you stopped building a system and started playing a slot machine.
That reflex — re-roll until it looks right — is the clearest symptom of a problem that has nothing to do with how good the model is. AI didn’t change what good software engineering looks like. It just made it much easier to skip.
First, what “determinism” actually means
Two plain definitions, because the whole argument rests on them.
Determinism means the same input always produces the same output. Think of a pure function: give it 2, it returns 4, every time, forever. (It can still be slow or asynchronous — determinism is about predictability, not speed.)
Non-determinism means the same input can produce different outputs. That does not mean random. It means the output is drawn from a range of possibilities. Ask the model to draft the same email twice and you get two different drafts — both reasonable, neither identical.
This distinction is older than AI. Computer science has drawn it for decades, and the lesson from that older theory is worth keeping: you can always convert an unpredictable machine into a predictable one, but the predictable version is more complex. You don’t remove non-determinism for free — you pay for it in structure. That trade never went away. Large language models just made everyone feel it again.
Slop is code without a model
The easy definition of “slop” is AI writing bad code. That’s wrong, and the correction is the whole point:
Slop is code without a reliable model behind it.
Slop has no clear boundaries. Its rules are implied rather than stated. You can’t inspect its states. Its behaviour is scattered across files, and there’s no safe way to change it. And here’s the part that catches people out — it might work perfectly. Slop is not a synonym for “broken,” or even “bad.” Code can run, pass its tests, ship to production, and still be slop, because slop is about the absence of a model, not the presence of bugs.
That’s what makes the re-prompt reflex so dangerous. When the output isn’t quite right, fixing the underlying structure is hard, and pulling the lever again is easy. So you pull it. It feels like momentum. None of it adds a model. This is the same trap that turns vibe coding sour: you can generate a mountain of working code while your understanding of it never catches up.
Why AI drifts toward slop
Two reasons, both structural — neither is “the model isn’t smart enough.”
It learned from all our code, not the good code. Left unconstrained, an agent doesn’t average toward best practice. It reproduces and amplifies whatever patterns already dominate the codebase it’s working in — good ones and bad ones alike. Point it at a tidy system and it extends the tidiness; point it at a mess and it will confidently, fluently extend the mess.
Agents are obedient, but they don’t have much agency. This is the counterintuitive one. Ask an agent to build something and it goes straight to code. It won’t stop to propose a model first, won’t ask what the states are, won’t push back that the requirements are incomplete — unless you explicitly tell it to. The usual worry about agents is that they’re too autonomous. The real failure is the opposite: nobody handed them a model to work from, so they fill the gaps with assumptions, and a wrong assumption gets amplified through everything built on top of it.
The four ways the model gets left out
AI-driven work tends to exclude the model entirely, and it usually happens through one of four moves:
- One-shotting. Prompt straight to result, skipping the iteration and the human judgement that would have surfaced the real requirements.
- Prose control flow. Trying to encode “always do X, never do Y, then step 1, 2, 3” as natural-language instructions in a skills or
CLAUDE.mdfile — and hoping the agent follows them. It may. It may not. Instructions in prose are not the same as structure in the system. - Agents as the whole system. Throwing more agents at the problem with nothing constraining them. More agents without more structure is just more chaos, faster.
- Entanglement of concerns. The agent declares the goal “done” the moment something works, with no separation between the parts. It’s the AI-era version of junior-developer code: functional, tangled, and frightening to change.
There’s a tempting fifth escape hatch — just add more context. It doesn’t help. More context is not more structure. A bigger window doesn’t substitute for an explicit model, and past a point, stuffing more instructions in just adds noise. (That’s the real lesson of context engineering: what the model can see matters enormously, but a pile of instructions is not a model.)
The fix: deterministic core, agentic shell
Here’s the correction, and it’s a shape rather than a technique.
The common pattern today is an AI that calls programs — the agent sits at the centre as the router, the traffic controller, deciding everything and reaching for tools as it goes. That’s both risky and lazy, because it hands every decision to the one component that’s non-deterministic by nature.
Invert it. Write a program that calls the AI.
- Push non-determinism to the edges — the genuinely fuzzy judgement calls. Drafting text. Filling a gap. Deciding whether a description is complete. These are things there’s no single right answer for, and they’re exactly what the model is good at.
- Keep determinism at the core — the actual transitions, enforced by the surrounding program and not by the model. This action is impossible until that precondition holds. Those are hard rules the AI never gets to override with a confident paragraph.
The surrounding program is the part that guarantees behaviour. The AI is a specialist you call for the fuzzy sub-tasks, not the manager who decides what’s allowed to happen.
What that looks like in practice
Picture an email assistant, modelled as a small set of explicit states:
gather requirements → draft → iterate with you → send
The structure — not the model — decides what can happen next. You cannot send without an approved draft. You cannot draft without complete requirements. Those are enforced by the program. Within each state, the AI does the part it’s actually suited to: is anything missing from these requirements? and what should this draft say? The rails hold; the model fills in the fuzzy content between them.
Notice how much safer this is than an agent that holds the whole flow in its head. There’s no prompt clever enough to make the system send an unapproved email, because “send” simply isn’t reachable from a state without an approved draft. You didn’t ask the model to be careful — you made the unsafe move structurally impossible.
This is the reliable core underneath the more advanced patterns elsewhere on this site: a dynamic workflow that spawns and coordinates many agents is only trustworthy because a deterministic script — not a model’s mood — decides the order, the gates, and what counts as done.
Modeling without ceremony
None of this means formalising everything. The guiding line:
Modeling is not ceremony when it replaces confusion.
Model only the confusing parts of a system, and only where the model earns its place by removing genuine confusion. You also don’t need state machines specifically. They’re one clean way to make the allowed transitions explicit, but the real requirement is just structure — a flowchart, a process diagram, a plain domain model, anything that makes the core logic explicit and keeps it separate from the interface.
The point isn’t the notation. The point is that somewhere in your system there’s an honest answer to “what states exist, and which transitions are allowed” — and that answer lives in the program, not only in the model’s head.
Where to start
You don’t need to re-architect anything. Pick one confusing existing workflow — the one you already re-prompt your way through — and model it explicitly. Then ask three questions:
- What should be deterministic here? Which transitions have to be guaranteed, every time, no exceptions?
- Where is iteration actually useful? Where does a human genuinely belong in the loop, rather than being asked to rubber-stamp?
- Where can the logic be separated from the interface? What’s core behaviour, and what’s just presentation wrapped around it?
That’s the whole practice. Not a framework migration, not a rewrite — one workflow, made legible.
The bigger picture
The instinct when AI output is unreliable is to reach for a better prompt or a bigger context window. Both are treating a structural problem as a wording problem. The answer that actually holds is a model: an explicit account of how the system should behave, sitting at the deterministic core, with the fuzzy work pushed safely out to the edges.
Get that shape right and the agent stops being a slot machine you keep feeding. It becomes what it’s genuinely good at — a fast, tireless specialist you call for the parts that were always going to be judgement calls, inside a system that still knows exactly what it’s allowed to do.