返回

CLAUDE.md 越写越长反而失效:Claude Code 三种约束加载机制的演化

为什么 CLAUDE.md 写到 200 行后规则反而失效?从一份朴素 CLAUDE.md 演化到 CLAUDE.md + skill + memory 三层结构,揭示约束 Claude 的关键不在内容堆积,在加载时机。

从一个具体问题开始

打开你的 CLAUDE.md,数一下行数。如果超过 150 行,你大概率遇到过这种情况:明明白纸黑字定了某条规则,Claude 在对话里就是不执行。你去补这条规则,加上"重要"、“必须”、“禁止"这些字眼,第二天它还是跳过去,连提都不提。

这条路是错的。问题不在规则的措辞,在它怎么被加载到模型的注意力里。

我自己摸索过这个过程。最早的版本是把所有约束塞进一份 CLAUDE.md,理由很自然:这个文件存在的意义就是约束 Claude 在项目里的行为,所有规则当然都进这里。文件超过 100 行之后,规则的执行率开始下滑。我第一反应是补字眼、加层级、加大写,没用。后来才意识到 Claude Code 给作者的约束工具其实不止这一种,每种的加载机制完全不同,加载机制错了,再好的内容也救不回来。

朴素方案为什么不够

第一版的逻辑很自然:所有约束塞进同一份 CLAUDE.md。

这个方案在文件不超过 50 行的时候真的有效。模型能完整读完每一条,注意力足够覆盖。问题从某个临界点开始出现,我的经验是大约 100 行之后,再往里加规则就开始变成背景。模型每次启动会话都会读一遍 CLAUDE.md,但读到第 150 行的时候,前 50 行已经被后面的内容稀释掉了。具体哪条会被忽略看运气。

这背后是注意力机制的物理限制。模型不是"先记住一份文档,再逐条执行”,它在每次响应时把整段 prompt(包括 CLAUDE.md 全文)和当前任务一起做加权。条款越多,单条被认真处理的权重就越低。一份 200 行的规则文件,约束力还不如一份 30 行的。这不是模型偷懒,是一份太长的输入和具体任务在争夺有限的注意力预算。

更隐蔽的问题是 CLAUDE.md 里规则常常和"项目动态信息"混在一起。比如有人把"主要文件在 src/foo.go"和"提交前跑 make test"写进同一个文件。前者会过期,后者不会,但两者一起被加载。模型一旦发现某条不再准确,对其他条款的执行也会跟着变松——这是"过期信息拉低整份文件可信度"的连锁反应。

加更多规则解决不了这件事。问题不在文本,在加载方式本身。

关键 insight:把视角从"内容"切到"加载机制"

一条规则要真正生效,至少要跨过三关:在合适的时机被加载到上下文,在加载后被注意到,在被注意到后被执行。CLAUDE.md 这种"启动期一次性全量加载"的方式,把所有重量都压在第一关——它保证规则一定进上下文,但代价是后面两关的成功率会随文件长度衰减。

约束系统的设计如果只考虑内容不考虑加载,等于把所有重量都堆在第一关上。规则越多,注意力关就越难过。

真正可控的设计要把加载机制当成第一公民。不同规则应该有不同的加载条件:有的应该每次都生效,有的应该只在做某类任务时生效,有的应该只在过往讨论过类似问题时才被想起来。

Claude Code 实际给作者提供的就是这样三种加载机制,按演化的顺序往下看。

第二代:把规则从"启动加载"改成"任务匹配"

skill 这个机制把"什么时候加载"从 CLAUDE.md 的"每次会话启动"换成了"任务类型匹配"。

具体怎么工作:每个 skill 是一个 markdown 文件,frontmatter 写一个 description 说明它"用于 X 场景"。会话开始时 Claude 只看到所有 skill 的 name 和 description,正文不进上下文。当用户的某个任务匹配上某个 skill 的描述时,那个 skill 的全文才被读进来。

karpathy-guidelines 是个典型例子。它的 description 大致是"写、审、改代码时使用"。当你随便聊天或只是看代码时,它的内容根本不在上下文里——这时候它写得再好也不影响 Claude。当你说"帮我改一下这个函数",描述匹配,正文 4 条规则被注入:先想再写、保持简单、外科式修改、目标驱动。

这个机制解决了 CLAUDE.md 的核心痛点:规则不再永久占注意力。代价是 description 写得不好的话,规则永远不被触发,等于没写。

karpathy-guidelines 那 4 条本身的设计也值得拆开看。每一条都极短,每一条都绑定动作。“先想再写"如果只写"要谨慎"等于没写,它接着写的是 State your assumptions explicitly. If uncertain, ask.——动作明确,模型读完每条都知道下一步该输出什么形态的内容。这种"动作即条款"的写法,是 skill 内容能在被加载的瞬间立刻起作用的前提。

我原本以为 karpathy 这套规则的有效性来自内容本身——毕竟"先想再写、保持简单"这些原则任何工程师培训手册都有。后来才看清,4 条本身没有新东西,关键在它绑在 skill 这个机制上:按需加载 + 动作明确。同样的 4 条字写在 CLAUDE.md 里,效果会差一截。

第三代:把规则从"作者写"改成"对话生长”

skill 解决了"按任务加载",但还有一类规则它处理不了:作者自己也不知道的偏好和经验。

memory 是为这类情况设计的。它的加载机制不是任务匹配,是语义匹配。每条记忆是独立的 markdown 文件,附带一段 description 说明它是关于什么的。当当前任务和某条记忆的 description 语义接近时,那条记忆才被读进上下文。

更关键的是它的写入方式。memory 不是作者预先写的,是 Claude 从对话里自动提取。Claude Code 把记忆分成四类:user(关于用户)、feedback(关于工作方式)、project(关于项目)、reference(指向外部系统)。每一类有不同的写入触发条件。比如 feedback 不只在你纠正 Claude 时写入,还在你确认了某个非显然的做法可行时写入。

举个例子:你做某个判断时 Claude 选了一个不太常规的方案,你没纠正反而说"对,就这样"。这就是值得 memory 沉淀的信号。下次遇到同类决策,那条记忆会被语义匹配到,Claude 不需要重新猜。

代价是 memory 会过期。一条说"X 函数在 internal/cache.go" 的 memory 三个月后可能完全不准。所以这层有一条元规则:基于记忆做推荐之前,先验证记忆里的事实还成立。前两层不需要这条,因为内容由作者负责正确性;memory 由 Claude 自己负责正确性,得自己判断这条记忆还活着没。

三代怎么协同

三代不是替代关系。第一代解决"项目级硬约束",第二代解决"按任务类型生效的规则",第三代解决"从对话里学到的、动态变化的偏好"。它们的差别可以用一张表并排看:

载体 加载时机 内容来源 衰减规律 维护责任
CLAUDE.md 每次会话启动 作者手写 文件越长,单条越淡 作者
skill 任务描述匹配 作者手写 不衰减(按需触发) 作者
memory 语义匹配 Claude 自动提取 内容随项目演化而过期 Claude(带元规则)

下面这张图把三种加载机制的差别可视化出来:

flowchart TD
    A[新会话启动] --> B[CLAUDE.md 全量进上下文]
    A --> C[扫描 skill description]
    A --> D[扫描 memory description]
    U[用户输入任务] --> M{匹配判断}
    C --> M
    D --> M
    M -->|描述命中| S[加载对应 skill 全文]
    M -->|语义近似| R[加载对应 memory 全文]
    B --> X[模型响应]
    S --> X
    R --> X

错放层最常见的两种症状:把动态学习的内容(比如"主要文件在哪")写进 CLAUDE.md,导致整份文件被过期信息拖累;把通用行为准则(“写代码前先想清楚”)放进 memory,导致它的触发完全看 description 匹配的运气,有的会话生效有的不生效。

判断一条新规则该放哪一层,对我有用的不是规则手册,是几个反问。这条规则三年后还成立吗?不一定就先排除 CLAUDE.md。它是不是只在写某类代码时才需要?是的话属于 skill。它是不是从某次具体对话里冒出来的、之前我自己也没意识到?那它最适合先进 memory,沉淀几次再决定要不要"升格"。

落到目录大致是这样:

项目根/
├── CLAUDE.md                          # 30-50 行硬约束
└── ~/.claude/
    ├── skills/karpathy-guidelines/    # 通用行为准则
    └── projects/<project>/memory/     # 自动学习的偏好

三个位置加起来构成完整的约束系统。任何一个位置承担太多都会出问题。

什么时候这套是过度设计

三层结构有冷启动成本。新项目的 memory 是空的,前几次会话得多教几遍重复的事。skill 的 description 设计本身需要工程时间,写得不准就是隐形浪费——description 不准的 skill 永远不会被正确的任务触发,等于白写。memory 维护没人催,懒了几个月之后会出现 Claude 拿过期记忆做错误推荐的情况。

如果你的项目就是一个跑完就丢的 200 行脚本,这套分层是过度设计,一行 CLAUDE.md 都不写也能跑完。

划算的场景是中长期项目:会话密度高、上下文复杂、规则多到一个文件装不下。这时候三层结构里"按需加载 + 自动学习"的部分才真正起作用。你不必把所有约束都重新写一遍才能让 Claude 进入工作状态——大部分常识被 skill 兜住,项目特殊性被 memory 沉淀,CLAUDE.md 只放真正不变的少数硬约束。

留白

如果你打开自己的 CLAUDE.md 数过行数,超过了 100 行,多半有几条放错了抽屉。下次往里加东西的时候多停一秒钟问自己:这条规则是每次都该生效,还是只在某类任务里生效,还是更适合让 Claude 自己从对话里学?答案不一样,放的位置就不一样。

下次再想往 CLAUDE.md 里塞规则之前,先想想这条规则要在什么时候被加载。这一步换视角的代价很小,能省下的反复试错却不少。