To start things off, I don’t think I’m at the point in my career where I should be blogging. I don’t have years of experience, tons of battles fought, or deep expertise in many topics. But I do share strong opinions that are maybe right at least 51% of the time, and I always ask why.

None of that is why I wrote this. It was a car ride thought. One of those things that keeps looping in your head until you write it down.

So here it is.

The topic

Deterministic outcomes in an agentic probabilistic world.

That’s the thought that wouldn’t leave my head. Let me explain.

In a world where we can easily tell Claude, Codex, Kimi, or whatever sauce you like what to do, why lean into deterministic code?

In short: it’s faster, cleaner, easier to debug, and everyone gets the same result. Not a different result every time like a McDonald’s milkshake from a different location. And the machine is always broken anyway.

The double standard

Think about it. You wouldn’t accept a function that returns a slightly different result every time you call it. You’d file a bug, blame the intern or the senior who retired three years ago, and move on with your life.

But somehow when an agent does it, we call it “creative” and ship it to prod.

Make it make sense.

What agents are actually good at

Agents are amazing at deciding what to do. They’re terrible at being the thing that does it consistently. That’s not a dig, that’s just how probabilistic systems work. You roll the dice, you get a number. Cool.

But I don’t want my event handler rolling dice. When a message comes in, it should be stored and sent to every connected client the same way every time. Same input, same output. Same patterns, same structure, same naming conventions. So when something breaks, you already know where to look because the code looks the same everywhere. Easy to debug, easy to test, easy to hand off to someone who’s never seen the codebase.

The Jenga tower

Because AI makes coding “faster,” people stop thinking about the early decisions. Not even code decisions. Business decisions. Who is the user? What do they actually need? What’s the workflow? Nobody asks because it’s easier to just plop out a demo and call it progress. Wrong assumptions baked in from day one? Whatever, we’ll pivot later. No user research? Who cares, the agent built it in 20 minutes.

Except later never comes because you’re already three features deep on top of a foundation that was wrong from the start. That’s a Jenga tower. Every block you pull is a decision you skipped because the agent made it feel like it didn’t matter.

Now stack that with a non-deterministic system generating non-deterministic code in a non-deterministic environment. Three layers of “well, usually it works” on top of choices nobody thought through because it was fast. That’s not velocity. That’s debt with interest.

Zuckerberg once said “Move fast and break things.” Even Facebook had to walk that back to “Move fast with stability” once they realized speed without thinking just meant breaking things faster.

What I actually believe in

Use AI to build the deterministic machines faster. Generate the code generators. Scaffold the schemas. Write the OpenAPI specs. Draft the emails, write the messages, knock out the stuff that used to eat your day. Not so you can do more work, so you can spend the time you saved making sure what you built is actually correct. Review it. Test it. Confirm it does what it should.

AI gets you to the finish line faster. You use that extra time to make sure you crossed the right one. That’s the value. Do better work, not just more work.

Build the tools, not the slop

We already solved this problem. Years ago.

T4 templates generated the same model classes every time. Custom DTO generators spit out the same data transfer objects from the same schema, no surprises. CRUD scaffolders gave you the same endpoints, same patterns, same boring reliable code. And then OpenAPI came along and said “here’s a spec, now generate a client” and every client it generates is identical. Same types, same methods, same contract. Every. Single. Time.

We even have transpilers that convert code between languages. TypeScript to JavaScript, Kotlin to Java bytecode, C# to IL. Same input, same output. The tooling to produce consistent, reliable code across languages has existed for years. Nobody asked an agent to “rewrite this in Go and see what happens.”

These tools were deterministic by design. They didn’t “interpret” your schema. They didn’t get creative with your API contract. They read the input and produced the output.

Uncle Bob has been saying this for decades. Clean architecture, clean code, well-defined boundaries. AI doesn’t change that, it makes it more important. AI-generated code is a pattern recognizer spitting out plausible solutions. It doesn’t understand why one structure is better than another for maintainability or testability. That’s still your job. The principles don’t go away because the code writes itself faster. He’s been proving it too with Gherkin. Given/When/Then. Write what the system should do in plain english, run it as a test, get a pass or fail. He took a game from the ’70s, wrote Gherkin scenarios for it, then had Claude build the same game in C, Clojure, Ruby, Rust, and JavaScript from those same scenarios. Same spec, same behavior, six different languages. The spec was the deterministic part. The AI just did the typing.

That’s the model. You define the outcome before you write the code. Not after, not “let’s see what the agent comes up with.” No ambiguity, no “well it sort of works.”

And before someone says “just write better specs” as the answer to everything, specs are their own kind of slop. A 40-page requirements doc that nobody reads or an AI-generated Notion board full of agentic slop is just as probabilistic as the agent itself, you’re just rolling the dice with a nicer font. Gherkin works better than most because it’s short, executable, and proves itself. It’s not a full solution either, it’s still clunky and nobody loves writing it. But at least it runs. Most specs just sit there, age out of date, get filled with agent slop, or just don’t end up reflecting how things actually work.

When software was architected right, clean interfaces, dependency injection, well-defined contracts, features were genuinely plug and play. You didn’t rewrite the plumbing every time you added a room to the house.

So why are we going backwards?

AI generating features isn’t the problem. AI is great at writing a function, building a component, wiring up an endpoint. The problem is when you give it too many choices. Pick the framework, pick the patterns, pick the naming, pick the folder structure, decide how errors work, figure out auth. That’s not one task, that’s fifty decisions, and every one of them is a dice roll. Constrain the choices and AI is fast and reliable. Leave them open and you get a different codebase every time you hit enter.

The goal should be build the tools that let agents be faster while staying deterministic. Code generators, not code guessers. Schema-driven output, not vibes-driven output. Tests and contracts that force a boolean outcome. It either matches the spec or it doesn’t. No “close enough.”

The elephant in the room

I’ll say it. The current direction of “agentic development” is wrong.

Skills, MCPs, CLI wrappers, tool-use chains. All of it is just giving a probabilistic system more buttons to press. More surface area for things to go sideways. You’re not making the agent more reliable by giving it more tools. You’re giving a drunk driver a faster car.

Look at what’s actually happening. Security researchers found over a thousand MCP servers exposed on the internet with zero authentication. The context window, the thing the agent uses to understand what it’s doing, gets stuffed with tool definitions, schemas, conversation history, instruction files, and whatever else the harness decides to shove in there. Claude Code alone manages around 50 tools, multiple instruction files, and a dozen different methods just to compact and summarize the context so it doesn’t overflow. When it does overflow, stuff gets dropped. The agent just forgets things.

Then there’s the context itself. CLAUDE.md files get read automatically before you type anything. Whoever controls the repo controls what the agent reads first. Prompt injection through code comments, through tool descriptions, through the very context that’s supposed to help the agent make better decisions. The context is the attack surface and we keep making it bigger.

And here’s the part nobody talks about. Every developer has their own CLAUDE.md. Sure, put a good one in the project repo. In theory it catches things. In theory. Until someone’s personal config overrides it, or they don’t read the output, or the agent ignores half of it because the context window is full of tool definitions. And the fix? More agents. An agent to review the PR. An agent to check the agent’s review. An agent to auto-merge if the review agent approves. At what point do we admit we built a Rube Goldberg machine to avoid reading code and supposedly ship faster?

Meanwhile the junior who had their agent-generated PR rejected didn’t learn why it was wrong. They learned to re-prompt. The growth opportunity, the “here’s why we do it this way” conversation, that’s gone. Replaced by a diff comment and a thumbs down emoji.

I say this as someone living it. I don’t have a senior dev on my team to tell me when I’m wrong. I read, I ask questions, I try to figure it out. But agents make it harder, not easier, to grow. They give you an answer so fast you never sit with the problem long enough to understand it. And then every engineer on the team is throwing around AI-generated PR comments that read like a corporate greeting card, have no backbone, and reference patterns they couldn’t explain if you asked them. Every shortcut feels like progress until you realize you’ve been confidently walking the wrong direction and nobody stopped you because the agent said it looked fine.

And while we’re at it, who on god’s green earth thought it was a good idea to give an agent the ability to drop a database? A human developer shouldn’t even have that in prod. But sure, let’s give the probabilistic system the keys to the kingdom and hope the prompt said “be careful.” Even better, who set up the service account permissions? Was it another agent? Probably. It’s agents all the way down. And the guardrails? Don’t get me started. The approved actions list, the “are you sure?” prompts, the permission modes, all of it falls apart in practice. Skip the dangerous action check and you’re flying blind. Leave it on and auto mode grinds to a halt asking you to approve every file read like it’s requesting launch codes. The behavior isn’t even consistent. Run the same task twice at different times of day with different context window pressure and you get different permission outcomes. The safety layer is probabilistic too. Anyway, sorry, side tangent. Back to the point.

An agent calling an MCP server/skill that calls a CLI that calls an API is not deterministic. It’s a probabilistic system making choices at every hop. Which tool to call, what arguments to pass, how to interpret the response. Each one is a dice roll. The fact that the underlying API is deterministic doesn’t matter if the thing calling it isn’t.

The whole “let’s build an ecosystem of tools for agents to use” framing has it backwards. We don’t need more tools for agents. We need more tools that replace what agents are doing poorly. If the agent keeps generating slightly different CRUD endpoints, don’t give it a better prompt, build a generator that takes a schema and outputs the endpoints. If the agent keeps botching your API client, don’t add a retry loop, use OpenAPI and generate the client.

Every time you catch an agent doing something inconsistently, that’s not a prompt engineering problem. That’s a “this should be a deterministic tool” problem.

And if an agent needs to fetch examples, spin up a service, or set up a database, why is that an agent’s job? That’s automation. That’s a script. That’s CI/CD. We solved this years ago. make setup exists. Docker Compose exists. If it can be automated, automate it. Don’t wrap it in a probabilistic layer and call it innovation.

The point

At the end of the day, users don’t care that there’s an agent in the loop. They care that the thing works.

Build the pipeline that guarantees it, not the agent orchestra that hopes it does.

And “works” means it does the same thing today that it did yesterday. cough GitHub cough cough Claude Code cough


Title is a nod to Jimmy Buffett’s “A Pirate Looks at Forty.” If pirates didn’t rule the world, agents aren’t going to either, not with probabilistic outcomes at the helm.

Yes, AI helped edit this post. I used it to structure my thoughts, not generate them. That’s the whole point of the article. Use the tool, stay in control of the outcome. The opinions are mine. The formatting is Claude’s. Deterministic collaboration.