skip to content
coograph 17

Instruction layout

An agent reads its instruction files before it reads any code, so every byte in them is paid on every task. Left alone they grow. On one real monorepo, after about ten days of agent work, a typical web task loaded about 120 000 tokens of instructions before touching a file, and one workspace map alone was 91 645 tokens, 227 KB of it table padding.

The causes are structural. A map turns into a changelog, one dated section per change. One feature is described in three files. Hard rules are copied into every root file, so they are paid for three times. Coograph now gives those files a shape, measures them against it, and tells you when they drift.

Three tiers, plus gotchas

TierWhatDefault budgetLoaded
Always-loadedRoot CLAUDE.md, AGENTS.md, .github/copilot-instructions.md, plus every file they pull in with @path9 000 tokens combinedEvery session
Router<workspace>/AGENTS.md (and a nested CLAUDE.md): commands, gotcha pointers, an area index2 000 eachWhen the agent enters that folder
Docdocs/features/<area>.md, one per product area, current state only, with paths: frontmatter8 000 eachOn demand
GotchasGOTCHAS.md entries8 000 per file, 800 bytes per entryOnly when an entry’s paths or commands match

Each rule lives in one file. Hard rules and the workflow are in AGENTS.md, code conventions in copilot-instructions.md, and CLAUDE.md imports both with @ and adds only what is specific to Claude Code. Coograph’s own template measures 5 510 of its 9 000 always-loaded tokens.

Content rules are short: no dated sections (history lives in OpenSpec and git), bullet lists over wide tables, one home per rule.

The config

.github/layout/layout.json is the project’s. It is created once from the shipped seed and never overwritten by sync; new seed keys are added without touching your values.

{
  "always_loaded": ["CLAUDE.md", ".claude/CLAUDE.md", "AGENTS.md", ".github/copilot-instructions.md"],
  "routers": ["*/AGENTS.md", "*/*/AGENTS.md", "*/CLAUDE.md", "*/*/CLAUDE.md"],
  "docs": ["docs/features/*.md"],
  "gotchas": ["GOTCHAS.md", "*/GOTCHAS.md", "*/*/GOTCHAS.md"],
  "budgets": {"always_loaded": 9000, "router": 2000, "doc": 8000, "gotchas": 8000},
  "guard": {"ignore": ["**/*.lock", "openspec/**", "**/*.md"], "skip_marker": "[skip docs]"},
  "structural_factor": 1.25
}

A single-package repo sets routers to []; a monorepo lists its workspaces. Linked git worktrees are never measured: their files duplicate the main checkout’s.

Gotchas

GOTCHAS.md holds traps learned the hard way, one entry each:

## expo-export-ignores-shell-env
- **Symptom:** `expo export` builds with the old API URL after `export API_URL=...`
- **Cause:** `.env` wins over the shell for `expo export`.
- **Fix / rule:** change `.env.production`, never the shell.
- **paths:** `apps/mobile/**`, `app.config.ts`
- **commands:** `expo export`
- **confirmed:** 2026-10-01

Gotchas are never loaded up front. In Claude Code, the gotcha-surface.py hook puts a matching entry in front of the agent once per session, at the moment it edits a file under the entry’s paths or runs a command that contains one of its commands. Other tools reach the file through the routing table in AGENTS.md. /coograph-debug offers an entry when a root cause was a trap the code does not show, and Retro proposes pruning an entry whose paths no longer match any file.

Checks

python3 .github/layout/layout.py --budget            # exit 1 when anything is over
python3 .github/layout/layout.py --guard BASE HEAD   # exit 1 when covered code changed but its doc did not
python3 .github/layout/layout.py --json              # the full measurement, which Retro reads

--budget also fails on an @ import that points nowhere (Claude Code skips it without a word) and on an invalid gotcha, and prints a STRUCTURAL verdict when small edits cannot close the gap: the always-loaded tier is more than 25% over budget (structural_factor), a router or doc is over twice its budget, or a file has three or more dated headings.

--guard maps each changed source path to the feature docs whose paths: cover it and fails when none of them changed in the same diff. Paths no doc covers never fail. Put [skip docs] in the PR title to skip it once.

/coograph-init offers both as opt-in: a CI workflow and a pre-commit hook. The workflow fetches the checker itself when a project keeps coograph tooling out of git.

When files have grown too far

Every Claude Code session start says so:

[layout] STRUCTURAL: apps/web/AGENTS.md over twice its budget (+5 more), run /coograph-docs-restructure

/coograph-docs-restructure takes stock, works out the project’s shape (workspaces, tools in use, product areas, rules stated twice, traps buried in maps), and writes an OpenSpec with a fact list per area. Nothing is edited until you approve it. After approval it migrates one area per agent and checks every fact against the code: confirmed facts are kept with their file:line, facts the code cannot confirm or contradict (a deploy step, a console setting) are kept and marked, and only facts the code contradicts, history, and duplicates are dropped, each with a reason. On the monorepo above it took the always-loaded set from 30 745 to 7 914 tokens and the 91 645-token web map to 591; no workspace map is over 700 now.

Next