The infrastructure your agent picks for you on the first build

The infrastructure your agent picks for you on the first build

Review catches bad code. It rarely catches the decision underneath the code, because the decision does not look like a decision. It looks like a reasonable default that somebody must have thought about.

Short answer: an agent asked to build a feature also picks the infrastructure that feature sits on, in seconds, with no view of where the project is going and no memory of what it chose last time. Those picks outlive the code around them: a function gets rewritten in an afternoon, a storage layout survives three rewrites. Constrain them on the first build, because the second build is a migration.

An agent node branching into five infrastructure sockets labelled auth, data, media, deploy and queue, three filled in cyan and two empty with amber question marks

The decisions that get made without a decision

Ask for a feature and watch what else gets chosen.

“Add user avatars.” The agent picks a storage location, a file naming scheme, an image format, a maximum size, whether anything is resized, and what the alt text policy is. Five architectural decisions, none of them mentioned in the response, all of them now in your repository.

“Add login.” A library, a session strategy, where tokens live, how long they last, what happens on refresh.

“Make this async.” A queue or no queue, retry semantics, what happens to a job that fails four times.

“Store this.” A schema shape, an index or none, a migration approach, and a decision about whether the thing is normalised that will be extremely annoying to revisit.

Each choice is locally defensible. That is exactly why they get through review. The reviewer is reading a diff that adds avatars and thinking about avatars.

Why the agent gets these wrong specifically

Not because it is bad at architecture. Because architecture is a question about the future and the agent has no access to the future, or to the past.

It does not know you are about to add a second tenant. It does not know the mobile client is coming in Q4. It does not know that the last three times someone added storage they used a different convention. It has your prompt, the current state of the repository, and whatever it read, and it has to pick something.

So it picks the most statistically common pattern for the thing you described. That is often reasonable, sometimes wrong, and always uninformed by anything that is not visible in the code today. The context problem is the whole explanation, and it does not go away with a better model.

An isometric row of five identical grey boxes, one lifted to reveal a complex structured cyan interior

Media is the clearest example

It is worth working through one in detail, and media is the best case because the decision is invisible and the consequence is permanent.

An agent asked to handle images with a plain object store makes four calls immediately. Format: whatever came in, or PNG because PNG feels safe, with no way to know whether the requesting browser supports modern formats. Dimensions: whatever was uploaded, so a 4032-pixel phone photo is served into a 400-pixel slot. Alt text: empty, or invented from the filename, which is worse because it looks fine in review. Naming: a convention invented on the spot that it will not remember next session.

None of those are bugs. They are the correct output of a process with no information, because a file in a bucket is a byte string with a name. There is nothing on it to query.

The alternative is that the asset carries its own context, so the thing acting on it can check before it acts rather than guess. That is the difference between a file store and a media layer, and it is why platforms like Cloudinary keep identity, format handling and delivery rules on the asset rather than in the code around it. Practically, the constraint lands as an upload preset: storage location, format policy, size caps and required fields pinned server-side, so the agent cannot get them wrong regardless of what it decided to pass.

The general principle, which applies well beyond media: an agent given a system that can answer questions makes better decisions than one given a system that only stores bytes. Not because it is smarter, but because there is something to ask.

The pattern: constrain at the boundary

Instruction files help and they are advisory. The agent reads them, mostly follows them, and drifts. For decisions you genuinely cannot afford to have re-litigated every session, the constraint has to live in the system rather than in the prompt.

Four places that works.

  • Presets and server-side configuration. The upload preset, the deploy config, the migration template. Applied whether or not the caller remembered.
  • Types and schemas at the boundary. A wrong call does not compile. This is the cheapest enforcement mechanism available and it is under-used precisely at the boundaries where it matters most.
  • Lint and static analysis rules that encode your architecture. Semgrep rules for “never call this directly”, “always go through the wrapper”. Every one is a review comment you never have to make again.
  • Fewer choices available. If there is one blessed way to store a file in your codebase and it is obvious, the agent will use it. If there are three, it will invent a fourth.

The twelve-factor habits help here too, not because the specific list is sacred but because a codebase where configuration, dependencies and backing services are handled uniformly gives the agent one visible pattern to copy instead of four.

A schematic of a decision point early on a long track, dimension lines showing a very short distance to the decision and a very long one after it

Why this bites hardest on small teams

The intuition is that this is an enterprise problem, and it is backwards.

A large team has an architect, a review culture and existing conventions the agent can copy. The defaults get caught. A two-person team building with an agent has none of that: no media specialist, no ops function, no one reviewing what the agent decided. The agent is the architect, and it is making these calls on the first upload rather than at a million assets.

That is not a scale problem arriving later. It is wrong output on the first build, and it is the argument for choosing your infrastructure layers deliberately on day one rather than treating them as something you graduate into. Not choosing is itself a choice, and it is the choice that creates the debt.

What to actually do

Three things, in rough order of return.

Decide the five load-bearing layers before you start. Auth, data, media, deploy, background work. Write down the choice and one sentence of reasoning. This is a thirty-minute exercise that prevents a category of six-month problem.

Put them in the instruction file as rules with reasons. A rule with a stated reason survives cases you did not anticipate, because the agent can extrapolate from it. Bare rules break at the first edge case. More on writing these properly in instruction files an agent will follow.

Enforce the two you care about most in the system. Not all five. Pick the two with the worst migration story and make the wrong path unavailable, via a preset, a type, or a lint rule.

The review question worth adding

One line, added to how you read agent diffs:

What did this change commit us to?

Not “is this code good”. Not “does it work”. What is now assumed, that was not assumed before, that will be expensive to unassume. A storage layout, a naming convention, a library, a schema shape.

That question catches the class of problem ordinary review misses entirely, and it takes about ten seconds per change. It pairs with the rest of the discipline in reviewing code your agent wrote, and it applies whichever tool you use, because it is a property of how agents work rather than of any product. Where the surrounding systems can answer questions rather than just store bytes, as MCP-connected tools increasingly can, the agent has something better than a guess to work from, and the cost of that difference compounds in a way the token bill never will.