<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>潘锦的空间 &#187; ClaudeCode</title>
	<atom:link href="https://www.phppan.com/tag/claudecode/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sun, 10 May 2026 02:26:45 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>Claude Code 的 SKILLS 技能渐进式披露实现原理解析</title>
		<link>https://www.phppan.com/2026/04/claude-code-ai-skills-source/</link>
		<comments>https://www.phppan.com/2026/04/claude-code-ai-skills-source/#comments</comments>
		<pubDate>Sun, 12 Apr 2026 03:47:23 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[skills]]></category>
		<category><![CDATA[渐进式披露]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2490</guid>
		<description><![CDATA[SKILLS 和 渐进式披露 是 A 家最早提出来的方案，也是 OpenClaw 火了后大家一直讨论的哪个技能 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com" data-pm-slice="0 0 []">
<p data-tool="mdnice编辑器">SKILLS 和 <strong style="color: #0e88eb;">渐进式披露</strong> 是 A 家最早提出来的方案，也是 OpenClaw 火了后大家一直讨论的哪个技能好用很核心的强依赖的实现逻辑。</p>
<p data-tool="mdnice编辑器">如果把 Claude Code 的 skills 理解成一堆 prompt 文件，后面的很多设计都解释不通。</p>
<p data-tool="mdnice编辑器">从其源码实现来看，会发现它在解决的核心问题是：<strong style="color: #0e88eb;">怎么让模型保留足够强的技能召回能力，同时又不把常驻上下文撑爆。</strong></p>
<p data-tool="mdnice编辑器">这件事说穿了就是五个字：<strong style="color: #0e88eb;">渐进式披露</strong>。</p>
<p data-tool="mdnice编辑器">大概的逻辑是：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">先告诉模型「系统里存在 skills 机制」。</section>
</li>
<li>
<section style="color: #010101;">再告诉它「当前有哪些 skill 名称和简短说明」。</section>
</li>
<li>
<section style="color: #010101;">等它真的决定调用某个 skill 时，再把正文、权限、hooks、模型覆盖、附加工具权限这些重内容展开。</section>
</li>
<li>
<section style="color: #010101;">如果某些 skill 还和路径、目录、文件类型绑定，那就继续往后拖，拖到模型真的碰到对应文件时再激活。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这是一个优雅且干净的工程化设计。它没有发明一套复杂到难以维护的 skill runtime，也没有把所谓智能寄托在黑盒检索器上，而是先把「披露成本」这件事控制住。</p>
<p data-tool="mdnice编辑器">我们按工程实现往下拆：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">skill 在系统里到底被建模成什么</section>
</li>
<li>
<section style="color: #010101;">多来源 skill 是怎么统一装配的</section>
</li>
<li>
<section style="color: #010101;">渐进式披露具体分了哪几层</section>
</li>
<li>
<section style="color: #010101;">条件激活和动态发现是怎么接进文件操作链路的</section>
</li>
<li>
<section style="color: #010101;">inline 和 fork 两条执行路径分别解决什么问题</section>
</li>
<li>
<section style="color: #010101;">这套设计真正适合什么场景，代价又是什么</section>
</li>
<li>
<section style="color: #010101;">如果要在自己的 Agent 里复刻，最短落地路径应该怎么走</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">一、先看 skills 在系统里被建模成什么</span></h1>
<p data-tool="mdnice编辑器">Claude Code 里，skill 最终会被统一建模成 <code style="color: #0e8aeb;">Command</code>，而且类型是 <code style="color: #0e8aeb;">prompt</code>。</p>
<p data-tool="mdnice编辑器">最核心的构造函数是 createSkillCommand：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">return</span> {
<span style="color: #c678dd;">type</span>: <span style="color: #98c379;">'prompt'</span>,
  name: skillName,
  description,
  hasUserSpecifiedDescription,
  allowedTools,
  argumentHint,
  argNames: argumentNames.length &gt; <span style="color: #d19a66;">0</span> ? argumentNames : <span style="color: #56b6c2;">undefined</span>,
  whenToUse,
  version,
  model,
  disableModelInvocation,
  userInvocable,
  context: executionContext,
  agent,
  effort,
  paths,
  contentLength: markdownContent.length,
  isHidden: !userInvocable,
  progressMessage: <span style="color: #98c379;">'running'</span>,
  userFacingName(): <span style="color: #e6c07b;">string</span> {
    <span style="color: #c678dd;">return</span> displayName || skillName
  },
  source,
  loadedFrom,
  hooks,
  skillRoot: baseDir,
<span style="color: #c678dd;">async</span> getPromptForCommand(args, toolUseContext) {
    ...
    <span style="color: #c678dd;">return</span> [{ <span style="color: #c678dd;">type</span>: <span style="color: #98c379;">'text'</span>, text: finalContent }]
  },
}
</code></pre>
<p data-tool="mdnice编辑器">这段代码说明有几个关键点：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">skill 不是特殊 runtime object，而是 <code style="color: #0e8aeb;">prompt command</code></section>
</li>
<li>
<section style="color: #010101;">skill 本体是 <code style="color: #0e8aeb;">getPromptForCommand()</code> 生成的一组文本 block</section>
</li>
<li>
<section style="color: #010101;">skill 可以带：</section>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">allowedTools</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">model</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">effort</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">paths</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">hooks</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">context: inline | fork</code></section>
</li>
</ul>
</li>
<li>
<section style="color: #010101;">skill 的调用结果，不是「执行一段脚本」，而是<strong style="color: #0e88eb;">把 skill 展开成后续对话消息，或者 fork 成子代理执行</strong></section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果我们自己做 Agent，建议参考。skill 不要单独发明一套 DSL runtime，直接把它抽象成「可延迟展开的 prompt 命令」就够了。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">二、skills 的来源有哪几类</span></h1>
<p data-tool="mdnice编辑器">skills 并不只来自一个目录。<code style="color: #0e8aeb;">getSkills()</code> 会把多个来源统一聚合。[commands.ts] commands.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L353</a>-L398</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">const</span> [skillDirCommands, pluginSkills] = <span style="color: #c678dd;">await</span> <span style="color: #e6c07b;">Promise</span>.all([
  getSkillDirCommands(cwd)...
  getPluginSkills()...
])
<span style="color: #c678dd;">const</span> bundledSkills = getBundledSkills()
<span style="color: #c678dd;">const</span> builtinPluginSkills = getBuiltinPluginSkillCommands()
</code></pre>
<p data-tool="mdnice编辑器">然后 <code style="color: #0e8aeb;">loadAllCommands()</code> 再把这些东西和 workflow/plugin/内建命令一起合并。[commands.ts] commands.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L445</a>-L469</p>
<p data-tool="mdnice编辑器">也就是说，skills 的来源至少有：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">bundled skills</section>
</li>
<li>
<section style="color: #010101;">磁盘上的 <code style="color: #0e8aeb;">/skills/</code></section>
</li>
<li>
<section style="color: #010101;">plugin skills</section>
</li>
<li>
<section style="color: #010101;">builtin plugin skills</section>
</li>
<li>
<section style="color: #010101;">兼容旧 <code style="color: #0e8aeb;">/commands/</code> 目录加载进来的 prompt commands</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">SkillTool 根本不需要知道 skill 来自哪里</strong>。只要最后是 <code style="color: #0e8aeb;">prompt command</code>，就能走统一调用路径。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">三、skills 的「渐进式披露」分 5 层</span></h1>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1）第一层：系统提示只声明「技能机制存在」</span></h2>
<p data-tool="mdnice编辑器">系统提示里不会把所有 skill 正文直接塞进去。它只给一个能力声明，告诉模型：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">用户说 <code style="color: #0e8aeb;">/&lt;skill-name&gt;</code>，其实是在指 skill</section>
</li>
<li>
<section style="color: #010101;">可以用 <code style="color: #0e8aeb;">SkillTool</code> 去执行</section>
</li>
<li>
<section style="color: #010101;">不要乱猜，只能调用列出来的那些</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这段在 [prompts.ts] prompts.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L353</a>-L401：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;">hasSkills
  ? <span style="color: #98c379;">`/&lt;skill-name&gt; (e.g., /commit) is shorthand for users to invoke a user-invocable skill. When executed, the skill gets expanded to a full prompt. Use the <span style="color: #e06c75;">${SKILL_TOOL_NAME}</span> tool to execute them. IMPORTANT: Only use <span style="color: #e06c75;">${SKILL_TOOL_NAME}</span> for skills listed in its user-invocable skills section - do not guess or use built-in CLI commands.`</span>
  : <span style="color: #56b6c2;">null</span>
</code></pre>
<p data-tool="mdnice编辑器">这一步只暴露了<strong style="color: #0e88eb;">机制</strong>，没有暴露<strong style="color: #0e88eb;">内容</strong>。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2）第二层：只披露 skill 名称和短描述</span></h2>
<p data-tool="mdnice编辑器">真正给模型看的 skill 列表，是通过 <code style="color: #0e8aeb;">getSkillToolCommands()</code> 过滤出来的。[commands.ts] commands.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L561</a>-L580</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">return</span> allCommands.filter(
  cmd =&gt;
    cmd.type === <span style="color: #98c379;">'prompt'</span> &amp;&amp;
    !cmd.disableModelInvocation &amp;&amp;
    cmd.source !== <span style="color: #98c379;">'builtin'</span> &amp;&amp;
    (
      cmd.loadedFrom === <span style="color: #98c379;">'bundled'</span> ||
      cmd.loadedFrom === <span style="color: #98c379;">'skills'</span> ||
      cmd.loadedFrom === <span style="color: #98c379;">'commands_DEPRECATED'</span> ||
      cmd.hasUserSpecifiedDescription ||
      cmd.whenToUse
    ),
)
</code></pre>
<p data-tool="mdnice编辑器">这段有两个要点：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">只有 <code style="color: #0e8aeb;">prompt</code> 命令才能进 skill 列表</section>
</li>
<li>
<section style="color: #010101;">并不是所有 prompt command 都自动暴露，至少得满足可描述性要求</section>
</li>
</ul>
<p data-tool="mdnice编辑器">也就是说，<strong style="color: #0e88eb;">可执行集合</strong>和<strong style="color: #0e88eb;">对模型披露集合</strong>不是完全相同的。<br />
Claude Code 在这里收了一刀，避免模型看到一堆没有描述、无法判断用途的技能。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3）第三层：列表本身还要走预算裁剪</span></h2>
<p data-tool="mdnice编辑器">skill 列表不是全量原文塞进 prompt，而是按预算压缩过的。核心逻辑在 [prompt.ts] tools/SkillTool/prompt.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L20</a>-L171。</p>
<p data-tool="mdnice编辑器">最关键的常量：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">export</span> <span style="color: #c678dd;">const</span> SKILL_BUDGET_CONTEXT_PERCENT = <span style="color: #d19a66;">0.01</span>
<span style="color: #c678dd;">export</span> <span style="color: #c678dd;">const</span> DEFAULT_CHAR_BUDGET = <span style="color: #d19a66;">8</span>_000
<span style="color: #c678dd;">export</span> <span style="color: #c678dd;">const</span> MAX_LISTING_DESC_CHARS = <span style="color: #d19a66;">250</span>
</code></pre>
<p data-tool="mdnice编辑器">以及格式化逻辑：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">return</span> <span style="color: #98c379;">`- <span style="color: #e06c75;">${cmd.name}</span>: <span style="color: #e06c75;">${getCommandDescription(cmd)}</span>`</span>
</code></pre>
<p data-tool="mdnice编辑器">和预算裁剪：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">if</span> (fullTotal &lt;= budget) {
  <span style="color: #c678dd;">return</span> fullEntries.map(e =&gt; e.full).join(<span style="color: #98c379;">'\n'</span>)
}
</code></pre>
<p data-tool="mdnice编辑器">如果超预算，就会：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">bundled skills 尽量保留完整描述</section>
</li>
<li>
<section style="color: #010101;">其它 skills 截断 description</section>
</li>
<li>
<section style="color: #010101;">极端情况下退化成只发 <code style="color: #0e8aeb;">- skill-name</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">这就是很典型的渐进式披露：<strong style="color: #0e88eb;">先给最小可用索引，不给正文</strong>。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4）第四层：列表还是增量下发，不是每轮全量重发</span></h2>
<p data-tool="mdnice编辑器">技能列表通过 <code style="color: #0e8aeb;">skill_listing</code> attachment 发给模型。发送逻辑在 [attachments.ts] utils/attachments.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L2669</a>-L2752。</p>
<p data-tool="mdnice编辑器">核心逻辑：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">const</span> newSkills = allCommands.filter(cmd =&gt; !sent.has(cmd.name))
...
<span style="color: #c678dd;">for</span> (<span style="color: #c678dd;">const</span> cmd of newSkills) {
  sent.add(cmd.name)
}
...
<span style="color: #c678dd;">return</span> [
  {
    <span style="color: #c678dd;">type</span>: <span style="color: #98c379;">'skill_listing'</span>,
    content,
    skillCount: newSkills.length,
    isInitial,
  },
]
</code></pre>
<p data-tool="mdnice编辑器">这个 <code style="color: #0e8aeb;">sentSkillNames</code> 机制说明：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">第一次发的是初始批次</section>
</li>
<li>
<section style="color: #010101;">后面只发新增的 skill</section>
</li>
<li>
<section style="color: #010101;">resume 之后还会 suppress，避免重复污染上下文</section>
</li>
</ul>
<p data-tool="mdnice编辑器">然后 <code style="color: #0e8aeb;">messages.ts</code> 会把它包成系统提醒。[messages.ts] utils/messages.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L3763</a>-L3772</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">return</span> wrapMessagesInSystemReminder([
  createUserMessage({
    content: <span style="color: #98c379;">`The following skills are available for use with the Skill tool:\n\n<span style="color: #e06c75;">${attachment.content}</span>`</span>,
    isMeta: <span style="color: #56b6c2;">true</span>,
  }),
])
</code></pre>
<p data-tool="mdnice编辑器">很多 Agent 会每轮把所有 tools / skills 全量重发，Claude Code 显然在认真控 token。 当然，如果技能不多，也可以直接全量发，不要过早优化。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">5）第五层：真正的 skill 内容延迟到调用时才展开</span></h2>
<p data-tool="mdnice编辑器">直到调用 <code style="color: #0e8aeb;">SkillTool</code>，skill 的真实正文才会通过 <code style="color: #0e8aeb;">command.getPromptForCommand()</code> 生成。[SkillTool.ts] utils/processUserInput/processSlashCommand.tsx<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L869</a>-L920</p>
<p data-tool="mdnice编辑器">这里才会发生：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">$ARGUMENTS</code> 替换</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">${CLAUDE_SKILL_DIR}</code> 替换</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">${CLAUDE_SESSION_ID}</code> 替换</section>
</li>
<li>
<section style="color: #010101;">markdown 内嵌 shell 执行</section>
</li>
<li>
<section style="color: #010101;">hooks 注册</section>
</li>
<li>
<section style="color: #010101;">附加权限 attachment 注入</section>
</li>
<li>
<section style="color: #010101;">invoked skill 记录</section>
</li>
</ul>
<p data-tool="mdnice编辑器">换句话说，skill 的重内容、重权限、重上下文副作用，都是<strong style="color: #0e88eb;">按需加载</strong>。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">四、除了延迟加载，它还做了「条件激活」</span></h1>
<p data-tool="mdnice编辑器">这也是渐进式披露的重要一层，而且很多人会漏掉。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1）带 <code>paths</code> frontmatter 的 skill，不会启动即暴露</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">getSkillDirCommands()</code> 里会把 skill 分成两类：[loadSkillsDir.ts] loadSkillsDir.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L771</a>-L803</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">if</span> (
  skill.type === <span style="color: #98c379;">'prompt'</span> &amp;&amp;
  skill.paths &amp;&amp;
  skill.paths.length &gt; <span style="color: #d19a66;">0</span> &amp;&amp;
  !activatedConditionalSkillNames.has(skill.name)
) {
  newConditionalSkills.push(skill)
} <span style="color: #c678dd;">else</span> {
  unconditionalSkills.push(skill)
}
</code></pre>
<p data-tool="mdnice编辑器">然后 conditional skills 被先放进 <code style="color: #0e8aeb;">conditionalSkills</code> map，而不是直接进入模型可见集合。</p>
<p data-tool="mdnice编辑器">这意味着：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">你定义了某个 skill 只适用于 <code style="color: #0e8aeb;">*.tsx</code></section>
</li>
<li>
<section style="color: #010101;">它不会在项目启动时就干扰所有任务</section>
</li>
<li>
<section style="color: #010101;">只有模型真的碰到匹配文件时，这个 skill 才会被激活</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2）激活时机挂在文件操作上</span></h2>
<p data-tool="mdnice编辑器">FileRead / FileWrite / FileEdit 三个工具里，都有两步副作用：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">发现上层目录里的 <code style="color: #0e8aeb;">.claude/skills</code></section>
</li>
<li>
<section style="color: #010101;">激活匹配当前文件路径的 conditional skills</section>
</li>
</ul>
<p data-tool="mdnice编辑器">比如 FileReadTool：[FileReadTool.ts] /tools/FileReadTool/FileReadTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L575</a>-L591</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">const</span> newSkillDirs = <span style="color: #c678dd;">await</span> discoverSkillDirsForPaths([fullFilePath], cwd)
...
addSkillDirectories(newSkillDirs).catch(() =&gt; {})
...
activateConditionalSkillsForPaths([fullFilePath], cwd)
</code></pre>
<p data-tool="mdnice编辑器">对应的激活实现是 [activateConditionalSkillsForPaths] skills/loadSkillsDir.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L997</a>-L1058：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">const</span> skillIgnore = ignore().add(skill.paths)
...
<span style="color: #c678dd;">if</span> (skillIgnore.ignores(relativePath)) {
  dynamicSkills.set(name, skill)
  conditionalSkills.delete(name)
  activatedConditionalSkillNames.add(name)
}
</code></pre>
<p data-tool="mdnice编辑器">这一步非常像条件规则系统，而不是纯静态注册。<br />
效果就是：<strong style="color: #0e88eb;">技能集合会随着你读写哪些文件而变化</strong>。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">五、动态发现本身也是渐进式披露的一部分</span></h1>
<p data-tool="mdnice编辑器">除了 path-conditional activation，Claude Code 还支持<strong style="color: #0e88eb;">目录级动态发现</strong>。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1）启动时只加载一部分 skill 目录</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">getSkillDirCommands()</code> 启动时会加载：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">managed</section>
</li>
<li>
<section style="color: #010101;">user</section>
</li>
<li>
<section style="color: #010101;">project dirs</section>
</li>
<li>
<section style="color: #010101;">additional dirs</section>
</li>
<li>
<section style="color: #010101;">legacy commands</section>
</li>
</ul>
<p data-tool="mdnice编辑器">但它不会把所有嵌套目录里的 <code style="color: #0e8aeb;">.claude/skills</code> 一次性全扫出来。[loadSkillsDir.ts] skills/loadSkillsDir.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L638</a>-L804</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2）当模型碰到某个文件时，再向上走目录树找嵌套 skill</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">discoverSkillDirsForPaths()</code> 会从当前文件的父目录开始，一路往上走到 cwd，查找 <code style="color: #0e8aeb;">.claude/skills</code>。[loadSkillsDir.ts] skills/loadSkillsDir.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L861</a>-L915</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">while</span> (currentDir.startsWith(resolvedCwd + pathSep)) {
  <span style="color: #c678dd;">const</span> skillDir = join(currentDir, <span style="color: #98c379;">'.claude'</span>, <span style="color: #98c379;">'skills'</span>)
  ...
  <span style="color: #c678dd;">await</span> fs.stat(skillDir)
  ...
  newDirs.push(skillDir)
}
</code></pre>
<p data-tool="mdnice编辑器">而且还做了两个非常实用的约束：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">已检查过的目录不会重复 stat</section>
</li>
<li>
<section style="color: #010101;">gitignored 目录里的 skills 不会静默加载</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这个设计让：<br />
<strong style="color: #0e88eb;">技能跟着你进入子目录而出现，不跟整个仓库一起一次性曝光。</strong></p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">六、SkillTool 的调用链，实际上分 inline 和 fork 两条路</span></h1>
<p data-tool="mdnice编辑器">这是技能系统和普通 slash command 最大的不同之一。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1）调用前校验</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">SkillTool.validateInput()</code> 会做：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">去掉前导 <code style="color: #0e8aeb;">/</code></section>
</li>
<li>
<section style="color: #010101;">检查 skill 是否存在</section>
</li>
<li>
<section style="color: #010101;">检查是否 <code style="color: #0e8aeb;">disableModelInvocation</code></section>
</li>
<li>
<section style="color: #010101;">检查是否为 <code style="color: #0e8aeb;">prompt</code> 类型<br />
见 [SkillTool.ts] tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L355</a>-L430</section>
</li>
</ul>
<p data-tool="mdnice编辑器">关键逻辑：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">const</span> commands = <span style="color: #c678dd;">await</span> getAllCommands(context)
<span style="color: #c678dd;">const</span> foundCommand = findCommand(normalizedCommandName, commands)
...
<span style="color: #c678dd;">if</span> (foundCommand.type !== <span style="color: #98c379;">'prompt'</span>) {
  <span style="color: #c678dd;">return</span> {
    result: <span style="color: #56b6c2;">false</span>,
    message: <span style="color: #98c379;">`Skill <span style="color: #e06c75;">${normalizedCommandName}</span> is not a prompt-based skill`</span>,
  }
}
</code></pre>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2）权限检查</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">SkillTool.checkPermissions()</code> 很细，除了 allow / deny 规则，还会对「只有安全属性的 skill」自动放行。[SkillTool.ts] /tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L433</a>-L579</p>
<p data-tool="mdnice编辑器">这个设计的意义是：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">简单 declarative skill 不必每次都弹权限</section>
</li>
<li>
<section style="color: #010101;">带额外风险属性的 skill 要 ask user</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3）inline skill：展开成后续对话消息</span></h2>
<p data-tool="mdnice编辑器">默认分支会走 <code style="color: #0e8aeb;">processPromptSlashCommand()</code>。[SkillTool.ts] tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L635</a>-L644</p>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">getMessagesForPromptSlashCommand()</code> 干的事情很丰富：[processSlashCommand.tsx] utils/processUserInput/processSlashCommand.tsx<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L827</a>-L920</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">command.getPromptForCommand(args, context)</code> 得到真正 skill 正文</section>
</li>
<li>
<section style="color: #010101;">注册 hooks</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">addInvokedSkill()</code> 记录 skill 内容，供 compact 时恢复</section>
</li>
<li>
<section style="color: #010101;">从 skill 文本里再抽 attachment</section>
</li>
<li>
<section style="color: #010101;">增加 <code style="color: #0e8aeb;">command_permissions</code> attachment</section>
</li>
<li>
<section style="color: #010101;">生成一批 <code style="color: #0e8aeb;">messages</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">返回结构里最关键的是：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">return</span> {
  messages,
  shouldQuery: <span style="color: #56b6c2;">true</span>,
  allowedTools: additionalAllowedTools,
  model: command.model,
  effort: command.effort,
  command
}
</code></pre>
<p data-tool="mdnice编辑器">也就是说，inline skill 的本质是：<br />
<strong style="color: #0e88eb;">把 skill 变成一段新的上下文和权限修饰，然后让主对话继续跑。</strong></p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4）fork skill：交给子代理跑，再把结果归还</span></h2>
<p data-tool="mdnice编辑器">如果 skill frontmatter 里声明 <code style="color: #0e8aeb;">context === 'fork'</code>，就走 <code style="color: #0e8aeb;">executeForkedSkill()</code>。[SkillTool.ts] tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L622</a>-L633</p>
<p data-tool="mdnice编辑器">它会：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">构造子代理上下文</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">runAgent()</code></section>
</li>
<li>
<section style="color: #010101;">收集 agent messages</section>
</li>
<li>
<section style="color: #010101;">抽取结果文本</section>
</li>
<li>
<section style="color: #010101;">最终返回 <code style="color: #0e8aeb;">{ status: 'forked', agentId, result }</code><br />
见 [executeForkedSkill] /tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L122</a>-L290</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这一步说明 Claude Code 已经把 skill 分成两类：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">知识/流程模板型 skill</strong>：inline 展开</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">工作委派型 skill</strong>：fork 子代理执行</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这个值得学一下。不是所有 skill 都应该展开在主上下文里。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">七、结果返回逻辑</span></h1>
<p data-tool="mdnice编辑器">为什么它也算渐进式披露的一部分？</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1）inline skill 的 tool_result</span></h2>
<p data-tool="mdnice编辑器">很轻</p>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">mapToolResultToToolResultBlockParam()</code> 对 inline skill 的返回只是：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;">content: <span style="color: #98c379;">`Launching skill: <span style="color: #e06c75;">${result.commandName}</span>`</span>
</code></pre>
<p data-tool="mdnice编辑器">见 [SkillTool.ts] tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L857</a>-L862</p>
<p data-tool="mdnice编辑器">也就是说，tool_result 本身不承载 skill 的全部结果。<br />
真正有价值的内容在 <code style="color: #0e8aeb;">newMessages</code> 里，已经被送回主会话继续推理。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2）fork skill 的 tool_result</span></h2>
<p data-tool="mdnice编辑器">直接带最终结果</p>
<p data-tool="mdnice编辑器">fork skill 返回的是：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;">content: <span style="color: #98c379;">`Skill "<span style="color: #e06c75;">${result.commandName}</span>" completed (forked execution).\n\nResult:\n<span style="color: #e06c75;">${result.result}</span>`</span>
</code></pre>
<p data-tool="mdnice编辑器">见 [SkillTool.ts] tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L848</a>-L855</p>
<p data-tool="mdnice编辑器">这是因为 fork skill 已经在独立上下文里把工作做完了，主线程要拿的是总结结果。</p>
<p data-tool="mdnice编辑器">所以在 Claude Code 里，skill 结果返回不是单一模式，而是：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">inline：返回「已加载 skill」，真正内容进主对话</section>
</li>
<li>
<section style="color: #010101;">fork：返回「子代理执行结果」</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这也是一种披露控制。<br />
不同执行语义，对结果暴露方式也不同。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">八、如何简要实现</span></h1>
<p data-tool="mdnice编辑器">一个新 Agent，如何简要实现 skills 的发现、召回、调用、结果返回？</p>
<p data-tool="mdnice编辑器">一个<strong style="color: #0e88eb;">够用、够短、能落地</strong>的最小设计，不追求和 Claude Code 一模一样，但核心思路一致。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">1）第一步：统一 skill 数据结构</span></h2>
<p data-tool="mdnice编辑器">最小结构建议这样：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">type</span> Skill = {
  name: <span style="color: #e6c07b;">string</span>
  description: <span style="color: #e6c07b;">string</span>
  whenToUse?: <span style="color: #e6c07b;">string</span>
  contentLoader: (args: <span style="color: #e6c07b;">string</span>, ctx: AgentContext) =&gt; <span style="color: #e6c07b;">Promise</span>&lt;<span style="color: #e6c07b;">string</span>&gt;
  allowedTools?: <span style="color: #e6c07b;">string</span>[]
  model?: <span style="color: #e6c07b;">string</span>
  effort?: <span style="color: #98c379;">'low'</span> | <span style="color: #98c379;">'medium'</span> | <span style="color: #98c379;">'high'</span>
  context?: <span style="color: #98c379;">'inline'</span> | <span style="color: #98c379;">'fork'</span>
  paths?: <span style="color: #e6c07b;">string</span>[]
}
</code></pre>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">contentLoader</code> 允许延迟展开</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">context</code> 决定 inline/fork</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">paths</code> 支持条件激活</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">allowedTools/model/effort</code> 支持 skill 级上下文修饰</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这和 Claude Code 的 <code style="color: #0e8aeb;">createSkillCommand()</code> 思路是一致的。[loadSkillsDir.ts] skills/loadSkillsDir.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L270</a>-L401</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">2）第二步：启动时只加载「索引」，不要加载正文</span></h2>
<p data-tool="mdnice编辑器">最简做法：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">扫描 skills 目录</section>
</li>
<li>
<section style="color: #010101;">解析 frontmatter</section>
</li>
<li>
<section style="color: #010101;">只把 <code style="color: #0e8aeb;">name / description / whenToUse / paths / context</code> 放进 registry</section>
</li>
<li>
<section style="color: #010101;">skill 正文不要此时进 prompt</section>
</li>
</ul>
<p data-tool="mdnice编辑器">示意：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">async</span> <span style="color: #c678dd;">function</span> <span style="color: #61aeee;">loadSkillIndex</span>(skillDirs: <span style="color: #e6c07b;">string</span>[]): <span style="color: #61aeee;">Promise</span>&lt;<span style="color: #61aeee;">Skill</span>[]&gt; {
<span style="color: #c678dd;">const</span> skills: Skill[] = []
<span style="color: #c678dd;">for</span> (<span style="color: #c678dd;">const</span> dir of skillDirs) {
    <span style="color: #c678dd;">for</span> (<span style="color: #c678dd;">const</span> skillFile of <span style="color: #c678dd;">await</span> listSkillFiles(dir)) {
      <span style="color: #c678dd;">const</span> raw = <span style="color: #c678dd;">await</span> readFile(skillFile, <span style="color: #98c379;">'utf8'</span>)
      <span style="color: #c678dd;">const</span> { frontmatter, content } = parseFrontmatter(raw)
      skills.push({
        name: basename(dirname(skillFile)),
        description: <span style="color: #e6c07b;">String</span>(frontmatter.description ?? <span style="color: #98c379;">''</span>),
        whenToUse: frontmatter.when_to_use ? <span style="color: #e6c07b;">String</span>(frontmatter.when_to_use) : <span style="color: #56b6c2;">undefined</span>,
        paths: <span style="color: #e6c07b;">Array</span>.isArray(frontmatter.paths) ? frontmatter.paths : <span style="color: #56b6c2;">undefined</span>,
        context: frontmatter.context === <span style="color: #98c379;">'fork'</span> ? <span style="color: #98c379;">'fork'</span> : <span style="color: #98c379;">'inline'</span>,
        contentLoader: <span style="color: #c678dd;">async</span> () =&gt; content,
      })
    }
  }
<span style="color: #c678dd;">return</span> skills
}
</code></pre>
<p data-tool="mdnice编辑器">这个阶段要学 Claude Code 的不是目录细节，而是<strong style="color: #0e88eb;">索引和正文分离</strong>。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">3）第三步：做一个「未发送 skill 集合」</span></h2>
<p data-tool="mdnice编辑器">这是渐进式披露的核心。</p>
<p data-tool="mdnice编辑器">维护一个 session 级状态：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">type</span> SkillDisclosureState = {
  sentSkillNames: Set&lt;<span style="color: #e6c07b;">string</span>&gt;
}
</code></pre>
<p data-tool="mdnice编辑器">每轮只发送新的：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">function</span> <span style="color: #61aeee;">getNewSkillListings</span>(skills: Skill[], sent: Set&lt;<span style="color: #e6c07b;">string</span>&gt;): <span style="color: #61aeee;">Skill</span>[] {
  <span style="color: #c678dd;">const</span> fresh = skills.filter(s =&gt; !sent.has(s.name))
  <span style="color: #c678dd;">for</span> (<span style="color: #c678dd;">const</span> s of fresh) sent.add(s.name)
  <span style="color: #c678dd;">return</span> fresh
}
</code></pre>
<p data-tool="mdnice编辑器">然后把它格式化成短列表，而不是全文：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">function</span> <span style="color: #61aeee;">formatSkillListing</span>(skills: Skill[]): <span style="color: #61aeee;">string</span> {
  <span style="color: #c678dd;">return</span> skills.map(s =&gt; <span style="color: #98c379;">`- <span style="color: #e06c75;">${s.name}</span>: <span style="color: #e06c75;">${s.description}</span>`</span>).join(<span style="color: #98c379;">'\n'</span>)
}
</code></pre>
<p data-tool="mdnice编辑器">这对应 Claude Code 的 <code style="color: #0e8aeb;">sentSkillNames + skill_listing attachment</code> 方案。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">4）第四步：把文件操作接成动态发现触发器</span></h2>
<p data-tool="mdnice编辑器">如果你也想要「技能跟着目录出现」，最小版本就是：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">用户或模型读/写/改文件时</section>
</li>
<li>
<section style="color: #010101;">从文件父目录往上走到 cwd</section>
</li>
<li>
<section style="color: #010101;">看有没有 <code style="color: #0e8aeb;">.agent/skills</code> 或 <code style="color: #0e8aeb;">.claude/skills</code></section>
</li>
<li>
<section style="color: #010101;">找到新目录就加载 skill index</section>
</li>
</ul>
<p data-tool="mdnice编辑器">示意：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">async</span> <span style="color: #c678dd;">function</span> <span style="color: #61aeee;">discoverSkillDirsForFile</span>(filePath: <span style="color: #e6c07b;">string</span>, cwd: <span style="color: #e6c07b;">string</span>): <span style="color: #61aeee;">Promise</span>&lt;<span style="color: #61aeee;">string</span>[]&gt; {
<span style="color: #c678dd;">const</span> dirs: <span style="color: #e6c07b;">string</span>[] = []
<span style="color: #c678dd;">let</span> current = dirname(filePath)
<span style="color: #c678dd;">while</span> (current.startsWith(cwd + sep)) {
    <span style="color: #c678dd;">const</span> candidate = join(current, <span style="color: #98c379;">'.agent'</span>, <span style="color: #98c379;">'skills'</span>)
    <span style="color: #c678dd;">if</span> (<span style="color: #c678dd;">await</span> exists(candidate)) dirs.push(candidate)
    <span style="color: #c678dd;">const</span> parent = dirname(current)
    <span style="color: #c678dd;">if</span> (parent === current) <span style="color: #c678dd;">break</span>
    current = parent
  }
<span style="color: #c678dd;">return</span> dirs
}
</code></pre>
<p data-tool="mdnice编辑器">Claude Code 的现成参考是 [discoverSkillDirsForPaths] skills/loadSkillsDir.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L861</a>-L915。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">5）第五步：做条件激活，而不是启动时全暴露</span></h2>
<p data-tool="mdnice编辑器">如果 skill 定义里有 <code style="color: #0e8aeb;">paths</code>，就不要一开始暴露。<br />
等碰到匹配文件时再激活：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">function</span> <span style="color: #61aeee;">activatePathScopedSkills</span>(
  pending: Skill[],
  touchedFiles: <span style="color: #e6c07b;">string</span>[],
): { active: Skill[]; remaining: Skill[] } {
<span style="color: #c678dd;">const</span> active: Skill[] = []
<span style="color: #c678dd;">const</span> remaining: Skill[] = []
<span style="color: #c678dd;">for</span> (<span style="color: #c678dd;">const</span> skill of pending) {
    <span style="color: #c678dd;">if</span> (!skill.paths || skill.paths.length === <span style="color: #d19a66;">0</span>) {
      active.push(skill)
      <span style="color: #c678dd;">continue</span>
    }
    <span style="color: #c678dd;">const</span> matched = touchedFiles.some(file =&gt; matchAny(file, skill.paths!))
    <span style="color: #c678dd;">if</span> (matched) active.push(skill)
    <span style="color: #c678dd;">else</span> remaining.push(skill)
  }
<span style="color: #c678dd;">return</span> { active, remaining }
}
</code></pre>
<p data-tool="mdnice编辑器">这就是 Claude Code <code style="color: #0e8aeb;">conditionalSkills -&gt; activateConditionalSkillsForPaths()</code> 的最小复刻。</p>
<hr />
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">6）第六步：调用 skill 时才真正加载正文</span></h2>
<p data-tool="mdnice编辑器">不要提前把 skill 正文塞到 prompt。<br />
调用时再做：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">async</span> <span style="color: #c678dd;">function</span> <span style="color: #61aeee;">invokeSkill</span>(
  skill: Skill,
  args: <span style="color: #e6c07b;">string</span>,
  ctx: AgentContext,
): <span style="color: #61aeee;">Promise</span>&lt;<span style="color: #61aeee;">SkillInvocationResult</span>&gt; {
<span style="color: #c678dd;">const</span> prompt = <span style="color: #c678dd;">await</span> skill.contentLoader(args, ctx)

<span style="color: #c678dd;">if</span> (skill.context === <span style="color: #98c379;">'fork'</span>) {
    <span style="color: #c678dd;">const</span> result = <span style="color: #c678dd;">await</span> runSubAgent({
      prompt,
      allowedTools: skill.allowedTools,
      model: skill.model,
      effort: skill.effort,
    })
    <span style="color: #c678dd;">return</span> { mode: <span style="color: #98c379;">'fork'</span>, result }
  }

<span style="color: #c678dd;">return</span> {
    mode: <span style="color: #98c379;">'inline'</span>,
    newMessages: [
      { role: <span style="color: #98c379;">'user'</span>, content: <span style="color: #98c379;">`[SKILL:<span style="color: #e06c75;">${skill.name}</span>]`</span> },
      { role: <span style="color: #98c379;">'user'</span>, content: prompt, meta: <span style="color: #56b6c2;">true</span> },
    ],
    allowedTools: skill.allowedTools,
    model: skill.model,
    effort: skill.effort,
  }
}
</code></pre>
<p data-tool="mdnice编辑器">这就是 Claude Code <code style="color: #0e8aeb;">SkillTool.call()</code> 的最小骨架。[SkillTool.ts] tools/SkillTool/SkillTool.ts<a class="wx_topic_link" style="color: #576b95 !important;" data-topic="1" data-recommend="">#L581</a>-L863</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">7）第七步：结果返回必须分 inline 和 fork</span></h2>
<p data-tool="mdnice编辑器">直接照 Claude Code 的语义分两种：</p>
<h3 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e88eb;">inline</span></h3>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">返回一个轻 tool_result：<code style="color: #0e8aeb;">Launching skill: xxx</code></section>
</li>
<li>
<section style="color: #010101;">真正内容通过 <code style="color: #0e8aeb;">newMessages</code> 回到主对话继续推理</section>
</li>
</ul>
<h3 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e88eb;">fork</span></h3>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">返回最终结果摘要</section>
</li>
<li>
<section style="color: #010101;">子代理对话不污染主上下文</section>
</li>
</ul>
<p data-tool="mdnice编辑器">示意：</p>
<pre data-tool="mdnice编辑器"><code style="color: #abb2bf;"><span style="color: #c678dd;">type</span> SkillInvocationResult =
  | {
      mode: <span style="color: #98c379;">'inline'</span>
      newMessages: Message[]
      allowedTools?: <span style="color: #e6c07b;">string</span>[]
      model?: <span style="color: #e6c07b;">string</span>
      effort?: <span style="color: #e6c07b;">string</span>
    }
  | {
      mode: <span style="color: #98c379;">'fork'</span>
      result: <span style="color: #e6c07b;">string</span>
    }
</code></pre>
<p data-tool="mdnice编辑器">这一步是很多新 Agent 最容易偷懒的地方。<br />
要么所有 skill 都 inline，主上下文爆炸；要么所有 skill 都 fork，失去细粒度引导。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">九、小结</span></h1>
<p data-tool="mdnice编辑器">「skills 的渐进式披露」其实就是 Claude Code 在控制 prompt 成本和能力密度时最典型的设计之一。它真正解决的问题不是「怎么找到一个 skill」，而是「怎么在不把上下文撑爆的前提下，让模型知道自己有技能可用」。</p>
<p data-tool="mdnice编辑器">它背后的思路：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">先给索引</section>
</li>
<li>
<section style="color: #010101;">再给局部集合</section>
</li>
<li>
<section style="color: #010101;">再给真实正文</section>
</li>
<li>
<section style="color: #010101;">最后才给执行结果</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这是一个很像搜索引擎的设计：摘要、点击、展开、消费，而不是把整本书扔给你。</p>
<p data-tool="mdnice编辑器">以上。</p>
<p>&nbsp;</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/04/claude-code-ai-skills-source/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Claude Code 的下一个 AI 范式：KAIROS</title>
		<link>https://www.phppan.com/2026/04/claude-code-ai-kairos/</link>
		<comments>https://www.phppan.com/2026/04/claude-code-ai-kairos/#comments</comments>
		<pubDate>Sun, 12 Apr 2026 03:46:09 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[KAIROS]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2488</guid>
		<description><![CDATA[在 Claude Code 的代码中，如果只算 KAIROS 出现的次数，其出现了 154 次；如果算上以其为 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com" data-pm-slice="0 0 []">
<p data-tool="mdnice编辑器">在 Claude Code 的代码中，如果只算 KAIROS 出现的次数，其出现了 154 次；如果算上以其为前缀的变量啥的，其出现了 365 次。</p>
<p data-tool="mdnice编辑器">KAIROS 是什么？</p>
<p data-tool="mdnice编辑器">简单来说，KAIROS 是 Claude Code 未来的 AI 形态，一个在恰当时机出现的，一直在线的协同工作伙伴。</p>
<p data-tool="mdnice编辑器">KAIROS (καιρός) 源自古希腊语，意为「正确的、关键的或合宜的时刻」，代表定性的、超越时序的「时机」或「关键瞬间」。</p>
<p data-tool="mdnice编辑器">KAIROS 这件事，重点从来不在于它多了几个工具开关，也不在于文档里写了多少「常驻助手」「主动工作」这种产品话术。它真正改变的，是 Claude Code 的运行范式：从「终端里的同步问答器」，切到「长期在线、异步协作、跨渠道接入、能自己维持工作节奏的常驻代理」。</p>
<p data-tool="mdnice编辑器">这个变化很大。大到我不太愿意把它叫成一次功能升级。它更像一次产品类别切换。</p>
<p data-tool="mdnice编辑器">如果这个方向跑通，Claude Code 的竞争对象会变。它不再只是和一批 coding assistant CLI 去比「补全快不快、命令懂不懂、上下文长不长」。它会开始进入另一条赛道：谁更像一个持续在线的工程协作者，谁能承接跨时间、跨终端、跨系统的工作责任。</p>
<p data-tool="mdnice编辑器">问题也在这里。KAIROS 现在的仓库状态，远没有到「产品封版」的程度。外围能力已经长出不少，主闭环还没彻底打穿。Bridge、Brief、频道消息、每日记忆日志、后台任务基础设施，这些都不是 PPT。assistant 主入口、gate、proactive 状态、session discovery 这些地方，又明显还是 stub。方向很清楚，骨架也搭起来了，真正决定产品能不能稳定落地的那几条主链路，还差最后一截硬骨头。</p>
<p data-tool="mdnice编辑器">这篇文章我们不按功能清单来复述一遍。那样太浅，也没工程价值。回答三个关键的问题：</p>
<ol class="list-paddingleft-1">
<li>
<section style="color: #010101;">KAIROS 在 Claude Code 里到底重新定义了什么</section>
</li>
<li>
<section style="color: #010101;">它已经落地了哪些关键能力，哪些地方还没闭环</section>
</li>
<li>
<section style="color: #010101;">为什么它必须改写记忆系统、交互渠道和执行模型</section>
</li>
</ol>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS 改写的不是功能，是运行模型</span></h1>
<p data-tool="mdnice编辑器">普通 CLI 的交互模型很简单。</p>
<p data-tool="mdnice编辑器">用户打开终端。输入一条指令。模型分析上下文。调用工具。给出回答。进程结束，或者这一轮逻辑结束。下一次再来，虽然可能还能靠项目文件、历史记录、memory 文件接上一部分语境，但本质上还是新一轮同步请求-响应。</p>
<p data-tool="mdnice编辑器">这个模式有一个天然上限：AI 只在用户看着终端的时候存在。用户不在，系统就不工作。外部事件来了，也接不住。长任务只能靠用户盯着。跨设备继续工作这件事，基本也无从谈起。</p>
<p data-tool="mdnice编辑器">KAIROS 想改掉的，就是这个上限。</p>
<p data-tool="mdnice编辑器">它想要的模型是另一种结构：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">会话可以长期存在</section>
</li>
<li>
<section style="color: #010101;">进程重启后还能接回原来的会话</section>
</li>
<li>
<section style="color: #010101;">外部系统可以把消息推到这个会话里</section>
</li>
<li>
<section style="color: #010101;">用户没有新输入时，Agent 也能继续推进任务</section>
</li>
<li>
<section style="color: #010101;">工作状态通过记忆、日志和摘要维持</section>
</li>
<li>
<section style="color: #010101;">输出形态适配异步消费，而不是只适配终端前的一次性阅读</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这不是「更主动一点」。这是一整套运行时假设变了。</p>
<p data-tool="mdnice编辑器">我一直觉得，很多人看这类能力时容易掉进一个误区：看见 <code style="color: #0e8aeb;">SleepTool</code>、push notification、channels，就以为这只是「给 CLI 加点自动化」。这个理解有点太保守。真正的变化是，系统开始假设自己是一个持续值班的实体，而不是一个按回车键才苏醒的函数调用。</p>
<p data-tool="mdnice编辑器">一旦假设变了，后面的东西都会跟着变。会话管理会变。记忆策略会变。输出格式会变。安全边界会变。成本模型会变。产品定位也会变。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">从代码现状看，KAIROS 已经是一组能力家族</span></h1>
<p data-tool="mdnice编辑器">从文档和源码状态看，KAIROS 不是单点 feature flag，它更像一个能力总开关，把若干子系统串成一个共同叙事。</p>
<p data-tool="mdnice编辑器">已有的子功能包括：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">KAIROS_BRIEF</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">KAIROS_CHANNELS</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">KAIROS_PUSH_NOTIFICATION</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">KAIROS_GITHUB_WEBHOOKS</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">KAIROS_DREAM</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">工具注册层能看到对应工具：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">SleepTool</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">SendUserFileTool</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">PushNotificationTool</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">SubscribePRTool</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">从这些就可能看到背后对应的产品动作：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">控制执行节奏和保活</section>
</li>
<li>
<section style="color: #010101;">主动把结果回传给用户</section>
</li>
<li>
<section style="color: #010101;">在终端外做异步通知</section>
</li>
<li>
<section style="color: #010101;">订阅外部事件，反向驱动内部执行</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这里最值得注意的是其产品动作的形态已经变了。</p>
<p data-tool="mdnice编辑器">传统 CLI 工具的动作集合通常是：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">读文件</section>
</li>
<li>
<section style="color: #010101;">写文件</section>
</li>
<li>
<section style="color: #010101;">执行命令</section>
</li>
<li>
<section style="color: #010101;">输出结果</section>
</li>
</ul>
<p data-tool="mdnice编辑器">KAIROS 的动作集合变成：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">等待</section>
</li>
<li>
<section style="color: #010101;">监听</section>
</li>
<li>
<section style="color: #010101;">回传</section>
</li>
<li>
<section style="color: #010101;">唤醒</section>
</li>
<li>
<section style="color: #010101;">跨渠道接入</section>
</li>
<li>
<section style="color: #010101;">持续维持上下文</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这说明它的定位已经不再是单纯的「本地操作器」。它正往「工作流中枢」走。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">Bridge 是 KAIROS 最关键的基础设施之一</span></h1>
<p data-tool="mdnice编辑器">KAIROS 能不能成立，第一件事不是主动性，而是<strong style="color: #0e88eb;">连续性</strong>。</p>
<p data-tool="mdnice编辑器">如果会话不能连续存在，所谓常驻助手就是假的。它最多是一个本地守护进程。用户侧体验还是断裂的。今天在一个终端开的事情，明天换个终端、换个设备、换个入口，就接不上了。那这个产品心智根本立不起来。</p>
<p data-tool="mdnice编辑器">Bridge 正是在补这个问题。</p>
<p data-tool="mdnice编辑器">从现有设计看，Bridge 的数据流：远端入口收到用户消息，通过 bridge 拉取工作，创建或恢复 REPL，会话继续执行，再把结果回传。这个思路解决的是「<strong style="color: #0e88eb;">用户感知到的是不是同一个持续存在的助手</strong>」。</p>
<p data-tool="mdnice编辑器">代码里关键点在 <code style="color: #0e8aeb;">useReplBridge</code>。assistant 模式下会启用 perpetual bridge session，目的是让远端看到的是同一条持续会话，而不是每次 CLI 启动都开一条新的 session。</p>
<p data-tool="mdnice编辑器">没有 perpetual session，用户面对的是很多段相似但断裂的对话。每次恢复都像重新认识一次项目。上下文可能靠 memory 拼回来一点，但主观体验一定是断的。</p>
<p data-tool="mdnice编辑器">有了 perpetual session，用户会开始把这个东西当成「同一个一直在线的协作者」。这就是范式变化真正落地的第一步。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">但真实闭环卡住的地方，不在 Bridge 本身</span></h1>
<p data-tool="mdnice编辑器">Bridge 骨架基本有了，assistant 产品主链路还没闭环。</p>
<p data-tool="mdnice编辑器">很多团队做 Agent 系统时，最容易陷入一种错觉：底层传输能通，远程会话能恢复，消息能送达，就以为产品主路径已经打通。其实不是。通道打通，只代表系统能传递状态。离「一个稳定可用的常驻助手产品」还差几个关键层：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">身份判定</section>
</li>
<li>
<section style="color: #010101;">gate 放行</section>
</li>
<li>
<section style="color: #010101;">会话发现</section>
</li>
<li>
<section style="color: #010101;">assistant 专属上下文初始化</section>
</li>
<li>
<section style="color: #010101;">assistant 专属系统提示</section>
</li>
<li>
<section style="color: #010101;">持续工作状态机</section>
</li>
<li>
<section style="color: #010101;">长期记忆蒸馏</section>
</li>
</ul>
<p data-tool="mdnice编辑器">当前源码里，这些都还没有。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">assistant 主入口还是 stub</span></h2>
<p data-tool="mdnice编辑器">assistant 主模块里，<code style="color: #0e8aeb;">isAssistantMode()</code> 返回 <code style="color: #0e8aeb;">false</code>，初始化函数是空的，assistant 专属 prompt addendum 也是空的。</p>
<p data-tool="mdnice编辑器">这意味着什么？</p>
<p data-tool="mdnice编辑器">意味着一大堆外围逻辑虽然预留了 assistant 分支，但真正运行时根本进不去。Bridge 想按 assistant 模式走 perpetual session，要靠 <code style="color: #0e8aeb;">isAssistantMode()</code>。主程序想按 assistant 模式切换运行逻辑，也要靠它。远端 worker 类型的区分，还是要靠它。</p>
<p data-tool="mdnice编辑器">入口判定没实现，后面这条链就全断了。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS gate 还是 stub</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">isKairosEnabled()</code> 直接返回 false，这表示：产品级放行逻辑还没接上。</p>
<p data-tool="mdnice编辑器">这不是个小洞。因为常驻助手和普通 CLI 完全不是一个风险等级的东西。它能后台执行、接外部消息、长期持有上下文、主动做事。没有真实 gate，这种能力根本不适合大面积开。</p>
<p data-tool="mdnice编辑器">所以从工程视角看，gate 现在是缺实现。从产品视角看，这代表「产品入口控制逻辑还停留在骨架层」。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">session discovery 还是 stub</span></h2>
<p data-tool="mdnice编辑器">如果用户执行 assistant viewer 路径，系统理论上应该能发现已有常驻会话，然后接回去。现在 discovery 返回空数组，这条体验链路就断了。</p>
<p data-tool="mdnice编辑器">这直接伤到产品最核心的承诺之一：会话连续性。</p>
<p data-tool="mdnice编辑器">你都号称是常驻助手了，结果用户回来时找不到自己之前那条会话，这个心智会瞬间塌掉。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">proactive 状态模块还是 stub</span></h2>
<p data-tool="mdnice编辑器">KAIROS 的 prompt 层已经开始定义 autonomous work 的行为协议了，比如：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">没有事做时必须 sleep</section>
</li>
<li>
<section style="color: #010101;">用户不在终端前时偏向自主推进</section>
</li>
<li>
<section style="color: #010101;">用户正在看终端时偏向协作和简洁输出</section>
</li>
</ul>
<p data-tool="mdnice编辑器">行为协议有了，状态机没落地。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS 最大的区别，不是工具多，而是 prompt 协议变了</span></h1>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">对于 Agent 系统，prompt 在很多时候就是运行协议的一部分。</strong></p>
<p data-tool="mdnice编辑器">普通模式下，模型更像一个执行器。收到请求，完成任务，给出答复。</p>
<p data-tool="mdnice编辑器">KAIROS 模式下，prompt 在定义另一种工作方式：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">什么时候该自己推进</section>
</li>
<li>
<section style="color: #010101;">什么时候该停下来等待</section>
</li>
<li>
<section style="color: #010101;">什么时候该用 Brief 压缩输出</section>
</li>
<li>
<section style="color: #010101;">什么时候该把结果异步推给用户</section>
</li>
<li>
<section style="color: #010101;">用户是否在终端前，会影响表达风格和协作策略</section>
</li>
<li>
<section style="color: #010101;">没有明确工作时不能空转，必须 sleep</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这本质上是在给模型灌输一套「值班协作者协议」。</p>
<p data-tool="mdnice编辑器">因为常驻助手和一次性问答器的差别，很大一部分在于它是否具备稳定的工作节奏。节奏不稳，再强的工具集也会把系统拖进两个极端：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">一直唤醒，疯狂消耗 token 和 API 调用</section>
</li>
<li>
<section style="color: #010101;">一直沉睡，错过事件和推进时机</section>
</li>
</ul>
<p data-tool="mdnice编辑器">所以 <code style="color: #0e8aeb;">SleepTool</code> 这种东西，表面看是个小工具，本质上是在为 Agent 增加时间维度。普通 CLI 处理的是空间里的资源：文件、命令、输出。KAIROS 开始处理时间里的资源：等待、唤醒、周期、空闲、值班。</p>
<p data-tool="mdnice编辑器">这一步一旦做出来，产品形态就变了。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS 为什么必须改写记忆系统</span></h1>
<p data-tool="mdnice编辑器">这里必须要聊一下。</p>
<p data-tool="mdnice编辑器">在长任务中，我们不能拿短会话的记忆模型去硬撑长时间在线系统。写放大、污染、冲突、检索噪音、摘要失真，很快全出来。</p>
<p data-tool="mdnice编辑器">KAIROS 在这件事上切到了 daily log 模式。</p>
<p data-tool="mdnice编辑器">普通模式下，长期记忆更接近「主题文件 + 索引」：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">新信息被整理成相对成型的 topic files</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">MEMORY.md</code> 维护索引</section>
</li>
<li>
<section style="color: #010101;">模型下次需要时按主题读回</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这个模式适合短周期会话。信息密度高，整理成本还能接受。</p>
<p data-tool="mdnice编辑器">KAIROS 场景不一样。它面对的是：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">长时间持续执行</section>
</li>
<li>
<section style="color: #010101;">高频事件流输入</section>
</li>
<li>
<section style="color: #010101;">用户不一定实时盯着</section>
</li>
<li>
<section style="color: #010101;">外部渠道消息可能随时插入</section>
</li>
<li>
<section style="color: #010101;">同一天内工作状态会不断变化</section>
</li>
<li>
<section style="color: #010101;">大量信息是过程态，不适合立刻主题化</section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果还按普通模式那种「一有信息就整理成 topic file」去写，工程上会出三个明显问题。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">第一，写放大会很严重</span></h2>
<p data-tool="mdnice编辑器">频繁改 topic files，会导致目录不断抖动。<code style="color: #0e8aeb;">MEMORY.md</code> 也会被频繁重写。对于常驻系统，这种写入模式很不稳。量一上来就开始恶心人。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">第二，过程信息会过早结构化</span></h2>
<p data-tool="mdnice编辑器">很多工作过程在当下并不适合写成结论。比如一条外部消息、一次等待中的验证、一个还没确认的假设、某项任务中间状态。这些东西如果过早塞进长期记忆，很容易污染后续上下文。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">第三，恢复和追溯会变差</span></h2>
<p data-tool="mdnice编辑器">当你把所有信息即时揉进主题文件里，原始事件流会逐渐丢失。系统后面做蒸馏、回溯、纠错时，材料反而不够。</p>
<p data-tool="mdnice编辑器">daily log 方案就是为了解这些问题。</p>
<p data-tool="mdnice编辑器">它的核心思想很简单：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">白天先 append-only 记录到当日日志</section>
</li>
<li>
<section style="color: #010101;">不急着重组和提炼</section>
</li>
<li>
<section style="color: #010101;">后续再把成熟信息蒸馏成长期 memory</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这是典型的事件流优先设计。先保留工作轨迹，再做结构化提炼。对常驻助手来说，这个方向很稳。</p>
<p data-tool="mdnice编辑器">普通模式里的 memory 是模型的记忆补丁，KAIROS 里的 memory 是产品连续性的基础设施。</p>
<p data-tool="mdnice编辑器">这两者不是一个级别的东西。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">transcript 被纳入记忆蒸馏</span></h1>
<p data-tool="mdnice编辑器">KAIROS 还想做一件更重要的事：把 session transcript 也纳入记忆蒸馏输入。</p>
<p data-tool="mdnice编辑器">这意味着长期记忆的来源，不再只靠模型「当前轮总结出的信息」，而是开始吸收完整工作轨迹。</p>
<p data-tool="mdnice编辑器">你可以把这理解成两种 memory 策略的差异：</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">普通模式</span></h2>
<p data-tool="mdnice编辑器">长期记忆主要存「结论」</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS 模式</span></h2>
<p data-tool="mdnice编辑器">长期记忆想从「事件流」中提取结论</p>
<p data-tool="mdnice编辑器">因为一个持续在线的协作者，真正有价值的上下文往往不只在最后结果里，还在过程里：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">谁在什么时候发来过什么消息</section>
</li>
<li>
<section style="color: #010101;">某项任务等待了多久</section>
</li>
<li>
<section style="color: #010101;">哪个验证步骤失败过</section>
</li>
<li>
<section style="color: #010101;">某个方向为什么被放弃</section>
</li>
<li>
<section style="color: #010101;">同一项目在不同日期里怎么演化的</section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果记忆系统拿不到这些过程信息，系统就只能越活越像一个失忆的执行器：只记结论，不记来路。</p>
<p data-tool="mdnice编辑器">当前仓库里这条链还没补齐，session transcript 相关实现还是 stub。当把普通 auto-dream 关闭，改走 KAIROS 专属 dream 路径，那就必须有足够材料来做蒸馏。daily logs 和 transcript 就是这个材料池。</p>
<p data-tool="mdnice编辑器">没有材料池，dream 只是概念。<br />
没有蒸馏，daily log 只是堆积。<br />
没有长期记忆收敛，常驻助手就只剩「一直在线」，没有「越用越像同一个协作者」。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">Brief 不是 UI 花活</span></h1>
<p data-tool="mdnice编辑器">它是异步协作场景里的输出压缩层</p>
<p data-tool="mdnice编辑器">我很喜欢 KAIROS 里对 Brief 的定位，因为这个点很多产品会忽略。</p>
<p data-tool="mdnice编辑器">一旦系统进入长期运行、跨终端、跨渠道、还带移动端通知的场景，传统那种长篇回复会迅速失效。不是模型写不出来，而是用户根本没法消费。</p>
<p data-tool="mdnice编辑器">你想象一下：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">一个后台任务跑了两小时</section>
</li>
<li>
<section style="color: #010101;">外部 webhook 触发了一轮检查</section>
</li>
<li>
<section style="color: #010101;">Slack 里推来一条状态更新</section>
</li>
<li>
<section style="color: #010101;">用户此时在手机上看通知</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这时候如果系统还按终端里的详细答复风格，甩一大段解释文字出去，体验会很差。信息密度低，确认成本高，真正关键的状态反而埋住了。</p>
<p data-tool="mdnice编辑器">Brief 的价值就在这里。它是异步工作场景的输出压缩层。</p>
<p data-tool="mdnice编辑器">它解决的不是「怎么更优雅地显示」，而是「在不同消费界面里，用最低认知成本传递足够状态」。这是一个工程问题，不是文案问题。</p>
<p data-tool="mdnice编辑器">所以我会把 Brief 看成 KAIROS 的必要配套，而不是锦上添花。没有它，常驻助手很容易被自己的输出拖死。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">channels 让 Claude Code 开始脱离终端边界</span></h1>
<p data-tool="mdnice编辑器">KAIROS 另一条很重要的线，是频道消息系统，也是一个很让人期待的逻辑，虽然当前有一些方法已经可以实现。</p>
<p data-tool="mdnice编辑器">当前设计已经允许外部消息通过 channel notification 之类的机制进入会话，被包装成结构化消息再投递给模型处理。这意味着：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">Claude Code 不再只属于一个本地终端</section>
</li>
<li>
<section style="color: #010101;">用户可以在终端外部和同一条工作流继续互动</section>
</li>
<li>
<section style="color: #010101;">AI 可以成为跨渠道的工作代理</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这个变化一旦做成，产品就会从 Developer Tool 往 Agent Platform 滑过去。</p>
<p data-tool="mdnice编辑器">再直白一点。</p>
<p data-tool="mdnice编辑器">终端工具的边界，是「你必须来到我的界面里，我才能帮你做事」。<br />
工作流代理的边界，是「我能在你的工作流里持续存在，你在哪个入口出现，我都能接上」。</p>
<p data-tool="mdnice编辑器">这是两种完全不同的产品位置。</p>
<p data-tool="mdnice编辑器">这样，channels 就很重要了，但不该排在最前面的优先级。因为它解决的是入口扩展问题，不是主闭环问题。一个内部还没站稳的系统，入口接得越多，事故面越大。没有 assistant 激活链、记忆蒸馏链和 proactive 状态机托底，channels 只会让问题更快暴露。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">后台执行是一等能力，不是附属能力</span></h1>
<p data-tool="mdnice编辑器">KAIROS 把后台执行推成一等能力。</p>
<p data-tool="mdnice编辑器">这是常驻助手能否成立的另一条底线。</p>
<p data-tool="mdnice编辑器">如果 Agent 像同事一样持续工作，它就不能被单次命令执行周期绑死。用户离开终端，任务还得继续跑。长任务不能把主线程挂住。等待回调、监听事件、轮询状态这些行为，不能都靠用户盯着终端来维持。</p>
<p data-tool="mdnice编辑器">很多 coding assistant 在 demo 阶段看起来很聪明，一到真实项目就暴露上限：用户一旦离开终端，整个系统的价值密度就迅速下降。长任务没人接。回调没人等。状态没人维持。所谓 Agent，最后还是个问答器。</p>
<p data-tool="mdnice编辑器">KAIROS 显然想突破这个上限。</p>
<p data-tool="mdnice编辑器">后台执行一旦变成一等能力，系统复杂度会明显上升。你要处理：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">阻塞任务后台化</section>
</li>
<li>
<section style="color: #010101;">任务状态跟踪</section>
</li>
<li>
<section style="color: #010101;">唤醒与恢复</section>
</li>
<li>
<section style="color: #010101;">错误回传</section>
</li>
<li>
<section style="color: #010101;">与记忆系统的状态对齐</section>
</li>
<li>
<section style="color: #010101;">与通知节奏的协调</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些东西没有一项是白送的。做不好，后台任务会变成后台事故。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS 的产品价值，核心在五个地方</span></h1>
<p data-tool="mdnice编辑器">如果从产品结果看，我会把它的价值拆成五个方面。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">一，留存会显著提升</span></h2>
<p data-tool="mdnice编辑器">一次性问答工具的留存天然一般。因为每轮交互都相对独立，用户用完就走。上下文积累浅，切换成本低。</p>
<p data-tool="mdnice编辑器">常驻会话、跨重启续接、长期记忆、异步回传这些东西组合起来，用户会开始把 Claude Code 当成「当前项目的长期协作体」。一旦心智变成这样，迁移成本就会明显上升。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">二，任务完成度会提升</span></h2>
<p data-tool="mdnice编辑器">很多高价值任务不是一轮 prompt 能做完的。它们需要等待、重试、监听、回调、验证、持续推进。普通模式下，这类任务经常会在用户离开终端时中断。</p>
<p data-tool="mdnice编辑器">KAIROS 提供的后台执行、sleep 唤醒、外部事件驱动，正好在补这个缺口。产品会从「答题器」往「任务执行器」走。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">三，渠道覆盖会扩大</span></h2>
<p data-tool="mdnice编辑器">有了 channels、push、bridge、webhook，Claude Code 的触点会明显增加。对产品运营和组织传播来说，这个价值非常直接。一个只能在终端里使用的工具，天然局限在一小撮高频命令行用户。一个能进入 Slack、移动通知、远程 viewer 的系统，扩散面会大得多。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">四，粘性会增强</span></h2>
<p data-tool="mdnice编辑器">session continuity、daily logs、structured brief、跨渠道接续，这几样东西叠在一起，会形成很强的黏着效应。用户会越来越依赖它手里的上下文。上下文越深，替换成本越高。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">五，产品想象空间会被抬高</span></h2>
<p data-tool="mdnice编辑器">做到这里，Claude Code 的竞争对象就不再只是其它 coding assistant。它会开始接近「开发团队的操作层代理」：监听、执行、回报、沉淀记忆、跨渠道协作。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS 的代价非常重</span></h1>
<p data-tool="mdnice编辑器">讲到这里如果只谈价值，那就是宣传稿了。工程里没有这么轻松的事。</p>
<p data-tool="mdnice编辑器">KAIROS 的代价主要有四类。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">第一，系统复杂度暴涨</span></h2>
<p data-tool="mdnice编辑器">从一次性 CLI 进入常驻模式后，系统要处理的东西会指数级增加：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">长生命周期会话</section>
</li>
<li>
<section style="color: #010101;">bridge 重连</section>
</li>
<li>
<section style="color: #010101;">会话恢复</section>
</li>
<li>
<section style="color: #010101;">远端消息幂等</section>
</li>
<li>
<section style="color: #010101;">外部事件接入</section>
</li>
<li>
<section style="color: #010101;">后台任务状态</section>
</li>
<li>
<section style="color: #010101;">唤醒节奏</section>
</li>
<li>
<section style="color: #010101;">记忆蒸馏</section>
</li>
<li>
<section style="color: #010101;">多渠道权限边界</section>
</li>
<li>
<section style="color: #010101;">通知频率控制</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些全都会增加测试成本、排障成本、回归成本。</p>
<p data-tool="mdnice编辑器">传统 CLI 很多问题是可复现、可局部调试的。常驻 Agent 的问题常常是跨时间、跨系统、跨状态累积的。定位难度完全不是一个量级。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">第二，成本模型会变差</span></h2>
<p data-tool="mdnice编辑器">tick + sleep 这套机制，本质上是在用更多调用换取持续在线行为。架构会更顺，产品体验会更强，API 成本也会上去。</p>
<p data-tool="mdnice编辑器">如果没有很严格的唤醒控制、任务优先级控制和输出压缩策略，系统会非常烧钱。尤其当常驻会话一多，哪怕每个会话只是周期性地「看一眼有没有事」，成本都可能迅速放大。</p>
<p data-tool="mdnice编辑器">很多团队做 Agent 产品，死得最早的往往不是能力不够，是成本失控。KAIROS 这条路如果真要产品化，成本治理必须和功能迭代同步推进，不能等做大了再补。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">第三，安全与信任门槛会更高</span></h2>
<p data-tool="mdnice编辑器">一个普通 CLI 助手，最多是「用户下命令，它帮你执行」。<br />
一个 KAIROS 式常驻助手，是「它持续持有上下文，接外部消息，可能在后台自主执行」。</p>
<p data-tool="mdnice编辑器">这两个系统的风险等级完全不同。</p>
<p data-tool="mdnice编辑器">assistant 模式下先检查 trusted directory，再检查 KAIROS gate，这个设计信号已经很明确：作者知道风险在上升。问题是现在 gate 还是 stub，主链路还没完全接上，说明这块还在建设中。</p>
<p data-tool="mdnice编辑器">产品能不能卖进团队、卖进企业，很多时候就看这里。因为企业不会只问「它能做什么」，他们会问得更直接：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">它什么时候能自己执行</section>
</li>
<li>
<section style="color: #010101;">谁能给它发消息</section>
</li>
<li>
<section style="color: #010101;">它能看到哪些目录</section>
</li>
<li>
<section style="color: #010101;">它会把什么记下来</section>
</li>
<li>
<section style="color: #010101;">它错了怎么停</section>
</li>
<li>
<section style="color: #010101;">它怎么审计</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些都是 KAIROS 必须正面回答的问题。</p>
<h2 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">第四，产品承诺和实现闭环还没完全对齐</span></h2>
<p data-tool="mdnice编辑器">这是当前最现实的问题。</p>
<p data-tool="mdnice编辑器">外围能力铺得已经不少，主入口和主状态层仍然有 stub。这个状态很典型：战略方向先行，支撑设施先铺，真正的产品主通路还在补。</p>
<p data-tool="mdnice编辑器">这种状态有机会，也有风险。</p>
<p data-tool="mdnice编辑器">机会在于，一旦主入口补齐，成长速度可能很快，因为外围都在等它。<br />
风险在于，如果主闭环补得太慢，外围越多，系统越像「展台很大，地基不稳」。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">为什么我认为 KAIROS 是整个项目里最重要的方向</span></h1>
<p data-tool="mdnice编辑器">因为它决定的不是某个 feature，而是产品类别。</p>
<p data-tool="mdnice编辑器">没有 KAIROS，Claude Code 依然可以是一个很强的 coding assistant CLI。<br />
有了 KAIROS，而且真做成了，它会变成一个持续在线、能跨渠道、能长期记忆、能异步执行的工程助手。</p>
<p data-tool="mdnice编辑器">这两者在商业形态上不是一个东西。<br />
在用户心智上不是一个东西。<br />
在组织采购逻辑上也不是一个东西。</p>
<p data-tool="mdnice编辑器">对个人开发者，这意味着「我下班以后，它还能继续跑」。<br />
对团队协作，这意味着「我们多了一个持续在线的 AI teammate」。<br />
对企业管理者，这意味着「AI 被接入的是工作流，不是单次问答」。</p>
<p data-tool="mdnice编辑器">这三层价值如果连起来，产品天花板会明显抬高。</p>
<p data-tool="mdnice编辑器">我甚至会说，KAIROS 成不成，决定了 Claude Code 最终是一个强工具，还是一个强平台。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">当前仓库里，KAIROS 的真实成熟度如何？</span></h1>
<p data-tool="mdnice编辑器">大概是四点：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">产品意图非常清楚</strong>： 因为从文档、prompt、memory 策略、bridge 设计、channels、brief，到后台任务工具，整个方向是一致的。不是东一块西一块拼起来的。它们都在服务同一个产品叙事：把 Claude Code 推成常驻助手。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">框架布线已经做了很多</strong>： 工具层、提示词层、bridge 层、memory prompt 分叉、channel notification、部分 viewer 路径，这些都已经不是空想。它们证明团队不是在写概念文档，而是在提前铺路。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">关键外围能力有真实实现</strong>：Bridge perpetual session、频道消息接入、Brief 规则、daily-log memory prompt，这些都具备产品骨架。哪怕主入口还没封口，外围支撑已经能看出最终形态。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">主入口和核心状态闭环仍有明显缺口</strong>：assistant 主模块、gate、session discovery、proactive 状态、session transcript 等地方的 stub，说明核心闭环还没完全长实。这个阶段我会把它定义为：接近产品化的战略子系统，而不是一个完整封版的功能包。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">KAIROS 的本质，是把 Claude Code 从「提效工具」推向「责任承接者」</span></h1>
<p data-tool="mdnice编辑器">文章写到这里，其实主已经很清楚了。</p>
<p data-tool="mdnice编辑器">KAIROS 真正改变的，是 Claude Code 的「存在方式」。</p>
<p data-tool="mdnice编辑器">普通 Claude Code 的存在方式是：用户打开终端，它出现；终端关闭，这轮交互的主价值结束。<br />
KAIROS 的存在方式是：用户不在场时，它仍然能持有状态、接收事件、维持节奏、积累记忆、回传结果。</p>
<p data-tool="mdnice编辑器">它在把 Claude Code 从前台交互工具，推向后台协作代理。</p>
<p data-tool="mdnice编辑器">一旦这个方向跑通，产品的护城河也会变。将来真正难被替代的，不只是模型回答质量，而会落在这些更重的东西上：</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">上下文沉淀深度</section>
</li>
<li>
<section style="color: #010101;">渠道接入深度</section>
</li>
<li>
<section style="color: #010101;">工作流嵌入程度</section>
</li>
<li>
<section style="color: #010101;">组织内协同能力</section>
</li>
<li>
<section style="color: #010101;">长期责任承接能力</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这才是 KAIROS 值得持续投入的原因。</p>
<h1 data-tool="mdnice编辑器"><span style="font-weight: bold; color: #0e8aeb;">小结</span></h1>
<p data-tool="mdnice编辑器">KAIROS 已经完成了产品方向的自洽，完成了相当一部分外围基础设施铺设，正在卡在主入口、记忆蒸馏和自主循环这三条上。只要这三条主链补齐，它很快就会从「战略子系统」变成「真正定义 Claude Code 下一阶段的核心产品」。</p>
<p data-tool="mdnice编辑器">这件事一旦做成，Claude Code 的故事就不再是「一个更强的 coding assistant CLI」。</p>
<p data-tool="mdnice编辑器">它会变成另一个东西。</p>
<ul class="list-paddingleft-1">
<li>
<section style="color: #010101;">一个持续在线的工程协作者。</section>
</li>
<li>
<section style="color: #010101;">一个能接进团队工作流的后台代理。</section>
</li>
<li>
<section style="color: #010101;">一个真正开始承接持续工作责任的 AI 系统。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这才是 KAIROS 的星辰大海。</p>
<p data-tool="mdnice编辑器">以上。</p>
<p>&nbsp;</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/04/claude-code-ai-kairos/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>深入 Claude Code 源码了解其记忆系统</title>
		<link>https://www.phppan.com/2026/04/claude-code-source-memory/</link>
		<comments>https://www.phppan.com/2026/04/claude-code-source-memory/#comments</comments>
		<pubDate>Sat, 04 Apr 2026 01:19:58 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[harness engineering]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2484</guid>
		<description><![CDATA[最近做 Agent 的同学应该大部分都有研读 Claude Code 泄漏的源码，网上出了各种 AI 加持下的 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<section id="nice" style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">最近做 Agent 的同学应该大部分都有研读 Claude Code 泄漏的源码，网上出了各种 AI 加持下的各种解读，教程，细节分析，甚至包括换了一种语言实现的版本，如 Python，Go，Rust 等等。感觉有点「一鲸落，万物生」的感觉。</p>
<p data-tool="mdnice编辑器">之前学习了 Claude Code 的系统提示词，写了一篇关于记忆系统的提示词。今天我们再深入其源码，看看其实现的细节。</p>
<p data-tool="mdnice编辑器">从其源码来看，</p>
<p data-tool="mdnice编辑器">Claude Code 这套记忆系统把几类完全不同的问题拆开处理了：长期记忆、当前轮相关记忆、会话压缩摘要、子代理独立记忆。和 OpenClaw 不同，OpenClaw 使用了统一的 Memory Service，加上一个向量库做检索，Claude Code 走的是另一条路：<strong style="color: #0e88eb;">文件系统优先，分层清晰，召回时机明确，代价可控</strong>。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">1. Claude Code 到底要记了什么</span></h1>
<p data-tool="mdnice编辑器">在 memoryTypes.ts#L14-L31 里，长期记忆的类型是的四类：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">user</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">feedback</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">project</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">reference</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">这四类东西有一个共同点：它们都<strong style="color: #0e88eb;">不容易从当前代码状态直接推导出来</strong>。用户习惯、项目背景、团队约束、外部系统入口，这些信息不写下来，下次对话就丢了。反过来，代码结构、文件路径、Git 历史、当前临时任务，这些内容源码里明确要求不要进长期记忆，因为它们本来就有权威来源。memoryTypes.ts#L183-L195</p>
<p data-tool="mdnice编辑器">记忆系统只该保存「代码外的信息」和「会跨轮次继续影响决策的信息」。以编程为例，当我们把代码事实也塞进去，后面一定会出现双份真相。你会遇到一个很尴尬的局面：代码说 A，memory 说 B，模型开始摇摆。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2. 四层分工</span></h1>
<p data-tool="mdnice编辑器">第一层是 <code style="color: #0e8aeb;">auto memory / team memory</code>。这是长期记忆，负责跨会话保存信息。目录逻辑在 paths.ts#L79-L259 和 teamMemPaths.ts#L66-L94。</p>
<p data-tool="mdnice编辑器">第二层是 <code style="color: #0e8aeb;">relevant memories</code>。这一层不关心长期存储，它只负责一件事：用户当前这一问，应该把哪几条历史记忆临时塞进上下文。入口在 findRelevantMemories.ts#L39-L141 和 attachments.ts#L2197-L2425。</p>
<p data-tool="mdnice编辑器">第三层是 <code style="color: #0e8aeb;">session memory</code>。这层服务的是长会话压缩，不负责跨会话记忆。位于当前 session 下的 <code style="color: #0e8aeb;">summary.md</code>。sessionMemory.ts#L183-L350</p>
<p data-tool="mdnice编辑器">第四层是 <code style="color: #0e8aeb;">agent memory</code>。子代理如果要持久化自己的经验，可以放 user/project/local 三种 scope 的独立目录。agentMemory.ts#L12-L177</p>
<p data-tool="mdnice编辑器">这四层拆开之后，很多设计选择就顺了：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">长期记忆用文件，便于审计和手工修复</section>
</li>
<li>
<section style="color: #010101;">当前轮召回走轻量检索，减少 prompt 污染</section>
</li>
<li>
<section style="color: #010101;">长会话压缩用单独 summary，避免每次 compact 都从头总结</section>
</li>
<li>
<section style="color: #010101;">子代理隔离状态，减少串味</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3. 长期记忆为什么选文件</span></h1>
<p data-tool="mdnice编辑器">Claude Code 的长期记忆是使用的 Markdown 文件。每条记忆一个文件，外加一个 <code style="color: #0e8aeb;">MEMORY.md</code> 入口索引。这部分规则在 memdir.ts#L199-L316 里写得很明白。</p>
<p data-tool="mdnice编辑器">源码里的写入约束是这样的：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-string" style="color: #98c379;">'## How to save memories'</span>,
<span class="hljs-string" style="color: #98c379;">''</span>,
<span class="hljs-string" style="color: #98c379;">'Saving a memory is a two-step process:'</span>,
<span class="hljs-string" style="color: #98c379;">''</span>,
<span class="hljs-string" style="color: #98c379;">'**Step 1** — write the memory to its own file (e.g., `user_role.md`, `feedback_testing.md`) using this frontmatter format:'</span>,
<span class="hljs-string" style="color: #98c379;">''</span>,
...MEMORY_FRONTMATTER_EXAMPLE,
<span class="hljs-string" style="color: #98c379;">''</span>,
<span class="hljs-string" style="color: #98c379;">`**Step 2** — add a pointer to that file in \`<span class="hljs-subst" style="color: #e06c75;">${ENTRYPOINT_NAME}</span>\`. \`<span class="hljs-subst" style="color: #e06c75;">${ENTRYPOINT_NAME}</span>\` is an index, not a memory — each entry should be one line, under ~150 characters: \`- [Title](file.md) — one-line hook\`. It has no frontmatter. Never write memory content directly into \`<span class="hljs-subst" style="color: #e06c75;">${ENTRYPOINT_NAME}</span>\`.`</span>,
</code></pre>
<p data-tool="mdnice编辑器">实现位置见 memdir.ts#L219-L230。</p>
<p data-tool="mdnice编辑器">还有有三个工程判断。</p>
<p data-tool="mdnice编辑器">第一，<code style="color: #0e8aeb;">MEMORY.md</code> 只是索引，不承载正文。如果把所有记忆都堆到一个大文件里，前期简单，后期灾难。Claude Code 从一开始就做拆分，每条记忆单文件，这样更新一条信息时不会引起全量重写。</p>
<p data-tool="mdnice编辑器">第二，frontmatter 强制有 <code style="color: #0e8aeb;">description</code> 字段，这个字段后面要参与召回。很多团队做知识条目，只写正文，不写检索摘要，最后靠 embedding 硬扛。Claude Code 反过来，它要求记忆写入阶段就产出一条高质量摘要。召回质量在写入那一刻就埋下去了。</p>
<p data-tool="mdnice编辑器">第三，完全基于文件系统，调试成本低。你可以直接去目录里看文件，团队同步时还能走 Git 或远端同步链路。数据库方案最大的问题不在性能，在可观察性。出了问题你要查 schema、查索引、查 embedding 版本、查写入日志，排障很慢。</p>
<p data-tool="mdnice编辑器">文件方案当然也有代价。文件一多，目录扫描成本会上升；<code style="color: #0e8aeb;">MEMORY.md</code> 入口过长也会逼近 prompt token 上限。Claude Code 后面靠动态召回机制兜住了这个问题，这个设计是连起来看的。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4. 写入链路</span></h1>
<p data-tool="mdnice编辑器">它怎么把记忆真正落盘</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4.1 主模型直接写</span></h2>
<p data-tool="mdnice编辑器">长期记忆的第一条写入链路，是主模型自己写。系统 prompt 里已经告诉它记忆目录在哪、允许写什么、怎么写文件、什么时候写。</p>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">loadMemoryPrompt()</code> 会把 memory rules 注入系统提示词，入口在 [loadMemoryPrompt] memdir.ts#L419-L507。这一段 prompt 并没有替模型做决策，它只是把写入协议放进脑子里：目录、类型、索引格式、读取时机、失效校验。</p>
<p data-tool="mdnice编辑器">这意味着 Claude Code 对模型的假设很明确：模型可以自己判断「这条信息值不值得保存」，然后调用写文件工具去落盘。写入不是一个外置 API，写入就是普通文件操作。</p>
<p data-tool="mdnice编辑器">这条路有个好处：反馈延迟很低。用户刚说完「记住这个偏好」，主模型当轮就能写，不用等后台任务。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4.2 后台抽取器补写</span></h2>
<p data-tool="mdnice编辑器">如果主模型这一轮没动手写，系统会在 turn end 触发后台抽取器。stop hook 在 stopHooks.ts#L141-L156：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">if</span> (
  feature(<span class="hljs-string" style="color: #98c379;">'EXTRACT_MEMORIES'</span>) &amp;&amp;
  !toolUseContext.agentId &amp;&amp;
  isExtractModeActive()
) {
  <span class="hljs-built_in" style="color: #e6c07b;">void</span> extractMemoriesModule!.executeExtractMemories(
    stopHookContext,
    toolUseContext.appendSystemMessage,
  )
}
</code></pre>
<p data-tool="mdnice编辑器">真正逻辑在 extractMemories.ts#L329-L567。</p>
<p data-tool="mdnice编辑器">这条链路里最重要的一段判断是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">if</span> (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
  logForDebugging(
    <span class="hljs-string" style="color: #98c379;">'[extractMemories] skipping — conversation already wrote to memory files'</span>,
  )
  ...
  <span class="hljs-keyword" style="color: #c678dd;">return</span>
}
</code></pre>
<p data-tool="mdnice编辑器">位置见 extractMemories.ts#L345-L360。</p>
<p data-tool="mdnice编辑器">它防的是双写。主模型已经写过，后台抽取器就别再重做一遍。很多系统做异步归档时忘了这件事，最后要么生成重复记忆，要么覆盖用户刚刚确认的内容。</p>
<p data-tool="mdnice编辑器">后台抽取器的权限也非常收敛。<code style="color: #0e8aeb;">createAutoMemCanUseTool()</code> 明确规定，只准：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">Read / Grep / Glob</section>
</li>
<li>
<section style="color: #010101;">只读 Bash</section>
</li>
<li>
<section style="color: #010101;">memory 目录内的 Edit / Write</section>
</li>
</ul>
<p data-tool="mdnice编辑器">实现见 [createAutoMemCanUseTool] extractMemories.ts#L166-L222。</p>
<p data-tool="mdnice编辑器">extractor 的职责：它只做归档，不许顺手验证代码，不许顺手修改业务文件，不许借机跑工具链。权限如果不锁死，后台代理迟早会从归档器膨胀成第二个主代理。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4.3 KAIROS 下的写法</span></h2>
<p data-tool="mdnice编辑器">KAIROS 模式更有意思。它不要求模型实时维护 <code style="color: #0e8aeb;">MEMORY.md</code>，新记忆先按天追加到日志文件里。规则在 [buildAssistantDailyLogPrompt] memdir.ts#L318-L370。</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-string" style="color: #98c379;">"This session is long-lived. As you work, record anything worth remembering by **appending** to today's daily log file:"</span>,
<span class="hljs-string" style="color: #98c379;">` \`<span class="hljs-subst" style="color: #e06c75;">${logPathPattern}</span>\` `</span>,
<span class="hljs-string" style="color: #98c379;">'Write each entry as a short timestamped bullet. Create the file (and parent directories) on first write if it does not exist. Do not rewrite or reorganize the log — it is append-only. A separate nightly process distills these logs into `MEMORY.md` and topic files.'</span>,
</code></pre>
<p data-tool="mdnice编辑器">这条策略很适合长驻 Agent。会话存活时间长时，频繁重写 topic files 和索引很贵，冲突也多。先写 append-only 日志，夜间再蒸馏，吞吐更稳，模型也更不容易在白天工作时把记忆目录写乱。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5. 召回链路</span></h1>
<p data-tool="mdnice编辑器">它怎么决定哪段记忆该进来</p>
<p data-tool="mdnice编辑器">Claude Code 的召回要分成两种看。</p>
<p data-tool="mdnice编辑器">一种是静态注入，也就是固定随上下文加载的那些东西。另一种是动态召回，根据当前 query 临时挑选最相关的记忆文件。</p>
<p data-tool="mdnice编辑器">很多系统只做前者，结果上下文越来越肥。很多系统只做后者，结果基本行为约束丢了。Claude Code 两条都做。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5.1 静态注入</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">getUserContext()</code> 会构造一个 <code style="color: #0e8aeb;">claudeMd</code> 字段，位置在 context.ts#L155-L188。</p>
<p data-tool="mdnice编辑器">核心调用是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">const</span> claudeMd = shouldDisableClaudeMd
  ? <span class="hljs-literal" style="color: #56b6c2;">null</span>
  : getClaudeMds(filterInjectedMemoryFiles(<span class="hljs-keyword" style="color: #c678dd;">await</span> getMemoryFiles()))
</code></pre>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">getMemoryFiles()</code> 的实现很长，在 claudemd.ts#L790-L1075。它会统一加载：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">Managed 指令</section>
</li>
<li>
<section style="color: #010101;">User 指令</section>
</li>
<li>
<section style="color: #010101;">Project 指令</section>
</li>
<li>
<section style="color: #010101;">Local 指令</section>
</li>
<li>
<section style="color: #010101;">AutoMem 的 <code style="color: #0e8aeb;">MEMORY.md</code></section>
</li>
<li>
<section style="color: #010101;">TeamMem 的 <code style="color: #0e8aeb;">MEMORY.md</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">然后 <code style="color: #0e8aeb;">getClaudeMds()</code> 把这些文件串成提示词内容，[getClaudeMds] claudemd.ts#L1153-L1195。</p>
<p data-tool="mdnice编辑器">它给模型一个稳定的全局工作框架。它会知道项目规则、用户偏好、团队共享记忆索引。它适合放那些「大方向会持续生效」的内容。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">5.2 动态召回</span></h2>
<p data-tool="mdnice编辑器">静态注入解决不了所有问题。长期记忆正文一多，全部塞进 prompt 代价太高。Claude Code 的处理方式，是每轮用户发言后启动一个相关记忆预取。</p>
<p data-tool="mdnice编辑器">入口在 query.ts#L297-L304：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">using pendingMemoryPrefetch = startRelevantMemoryPrefetch(
  state.messages,
  state.toolUseContext,
)
</code></pre>
<p data-tool="mdnice编辑器">这个预取不会阻塞主流程。到后面条件满足时再消费，query.ts#L1595-L1617。</p>
<p data-tool="mdnice编辑器">真正检索逻辑在 attachments.ts#L2197-L2425。它会先决定搜索哪个目录：如果用户显式提到某个 agent，就搜 agent memory；否则搜 auto memory。</p>
<p data-tool="mdnice编辑器">然后调用 [findRelevantMemories] findRelevantMemories.ts#L39-L141。</p>
<p data-tool="mdnice编辑器">这里最有意思的点在于，它没有用向量库。它的步骤是：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">
<p style="color: #000000;"><code style="color: #0e8aeb;">scanMemoryFiles()</code> 扫 memory 目录里的 <code style="color: #0e8aeb;">.md</code> 文件，读 frontmatter，产出一个 manifest<br />
见 memoryScan.ts#L35-L94</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">把 <code style="color: #0e8aeb;">用户 query + manifest</code> 发给一个 sideQuery 模型<br />
见 findRelevantMemories.ts#L77-L141</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">让这个模型返回最多 5 个文件名</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">再去读取这些文件正文，截断到限定行数和字节数<br />
见 [readMemoriesForSurfacing] attachments.ts#L2280-L2333</p>
</section>
</li>
</ol>
<p data-tool="mdnice编辑器">这套方案的好处：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">没有 embedding 构建成本</section>
</li>
<li>
<section style="color: #010101;">没有索引维护复杂度</section>
</li>
<li>
<section style="color: #010101;">manifest 很小，side query 很快</section>
</li>
<li>
<section style="color: #010101;">召回逻辑对开发者可见，容易调</section>
</li>
</ul>
<p data-tool="mdnice编辑器">缺点也明确。召回质量强依赖 frontmatter 的 <code style="color: #0e8aeb;">description</code>。写入时 description 写差了，后面召回一定差。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6. 记忆怎么进入上下文</span></h1>
<p data-tool="mdnice编辑器">不是一处注入，是四处入口</p>
<p data-tool="mdnice编辑器">很多人看 Agent 源码时老在问「上下文是在什么地方拼进去的」。这个问题本身就有误导性。Claude Code 里记忆的注入入口不止一个。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.1 system prompt 入口</span></h2>
<p data-tool="mdnice编辑器">第一处是 system prompt。这里进来的内容主要是「记忆系统的使用规则」，比如什么时候读、什么时候存、什么时候验证失效。对应实现是 prompts.ts#L492-L527 调 <code style="color: #0e8aeb;">loadMemoryPrompt()</code>。</p>
<p data-tool="mdnice编辑器">这是行为层指令。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.2 user context 入口</span></h2>
<p data-tool="mdnice编辑器">第二处是 <code style="color: #0e8aeb;">getUserContext()</code> 构造的 <code style="color: #0e8aeb;">claudeMd</code>。这里进来的是 <code style="color: #0e8aeb;">CLAUDE.md</code>、rules、<code style="color: #0e8aeb;">MEMORY.md</code> 这种比较稳定的文本。context.ts#L155-L188</p>
<p data-tool="mdnice编辑器">这是稳定背景层。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.3 attachment 入口</span></h2>
<p data-tool="mdnice编辑器">第三处是 relevant memory attachment。被召回的正文不会直接拼到 <code style="color: #0e8aeb;">claudeMd</code>，而是先变成 attachment，再由 messages.ts#L3743-L3756 包装成 <code style="color: #0e8aeb;">&lt;system-reminder&gt;</code>。</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">return</span> wrapMessagesInSystemReminder(
  attachment.memories.map(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> {
    <span class="hljs-keyword" style="color: #c678dd;">const</span> header = m.header ?? memoryHeader(m.path, m.mtimeMs)
    <span class="hljs-keyword" style="color: #c678dd;">return</span> createUserMessage({
      content: <span class="hljs-string" style="color: #98c379;">`<span class="hljs-subst" style="color: #e06c75;">${header}</span>\n\n<span class="hljs-subst" style="color: #e06c75;">${m.content}</span>`</span>,
      isMeta: <span class="hljs-literal" style="color: #56b6c2;">true</span>,
    })
  }),
)
</code></pre>
<p data-tool="mdnice编辑器">这意味着这些记忆是临时的、按轮次加载的、带 freshness header 的系统提醒。它的优先级和普通用户消息不同。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">6.4 compact summary 入口</span></h2>
<p data-tool="mdnice编辑器">第四处是 session memory compact。上下文过长后，系统会把会话前半段替换为一条 summary message，summary 内容来自 <code style="color: #0e8aeb;">summary.md</code> 的裁剪版。sessionMemoryCompact.ts#L437-L503</p>
<p data-tool="mdnice编辑器">这是上下文续命层。</p>
<p data-tool="mdnice编辑器">四处入口分工以后，就能看明白为什么 Claude Code 的行为相对稳定：规则、稳定背景、临时相关信息、压缩摘要，各走各的通道，互相不抢角色。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">7. 真正的压缩发生在哪里</span></h1>
<p data-tool="mdnice编辑器">很多人一听「记忆系统」，第一反应是长期 memory 压缩。Claude Code 里最成熟的压缩逻辑，实际上落在 session memory 上。</p>
<p data-tool="mdnice编辑器">前面说过，session memory 是 <code style="color: #0e8aeb;">summary.md</code>，它本身就是会话结构化摘要。维护逻辑在 sessionMemory.ts#L272-L350。</p>
<p data-tool="mdnice编辑器">当上下文真的不够时，系统优先尝试 <code style="color: #0e8aeb;">trySessionMemoryCompaction()</code>，sessionMemoryCompact.ts#L514-L619。</p>
<p data-tool="mdnice编辑器">它的动作顺序：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">先确认 session memory 功能和 compact 功能都开着</section>
</li>
<li>
<section style="color: #010101;">等待正在进行中的 session memory 抽取结束</section>
</li>
<li>
<section style="color: #010101;">读取 <code style="color: #0e8aeb;">summary.md</code></section>
</li>
<li>
<section style="color: #010101;">如果还是空模板，放弃，退回传统 compact</section>
</li>
<li>
<section style="color: #010101;">计算需要保留的 recent messages 窗口</section>
</li>
<li>
<section style="color: #010101;">用 <code style="color: #0e8aeb;">summary.md</code> 的裁剪版构造 compact summary</section>
</li>
<li>
<section style="color: #010101;">组装 <code style="color: #0e8aeb;">boundary + summary + recent messages + attachments + hooks</code></section>
</li>
</ol>
<p data-tool="mdnice编辑器">这里的「保留 recent messages」特别关键。作者没有图省事把所有旧消息都抹掉，而是保留一段最近窗口。窗口大小由 [DEFAULT_SM_COMPACT_CONFIG] sessionMemoryCompact.ts#L56-L66 定义，默认：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">minTokens = 10000</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">minTextBlockMessages = 5</code></section>
</li>
<li>
<section style="color: #010101;"><code style="color: #0e8aeb;">maxTokens = 40000</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">保留窗口的计算在 [calculateMessagesToKeepIndex] sessionMemoryCompact.ts#L324-L397。</p>
<p data-tool="mdnice编辑器">这个策略解决的问题是：<strong style="color: #0e88eb;">摘要永远会损失细节</strong>，最近一段工作现场最好保留原始消息，模型续做时不至于失真。要是所有内容都只剩 summary，模型会失去工具调用上下文、局部错误信息、最近的计划变更。</p>
<p data-tool="mdnice编辑器">更细的一层防御在 <code style="color: #0e8aeb;">adjustIndexToPreserveAPIInvariants()</code>。 sessionMemoryCompact.ts#L232-L314</p>
<p data-tool="mdnice编辑器">它干的事情很硬核，也很必要：如果最近保留窗口里出现了 <code style="color: #0e8aeb;">tool_result</code>，系统必须把匹配的 <code style="color: #0e8aeb;">tool_use</code> 也补进来；如果 assistant 消息因为流式输出被拆成多个共享 <code style="color: #0e8aeb;">message.id</code> 的块，thinking 和 tool_use 也要一起补齐。否则 compact 后发给 API 的消息链会断，直接报错。</p>
<p data-tool="mdnice编辑器">这一段代码说明作者踩过坑，或者至少认真想过 API 侧不变量。很多开源 Agent 框架在消息压缩这里写得很草，最后线上 bug 都长一个样：tool result 找不到 parent，thinking 丢了，message 合并失败。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">8. session memory 自己也会被裁剪</span></h1>
<p data-tool="mdnice编辑器">就算 <code style="color: #0e8aeb;">summary.md</code> 已经是摘要，compact 时还会再做一次 section 级裁剪。逻辑在 [truncateSessionMemoryForCompact] prompts.ts#L249-L295。</p>
<p data-tool="mdnice编辑器">过程如下：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">按 <code style="color: #0e8aeb;"># section</code> 拆段</section>
</li>
<li>
<section style="color: #010101;">每个 section 允许的大小用 <code style="color: #0e8aeb;">MAX_SECTION_LENGTH * 4</code> 粗略换算成字符数</section>
</li>
<li>
<section style="color: #010101;">超过就按行保留前半部分</section>
</li>
<li>
<section style="color: #010101;">最后插入 <code style="color: #0e8aeb;">[... section truncated for length ...]</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">实际截断函数见 [flushSessionSection] prompts.ts#L298-L324。</p>
<p data-tool="mdnice编辑器">这套逻辑谈不上优雅，语义理解也谈不上深入，但它有一个优点：非常稳。系统真的到了上下文极限时，保底截断总比把整个 compact 失败掉强。工程里很多时候要的是「退化可接受」，不是「完美压缩」。</p>
<p data-tool="mdnice编辑器">然后 <code style="color: #0e8aeb;">createCompactionResultFromSessionMemory()</code> 把裁剪后的 session memory 包成 summary message。 sessionMemoryCompact.ts#L437-L503</p>
<p data-tool="mdnice编辑器">这里还有一个细节：如果发生过裁剪，它会额外附一句话，告诉模型和人类完整 session memory 文件路径在哪。排障时你可以直接打开原始 <code style="color: #0e8aeb;">summary.md</code>，不用猜裁掉了什么。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">9. KAIROS 的压缩逻辑和普通模式不一样</span></h1>
<p data-tool="mdnice编辑器">KAIROS 里还有另一种「压缩」，它压的不是当前上下文，而是长期事件流。</p>
<p data-tool="mdnice编辑器">在 memdir.ts#L321-L349 里能看到，KAIROS 模式下白天写的是 append-only daily log。到夜间，<code style="color: #0e8aeb;">/dream</code> 流程会把这些日志蒸馏成 topic files 和 <code style="color: #0e8aeb;">MEMORY.md</code>。</p>
<p data-tool="mdnice编辑器">这是另一类压缩：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">输入是时间顺序日志</section>
</li>
<li>
<section style="color: #010101;">输出是主题化长期记忆</section>
</li>
</ul>
<p data-tool="mdnice编辑器">session memory compact 处理的是「上下文窗口」问题。KAIROS dream 处理的是「长期事件沉淀」问题。这两类压缩混在一起看会非常乱，源码里其实已经把它们分得很开。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">10 小结</span></h2>
<p data-tool="mdnice编辑器">这套设计的工程代价与收益</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">10.1 一些值得学习的点</span></h2>
<p data-tool="mdnice编辑器">第一，分层彻底。长期记忆、当前轮召回、会话摘要、子代理记忆，各自有自己的存储形态和注入入口。系统复杂度是被隔离开的。</p>
<p data-tool="mdnice编辑器">第二，文件优先。排查方便，审计方便，人工纠错方便。很多团队高估了数据库和向量库的必要性，低估了可观察性的重要性。</p>
<p data-tool="mdnice编辑器">第三，动态召回走轻量 manifest + side query。对 CLI Agent 这种高频交互场景，这个方案的性价比很高。它把复杂度留给模型的小规模选择，而不是重型检索基础设施。</p>
<p data-tool="mdnice编辑器">第四，压缩时保 recent window，并修补 tool_use/tool_result 不变量。这一点极少有团队一开始就写对。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">10.2 一些代价</span></h2>
<p data-tool="mdnice编辑器">第一，frontmatter 的 <code style="color: #0e8aeb;">description</code> 质量变成关键依赖。这个字段一旦写烂，召回效果会大幅波动。它省掉了 embedding 的复杂度，也把一部分压力前置给写入质量。</p>
<p data-tool="mdnice编辑器">第二，双通道写入意味着状态机会更复杂。主模型可以写，后台 extractor 也能写。虽然代码里有跳过逻辑，但这类架构天然比单通道更需要小心。</p>
<p data-tool="mdnice编辑器">第三，session memory 的 section 截断是粗粒度的。它靠字符数近似 token，再按行截断，这属于保底工程，不属于精细压缩。能用，谈不上漂亮。</p>
<p data-tool="mdnice编辑器">第四，<code style="color: #0e8aeb;">MEMORY.md</code> 仍然有索引容量压力。即便动态召回已经分担了很大一部分负担，入口索引的组织质量依然重要。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">10.3 我们能用什么</span></h2>
<p data-tool="mdnice编辑器">如果把这套思路迁移到我们自己的 Agent 系统，可以借鉴（抄）：</p>
<p data-tool="mdnice编辑器">第一，先拆问题，再选技术。你要先决定自己在解哪件事：跨会话长期记忆、当前轮检索、超长对话压缩、团队共享经验。不要一上来就建一个统一 Memory API。</p>
<p data-tool="mdnice编辑器">第二，先用文件，再考虑数据库。只要你的系统规模还没逼到那个份上，文件系统几乎总是更划算。它便宜、透明、好调试。很多团队用数据库，是因为觉得那样「更像正经系统」，这个判断没什么含金量。</p>
<p data-tool="mdnice编辑器">第三，把召回质量的责任前移到写入阶段。Claude Code 用 <code style="color: #0e8aeb;">description</code> 做 manifest 检索这件事，给我的启发很大。与其指望后面靠复杂召回算法弥补，不如要求写入时就产出高质量摘要和类型信息。</p>
<p data-tool="mdnice编辑器">如果你们团队正在做本地化的企业内 Agent，我甚至建议先抄一版这种架构原型：<br />
长期记忆用 Markdown + frontmatter，当前轮召回走 manifest + 小模型筛选，会话压缩单独维护结构化 summary。三周内就能跑起来。比起一开始堆向量库、事件总线、关系数据库，这条路短得多。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">11. 其它</span></h1>
<p data-tool="mdnice编辑器">Claude Code 的记忆系统没有神秘技术。它的难点不在某个单点算法，在边界控制和时机设计。什么时候写，写到哪里，什么时候读，读多少，压缩后保留什么，这些问题都比「用什么模型做召回」更重要。</p>
<p data-tool="mdnice编辑器">如果你把这篇文章里的结论压成一句工程建议，那就是：<br />
先把长期记忆、短期相关记忆、会话压缩拆开，再谈检索和存储。</p>
<p data-tool="mdnice编辑器">源码入口可以优先读这几组文件：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">
<p style="color: #000000;">长期记忆规则与路径：<br />
[memdir.ts] [paths.ts] [memoryTypes.ts]</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">记忆静态注入与动态召回：<br />
[claudemd.ts] [attachments.ts] [findRelevantMemories.ts]</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">会话记忆与压缩：<br />
[sessionMemory.ts] [prompts.ts] [sessionMemoryCompact.ts]</p>
</section>
</li>
<li>
<section style="color: #010101;">
<p style="color: #000000;">团队共享记忆：<br />
[teamMemPaths.ts] [teamMemorySync/index.ts]</p>
</section>
</li>
</ul>
<p data-tool="mdnice编辑器">以上。</p>
</section>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/04/claude-code-source-memory/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>深度拆解 Claude Code 系统提示词中的记忆管理逻辑</title>
		<link>https://www.phppan.com/2026/03/in-depth-analysis-of-the-memory-management-logic-in-claude-code-system-prompts/</link>
		<comments>https://www.phppan.com/2026/03/in-depth-analysis-of-the-memory-management-logic-in-claude-code-system-prompts/#comments</comments>
		<pubDate>Sun, 29 Mar 2026 00:21:43 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[AIAgent]]></category>
		<category><![CDATA[ClaudeCode]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2482</guid>
		<description><![CDATA[最近在做 Agent 相关的工作，研究了 Claude Code 的系统提示词。分享一下看到的东西。 Clau [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">最近在做 Agent 相关的工作，研究了 Claude Code 的系统提示词。分享一下看到的东西。</p>
<p data-tool="mdnice编辑器">Claude Code 这套逻辑最值得学习的部分，不是它有多少类型，也不是它怎么写文件，而是它把「记忆」从聊天历史里剥离成了一个有边界的系统对象。</p>
<p data-tool="mdnice编辑器">其提示词给出了四段闭环：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">类型化存储</section>
</li>
<li>
<section style="color: #010101;">索引化管理</section>
</li>
<li>
<section style="color: #010101;">触发式召回</section>
</li>
<li>
<section style="color: #010101;">使用前校验</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这套闭环的根本目的只有一个：<strong>极度压缩进入大模型上下文的无效 Token</strong>。</p>
<h1 data-tool="mdnice编辑器"><span class="content">类型化存储</span></h1>
<p data-tool="mdnice编辑器">类型化存储解决的是「谁有资格被记住」的问题。</p>
<p data-tool="mdnice编辑器">Claude Code 里把记忆分成 <code style="color: #ef7060;">user / feedback / project / reference</code>。这一步看上去像分类，实际上是在做准入控制。</p>
<p data-tool="mdnice编辑器">很多团队一开始偷懒，做一个统一的 memory 表，字段有 <code style="color: #ef7060;">content</code>、<code style="color: #ef7060;">created_at</code>、<code style="color: #ef7060;">embedding</code>，剩下全靠检索兜底。前期跑 demo 很爽，后期一团糟。因为「用户偏好」「项目约束」「纠错反馈」「外部入口」这几类东西的生命周期、可信度、更新频率和召回优先级完全不同。你把它们混在一起，后面所有策略都要靠额外条件补救。</p>
<p data-tool="mdnice编辑器">Claude Code 这里的好处在于，它先承认记忆不是同质数据。类型不同，保存条件就不同，召回方式也不同。</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><code style="color: #ef7060;">user</code> 影响的是回答风格和交互方式，天然高权重。</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #ef7060;">feedback</code> 代表用户纠正过的内容，这类信息如果不复用，系统会反复踩一个坑。</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #ef7060;">project</code> 带明显时效性，过期不处理就是埋雷。</section>
</li>
<li>
<section style="color: #010101;"><code style="color: #ef7060;">reference</code> 更接近外部入口或指针，重点在可定位，不在长文本本身。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这种分类把后面复杂度最高的事情提前处理了，这就不用在召回阶段临时猜「这一条历史到底算偏好还是事实」，因为写入时已经分流了。</p>
<h1 data-tool="mdnice编辑器"><span class="content">索引化管理</span></h1>
<p data-tool="mdnice编辑器">Claude Code 会「先写独立记忆文件，再更新 <code style="color: #ef7060;">MEMORY.md</code> 索引」。这里把正文存储和索引存储分开了。</p>
<p data-tool="mdnice编辑器">有两个收益。</p>
<p data-tool="mdnice编辑器">第一，索引足够轻。<code style="color: #ef7060;">MEMORY.md</code> 只存索引，不存正文。这样它天然适合作为一个轻量入口，被优先加载、优先扫描、优先过滤。</p>
<p data-tool="mdnice编辑器">第二，正文可以演进。真正的记忆文件有 frontmatter 和正文，这意味着它可以承载更完整的上下文，而不用把所有内容都堆到一个总文件里。总文件一旦既做索引又做正文，后面就很难控制体积，也很难做精细更新。</p>
<p data-tool="mdnice编辑器">在写入时，有两条规则。</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">同主题记忆优先 update，避免重复新增。</section>
</li>
<li>
<section style="color: #010101;">用户明确说 forget，就删除对应记忆。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这两条是在控制系统熵增。记忆只要能无限追加，迟早会出现语义重复、事实冲突、时间污染。只做新增，不做更新和删除，系统很快就会进入「候选很多，但没有一条完全可信」的状态。到了那个阶段，召回层再聪明也救不回来。</p>
<h1 data-tool="mdnice编辑器"><span class="content">触发式召回</span></h1>
<p data-tool="mdnice编辑器">Claude Code 的建议流程是：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">先判断当前请求是否需要记忆；</section>
</li>
<li>
<section style="color: #010101;">按类型和关键词做少量 Top-K 粗召回；</section>
</li>
<li>
<section style="color: #010101;">再按「任务相关性 &gt; 新鲜度 &gt; 可靠性」精筛；</section>
</li>
<li>
<section style="color: #010101;">只注入必要片段；</section>
</li>
<li>
<section style="color: #010101;">如果和当前事实冲突，以当前事实为准并回写修正。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">其逻辑有如下几种：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">强指令触发（显式召回）：当用户明确下达指令（如“查一下”、“回想一下”、“你还记得吗”）时，系统被<strong style="color: #000000;">强制（MUST）</strong>触发召回链路。</section>
</li>
<li>
<section style="color: #010101;">上下文/语义触发（隐式召回）：系统在对话过程中，如果发现当前任务与已有记忆具有强相关性，或者用户提到了“之前的对话/工作”，则隐式触发召回。这要求大模型在理解当前意图时，顺带做一次记忆相关性判定。</section>
</li>
<li>
<section style="color: #010101;">负向门控触发（屏蔽/阻断召回）：当用户明确要求“忽略记忆”或“不要用记忆”时，系统必须直接切断召回链路，假装索引文件 MEMORY.md 是空的，防止历史上下文污染当前的新任务。</section>
</li>
</ol>
<h1 data-tool="mdnice编辑器"><span class="content">使用前校验</span></h1>
<p data-tool="mdnice编辑器">使用前校验，解决的是「记忆不是事实源」</p>
<p data-tool="mdnice编辑器">记忆里如果提到文件、函数、flag，落地前必须重新核验当前状态。</p>
<p data-tool="mdnice编辑器">记忆的本质是「<strong>过去曾经成立过的信息</strong>」。代码仓库、配置开关、函数签名这些东西会变。如果把记忆当事实源，模型越有记忆，出错概率越高。尤其在代码场景里，这种错会放大。因为模型不是只回答一句话，它还会基于过期事实继续生成修改方案、命令、排障路径。</p>
<p data-tool="mdnice编辑器">记忆负责缩小搜索空间，当前状态负责给出最终裁决。</p>
<p data-tool="mdnice编辑器"><strong>做记忆系统时，最警惕的一直是脏记忆。空记忆顶多让模型少一点个性，脏记忆会直接让模型说错话。</strong></p>
<p data-tool="mdnice编辑器">以上。</p>
<p data-tool="mdnice编辑器">附原始提示词（2.1.86 版本）</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">
<span class="hljs-comment" style="font-style: italic; color: #5c6370;">## auto memory</span>

You have a persistent, file-based memory system at `/root/.claude/projects/-tmp-claude-history-1774690103689-avi2cu/memory/`. This directory already exists — write to it directly with the Write tool (<span class="hljs-keyword" style="color: #c678dd;">do</span> not run mkdir or check <span class="hljs-keyword" style="color: #c678dd;">for</span> its existence).

You should build up this memory system over time so that future conversations can have a complete picture of who the user is, how they<span class="hljs-string" style="color: #98c379;">'d like to collaborate with you, what behaviors to avoid or repeat, and the context behind the work the user gives you.

If the user explicitly asks you to remember something, save it immediately as whichever type fits best. If they ask you to forget something, find and remove the relevant entry.

### Types of memory

There are several discrete types of memory that you can store in your memory system:

&lt;types&gt;
&lt;type&gt;
    &lt;name&gt;user&lt;/name&gt;
    &lt;description&gt;Contain information about the user'</span>s role, goals, responsibilities, and knowledge. Great user memories <span class="hljs-built_in" style="color: #e6c07b;">help</span> you tailor your future behavior to the user<span class="hljs-string" style="color: #98c379;">'s preferences and perspective. Your goal in reading and writing these memories is to build up an understanding of who the user is and how you can be most helpful to them specifically. For example, you should collaborate with a senior software engineer differently than a student who is coding for the very first time. Keep in mind, that the aim here is to be helpful to the user. Avoid writing memories about the user that could be viewed as a negative judgement or that are not relevant to the work you'</span>re trying to accomplish together.&lt;/description&gt;
    &lt;when_to_save&gt;When you learn any details about the user<span class="hljs-string" style="color: #98c379;">'s role, preferences, responsibilities, or knowledge&lt;/when_to_save&gt;
    &lt;how_to_use&gt;When your work should be informed by the user'</span>s profile or perspective. For example, <span class="hljs-keyword" style="color: #c678dd;">if</span> the user is asking you to explain a part of the code, you should answer that question <span class="hljs-keyword" style="color: #c678dd;">in</span> a way that is tailored to the specific details that they will find most valuable or that helps them build their mental model <span class="hljs-keyword" style="color: #c678dd;">in</span> relation to domain knowledge they already have.&lt;/how_to_use&gt;
    &lt;examples&gt;
    user: I<span class="hljs-string" style="color: #98c379;">'m a data scientist investigating what logging we have in place
    assistant: [saves user memory: user is a data scientist, currently focused on observability/logging]

    user: I'</span>ve been writing Go <span class="hljs-keyword" style="color: #c678dd;">for</span> ten years but this is my first time touching the React side of this repo
    assistant: [saves user memory: deep Go expertise, new to React and this project<span class="hljs-string" style="color: #98c379;">'s frontend — frame frontend explanations in terms of backend analogues]
    &lt;/examples&gt;
&lt;/type&gt;
&lt;type&gt;
    &lt;name&gt;feedback&lt;/name&gt;
    &lt;description&gt;Guidance the user has given you about how to approach work — both what to avoid and what to keep doing. These are a very important type of memory to read and write as they allow you to remain coherent and responsive to the way you should approach work in the project. Record from failure AND success: if you only save corrections, you will avoid past mistakes but drift away from approaches the user has already validated, and may grow overly cautious.&lt;/description&gt;
    &lt;when_to_save&gt;Any time the user corrects your approach ("no not that", "don'</span>t<span class="hljs-string" style="color: #98c379;">", "</span>stop doing X<span class="hljs-string" style="color: #98c379;">") OR confirms a non-obvious approach worked ("</span>yes exactly<span class="hljs-string" style="color: #98c379;">", "</span>perfect, keep doing that<span class="hljs-string" style="color: #98c379;">", accepting an unusual choice without pushback). Corrections are easy to notice; confirmations are quieter — watch for them. In both cases, save what is applicable to future conversations, especially if surprising or not obvious from the code. Include *why* so you can judge edge cases later.&lt;/when_to_save&gt;
    &lt;how_to_use&gt;Let these memories guide your behavior so that the user does not need to offer the same guidance twice.&lt;/how_to_use&gt;
    &lt;body_structure&gt;Lead with the rule itself, then a **Why:** line (the reason the user gave — often a past incident or strong preference) and a **How to apply:** line (when/where this guidance kicks in). Knowing *why* lets you judge edge cases instead of blindly following the rule.&lt;/body_structure&gt;
    &lt;examples&gt;
    user: don't mock the database in these tests — we got burned last quarter when mocked tests passed but the prod migration failed
    assistant: [saves feedback memory: integration tests must hit a real database, not mocks. Reason: prior incident where mock/prod divergence masked a broken migration]

    user: stop summarizing what you just did at the end of every response, I can read the diff
    assistant: [saves feedback memory: this user wants terse responses with no trailing summaries]

    user: yeah the single bundled PR was the right call here, splitting this one would've just been churn
    assistant: [saves feedback memory: for refactors in this area, user prefers one bundled PR over many small ones. Confirmed after I chose this approach — a validated judgment call, not a correction]
    &lt;/examples&gt;
&lt;/type&gt;
&lt;type&gt;
    &lt;name&gt;project&lt;/name&gt;
    &lt;description&gt;Information that you learn about ongoing work, goals, initiatives, bugs, or incidents within the project that is not otherwise derivable from the code or git history. Project memories help you understand the broader context and motivation behind the work the user is doing within this working directory.&lt;/description&gt;
    &lt;when_to_save&gt;When you learn who is doing what, why, or by when. These states change relatively quickly so try to keep your understanding of this up to date. Always convert relative dates in user messages to absolute dates when saving (e.g., "</span>Thursday<span class="hljs-string" style="color: #98c379;">" → "</span>2026-03-05<span class="hljs-string" style="color: #98c379;">"), so the memory remains interpretable after time passes.&lt;/when_to_save&gt;
    &lt;how_to_use&gt;Use these memories to more fully understand the details and nuance behind the user's request and make better informed suggestions.&lt;/how_to_use&gt;
    &lt;body_structure&gt;Lead with the fact or decision, then a **Why:** line (the motivation — often a constraint, deadline, or stakeholder ask) and a **How to apply:** line (how this should shape your suggestions). Project memories decay fast, so the why helps future-you judge whether the memory is still load-bearing.&lt;/body_structure&gt;
    &lt;examples&gt;
    user: we're freezing all non-critical merges after Thursday — mobile team is cutting a release branch
    assistant: [saves project memory: merge freeze begins 2026-03-05 for mobile release cut. Flag any non-critical PR work scheduled after that date]

    user: the reason we're ripping out the old auth middleware is that legal flagged it for storing session tokens in a way that doesn't meet the new compliance requirements
    assistant: [saves project memory: auth middleware rewrite is driven by legal/compliance requirements around session token storage, not tech-debt cleanup — scope decisions should favor compliance over ergonomics]
    &lt;/examples&gt;
&lt;/type&gt;
&lt;type&gt;
    &lt;name&gt;reference&lt;/name&gt;
    &lt;description&gt;Stores pointers to where information can be found in external systems. These memories allow you to remember where to look to find up-to-date information outside of the project directory.&lt;/description&gt;
    &lt;when_to_save&gt;When you learn about resources in external systems and their purpose. For example, that bugs are tracked in a specific project in Linear or that feedback can be found in a specific Slack channel.&lt;/when_to_save&gt;
    &lt;how_to_use&gt;When the user references an external system or information that may be in an external system.&lt;/how_to_use&gt;
    &lt;examples&gt;
    user: check the Linear project "</span>INGEST<span class="hljs-string" style="color: #98c379;">" if you want context on these tickets, that's where we track all pipeline bugs
    assistant: [saves reference memory: pipeline bugs are tracked in Linear project "</span>INGEST<span class="hljs-string" style="color: #98c379;">"]

    user: the Grafana board at grafana.internal/d/api-latency is what oncall watches — if you're touching request handling, that's the thing that'll page someone
    assistant: [saves reference memory: grafana.internal/d/api-latency is the oncall latency dashboard — check it when editing request-path code]
    &lt;/examples&gt;
&lt;/type&gt;
&lt;/types&gt;

### What NOT to save in memory

- Code patterns, conventions, architecture, file paths, or project structure — these can be derived by reading the current project state.
- Git history, recent changes, or who-changed-what — `git log` / `git blame` are authoritative.
- Debugging solutions or fix recipes — the fix is in the code; the commit message has the context.
- Anything already documented in CLAUDE.md files.
- Ephemeral task details: in-progress work, temporary state, current conversation context.

These exclusions apply even when the user explicitly asks you to save. If they ask you to save a PR list or activity summary, ask what was *surprising* or *non-obvious* about it — that is the part worth keeping.

### How to save memories

Saving a memory is a two-step process:

**Step 1** — write the memory to its own file (e.g., `user_role.md`, `feedback_testing.md`) using this frontmatter format:


---
name: {{memory name}}
description: {{one-line description — used to decide relevance in future conversations, so be specific}}
type: {{user, feedback, project, reference}}
---

{{memory content — for feedback/project types, structure as: rule/fact, then **Why:** and **How to apply:** lines}}


**Step 2** — add a pointer to that file in `MEMORY.md`. `MEMORY.md` is an index, not a memory — each entry should be one line, under ~150 characters: `- [Title](file.md) — one-line hook`. It has no frontmatter. Never write memory content directly into `MEMORY.md`.

- `MEMORY.md` is always loaded into your conversation context — lines after 200 will be truncated, so keep the index concise
- Keep the name, description, and type fields in memory files up-to-date with the content
- Organize memory semantically by topic, not chronologically
- Update or remove memories that turn out to be wrong or outdated
- Do not write duplicate memories. First check if there is an existing memory you can update before writing a new one.

### When to access memories
- When memories seem relevant, or the user references prior-conversation work.
- You MUST access memory when the user explicitly asks you to check, recall, or remember.
- If the user says to *ignore* or *not use* memory: proceed as if MEMORY.md were empty. Do not apply remembered facts, cite, compare against, or mention memory content.
- Memory records can become stale over time. Use memory as context for what was true at a given point in time. Before answering the user or building assumptions based solely on information in memory records, verify that the memory is still correct and up-to-date by reading the current state of the files or resources. If a recalled memory conflicts with current information, trust what you observe now — and update or remove the stale memory rather than acting on it.

### Before recommending from memory

A memory that names a specific function, file, or flag is a claim that it existed *when the memory was written*. It may have been renamed, removed, or never merged. Before recommending it:

- If the memory names a file path: check the file exists.
- If the memory names a function or flag: grep for it.
- If the user is about to act on your recommendation (not just asking about history), verify first.

"</span>The memory says X exists<span class="hljs-string" style="color: #98c379;">" is not the same as "</span>X exists now.<span class="hljs-string" style="color: #98c379;">"

A memory that summarizes repo state (activity logs, architecture snapshots) is frozen in time. If the user asks about *recent* or *current* state, prefer `git log` or reading the code over recalling the snapshot.

### Memory and other forms of persistence
Memory is one of several persistence mechanisms available to you as you assist the user in a given conversation. The distinction is often that memory can be recalled in future conversations and should not be used for persisting information that is only useful within the scope of the current conversation.
- When to use or update a plan instead of memory: If you are about to start a non-trivial implementation task and would like to reach alignment with the user on your approach you should use a Plan rather than saving this information to memory. Similarly, if you already have a plan within the conversation and you have changed your approach persist that change by updating the plan rather than saving a memory.
- When to use or update tasks instead of memory: When you need to break your work in current conversation into discrete steps or keep track of your progress use tasks instead of saving to memory. Tasks are great for persisting information about the work that needs to be done in the current conversation, but memory should be reserved for information that will be useful in future conversations.
</span></code></pre>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/03/in-depth-analysis-of-the-memory-management-logic-in-claude-code-system-prompts/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
