Write agent instructions that explain the decision
By Flavio
Turn a brittle AGENTS.md into useful project guidance. Explain the reason behind a rule, keep hard checks in code, and make approval boundaries explicit.
An instruction file can grow every time an agent makes a mistake. After a month, it contains several versions of the same warning and a few rules that contradict each other.
The agent still has to decide what applies to its current task. Giving it more sentences doesn’t always make that decision easier.
I would organize the file around the choices the project repeatedly needs: where a change belongs, how to verify it, and which actions need a person’s decision. Explain why a constraint exists, and move mechanically checkable rules into the tools that enforce them.
Let’s use a fictional reading-list application. The examples below are illustrative AGENTS.md files. They don’t change this site’s actual instructions.
Start with a rule that has lost its context
Imagine finding this in the project:
# Agent rules
Always use the shared helper.
Never add another helper.
Be careful with user data.
Always run all tests.
Don't change unrelated things.
Ask before doing anything risky.
Don't ask too many questions.
If the user doesn't reply, keep going.
The author probably had a reason for every line. The file doesn’t say which helper matters or what data boundary the application has.
The last two lines are particularly unhelpful together. An agent can interpret “keep going” as permission to cross a boundary that the earlier line says requires a decision.
You can improve the file without turning it into a long policy manual.
Say what the application is protecting
Start with a short description of the product and its important constraints:
# Reading list
People save private links and notes. Every bookmark belongs to one account.
Server handlers must check ownership before reading or changing a bookmark.
Changing an ID in a request must never reveal another account's data.
URL normalization lives in src/lib/url.ts. Use it for new bookmark inputs
so the form and import flow agree about duplicate links.
Now “be careful with user data” has a concrete meaning. The ownership rule applies to reads and writes. The example also explains why the shared URL helper exists.
That reason helps when the agent meets a new case. If we add a CSV import, the agent can recognize that it needs the same normalization policy. It doesn’t need a new sentence naming every future feature.
A principle should still be specific enough to affect a decision. “Write good code” tells the agent nothing about whether importing a second URL parser is appropriate.
Keep an absolute constraint absolute
Explaining the reason behind a rule doesn’t make the rule optional. If production data must stay untouched, say so directly:
# Data and external actions
Use the local fixture database for development and checks.
Do not connect tests or preview deployments to production data.
Before sending an email or running a production migration, show the exact
recipient and message, or migration and target environment, and wait for
explicit approval. Silence and elapsed time do not grant permission.
The agent can continue independent preparation while a decision is pending. It can write the migration, inspect its output, or prepare the email preview when those actions are within the task’s scope.
The external action remains pending until the person authorizes it. A timer expiring is a reason to pause or report the missing decision, not a substitute for consent.
Keep approval tied to the proposed action. If the migration or target environment changes, the earlier decision may no longer cover it. An instruction like “ask before risky things” leaves too much of that boundary implicit.
Give verification a purpose
“Always run every test” is easy to write and expensive to follow. It also doesn’t guarantee that the tests cover the change.
For the reading-list app, explain how to choose checks:
# Verification
For server changes, check the affected behavior and account ownership.
A bookmark update needs checks for its owner and another signed-in account.
For UI changes, use the changed flow in a browser, including empty and error
states. Run the relevant automated tests. Run the full suite before release.
State what you checked and report failures that remain unresolved.
This gives a small fix a focused verification path while preserving a broader release gate. The full suite can still be mandatory at that gate because the project has chosen to require it.
If an important check is repeatable, give it a reliable command. A test for cross-account access is stronger than hoping an agent remembers to simulate it manually every time.
Put hard checks where they can run
Instructions guide decisions. A formatter can enforce formatting, and CI can require a test result before merging. Repository rules can restrict who updates the production branch.
Use each mechanism for the thing it can establish. A line in AGENTS.md saying “don’t commit secrets” doesn’t prevent an accidental secret from entering Git. A secret scanner can catch some patterns; credential scope and rotation handle different parts of the problem.
Likewise, a test proving one ownership check works doesn’t establish every route is protected. Add coverage as new routes appear, and make the source of the authorization policy easy to find.
GitHub’s branch protection documentation describes required checks and reviews. Those repository settings enforce a different boundary from prose that an agent reads.
Keep instructions close to their owner
A repository-wide file should explain decisions that apply throughout the project. A subsystem can need more specific guidance: an import parser’s encoding rules or a payment integration’s retry behavior.
Point to that guidance rather than duplicating it in several files. Duplicates drift when one gets updated and another survives in an older form.
Also check how your coding tool loads instructions. File names, directory scope, and precedence differ between tools. Claude Code’s memory and project instruction guide documents its own behavior. Don’t assume every tool will interpret the same file tree identically.
Revise the reason after a real failure
Suppose an agent adds a second normalizer while implementing imports. Before adding “never create normalizeImportedUrl,” look at why it missed the shared one.
The helper could be hard to find, or its name could suggest it only handles browser input. Its implementation may not support imported URLs yet, even though the instructions say to reuse it.
Fix that cause, then update the guidance if needed. A useful revision can be a path and one sentence explaining the shared policy.
When you next give an agent an import task, check whether the file leads it to the right decision. Keep the example and the relevant test together so future changes can show whether the instruction still matches the code.
Want me to talk about your product? You can sponsor this site.
Related posts about ai: