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

<channel>
	<title>潘锦的空间 &#187; harness engineering</title>
	<atom:link href="https://www.phppan.com/tag/harness-engineering/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sun, 05 Jul 2026 14:49:59 +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>聊聊 Harness：从 Agent 到组织</title>
		<link>https://www.phppan.com/2026/05/harness-engineering-anent-org/</link>
		<comments>https://www.phppan.com/2026/05/harness-engineering-anent-org/#comments</comments>
		<pubDate>Sat, 30 May 2026 09:35:05 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[harness]]></category>
		<category><![CDATA[harness engineering]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2501</guid>
		<description><![CDATA[我们在落地 Agent 时面临的核心矛盾，是大模型的概率生成机制与工程系统所需的绝对确定性存在天然冲突。要获取 [&#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 时面临的核心矛盾，是大模型的概率生成机制与工程系统所需的绝对确定性存在天然冲突。要获取大规模、可维护且值得信赖的代码，必须在系统外围构建 Harness。<strong>Harness 的本质是将不确定性转化为确定性。</strong></p>
<p data-tool="mdnice编辑器">提高信任度和可靠性需要极度压缩 Agent 的解决方案空间。我们必须放弃让模型「生成任何内容」的灵活性，转而采用包含大量技术细节的提示、规则和框架。特定的架构模式、强制执行的边界以及标准化的结构，构成了这套护栏的物理基础。</p>
<p data-tool="mdnice编辑器">当前越来越多的团队在持续快速的产生代码，而这些演示很好看，当真的进入整个软件生命周期中，就会产生混乱，当越来越多的人随着时间的推移在仓库中堆砌代码，组织就开始堆人进行 review、反复返工，最后 AI 的吞吐量被人类注意力卡死，表面上用了 Agent，实际产能没上去，维护成本还更高。</p>
<p data-tool="mdnice编辑器">当然，这是一种结果，也有人在过程中不停的构建基建，做 Harness 工程，整个代码不再是无序的扩张。从这个逻辑来讲，harness 的作用是把<strong>大模型输出从概率事件压回工程确定性的系统设计</strong>。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Agent 的 Harness</span></h1>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">harness 是什么</span></h2>
<p data-tool="mdnice编辑器"><strong>很多人把 harness 比作一个操作系统，模型是 CPU，但我觉得并不是。</strong></p>
<p data-tool="mdnice编辑器">如果模型真的是 CPU，那它接收指令后的执行结果应该是绝对严格且可预测的；但大模型本质上是一个概率引擎，它在潜空间里做的是模式匹配与概率生成。因此，harness 并不是像操作系统那样去调度底层硬件资源或分配内存，它更像是一套概率过滤器和对齐机制。它依靠纯粹的工程手段，把模型那种发散的、充满不确定性的「创造力」或「幻觉」，强行压缩进一条狭窄、严谨且符合人类预期的流水线里。</p>
<p data-tool="mdnice编辑器">这种工程逻辑在实践中，体现为无处不在的防御性设计和反馈闭环。当模型吐出一串代码或一个决策时，harness 并不负责直接「运行」它，而是负责「质检」和「纠偏」。它通过静态检查、架构规则扫描、自动化测试和沙箱验证，把模型给出的「大概率正确」转化为工程上非黑即白的「通过或驳回」。正是这种让概率不断撞击确定性规则的过程，才使得最终沉淀到代码库里的产物是安全、可控且符合系统长期利益的。</p>
<p data-tool="mdnice编辑器">harness 解决确定性问题的终极目的，是为了在系统中建立无需人工干预的信任，从而真正释放 AI 的吞吐量。如果没有这套逻辑，模型生成的代码越多，人类审查的负担就越重，整个组织的运转速度依然会被人类的注意力瓶颈卡死。</p>
<p data-tool="mdnice编辑器">Martin Fowler 的博客中发表了 Thoughtworks 的技术专家的一篇文章，将 OpenAI 文章中所描述的 harness 分为三个方面：</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">上下文工程</span></h2>
<p data-tool="mdnice编辑器">上下文工程需要做到动态与静态的交织</p>
<p data-tool="mdnice编辑器">单纯依赖超长 Prompt 无法解决复杂工程问题。上下文工程的核心在于构建代码库中持续增强的知识库，并打通 Agent 对动态上下文的访问路径。</p>
<p data-tool="mdnice编辑器">静态知识库定义了系统的基础法则。我们将领域模型、API 契约和历史架构决策文档化，作为 Agent 初始化的基线上下文。动态上下文决定了 Agent 在运行时的决策质量。系统需要将实时的可观测性数据、测试覆盖率报告甚至浏览器导航状态，实时注入到 Agent 的工作流中。缺乏动态上下文的 Agent 就像蒙眼狂奔的打字机，产出的代码在语法上完美，在逻辑上完全脱离系统现状。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">架构约束</span></h2>
<p data-tool="mdnice编辑器">架构约束是确定性的防线。</p>
<p data-tool="mdnice编辑器">完全依赖 LLM 进行自我反思和代码审查，在生产环境中极度危险。架构约束必须由确定性的自定义代码检查器和结构测试来强制执行。</p>
<p data-tool="mdnice编辑器">我们通过静态分析工具拦截不合规的依赖调用，利用 AST（抽象语法树）解析确保代码分层符合规范。当 Agent 试图在 UI 层直接发起数据库连接时，确定性的检查器会立即阻断该行为，并将具体的错误堆栈和修复路径作为反馈输入给 Agent。这种混合架构确保了系统的底线由死板的规则守卫，Agent 的创造力被严格限制在安全的沙盒内。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">垃圾回收</span></h2>
<p data-tool="mdnice编辑器">垃圾回收主要是用于对抗代码熵增。</p>
<p data-tool="mdnice编辑器">完全自主的智能体引入了代码库衰败的新问题。Agent 会精准且不知疲倦地复现代码仓库中已存在的模式，包含那些不均衡或不够理想的遗留设计。随着时间的推移，这种行为不可避免地导致系统架构漂移。</p>
<p data-tool="mdnice编辑器">最初，人类开发者试图手动处理这个问题。团队过去每周五要花费 20% 的时间清理「AI 残渣」。这种依赖人力的做法毫无可扩展性。</p>
<p data-tool="mdnice编辑器">我们将资深工程师的主观品味转化为机械规则，提炼为「黄金原则」并直接编码到代码仓库中，建立了一个循环清理流程。我们强制要求使用共享的实用程序包，禁止手工编写零散的辅助工具，确保不变式集中管理。我们严禁使用猜测性的数据探测，强制验证边界或依赖类型化的 SDK，防止 Agent 基于虚幻的结构进行构建。</p>
<p data-tool="mdnice编辑器">系统定期运行一组后台 Agent 任务，扫描代码库中的偏差、更新质量等级，并发起有针对性的重构 Pull Request。这些 PR 大多可以在一分钟内完成审查并自动合并。这套机制的功能等同于内存管理中的垃圾回收。技术债务如同高息贷款，通过高频的微小重构不断偿还，远胜过让债务累积到系统崩溃。人类的架构品味一旦被捕获并规则化，就会无情地应用于每一行代码，每天自动发现并消灭不良模式。</p>
<h1 data-tool="mdnice编辑器"><span class="content">AI Agent Harness 的工程化落地</span></h1>
<p data-tool="mdnice编辑器">从几个流行的框架来看，主要是从流程强化、规格沉淀、任务编排等逻辑上来做事情。</p>
<p data-tool="mdnice编辑器">将这些逻辑拆开可以分为四个维度：</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">上下文工程</span></h2>
<p data-tool="mdnice编辑器">上下文工程主要是在规范层解决问题，其主要解决的「规则文件失控」的问题，实现规格沉淀与对齐，以及上下文工程的可控。</p>
<p data-tool="mdnice编辑器">之前，我们习惯把所有规范塞进类似于单个 <code style="color: #ef7060;">.cursorrules</code> 文件，导致 AI 上下文过载且容易忽略细节。这一层落地的第一步是建立结构化、按需加载的规范体系。主要做到如下的点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #000000;">规范模块化</strong>：将系统架构、数据库规范、错误处理等拆分为独立的结构化文档。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">按需检索</strong>：AI 不需要每次都通读所有规范，而是根据当前所处的任务阶段，动态检索并加载所需的上下文。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">任务记忆隔离</strong>：为每个独立任务建立物理隔离的工作区和日志。AI 每次开启新会话时，只读取当前任务的精确记忆，既解决了“跨会话失忆”，又屏蔽了无关信息的干扰。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">以 Trellis 框架为例，Trellis 摒弃了单一庞大的全局提示词文件，而是采用 spec/ 目录将规范模块化（如拆分为 database-guidelines.md）。在执行任务时，它利用 tasks/ 目录下的 JSONL 配置文件，让 Agent 动态检索并按需加载上下文。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">架构约束</span></h2>
<p data-tool="mdnice编辑器">架构约束的<strong>核心逻辑：用代码约束代码，实现闭环自愈。</strong> 口头约定或纯文本规范在 AI 面前是脆弱的，它极易为了「跑通逻辑」而破坏架构分层。</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #000000;">规则代码化</strong>：将核心的架构依赖规则（例如“前端组件严禁直接调用数据库”）编写为静态分析脚本或自定义 Linter。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">带解释的强阻断</strong>：在代码提交或验证阶段强制执行这些拦截器。关键在于，报错信息不能仅仅是「检查失败」，必须输出高度结构化的指导：明确告诉 AI“为什么违反了规则”以及“正确的做法是什么”。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">自动修复</strong>：AI 读取到结构化的报错指导后，能够自动理解并修正代码，形成无需人类介入的自愈闭环。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">反馈循环</span></h2>
<p data-tool="mdnice编辑器"><strong>核心逻辑：降噪处理，防范死循环。</strong> LLM 的注意力会被长篇大论的日志（如几千行的覆盖率输出）稀释注意力，从而忽略真正致命的错误。 因此我们需要做到：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #000000;">零输出原则</strong>：改造验证脚本。如果测试通过，脚本应保持完全沉默；如果失败，只输出精简的错误堆栈和失败原因。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">强制验收清单</strong>：在 AI 试图标记任务「已完成」之前，系统应强制拦截，要求其对照需求文档逐项确认边界条件。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">防死循环干预</strong>：设定重试阈值。如果 AI 对同一文件连续修改多次且测试依然失败，系统应主动中断并强制其回滚代码、重新审视需求，防止 AI 陷入无效的「幻觉修 Bug」循环。</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">熵管理</span></h2>
<p data-tool="mdnice编辑器">熵管理主要是阻断「坏模式」的指数级扩散</p>
<p data-tool="mdnice编辑器"><strong>核心逻辑：快速偿还技术债。</strong> AI 复制坏代码的速度是指数级的。一旦允许一个临时的妥协方案合入主分支，AI 会在极短时间内将其复制到整个代码库。</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #000000;">高频垃圾收集</strong>：彻底放弃“集中清技术债”的传统做法。每天必须安排固定时间，专门 Review AI 生成的代码（人工或 AI 自动），及时识别新引入的坏模式。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">规范资产的动态演进</strong>：一旦发现坏模式，立即让 AI 深度分析根因，并<strong style="color: #000000;">自动将正确的防范规则更新到规范库中</strong>。</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">团队级免疫</strong>：由于规范库与代码同源管理（存在于 Git 仓库中），当这段新规则被提交后，团队其他成员拉取代码时，他们的 AI 助手就能立刻“学会”这个新技能。这把偿还技术债的动作，变成了每天自动化、可积累的系统进化。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">以 Cursor 为例，可以更新 Team Rules</p>
<h1 data-tool="mdnice编辑器"><span class="content">组织级 Harness</span></h1>
<p data-tool="mdnice编辑器">聊完 Agent 的 Harness，再聊一下组织的。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">人的角色已经变了</span></h2>
<p data-tool="mdnice编辑器">大家都知道康威定律，简单来说就是：<strong>设计系统的组织，其产生的设计受限于这些组织的沟通结构。</strong></p>
<p data-tool="mdnice编辑器">而系统设计到最后，也一定会遇到一个问题：<strong>谁来定义规则，谁来解释例外，谁来承担后果。</strong></p>
<p data-tool="mdnice编辑器">以前的软件开发分工相对稳定。PM 写需求，设计出稿，前后端分别实现，测试验证，运维发布。大家各自占一段链路，边界虽然有摩擦，但总体清楚。</p>
<p data-tool="mdnice编辑器">AI 进来以后，边界开始模糊。</p>
<p data-tool="mdnice编辑器">PM 已经可以直接产出前端原型，很多时候产出的还不是静态图，而是真能跑的页面代码。设计师也不再只是给稿子，很多交互和组件约束可以直接沉淀成生成资产。前端工程师花在纯页面搭建上的时间下降，开始更多介入状态管理、交互抽象、可维护性收拢。后端和算法也更早被拉进来，因为很多 AI 生成的原型一开始就会碰到真实数据和能力边界。</p>
<p data-tool="mdnice编辑器">这是现在很多团队正在进行的转型。</p>
<p data-tool="mdnice编辑器">如果组织还按旧的分工运转，Agent 会把协作缝隙快速放大。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">组织级 Harness 要管什么</span></h2>
<p data-tool="mdnice编辑器">我理解的组织级 harness，重点在三件事：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #000000;">定义新的协作接口</strong></section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">重新分配注意力</strong></section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">把责任从「谁写了代码」改成「谁定义了系统」</strong></section>
</li>
</ol>
<h3 data-tool="mdnice编辑器"><span class="content">协作接口要前移</span></h3>
<p data-tool="mdnice编辑器">以前很多问题可以留到开发阶段再对齐。现在不行。</p>
<p data-tool="mdnice编辑器">因为 PM 通过 AI 已经能直接产出前端代码，需求不再是文字说明，而可能是一个可交互原型；设计规范也不再只是 Figma 标注，而是可以半自动映射到组件约束；后端接口能力如果不提前讲清楚，前面的生成很容易一路偏到错误方向。</p>
<p data-tool="mdnice编辑器">所以组织里的评审必须前移，重点也得改。</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>
</ul>
<p data-tool="mdnice编辑器">这几个东西不提前定，后面会出现一个很常见的问题：原型阶段看起来进展飞快，进入工程化后才发现返工巨大。</p>
<h3 data-tool="mdnice编辑器"><span class="content">注意力要重新分配</span></h3>
<p data-tool="mdnice编辑器">我现在越来越少鼓励资深工程师花时间逐行抠低风险代码。</p>
<p data-tool="mdnice编辑器">这不是说 review 不重要，而是注意力要贵着用。</p>
<p data-tool="mdnice编辑器">在 Agent 环境里，重要的工作变成了：</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>
<li>
<section style="color: #010101;">决定哪些异常值得阻塞主流程</section>
</li>
<li>
<section style="color: #010101;">审核高风险改动和高影响面重构</section>
</li>
</ul>
<p data-tool="mdnice编辑器">反过来，低风险、重复性、局部性的东西，应该尽量交给自动化校验和后台清理任务。</p>
<p data-tool="mdnice编辑器">如果一个组织还在让最贵的人力去看大批格式化差异、小工具改名、重复样板代码，那 harness 基本等于没有。</p>
<h3 data-tool="mdnice编辑器"><span class="content">责任归属要重写</span></h3>
<p data-tool="mdnice编辑器"><strong>在 AI-Native 组织里，谁对结果负责？</strong></p>
<p data-tool="mdnice编辑器">PM 产出了页面代码，前端做了工程化收拢，Agent 自动补了测试，清理 Agent 又改了一轮共享工具。最后线上出问题，算谁的？</p>
<p data-tool="mdnice编辑器">如果这个问题没有明确答案，团队会很快进入防御状态。每个人都怕接 AI 产出的锅，于是流程开始重新变重，所有人都试图把责任往后传。</p>
<p data-tool="mdnice编辑器">所以组织级 harness 一定要明确责任模型。</p>
<p data-tool="mdnice编辑器">可以按三层分：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #000000;">需求责任</strong>：谁定义了目标与验收标准，谁负责需求正确性</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">架构责任</strong>：谁定义了边界、模式和约束，谁负责系统一致性</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #000000;">发布责任</strong>：谁决定进入生产环境，谁负责风险接受</section>
</li>
</ul>
<p data-tool="mdnice编辑器">不要再执着于「谁手写了这行代码」。就像团队管理一样，最后拍板的人担责。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">可落地的 AI-Native 研发流程</span></h2>
<p data-tool="mdnice编辑器">以下为我们当前在跑的流程：</p>
<h3 data-tool="mdnice编辑器"><span class="content">需求生成</span></h3>
<p data-tool="mdnice编辑器">第一步由 PM 主导，但交付物不再只是 PRD，而是<strong>带验收标准的可运行原型</strong>。</p>
<p data-tool="mdnice编辑器">但是，原型代码不等于可直接上线代码。它的价值是澄清需求、暴露分歧、提前感知交互复杂度。</p>
<p data-tool="mdnice编辑器">所以 PM 可以生成，但不能默认拥有工程决策权。最终所有的代码都需要前端工程师构建的工具链条，以及 AI 和人工的审核及合入。</p>
<h3 data-tool="mdnice编辑器"><span class="content">联合评审</span></h3>
<p data-tool="mdnice编辑器">第二步是全员参与的需求评审与架构设计。设计、前端、后端、算法都要尽早介入。</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>
<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编辑器">这这一步的产出结构化进仓库，因为后面它会直接成为 Agent 的约束输入。</p>
<h3 data-tool="mdnice编辑器"><span class="content">工程收拢</span></h3>
<p data-tool="mdnice编辑器">第三步是工程化整合。这个阶段前端、后端、算法开始把前面的原型和需求收敛进正式系统。</p>
<p data-tool="mdnice编辑器">这里 Agent 会大量参与，但人类不能退出。重点工作包括：</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>
<li>
<section style="color: #010101;">处理跨模块影响面</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这一段最考验 harness，因为原型代码最容易带着局部最优、全局失真、风格漂移的问题冲进主仓。</p>
<h3 data-tool="mdnice编辑器"><span class="content">自动验证与灰度</span></h3>
<p data-tool="mdnice编辑器">最后一步是自动化测试、灰度发布和反馈回收。</p>
<p data-tool="mdnice编辑器">这一步先由专门的工程团队来负责，加入部分的 AI 成分，固化系统。</p>
<h1 data-tool="mdnice编辑器"><span class="content">从 Agent 到组织，真正难的是控制系统</span></h1>
<p data-tool="mdnice编辑器">很多人以为 AI 落地的核心挑战在模型能力、成本或者工具接入。我现在看，最大挑战更集中在三个词：<strong>环境、反馈回路、控制系统。</strong></p>
<p data-tool="mdnice编辑器">环境决定 Agent 看到了什么、能做什么、不能做什么。</p>
<p data-tool="mdnice编辑器">反馈回路决定错误会被放大，还是会被系统吸收成改进信号。</p>
<p data-tool="mdnice编辑器">控制系统决定生成能力增长之后，组织是变得更稳，还是更乱。</p>
<p data-tool="mdnice编辑器">这三个东西做不好，模型再强也只是更快地产生问题。</p>
<p data-tool="mdnice编辑器">做得好，哪怕模型能力没到最顶尖，系统一样能稳定进化。因为工程上真正稀缺的，从来不是一次惊艳输出，而是长期重复地产出靠谱结果。</p>
<h1 data-tool="mdnice编辑器"><span class="content">组织的 AI-Native 化</span></h1>
<p data-tool="mdnice编辑器">组织的 AI-Native 化也是慢慢进货，逐步推进的，先从小范围试起，再根据结果不断调整规则和流程。并且各家有各家的风格和气质。</p>
<p data-tool="mdnice编辑器">第一，<strong>选一条链路打透</strong>。不要一开始就全组织铺开。先找一个协作关系清楚、反馈周期短、风险相对可控的场景，比如中后台、运营工具、内部系统，或者低风险服务改造。重点不是让 AI 多写代码，而是先验证：信息怎么给、边界怎么定、错误怎么发现、问题怎么清理。</p>
<p data-tool="mdnice编辑器">第二，<strong>先改规则，再谈效率</strong>。很多团队一上来就问产能能提升多少，但更重要的是：规则有没有沉淀下来，错误能不能回流，坏模式能不能及时发现并清掉。如果这些没做好，所谓提效往往只是把问题推后，甚至把混乱放大。</p>
<p data-tool="mdnice编辑器">第三，<strong>把人的位置往上移</strong>。资深工程师要逐渐从大量写代码，转向定规则、画边界、看反馈；技术管理者要从盯人和排期，转向设计流程、分层风险、明确责任；产品可以更早参与原型，但不能越过工程判断。</p>
<p data-tool="mdnice编辑器">组织真正变成 AI-Native，不是因为每个人都在用 Agent，而是协作方式已经围绕 Agent 被重新设计过。</p>
<p data-tool="mdnice编辑器">模型当然重要，但不是决定性因素。真正拉开差距的，是谁先意识到：Agent 不是一个更快的开发者，而是一个高吞吐的生产单元。它会放大环境本身。规则清楚，它就放大规则；流程混乱，它就放大混乱。</p>
<p data-tool="mdnice编辑器">所以到最后，harness 这件事谈的根本不只是 AI。</p>
<p data-tool="mdnice编辑器">谈的是工程纪律怎么重新编码。</p>
<p data-tool="mdnice编辑器">谈的是组织协作怎么重新布线。</p>
<p data-tool="mdnice编辑器">谈的是我们怎么把概率生成系统，放进一个仍然要求长期维护、长期演进、长期负责的软件世界。</p>
<p data-tool="mdnice编辑器">这是我理解的「从 Agent 到组织」。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/05/harness-engineering-anent-org/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>
	</channel>
</rss>
