The Agent-Instruction House Standard

Agent-Instruction House Standard

How to structure agent-instruction files across a set of repos: one source of truth, an in-repo fix log, written anti-drift rules, and a self-healing session hook.

Apply this to any repo an AI agent (Claude Code, Codex, Cursor, Gemini) works in.


1. One source of truth

A repo has one canonical instruction file: AGENTS.md. It holds everything an agent (or human) needs to work on the repo: architecture pointer, build/test commands, conventions, gotchas, ship rules.

CLAUDE.md is one line, an include:

@AGENTS.md

Why: AGENTS.md is the cross-harness convention (Codex, Cursor, Gemini, Agent Skills all read it). Claude Code reads CLAUDE.md, so the include points it at the same canonical file. Never maintain the same content in two files. That is the failure mode this prevents (a large monolithic instruction file drifted because there was no single source).

A CLAUDE.md symlink → AGENTS.md is equally compliant and in some ways stronger: any tool that opens CLAUDE.md gets the full canonical content with zero drift, no @import support required (a CLAUDE.md -> AGENTS.md symlink, git mode 120000). Leave such repos as-is.

Tradeoff: symlinks don’t survive some Windows checkouts / zip exports, where the @AGENTS.md text include is more portable. Pick per repo; both satisfy one-source-of-truth. Do not convert a working symlink to an include just for uniformity; it is a lateral move.

If a repo genuinely needs Claude-only nuance, put the @AGENTS.md line first, then the small Claude-specific addendum below it. This should be rare.

Accepted exception: a deliberate two-file split

A repo may keep CLAUDE.md and AGENTS.md as complementary files (not duplicates) when the content genuinely divides by audience, e.g. AGENTS.md = install + operating protocol + routing (cross-harness onboarding), CLAUDE.md = architecture reference / key files / test layout. This is compliant as long as the two never hold the same content and a ## Keep in sync rule covers any overlap. The anti-pattern is duplication, not two files.

Scoped instruction files (subdirectories)

The root AGENTS.md doesn’t have to carry everything. Context that belongs to one part of the repo can live there, so it loads only when an agent actually works there:

Two disciplines keep scoped files from becoming the drift problem they solve: a folder file holds only what the root doesn’t (zero duplication), and it pins decisions and constraints, not structure — file trees and stack lists are derivable from ls and rot fast (§3). A folder of static reference files needs no instruction file at all.

For the wider doc set an AGENTS.md routes to (runbooks, design docs, system notes), make discovery mechanical: open every doc with a dense summary in its first few lines, so an agent can head across the folder and pick the right file instead of loading them all. Note the convention in AGENTS.md — both so agents rely on it when searching and so they keep the summary true when they edit the doc.

AGENTS.md skeleton

# <Repo>: Agent Guide

One-sentence description of what this repo is.

## Architecture
Where the code lives, the 3 to 5 things you must understand before editing.

## Commands
Build / dev / test commands. The ones you actually run.

## Conventions
House style, naming, patterns to follow, orphaned code NOT to recreate.

## Gotchas
The traps. (See docs/solutions/ for the full fix log.)

## Before shipping
Tests, lint, build, whatever the ship gate is.

## Keep in sync
<see section 3>

Keep AGENTS.md scannable. Anything that is a specific past incident goes in docs/solutions/, not inline. That keeps AGENTS.md from growing without bound.

Write hard rules with their exceptions attached: “never commit secrets, except .env.example” survives contact with edge cases; a bare NEVER gets ignored the first time an edge case makes it wrong.

What good context covers: the four S’s

Instruction-file content comes in four layers — Syntax, Service, System, Strategy. Each builds on the one below; skipping a layer produces slop regardless of model quality:

A minimal AGENTS.md covers Syntax and Service. System often lives in a shared org-level file the repo points to. Strategy is frequently private config — but each layer should exist in writing somewhere an agent can read, because this hierarchy is exactly the implicit knowledge senior engineers carry, made explicit and machine-readable.

One name per concept (domain language)

Content drift has a twin: naming drift — one concept accruing synonyms (“issue”, “ticket”, “task item”) until an agent builds around the wrong one. Where a repo’s domain has terms worth defending, give AGENTS.md (or a file it points to) a short domain-language block:

Keep it to terms that actually get confused. A glossary of the obvious is an inventory, and inventories rot (§3).

Name the current layer when the codebase has strata

Naming drift has a structural twin. A repo maintained by a succession of leads accumulates strata: each lead built in the idiom they understood, and because nobody had enough context to migrate the previous one, the old idiom survives underneath. The result is a codebase where two files solve the same problem in two shapes, both load-bearing, neither wrong. This is most common in small teams on complex domains — permissions, billing, identity — where the work is permanently valuable but rarely the quarter’s priority, so nothing ever forces the layers to compact.

Strata break the instruction “write code consistent with the surrounding code”, because surrounding is ambiguous by construction. An agent that copies the nearest pattern picks a layer at random, and a harness that ships all day picks a new one every session — which is how the repo gains a stratum per contributor instead of per lead.

Where a repo has strata, AGENTS.md (or a file it points to) says so plainly:

Left unwritten, this knowledge lives only with whoever has been there longest, and it decays exactly the way §11 describes — except here the cost isn’t a lost runbook, it’s a repo that gains a layer every time someone new starts shipping.

Finding the strata when nobody can name them

The block above assumes someone can enumerate the layers. Often nobody can — the person who could is exactly the person who left, which is the condition that produced the strata in the first place. So the layers have to be recovered from evidence, and the repo carries plenty: strata are visible as clusters of files that solve the same problem in different shapes, and those clusters correlate with periods of history.

The recovery is a §11 discovery pass narrowed to one question — what idioms are live here? — and it is read-only:

The output is the block above, plus a fix-log entry (§2) recording how the strata arose where that is knowable — the entry is the why, and the AGENTS.md block is the compiled rule. Re-run the pass when a migration finishes or a new shape starts appearing, not on a schedule; strata change on the timescale of leads, not sprints.

Scoring compliance: a maturity level, not a point total

Compliance is checkable (adopt --check), and the score is deliberately shaped as a maturity level, not a raw count of passing checks. A repo with a rich AGENTS.md, a fix log, and a sync block but no secret hygiene is not “almost compliant” — it is one clear rung below a repo that also keeps secrets out of history, because the missing piece is a floor, not one point among many. Levels gate on the shape of the harness:

A point total lets 80% of beautiful docs mask a dangerous gap; a level makes the gap the headline. The gate (--check exit 0) is reaching the top level, not collecting the most points.

The check IDs are a contract. Each check has a stable identifier (STD-01STD-06); an ID never changes meaning, and the --json output only gains fields, never renames them — so a CI pipeline gating on the score never silently misreads a new version.

What the check does not measure. A deterministic scan can confirm a file exists, parses, and matches a pattern — never that its contents are true. A stale rule scores like a fresh one; an AGENTS.md full of wrong commands passes the presence check; a fix log of outdated entries still counts as a fix log. A high score means the infrastructure for reliable agent work is in place — it is necessary, not sufficient, and that is the honest ceiling of any automated check. Keeping the contents true is the §2 “compile, don’t retrieve” discipline, which no scanner can do for you.


2. docs/solutions/: the fix log

A committed, queryable record of past bugs, fixes, and hard-won patterns. The in-repo, shared version of per-machine agent memory: every agent and human that opens the repo sees it.

One fix per file. Filename: docs/solutions/<area>-<short-slug>.md.

Required frontmatter:

---
module: <which part of the codebase, e.g. "booking", "auth", "build">
tags: [<keywords for search>]
problem_type: bug | gotcha | pattern | workflow
date: YYYY-MM-DD
---

## Problem
What went wrong / what's confusing.

## Cause
Why it happens (the root cause, not the symptom).

## Fix
What to do. Concrete, copy-pasteable where possible.

When a repo already grows an inline “Corrections Log” or “Things Claude Has Learned” section, migrate those entries into docs/solutions/ one file each and leave a one-line pointer in AGENTS.md.

The slop list

Not every entry is a bug. Recurring slop — agent output that compiles, passes the cheap checks, and looks plausible but is subtly wrong — gets logged the same way (problem_type: pattern), one category per file. The categories that show up everywhere:

  1. Plausible but wrong — right types, wrong answer at the edge cases.
  2. Over-engineered — three abstractions for a ten-line problem.
  3. Convention-blind — generic good code that ignores this repo’s patterns.
  4. Hallucinated APIs — methods that don’t exist, or were renamed two versions ago, inside otherwise-legitimate code.
  5. Defensive slop — error handling that hides failures instead of preventing them; null checks for values that can’t be null.
  6. Cargo-cult patterns — retries, caches, async wrappers where they don’t fit.

Capture a category once and it becomes context that prevents it forever. The slop list is the institutional memory of how agents fail on this codebase — and the review lens for §10’s contract check.

Compile, don’t retrieve

A fix log only pays off if entries get read into AGENTS.md and each other, not just accumulated as files an agent might grep. Retrieval re-derives an answer from raw entries on every session and compounds nothing; compilation folds an entry’s implication into the standing instructions once, so every future session starts from the compiled result instead of re-discovering it. Concretely: when a fix-log entry reveals a rule an agent should follow by default (not just a past incident to know about), promote that rule into AGENTS.md’s Gotchas or Conventions section — don’t leave it as something only found by searching docs/solutions/. The entry stays as the record of why; the rule it produced belongs in the file agents read every session.

Prose is the floor of compilation, not the ceiling. When a logged pattern is mechanically checkable, promote it past AGENTS.md into a linter or pre-commit hook — one that fixes the problem (--fix), or at minimum blocks it, never one that only flags it. A rule enforced by a hook cannot be skimmed past, and it frees the instruction file’s budget for rules that need judgment. The same goes for workflows: a multi-step incantation agents keep re-deriving (how to kick off a review, how to run one targeted test) gets compiled into a small script in the repo’s bin/, pointed to from AGENTS.md.

Add entries one at a time. Write a fix-log entry right after the incident, while the cause is fresh. The highest-signal trigger is a human correcting the agent on something the instructions should have prevented: log it and promote the rule in the same session, not in a later documentation pass. Cross-link each entry to related entries and to the AGENTS.md rule it feeds (§3’s “Keep in sync” is the place to declare that link if it’s easy to miss). Do not batch-import a backlog of old incidents in one pass — a bulk import produces isolated files with no cross-links and no promoted rules, which is a pile, not a compiled fix log.

Gate the commit, don’t trust the habit

Promoting a rule into a hook (above) only bites if the hook actually runs before the commit lands. The reliable shape is a review marker: a review step (a /code-review, a reviewer agent, a lint pass) writes a marker file on pass, and a PreToolUse hook blocks git commit until the marker exists. The difference from a convention is that the agent cannot skip it — an unreviewed commit is refused, not merely frowned upon.

Make the marker session-scoped (keyed by repo + agent session id), so parallel agent sessions in the same checkout don’t clear each other’s gate, and so the requirement resets per session rather than leaking across unrelated work. Scope the strictness to the diff: require only a light review for most changes, and a deeper one (a data-migration reviewer, a security pass) only when the staged paths match the sensitive set — a blanket heavy gate on every commit gets disabled within a week. Keep a single documented bypass for genuine exceptions (an env flag, --no-verify); a gate with no escape hatch gets ripped out instead of bypassed. templates/hooks/scripts/review-gate.sh is a copy-in implementation.

Ratchet debt down; don’t gate on an absolute

Some rules can’t be enforced as a hard line without failing on day one. “No source file over 500 lines” is unachievable in a repo that already has fifty such files, so the rule never ships. Ratchet instead: snapshot the current count of the thing you want less of (files over N lines, TODOs, suppressions, any-casts) into a committed baseline, and fail CI only when a metric goes up. Existing debt is grandfathered; new debt is blocked; lowering the baseline is a deliberate commit, so the number only moves in the good direction — never silently. This turns an aspiration a repo can’t meet today into a gate it can adopt today and tighten over time. templates/hooks/scripts/ratchet.sh is a language-agnostic implementation; edit its metric list for the repo.

Record the decision before the change, not only the fix after

The fix log (above) captures what broke and why — after the fact. Its forward-looking twin is a short decision record written before a significant change: what’s being decided, why, and the alternatives rejected. Keep it lightweight — a few paragraphs in docs/adr/NNNN-slug.md — and, exactly like the fix log, bound it with an explicit skip-list so it doesn’t become ceremony. Require it for: new features, architectural changes, new external integrations, changes spanning many modules, or a new cross-cutting pattern. Do not require it for: bug fixes, single-file refactors, doc-only changes, or test additions. The rule earns its keep precisely because it says loudly when it does not apply — an unbounded “write an ADR for everything” is the drift, not the discipline.


3. Anti-drift sync contracts

When two files must agree, say so in writing. Drop a ## Keep in sync block into AGENTS.md:

## Keep in sync
- Add an env var → document it in `CONFIGURATION.md` (or `.env.example`).
- Add a CLI flag / route → update the relevant section here and the README example.
- Change <file A> → update <file B> (and the test that pins them, if any).

List only the pairs that actually drift in this repo. The rule exists because prose inventories rot: a hand-kept “Inventory (legacy)” file-list drifts from the codebase until it is removed. Prefer “read the directory” over a hand-kept list; where a list is unavoidable, pin it with a sync rule or a test.


4. Self-healing SessionStart hook

Only for repos with config that fails silently (an .env with secrets, a required output dir). A SessionStart hook fixes the common problems before they bite instead of after.

hooks/hooks.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "bash \"${CLAUDE_PLUGIN_ROOT:-.}/hooks/scripts/check-config.sh\""
          }
        ]
      }
    ]
  }
}

The script (template in templates/hooks/scripts/check-config.sh) does two generically-useful things, both cross-platform-guarded:

Adapt the env-file path and dir per repo. Skip the hook entirely where there’s no silent-failure config to heal. Do not add ceremony for its own sake.


5. Commit authorship

Commits in any repo under this standard must be authored by one of a small, explicit set of sanctioned identities. No stray author (a work email, a machine default, a bot) should ever land in history. Pick your allowed identities and list them, e.g.:

Before committing, verify the local identity resolves to one of them:

git config user.name && git config user.email

If it doesn’t, set it per-repo (git config user.email you@example.com). Do not commit under a different identity and fix it later. An agent committing on your behalf uses whichever sanctioned identity the repo is already configured for; if unset, fall back to a documented default.

Co-author trailers (Co-Authored-By:) for the agent are fine and don’t count as the commit author.

Agent-authorship disclosure

Separate from whose identity a commit carries is whether the work was agent-generated at all — and that must never be invisible. Disclosure is continuous, not a one-time note:

Multi-account hosting. If your repos live under more than one GitHub (or GitLab) account, remember the hosting account is separate from commit identity, and CLIs like gh keep only one account active at a time. Working in a repo owned by a non-default account without switching first (gh auth switch --user <account>) makes reads/pushes hit the wrong account, which returns a bare 404 / repository not found, a silent “wrong active account,” not a missing repo. Note the required account at the top of that repo’s AGENTS.md and switch before any gh/push operation.


6. Commit + push flow: default to the main branch

Default: commit straight to the default branch (main/master) and push it. For solo / small-team repos, a feature branch + PR for routine work just adds ceremony and leaves stale branches behind (see the anti-pattern below). Complete the loop: commit and git push origin <default>, so the work is actually on the remote, not parked on a local branch waiting for a second ask.

Branch + PR only when the change is risky. Open a branch instead of committing to main when the change is any of:

For those, branch off the default, push the branch, and open a PR so main stays green. Everything else goes straight to main. The user can always override in either direction (“just commit it”, “put it on a branch”); when they do, that wins for that change.

Anti-pattern: branch-per-trivial-change

Do not create a feature/add-x branch for a routine, low-risk edit (docs, a diagram, a copy tweak, a one-line fix) and then merge it yourself moments later. The branch adds no review value on a solo repo, and if it’s fast-forwarded or rebased into main the leftover branch lingers on the remote showing a misleading “Compare & pull request” banner. Commit low-risk work directly to main; reserve branches for the risky cases above. If a redundant branch does get created, delete it (remote + local) once its content is on main.


7. Deploy-account hygiene (multi-account setups)

If you deploy across more than one account on a host (Vercel, Netlify, Fly, Cloudflare, etc.), the CLI usually keeps one account logged in at a time, and the account that owns a deployment is independent of the git remote owner. Deploying under the wrong account fails (“Could not retrieve Project Settings”) or, worse, deploys to the wrong project.

Rules for any agent about to deploy:

  1. Never run a bare deploy command. Run an account-check first that compares the current CLI login against the account this repo requires, and only then deploy. A tiny deploy-check wrapper that reads the required account from the repo’s AGENTS.md and diffs it against the active login pays for itself.
  2. Never infer the account from the git remote. Read the > **Deploy:** line in AGENTS.md, or cross-check the linked project’s org id against a maintained account→repo map.
  3. The deploy link is not always at the repo root. Some projects link the deploy config from a subdirectory. If the root has none, find the real one (find . -path '*/.vercel/project.json', adjust per host) and run deploy commands from that directory, otherwise the CLI silently uses whatever account is logged in.
  4. Prefer stored tokens over interactive login. A per-account token (e.g. from the OS keychain / a secrets manager) lets deploys run headless with --token and avoids flipping the global CLI session to the wrong account. The fix for a wrong-account error is the correct account’s token, not a bare login that mutates global state.

If the account-check reports a mismatch, stop and switch accounts. Do not guess your way through auth.

Keep the concrete account↔︎repo map (emails, org ids, domains) in a private file or a secrets manager, not in this public standard. This section is the policy; your account list is config.


8. Model routing (multi-model setups)

If more than one model or agent CLI is available, keep a small ranking table of the models you use, scored on three axes: cost, intelligence (how hard a problem it can be handed unsupervised), and taste (UI/UX, code quality, API design, copy). The table is config — keep it private and current. This section is the policy for using it.

A worked, harness-specific setup for all of this is in examples/orchestration-workflow.md.


9. Delegation and long-running work

Rules for work that spans subagents, background jobs, or hours. The theme: files are the state, context is scarce, and the user is not a polling target.


10. Guardrails and recovery

Rules for keeping autonomous work safe when things go wrong — and for deciding, in writing, when a human takes over.


11. Knowledge succession (skill libraries)

AGENTS.md (§1) and the fix log (§2) cover a repo’s day-to-day operating knowledge. Some repos also carry knowledge that lives only in one person’s head — the debugging instincts, the settled arguments, the unwritten rules nobody documented because the senior engineer just knew them. When that knowledge needs to survive the person, or needs to run on a cheaper model than the one that holds it today, generalize it into a skill library (.claude/skills/<name>/SKILL.md or the harness-equivalent path) instead of letting it stay tacit.

A skill library is a compiled artifact in the same sense as §2’s “compile, don’t retrieve”: it is the settled output of someone’s tacit judgment, written once so a reader gets the answer directly instead of re-deriving it from raw history, Slack threads, or trial and error. A library that just links out to source material without stating the settled rule has not actually succeeded the knowledge — it has relocated the retrieval step.

This is expensive relative to a normal AGENTS.md update, so reserve it for knowledge that is genuinely at risk of being lost or that must run on a materially cheaper model than the one that holds it — not as the default way to document a repo.


Common rationalizations

The standard fails one skipped step at a time, and every skip arrives wearing a plausible excuse. These are the recurring ones, each with why it doesn’t hold. An agent about to act on an excuse from the left column should treat that as the signal to stop and follow the section on the right instead.

Rationalization Reality
“I’ll add this note to CLAUDE.md too, so it’s visible everywhere.” Two copies is the exact failure mode §1 exists to prevent. The second copy starts drifting the moment it lands; put it in AGENTS.md once.
“This fix is too small to log.” Size of fix and cost of rediscovery are unrelated — one-line fixes with invisible causes are exactly what the fix log (§2) is for. If it took real digging, log it.
“The fix-log entry exists; anyone can grep for it.” Retrieval isn’t compilation (§2). If the entry implies a standing rule, promote the rule into AGENTS.md — an entry only found by searching protects nobody by default.
“I’ll update the paired file in a follow-up.” The follow-up is the step that never happens; that’s why the pair is listed in ## Keep in sync (§3). The sync is part of this change, not a second task.
“My identity is on the commit, so the agent trailer is redundant.” The author line says whose commit it is; the trailer says how it was made. Agent involvement must never be invisible (§5), whoever’s name is on it.
“Safer to put this on a branch.” Unless it’s risky per §6’s list (migration, wide refactor, hard to revert, build-breaking), the branch is ceremony that leaves litter behind. Safe-by-default is main.
“Tests are slow and this change is obviously safe.” “Obviously safe” is a self-grade, and the author is not the judge (§10). The ship gate exists precisely for changes that look safe. Run it.
“The output looks right, so it’s done.” Looks-right is how slop (§2) ships. Done is what the tests and an independent check say (§10) — verify against the contract, not the vibe.
“I matched the file next to it, so it’s consistent.” In a repo with strata (§1), the nearest file is a random layer, not the current one. Match the layer AGENTS.md names as current, and leave frozen layers unseeded.
“The newest pattern has the most files, so that’s the canonical one.” File counts and recency establish which layers are live, never which one is meant to win (§1). That’s a decision a human makes; inferring it enshrines a layer nobody chose.
“The query ran clean, so the numbers are right.” Clean execution proves the query ran, not that it covered the right rows (§10). State the population and reconcile it against a second source before reasoning on the result.
“We’re still learning the tool, so a rough result is expected here.” Then it wasn’t production work (§10). Learning gets its own bounded space; a real deliverable clears the same gates regardless of how new the tooling is.

Migration recipe (monolithic CLAUDE.md → standard)

  1. Back up the current CLAUDE.md (scratchpad copy; git init + commit first if the repo isn’t under git).
  2. Rename/move the content into AGENTS.md (if AGENTS.md exists as a stub, merge into it; if it’s a duplicate, the content is already there).
  3. Replace CLAUDE.md with the single line @AGENTS.md.
  4. Extract any inline “corrections / lessons / gotchas log” into docs/solutions/*.md with frontmatter; leave a pointer in AGENTS.md.
  5. Add a ## Keep in sync block for this repo’s drift-prone file pairs.
  6. Add the SessionStart hook only if the repo has silent-failure config.
  7. Verify: head -2 CLAUDE.md shows the include; diff AGENTS.md against the backup to confirm zero content loss (relocation only); git diff is reviewable.