Rules & Auto Memory

Rules & Auto Memory

Part 2 — Context Engineering Chapter 3 of 5
Listen to this article
Read aloud in your browser

Overview

This chapter covers:

  • Why splitting CLAUDE.md into rules only helps if the rules are scoped
  • paths: frontmatter, when a scoped rule actually fires, and the glob edge cases that make one match nothing
  • The four kinds of note Claude writes for itself, and the larger set it deliberately refuses to write
  • The 200-line ceiling on MEMORY.md and what happens to everything past it
  • Which of these reach a subagent, and which survive a compact

Two systems, opposite authors

Both load at the start of every session. The difference is who writes them.

CLAUDE.md and rulesAuto memory
Written byYouClaude
HoldsInstructions and conventionsCorrections you gave, and preferences it inferred
ScopeProject, user, or organisationPer repository, on this machine
Good for“Always do X”“You told me last week that X”

Neither is enforcement. Both are context Claude reads and tries to honour — the Chapter 6 point, and still the reason a guarantee needs a hook.

Rules

Chapter 6 ended on a limitation: @path imports organise a large CLAUDE.md without reducing what loads. .claude/rules/ is the mechanism that reduces it.

The failure it addresses has a name worth borrowing: priority saturation. A 400-line CLAUDE.md holding React conventions, API guidelines, migration warnings, security policy and testing rules is carrying every one of them into every request. When Claude edits a React component it is also holding your database-migration safety rules; when it writes SQL it is holding your React patterns. When everything is high priority, nothing is — attention gets spread across instructions that do not apply to the task in front of it.

The arithmetic is the argument. A 400-line CLAUDE.md is 400 lines competing for attention on every prompt. A 50-line CLAUDE.md plus five 50-line rule files is 100 lines active at any moment — the base file plus the one rule that matches what Claude is actually touching.

your-project/
└── .claude/
    ├── CLAUDE.md
    └── rules/
        ├── code-style.md
        ├── testing.md
        └── frontend/
            └── components.md

Every .md file is discovered recursively, so subdirectories are for your benefit, not Claude’s. A rule with no frontmatter loads at launch with the same priority as .claude/CLAUDE.md — which means splitting a 400-line CLAUDE.md into eight unscoped rule files has changed nothing except your file browser.

The payoff comes from the paths field:

---
paths:
  - "src/api/**/*.ts"
---

# API rules

- Validate input on every endpoint
- Return the standard error shape

That file is not in context when Claude is editing CSS. It enters when Claude reads a file matching the pattern — not on every tool call, and not because the conversation is about the API.

~/.claude/rules/ works the same way for personal rules across every project. They load before project rules, which gives project rules the later, stronger position.

Which of the two does an instruction belong in?

The split is universal versus file-shaped:

CLAUDE.mdA rule file
Project overview and tech stackPatterns specific to one file type
Build and test commandsFramework guidelines — React, Spring, Django
Architecture overviewSecurity rules for one sensitive directory
Conventions that hold everywhereTest-writing conventions
Gotchas that affect everythingMigration safety rules
Git workflow and branchingAPI design guidelines

The test: would this instruction be wrong, or merely irrelevant, while Claude edits an unrelated file? Merely irrelevant means it should be scoped.

Watch what loads

Claude reads a file
In context

The lesson in the numbers: unscoped rules are CLAUDE.md with extra steps. Scoping is the entire feature.

Glob syntax, and three ways to write one that matches nothing

Patterns are globs — **/*.ts, src/**/*, *.md, src/components/*.tsx — and brace expansion works:

paths:
  - "src/**/*.{ts,tsx}"
  - "tests/**/*.test.ts"

Three edges are worth knowing, because each fails silently:

  • Brace expansion has a budget. Each group multiplies: {a,b}/{c,d}/*.{ts,tsx} is eight patterns. A rule’s whole paths list shares a budget of 1,000 expanded patterns and 4 MiB. Exceed it and the pattern is used unexpanded, so its literal braces match no file. Before v2.1.217 this stalled or crashed the CLI at startup instead.
  • [ opens a bracket expression. photos [2024/** is not a path with a bracket in it — it is an unterminated [abc] class, so it matches nothing. Escape it: photos \[2024/**. Before v2.1.207, one invalid pattern made the Read tool fail for every file the rule was checked against.
  • A rule that never matches looks identical to a rule that is being ignored. There is no error either way.

That last point is why the InstructionsLoaded hook exists: it logs which instruction files loaded, when, and why. For debugging a scoped rule it is the only direct evidence.

Sharing rules between projects

.claude/rules/ follows symlinks, and circular links are detected rather than fatal:

ln -s ~/company-standards/security.md .claude/rules/security.md
ln -s ~/shared-claude-rules .claude/rules/shared

In a monorepo where other teams’ instruction files get swept up, claudeMdExcludes skips them by glob — and it covers rules directories, not just CLAUDE.md:

{
  "claudeMdExcludes": ["/home/me/monorepo/other-team/.claude/rules/**"]
}

Patterns match against absolute paths, arrays merge across settings layers, and for a symlinked rule a pattern matching either the link or its target excludes the file (v2.1.239+; before that, only the target worked).

Auto memory

The other half needs no files from you. As you work, Claude writes notes for itself into a per-repository directory, and reads them back at the start of the next session.

It saves four kinds, tagged in each file’s frontmatter:

typeWhat it holds
userYour role, expertise, working preferences
feedbackCorrections you gave, and approaches you confirmed
projectOngoing work, deadlines, decisions not derivable from the code
referenceWhere information lives outside the project — trackers, dashboards

What it refuses to save is the more interesting list. Claude skips anything derivable from the codebase — architecture, file paths, how a bug was fixed — and anything your CLAUDE.md already says. It is the same heuristic /doctor applies when trimming CLAUDE.md, and for the same reason: a note that repeats the code is a note that costs context and pays nothing.

It also does not save every session. It writes when something looks useful later, which is why “Saved 2 memories” appears some turns and not others.

Where it lives, and the ceiling

~/.claude/projects/<project>/memory/
├── MEMORY.md            # index, one line per memory
├── user_role.md
└── feedback_testing.md

<project> derives from the git repository, so every worktree and subdirectory of one repo share a single memory directory. It is machine-local: nothing syncs to another machine or to a cloud session.

The number that matters: only the first 200 lines or 25 KB of MEMORY.md, whichever comes first, load at session start. Everything past that is silently dropped on the next load. Topic files are not loaded at startup at all — Claude opens them on demand with its ordinary file tools.

That is why MEMORY.md is an index: one line per memory, detail pushed into topic files. Claude Code measures the file after each write, reminds Claude to shorten it near the limit, and returns an error telling it to rewrite the index once past. The limit applies only to MEMORY.md — a CLAUDE.md loads in full up to 4 MiB.

Memory files are also exempt from the session-transcript retention sweep that cleanupPeriodDays drives. They stay until you or Claude edits them.

Controlling it

WhatHow
Browse and edit/memory, then open the auto memory folder — plain markdown, editable and deletable
Turn it off for one project"autoMemoryEnabled": false in that project’s settings
Turn it off everywhereThe /memory toggle, or CLAUDE_CODE_DISABLE_AUTO_MEMORY=1
Move the directory"autoMemoryDirectory": "~/my-memory" — absolute or ~/ only

Asking Claude to “remember that we use pnpm” writes to auto memory. Asking it to “add that to CLAUDE.md” writes to the file. The distinction is worth making deliberately: auto memory is yours and machine-local, CLAUDE.md is your team’s and committed.

Since v2.1.214 each memory file with frontmatter gets a modified timestamp on write, so both you and Claude can see how stale a fact is. A recalled memory reflects what was true when it was written — if it names a flag or a file, that is a claim to verify, not a fact to act on.

The third layer

CLAUDE.md and auto memory are the two you configure. There is a third that you do not, and naming it completes the picture:

LayerWritten byLoadedShared
CLAUDE.md and rulesYouEvery session startYour team, via git
Auto memoryClaudeFirst 200 lines every session; topic files on demandNobody — machine-local
Session memoryClaude CodeThe conversation itself, and its summariesNobody — per session

Session memory is Chapter 9’s territory: the transcript on disk, the summaries compaction produces, and what --continue and --resume restore. It is what lets you pick up mid-thought rather than mid-project.

The three answer different questions. CLAUDE.md says how this project works. Auto memory says what I learned about you. Session memory says where we were. A setup missing any one of them re-explains something every day.

Where each mechanism reaches

The last question is which of this survives the events that clear context.

Reloads after /compactLoaded into a subagent
Project-root CLAUDE.mdYes — re-read from diskYes
Nested CLAUDE.md, path-scoped rulesWhen Claude next touches a matching fileOn the same terms
Auto memoryYesNo — except a fork, which inherits the parent conversation
An instruction given in chatNoNo

A subagent gets its own auto memory directory if you enable the memory field on it; the main conversation’s memory is not shared into it.

So the diagnostic from Chapter 6 sharpens: if something Claude knew is gone after a compact, it was said in conversation only. If something it knew is gone inside a subagent, it was probably auto memory.

Summary

  • Rules exist to defeat priority saturation — when everything is high priority, nothing is. A 50-line CLAUDE.md plus five scoped rule files keeps ~100 lines active instead of 400.
  • Splitting CLAUDE.md into rules saves nothing unless the rules are scoped. An unscoped rule loads at launch like any other instruction.
  • Placement test: would the instruction be wrong, or merely irrelevant, in an unrelated file? Merely irrelevant means scope it.
  • A paths: rule fires when Claude reads a matching file, not when the conversation is about that area.
  • User rules load before project rules, so project rules land later and stronger.
  • Three silent glob failures: an over-budget brace expansion is used unexpanded, an unescaped [ matches nothing, and neither reports an error. InstructionsLoaded is the way to see what really loaded.
  • Auto memory saves four kinds of note and deliberately skips anything derivable from the code or already in your CLAUDE.md.
  • Only the first 200 lines or 25 KB of MEMORY.md load. It is an index; detail belongs in topic files, which load on demand.
  • Auto memory is per-repository and machine-local, shared across worktrees, and exempt from the transcript retention sweep.
  • Auto memory does not reach a subagent, but CLAUDE.md does.
  • There are three memory layers, not two: your instructions, Claude’s notes, and the session itself.
  • Full reference: memory and rules, monorepos.

Chapter 8 is the budget all of this spends: the context window — what fills it, what /compact keeps, and the habits that stop you hitting the ceiling mid-task.