Mostly Guidelines

There is a line from Pirates of the Caribbean that keeps coming to mind when I work with AI lately.

Mostly guidelines.

That increasingly seems to describe what happens when we give an AI a large collection of programming rules.

We tell it which part of the application owns a particular responsibility. We tell it not to duplicate an existing implementation. We explain which file is authoritative. We describe the architecture. We tell it where shared code belongs. We explain what must not change.

We may even tell it that the same mistake has happened before and document exactly how it was corrected.

Then one day the AI encounters a problem, finds a perfectly reasonable solution nearby, implements it, tests it, and proudly reports that the problem is fixed.

Except it solved the problem in the wrong place.

The rule was there. The AI may even have read it.

But apparently it was mostly a guideline.

The way we program with AI keeps changing

One of the harder things about working seriously with AI is that the working methods don't stay still.

A year ago, a large part of successful AI programming was prompt engineering. If you wanted better results, you wrote a better prompt.

You explained the problem carefully. You described the architecture. You listed the constraints. You showed examples. You told the AI exactly what it should and should not do.

That worked surprisingly well.

Then projects became larger and AI agents became more capable. The problem changed.

Instead of trying to fit everything into one enormous prompt, we started thinking about context engineering.

Give the AI a short starting point. Give it a map of the project. Identify authoritative documents. Keep historical material separate from current requirements. Let the AI retrieve the information it needs instead of loading the entire history of the project before it starts.

That was another improvement.

Then the work became large enough that one AI conversation was no longer a sensible place to manage everything.

So we began separating responsibilities.

A project conductor maintains the larger picture. Workers receive bounded assignments. Workers return structured handoffs. Qualification happens separately from implementation. Long-running projects are divided into pieces that an AI can actually reason about effectively.

That works too.

But I think we're now running into the next problem.

The problem isn't always that the AI didn't know the rule

For a long time, when AI violated an architectural rule, the obvious response was to improve the instructions.

Maybe we hadn't explained the rule clearly enough. Maybe the architecture document needed another paragraph. Maybe the prompt needed a stronger warning. Maybe the worker needed to be explicitly told to inspect the shared implementation before writing anything locally.

Sometimes that really was the problem.

Increasingly, I don't think it is.

The AI may know perfectly well that a rule exists and still arrive at a local solution that seems reasonable within the context it is currently using.

That distinction matters.

Imagine a large application with a Markdown editor.

The application has one approved implementation for handling images.

A bug appears in one product.

The AI searches for the code involved in the bug. It finds a component. It reads the surrounding code. It discovers that adding a small amount of logic to that component will fix the problem.

From the local view, that may be an excellent solution.

The code is clean. The test passes. The feature works.

Unfortunately, another part of the system already owns that responsibility.

The AI has just created a second semantic implementation of the same behavior.

Nothing about the local fix necessarily tells it that. The problem only becomes obvious when you look at the architecture as a whole.

Large projects encourage local reasoning

This becomes even more important as source files and repositories grow.

Suppose an application contains a 40,000-line source file.

That file is probably too large. But whether it should have become that large isn't the immediate problem. It exists, and something needs to be fixed in it.

An AI agent isn't necessarily going to absorb all 40,000 lines and maintain a perfect mental model of everything happening in the file.

It searches. It finds a symbol. It reads the area around that symbol. It follows references that appear relevant. It builds enough context to solve the immediate problem.

That's usually a sensible way to work.

It may also miss something important 15,000 lines away.

The same thing can happen with an enormous project rulebook.

A 5,000-line instruction document may contain everything the AI could possibly need to know. That doesn't mean every line of it is influencing every decision the AI makes.

This is one reason I no longer believe that the answer to an AI ignoring a 5,000-line rulebook is necessarily a 6,000-line rulebook.

At some point, adding another instruction isn't adding another constraint.

It's adding another thing the AI has to notice.

A rule and a guard are different things

That leads to a distinction I think is becoming fundamental to AI programming.

A rule says:

Do not create another Markdown image implementation. Use the shared implementation.

A guard says:

The build fails if a product contains its own Markdown image implementation.

Those are not the same thing.

The first depends on the AI understanding the rule, recognizing when it applies, remembering it during the work, and deciding not to take another path.

The second doesn't care what the AI remembers.

The wrong result fails.

There are several levels of this.

We can tell the AI what to do. We can detect when the AI does something else. Or, best of all, we can design the system so the wrong implementation isn't available as a reasonable choice in the first place.

That last one is where things get interesting.

One source of truth is more than good architecture

We recently started restructuring some of our own products around this idea.

PageSnip had become the proving ground for a number of technologies and behaviors that later appeared in other applications.

That was useful.

It was also dangerous.

We could explain how PageSnip handled something. We could copy the pattern into another application. We could tell another AI worker to reproduce the same behavior. We could even create elaborate transfer prompts explaining exactly how the implementation worked and what needed to remain consistent.

Eventually they drifted anyway.

So we're moving away from reproducing shared behavior and toward actually sharing the implementation.

If there should be one Markdown editor, then there should be one Markdown editor.

PageSnip can use it. ProHelp Studio can use it. Another application can use it.

But there shouldn't be four implementations that are expected to remain conceptually identical because four sets of programmers, human or AI, have been told to keep them that way.

That's good software architecture even without AI.

With AI, it becomes something more.

It removes choices that should never have existed.

An AI can't casually create a slightly different version of a shared implementation if the product simply consumes the shared component and has no private implementation to modify.

The architecture becomes part of the instruction set.

The best rule may be one the AI never has to read

This turns some traditional thinking about AI programming upside down.

We spend a lot of time asking:

How do I make the AI understand this rule?

A better question may be:

Why does the AI need to understand this rule at all?

If every database record must pass through a validator, perhaps the API should make it impossible to write one without validation.

If products are not allowed to import from an internal package, perhaps the dependency rules should reject the import.

If a release requires a particular regression suite, perhaps the build should not produce the release artifact until that suite passes.

If iteration numbers cannot be reused, perhaps the delivery tooling should reject an already consumed number.

If a product must consume the shared renderer, perhaps there should be no product-local renderer available for the AI to modify.

Every time we can replace "remember not to do this" with "this cannot successfully happen," we remove one probabilistic decision from the system.

That seems increasingly valuable.

Guard scripts are becoming part of the codebase

I've noticed another change in recent AI-generated code.

There are more guard scripts.

Not just tests that ask whether a function returns the right answer.

Structural guards. Repository guards. Dependency guards. Qualification scripts.

Scripts that make sure expected files exist.

Scripts that make sure forbidden files do not exist.

Scripts that verify manifests, hashes, generated artifacts, package boundaries, retained behavior, and other assumptions about the project.

A few months ago, many of these would have looked like unnecessary machinery.

Why write a script to check something that every programmer working on the project already knows?

Because the programmer working on the project may now be an AI agent that has reconstructed its understanding of the project from whatever information looked relevant to the current assignment.

The guard doesn't need to know why the mistake happened.

It only needs to know that the resulting state is unacceptable.

That's a powerful change.

Don't confuse guards with micromanagement

There is a danger here too.

The lesson isn't that we should surround AI with thousands of arbitrary restrictions until it can't do anything useful.

That's just another form of over-prompting.

Good guards protect invariants.

They protect things that are actually important to the integrity of the system.

One implementation owns this responsibility. Dependencies flow in this direction. This data has to validate before it crosses this boundary. This test has to pass. This artifact has to come from this source. This release must preserve this behavior.

Those are useful constraints.

Whether the AI uses a for loop or a map operation probably isn't.

Whether it names a local variable result or output probably isn't.

The goal is not to eliminate judgment.

The goal is to reserve judgment for places where multiple answers are genuinely acceptable.

Let the AI be creative inside a box it can't move

This may sound like an argument for giving AI less freedom.

I think it's actually the opposite.

The more confidence I have in the boundaries, the more willing I am to let the AI work aggressively inside them.

If the architecture prevents it from reaching across the wrong dependency boundary, I don't have to hover over every import.

If a regression test protects an important behavior, I don't have to repeatedly tell the AI not to break it.

If the build rejects duplicate implementations, I can let the AI find its own solution knowing that one dangerous class of solution can't survive.

The guard creates freedom.

That's very different from a 40-page prompt telling the AI exactly how to write every line of code.

The project conductor changes too

I think this also changes the role of an AI project conductor.

A conductor shouldn't merely remember the rules and repeat them to workers.

It should enforce the parts that can be enforced.

Did the worker start from the correct source authority? Did the source hash match? Were the expected tests present? Did the retained qualification suite pass? Did the worker introduce a new implementation into a part of the repository that doesn't own it? Did the delivery reuse an iteration number that had already been consumed? Did the work preserve the architectural invariants associated with the change?

Some of those questions require judgment.

Many don't.

The ones that don't should eventually become mechanical.

The conductor then spends more of its intelligence on the things that actually need intelligence.

Assume every instruction will eventually be misunderstood

I used to think the goal was to write instructions clear enough that the AI wouldn't misunderstand them.

I'm beginning to think that's the wrong goal.

A more useful design assumption is this:

If an important instruction can be misunderstood, eventually some AI agent will misunderstand it.

Maybe not this model. Maybe not this week. Maybe not on this particular assignment.

But if the project lives long enough and enough work passes through it, the opportunity is there.

So ask a different question.

What happens when the instruction is misunderstood?

If the answer is "the wrong code gets committed and nobody notices," you have a problem.

If the answer is "the guard fails and the work comes back for correction," you have a much stronger system.

Mostly guidelines isn't good enough for invariants

Prompts still matter. Context still matters. Architecture documents still matter. Project conductors still matter. Human judgment still matters.

None of those things are going away.

But I think we're entering another stage of AI programming.

The first stage was learning to ask the AI clearly.

The next was learning to give it the right context.

Then we learned to break large work into manageable assignments and coordinate multiple agents.

Now we're learning something else.

Important rules cannot live only in prose.

The more important the rule is, the more we should ask whether the architecture, tooling, tests, or build process can enforce it.

Because if an AI can violate an important architectural rule and still produce a successful build, the rule isn't really being enforced.

It's mostly a guideline.

The holy grail of AI programming may not be an AI that always makes the right choice.

It may be an architecture in which the AI cannot successfully make the wrong one.

Related field note

For the story behind how repeated PageSnip and shared-platform drift changed the way I think about AI rules, read The Rule Was Already There.