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:
## 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:
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:
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:
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:
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:
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:
| Element | Purpose | What it establishes |
|---|---|---|
| Instruction | Tell the agent how the repository should be changed | The desired approach was available to the agent |
| Structured rule | Make part of that intent machine-readable | The rule can be evaluated consistently |
| Check | Compare the result with the rule | The change followed or violated the rule |
| Gate | Attach a consequence to the result | The 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:
- Give the agent the relevant repository rule and structural context.
- Let it make the change.
- Run the deterministic checks that apply to that change.
- Return specific evidence when a check fails.
- Let the agent revise the implementation.
- 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:
# 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?.