<?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>潘锦的空间</title>
	<atom:link href="https://www.phppan.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sat, 25 Apr 2026 00:56:17 +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>对最近 AI 落地工程实践的一些想法和思考</title>
		<link>https://www.phppan.com/2026/04/some-thoughts-and-reflections-on-recent-ai-implemented-engineering-practices/</link>
		<comments>https://www.phppan.com/2026/04/some-thoughts-and-reflections-on-recent-ai-implemented-engineering-practices/#comments</comments>
		<pubDate>Sat, 25 Apr 2026 00:56:17 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[AI幻觉]]></category>
		<category><![CDATA[AI架构]]></category>
		<category><![CDATA[RAG]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2493</guid>
		<description><![CDATA[最近和小区某上市公司的 CFO 喝茶聊 AI，在过程中思维和实际场景的碰撞，记录如下： 穿透复杂的表象，当前  [&#8230;]]]></description>
				<content:encoded><![CDATA[<p style="color: #424b5d;" data-tool="mdnice编辑器">最近和小区某上市公司的 CFO 喝茶聊 AI，在过程中思维和实际场景的碰撞，记录如下：</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">穿透复杂的表象，当前 LLM 的底层运行逻辑其实非常单一：它本质上是一个自回归的序列生成器，根据已有的上下文，计算词表中每一个 token 出现的概率分布，然后从中采样出下一个 token。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">但这里的「概率」绝非毫无逻辑的随机掷骰子。 这种概率分布，是模型在海量预训练数据中内化的语言规律、世界知识以及逻辑推理能力的数学投影。通过多层 Transformer 网络与注意力机制（Attention），模型在极高的维度上完成了对上下文语义的深度解析与特征关联，从而将符合人类逻辑、契合当前语境的 token 赋予极高的概率权重。它是在用统计学的方式，重现人类的逻辑推理过程。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">然而，无论其内部的概率计算多么精密，从软件工程的宏观视角来看，我们本质上依然是在传统的确定性系统中，强行引入了一个基于概率采样的非确定性组件。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">传统软件工程建立在严格的确定性之上。输入特定的参数，经过固定的业务逻辑，必然得到预期的输出。现在我们将核心逻辑交由概率模型处理，相同的输入在不同的时间点，可能会产生完全不同的输出路径。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">幻觉无法被根除。它是自回归模型的内生特性，是概率采样的必然产物。我们在进行系统架构设计时，必须将幻觉视为系统的常态。试图通过修改 Prompt 来彻底消除幻觉，在工程上徒劳无功。我们需要在系统边界处建立起拦截机制，用确定性的规则去兜底概率模型的不确定性。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">容错度决定落地</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">当前商业化落地最顺畅、ROI 最高的场景，全部集中在高容错度领域。写行业报告、生成营销文案、文生图、视频生成、游戏 NPC 对话。这类场景的核心特征在于缺乏绝对的客观标准。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">在内容创作领域，模型偶尔的逻辑发散会被用户视为创造力。工程团队不需要在接口的绝对可用性和输出的绝对准确性上死磕，只需要保证底线的内容安全和合理的响应延迟。系统可用性达到 95% 就能让用户产生极强的获得感。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">一旦进入低容错度场景，工程实现的复杂度会呈指数级上升。医疗诊断、工业控制、核心交易链路。在这些领域，0.1% 的幻觉率都会导致灾难性的业务后果。我们在评估一个 AI 项目是否立项时，首要考量指标就是业务场景的容错底线。容错度越低，外围需要的确定性校验代码就越厚重，最终会导致系统的维护成本远超 AI 带来的效率提升。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">知识外挂 RAG</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">RAG 的出现是为了解决模型内部知识更新滞后和私有数据隔离的问题。其核心原理是将外部文档切片、向量化，在用户提问时检索相关切片，拼接到 Prompt 中作为上下文喂给大模型。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">在实际的工程环境里，RAG 的核心瓶颈在检索链路。切片策略直接决定了召回质量。按固定 token 长度切分会破坏语义完整性，导致关键信息被腰斩。按标点符号或段落切分会导致切片长度方差过大，影响向量化模型的表达能力。我们在生产环境中通常需要针对不同格式的文档编写定制化的解析器，将 PDF 或 Word 还原为结构化的文档树，再基于文档树的层级进行语义切片。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">单一的向量检索在面对专有名词和长尾词汇时表现极差。我们必须采用混合检索架构：稠密向量检索加上稀疏词表检索。向量检索负责语义泛化，处理同义词和模糊表达。词表检索负责精准匹配产品型号、人名和内部项目代号。混合检索引入了多路召回合并的问题，通常需要引入倒数秩融合算法来重排结果。系统复杂度和查询延迟会成倍增加。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">数据清洗占据了 RAG 项目 80% 的研发精力。直接将企业内部的原始文档灌入向量数据库，最终的问答准确率通常不到 40%。文档中存在大量的废话、过期的流程规范以及相互冲突的条款。垃圾进，垃圾出。我们在构建知识库之前，必须通过脚本和人工介入，对语料进行严格的去重、降噪和结构化提取。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">工具调用确定性</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">为了弥补概率模型的缺陷，我们需要引入确定性的工具。Function Calling 机制本质上是给 LLM 接上双手。模型负责理解自然语言意图并提取结构化参数，具体的业务逻辑交由传统的确定性脚本执行。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器"><strong>工具调用的工程难点在于参数提取的稳定性</strong>。当注册的工具数量超过十个，或者参数结构嵌套层级过深时，模型的输出格式极易崩溃。我们在中间层必须加入严格的 Schema 校验机制。一旦校验失败，需要截断错误信息并触发重试。重试次数上限通常设定为 3 次，继续增加会耗尽上下文窗口并导致请求超时。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">多轮工具调用会带来严重的延迟问题。模型每决定调用一次工具，都需要经历一次完整的网络请求和推理过程。如果一个复杂任务需要串行调用三个工具，用户的等待时间会轻易突破 10 秒。我们在架构设计时，需要尽可能将细粒度的 API 聚合成粗粒度的宏接口，<strong>减少模型与业务系统的交互频次</strong>。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">Agent 架构的脆弱性与状态管理</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">多智能体（Multi-Agent）架构在技术社区被过度神话。多个大模型相互协作、自主规划任务的 Demo 看起来非常惊艳。在真实的工业场景中，完全由 LLM 自主驱动的 Agent 链路极其脆弱。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">误差会在多步推理中被迅速放大。假设单个 Agent 节点的输出准确率为 90%，一个包含五个节点的串行任务，最终的成功率会暴跌至 59%。任何一个节点的幻觉都会导致后续链路彻底跑偏。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">我们在生产环境中构建复杂任务流时，坚决摒弃由 LLM 自主决定执行路径的黑盒模式。控制流必须由传统的有向无环图（DAG）或状态机来接管。LLM 仅仅作为状态机中的一个计算节点，负责处理非结构化数据的理解和生成。节点与节点之间的状态流转、条件判断、异常重试，全部由确定性的代码实现。这种设计牺牲了系统的灵活性，换取了业务系统必须具备的稳定性和可观测性。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">非确定性系统的测试与监控</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">非确定性系统的测试与监控，是传统软件工程团队转型 AI 开发时遇到的最大痛点。传统的单元测试基于断言，期望输出是固定的字符串或数值。面对 LLM 每次都不一样的回答，基于精确匹配的 CI/CD 流水线会全线崩溃。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">我们重构了整个测试评估体系。引入 LLM-as-a-Judge 机制，使用一个能力更强、参数规模更大的模型来评估业务模型的输出质量。评估维度被拆解为相关性、事实一致性、格式合规性等具体指标。在每次模型版本迭代或 Prompt 修改后，必须在包含上千个真实业务 Case 的黄金数据集上运行自动化评估。只有各项指标的波动在可控范围内，才能进行灰度发布。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">在监控层面，传统的 APM 工具无法满足需求。我们需要采集每一个请求的 Prompt 模板版本、输入变量、输出结果、Token 消耗量以及推理延迟。这些数据是后续进行 Bad Case 分析和模型微调的唯一原料。针对 Token 消耗的监控直接与业务成本挂钩。我们会在网关层设置严格的并发限制和预算熔断机制，防止恶意请求或死循环调用导致账单失控。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">两种范式的碰撞</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">AI First 与 AI 辅助是完全不同的架构逻辑。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">AI 辅助是在现有系统中打补丁。主干流程依然是传统的表单和按钮，AI 作为一个侧边栏或悬浮窗存在，提供总结、翻译、润色功能。开发成本极低，对原有系统无侵入。用户在遇到问题时，可以选择性地向 AI 求助。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">AI First 要求重构整个交互形态和底层流转逻辑。系统不再依赖预设的菜单树，由 LLM 充当中央路由。用户的自然语言输入直接驱动底层状态机流转。这要求所有内部 API 具备极高的自描述能力，业务逻辑必须高度解耦。我们在推进 AI First 架构时，面临的最大阻力通常来自老旧系统的技术债。历史遗留的紧耦合代码根本无法被封装成独立的工具供模型调用。</p>
<h1 style="color: #000000;" data-tool="mdnice编辑器"><span style="font-weight: bold; color: #e7642b;">财务场景的拆解</span></h1>
<p style="color: #424b5d;" data-tool="mdnice编辑器">财务场景是典型的低容错度、高确定性要求的领域。将概率模型直接应用于财务核心链路会引发严重的合规风险。可落地的切入点集中在外围的非结构化数据处理和信息流转环节。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">发票与报销单据的信息抽取是一个高价值场景。传统 OCR 结合正则匹配在面对版式多变的票据时维护成本极高。引入大模型进行多模态信息抽取，将非结构化的图片或 PDF 转换为结构化的 JSON 数据。抽取后的数据必须经过传统规则引擎的二次校验，例如金额试算平衡验证、税号合规性检查。模型在这里承担的是「粗加工」角色，最终的业务落库动作依然由确定性代码把控。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">财务制度问答可以大幅降低沟通成本。基于企业内部报销规范构建 RAG 系统。员工在提单前通过自然语言查询报销标准。这里的 RAG 必须严格限制模型的发散，Prompt 中需强制要求「仅根据检索到的内容回答，未提及的内容直接回复不知道」。为了防止模型编造财务政策，我们会在输出层增加一层文本相似度校验，确保模型的回答与检索到的原文保持高度一致。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">财务分析报告初稿生成也是一个可行的方向。将结构化的财务报表数据通过代码转换为文本描述，作为上下文喂给模型，让其生成趋势分析和异常波动提示。模型在这里仅作为「翻译官」和「排版员」，不参与任何数值计算。所有的同比、环比计算必须在传统代码层完成，将计算结果以明确的数值形式提供给模型。让 LLM 去做算术题是工程上的反模式。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">数据隐私在财务场景中是不可逾越的红线。公有云 API 无法满足审计要求。我们通常需要采用本地私有化部署的开源模型。7B 到 14B 参数规模的模型经过量化处理后，可以在单张消费级显卡上流畅运行。通过针对财务语料的微调，这些小模型在特定信息抽取任务上的表现可以持平甚至超越千亿参数的通用大模型。私有化部署带来了硬件采购和模型运维的额外成本，需要在项目初期进行严格的 ROI 测算。</p>
<p style="color: #424b5d;" data-tool="mdnice编辑器">以上</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/04/some-thoughts-and-reflections-on-recent-ai-implemented-engineering-practices/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<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>
		<item>
		<title>OpenClaw 的 Skills 的实现和 Claude Code 不一样</title>
		<link>https://www.phppan.com/2026/03/openclaw-skilss-claude-code/</link>
		<comments>https://www.phppan.com/2026/03/openclaw-skilss-claude-code/#comments</comments>
		<pubDate>Sat, 21 Mar 2026 01:01:17 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[OpenClaw]]></category>
		<category><![CDATA[skills]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2479</guid>
		<description><![CDATA[OpenClaw 的 Skills，本质上不是一个「可调用工具」，它更像一套经过约束的运行手册：启动时把技能目 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">OpenClaw 的 Skills，本质上不是一个「可调用工具」，它更像一套经过约束的运行手册：启动时把技能目录扫描出来，压成一份 <code>&lt;available_skills&gt;</code> 清单塞进 system prompt，模型自己判断要不要选一个 skill，然后再通过 Read 工具去读这个 skill 的 <code>SKILL.md</code>。读完以后，没有任何独立执行器接管，还是在当前这条 session 的 tool-loop 里继续跑。</p>
<p data-tool="mdnice编辑器">Claude Code 走的是另一条路。它把 skill 做成了 tool，工具里负责校验、加载、执行，甚至可以放进一个新上下文里跑完，再把结果回传主对话。</p>
<p data-tool="mdnice编辑器">这两个实现方向，表面上都叫 skills，工程含义完全不一样。</p>
<p data-tool="mdnice编辑器">如果你是做 Agent 平台、企业内 Copilot、代码助手、任务执行器，这个差异不只是架构图上的审美问题。它会直接影响：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>prompt 预算怎么花</section>
</li>
<li>
<section>权限边界放在哪里</section>
</li>
<li>
<section>skill 的治理成本有多高</section>
</li>
<li>
<section>运行链路是可控还是失控</section>
</li>
<li>
<section>你后面想不想做隔离执行、审计、回放、灰度发布</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content">1. OpenClaw 的 Skills，到底是怎么被「召回」的</span></h1>
<p data-tool="mdnice编辑器">很多人一看到 skills，第一反应是：是不是和 memory 一样，先走 embedding 检索，再把相关技能召回进上下文。</p>
<p data-tool="mdnice编辑器">不是。</p>
<p data-tool="mdnice编辑器">OpenClaw 的 skills 召回机制非常直接，甚至可以说有点「朴素」：<strong>扫描目录，生成目录清单，注入提示词，让模型自己选</strong>。整条链路分三段：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>发现</section>
</li>
<li>
<section>注入</section>
</li>
<li>
<section>读取</section>
</li>
</ol>
<p data-tool="mdnice编辑器">OpenClaw 反过来做了一件更工程化的事：先把 skill 列表显式给模型，再用 system prompt 约束它怎么选。</p>
<p data-tool="mdnice编辑器">这个机制的起点在 <code>src/agents/skills/workspace.ts</code> 的 <code>loadSkillEntries()</code>。</p>
<h2 data-tool="mdnice编辑器"><span class="content">1.1 技能发现：先扫目录，再谈调用</span></h2>
<p data-tool="mdnice编辑器">OpenClaw 会从多个 root 目录扫描 <code>SKILL.md</code>，然后合并成最终技能集。这里是有明确的覆盖优先级的：</p>
<p data-tool="mdnice编辑器"><code>extra &lt; bundled &lt; managed &lt; agents-personal &lt; agents-project &lt; workspace</code></p>
<p data-tool="mdnice编辑器">它的逻辑是：越靠近当前工作空间、越贴近用户项目的 skill，优先级越高。平台预置的 bundled skill 可以兜底，但项目级 skill 要能覆盖它。否则你做企业落地时会很难受，团队定制流程永远被平台内置逻辑压着打。</p>
<p data-tool="mdnice编辑器">从目录结构看，OpenClaw 支持两种 skill 形态：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>root 本身就是一个 skill，目录下直接有 <code>SKILL.md</code></section>
</li>
<li>
<section>root 下的子目录分别是 skill，每个子目录里有自己的 <code>SKILL.md</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">这样，团队在实际维护 skill 时，有两种典型组织方式：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>单一 skill 仓库，根目录就是技能内容</section>
</li>
<li>
<section>skills 集合仓库，每个子目录一个技能</section>
</li>
</ul>
<p data-tool="mdnice编辑器">都支持，落地阻力会小很多。</p>
<p data-tool="mdnice编辑器">它对 <code>SKILL.md</code> 做了体积限制，默认超过 256KB 就直接跳过。</p>
<p data-tool="mdnice编辑器"><code>SKILL.md</code> 本来就应该是高密度操作指南，不应该演变成一个什么都往里塞的大文档。你一旦允许 skill 文件无限变大，后面一定会有人把 SOP、FAQ、设计文档、事故复盘全扔进去，最后技能选择和加载成本一起爆炸。OpenClaw 在扫描阶段直接卡体积，其实是在替平台维护纪律。</p>
<h2 data-tool="mdnice编辑器"><span class="content">1.2 frontmatter 解析</span></h2>
<p data-tool="mdnice编辑器">给 skill 增加最小限度的结构化元信息</p>
<p data-tool="mdnice编辑器">扫描到 <code>SKILL.md</code> 后，OpenClaw 会读原文并解析 frontmatter。坏掉或者缺失的 frontmatter 会被忽略。</p>
<p data-tool="mdnice编辑器">这个决策也很对。</p>
<p data-tool="mdnice编辑器">OpenClaw 这里更像是「有就用，没有拉倒」。它承认 markdown 本体才是 skill 的核心，frontmatter 只是辅助控制面。这个姿态很适合 skills 这种增长很快、来源很多的资产类型。</p>
<p data-tool="mdnice编辑器">但这里也埋了一个工程 trade-off：frontmatter 被弱约束，意味着后续治理和平台能力扩展会受限。你今天只做 discovery 和 basic gating，这么玩没问题；你明天如果要做 skill 分类、依赖分析、版本兼容、批量审计，元信息松散会让成本陡增。</p>
<p data-tool="mdnice编辑器">OpenClaw 当前站在了「先把系统跑起来」的一边，没有走「先把规范做重」的路子。</p>
<p data-tool="mdnice编辑器">这意味着它更适合中小规模 skill 生态，或者说更适合个人助手这类工具，而不是一个强治理的企业级 skill marketplace。</p>
<h1 data-tool="mdnice编辑器"><span class="content">2. 不是所有 skill 都会进 <code>&lt;available_skills&gt;</code></span></h1>
<p data-tool="mdnice编辑器">扫描出来只是候选集，不等于模型能看到。</p>
<p data-tool="mdnice编辑器">OpenClaw 在注入 system prompt 之前会做一轮 gating。这个步骤比很多人想象得重要，因为它决定了模型到底暴露给了哪些能力。</p>
<p data-tool="mdnice编辑器">过滤逻辑里有几类条件。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.1 配置开关</span></h2>
<p data-tool="mdnice编辑器">最基础的 enable/disable</p>
<p data-tool="mdnice编辑器">如果某个 skill 在配置里被标成 <code>skills.entries.&lt;skillKey&gt;.enabled === false</code>，它就会被剔除。</p>
<p data-tool="mdnice编辑器">这是最基础的 kill switch。工程上没什么可争议的，必须有。不然你没法快速止血。某个 skill 写坏了、依赖环境挂了、被发现会引导模型做危险操作，没有一键下线能力，平台就不配上线。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.2 bundled allowlist</span></h2>
<p data-tool="mdnice编辑器">对内置技能单独管控</p>
<p data-tool="mdnice编辑器"><code>skills.allowBundled</code> 只约束 bundled 来源。</p>
<p data-tool="mdnice编辑器">这个细节很有意思。它说明 OpenClaw 把 bundled skills 当成一类特殊资产处理：平台自带，但不默认无条件信任。</p>
<p data-tool="mdnice编辑器">为什么这事重要？因为内置 skill 经常是平台演进中最容易「偷偷变多」的那部分。你今天打包 5 个，明天为了演示方便塞到 20 个，后天 prompt 里一大坨 descriptions，模型选 skill 的噪声越来越大。allowlist 的存在，本质上是在给平台预装能力上保险栓。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.3 eligibility 判断</span></h2>
<p data-tool="mdnice编辑器">按运行环境判断 skill 是否可用</p>
<p data-tool="mdnice编辑器">OpenClaw 会根据 <code>metadata.requires</code> 去判断 skill 能不能用，条件包括：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>二进制依赖是否存在</section>
</li>
<li>
<section>环境变量是否满足</section>
</li>
<li>
<section>配置是否具备</section>
</li>
<li>
<section>操作系统是否匹配</section>
</li>
<li>
<section>remote 平台是否匹配</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这个设计非常工程化。因为 skill 如果涉及真实操作，它一定和环境耦合。比如某个 skill 依赖特定 cli，或者要求某个 API token，或者只能在 Linux 跑。你如果不在注入前做 eligibility，而是让模型先看到 skill，再在执行时失败，用户体验会很差，模型行为也会变形：它会看到一个貌似可用的方案，实际一调就炸。</p>
<p data-tool="mdnice编辑器">OpenClaw 选择「<strong>先过滤，再暴露</strong>」，这是我非常认同的策略。因为对模型来说，看得见就等于潜在可用。你让它看见无效 skill，本质上是在制造认知噪声。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.4 <code>disable-model-invocation</code></span></h2>
<p data-tool="mdnice编辑器">系统内部保留，模型不可见</p>
<p data-tool="mdnice编辑器">如果某个 skill 标记了 <code>disable-model-invocation</code>，它不会进入 prompt 的 <code>&lt;available_skills&gt;</code>，但仍然存在于系统内部，可用于管理或校验。</p>
<p data-tool="mdnice编辑器">因为技能资产不只有「给模型用」这一种角色。你可能有些 skill 需要：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>用于测试</section>
</li>
<li>
<section>用于审核</section>
</li>
<li>
<section>用于运维校验</section>
</li>
<li>
<section>用于内部 pipeline</section>
</li>
<li>
<section>用于未来发布但暂不开放</section>
</li>
</ul>
<p data-tool="mdnice编辑器">OpenClaw 没把「存在」和「可被模型调用」绑死，这样 skill registry 才像个平台能力，而不是一个 prompt 拼装器。</p>
<h1 data-tool="mdnice编辑器"><span class="content">3. <code>&lt;available_skills&gt;</code> 才是 OpenClaw 的真正召回入口</span></h1>
<p data-tool="mdnice编辑器">很多人说 skills 被召回，其实这句话在 OpenClaw 语境里容易让人误解。</p>
<p data-tool="mdnice编辑器">真正被放进上下文里的第一层，不是 <code>SKILL.md</code> 内容，而是 <code>&lt;available_skills&gt;</code> 这个目录清单。它由 <code>buildWorkspaceSkillsPrompt()</code> 生成，注入到 system prompt。</p>
<p data-tool="mdnice编辑器">也就是说，模型最先看到的是技能目录，不是技能正文。</p>
<p data-tool="mdnice编辑器">这个做法也算是渐进式披露的一种实现。先给模型足够决定是否要读 skill 的最小信息，避免一开始就把所有技能全文灌进去。</p>
<h2 data-tool="mdnice编辑器"><span class="content">3.1 system prompt 里的强规则，决定了技能选择流程</span></h2>
<p data-tool="mdnice编辑器">OpenClaw 在 system prompt 里有一段明确的 Skills mandatory 规则，大意是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>先扫描 <code>&lt;available_skills&gt;</code> 里的 <code>&lt;description&gt;</code></section>
</li>
<li>
<section>只有明确匹配一个 skill 时，才去读对应的 <code>SKILL.md</code></section>
</li>
<li>
<section>最多只读一个 skill</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这三条里，最重要的是后两条。</p>
<h3 data-tool="mdnice编辑器"><span class="content">3.1.1 只有明确匹配一个才读</span></h3>
<p data-tool="mdnice编辑器">这是在压制模型的泛化冲动。模型很喜欢「我觉得这个也相关，那个也相关」，然后多读几个，最后把自己卷进上下文泥潭。OpenClaw 用提示词硬性要求「明确匹配一个」才允许进入下一步，这本质上是在给模型加稀疏化约束。</p>
<p data-tool="mdnice编辑器">效果未必 100% 可控，但方向是对的。</p>
<h3 data-tool="mdnice编辑器"><span class="content">3.1.2 最多只读一个 skill</span></h3>
<p data-tool="mdnice编辑器">如果你做过基于 prompt 的 tool orchestration，就会知道一个常见死法：模型在一堆说明里来回跳，读完 A 觉得 B 也有用，再读 B，顺手 C 也看看，最后 token 花了不少，任务还没干。</p>
<p data-tool="mdnice编辑器">OpenClaw 这里直接规定 upfront 只能读一个 skill。我个人判断，这不是因为理论上最优，而是因为工程上最稳。</p>
<p data-tool="mdnice编辑器">它牺牲了一部分组合式技能能力，换来了几个非常实际的好处：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>token 消耗更可控</section>
</li>
<li>
<section>模型决策路径更短</section>
</li>
<li>
<section>skill 选择失败更容易定位</section>
</li>
<li>
<section>调试和回放更容易做</section>
</li>
</ul>
<p data-tool="mdnice编辑器">代价也很明确：如果任务天然需要多个 skill 协同，OpenClaw 当前机制会显得笨。模型要么自己在单个 skill 指导下绕着做，要么根本选不准。</p>
<p data-tool="mdnice编辑器">这也是它和 Claude Code 一个很大的分歧。Claude Code 把 skill 视作 tool，更容易天然支持复杂编排；OpenClaw 把 skill 视作可读说明，天然倾向于单次聚焦。</p>
<h2 data-tool="mdnice编辑器"><span class="content">3.2 技能过多时的降级策略：compact 和 truncated</span></h2>
<p data-tool="mdnice编辑器"><code>&lt;available_skills&gt;</code> 不是无上限注入的。技能太多时，OpenClaw 会降级成 compact 格式，甚至截断，并提示 <code>skills truncated</code>。</p>
<p data-tool="mdnice编辑器">这个点看起来像 prompt 小技巧，其实是很重要的预算治理。</p>
<p data-tool="mdnice编辑器">因为技能系统一旦跑起来，数量几乎只会越来越多。最初十几个 skill，description 全量塞进去还行；到几十上百个 skill，再这么搞，system prompt 很快会变成垃圾堆。OpenClaw 至少意识到了这个问题，所以做了两级退化：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>compact：保留更少信息</section>
</li>
<li>
<section>truncated：截断并明确提示</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这说明它把 prompt 当成有限资源，而不是无限容器。</p>
<p data-tool="mdnice编辑器">但实话实说，这个策略只是在「延缓爆炸」，不是根治。skill 数量继续增长以后，靠 compact/truncate 顶不住。因为问题不只是 token 多了，而是模型在目录里做决策的辨识难度会越来越高。description 再压缩，skill 之间的区分度也会变差。</p>
<p data-tool="mdnice编辑器">所以我对这块的判断是：<strong>OpenClaw 的 catalog 注入机制适合中等规模 skill 集，不适合无限扩张。</strong><br />
如果你团队未来真要做上百个 skill 的企业级平台，迟早要引入更分层的 catalog、分类路由、或者显式 selector，而不是靠一坨 <code>&lt;available_skills&gt;</code> 让模型裸选。</p>
<h1 data-tool="mdnice编辑器"><span class="content">4. 真正的 <code>SKILL.md</code> 是怎么进入上下文的</span></h1>
<p data-tool="mdnice编辑器">OpenClaw 的第二层加载是按需读取。模型先看到目录清单，选中 skill 以后，才通过 Read 工具去读对应路径的 <code>SKILL.md</code>。</p>
<h2 data-tool="mdnice编辑器"><span class="content">4.1 技能正文不是 system prompt 的一部分</span></h2>
<p data-tool="mdnice编辑器">system prompt 里只有规则和技能目录。真正的 <code>SKILL.md</code> 内容，是在模型发起一次 read tool call 后，作为 toolResult 追加进当前消息流。</p>
<p data-tool="mdnice编辑器">也就是说，skill 正文进入上下文的方式，和你用工具读一个普通文件没有本质区别。差别只是 system prompt 先规定了什么时候允许这样读。</p>
<p data-tool="mdnice编辑器">这个设计的好处非常明确：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>大幅降低初始上下文体积</section>
</li>
<li>
<section>让技能正文成为按需成本，而不是固定成本</section>
</li>
<li>
<section>skill 更新后无需改 system prompt 模板，只影响运行时读到的内容</section>
</li>
<li>
<section>便于把 skill 当文件资产管理，而不是 prompt 模板片段</section>
</li>
</ul>
<p data-tool="mdnice编辑器">坏处也很明确：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>skill 的执行效果更依赖模型有没有正确触发 read</section>
</li>
<li>
<section>skill 的权威性被弱化成一条 toolResult，而不是顶层指令</section>
</li>
<li>
<section>如果 toolResult 很长，后续上下文里它和普通读文件结果没什么层级差异</section>
</li>
</ul>
<p data-tool="mdnice编辑器">从实现角度看，OpenClaw 这里是很克制的。它没有发明一个 Skill Runtime，没有做一套专门的 skill 调度协议，就是借已有的 read 工具完成正文加载。</p>
<p data-tool="mdnice编辑器">这让系统保持简单，但也让 skill 更像「读到的一份说明书」，而不是「被激活的执行单元」。</p>
<h1 data-tool="mdnice编辑器"><span class="content">5. OpenClaw 的安全边界</span></h1>
<p data-tool="mdnice编辑器">既然 skill 是文件，模型要去读 <code>SKILL.md</code>，安全问题马上就来了：路径能不能逃逸？symbolic link 能不能把 skill 指到 root 外？模型能不能顺着 location 读到不该读的内容？</p>
<p data-tool="mdnice编辑器">OpenClaw 这里做了两层保护。</p>
<h2 data-tool="mdnice编辑器"><span class="content">5.1 第一层：扫描阶段的 containment 检查</span></h2>
<p data-tool="mdnice编辑器">在技能发现阶段，它会对 realpath 做 containment 检查，防止 symlink 把 skill 指到 root 外面。</p>
<p data-tool="mdnice编辑器">这是典型的「尽早失败」策略。我一直觉得文件系统相关的安全问题，能在发现阶段拦的，不要拖到执行阶段。因为一旦把异常路径放进 skill registry，后面每个环节都得假设它可能有问题，整个系统会变得很难推理。</p>
<p data-tool="mdnice编辑器">扫描时就把越界项挡掉，registry 内部的数据至少是干净的。</p>
<h2 data-tool="mdnice编辑器"><span class="content">5.2 第二层：Read 工具的 workspace root guard</span></h2>
<p data-tool="mdnice编辑器">模型真正发起读取时，Read 工具还会做 sandbox root 校验，禁止 <code>..</code> 或绝对路径逃逸 workspace root。</p>
<p data-tool="mdnice编辑器">也就是说，即使 discovery 阶段没出问题，真正读取文件时还有一层运行时防线。</p>
<p data-tool="mdnice编辑器">文件边界这事，单点防护永远不够。扫描阶段挡的是「注册进来的 skill 本身」，读取阶段挡的是「模型实际提出的路径参数」。二者保护的对象不一样。</p>
<p data-tool="mdnice编辑器">工程上要是只做第一层，你会被运行时路径拼接坑；只做第二层，你会把很多脏数据带进 registry，影响可观测性和调试。</p>
<p data-tool="mdnice编辑器">OpenClaw 在这块虽然不算复杂，但做法是标准的。</p>
<h1 data-tool="mdnice编辑器"><span class="content">6. Skills 的渐进式披露</span></h1>
<p data-tool="mdnice编辑器">OpenClaw 的 skills 机制，如果只看「枚举目录 -&gt; 注入 prompt -&gt; read 文件」，会有人觉得太朴素。真正值得注意的是，它和 memory 一样，贯彻了一套很明确的渐进式披露思路。</p>
<p data-tool="mdnice编辑器">这是上下文预算治理的逻辑。</p>
<p data-tool="mdnice编辑器">原则就两条：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>先给最小可用信息</section>
</li>
<li>
<section>确认需要后再扩大读取范围</section>
</li>
</ol>
<p data-tool="mdnice编辑器">在 OpenClaw 里，memory 和 skills 都是这么干的，只是路径不同。</p>
<h2 data-tool="mdnice编辑器"><span class="content">6.1 Memory 是先搜 snippet，再精读指定行段</span></h2>
<p data-tool="mdnice编辑器"><code>memory_search</code> 先返回短片段，带 path 和 line range，不是全文注入。底层还有 <code>SNIPPET_MAX_CHARS = 700</code> 的硬上限。如果后端预算紧，还会继续裁结果。</p>
<p data-tool="mdnice编辑器">之后再通过 <code>memory_get</code> 按 <code>path + from/lines</code> 去拉具体行段。</p>
<p data-tool="mdnice编辑器">这个流程是非常标准的 progressive disclosure：先定位，再精读。</p>
<h2 data-tool="mdnice编辑器"><span class="content">6.2 Skills 是先给目录，再只读一个 <code>SKILL.md</code></span></h2>
<p data-tool="mdnice编辑器">skills 这边没有向量召回，而是目录清单。模型先看 <code>&lt;available_skills&gt;</code>，只在明确匹配一个时才读正文，并且 upfront 最多一个。</p>
<p data-tool="mdnice编辑器">两条链路表面不同，本质一样：都在防止大块文本无脑灌进上下文。</p>
<p data-tool="mdnice编辑器">我为什么说这点值钱？因为很多团队做 Agent 系统时，最大的问题不是模型不会做事，而是上下文管理太粗糙。什么都想喂进去，最后 token 烧得飞快，模型还因为噪声太高做不准。</p>
<p data-tool="mdnice编辑器">OpenClaw 至少在架构层面承认了一件事实：<strong>上下文是预算，不是仓库。</strong></p>
<p data-tool="mdnice编辑器">这件事说起来简单，真正落实到工具语义、提示词规则、底层裁剪，很多系统做不到。</p>
<h1 data-tool="mdnice编辑器"><span class="content">7. OpenClaw 没有「独立 Skill 执行器」</span></h1>
<p data-tool="mdnice编辑器">在 OpenClaw 里，skill 读完之后，并没有一个单独的执行环境接管它。没有所谓「Skill Runtime」去解释 <code>SKILL.md</code>，也没有「新上下文执行 skill」这回事。它还是在同一个 <code>activeSession.prompt(...)</code> 的 tool-loop 里继续跑。</p>
<p data-tool="mdnice编辑器">链路大概是这样：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>system prompt 里给出 <code>&lt;available_skills&gt;</code> 和选择规则</section>
</li>
<li>
<section>模型决定某个 skill 匹配当前请求</section>
</li>
<li>
<section>模型调用 read 工具读取对应 <code>SKILL.md</code></section>
</li>
<li>
<section>toolResult 里带回 <code>SKILL.md</code> 文本</section>
</li>
<li>
<section>这个 toolResult 被追加到当前 session 的 messages</section>
</li>
<li>
<section>模型再次被调用，看到刚才读到的 skill 内容</section>
</li>
<li>
<section>模型按 skill 指导，继续发起后续 toolCall，比如 <code>exec</code>、<code>write</code>、<code>edit</code></section>
</li>
<li>
<section>直到输出最终回答</section>
</li>
</ol>
<p data-tool="mdnice编辑器">关键点：<strong>skill 内容只是同一消息流里多了一条 toolResult</strong>。</p>
<p data-tool="mdnice编辑器">这意味着什么？</p>
<p data-tool="mdnice编辑器">意味着 OpenClaw 的 skill 执行，本质上是「模型读了一份流程说明，然后继续在原对话里行动」。它没有新的边界，没有新的记忆隔离，没有新的权限域。真正的隔离，来自工具列表裁剪和文件读写沙箱，不来自上下文切换。</p>
<p data-tool="mdnice编辑器">这和 Claude Code 很不一样。</p>
<h1 data-tool="mdnice编辑器"><span class="content">8. Claude Code 的 skills，更像「可调用子程序」</span></h1>
<p data-tool="mdnice编辑器">Claude Code 的 skill 流程有几个特征：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>skill 是 tools 列表里的一个明确工具</section>
</li>
<li>
<section>模型会调用 Skill tool，而不是自己去 read 某个 markdown</section>
</li>
<li>
<section>skill prompt 的加载是 tool 内部动作</section>
</li>
<li>
<section>权限校验也主要发生在 tool 内</section>
</li>
<li>
<section>skill 可以在新上下文执行，执行完再把结果带回主对话</section>
</li>
</ul>
<p data-tool="mdnice编辑器">从工程抽象上看，Claude Code 的 skill 更像一个「子程序入口」。它有名字、有调用接口、有内部装载逻辑，甚至有上下文隔离能力。</p>
<p data-tool="mdnice编辑器">OpenClaw 没有走这条路。它把 skill 设计成「模型可读的文件」，由模型自己决定是否展开，并在原上下文里继续操作。</p>
<p data-tool="mdnice编辑器">这两个方向，没有谁天然高级，但适用面不同</p>
<p data-tool="mdnice编辑器">如果你要的是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>更强的执行隔离</section>
</li>
<li>
<section>更容易做权限封装</section>
</li>
<li>
<section>更容易做结果回传和子任务边界</section>
</li>
<li>
<section>更适合复杂、多步骤、可复用的任务单元</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Claude Code 那种 tool 化 skill 更合适。</p>
<p data-tool="mdnice编辑器">如果你要的是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>实现简单</section>
</li>
<li>
<section>skill 作者门槛低</section>
</li>
<li>
<section>把 skill 当 markdown 资产管理</section>
</li>
<li>
<section>快速把流程知识接进现有 ReAct tool-loop</section>
</li>
</ul>
<p data-tool="mdnice编辑器">OpenClaw 这种文件化 skill 更实用。</p>
<p data-tool="mdnice编辑器">工程上不看理念，看代价结构。</p>
<h1 data-tool="mdnice编辑器"><span class="content">9. OpenClaw 为什么会做成这样</span></h1>
<p data-tool="mdnice编辑器">我猜它的设计动机是：<strong>尽量复用现有 agent runtime，而不是为 skills 单独发明一层执行框架。</strong></p>
<p data-tool="mdnice编辑器">你看它的做法就知道了：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>discovery 复用文件系统扫描</section>
</li>
<li>
<section>selection 复用 system prompt</section>
</li>
<li>
<section>loading 复用 read 工具</section>
</li>
<li>
<section>execution 复用原有 tool-loop</section>
</li>
<li>
<section>safety 复用 sandbox path guard 和 tool policy</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这是一套很节制的架构思路。好处是：</p>
<p data-tool="mdnice编辑器"><strong>实现成本低</strong> ：不用造新协议，不用加新消息类型，不用多一层 runtime。对一个正在演进中的 agent 系统来说，这非常现实。 <strong>Skill 作者认知负担小</strong>：本质上写一个 <code>SKILL.md</code> 就行。对于组织内部推广，这是巨大优势。因为真正阻碍 skill 规模化的，从来不是模型能力，而是作者生态能不能起来。 <strong>运行链路可观测性还不错</strong>：所有事情都发生在同一 session 里。read 了什么、接着调了什么工具、toolResult 长什么样，回放起来相对直观。</p>
<p data-tool="mdnice编辑器">但它的代价也有。</p>
<p data-tool="mdnice编辑器"><strong>代价 1：skill 缺少强执行边界</strong></p>
<p data-tool="mdnice编辑器">skill 本身不是 tool，没有独立权限面。你没法像封装一个函数那样，给 skill 绑定专属 schema、专属校验、专属 side effect 边界。最终还是模型在拿到 skill 文本后，自由地调用后续工具。</p>
<p data-tool="mdnice编辑器">这就意味着 skill 的约束力主要来自提示词，不来自执行器。</p>
<p data-tool="mdnice编辑器"><strong>代价 2：组合式编排能力弱</strong></p>
<p data-tool="mdnice编辑器">system prompt 里要求 upfront 最多读一个 skill，这对预算控制很好，对复杂任务不友好。多 skill 协同在这个体系里不是一等公民。</p>
<p data-tool="mdnice编辑器"><strong>代价 3：skill 的结果不可天然封装</strong></p>
<p data-tool="mdnice编辑器">Claude Code 那种「在新上下文执行 skill，再返回结果」，天然有个输入输出边界。OpenClaw 这里没有。skill 一旦展开，后续动作直接混进主会话消息流。你很难把它当作一个独立执行单元来治理。</p>
<p data-tool="mdnice编辑器"><strong>代价 4：对模型的选择质量要求高</strong></p>
<p data-tool="mdnice编辑器">因为没有独立 selector，也没有 tool-level dispatcher，skill 能不能选对，主要靠 <code>&lt;available_skills&gt;</code> 的 descriptions 和 system prompt 规则。这对 skill 描述质量要求很高。写得不清楚，模型就选歪。数量一多，问题更明显。</p>
<h1 data-tool="mdnice编辑器"><span class="content">10. 权限控制</span></h1>
<p data-tool="mdnice编辑器">OpenClaw 的思路是「先裁剪能力，再让模型行动」</p>
<p data-tool="mdnice编辑器">这一点我很喜欢，因为它比「调用时再临时拦截」更稳。</p>
<p data-tool="mdnice编辑器">OpenClaw 的权限控制，不是把所有工具都亮给模型，然后在调用某个危险工具时说不行。它更像是：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>先根据 policy pipeline 把不允许的工具从列表里移掉</section>
</li>
<li>
<section>再把剩下的工具暴露给模型</section>
</li>
<li>
<section>模型根本看不到被禁的能力</section>
</li>
</ol>
<p data-tool="mdnice编辑器">skills 这边也一样：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>不合格的 skill 不进入 <code>&lt;available_skills&gt;</code></section>
</li>
<li>
<section><code>disable-model-invocation</code> 的 skill 对模型不可见</section>
</li>
<li>
<section>Read 路径有 workspace root guard</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这种「先裁剪，再推理」的方式有个很大的好处：模型认知空间更干净。它不会围着一堆不可用能力打转，也不会生成大量被拒调用的无效动作。</p>
<p data-tool="mdnice编辑器">如果你做企业场景，这比事后 deny 好得多。因为用户只会看到 agent 做合理尝试，而不是不停撞墙。</p>
<p data-tool="mdnice编辑器">当然，它的代价是灵活性差一点。你没法在非常细粒度的时刻做动态放行，除非重新构建 tool list 或 skill list。但我认为这笔账是划算的。Agent 系统的第一优先级不是灵活，是可控。</p>
<h1 data-tool="mdnice编辑器"><span class="content">11. 从平台设计角度看呢</span></h1>
<p data-tool="mdnice编辑器">觉得比较好的点：</p>
<p data-tool="mdnice编辑器"><strong>第一，简单。</strong><br />
这个简单不是简陋，是尽量不新增系统概念。skill 就是文件，加载靠 read，执行靠原有 tool-loop。对演进中的 agent 框架来说，这是非常健康的选择。</p>
<p data-tool="mdnice编辑器"><strong>第二，预算意识强。</strong><br />
<code>&lt;available_skills&gt;</code> 目录注入、最多只读一个 skill、compact/truncated 降级，这些都说明它把 context 当稀缺资源处理。</p>
<p data-tool="mdnice编辑器"><strong>第三，权限思路对。</strong><br />
先过滤 skill 和 tool，再让模型行动。可见性先于可调用性，这很工程化。</p>
<p data-tool="mdnice编辑器"><strong>第四，适合知识流程化。</strong><br />
很多团队真正需要的，不是一个会自己发明流程的 agent，而是把组织内已有 SOP 结构化地喂给模型。OpenClaw 这套很适合干这个。</p>
<p data-tool="mdnice编辑器">一些局限：</p>
<p data-tool="mdnice编辑器"><strong>第一，规模扩展性一般。</strong><br />
catalog 注入机制天然不适合 skill 数量无限增长。它适合几十级别，不适合大规模 marketplace。</p>
<p data-tool="mdnice编辑器"><strong>第二，组合能力弱。</strong><br />
「最多只读一个 skill」对稳态有帮助，对复杂任务编排是限制。</p>
<p data-tool="mdnice编辑器"><strong>第三，skill 缺少运行时身份。</strong><br />
它不是 tool，没有明确的输入输出边界，也没有独立权限与审计单元。后续做深治理会比较难。</p>
<p data-tool="mdnice编辑器"><strong>第四，过度依赖模型选择。</strong><br />
一旦 descriptions 写得不好，或者目录过大，skill 选择质量会变成系统上限。</p>
<h1 data-tool="mdnice编辑器"><span class="content">12. 适用场景</span></h1>
<p data-tool="mdnice编辑器">在系统架构选型时，如果有以下的条件，可以优先选择 OpenClaw 的设计逻辑：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>团队已经有一套成熟的 tool-loop，想低成本引入 skills</section>
</li>
<li>
<section>主要目标是把流程知识、操作手册、领域步骤注入 agent</section>
</li>
<li>
<section>skill 作者很多，技术水平参差不齐，需要低门槛 markdown 入口</section>
</li>
<li>
<section>更看重实现速度和治理可落地，而不是复杂编排能力</section>
</li>
<li>
<section>skill 数量在可控范围内，不会迅速膨胀到几百个</section>
</li>
</ul>
<p data-tool="mdnice编辑器">有如下的情况时，就不太适用使用这种方案：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>skill 本质上是可执行业务子任务，需要独立生命周期</section>
</li>
<li>
<section>需要强隔离执行、单独审计、结果回传和失败恢复</section>
</li>
<li>
<section>任务天然需要多个 skill 协同编排</section>
</li>
<li>
<section>技能库规模会非常大，必须做复杂路由</section>
</li>
<li>
<section>权限模型要求细到「某个 skill 可以做 A 但不能做 B」</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些场景下，我更倾向 Claude Code 那种 tool 化 skill，或者更进一步，直接走 subagent / workflow node / task runtime。</p>
<h1 data-tool="mdnice编辑器"><span class="content">13. 小结</span></h1>
<p data-tool="mdnice编辑器">如果只用一句话概括两者区别：</p>
<p data-tool="mdnice编辑器"><strong>Claude Code 的 skill 更像可调用子程序，OpenClaw 的 skill 更像按需展开的运行手册。</strong></p>
<p data-tool="mdnice编辑器">前者强调执行单元，后者强调上下文注入。<br />
前者天然适合隔离和编排，后者天然适合轻量接入和流程沉淀。<br />
前者的复杂度在 runtime，后者的复杂度在 prompt 和内容治理。</p>
<p data-tool="mdnice编辑器">OpenClaw 用极其精简的 Prompt 规则和现成的 Read 工具，低成本实现了 Agent Skills 标准。它够用，但也把长文本管理的压力，原封不动地推给了底层大模型的 Context Window。</p>
<p data-tool="mdnice编辑器">我个人对 OpenClaw 这套实现是认可的，前提是别把它想象成一个万能技能平台。它解决的是「如何在不重写 agent runtime 的前提下，把结构化流程知识接进模型执行链路」这个问题。这个问题它解得挺干净。</p>
<p data-tool="mdnice编辑器">但如果你要的是 Claude Code 那种「新上下文执行 skill、像子程序一样调用、跑完把结果带回来」，你该找的是子会话、subagent、任务编排层，而不是继续往 <code>SKILL.md</code> 上堆规则。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/03/openclaw-skilss-claude-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>聊下 OpenClaw 的记忆系统</title>
		<link>https://www.phppan.com/2026/03/openclaw-memory/</link>
		<comments>https://www.phppan.com/2026/03/openclaw-memory/#comments</comments>
		<pubDate>Sun, 15 Mar 2026 11:37:01 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[Memory System]]></category>
		<category><![CDATA[OpenClaw]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2477</guid>
		<description><![CDATA[OpenClaw 是最近 AI 圈最火的一个开源项目，没有之一。 从去年的 Agent 年，到今年的 AI 个 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">OpenClaw 是最近 AI 圈最火的一个开源项目，没有之一。</p>
<p data-tool="mdnice编辑器">从去年的 Agent 年，到今年的 AI 个人助理，OpenClaw 和去年 Manus 一样的，爆到不行，而且还是开源的版本。</p>
<p data-tool="mdnice编辑器">由于最近自己也在做 Agent，于是也看了 OpenClaw 的代码来了解其记忆系统的实现。有一些觉得可以借鉴学习的地方。</p>
<p data-tool="mdnice编辑器">OpenClaw 的记忆系统其实比较简单：它把「记忆」拆成了<strong>文件</strong>、<strong>索引</strong>、<strong>召回注入</strong>。</p>
<h1 data-tool="mdnice编辑器"><span class="content">1. 「Agent 记忆系统」的定义</span></h1>
<p data-tool="mdnice编辑器">在 Agent 工程里，记忆是一套能力组合：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>持久化</strong>：跨会话保存事实、偏好、决策、未完成事项。</section>
</li>
<li>
<section><strong>可检索</strong>：能在需要时把相关片段拉出来，且可控预算。</section>
</li>
<li>
<section><strong>可注入</strong>：把召回结果以确定的结构进入模型上下文，不靠「它自己想起来」。</section>
</li>
<li>
<section><strong>可审计</strong>：出了错能定位「写入发生在什么时候」「召回命中了什么」「注入了哪些行」。</section>
</li>
<li>
<section><strong>可治理</strong>：能处理泄露风险、过期信息、重复信息、冲突信息。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">OpenClaw 的实现路径非常「工程」：<strong>Markdown 作为事实源</strong>，<strong>SQLite 作为检索索引</strong>，<strong>toolResult 作为注入通道</strong>。</p>
<h1 data-tool="mdnice编辑器"><span class="content">2. OpenClaw 的三层记忆</span></h1>
<p data-tool="mdnice编辑器">OpenClaw 的记忆从存储逻辑上来看可以分为三层：</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.1 会话记忆</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>介质</strong>：内存为主，但 OpenClaw 会把 session 打印成类似日志的文件，放到 <code>sessions</code> 目录。</section>
</li>
<li>
<section><strong>内容</strong>：用户消息、OpenClaw 的思考过程、工具调用、skill 调用、最终回复。</section>
</li>
<li>
<section><strong>边界</strong>：会话结束后「可用性」就不可靠了。你能在文件里回放，但模型下一次对话并不会天然带着它。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">会话记忆更像「trace」。我们不能指望它解决跨会话连续性，只用它做排障、复盘、抽取素材（写入短期/长期）。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.2 短期记忆</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>介质</strong>：磁盘，<code>memory/YYYY-MM-DD.md</code> 为主（参考内容给了例子 <code>2026-03-10.md</code>）。</section>
</li>
<li>
<section><strong>内容</strong>：当天重要事件、过程笔记、TODO。关键点是「重要性」由人设与调教决定。</section>
</li>
<li>
<section><strong>边界</strong>：短期记忆是<strong>追加式日志</strong>，质量会漂移。写得越多，噪声越大；但写得太少，又召回不到。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">短期记忆适合承接「会话压缩之前的落盘」和「跨几天的上下文连续性」。它不是最终事实源，别把它当永久协议文档。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.3 长期记忆</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>介质</strong>：工作区根目录 <code>MEMORY.md</code>（参考内容明确）。</section>
</li>
<li>
<section><strong>内容</strong>：核心认知与关系、偏好风格、长期目标、进行中任务、关键事件/教训/决策。</section>
</li>
<li>
<section><strong>边界</strong>：参考内容强调「只在主会话加载」，群聊等任务不加载，避免泄露。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><code>MEMORY.md</code> ==「可执行的组织记忆」。它的价值不在于「写得多」，在于「冲突少、可被召回、能约束后续行为」。这层要治理，要像维护配置一样维护。</p>
<h1 data-tool="mdnice编辑器"><span class="content">3. OpenClaw 的文件布局</span></h1>
<h2 data-tool="mdnice编辑器"><span class="content">3.1 「会话快照」文件</span></h2>
<p data-tool="mdnice编辑器">快照文件主要是解决 <code>/new</code>、<code>/reset</code> 指令的断片</p>
<p data-tool="mdnice编辑器">session-memory 的 Hook 会在你执行 <code>/new</code> 或 <code>/reset</code> 前，把上一会话最近 N 条对话（默认 15 条）抽出来，写成一个 Markdown 文件放到 <code>workspace/memory/</code> 下。</p>
<ul data-tool="mdnice编辑器">
<li>
<section>命名：<code>YYYY-MM-DD-&lt;slug&gt;.md</code>，slug 通常由 LLM 根据主题生成；LLM 不可用就回退成 <code>HHMM</code>。</section>
</li>
<li>
<section>关键点：这种文件也会被检索索引到。原因是 <code>listMemoryFiles()</code> 会递归扫描 <code>workspace/memory/</code> 下所有 <code>.md</code>，并不要求必须是 <code>YYYY-MM-DD.md</code>（ <code>src/memory/internal.ts:115-145</code> ）。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这个机制对「人类工作流」很友好。很多团队的真实使用是：今天临时开了个话题，明天又忘了开在另一个会话里。会话快照能把碎片变成可检索素材，后面再沉淀进 <code>MEMORY.md</code>。</p>
<h2 data-tool="mdnice编辑器"><span class="content">3.2 <code>memory/main.sqlite</code>：索引库</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><code>memory/main.sqlite</code> 基本可以确定是「记忆搜索（memory_search）」用的 SQLite 索引库。</section>
</li>
<li>
<section>索引对象：<code>MEMORY.md</code>、<code>memory/**/*.md</code>，以及你配置的 <code>extraPaths</code>，可选 session transcripts。</section>
</li>
<li>
<section>检索方式：FTS/BM25 关键词检索 +（可选）向量相似度检索（sqlite-vec）。</section>
</li>
<li>
<section>它存的典型结构：<code>files</code>、<code>chunks</code>、<code>embedding_cache</code>，以及可选的 FTS 表、向量虚表。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><strong>事实源是文本文件</strong>，<strong>索引是可重建的派生物</strong>。索引坏了你删掉重建就行；事实源坏了才是真的坏。</p>
<h1 data-tool="mdnice编辑器"><span class="content">4. 怎么建索引：</span></h1>
<p data-tool="mdnice编辑器">建索引我们关心的三件事：增量、去重、成本</p>
<p data-tool="mdnice编辑器">OpenClaw 的索引构建不是「每次全量重算」，也不是「精细 diff」：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>更新粒度</strong>：按文件做增量。文件没变直接跳过。</section>
</li>
<li>
<section><strong>分块粒度</strong>：文件变了就重建该文件 chunks。</section>
</li>
<li>
<section><strong>向量成本</strong>：chunk embedding 通过缓存复用，避免重复调用 embedding provider。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content">4.1 扫描哪些文件会进索引</span></h2>
<p data-tool="mdnice编辑器">OpenClaw 会递归扫描 <code>workspace/memory/</code> 下所有 <code>.md</code>，并包含 <code>MEMORY.md</code>/<code>memory.md</code>。对应定位是 <code>src/memory/internal.ts:115-145</code>。</p>
<h2 data-tool="mdnice编辑器"><span class="content">4.2 文件级 hash：没变就跳过</span></h2>
<p data-tool="mdnice编辑器">文件 hash 的策略：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>Markdown：<code>hash = sha256(content)</code>（<code>internal.ts:L245-L263</code>）</section>
</li>
<li>
<section>多模态：buffer 也会 hash，最后把 <code>{path, contentText, mimeType, dataHash}</code> 做 JSON 再 sha256（<code>internal.ts:L204-L243</code>）</section>
</li>
<li>
<section>增量判定：对比 <code>files</code> 表里的 hash，一致就跳过（参考内容列了 memory 文件与 session 文件两条路径）。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">hash 是增量的核心。它的意义不止省时间，还省钱：embedding provider 往往是计费点。这种主要是对于使用第三方 embedding 的。</p>
<h2 data-tool="mdnice编辑器"><span class="content">4.3 分块策略：<code>tokens*4</code> 的字符近似</span></h2>
<p data-tool="mdnice编辑器"><code>chunkMarkdown()</code> 的策略：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>maxChars = tokens * 4</code>，<code>overlapChars = overlap * 4</code></section>
</li>
<li>
<section>以「行」为主，超长行会被切段</section>
</li>
<li>
<section>每块生成 <code>hash=sha256(text)</code>，并有 <code>embeddingInput</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器"><code>tokens*4</code> 这种近似在工程里挺常见，优点是简单、稳定、跨模型大差不差。缺点也明显：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>语言差异会影响 token/char 比例；中英文混排时 chunk 尺寸会漂。</section>
</li>
<li>
<section>以行切块对 Markdown 友好，但对「一行很长的 JSON 或日志」不友好。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">可以盯两个指标来调 chunking：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>平均召回 snippet 的「可读性」和「自洽性」；</section>
</li>
<li>
<section>SQLite 体积与索引更新耗时。chunk 太小召回碎，太大注入贵。</section>
</li>
</ol>
<h2 data-tool="mdnice编辑器"><span class="content">4.4 chunk 的唯一标识与 upsert</span></h2>
<p data-tool="mdnice编辑器">去重靠 id，chunk 写入策略如下：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>chunk <code>id</code>：<code>sha256("${source}:${path}:${startLine}:${endLine}:${chunk.hash}:${provider.model}")</code></section>
</li>
<li>
<section>同一 id：<code>ON CONFLICT(id) DO UPDATE</code> 覆盖更新</section>
</li>
<li>
<section>文件要重建时会先清旧再写新（参考内容总结了「清旧再写新」语义）</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这里有个很实际的 trade-off：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>它不做「chunk diff」，所以文件变了就重建该文件 chunks，逻辑简单，坏处是 IO 多。</section>
</li>
<li>
<section>但 embedding 通过缓存复用，把最贵的部分压下去了。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">另外，<code>id</code> 里带了 <code>provider.model</code>，这会带来一个工程后果：<strong>embedding 模型换了，chunk id 会变</strong>，索引层面等价于全量重建。 <code>provider/model/providerKey</code> 变化会触发 full reindex。</p>
<h2 data-tool="mdnice编辑器"><span class="content">4.5 embedding_cache</span></h2>
<p data-tool="mdnice编辑器">embedding 缓存的主键设计：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>主键：<code>(provider, model, provider_key, hash)</code></section>
</li>
<li>
<section><code>provider_key</code> 会把 endpoint/headers 等纳入指纹，避免跨配置污染缓存（而且会剔除授权头的细节在参考内容里提到）。</section>
</li>
<li>
<section>批量加载命中就跳过 embed；miss 才请求，成功回写缓存（对应 <code>manager-embedding-ops.ts</code> 的行段）。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这是「上线能用」的关键。否则就会遇到一个很尴尬的情况：<br />
索引更新频繁触发 embedding 重算 → 延迟抖动 → API 费暴涨 → 还可能被 provider 限流。 system prompt 的 skills 段落甚至提醒「假设有 rate limits，避免 tight loop」，这就有点被打过之后写进规范的味道。</p>
<h1 data-tool="mdnice编辑器"><span class="content">5. 更新怎么触发</span></h1>
<p data-tool="mdnice编辑器">watch、interval、onSearch、session-delta</p>
<p data-tool="mdnice编辑器">索引更新如果做得「过勤」，会把 CPU 和 IO 吃满；做得「过懒」，召回就是过期的。OpenClaw 在触发上给了多条路径：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>watch</strong>：chokidar 监听 <code>MEMORY.md</code>、<code>memory.md</code>、<code>memory/**/*.md</code>（以及 extraPaths、多模态扩展），变更标记 dirty，debounce 后 <code>sync(reason="watch")</code>。</section>
</li>
<li>
<section><strong>interval</strong>：<code>sync.intervalMinutes&gt;0</code> 就 <code>setInterval</code> 定时跑。</section>
</li>
<li>
<section><strong>onSessionStart</strong>：search 前 <code>warmSession(sessionKey)</code>，若开了 <code>sync.onSessionStart</code>，每个 sessionKey 首次触发后台 sync。</section>
</li>
<li>
<section><strong>onSearch</strong>：search 时如果 dirty 且开了 <code>sync.onSearch</code>，后台触发 sync。</section>
</li>
<li>
<section><strong>session-delta</strong>：监听 transcript 更新，累计新增 bytes/lines，达到阈值后把相关 session 文件标脏，再 sync。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">还有两点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>单飞锁</strong>：同一时刻只跑一个 sync，复用同一个 Promise（参考内容定位 <code>manager.ts:452-467</code>）。</section>
</li>
<li>
<section><strong>全量重建的原子 swap</strong>：写到 <code>.tmp-UUID</code>，完成后 swap（含 <code>wal/-shm</code>），避免半成品索引（参考内容定位 <code>manager-sync-ops.ts:1050-1158</code>）。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content">6. 召回是怎么发生的</span></h1>
<p data-tool="mdnice编辑器">主要看 <code>buildMemorySection()</code> 的代码，因为它把策略写死了：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">提示词：
<span class="hljs-keyword">function</span> buildSkillsSection(params: { skillsPrompt?: string; readToolName: string }) {
  const trimmed = params.skillsPrompt?.trim();
  <span class="hljs-keyword">if</span> (!trimmed) {
    <span class="hljs-built_in">return</span> [];
  }
  <span class="hljs-built_in">return</span> [
    <span class="hljs-string">"## Skills (mandatory)"</span>,
    <span class="hljs-string">"Before replying: scan &lt;available_skills&gt; &lt;description&gt; entries."</span>,
    `- If exactly one skill clearly applies: <span class="hljs-built_in">read</span> its SKILL.md at &lt;location&gt; with \`<span class="hljs-variable">${params.readToolName}</span>\`, <span class="hljs-keyword">then</span> follow it.`,
    <span class="hljs-string">"- If multiple could apply: choose the most specific one, then read/follow it."</span>,
    <span class="hljs-string">"- If none clearly apply: do not read any SKILL.md."</span>,
    <span class="hljs-string">"Constraints: never read more than one skill up front; only read after selecting."</span>,
    <span class="hljs-string">"- When a skill drives external API writes, assume rate limits: prefer fewer larger writes, avoid tight one-item loops, serialize bursts when possible, and respect 429/Retry-After."</span>,
    trimmed,
    <span class="hljs-string">""</span>,
  ];
}

<span class="hljs-keyword">function</span> buildMemorySection(params: {
  isMinimal: boolean;
  availableTools: Set&lt;string&gt;;
  citationsMode?: MemoryCitationsMode;
}) {
  <span class="hljs-keyword">if</span> (params.isMinimal) {
    <span class="hljs-built_in">return</span> [];
  }
  <span class="hljs-keyword">if</span> (!params.availableTools.has(<span class="hljs-string">"memory_search"</span>) &amp;&amp; !params.availableTools.has(<span class="hljs-string">"memory_get"</span>)) {
    <span class="hljs-built_in">return</span> [];
  }
  const lines = [
    <span class="hljs-string">"## Memory Recall"</span>,
    <span class="hljs-string">"Before answering anything about prior work, decisions, dates, people, preferences, or todos: run memory_search on MEMORY.md + memory/*.md; then use memory_get to pull only the needed lines. If low confidence after search, say you checked."</span>,
  ];
  <span class="hljs-keyword">if</span> (params.citationsMode === <span class="hljs-string">"off"</span>) {
    lines.push(
      <span class="hljs-string">"Citations are disabled: do not mention file paths or line numbers in replies unless the user explicitly asks."</span>,
    );
  } <span class="hljs-keyword">else</span> {
    lines.push(
      <span class="hljs-string">"Citations: include Source: &lt;path#line&gt; when it helps the user verify memory snippets."</span>,
    );
  }
  lines.push(<span class="hljs-string">""</span>);
  <span class="hljs-built_in">return</span> lines;
}

工具描述：
 label: <span class="hljs-string">"Memory Search"</span>,
    name: <span class="hljs-string">"memory_search"</span>,
    description:
      <span class="hljs-string">"Mandatory recall step: semantically search MEMORY.md + memory/*.md (and optional session transcripts) before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with path + lines. If response has disabled=true, memory retrieval is unavailable and should be surfaced to the user."</span>,
    parameters: MemorySearchSchema,
</code></pre>
<p data-tool="mdnice编辑器">有三点：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>触发条件写得具体</strong>：prior work / decisions / dates / people / preferences / todos。模型不需要猜「算不算记忆相关」。</section>
</li>
<li>
<section><strong>两阶段召回</strong>：先 <code>memory_search</code> 找片段，再 <code>memory_get</code> 精读少量行，控制注入体积。</section>
</li>
<li>
<section><strong>引用策略可控</strong>：<code>citationsMode</code> 可以关掉，避免模型动不动把路径行号甩出来（对产品形态很重要）。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">两阶段召回比「一次性把相关文件 wholefile 塞进去」靠谱太多。</p>
<h1 data-tool="mdnice编辑器"><span class="content">7. 召回结果怎么进上下文</span></h1>
<p data-tool="mdnice编辑器">toolResult 消息是关键通道</p>
<p data-tool="mdnice编辑器">很多人以为「记忆」是把内容写进 system prompt。OpenClaw 不是这么干的。</p>
<p data-tool="mdnice编辑器">召回结果会以工具执行结果的形式进入会话消息列表，后续模型调用自然「看得到」。</p>
<ul data-tool="mdnice编辑器">
<li>
<section>工具返回会被包装成 JSON 文本块（参考内容定位 <code>jsonResult()</code> 在 <code>src/agents/tools/common.ts:230-239</code>）。</section>
</li>
<li>
<section>tool 返回会被标准化成 <code>content[] + details</code>（参考内容定位 <code>src/agents/pi-tool-definition-adapter.ts</code> 的 normalize）。</section>
</li>
<li>
<section>这些 toolResult 会被追加到 session messages，下一次模型调用会携带。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这条通道对排障非常友好：我们可以在 transcript 里看到「这次回答之前它到底召回了什么」。而且 toolResult 天然可控预算、可控格式，比让模型把记忆揉进自由文本稳得多。</p>
<h1 data-tool="mdnice编辑器"><span class="content">8. 写入时机</span></h1>
<h2 data-tool="mdnice编辑器"><span class="content">8.1 会话快照写入</span></h2>
<p data-tool="mdnice编辑器">人为触发的「切会话」</p>
<p data-tool="mdnice编辑器">当执行 <code>/new</code>、<code>/reset</code> 时，上一段会话尾部会被抽取成 <code>YYYY-MM-DD-&lt;slug&gt;.md</code>。这是「防断片」写入，价值是保住最近上下文。</p>
<p data-tool="mdnice编辑器">坑：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>抽取的 N 条对话里可能包含敏感信息。它会落在 <code>memory/</code>，并进入索引。</section>
</li>
<li>
<section>如果你把 workspace 目录同步到团队共享盘或提交到 repo，泄露面会扩大。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">我们的做法：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>明确区分「个人 workspace」和「团队 workspace」。个人的 <code>memory/</code> 默认不进 repo。</section>
</li>
<li>
<section>开启 citations 时，产品侧要想清楚是否允许暴露路径与行号。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content">8.2 短期记忆写入</span></h2>
<p data-tool="mdnice编辑器">「需要我们调教，告诉她哪些重要」。</p>
<p data-tool="mdnice编辑器">短期记忆要走「稀疏高密度」路线：条目少，但每条都能在未来的某个问题上直接复用。写入策略要围绕「将来会搜什么」来定，不要围绕「当下发生了什么」来记流水账。</p>
<h2 data-tool="mdnice编辑器"><span class="content">8.3 长期记忆更新</span></h2>
<p data-tool="mdnice编辑器">心跳 / AGENTS.md / cron</p>
<p data-tool="mdnice编辑器">三种更新机制：心跳、核心流程、cron。</p>
<p data-tool="mdnice编辑器">读最近几天短期记忆 → 选值得长期记住的 → 提炼写入 <code>MEMORY.md</code>。</p>
<p data-tool="mdnice编辑器">观点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>cron 最稳</strong>，可观测、可控、可回滚。</section>
</li>
<li>
<section>心跳更新很容易在负载高时抖动，或者在你最不想更新的时候更新。</section>
</li>
<li>
<section>把它塞进核心流程（AGENTS.md）要谨慎，一旦每次任务都触发提炼，会把延迟拉上去。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">一般做法：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>工作日每天固定一次 consolidation（cron）。</section>
</li>
<li>
<section>遇到重大决策或事故复盘，当天手动提炼写入 <code>MEMORY.md</code>，不等自动化。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content">9. 怎么「调教」</span></h1>
<p data-tool="mdnice编辑器">把记忆当成协议，不当成日记</p>
<p data-tool="mdnice编辑器">「告诉她哪些重要」。把「重要」拆成几类，每类有明确写入规则，避免模型自由发挥。</p>
<p data-tool="mdnice编辑器">一般的规则：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>稳定偏好</strong>：例如输出格式偏好、技术栈偏好、代码风格偏好。</section>
</li>
<li>
<section><strong>组织事实</strong>：团队结构、系统边界、核心服务依赖、环境约束。</section>
</li>
<li>
<section><strong>关键决策</strong>：ADR 级别的决策，包含时间点与理由。</section>
</li>
<li>
<section><strong>长期目标与在途事项</strong>：能跨周追踪的，不写「今天要做的」。</section>
</li>
<li>
<section><strong>事故教训</strong>：明确到「哪个坑踩过」「如何避免」。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">短期记忆（<code>memory/YYYY-MM-DD.md</code>）会允许更多过程性信息，但要满足一个条件：<strong>未来能被搜索问题命中</strong>。比如你写「今天讨论了 A」，基本没用；你写「决定 A 的原因是 B，后续若出现 C 用 D 回滚」，会有用一些。</p>
<p data-tool="mdnice编辑器">如果希望 <code>memory_search</code> 在关键时刻召回到正确内容，就得用「未来的查询语句」来写记忆。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/03/openclaw-memory/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>多 Agent 架构上下文传递的 4 种策略</title>
		<link>https://www.phppan.com/2026/03/four-strategies-for-context-passing-in-a-multi-agent-architecture/</link>
		<comments>https://www.phppan.com/2026/03/four-strategies-for-context-passing-in-a-multi-agent-architecture/#comments</comments>
		<pubDate>Sat, 07 Mar 2026 02:18:10 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[multi-agent architecture]]></category>
		<category><![CDATA[上下文]]></category>
		<category><![CDATA[上下文管理]]></category>
		<category><![CDATA[多 Agent]]></category>
		<category><![CDATA[架构]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2474</guid>
		<description><![CDATA[在多 Agent 系统里，上下文怎么传，决定了系统的稳定性上限、成本下限、以及排障时的血压。 最开始，很多团队 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">在多 Agent 系统里，上下文怎么传，决定了系统的稳定性上限、成本下限、以及排障时的血压。</p>
<p data-tool="mdnice编辑器">最开始，很多团队把它当成「把聊天记录拼一拼」的问题。上线后就会这样的问题：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>同样的输入，输出飘得离谱。这里的问题是上下文污染和信息密度不稳定。</section>
</li>
<li>
<section>Token 成本控不住。明明一个任务只需要 10 句关键信息，每次都喂 200 句。</section>
</li>
<li>
<section>自动化评测很难做。回归集跑出来波动大到没法设阈值，最后只能靠人工验收。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">从工程的角度简单定义一下「上下文传递」：<strong>在一条多步协作链路里，把下游完成任务所需的信息，以可控成本、可追踪方式送到它面前</strong>。这里面有两个关键词：<strong>可控</strong>、<strong>可追踪</strong>。</p>
<p data-tool="mdnice编辑器">下面聊下我理解的 4 种主流策略拆解：原理、适用场景、落地细节、坑、性能和效果的取舍。</p>
<h1 data-tool="mdnice编辑器"><span class="content">先定义一下上下文</span></h1>
<p data-tool="mdnice编辑器">很多争论的根源是大家说的「上下文」不是一个东西。可以在团队里先把上下文分层，后面所有策略都能对齐。这里我们把「上下文」拆成 4 类：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>任务上下文</strong>：当前这一步要干什么，验收标准是什么。</section>
</li>
<li>
<section><strong>状态上下文</strong>：链路运行到哪了，已经产生了哪些中间产物。</section>
</li>
<li>
<section><strong>记忆上下文</strong>：用户偏好、历史约束、长期设定，和当前任务不完全同一层级。</section>
</li>
<li>
<section><strong>证据上下文</strong>：引用了哪些原始材料（文档片段、对话原句、文件、数据库记录），用于追溯和评测。</section>
</li>
</ol>
<h1 data-tool="mdnice编辑器"><span class="content">策略 1：共享状态或黑板模式</span></h1>
<p data-tool="mdnice编辑器">这套是 LangGraph、CrewAI 这类框架最常见的默认选项。工程上它像一个「全局状态对象」，也像一个「写满便签的白板」。</p>
<h2 data-tool="mdnice编辑器"><span class="content">机制</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section>所有 Agent 对同一个 State 读写。</section>
</li>
<li>
<section>Agent A 产出结果写入 State 的某个字段。</section>
</li>
<li>
<section>Agent B 读取字段，继续处理，再写回。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果我们不想把大对象放内存，也可以用文件系统或对象存储做「外置状态」，State 里只放路径和元信息。像 Manus 文章中说的「将文件系统作为存储，直接共享文件系统的路径，渐进式披露」，就是这么个逻辑：<strong>把大内容放外面，把指针放 State 里</strong>。</p>
<h2 data-tool="mdnice编辑器"><span class="content">什么时候用</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>复杂图流转</strong>：有回路、有分支、有重试、有人工介入的链路。</section>
</li>
<li>
<section><strong>任务跨度长</strong>：要做断点续跑、要保留每一步证据，方便回放和审计。</section>
</li>
<li>
<section><strong>需要可视化排障</strong>：一个状态树摆在那，定位问题快很多。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这类场景里，黑板是省心的。我们不用操心「A 怎么把消息发给 B」，大家都围着同一块板子写字。</p>
<h2 data-tool="mdnice编辑器"><span class="content">落地时的坑</span></h2>
<h3 data-tool="mdnice编辑器"><span class="content">坑 1：状态对象会长成「垃圾堆」</span></h3>
<p data-tool="mdnice编辑器">共享状态天然会诱导人偷懒：什么都往里塞。结果一周后 State 变成一个混杂体：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>当前任务指令</section>
</li>
<li>
<section>全量聊天记录</section>
</li>
<li>
<section>RAG 检索结果</section>
</li>
<li>
<section>中间产物全文</section>
</li>
<li>
<section>模型输出草稿</section>
</li>
</ul>
<p data-tool="mdnice编辑器">后果是下游 Agent 读到的信息密度越来越低，注意力越来越散。我们会直观感受到「同一个 Agent，越跑越不稳定」。</p>
<p data-tool="mdnice编辑器">共享状态可以用，前提是要给 State 立规矩。规矩不是写在 Confluence 上那种，是写进代码和评测里那种。</p>
<p data-tool="mdnice编辑器">常用的做法是给 State 分区，至少三块：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>「control」：流程控制字段（步数、路由、重试次数）</section>
</li>
<li>
<section>「artifacts」：产物指针（文件路径、对象存储 key、哈希）</section>
</li>
<li>
<section>「capsules」：给 LLM 的上下文胶囊（后面会讲）</section>
</li>
</ul>
<p data-tool="mdnice编辑器">State 里尽量少放大段文本，放「引用」和「摘要」。</p>
<h3 data-tool="mdnice编辑器"><span class="content">坑 2：并发写</span></h3>
<p data-tool="mdnice编辑器">多 Agent 并行时很容易出现：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>两个 Agent 同时更新同一字段，后写覆盖前写。</section>
</li>
<li>
<section>一个 Agent 基于旧 State 做决策，写回时把别人新写入的字段抹掉。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">解决思路按分布式系统处理：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>字段级别乐观锁（版本号 / compare-and-swap）</section>
</li>
<li>
<section>append-only 日志字段，避免覆盖（把「更新」变成「追加事件」），Gemini Cli 就是这个逻辑</section>
</li>
<li>
<section>把「写入」限定为少数字段，其他字段只读</section>
</li>
</ul>
<h3 data-tool="mdnice编辑器"><span class="content">坑 3：评测没法做「输入对齐」</span></h3>
<p data-tool="mdnice编辑器">共享状态经常带来一个隐性问题：每次运行 State 的非关键字段变化很大，导致没法保证下游 Agent 的输入一致。回归测试时同一条用例，今天多了两段日志，明天多了一个草稿，指标就会飘。</p>
<p data-tool="mdnice编辑器">建议：<strong>评测时固定「胶囊输入」</strong>，State 可以变，但进入 LLM 的那段上下文要可快照、可对比、可复现。</p>
<h2 data-tool="mdnice编辑器"><span class="content">成本 vs 效果的取舍</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>效果</strong>：流程可扩展，复杂图最好用。</section>
</li>
<li>
<section><strong>成本</strong>：需要治理 State 的 schema、并发、版本、清理策略。</section>
</li>
<li>
<section><strong>性能</strong>：状态越大，序列化 / 反序列化越痛；如果每一步都把 State 发给 LLM，更是直接烧钱。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">共享状态是一把大锤。能砸钉子，也能把玻璃砸碎。关键看有没有「状态卫生」这件事。</p>
<h1 data-tool="mdnice编辑器"><span class="content">策略 2：消息传递与直接调用</span></h1>
<p data-tool="mdnice编辑器">这套有点像微服务架构：上游把消息打包发给下游，下游处理完再回一个结果。</p>
<h2 data-tool="mdnice编辑器"><span class="content">机制</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section>Agent A 产出一个「消息」发给 Agent B。</section>
</li>
<li>
<section>消息可以走 HTTP、RPC、队列，也可以是框架内的函数调用。</section>
</li>
<li>
<section>每条消息都应该有明确的结构和版本。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content">什么时候用</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>流水线式任务</strong>：每一步都很明确，上游输出就是下游输入。</section>
</li>
<li>
<section><strong>要强可观测性</strong>：链路追踪、审计、回放都好做。</section>
</li>
<li>
<section><strong>团队边界清晰</strong>：不同组负责不同 Agent，接口契约能拉齐。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这类场景用消息传递灵活性更强一些，但是如果规模不大，直接单体应用来搞，函数间调用吧。</p>
<h2 data-tool="mdnice编辑器"><span class="content">落地时的坑</span></h2>
<h3 data-tool="mdnice编辑器"><span class="content">坑 1：消息里塞进「全量上下文」</span></h3>
<p data-tool="mdnice编辑器">很多团队为了省事，会把上游拿到的所有东西都塞进消息里。看起来省了裁剪逻辑，实际上把问题推给了下游：下游 LLM 要在一堆噪声里找信号。</p>
<p data-tool="mdnice编辑器">如果走消息传递，<strong>消息必须有「字段语义」</strong>。比如：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>「task」字段是当前要做的事</section>
</li>
<li>
<section>「constraints」字段是硬性限制</section>
</li>
<li>
<section>「evidence」字段是引用（原文片段或路径）</section>
</li>
<li>
<section>「history」字段如果存在，必须明确是「最近 N 轮且强相关」</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这里的关键词是「必须明确」。否则会出现消息看着结构化，内容依然是散装的。</p>
<h3 data-tool="mdnice编辑器"><span class="content">坑 2：接口版本失控</span></h3>
<p data-tool="mdnice编辑器">多 Agent 系统迭代快，接口字段会频繁变动。如果经历过一次「某个 Agent 升级后，下游全挂」就会理解版本的重要性。</p>
<p data-tool="mdnice编辑器">建议至少做到：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>每条消息带「schema_version」</section>
</li>
<li>
<section>下游支持 1~2 个旧版本的兼容解析</section>
</li>
<li>
<section>重要字段改动要有灰度期，别全量切</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Agent 世界里「prompt 和策略」变化太快，不做版本控制就是赌博。</p>
<h3 data-tool="mdnice编辑器"><span class="content">坑 3：把「LLM 输出」当成接口返回</span></h3>
<p data-tool="mdnice编辑器">LLM 输出天然存在幻觉。如果我们直接把自由文本当成 RPC 返回，然后让下游再去解析，事故率会非常高。</p>
<p data-tool="mdnice编辑器">有一个简单的方法：<strong>固定栏位的轻量输出格式</strong>，别一上来就上复杂 schema，也别放任自由发挥。它在工程上有一个很大的价值：解析稳定，回归测试有抓手。</p>
<p data-tool="mdnice编辑器">类似于这样：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">PROMPT:
...

NEGATIVE:
...

PARAMS:
- aspect: 16:9
- notes: ...
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content">成本 vs 效果的取舍</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>效果</strong>：可追踪性强，调试体验好。</section>
</li>
<li>
<section><strong>成本</strong>：要做接口契约、版本管理、兼容逻辑。</section>
</li>
<li>
<section><strong>性能</strong>：网络开销和序列化开销可控；真正的成本往往来自传了多少无用字段。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果业务链路更像「微服务编排」，消息传递会比共享状态更干净。</p>
<h1 data-tool="mdnice编辑器"><span class="content">策略 3：上下文压缩与自然语言传递</span></h1>
<p data-tool="mdnice编辑器">核心思路：<strong>下游 Agent 不该负责考古</strong>。</p>
<p data-tool="mdnice编辑器">把「长历史」变成「短胶囊」，把「噪声」变成「任务卡」，再交给执行 Agent。</p>
<h2 data-tool="mdnice编辑器"><span class="content">机制</span></h2>
<p data-tool="mdnice编辑器">上游做三件事：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>从历史里抽取和当前任务强相关的信息</section>
</li>
<li>
<section>把冲突的约束做决策或提出澄清问题</section>
</li>
<li>
<section>输出一个高密度、可控的自然语言指令</section>
</li>
</ol>
<h2 data-tool="mdnice编辑器"><span class="content">「上下文胶囊（context capsule）」</span></h2>
<p data-tool="mdnice编辑器">把给下游 Agent 的输入，固定成一个胶囊，结构大概是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>必须给</strong>：任务卡（Planner 压缩/改写后的自然语言描述）</section>
</li>
<li>
<section><strong>可选给</strong>（按需）：</p>
<ul>
<li>
<section>最近 N 轮「与任务强相关」的对话原句（最多 3–8 句）</section>
</li>
<li>
<section>一段「用户偏好/风格记忆」摘要（1–3 句）</section>
</li>
</ul>
</section>
</li>
<li>
<section><strong>坚决不直接给</strong>：全量聊天记录（除非做的就是风格延续式创作，而且做了脱敏）</section>
</li>
</ul>
<p data-tool="mdnice编辑器">它解决的是「你能不能控制它理解什么」。</p>
<p data-tool="mdnice编辑器">示例：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p><strong>任务卡</strong>：生成一张用于电商 banner 的图。主体是一只穿宇航服的柯基站在月球上，远处能看到地球。风格写实摄影，冷色调，高对比，电影感侧逆光。横向 16:9。不要任何文字、logo、血腥或恐怖元素。用户偏好极简、冷色、不要文字。若信息缺失请提 1–3 个澄清问题，否则直接输出可用于生图的 prompt 与 negative prompt。</p></blockquote>
<p data-tool="mdnice编辑器">这段话有几个关键点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>主体、场景、风格、画幅、禁忌、用户偏好都在</section>
</li>
<li>
<section>有「缺失信息时的行为规则」</section>
</li>
<li>
<section>不需要表单，依然可评测、可回归</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content">工具描述要写成「契约」</span></h2>
<p data-tool="mdnice编辑器">工具描述很重要。</p>
<p data-tool="mdnice编辑器">我更喜欢把工具说明写成「契约」，至少包含：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>工具支持的参数（prompt / negative / size / seed / style 等）</section>
</li>
<li>
<section>哪些信息必须出现（画幅、用途、限制）</section>
</li>
<li>
<section>输出必须遵守的格式（哪怕只有 PROMPT/NEGATIVE/PARAMS）</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content">可能翻车的地方</span></h2>
<p data-tool="mdnice编辑器">以一个增强提示词的 Agent 为例</p>
<h3 data-tool="mdnice编辑器"><span class="content">翻车 1：指代延续没被写进任务卡</span></h3>
<p data-tool="mdnice编辑器">用户说「按刚才那张风格」「把她换成红裙子」，Planner 如果没有把「刚才那张」总结成可引用的描述，下游增强 Agent 根本无从得知。</p>
<p data-tool="mdnice编辑器">补救方式使用「证据上下文」：把关键原句作为 1~3 条引用附在胶囊里。</p>
<h3 data-tool="mdnice编辑器"><span class="content">翻车 2：约束冲突没被处理</span></h3>
<p data-tool="mdnice编辑器">用户一会儿要极简纯色，一会儿又要复杂赛博城市场景。Agent 去解决冲突会很糟糕，因为它的职责是「增强表达」，不是「做产品决策」。</p>
<p data-tool="mdnice编辑器">冲突要在 Planner 层解决：要么做裁决（按最新指令为准、按用户偏好为准），要么问澄清问题。别把锅甩给执行 Agent。</p>
<h3 data-tool="mdnice编辑器"><span class="content">翻车 3：压缩带来的信息损失</span></h3>
<p data-tool="mdnice编辑器"><strong>上下文压缩是有损的。</strong> 压缩做得越狠，成本越低，翻车概率越高；压缩做得越松，成本越高，稳定性也未必更好，因为噪声会上来。</p>
<p data-tool="mdnice编辑器">建议做一个「胶囊长度预算」，按任务类型分档：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>低风险任务（格式化、简单问答）：胶囊可以短到 200~400 tokens</section>
</li>
<li>
<section>中风险任务（生成、改写、推理）：600~1200 tokens</section>
</li>
<li>
<section>高风险任务（工具调用、多约束、多回合创作）：1200~2000 tokens，再往上就该考虑别的策略了</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这样，可以让成本和稳定性可控一些。</p>
<h2 data-tool="mdnice编辑器"><span class="content">成本 vs 效果的取舍</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>效果</strong>：稳定性提升非常明显，Token 成本能压下来，评测也更容易做。</section>
</li>
<li>
<section><strong>成本</strong>：要多一个 Planner/Summarizer 步骤，链路延迟会上升；压缩质量要靠回归集打磨。</section>
</li>
<li>
<section><strong>工程判断</strong>：这套是「多 Agent 真正开始像工程系统」的起点。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content">策略 4：路由分发与层级管理</span></h1>
<p data-tool="mdnice编辑器">如果说策略 3 解决的是「给下游喂什么」，策略 4 解决的是「谁有资格看到什么」。</p>
<p data-tool="mdnice编辑器">我喜欢用「最小信息原则」去设计多 Agent：每个 Agent 只拿自己需要的那一部分上下文，别让它看到不该看的东西。</p>
<h2 data-tool="mdnice编辑器"><span class="content">机制</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section>一个 Supervisor（主管）拿到全量上下文。</section>
</li>
<li>
<section>Supervisor 拆任务、选 Agent、裁剪上下文。</section>
</li>
<li>
<section>子 Agent 只看到被裁剪后的输入，产出结果回传 Supervisor。</section>
</li>
<li>
<section>Supervisor 汇总，决定下一步。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">把「路由」和「信息裁剪」集中起来做，能显著减少上下游互相污染。</p>
<p data-tool="mdnice编辑器">这本质就是一个主从的逻辑。</p>
<h2 data-tool="mdnice编辑器"><span class="content">什么时候用</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>权限和合规敏感</strong>：有 PII、有商业机密、有分级数据。</section>
</li>
<li>
<section><strong>子 Agent 职责清晰</strong>：比如「检索」「评审」「生成」「合规检查」。</section>
</li>
<li>
<section><strong>系统要长期维护</strong>：人员流动、策略变动、模型替换都很频繁。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">层级路由把复杂性收敛到 Supervisor 这一点上。依赖它，也更容易把它做好。</p>
<h2 data-tool="mdnice编辑器"><span class="content">落地时的坑</span></h2>
<h3 data-tool="mdnice编辑器"><span class="content">坑 1：Supervisor 变成性能瓶颈</span></h3>
<p data-tool="mdnice编辑器">所有东西都过 Supervisor，它会成为热点：吞吐、延迟、可用性全压在它身上。</p>
<p data-tool="mdnice编辑器">解决办法通常有三种：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>Supervisor 只做「路由与裁剪」，不要在它身上做重推理</section>
</li>
<li>
<section>对路由做缓存（同一类任务走同一条路径）</section>
</li>
<li>
<section>Supervisor 逻辑尽量确定性，LLM 参与度降低</section>
</li>
</ul>
<p data-tool="mdnice编辑器">我见过很多团队把「大脑」写成一个超级 prompt，然后让它既拆任务又生成内容又做审查。这样迟早会蹦。</p>
<h3 data-tool="mdnice编辑器"><span class="content">坑 2：裁剪策略一开始过度依赖「拍脑袋」</span></h3>
<p data-tool="mdnice编辑器">裁剪不是凭感觉。裁剪是一套数据工程问题：哪些字段必须给，哪些字段给了会干扰。</p>
<p data-tool="mdnice编辑器">用「失败用例驱动」去迭代裁剪：每次线上翻车，都回放当时给子 Agent 的胶囊，问一个很残酷的问题：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>该给的没给，是哪一类信息缺失？</section>
</li>
<li>
<section>不该给的给了，是哪一类噪声触发了跑偏？</section>
</li>
</ul>
<p data-tool="mdnice编辑器">把这两类问题沉淀成裁剪规则，会越做越稳。</p>
<h3 data-tool="mdnice编辑器"><span class="content">坑 3：子 Agent 之间产生「隐性耦合」</span></h3>
<p data-tool="mdnice编辑器">很多系统表面上是层级的，实际上子 Agent 会通过共享外部资源互相影响，比如：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>共用同一个向量库检索空间，检索结果被不同策略污染</section>
</li>
<li>
<section>共用同一个临时文件目录，路径命名冲突</section>
</li>
<li>
<section>共用同一个「用户偏好记忆」，写入时缺少版本控制</section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果走 Supervisor 模式，「写入边界」会比较重要：哪些 Agent 允许写记忆，哪些只能读；写入要不要审批；写入是否带证据引用。</p>
<h2 data-tool="mdnice编辑器"><span class="content">成本 vs 效果的取舍</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>效果</strong>：稳定性和安全性非常强，复杂系统更容易控住。</section>
</li>
<li>
<section><strong>成本</strong>：Supervisor 设计难度高，容易成为瓶颈；需要更完善的可观测性和回放能力。</section>
</li>
<li>
<section><strong>工程判断</strong>：当开始被「数据泄露」「上下文污染」「责任边界不清」折磨时，Supervisor 往往是解药。</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content">选择策略时，如何判断</span></h1>
<p data-tool="mdnice编辑器">可以用四个可执行的问题来选方案：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>谁需要看到全量上下文？谁只需要胶囊？</strong><br />
如果答案是「大多数都只需要胶囊」，策略 3 和 4 优先级会上来。</p>
</section>
</li>
<li>
<section><strong>要不要并发？要不要异步？</strong><br />
需要并发、异步，策略 5 的价值会非常直接。</p>
</section>
</li>
<li>
<section><strong>失败主要来自哪里：信息缺失，还是噪声过多？</strong><br />
信息缺失优先补证据引用；噪声过多优先做裁剪和胶囊预算。</p>
</section>
</li>
<li>
<section><strong>是否真的在做回归评测？</strong><br />
没有回归，就别指望系统会「越调越稳」。上下文传递策略的好坏，最终都要落在可复现输入上。</p>
</section>
</li>
</ol>
<h1 data-tool="mdnice编辑器"><span class="content">小结</span></h1>
<p data-tool="mdnice编辑器"><strong>不做结构化，并不等于不做「约束与契约」</strong>。</p>
<p data-tool="mdnice编辑器">我见过的高质量落地项目，往往走的是「看起来很自然，实际上约束很硬」的路线。用户体验上像聊天，工程实现上像协议。</p>
<p data-tool="mdnice编辑器">如果准备做多 Agent 的上下文传递，至少把三件事落下来：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>「上下文胶囊」：任务卡 + 少量强相关原句 + 记忆摘要</section>
</li>
<li>
<section>「工具契约」：写清楚工具能力边界和必填信息</section>
</li>
<li>
<section>「受控输出格式」：固定栏位，解析稳定，评测可做</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这三件事做完，再谈共享状态、Supervisor、消息传递，才有意义。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/03/four-strategies-for-context-passing-in-a-multi-agent-architecture/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SaaS 已死？AI 当立？</title>
		<link>https://www.phppan.com/2026/03/saas-ai/</link>
		<comments>https://www.phppan.com/2026/03/saas-ai/#comments</comments>
		<pubDate>Sat, 07 Mar 2026 02:16:44 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[SaaS]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2472</guid>
		<description><![CDATA[在 AI 编程逛飙的年份，到当前的阶段，能感觉到速度在加快，形式也在不断的迭代，写代码这个事情也变得门槛很低。 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">在 AI 编程逛飙的年份，到当前的阶段，能感觉到速度在加快，形式也在不断的迭代，写代码这个事情也变得门槛很低。</p>
<p data-tool="mdnice编辑器">X 上有人在讲「AI 让软件开发成本接近零，所以 SaaS 价值也接近零」</p>
<p data-tool="mdnice编辑器">美股前段时间也因此大跌了一波，但当前这个价值为零的逻辑还是不成立的。</p>
<p data-tool="mdnice编辑器">这里有一个概念混淆：<strong>构建软件的成本</strong> vs <strong>拥有软件的成本</strong>。AI 主要压低前者，后者还在，甚至在很多公司里变得更贵。</p>
<p data-tool="mdnice编辑器">在各种自媒体、AI 编程培训或者博眼球的报道中，「一个下午做出 Linear 替代」「一个周末写完 Stripe 替代」这种话，我不觉得完全是吹牛。用 Claude Code 这类工具，把界面、CRUD、简单流程、甚至一些边角的自动化都拼出来，确实快。</p>
<p data-tool="mdnice编辑器">但在做出来的那一刻，资产没增加多少，负债突然多了。尤其是碰钱、碰身份、碰合规、碰客户数据的时候。</p>
<p data-tool="mdnice编辑器">以做了一个 Stripe 为例，我把 Vibe Coding 一个周末后，「现在拥有了什么」用更工程的语言翻译一下：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>拥有了 <strong>规则持续变化</strong> 的税务与开票适配：欧盟 VAT、各国电子发票、免税/退税、税率变更、发票作废红冲。</section>
</li>
<li>
<section>拥有了 <strong>审计与认证的对话成本</strong>：PCI DSS、SOC 2、ISO 27001、渗透测试报告、供应商安全评估问卷。企业客户要的往往不是「你写得对不对」，是「谁在对这件事负责」。</section>
</li>
<li>
<section>拥有了 <strong>支付与账务的边界条件</strong>：拒付、部分退款、重复扣款、汇率、四舍五入、账期、对账差异、资金在途、延迟入账。</section>
</li>
<li>
<section>拥有了 <strong>全球化的坑</strong>：货币重估、货币小数位变化、地区性监管、某个国家突然要求强制 3DS、某个渠道突然不支持某种卡组织。</section>
</li>
<li>
<section>拥有了 <strong>数据与权限的事故半径</strong>：某个字段脱敏没做好、某个导出接口忘了做权限校验、某个后台操作没有审计日志。</section>
</li>
<li>
<section>拥有了 <strong>7×24 的值班现实</strong>：系统降级策略、容量、限流、重试风暴、第三方故障兜底、SLA 与赔付条款。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些事大概率不是「可能发生」。按行业经验只是早晚问题。自从拥有系统开始，就等于自己签了那张无限期维护合同。</p>
<p data-tool="mdnice编辑器">回想一个问题，SaaS 是什么？软件即服务，<strong>核心是服务</strong>，根本就没有提代码。</p>
<p data-tool="mdnice编辑器">很多团队在内部系统上很执着，原因很直接：控制感强、改需求快、看起来省钱。上线半年以后，气氛通常变得不太好：需求排队、线上出过几次事故、业务抱怨响应慢、研发觉得自己在打杂。</p>
<p data-tool="mdnice编辑器">这时候再看 SaaS，价值就很清晰了：<strong>SaaS 的核心商品是「运营表面积」的转移</strong>。</p>
<p data-tool="mdnice编辑器">代码只是其中一层，除了代码还有：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>基础设施与部署：多区域、容灾、备份恢复演练、变更管理。</section>
</li>
<li>
<section>安全：漏洞扫描、依赖升级、密钥轮换、权限最小化、WAF、DDoS。</section>
</li>
<li>
<section>合规：隐私条款、数据驻留、审计证据、流程制度落地。</section>
</li>
<li>
<section>可靠性：监控告警、事故复盘、容量规划、灰度发布、回滚策略。</section>
</li>
<li>
<section>支持与客户成功：工单、排障、培训、文档、FAQ、升级沟通。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">当我们把软件「买回来」自己跑，这些都要自己补齐。很多公司低估的就是这一块。这也是为什么很多公司在 AI 赋能后，「运营表面积」的转移速度要慢于「代码」的转移速度。除非这些对公司不重要，如果这些都不重要， 这个系统可能也不重要了。</p>
<p data-tool="mdnice编辑器">从 SaaS 的生命周期来看，AI 提升的是「0 到 1」，然而从成本的角度看，最贵的是「1 到 ∞」</p>
<p data-tool="mdnice编辑器">这件事可以拆成两条曲线：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>vibe coded 工具</strong>：初期成本很低，后期成本增长很快。越多人用、越多数据、越多流程依赖，它的「改动风险」和「维护面」指数上升。</section>
</li>
<li>
<section><strong>成熟 SaaS</strong>：初期采购成本看着不低，后期增长更接近线性。因为供应商把大量共性维护<strong>摊薄</strong>到所有客户身上。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">AI 把 0→1 压得更便宜，直觉上会让人误判「买 SaaS 更不划算」。实际情况常常相反：<strong>AI 让 1→∞ 更贵了</strong>，因为我们会更频繁地改、更大胆地接更多业务进来，系统的负债增长速度被我们自己加速了。</p>
<p data-tool="mdnice编辑器">这个成本不仅仅是 SaaS 软件的。</p>
<p data-tool="mdnice编辑器">AI 让一个工程师单位时间产出更高，但<strong>单位工程小时的机会成本同步上升</strong>。</p>
<p data-tool="mdnice编辑器">至少当前的认知是这样的，可能到终极形态，完成不用人介入的时候，这个机会成本也会消失掉。</p>
<p data-tool="mdnice编辑器">那是另一个话题了。</p>
<p data-tool="mdnice编辑器">所以我对「AI 让 SaaS 价值归零」的判断是反的：<strong>AI 越强，成熟 SaaS 越值钱</strong>，前提是它真的把服务做扎实，真的帮我们把运营表面积吃掉。</p>
<p data-tool="mdnice编辑器"><strong>SaaS 本质上还是一种服务。</strong></p>
<p data-tool="mdnice编辑器">如果 SaaS 只是「界面更好看的 CRUD」，那这样的 SaaS 确实会死掉一批。</p>
<p data-tool="mdnice编辑器">这些 SaaS 太薄了。</p>
<p data-tool="mdnice编辑器">在 AI 时代要想活得更好，SaaS 通常需要如下的一些特征：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>规则密度高，正确性要求高</section>
</li>
<li>
<section>合规与安全负担重</section>
</li>
<li>
<section>生态变化快，需要持续跟进</section>
</li>
<li>
<section>故障代价高，客户不想背锅</section>
</li>
<li>
<section>接口与集成复杂，长期维护吃人</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Stripe、WorkOS、Cloudflare 这类产品的共同点很明显：它们难点不在「写出来」，难点在「长期把它跑对」。正确性要靠无数细碎决策堆出来，运营水位要常年拉满。</p>
<p data-tool="mdnice编辑器">AI 始终会改写 SaaS，整体逻辑会有一些变化。这里的 AI 改写过程，肯定不是「加一个聊天框」「做一个总结」「做一个生成报表」。</p>
<p data-tool="mdnice编辑器">我觉得至少有三个点在当前阶段能快速跟进：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>意图驱动</strong>，SaaS 的用户最终都是想通过 SaaS 完成工作，以前靠 UI 引导的，现在可以让用户意图表达，然后把意图落为可审计的操作序列，不管是用 MCP，还是 SKILLS；</section>
</li>
<li>
<section><strong>更关注结果</strong>，传统 SaaS 交付的是工具，客户负责把工具嵌进流程。AI 让厂商有机会把流程吃进去，直接交付结果，比如「自动完成对账」「自动完成入职」「自动完成工单分流」。关键点在<strong>「责任」</strong></section>
</li>
<li>
<section><strong>定制方式的迭代</strong>，企业客户永远会提定制。以前定制意味着项目制和人力黑洞；AI 让「生成」变得便宜，但不要把生成等同于可维护，可以允许客户用自然语言提出规则，系统把规则编译成可测试、可审计的约束，每次变更都能跑回归校验，出问题能定位到规则版本与变更人。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">我们常听到对于自研和购买 SaaS 的一个判断逻辑是：「核心业务自研，非核心买 SaaS」。</p>
<p data-tool="mdnice编辑器">这句话太粗，没有啥指导意义。</p>
<p data-tool="mdnice编辑器">如下一个判断清单，可以做为决策的一些依据：</p>
<p data-tool="mdnice编辑器"><strong>适合买 SaaS 的场景</strong> ：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>领域合规重：支付、税务、身份、隐私、审计</section>
</li>
<li>
<section>失败代价高：一出错就上新闻、上法务、上客户群</section>
</li>
<li>
<section>生态变化快：标准常变、监管常变、攻击手法常变</section>
</li>
<li>
<section>需要对外背书：企业客户会问「谁负责」「有没有认证」「有没有 SLA」</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这种场景自研的隐性成本巨大。AI 再强，也只是让我们更快地把「维护合同」签在自己身上。</p>
<p data-tool="mdnice编辑器"><strong>适合自研的场景</strong></p>
<ul data-tool="mdnice编辑器">
<li>
<section>强差异化：流程就是你的竞争力，外部产品很难贴合</section>
</li>
<li>
<section>业务规则变化快，且只对内部负责：错了能快速纠正，不会引发合规事故</section>
</li>
<li>
<section>生命周期短：一个季度就会重构或下线的东西</section>
</li>
<li>
<section>数据高度敏感，且已经有成熟的数据治理与安全团队</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这里自研的价值很实在：迭代速度、贴合度、数据控制。</p>
<p data-tool="mdnice编辑器"><strong>AI 时代的「自研陷阱」会更隐蔽</strong></p>
<p data-tool="mdnice编辑器">以前自研失败，多数死在「做不出来」。现在会死在「做出来以后一路堆债」。</p>
<p data-tool="mdnice编辑器">AI 会在早期持续正反馈：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>功能做得快</section>
</li>
<li>
<section>Demo 好看</section>
</li>
<li>
<section>业务觉得爽</section>
</li>
<li>
<section>老板觉得省钱</section>
</li>
</ul>
<p data-tool="mdnice编辑器">债务也在同期累积：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>没有威胁建模</section>
</li>
<li>
<section>没有权限与审计体系</section>
</li>
<li>
<section>没有数据分级与脱敏</section>
</li>
<li>
<section>没有灾备演练</section>
</li>
<li>
<section>没有 SLA 与值班机制</section>
</li>
<li>
<section>没有供应链安全策略（依赖库、镜像、密钥）</section>
</li>
</ul>
<p data-tool="mdnice编辑器">等到系统进入关键路径，会发现自己已经没有退路。再想补课，代价是「停业务」或者「带病重构」。</p>
<p data-tool="mdnice编辑器">这也是我反复强调「拥有软件是负债」的原因。负债不会因为 AI 变聪明就消失，它只会增长得更快。</p>
<p data-tool="mdnice编辑器"><strong>AI 把「做一个能用的软件」变成了常态，把「把软件长期跑对」推成了门槛</strong>。薄 SaaS 会被挤压，真正提供服务、背负责任、把运营表面积吃掉的 SaaS，会更值钱。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/03/saas-ai/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AI 编程狂飙的时代，程序员的价值在哪里？该走向何方？</title>
		<link>https://www.phppan.com/2026/02/ai-coding-opus4-6/</link>
		<comments>https://www.phppan.com/2026/02/ai-coding-opus4-6/#comments</comments>
		<pubDate>Sat, 14 Feb 2026 12:56:15 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[AI编程]]></category>
		<category><![CDATA[程序员价值]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2469</guid>
		<description><![CDATA[最近新上了 Opus 4.6 ，它又给我们这帮老程序员上了一课。 在一个近一年没有迭代（指没有被封）的程序员群 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">最近新上了 Opus 4.6 ，它又给我们这帮老程序员上了一课。</p>
<p data-tool="mdnice编辑器">在一个近一年没有迭代（指没有被封）的程序员群里，有大佬分享了如下的案例：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>团队有个复杂遗留系统，典型「多线程 + 历史包袱 + 不敢动」组合。模型先给了一个方案：加锁、等待、条件变量、再加一层保护，几百行代码，逻辑像一团湿毛线。能跑，理论上也对，但你让我把这玩意儿上线？我不敢。</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>我让它「再想想，能不能避免这些锁和等待」。它又跑了很久，最后给了一个极简方案：之前那些锁啊等待啊都删了，思路干净到让我怀疑它刚才在干嘛。</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>那一刻我脑子里冒出来的不是「AI 真强」，而是一个更别扭的问题：</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p><strong>我到底起了什么价值？</strong></p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>背景上下文？它从代码库里能 infer 掉绝大多数。设计约束？很多也能从调用关系、线程模型、运行时指标推出来。所谓「design taste」？我甚至可以写成 Markdown 规则让它照做。</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>过去资深开发的硬价值之一是 reasoning 的过程：拆问题、找不变量、选折中、落地细节。现在模型也能做，还能做得很快。</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>我绕了一圈，最后落在一个很不体面的结论上：<strong>人的价值被压缩到极小概率的「否决权」里</strong>。</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>99% 的时候，我是在给 AI 的解「盖萝卜章」：嗯，看起来没错。<br />
剩下那 1% 的时候，我得站出来说：不行，这条路走不通，换解空间里的另一个点。</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>这个角色像保险。你买的时候就知道大概率用不上，但真出事的时候要能扛住。更像现在的 L2-L4 自动驾驶：人坐在方向盘前，99.9% 的时间无事可做，为了那 0.1% 的「可能发生也可能不发生」。</p></blockquote>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>问题来了：这 1% 会不会也被另一个 agent 替掉？再给一个「专职 reviewer」去 challenge 写代码的 agent，让它把那 1% 找出来。</p></blockquote>
<p data-tool="mdnice编辑器"><strong>那人还剩什么？</strong></p>
<p data-tool="mdnice编辑器"><strong>以及这会把程序员的岗位推向哪里。</strong></p>
<p data-tool="mdnice编辑器">很多讨论卡在「AI 写代码快，所以程序员要失业」这种口号里。工程上更真实的变化是两条曲线的剪刀差：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>代码生成成本</strong>下降得很快：写一段能跑的实现、补齐样板、迁移接口、写单元测试骨架，这些都接近「文本补全」。</section>
</li>
<li>
<section><strong>承担后果的成本</strong>上升：上线事故、性能回退、并发死锁、数据一致性破坏、合规风险、供应链安全。AI 让改动频率变高，系统的「变更面」变大，出事概率跟着涨。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">我现在看 AI 产出的 patch，经常有一种荒诞感：<br />
改动本身很漂亮，解释也很漂亮，真正要命的点藏在「系统级不变量」里，而那部分恰好最难被 prompt 描述清楚，也最难被静态检查覆盖。</p>
<p data-tool="mdnice编辑器">所以讨论「程序员价值」别从「写代码」切入，从「为系统负责」切入。写代码只是责任链条里最便宜的一环。</p>
<p data-tool="mdnice编辑器">聊回到前面大佬分享的案例。</p>
<p data-tool="mdnice编辑器">为什么模型会先给「复杂加锁方案」，再给「极简方案」</p>
<p data-tool="mdnice编辑器">这不是模型「变聪明了」，更像搜索策略切换。</p>
<p data-tool="mdnice编辑器">我自己复盘过很多次类似现象，模型第一次给复杂方案，常见原因有几类：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>目标函数不清：模型默认把「不出错」权重大幅拉高</strong>，并发问题里，模型的默认倾向是「保守」：能锁就锁，能等就等。因为它无法确认你的系统允不允许重构线程模型，也不知道你能承受多少延迟和吞吐损失。一句「能不能避免」其实是在改目标函数：把「简单性」和「可维护性」的权重抬起来，把「局部可证明正确」的偏好压下去。</p>
</section>
</li>
<li>
<section><strong>它没拿到关键不变量</strong>：并发优化的核心不是技巧，是不变量。例如：哪些数据必须线性一致，哪些允许最终一致；哪些操作必须串行化，哪些可以交换；线程间共享状态的「所有权」到底属于谁；是否存在天然的「单 writer」路径。模型第一次通常拿不到这些，它会用锁把未知包起来。你追问一次，相当于逼它去反推不变量，或者提出重构以创造不变量。</p>
</section>
</li>
<li>
<section><strong>在「局部最优」里打转</strong>：遗留系统经常有局部约束：你动不了某个模块，改不了调用方，不能引入新队列，不能改变线程亲和性。</p>
</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><strong>人的价值最终会被压到 1%</strong></p>
<p data-tool="mdnice编辑器">这事已经发生了。</p>
<p data-tool="mdnice编辑器">如果把「写实现」交给模型，人还剩什么？</p>
<p data-tool="mdnice编辑器">我现在更愿意把角色拆成四层，分别看哪些会被自动化吃掉：</p>
<ol data-tool="mdnice编辑器">
<li>
<section><strong>执行层</strong>：写 CRUD、搬接口、补样板、按规范改文件结构<br />
这层基本被吃穿。</section>
</li>
<li>
<section><strong>局部推理层</strong>：读一段代码、定位 bug、做局部重构<br />
这层大幅被压缩，速度优势在模型。</section>
</li>
<li>
<section><strong>系统推理层</strong>：跨模块不变量、性能上界、故障模式、发布策略、回滚路径<br />
这层短期很难完全自动化，原因是信息不完备且目标冲突。</section>
</li>
<li>
<section><strong>责任层</strong>：线上事故谁背、合规谁签、业务损失谁扛<br />
这层本质是组织结构问题，不是智能问题。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">很多资深工程师过去主要靠第 2 层吃饭：你会拆、会想、会写出「更优雅」的实现。现在模型把这层的边际价值压得很薄，于是人的价值看起来就剩「在模型犯错时否决」。</p>
<p data-tool="mdnice编辑器">这就引出一个很工程的问题：<strong>能不能用另一个 agent 把「否决」也自动化？</strong></p>
<p data-tool="mdnice编辑器">答案是：能覆盖很大一部分，但永远留洞。洞的大小取决于你怎么搭系统。</p>
<p data-tool="mdnice编辑器"><strong>那 1% 到底是什么：哪些场景必须有人类 override</strong></p>
<p data-tool="mdnice编辑器">我现在把「必须 override」的场景分成几类，每一类都对应一套工程信号。我们可以用这些信号去训练 reviewer agent，也可以用来提醒自己别当「只会点 approve 的人」。</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>需求语义存在空洞，代码再正确也没意义</strong>：常见现象： PR 描述是「修复偶现 bug」，但没有可复现条件，没有失败判据；业务方说「按之前逻辑」，但「之前逻辑」存在灰度分支、历史例外。这类问题 AI 很难凭空补齐。它会把空洞当成「默认值」，然后写出一份自洽的实现。你看起来也挑不出毛病，直到线上行为偏了。<strong>override 的动作</strong>往往不是改代码，而是卡住合并，逼需求补齐验收条件和反例。</section>
</li>
<li>
<section><strong>系统级不变量被破坏，局部看不出，全局会炸</strong>：典型不变量：</p>
<ul>
<li>
<section>计费、库存、资金流的幂等与去重</section>
</li>
<li>
<section>订单状态机的单向性</section>
</li>
<li>
<section>写路径单 writer，读路径可并发</section>
</li>
<li>
<section>缓存一致性策略：写穿、失效、双写窗口</section>
</li>
<li>
<section>SLA 约束下的超时与重试预算</section>
</li>
</ul>
</section>
</li>
</ul>
<p data-tool="mdnice编辑器">模型能理解这些词，但它很难知道「你们公司真实的不变量是什么」。很多不变量写在事故复盘里，写在某个老同事的脑子里，写在那段「不要动」的注释里。</p>
<p data-tool="mdnice编辑器"><strong>override 的动作</strong>通常是把不变量显式化：写进 ADR（架构决策记录）、写进测试、写进发布 checklist。写完再让模型改。</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>代价函数冲突：延迟、吞吐、成本、可维护性互相打架</strong>这些对尾延迟很要命。你让它「简单化」以后，它可能会走向另一个极端：过度重构，侵入面太大，风险高得离谱。这类冲突靠 prompt 很难一次调对，得靠<strong>基准测试 + 真实流量回放</strong>。人类 override 的价值在于：你知道线上哪条曲线最敏感，知道预算是多少，知道哪里可以牺牲。</p>
</section>
</li>
<li>
<section><strong>生产环境的「脏现实」：测试覆盖不到</strong> 包括但不限于：</p>
</section>
</li>
</ul>
<ul data-tool="mdnice编辑器">
<li>
<section>时钟漂移、时区、闰秒</section>
</li>
<li>
<section>依赖服务的抖动、限流、半开连接</section>
</li>
<li>
<section>数据脏写、历史脏数据格式</section>
</li>
<li>
<section>热点 key、倾斜分片、长尾用户行为</section>
</li>
<li>
<section>灰度期间的双版本共存</section>
</li>
</ul>
<p data-tool="mdnice编辑器">AI 可以写出很干净的逻辑，干净得像从未上过线。</p>
<p data-tool="mdnice编辑器">人类 override 的价值在于：你知道哪些脏东西真实存在，知道一旦触发会损失多少钱。</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>安全与合规：模型会「帮你越线」</strong></section>
</li>
</ul>
<p data-tool="mdnice编辑器">安全问题里最阴的是「看起来像优化」：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>为了排查问题把敏感字段打进日志</section>
</li>
<li>
<section>为了方便把权限校验挪到上层，结果漏掉某些调用路径</section>
</li>
<li>
<section>为了提高命中率调整缓存 key，结果造成跨租户数据串读</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这类问题 reviewer agent 能抓一部分，靠规则匹配和数据流分析。但组织里真正要命的合规约束往往来自外部：合同、监管、审计口径。模型很难内建你们的合同条款。</p>
<p data-tool="mdnice编辑器">只留了 1%，那么大公司删人游戏才刚开始，接下来组织会怎么变？有前司裁员了一半。</p>
<p data-tool="mdnice编辑器">AI 带来了两件具体的事：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>单位时间的变更量</strong>上升</section>
</li>
<li>
<section><strong>对稳定性与合规的要求</strong>不会下降</section>
</li>
</ul>
<p data-tool="mdnice编辑器">组织会自然把资源往两端挤压：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>一端是「产出变更」的能力：更少的人能产出更多 patch</section>
</li>
<li>
<section>另一端是「控制风险」的能力：测试、发布、SRE、安全、平台工程会更吃香</section>
</li>
</ul>
<p data-tool="mdnice编辑器">中间那层「靠手写实现体现资深」的位置，会被挤得很难受。我们会看到更多岗位变成：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>平台/工具链负责人</section>
</li>
<li>
<section>质量与发布工程负责人</section>
</li>
<li>
<section>架构与技术治理（不变量、规范、依赖治理）</section>
</li>
<li>
<section>领域负责人（把业务语义写成可执行的约束）</section>
</li>
</ul>
<p data-tool="mdnice编辑器">如果还把价值押在「我写得快、我写得优雅」，会被模型直接碾过去。</p>
<p data-tool="mdnice编辑器">那我们该何去何从？</p>
<p data-tool="mdnice编辑器">我更建议把自己训练成「系统负责人」，别训练成「提示词手艺人」</p>
<p data-tool="mdnice编辑器">很多人看到 AI 就去卷 prompt。prompt 当然有用，但它属于「表达能力」，不属于「护城河」。</p>
<p data-tool="mdnice编辑器">我更建议把成长路线拆成三条，按你自己的背景选一条主线，另外两条补短板。</p>
<p data-tool="mdnice编辑器"><strong>路线 A：系统与可靠性（适合后端、架构、TL）</strong> 要能回答这些问题：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>这个系统的核心不变量是什么？写在哪里？谁维护？</section>
</li>
<li>
<section>出了事故，最可能的故障模式是哪几类？怎么提前打点？</section>
</li>
<li>
<section>一次改动上线，回滚点在哪里？数据怎么保证不被写坏？</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这条路线的硬技能是：可观测性、故障注入、容量规划、发布治理、数据一致性策略。<br />
AI 会让这条路线更值钱，因为改动变多，风险更高。</p>
<p data-tool="mdnice编辑器"><strong>路线 B：平台与工具链（适合喜欢搞基建的人）</strong> 把「盖章」自动化掉。</p>
<p data-tool="mdnice编辑器">要能把下面这些做成产品：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>代码生成与改动的规范化输入（任务模板、约束模板）</section>
</li>
<li>
<section>自动化评审（多 agent + 规则 + 静态分析 + 安全扫描）</section>
</li>
<li>
<section>针对你们域的测试体系（尤其是并发、回归、流量回放）</section>
</li>
<li>
<section>一键灰度、一键回滚、发布可视化</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这条路线的本质是：把工程经验固化成流水线能力。模型越强，平台越重要。</p>
<p data-tool="mdnice编辑器"><strong>路线 C：领域语义与业务工程（适合对业务理解深的人）</strong> AI 最弱的地方之一是「公司特有的业务语义」。能把语义变成约束，价值就会变硬。</p>
<p data-tool="mdnice编辑器">要能做的是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>把业务规则写成状态机与不变量</section>
</li>
<li>
<section>把验收条件变成测试与监控</section>
</li>
<li>
<section>把历史例外收敛掉，减少「口口相传」</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这条路线听上去不像「技术」，但在 AI 时代，它会决定你是不是不可替代的那批人。</p>
<p data-tool="mdnice编辑器">我不押「永远需要人」这种安全说法。我更愿意给一个工程化的判断：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><strong>在约束清晰、环境封闭、回滚容易的系统里</strong>，人类接管的概率会持续下降。很多内部工具、数据管道、非核心链路会率先做到「几乎无人值守」。</section>
</li>
<li>
<section><strong>在约束隐含、环境开放、后果昂贵的系统里</strong>，1% 会长期存在。典型是资金、合规、核心交易链路、跨组织协作系统。这里的问题从来不只是智能，还包括责任与审计。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">更关键的一点：就算模型能覆盖 99.99%，组织也未必允许完全无人。原因很现实：责任链条需要一个签字的人。</p>
<p data-tool="mdnice编辑器">我写到这里，还是没法给一个让人舒服的答案。AI 把很多我们曾经引以为傲的能力变成了廉价品，这是事实。难受也正常。</p>
<p data-tool="mdnice编辑器">但工程世界一向认结果。模型写得再快，只要系统一炸，组织就会把注意力拉回到「谁能把它跑住」。把自己训练成能跑住系统的人，价值就不会跟着 token 价格一起下跌。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/02/ai-coding-opus4-6/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
