You dispatch "add an audit log entry when a user is deactivated" into a codebase where deactivation lives behind a UserLifecycleManager resolved from a DI container, registered by a decorator, wired by a config file that names classes as strings. The agent greps for "deactivate", finds three near-misses and no handler, burns forty reads tracing the container, and finally does what 1.5 taught you optimizing generators do at a dead end: it writes a plausible new deactivateUser function and calls it from the route. Now there are two deactivation paths. The code was very clever. That was the problem.
Agent legibility is a mechanical property
An agent experiences your codebase only through its context window. It navigates by search and by reading files, every file read spends tokens from a budget that degrades as it fills (1.3), and everything it cannot find it will infer from plausibility (1.5, hallucinated APIs). Architecture, to an agent, is therefore not aesthetics. It is the answer to three mechanical questions: how many reads to find the right place, how many tokens to hold the relevant boundary, and how much of what it read can be trusted literally.
That gives you concrete design rules, each traceable to a failure class you already know:
- Small files, one responsibility each. A 200-line file is read in one gulp and fits alongside its callers. A 2,000-line file either blows the read budget or gets read in fragments, and fragments are how an agent misses the invariant declared eighty lines above the function it is editing. This course's own build rules cap components at about 200 lines for exactly this reason.
- Explicit names over clever abstraction. Agents navigate by grep before they navigate by comprehension.
cancelSubscriptionis findable;handledispatched through a command bus keyed on"sub.cancel"is invisible to search, and invisible-to-search is the precondition for the cold open's duplicate-path failure. Every reflection trick, magic string, and dynamic import is a place where search returns nothing and plausibility takes over. - Boundaries that fit a context. A module should be understandable, edit plus blast radius, inside one window: the file, its tests, its direct contracts. If understanding a change requires holding nine files from four directories, every dispatch into that area starts at a context fill level that 1.3 told you already degrades instruction-following.
- Colocated tests.
format-bytes.mjsnext toformat-bytes.test.mjsmeans the grader is one directory listing away from the work; the agent that reads one naturally reads the other, and your review (3.2) sees them in the same diff. - Convention over cleverness, boringly. Indirection is a loan against every future reader's context budget, and agents re-read the codebase from zero far more often than humans do: every session, in fact. A pattern that costs a human a one-time learning investment costs an agent that same investment on every dispatch, forever.
Here is the position, stated plainly: agent-legible architecture is just good architecture with the excuses removed. Small files, explicit names, and real boundaries were always right; humans could compensate for their absence with tenure, tribal knowledge, and IDE navigation, so clever codebases survived. Agents have no tenure. They make the cost of illegibility immediate and measurable, in wasted reads, degraded context, and invented functions, and a codebase that agents work well in is one your next human hire onboards into in a day. Optimize for the reader with no memory and you have optimized for everyone.
Docs are load-bearing now
In a human team, stale docs are a nuisance; people ask a colleague. An agent does not ask a colleague. What it reads is what it believes, which upgrades documentation from courtesy to infrastructure, and gives each doc type a specific mechanical job:
- CLAUDE.md carries what is always true and cannot be inferred from code: build commands, conventions that differ from defaults, repository etiquette, the gotchas. The docs' guidance is blunt about the include/exclude line: bash commands Claude can't guess, yes; anything Claude can figure out by reading code, no. Keep it short and prune it; a rule lost in a long file is a rule that does not fire, and if Claude keeps violating a written rule, the file is probably too long.
- Module READMEs are just-in-time context (the pattern the context-engineering post recommends over pre-loading everything): a paragraph per directory on what lives here, what the invariants are, what not to do. The agent reads the README of the module it is entering, not a wiki about the whole system.
- ADRs (architecture decision records) carry the why: "we chose SQLite over Postgres because single-process deployment; revisit if we shard." Their agent-facing job is to stop relitigation. An agent that finds an ADR treats the decision as settled context; an agent that finds bare code treats the same decision as an open question it is free to "improve" (drift, 1.5).
The corollary cuts: a wrong doc is worse than no doc, because it is context poisoning with your signature on it (1.5, and the same mechanism as the stale STATUS.md from 3.1). Every doc you add is a maintenance contract. Write few, keep them true, and update them in the same dispatch that changes what they describe, never as a separate someday-task.
The house of cards, and its audit
Agent-built systems accrete a specific pathology. Each dispatch produces something that works; the next dispatch builds on it without anyone re-examining the base; helpers get duplicated because finding the existing one cost more reads than writing a new one; three naming conventions coexist because three sessions each invented one. No single step failed review. The stack of them is a house of cards: it works, nobody can say why, and the next change collapses a floor you did not know was load-bearing.
The countermeasure is a recurring structure audit, run as a dispatch, read-only, producing findings rather than fixes:
DISPATCH: structure audit (read-only)
GOAL: a findings report on this repo's agent-legibility. No code changes.
SURVEY:
- Size: list every source file over 200 lines, worst first.
- Duplication: find functions or logic implemented more than once under different names.
- Dead surface: exports nothing imports; files nothing references.
- Test colocation: source files whose tests are missing or far away.
- Indirection: places where finding a behavior requires more than two hops (barrel re-exports, string-keyed dispatch, config-wired classes), with the hop chain spelled out.
- Doc truth: README and module docs that contradict the code as it exists now.
OUT OF SCOPE: fixing anything. Style opinions. Anything a linter already enforces.
VERIFICATION: the report lists file paths and line evidence for every finding, ranked by blast radius. I will spot-check three findings by hand before acting on any.
Then unwind one finding at a time, each as its own scoped dispatch with gates green before the next (3.1 and 3.2 doing their jobs). The unwind dispatch for the worst finding is usually some flavor of "collapse the duplicate implementations into one, migrate the callers, delete the losers", with the existing tests as the harness and DON'T TOUCH on everything else. Resist the mega-refactor: a house of cards is dismantled card by card, gates green at every step, or it is knocked over.
core/registry.ts maps string keys to handlers collected by a decorator at import time. Adding a behavior means knowing the incantation; finding one means reading the framework. Grep for the route returns nothing.
Ten minutes of orientation per session, paid every session, and a standing invitation for the agent to bypass what it cannot find.
billing/cancel-subscription.ts exports cancelSubscription, imported by name where it is used, test file adjacent, two-line README naming the module's one invariant.
Search finds it in one hop. The file plus its test fits in one read. The invariant travels with the code instead of living in someone's head.
Where it breaks
Over-fragmentation. "Small files" taken as religion produces five hundred ten-line files, and now every task needs thirty reads to assemble a picture a single well-shaped 150-line module would have given in one. The unit that matters is the boundary, not the line count: one concern per file, files sized so a concern is whole.
Doc sprawl. Documentation has the same failure curve as CLAUDE.md: past a size, it stops being read attentively, and its errors quietly poison. Ten crisp module READMEs beat one exhaustive architecture wiki that was true in March.
Legibility theater. Renaming things and splitting files while the real problem, a boundary that does not exist, stays put. If two modules mutate each other's state, no file organization fixes that; the audit's job is to find the missing boundary, and the unwind's job is to build it.
Grandfathered cleverness. Some indirection is earned: a plugin system that genuinely has plugins, an adapter layer you actually swap. The test is whether the indirection pays rent in the codebase you have, not the one the original author imagined. If nothing has ever been registered twice, the registry is a hop tax with no service behind it.
Lablab-3-3Audit the practice repo, unwind one finding
Goal: Run the read-only structure audit on your practice repo, verify the findings yourself, and fix exactly one through a gated dispatch.
Prereqs: the agentic-practice repo, with the accumulated labs of Parts 0 through 3 in it (a few months of agent-built accretion, which is exactly the specimen you want).
- Run the structure-audit dispatch from above in a fresh session. Read-only; if the agent starts editing, that is a scope violation you now know how to catch.
- Spot-check three findings by hand: open the files, confirm the duplication is real, confirm the dead export is actually unimported (
grep -rn "exportName" .). Auditors can hallucinate findings too; the audit report is agent output and gets skeptical review like any other. - Save the verified report as
audits/2026-07-structure.mdand log it in STATUS.md (known issues). - Pick the one finding with the best blast-radius-to-effort ratio. Write an unwind dispatch for it in 3.1's five-section shape, with the relevant tests named in VERIFICATION and, if any exist for the area, locked per 3.2.
- Execute, gate, review the diff against scope, commit; update STATUS.md and any module README the change made stale, in the same commit.
- Add a one-paragraph README to the messiest module the audit touched, stating what it is for and its one most important invariant.
Verify
audits/2026-07-structure.mdexists with at least four categories of findings, each carrying file paths and evidence, and your three spot-checks noted with their outcomes.- One finding is fixed, in a commit whose diff touches only files named in the unwind dispatch's IN SCOPE.
- Gates were green before and after the unwind (
npm testor your repo's equivalent, plus typecheck if applicable). - The new module README says something falsifiable (an invariant, a constraint), not marketing ("this module handles billing logic elegantly").
>Troubleshooting
- The audit report is vague ("some files are large"): your dispatch's VERIFICATION line was not enforced. Redispatch quoting it: every finding needs a path and line evidence. Vague findings are unfalsifiable and therefore unusable.
- A spot-check fails (the "duplicate" functions differ meaningfully): good catch, that is why step 2 exists. Strike the finding, note the miss in the audit file, and weigh the auditor's other claims accordingly.
- The unwind breaks tests the audit said were safe: stop, do not chase it in the same session. The finding's blast radius was underestimated; revert, split the unwind into smaller dispatches, and take the smallest one first.
Knowledge check
Knowledge check
Sources
- Best practices for Claude Code (CLAUDE.md include/exclude table, prune-when-ignored guidance, /init, delegating exploration to subagents): https://code.claude.com/docs/en/best-practices (fetched July 2026)
- Effective context engineering for AI agents (context as a finite resource with diminishing returns, just-in-time context over pre-loading, long-horizon state management): https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents (fetched July 2026)