enolaBlogInstructions are not controls

Blog

Instructions are not controls.

Coding agents need instructions. They also need something that checks whether those instructions were followed.

Gert7 min read

An instruction tells an agent what you want. A control measures what happened and determines what happens next.

A repository can contain an AGENTS.md, an architecture document, and carefully written prompts, but the agent can still introduce a prohibited dependency.

The instruction was present. The change was never checked.

A repository rule example

Consider a small application with three layers:

delivery → api → storage

The intended dependency direction is inward. delivery may use api, and api may use storage. Code in storage must not depend on delivery.

A team could document this in AGENTS.md:

markdown
## Architecture

Storage must not depend on delivery.
Keep notifications and other delivery concerns outside the storage layer.

This gives the agent information that may not be obvious from the task or the file it is editing. It may prevent the mistake before code is written.

But it does not enforce the rule.

Why?

The instruction may be absent from the agent’s current context. It may compete with other instructions. The agent may interpret it differently from the team. It may understand the rule and still violate it while solving the immediate task.

An instruction changes the probability of a result. It does not establish what the result was.

The code still works

Suppose the agent adds this import to a file in storage:

storage/receipt.go · new dependency
package storage

import "example.com/shop/notify"  // storage → delivery

The new helper reads a price and sends a receipt. The implementation works, and the compiler accepts the import. Established safeguards like tests cover the expected behavior and pass.

The defect is in the new relationship between two parts of the system, not in the implementation of either function.

That relationship contradicts a rule written elsewhere. A test concerned with the returned price has no reason to detect it. The compiler knows whether the dependency is legal, not whether it is legal architecture.

The agent reports success because every check it ran passed:

  • The code compiles.
  • The tests pass.
  • The requested behavior exists.

The architecture still moved in a direction the team did not want.

Turn the rule into something measurable

The next step is not a longer prompt. It is a representation that a tool can evaluate.

In enola, the layer order can be declared as repository intent:

enola-intent.yaml
layers:  # outermost first
  - {name: delivery, paths: ["web/**", "notify/**"]}
  - {name: api,      paths: ["api/**"]}
  - {name: storage,  paths: ["storage/**"]}

This still contains a human decision. enola did not discover that storage must sit inside delivery, nor can a graph prove that this is the correct design for the business. The team chose the boundary.

Now a tool can test the decision.

enola builds a graph before the change, builds it again afterward, and compares the two. The import creates a new edge from storage to delivery, so the check can report the exact regression:

enola check --fail-on=layers
FAIL — 1 structural regression introduced.

Regressions (fail):
  - [layers] 1.00 — Layer violation: storage -> delivery
      import of notify

Policy: fail on new findings from [layers] at confidence >= 1.00.

The instruction states what should happen. The verdict records what happened: the changed dependency, the boundary it crossed, and the rule it violated.

Reporting is not enforcement

A tool can detect a violation without blocking anything. enola does this by default:

shell
enola check

The finding is reported, but the command exits successfully because no failure policy was selected. This is useful while a team is learning what the graph contains or deciding which findings matter. It is not a quality gate.

To make new layer violations fail the run:

shell
enola check --fail-on=layers

Now the exit code gives the harness something enforceable. It can return the failure to the agent, stop a hook, or fail a CI job.

Turning an instruction into enforcement requires four elements:

ElementPurposeWhat it establishes
InstructionTell the agent how the repository should be changedThe desired approach was available to the agent
Structured ruleMake part of that intent machine-readableThe rule can be evaluated consistently
CheckCompare the result with the ruleThe change followed or violated the rule
GateAttach a consequence to the resultThe change cannot progress under the selected policy

A structured rule is not enforced until a check evaluates it and a gate acts on the result.

Use instructions before the change and checks after it

Repository instructions and gates solve different problems.

Instructions are feedforward: they help the agent choose a suitable implementation before it edits. Checks are feedback: they inspect the implementation afterward and give the agent a chance to correct it. Birgitta Böckeler uses this guides-and-sensors distinction in her harness engineering framework.

Use both.

With instructions but no checks, the agent can repeat a prohibited pattern without the harness noticing. With checks but no instructions, the agent learns the repository’s rules only after a failed check.

A useful loop looks like this:

  1. Give the agent the relevant repository rule and structural context.
  2. Let it make the change.
  3. Run the deterministic checks that apply to that change.
  4. Return specific evidence when a check fails.
  5. Let the agent revise the implementation.
  6. Apply the same policy in CI.

The local check gives the agent a chance to fix the change immediately. Run the same check in CI in case the local hook was skipped.

Not every instruction should become a gate

Executable controls are most useful when the property is observable and the team agrees on the consequence.

Good candidates include:

  • Declared layer boundaries
  • Forbidden dependencies
  • Dependency cycles where the language does not already prohibit them
  • API or messaging relationships that require a declared contract
  • Changes that spread outside a stated scope
  • Formatting, types, tests, security policies, and other properties covered by existing deterministic tools

Other instructions require judgement:

  • Prefer the simplest reasonable design.
  • Avoid an abstraction unless another use is likely.
  • Keep the user experience consistent.
  • Do not expand the scope of the feature unnecessarily.

An LLM reviewer or a person can assess these, but the result remains inferential. Turning them into a binary automated gate can create false confidence or block valid work.

The useful question is not, “Can we express this as a rule?” It is:

What evidence would show that this specific rule was followed, and is that evidence reliable enough to determine whether the change may proceed?

Sometimes the answer is a compiler, test, linter, or structural graph. Sometimes it is review. Often it is a combination.

Controls can fail too

The check itself must be observable.

Record whether each hook ran. Otherwise, a skipped check and a passing check look the same. A structural comparison against an incompatible baseline should not grade the change. A CI command that runs enola check without a failure policy reports findings but blocks none of them.

Record:

  • Which repository revision was checked?
  • Which baseline was used?
  • Which rules were enforced?
  • Did the hook run?
  • Was the analysis complete enough to support the verdict?
  • Did the command fail because of the code, or because the check could not run?

enola records the source revision and analysis configuration in its snapshots, refuses to compare incompatible snapshots, and exposes hook and baseline status through enola doctor. Without that evidence, a reported pass is not trustworthy.

Start with one rule that matters

Do not begin by converting an architecture document into fifty gates.

Choose one boundary that the team already treats as important. Define it precisely. Run the check in reporting mode. Inspect the results. Then decide whether new violations should fail locally, in CI, or both. The change-control documentation covers the baseline and check workflow.

For the example above, the complete path is:

shell
# Record the current structure
enola baseline pin

# Make a change, then inspect what moved
enola check

# Enforce the declared layer order
enola check --fail-on=layers

The repository instruction still belongs in AGENTS.md. It tells the agent how to work.

The check and gate belong in the harness. They tell you whether the result complied and whether it may proceed.

FAQ

What is the difference between an instruction and a control for a coding agent?

An instruction tells the coding agent how the repository should be changed. A control checks the resulting change against a rule and can prevent the change from progressing when the rule is violated.

Can AGENTS.md enforce architecture rules?

No. AGENTS.md can give a coding agent architecture instructions before it edits, but it does not establish whether the finished change followed them. Enforcement requires a check and a failure policy.

Which repository rules should become automated gates?

Use automated gates for observable properties with agreed consequences, such as layer boundaries, forbidden dependencies, dependency cycles, contract requirements, formatting, types, tests, and security policies. Rules that require judgement should remain review questions.

Should architecture checks run locally or in CI?

Run them in both places when practical. A local check lets the coding agent correct a violation immediately. The same check in CI still runs when the local hook is skipped.

This is part two of Harness Engineering for Coding Agents. Start with What is an agent harness?, then continue with The harness decides what the model sees and What makes a coding agent correct its own mistakes?.