CLAUDE.md / AGENTS.md — layered context that survives every session
Question 4 (Context engineering): What does your CLAUDE.md / AGENTS.md look like in a typical project?
Max-score answer: Tiered hierarchy, audited regularly — global + project +
.claude/rules/per area.
Why this matters in 2026
Section titled “Why this matters in 2026”Every Claude Code, Codex, or Cursor session starts as a blank brain. The agent has no idea which package manager you use, which folder is the API, whether you’re allowed to git push --force, or that the team agreed last sprint to stop using useEffect for data fetching. Without persistent context, you spend the first ten minutes of every session re-explaining the project — or worse, you skip that step and ship code that violates your own conventions.
CLAUDE.md and AGENTS.md are the fix. They’re plain-text Markdown files that the agent loads automatically at startup. A well-structured hierarchy means the agent walks into every session already knowing the global rules you care about across all projects (no semicolons, prefer rg over grep), the project-specific stack and commands, and the deep per-area rules that only matter when it’s touching auth, the database, or the payment integration.
The leverage is enormous. One hour spent writing tiered context pays back forever: every future session, every parallel agent, every team member who runs the same repo starts from the same baseline. Skip it and you’re paying the “explain the project again” tax forever — Anthropic’s own best practices guide calls a tuned CLAUDE.md the single highest-leverage change you can make to Claude Code.
The 2026 twist is AGENTS.md. It’s an open, tool-agnostic standard backed by OpenAI, Sourcegraph, Cursor, Google, and Factory (stewarded by the Agentic AI Foundation under the Linux Foundation) so the same file works with Codex, Cursor, Copilot, and any other agent. If you only write Claude-specific instructions, you’ll rewrite them all over again the moment a teammate opens Cursor. The max-score setup uses both — and treats them as a living, audited part of the codebase, not a one-time /init artifact.
What “max score” actually looks like
Section titled “What “max score” actually looks like”A tiered hierarchy means three layers of context, each scoped to a different concern, each small enough to not bloat the context window. Here’s the shape:
~/.claude/CLAUDE.md # Global: your personal defaults, valid in every repoproject-root/├── AGENTS.md # Project: tool-agnostic, read by Codex/Cursor/Copilot├── CLAUDE.md → AGENTS.md # Symlink so Claude Code reads the same file└── .claude/ ├── CLAUDE.md # Claude-specific overrides (optional) └── rules/ ├── auth.md # Per-area: only loads when working on auth ├── db.md # Per-area: schema, migrations, query patterns ├── payments.md # Per-area: Stripe/Polar specifics ├── testing.md # Per-area: Vitest conventions, fixtures └── api.md # Per-area: route handlers, error shapesThe point isn’t to fill every layer — it’s that each layer has one job and you know exactly where a new rule belongs. Global = “true in every repo I ever touch.” Project = “true in this codebase, regardless of which agent reads it.” Per-area = “true only when the agent is editing this slice of the system.”
A regularly audited hierarchy means you review it on a cadence (monthly or per-sprint), prune what no longer applies, promote frequent corrections into rules, and demote rules that everyone now follows by reflex.
Current landscape (web-verified)
Section titled “Current landscape (web-verified)”Global (~/.claude/CLAUDE.md)
Section titled “Global (~/.claude/CLAUDE.md)”The global layer is your personal ~/.claude/CLAUDE.md — read once per session for every project on your machine. It’s where preferences that are true regardless of repo belong: your default editor, your preferred shell tooling (rg over grep, fd over find), default commit message style, when to ask before running destructive commands, and any auto-PR or browser-automation workflows you want the agent to follow everywhere.
## Defaults across every project
- Use `rg` (ripgrep) instead of `grep`/`find` for searching.- Single quotes in JS/TS imports; double quotes in JSX attributes.- Commit messages: conventional commits, present tense, no body unless needed.- Never run `git push --force` to `main`/`master` without explicit confirmation.
## Browser automation
Default to `browser-harness` for E2E verification.Fallback: agent-browser → chrome-devtools MCP → Playwright.
## Auto-PR workflow
After substantive commits land locally, push and open a PR via `gh pr create`.On review comments, fetch with `gh pr view --comments` and address each onebefore re-pushing. Don't pause for confirmation on either step.Keep the global file under 200 lines. Frontier models can follow 150–200 instructions consistently; beyond that, rules start dropping silently.
Project (./CLAUDE.md or ./AGENTS.md)
Section titled “Project (./CLAUDE.md or ./AGENTS.md)”The project layer is the single most important file. It’s checked into the repo, shared across the team, and read on every session in this codebase. It should answer the questions a new contributor would ask in their first hour: what does this project do, what’s the stack, what are the commands, where do things live, and what are the non-obvious rules.
## Project overviewEdge-rendered docs site for developertoolkit.ai. Astro 5 + Starlight,Cloudflare Workers, D1 for auth/subscription, Polar.sh for payments.
## Stack & commands- `npm run dev` — Astro dev server on :4321- `npm run dev:cf` — Wrangler with real CF bindings- `npm run lint && npm run type-check && npm run test` — must pass before push- Deploy is CI-only on push to `master`. Do not run `npm run deploy` locally.
## Key directories- `src/content/docs/{en,pl}/` — bilingual MDX content- `src/lib/db/` — Drizzle schema + queries against D1- `src/pages/api/` — route handlers (use `locals.runtime.env` for bindings)- `public/paywall-inline.js` — vanilla JS paywall, do not TS-ify
## Conventions- Server components by default; only mark `'use client'` when truly needed.- Use Drizzle for D1 queries — no raw SQL except in `scripts/schema.sql`.- Astro env: `import.meta.env.PUBLIC_*` at build, bindings at runtime.
## Non-obvious traps- The paywall lives in `public/paywall-inline.js` (NOT TypeScript) and is loaded as a `<script>` from `Head.astro`. Edit the JS directly.- Submodules hold the private docs content. Run `npm run submodule:update` after fresh clone or CI will fail to build.Symlinking CLAUDE.md → AGENTS.md means Claude Code, Cursor, and Codex all read the same file. If you need Claude-only instructions (e.g., a specific hook), put them in .claude/CLAUDE.md so they don’t pollute the shared file.
Per-area rules (.claude/rules/)
Section titled “Per-area rules (.claude/rules/)”This is the layer most setups skip — and it’s the layer that separates a max-score answer from a merely-good one. The .claude/rules/ directory holds modular .md files, each scoped to one slice of the system. Anthropic’s memory docs confirm that .claude/rules/*.md files load alongside CLAUDE.md and support YAML frontmatter with a paths field, so rules can be scoped by glob and only load when the agent is actually touching those files.
---paths: - "src/lib/auth/**" - "src/pages/api/auth/**"---
# Auth rules
We use BetterAuth with Email-OTP + Polar.sh for subscriptions.
## Mental model- BetterAuth owns sessions, accounts, and the OTP flow.- Polar.sh owns subscription state, fed via webhook into `subscription_access_tokens`.- A signed-in user with no active subscription gets a 402 from gated routes.
## Do- Use `auth.api.*` helpers from BetterAuth; never roll your own session check.- Read subscription state from `subscription_access_tokens`, not from Polar's API at request time (latency + rate limits).- Always `await auth.api.getSession({ headers })` first thing in a gated handler.
## Don't- Don't create raw users in the `users` table. Always go through BetterAuth.- Don't call Polar's API from a Worker request handler — it adds 300ms+.- Don't store OTP codes in plaintext anywhere — they're hashed in BetterAuth.
## Common bugPolar webhook bridge is async. If you check subscription state immediatelyafter checkout success, the token row may not exist yet. Poll with backoffor use the redirect-with-state pattern in `src/pages/checkout/success.astro`.A file like that only loads when the agent is editing auth files — so it can be detailed without crowding out the rest of your context. The same pattern repeats for db.md (schema, migration policy, common Drizzle gotchas), payments.md (Polar.sh webhook flow, idempotency keys), testing.md (Vitest conventions, fixture patterns), and so on. The Knightli walkthrough shows the four-part setup — CLAUDE.md, rules, memory, hooks — and how they interact.
Some teams symlink rules across repos (the .claude/rules/ directory supports symlinks with circular-link detection), letting a “house style” set of rules stay in sync across every internal project.
Step-by-step: building your tiered hierarchy
Section titled “Step-by-step: building your tiered hierarchy”-
Start global, with a 30-minute writing session.
Open
~/.claude/CLAUDE.mdand write down the things you find yourself telling every agent on every project: your shell preferences, your default tool choices, your “never do X without asking” rules. Keep it tight — under 100 lines is plenty. Anything project-specific does not belong here. -
Run
/initin your most-used repo.In Claude Code, type
/init. It generates a project-aware first draft ofCLAUDE.mdby reading your repo. Treat it as a starting point, not the final artifact. Rename it toAGENTS.md(or symlink), then prune the generic content and replace it with the specific, testable, “this is how this project works” rules. -
Add the why to every non-obvious rule.
“Use Drizzle ORM” is a rule. “Use Drizzle ORM — raw SQL bypasses our D1 connection pooling and breaks under load” is a rule the model can generalize from. The HumanLayer guide puts it bluntly: a rule with a reason survives edge cases; a rule without one gets ignored.
-
Create
.claude/rules/and move the deep stuff there.Every time your root
CLAUDE.md/AGENTS.mdstarts pushing past 200 lines, split. Auth-specific rules go to.claude/rules/auth.md. Database rules todb.md. Testing conventions totesting.md. Each file gets apaths:frontmatter so it only loads when the agent is actually touching that area. -
Symlink for cross-tool compatibility.
ln -s AGENTS.md CLAUDE.md. Now Claude Code, Codex, and Cursor all read the same project context. If you genuinely need Claude-specific behavior (a hook, a slash-command reference), drop it in.claude/CLAUDE.md— that file is Claude-only. -
Audit on a cadence.
Put a 30-minute “context audit” on your calendar — monthly, or end of every sprint. Open the rules. Delete what’s now obvious. Promote things you’ve corrected the agent on three times into rules. Demote rules nobody references. The half-life of a useful instruction is short; treat the hierarchy as living code.
-
Verify with a fresh session.
Start a new Claude Code session in the repo and ask: “Summarize the project context you have loaded.” If the answer doesn’t match what your hierarchy promises, something’s not loading — check file paths, frontmatter syntax, and whether you symlinked correctly.
Common pitfalls
Section titled “Common pitfalls”Bloating to 500+ lines in one file. Past ~200 lines in a single file, instructions start dropping silently. The model isn’t telling you it lost the rule — it just isn’t following it. Split into .claude/rules/ long before you feel pressure to.
Set-and-forget after /init. A CLAUDE.md generated on day one and never touched again becomes stale within a sprint. New conventions emerge, old ones die, and the file lies. Treat it like any other code: PRs that change conventions also update the rules.
Aspirational rules with no teeth. “Write clean code” tells the agent nothing. “Use Server Components by default; only add 'use client' when you need state, refs, or browser-only APIs” is testable. Every rule should be something you could review a PR against.
No “why” anywhere. A rule without a reason can’t generalize. The first time the agent hits an edge case, it’ll guess — and probably wrong. Two sentences of rationale per rule are worth more than ten rules without them.
Putting secrets, personal style, or session-specific state into the project file. The project file is checked in and shared with the team. Personal preferences belong in your global file. Run-specific state (a task list, a TODO) belongs in a scratch buffer, not in AGENTS.md.
Claude-only when the team uses Cursor too. If a teammate opens the same repo in Cursor or Codex, they get zero context unless you’ve written to AGENTS.md (or symlinked). 60,000+ repos in 2026 now use AGENTS.md as the default — it’s the safer bet for multi-tool teams.
Per-area rules with no paths: scope. A rule file without frontmatter loads on every session, defeating the whole point of .claude/rules/. Add paths: to anything area-specific so it only loads when relevant.
How to verify you’re there
Section titled “How to verify you’re there”- I have
~/.claude/CLAUDE.mdwith personal defaults, under 100 lines. - I have a project-level
AGENTS.md(orCLAUDE.mdsymlinked to it) checked into every active repo. - The project file answers “what is this project, how do I run it, where do things live, what are the non-obvious rules” — and stops there.
- I have a
.claude/rules/directory with at least one area-specific file (auth, db, payments, testing — pick what’s complex in your codebase). - Every per-area rule file has a
paths:frontmatter so it only loads on relevant files. - Every non-obvious rule has a one-sentence “why” attached.
- A fresh session, asked to summarize loaded context, mentions the project’s real stack and the real area rules — not generic boilerplate.
- I have a recurring 30-minute audit on the calendar (monthly or per-sprint).
- The root project file is under 200 lines; deep stuff has been split into
.claude/rules/. - The hierarchy is multi-tool safe — Cursor and Codex teammates get the same context as I do.