<?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>Sun, 13 Sep 2026 02:08:46 +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>带团队的进阶：从解决问题到发现问题</title>
		<link>https://www.phppan.com/2026/09/advancing-in-team-leadership-from-solving-problems-to-identifying-them/</link>
		<comments>https://www.phppan.com/2026/09/advancing-in-team-leadership-from-solving-problems-to-identifying-them/#comments</comments>
		<pubDate>Sun, 13 Sep 2026 02:08:46 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[团队管理]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2538</guid>
		<description><![CDATA[好久没有写团队管理的文章了。今天写一下最近在思考到的一个问题。 我有一个习惯是会经常检查自己的日程，看看时间花 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">好久没有写团队管理的文章了。今天写一下最近在思考到的一个问题。</p>
<p data-tool="mdnice编辑器">我有一个习惯是会经常检查自己的日程，看看时间花在哪里了。</p>
<p data-tool="mdnice编辑器">我会发现很多的时间都是在解决问题，不管是架构的问题，还是需求的问题，还是人的问题。你会发现自己很忙，也很充实，觉得自己发挥了作用。</p>
<p data-tool="mdnice编辑器">我们一直讲 XXX左移，如测试左移，需求左移。问题也需要左移。</p>
<p data-tool="mdnice编辑器">如果我们正在忙的这些事情下个月还会以相似的形式发生，思考一下我今天到底解决了什么问题？</p>
<p data-tool="mdnice编辑器">从接到一个明确任务开始，延伸到问题尚未被准确描述的时候。</p>
<h1 data-tool="mdnice编辑器"><span class="content">题目从哪里来</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器"><strong>管理者需要对任务的来源负责。</strong> 方案评审只能检查给定题目下的解法，无法自动纠正题目本身。</p>
<h1 data-tool="mdnice编辑器"><span class="content">扁鹊的难题</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器">发现得早，只有在判断质量和处理成本都合适的时候，才值得投入。</p>
<h1 data-tool="mdnice编辑器"><span class="content">把症状写清</span></h1>
<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>
<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>
<h1 data-tool="mdnice编辑器"><span class="content">寻找提前信号</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器">提前发现需要足够的信息，却不能靠无限增加信息量完成。可以从几个高损失场景倒推：在结果恶化之前，我们有没有机会看到变化？现有信息能否区分不同原因？缺少的那部分，怎样以最小代价补上？</p>
<h1 data-tool="mdnice编辑器"><span class="content">追问到哪一层</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器">分析问题的深度，应当服务于干预选择。继续追问已经无法改变行动时，我会停止扩展讨论，把精力转向验证。</p>
<h1 data-tool="mdnice编辑器"><span class="content">问题也要排期</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器">有限的资源只能处理有限的问题。管理者的工作包含排序、延后和拒绝，不能只提供一份越来越长的风险清单。</p>
<h1 data-tool="mdnice编辑器"><span class="content">先做有限验证</span></h1>
<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>
<p data-tool="mdnice编辑器">如果测试任务都是规则清楚、信息完整的样本，结论也只能覆盖这部分任务。遇到资料缺失、规则冲突或无法判断的情况，系统怎样退出，谁来接手，都要进入验证范围。</p>
<p data-tool="mdnice编辑器">有限实验也有局限。它可能遗漏低频问题，额外受到团队关注，或者无法覆盖规模扩大后的协作成本。因此，通过一次实验以后，我会继续限制推广范围，保留回退条件。</p>
<p data-tool="mdnice编辑器">我愿意为这些步骤支付一些时间。相较于一次性更换完整流程，分阶段验证让我有机会在投入扩大之前修正判断。</p>
<h1 data-tool="mdnice编辑器"><span class="content">授权与介入</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器">发现问题之后，我还欠团队一个决定：谁负责、投入多少、何时检查，以及哪些事情因此不做。</p>
<h1 data-tool="mdnice编辑器"><span class="content">让风险能上来</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器">这样做会增加一些记录成本。我愿意保留与决策和验收有关的部分，不要求团队为每项改善制作完整汇报材料。</p>
<h1 data-tool="mdnice编辑器"><span class="content">检查已经完成的</span></h1>
<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>
<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>
<p data-tool="mdnice编辑器">我也会定期回看自己提出的问题。有多少得到了证据支持，有多少被否定，哪些因为迟迟没有决定而继续消耗团队，哪些处理动作带来了新的负担。</p>
<p data-tool="mdnice编辑器">否则，所谓发现问题，很容易变成管理者不断向团队追加任务。</p>
<p data-tool="mdnice编辑器">从解决问题到发现问题，对我意味着工作范围扩大了：需要参与定义，需要作出取舍，还需要在结果出现以后检查当初的判断。问题提前进入视野，只是给了我们更多处理空间。这个空间有没有被用好，最终仍然要回到具体的行动和结果上。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/09/advancing-in-team-leadership-from-solving-problems-to-identifying-them/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>DeepSeek Harness 的上下文管理、记忆和知识库剖析</title>
		<link>https://www.phppan.com/2026/09/an-analysis-of-deepseek-harnesss-context-management-memory-and-knowledge-base/</link>
		<comments>https://www.phppan.com/2026/09/an-analysis-of-deepseek-harnesss-context-management-memory-and-knowledge-base/#comments</comments>
		<pubDate>Sun, 06 Sep 2026 11:48:53 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[DeepSeekHarness]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2536</guid>
		<description><![CDATA[DeepSeek Harness 里，上下文、记忆和知识库并没有被设计成三个彼此独立的重型系统。它们围绕 Se [&#8230;]]]></description>
				<content:encoded><![CDATA[<div class="Post-RichTextContainer" style="color: #191b1f;">
<div class="css-1od93p9">
<div class="css-376mun">
<div class="RichText ztext Post-RichText css-1petdff">
<p data-first-child="" data-pid="ZVS7zDT9">DeepSeek <a class="RichContent-EntityWord css-b7erz1" style="color: #09408e;" href="https://zhida.zhihu.com/search?content_id=283001209&amp;content_type=Article&amp;match_order=1&amp;q=Harness&amp;zhida_source=entity" target="_blank" data-za-not-track-link="true" data-paste-text="true">Harness</a> 里，上下文、记忆和知识库并没有被设计成三个彼此独立的重型系统。它们围绕 Session Log 协同工作。</p>
<p data-pid="bUf-mryy">用三句话概括：</p>
<ul>
<li data-pid="nzNNv1Y7">上下文管理负责决定当前这一轮模型能看到什么。系统提示词、动态环境、工具定义和会话消息，会在模型调用前完成装配。</li>
<li data-pid="du7DZHN-">记忆机制负责在上下文窗口不足时折叠旧历史。原始会话日志继续保留，模型当前可见的历史被替换成结构化摘要。</li>
<li data-pid="IO-ekMll">知识获取负责把仓库文件和外部信息送入模型。文件通过路径引用和读取工具进入，网络信息通过搜索、抓取工具进入，所有结果最终写回 Session Log。</li>
</ul>
<p data-pid="2531wJkA">三个模块共享一条约束：</p>
<p data-pid="Ol_jid7N">模型看到的内容，需要拥有可追踪的来源，并且能够从会话记录中重新构造。</p>
<p data-pid="sdoo15aJ">这条约束决定了 DeepSeek Harness 的整体形态。上下文不能由控制器随手拼接，工具结果不能停留在进程内存，压缩不能覆盖原始记录，检索结果也不能绕过会话日志直接塞给模型。</p>
<p data-pid="Ztch0Epe">它由此形成了一条完整链路：</p>
<p data-pid="gYE12SPk"><code>SystemPrompt.assemble()</code> 组装当前上下文，<code>ReactLoopAgent.preStep()</code> 决定动态内容是否进入会话，<code>Session.append()</code> 保存事件，<code>Session.deriveMessages()</code> 生成模型历史，<code>buildRequest()</code> 构建最终请求，<code>llm.stream()</code> 完成模型调用。</p>
<p data-pid="Lxwlklrz">理解了这条链路，就大概能了解 DeepSeek Harness 的知识和记忆相关的逻辑了。</p>
<h2 style="font-weight: 500; font-style: inherit;">上下文管理</h2>
<p data-pid="-iIki_pi">上下文管理解决的是一个具体问题：每次调用模型时，系统应该选择、组织并发送哪些内容。</p>
<p data-pid="1OxMbrL6">现在大多数 Agent 系统不会简单维护一个持续增长的字符串，而是使用结构化消息列表管理上下文。较成熟的系统还会根据任务状态，动态选择系统提示词、历史消息、工具定义、运行环境和检索结果。</p>
<p data-pid="T4yW9dCg">真正的难的不是 「是否使用消息列表」，而在于：</p>
<ul>
<li data-pid="7hW92SbN">哪些内容应该进入本轮请求；</li>
<li data-pid="3WylJe-n">不同内容由哪个模块提供；</li>
<li data-pid="TEQOcncN">动态状态发生变化后如何更新；</li>
<li data-pid="Fo-aSMbf">重复信息是否需要再次发送；</li>
<li data-pid="yXNiuvMo">长会话中哪些历史应该保留或压缩；</li>
<li data-pid="7a2E9ue9">服务重启后能否恢复模型当时看到的内容；</li>
<li data-pid="MUCJcQJR">最终请求能否被追踪、审计和重放。</li>
</ul>
<p data-pid="68fGdd0A">DeepSeek Harness 同样以结构化消息为基础，但它没有让 AgentLoop 直接维护所有上下文，而是将上下文拆分为不同来源，在每个 step 开始前统一装配。</p>
<h3 style="font-weight: 500; font-style: inherit;">1. 分层组织上下文</h3>
<p data-pid="WIz0eDZE">DeepSeek Harness 中的上下文主要分为四类：</p>
<ul>
<li data-pid="h36hu1TF">系统规则：角色设定、行为约束和工具使用规范；</li>
<li data-pid="-CNOVSI9">动态状态：当前工作目录、运行环境和终端状态；</li>
<li data-pid="ROvWEBtd">会话历史：用户消息、模型回复和工具调用结果；</li>
<li data-pid="7JX2q7Pw">工具定义：当前允许模型调用的工具及其参数结构。</li>
</ul>
<p data-pid="CpfzbkmD">这些内容分别由 SystemPrompt、Runtime Context、Session 和 ToolRuntime 管理，最后在模型调用前合并。</p>
<p data-pid="df8iKdLJ">这种设计的重点不是改变消息格式，而是明确上下文的来源和职责。AgentLoop 只负责控制执行流程，不直接承担提示词拼接、历史管理和工具注册等工作。</p>
<h3 style="font-weight: 500; font-style: inherit;">2. 动态装配本轮内容</h3>
<p data-pid="sgu7nVD0">每个 step 开始前，<code>ReactLoopAgent.preStep()</code> 会调用 <code>SystemPrompt.assemble()</code>，收集本轮所需的系统提示、动态上下文和工具定义。</p>
<p data-pid="tqSj8DGU">其中：</p>
<ul>
<li data-pid="mY1XQCN0"><code>sections</code> 保存相对稳定的系统提示；</li>
<li data-pid="ZgLFFNa3"><code>contexts</code> 提供会随运行状态变化的环境信息；</li>
<li data-pid="BNF4s-T5"><code>tools</code> 描述当前可用的工具；</li>
<li data-pid="9xk_iDCF"><code>variables</code> 提供提示词渲染需要的变量。</li>
</ul>
<p data-pid="ZfuI-WKm">各插件只负责注册自己的内容，最终由 SystemPrompt 统一生成本轮快照。</p>
<p data-pid="EaCZoEMA">这意味着上下文并不是固定不变的。例如，插件启停、工作目录变化或工具权限调整后，下一次模型调用可以获得最新状态，不需要由 AgentLoop 编写额外的业务分支。</p>
<h3 style="font-weight: 500; font-style: inherit;">3. 避免重复注入动态状态</h3>
<p data-pid="03_Jujqq">动态上下文并不需要每个 step 都重复写入。</p>
<p data-pid="_bCGGseH"><code>RuntimeContextProjection</code> 会比较本轮快照和上一轮快照：</p>
<ul>
<li data-pid="dTQ2-qjZ">如果内容没有变化，就继续沿用已有信息；</li>
<li data-pid="bbu23Z-t">如果内容发生变化，就生成新的上下文消息并写入 Session。</li>
</ul>
<p data-pid="91vc9doZ">这种机制有两个作用。</p>
<p data-pid="zoPD8mBJ">第一，减少重复内容带来的 token 消耗。工作目录和运行环境可能连续多个 step 保持不变，没有必要反复加入会话历史。</p>
<p data-pid="AlGd9TJm">第二，保留状态变化轨迹。当环境发生变化时，新快照会进入 Session，后续可以追踪模型从哪一轮开始看到了新的状态。</p>
<p data-pid="9v-SU_7A">投影状态还可以从已有 Session 中恢复。因此，即使服务重启，系统也能判断某段动态上下文是否已经注入，避免因为进程内状态丢失而重复写入。</p>
<h3 style="font-weight: 500; font-style: inherit;">4. 统一派生会话历史</h3>
<p data-pid="T_QS3lDg">用户消息、模型回复和工具结果不会由控制器临时拼入请求，而是先写入 Session Log，再由 <code>Session.deriveMessages()</code> 派生为模型协议需要的消息列表。</p>
<p data-pid="iSoyk-o2">这使系统能够区分三个层次：</p>
<ul>
<li data-pid="eu_nI505">Log：完整保存原始会话事件；</li>
<li data-pid="AhfnXZpc">Surface：决定当前哪些事件参与模型上下文；</li>
<li data-pid="CmnI0n7m">Messages：最终发送给模型的结构化消息。</li>
</ul>
<p data-pid="5aa8i5ac">例如，工具执行结果先作为 <code>tool/result</code> 写入 Session，下一轮再由 <code>deriveMessages()</code> 转换成模型可以读取的消息。上下文压缩也只调整 Surface，不直接删除原始事件。</p>
<p data-pid="-E0H6G9I">因此，模型输入不是由多个模块临时修改的消息数组，而是由统一的会话记录派生出来的。</p>
<h3 style="font-weight: 500; font-style: inherit;">5. 构建并记录最终请求</h3>
<p data-pid="sl3tcmpK">上下文装配完成后，<code>buildRequest()</code> 会生成最终的模型请求，主要包括：</p>
<ul>
<li data-pid="ixYuPn8-"><code>system</code>：系统提示；</li>
<li data-pid="KtLWEBbm"><code>messages</code>：会话历史和动态上下文；</li>
<li data-pid="DFNV-EZY"><code>tools</code>：当前可用的工具定义；</li>
<li data-pid="Ioyqh8NZ"><code>sessionId</code>：当前会话标识；</li>
<li data-pid="2vAVk5LP"><code>signal</code>：调用控制信号。</li>
</ul>
<p data-pid="hrmW_wLH">同时，系统会记录 <code>request/header</code> 和 <code>request/context</code>，保存本次调用使用的关键上下文信息。</p>
<p data-pid="9eQeRYbp">这样可以回答几个重要问题：</p>
<ul>
<li data-pid="X2p3DZiu">模型当时看到了哪些历史；</li>
<li data-pid="CZAABVm5">使用了哪一版系统提示；</li>
<li data-pid="8t_iHaWU">当时有哪些可用工具；</li>
<li data-pid="saQ8_8Wk">动态环境是否已经发生变化；</li>
<li data-pid="DH5Ke_TC">某次异常是模型推理问题，还是输入上下文问题。</li>
</ul>
<p data-pid="mRQvX3q1">DeepSeek Harness 的上下文管理并不是简单地将内容放进消息列表，而是围绕分层管理、动态装配、变化检测和统一派生建立完整流程。</p>
<p data-pid="PKxIbdE1">其核心链路可以概括为：</p>
<p data-pid="EPI8iPtm">各模块提供上下文 → SystemPrompt 动态装配 → RuntimeContextProjection 检测变化 → Session 派生历史消息 → buildRequest 构建并记录最终请求。</p>
<p data-pid="X0YKAYYv">这样，每段上下文都有明确来源，动态信息不会被无意义地重复发送，模型输入可以从 Session 中恢复和追踪，AgentLoop 也不需要承载复杂的提示词与历史管理逻辑。</p>
<h2 style="font-weight: 500; font-style: inherit;">记忆压缩</h2>
<p data-pid="dsNB2BuQ">DeepSeek Harness 当前的记忆能力主要由 Compaction 提供。</p>
<p data-pid="LFWvoSd7">这里需要控制术语。Compaction 服务于当前会话的连续运行，它会把旧历史折叠成结构化检查点。跨 Session 的用户偏好、项目经验和长期事实存储，目前没有形成完整系统。</p>
<p data-pid="hNkisSRL">因此，我更愿意把它称为「会话记忆」。</p>
<h3 style="font-weight: 500; font-style: inherit;">1. 压缩对象</h3>
<p data-pid="QC-JxdGS">Session 内部存在三个层次：</p>
<ul>
<li data-pid="ycBa8_uy">Log：所有原始事件组成的追加日志；</li>
<li data-pid="qCfBJKyK">Surface：当前参与模型消息派生的事件视图；</li>
<li data-pid="jvQttHWA">Messages：发送给模型的协议消息。</li>
</ul>
<p data-pid="BFg8Zlwu">Compaction 修改的是 surface。</p>
<p data-pid="UCAJoucE">原始事件仍然保留在 Log 中，模型当前看到的历史则由摘要节点替代。这个结构同时满足了两个要求：</p>
<ul>
<li data-pid="Wkm6gkrd">模型输入得到缩短；</li>
<li data-pid="pot3sY_R">原始会话记录没有被覆盖。</li>
</ul>
<p data-pid="ko1qemzy">它比直接删除前 N 条消息安全很多。</p>
<p data-pid="VwKAhN6p">删除历史以后，调试系统无法知道模型之前看过什么。线上出现问题时，只剩下截断后的残缺记录。Compaction 保留原始事件，后续仍可审计压缩范围和摘要来源。</p>
<h3 style="font-weight: 500; font-style: inherit;">2. 触发条件</h3>
<p data-pid="cBCflI7k"><code>BasicCompactionEngine</code> 会通过 <code>tokenMeter.measure(session)</code> 估算当前会话的 token 压力，再与模型的 <code>contextWindow</code> 比较。</p>
<p data-pid="wjOHifKk">自动压缩拥有两个主要触发入口：</p>
<ul>
<li data-pid="wMv0knXY"><code>agent/pre-step</code> 阶段的常规压力检查；</li>
<li data-pid="Gk5b7txM">模型返回上下文溢出错误后的重试处理。</li>
</ul>
<p data-pid="LtB9n0TG">常规检查负责提前处理窗口压力，错误重试负责兜底。</p>
<p data-pid="Wwuadzpi">压缩区域由 <code>selectCompactableRange()</code> 选择。它会优先折叠较旧历史，并保留近期上下文。近期消息通常包含当前修改进度、最新工具结果和下一步操作，保留它们可以减少摘要对短期推理的影响。</p>
<p data-pid="iBa_vtM9">区域选择还需要保护工具调用链。</p>
<p data-pid="GDghhKSB">Assistant 发出的 tool-call 和对应的 tool-result 属于一组完整语义。若压缩边界将它们切开，后续模型可能看到孤立的调用或孤立的结果。部分模型适配器甚至会直接拒绝这种消息结构。</p>
<p data-pid="UU-WE4Zd"><code>validateSurfaceRegion()</code> 会检查压缩区域，避免破坏工具调用与结果的配对关系。</p>
<p data-pid="-lZ2Re3R">这类结构校验比简单的「保留最近 N 条消息」可靠。Agent 历史已经超出普通聊天记录的范畴，消息之间存在协议级关联，切割时必须理解这些关联。</p>
<h3 style="font-weight: 500; font-style: inherit;">3. 摘要生成</h3>
<p data-pid="HJCoBTOu">压缩区域选定后，<code>buildSummarizationInput()</code> 会恢复对应的模型消息，并取回当时的 system 和 tools。<code>summarizeWithLlm()</code> 随后调用 LLM 生成摘要。</p>
<p data-pid="KU3wMmSv">摘要受到固定模板约束，主要包含：</p>
<ul>
<li data-pid="amzfL5CH"><code>Primary Request and Intent</code></li>
<li data-pid="puqjUsET"><code>Files and Code</code></li>
<li data-pid="FmP0biWl"><code>Errors and Fixes</code></li>
<li data-pid="qK7lgmGd"><code>Next Step</code></li>
<li data-pid="FCQK469s"><code>Critical Context</code></li>
</ul>
<p data-pid="SEgF1nXL">结构化模板可以防止摘要退化成普通的对话概述。</p>
<p data-pid="OhSegI69">编程 Agent 的历史里，有用的信息通常集中在几类内容：</p>
<ul>
<li data-pid="NfNO3ymE">用户最初要解决的问题；</li>
<li data-pid="1A7RJ55N">已经读取或修改的文件；</li>
<li data-pid="ZuvW4Vjc">执行过的命令；</li>
<li data-pid="nETeOMJj">失败原因和修复过程；</li>
<li data-pid="GfwrSkyU">当前未完成步骤；</li>
<li data-pid="g60F_bNA">不能违反的环境约束。</li>
</ul>
<p data-pid="gFLu4uY4">如果只要求模型「概括以上对话」，输出往往会省略文件名、错误信息和失败尝试。摘要读起来流畅，后续执行却接不上。</p>
<p data-pid="8-nTKN9R">固定结构会提高这些信息的保留概率，但无法保证语义完整。</p>
<h3 style="font-weight: 500; font-style: inherit;">4. 替换事务</h3>
<p data-pid="WxSs0QW9">摘要生成以后，会被包装进 <code>&lt;compacted-summary&gt;</code>，随后作为新的 <code>user/message</code> 写入 Session，并通过 <code>surfaceOp: replace</code> 替换旧区域。</p>
<p data-pid="dF8F4HY4">参考实现中的主流程如下：</p>
<div class="highlight">
<pre><code class="language-ts"><span class="c1" style="font-style: italic; color: #9196a1;">// compaction-basic/region.ts 的逻辑简化
</span><span class="nx">session</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s1" style="color: #d95350;">'compaction/start'</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span> <span class="p">})</span>

<span class="kr" style="font-weight: 600;">const</span> <span class="nx">summary</span> <span class="o" style="font-weight: 600;">=</span> <span class="k" style="font-weight: 600;">await</span> <span class="nx">summarizeWithLlm</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">input</span><span class="p">,</span> <span class="nx">agent</span><span class="p">,</span> <span class="nx">signal</span><span class="p">)</span>
<span class="kr" style="font-weight: 600;">const</span> <span class="nx">framed</span> <span class="o" style="font-weight: 600;">=</span> <span class="nx">frameSummary</span><span class="p">(</span><span class="nx">summary</span><span class="p">.</span><span class="nx">summary</span><span class="p">)</span>

<span class="kr" style="font-weight: 600;">const</span> <span class="nx">summaryEvent</span> <span class="o" style="font-weight: 600;">=</span> <span class="nx">session</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s1" style="color: #d95350;">'compaction/summary'</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span> <span class="p">})</span>

<span class="nx">session</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span>
  <span class="s1" style="color: #d95350;">'user/message'</span><span class="p">,</span>
  <span class="nx">createUserMessage</span><span class="p">({</span>
    <span class="nx">content</span>: <span class="kt" style="font-weight: 600; color: #09408e;">framed</span><span class="p">,</span>
    <span class="nx">source</span>: <span class="kt" style="font-weight: 600; color: #09408e;">compactCheckpointSource</span><span class="p">(...),</span>
  <span class="p">}),</span>
  <span class="p">{</span>
    <span class="nx">surfaceOp</span><span class="o" style="font-weight: 600;">:</span> <span class="p">{</span> <span class="nx">op</span><span class="o" style="font-weight: 600;">:</span> <span class="s1" style="color: #d95350;">'replace'</span><span class="p">,</span> <span class="nx">start</span><span class="p">,</span> <span class="nx">end</span> <span class="p">},</span>
    <span class="nx">sourceEventSeqs</span><span class="o" style="font-weight: 600;">:</span> <span class="p">[</span><span class="nx">startEvent</span><span class="p">.</span><span class="nx">seq</span><span class="p">,</span> <span class="nx">summaryEvent</span><span class="p">.</span><span class="nx">seq</span><span class="p">,</span> <span class="p">...</span><span class="nx">shadowedSeqs</span><span class="p">],</span>
  <span class="p">},</span>
<span class="p">)</span>

<span class="nx">session</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s1" style="color: #d95350;">'compaction/end'</span><span class="p">,</span> <span class="p">{</span> <span class="p">...</span> <span class="p">})</span>
</code></pre>
</div>
<p data-pid="oDC-ylQr">整个过程会记录：</p>
<ul>
<li data-pid="U-aEeBwl">压缩开始；</li>
<li data-pid="lErGaeYK">摘要内容；</li>
<li data-pid="yYYEz4ey">replacement message；</li>
<li data-pid="lcxkb6F-">被覆盖的事件序列；</li>
<li data-pid="7H6ZondC">压缩结束。</li>
</ul>
<p data-pid="DOHBd6kw"><code>sourceEventSeqs</code> 建立了检查点与原始历史之间的关联。排查错误摘要时，开发者可以反查它覆盖了哪些事件。</p>
<p data-pid="u5i0F0Xk"><code>Session.deriveMessages()</code> 检测到 <code>replaceGeneration</code> 发生变化后，会重建消息缓存。后续模型看到的是新检查点和保留下来的近期历史。</p>
<p data-pid="8W5Xh3G1">当前的设计保证了事务的完整性。压缩失败时，旧 surface 仍然可以继续使用。摘要生成成功且提交完成后，模型视图才发生变化。</p>
<h3 style="font-weight: 500; font-style: inherit;">5. 有损风险</h3>
<p data-pid="EbSf2fmc">Compaction 无法绕开有损问题。</p>
<p data-pid="PmrNGKPj">假设旧历史中出现过一条约束：测试环境使用特定环境变量，变量为空时必须跳过某项操作。摘要模型漏掉这条信息以后，后续 Agent 可能执行错误命令。</p>
<p data-pid="Oh8s4BOJ">原始事件虽然还在 Log 中，当前模型无法自动访问。对推理过程而言，这条信息已经离开热上下文。</p>
<p data-pid="o7DmBcyd">因此，Session Log 的完整性解决了审计问题，没有完全解决信息恢复问题。</p>
<p data-pid="3Zr5ws-X">我会在现有 Compaction 上增加一层历史召回能力。摘要中保留关键事件引用，模型缺少细节时，可以调用类似 <code>recall_history</code> 的工具读取旧历史。</p>
<p data-pid="c2MrZod8">召回参数可以采用结构化维度：</p>
<ul>
<li data-pid="KpD1eV5V">turn 范围；</li>
<li data-pid="FszCvF2t">step 范围；</li>
<li data-pid="v_YuQSCX">event seq；</li>
<li data-pid="M-uEjIcR">tool call id；</li>
<li data-pid="utyaSRh1">文件路径；</li>
<li data-pid="HmUxQXKs">compaction id。</li>
</ul>
<p data-pid="lt3jM4DU">这种方式比单纯的语义搜索更稳定一些。Session 已经拥有完整事件顺序和来源关系，应优先利用现有结构。</p>
<p data-pid="RzgNrWMq">召回结果也要写成 <code>tool/result</code>。这样系统可以知道模型恢复了哪段历史，以及这段历史怎样影响后续决策。</p>
<h3 style="font-weight: 500; font-style: inherit;">6. 调度延迟</h3>
<p data-pid="mGNvns0D">当前压缩会调用一次 LLM。压缩发生在主流程上时，用户需要等待摘要完成。</p>
<p data-pid="uK38qW36">会话较短时，这个延迟可以接受。长会话的摘要输入很大，调用时间会明显增加。编程 Agent 还可能连续执行多个工具，压缩恰好卡在下一步之前，交互体验会出现停顿。</p>
<p data-pid="P8yYqH2p">可以引入两级水位：</p>
<ul>
<li data-pid="dSmiaE4t">接近窗口上限时，后台生成候选 checkpoint；</li>
<li data-pid="pzOUr8Tu">到达硬上限时，提交已有 checkpoint；</li>
<li data-pid="m4u4YITb">候选结果过期时放弃；</li>
<li data-pid="QgDYfGIm">没有可用结果时回退到同步压缩。</li>
</ul>
<p data-pid="yvTt5-Qy">异步压缩的难点集中在一致性。</p>
<p data-pid="MzgMb-Og">生成摘要期间，Session 还会继续追加事件。提交前必须校验它对应的 surface generation，确保被压缩区域没有发生冲突。检查点过期后不能强行替换，否则可能覆盖新的工具结果或用户输入。</p>
<p data-pid="wuZWyAhF">当前已有的压缩重入保护、surface generation 和事务事件，为异步化提供了基础。早期保持同步更稳，长任务比例上升以后，再逐步引入后台 checkpoint。</p>
<h2 style="font-weight: 500; font-style: inherit;">知识获取</h2>
<p data-pid="3Zni8Gfa">DeepSeek Harness 没有传统意义上的统一向量知识库。</p>
<p data-pid="6knaQaz1">它的知识入口由三部分组成：</p>
<ul>
<li data-pid="mZwO47nm">文件路径引用；</li>
<li data-pid="h7CAILu8">Web 搜索与抓取；</li>
<li data-pid="mZByYlxr">工具结果写入 Session。</li>
</ul>
<p data-pid="ITb0RONj">这种组合适合编程 Agent。代码仓库里的信息具有路径、符号、定义和引用关系。统一切块并写入向量数据库，会损失一部分结构信息，还要承担索引更新成本。</p>
<h3 style="font-weight: 500; font-style: inherit;">1. 文件引用</h3>
<p data-pid="UVWk65o1"><code>file-reference-local</code> 提供工作区文件和目录候选。</p>
<p data-pid="LwC_-47d">它会根据 <code>session.header.cwd</code> 创建 <code>WorkspaceFileSearch</code>，然后扫描当前工作区。结果只包含：</p>
<ul>
<li data-pid="_XfNjtTK"><code>path</code></li>
<li data-pid="A0ittOcn"><code>kind</code></li>
</ul>
<p data-pid="R5vJfsfF">用户在前端选择文件后，输入框里会插入 <code>@path</code> 或 <code>@"path with spaces"</code>。</p>
<p data-pid="7akSAKmK">文件内容不会在这个阶段进入模型。</p>
<p data-pid="0Rg1X9nJ">系统提示会告诉模型，<code>@</code> token 表示工作区路径。模型需要读取内容时，应调用 <code>read</code> 工具。</p>
<p data-pid="UWdY7um2">这个分层处理了两个不同问题：</p>
<ul>
<li data-pid="ndMMFBs0"><code>file-reference-local</code> 负责找到路径；</li>
<li data-pid="2uUP6r2f"><code>read</code> 工具负责读取内容。</li>
</ul>
<p data-pid="O9YeUKjB">如果选择路径时就把整个文件自动塞进消息，系统会遇到几类麻烦。</p>
<p data-pid="f0Jb38WJ">第一，大文件会快速占满上下文。</p>
<p data-pid="G3e59NMN">第二，用户无法知道系统展开了多少内容。</p>
<p data-pid="jh80ovsF">第三，读取行为缺少独立记录。</p>
<p data-pid="XtGR3-zr">第四，文件权限和读取范围可能绕过工具 guard。</p>
<p data-pid="C8YbYvPF">第五，文件修改以后，很难判断模型当时看到的是哪个版本。</p>
<p data-pid="jVmlGDOA">路径引用保留了用户意图，工具调用保留了实际读取行为。两类信息都会进入 Session，审计链更完整。</p>
<p data-pid="hWkae2qB"><code>WorkspaceFileSearch</code> 还会控制目录边界、排除路径、最大条目和候选数量，并拒绝通过 <code>..</code> 或符号链接跳出 workspace。</p>
<p data-pid="-t-NIkd4">这些限制属于安全边界。文件路径来自用户输入，也可能由模型生成，不能默认可信。</p>
<h3 style="font-weight: 500; font-style: inherit;">2. 精确检索</h3>
<p data-pid="zi6Vf8X1">文件路径补全只能解决「大致知道文件在哪里」的问题。</p>
<p data-pid="KZXBexpk">大型仓库中，开发者和模型经常需要回答另一类问题：</p>
<ul>
<li data-pid="RnYecD6s">接口定义位于哪个文件；</li>
<li data-pid="VVcMC8wF">某个符号有哪些引用；</li>
<li data-pid="LsAIvOSw">哪些实现依赖当前类型；</li>
<li data-pid="uJzWfnix">一次修改会影响哪些调用点；</li>
<li data-pid="ejCKmWP0">编译诊断关联到哪些符号。</li>
</ul>
<p data-pid="ZadaIexL">这些查询适合交给 LSP。</p>
<p data-pid="54-7xxBH">项目已经存在 <code>packages/lsp/</code>，可以继续扩展为代码检索能力。优先提供：</p>
<ul>
<li data-pid="Vxe0Zj_E">Go to Definition；</li>
<li data-pid="u4ltMKDF">Find References；</li>
<li data-pid="3SdAFfqC">Workspace Symbols；</li>
<li data-pid="v_B0WNlg">Diagnostics；</li>
<li data-pid="5u28Ji9V">调用关系。</li>
</ul>
<p data-pid="nssrfBB2">向量检索依赖语义相似度。两个函数命名和注释很接近，并不能证明它们具有依赖关系。LSP 返回的是语言服务器维护的定义和引用关系，适合代码修改场景。</p>
<p data-pid="Pd3VaVyG">可以把仓库检索分成四层：</p>
<ol>
<li data-pid="82PUHsGQ">已知路径，直接读取文件；</li>
<li data-pid="-RZKY-5O">已知文本，使用 <code>grep</code>；</li>
<li data-pid="WBFMexCi">已知符号，使用 LSP；</li>
<li data-pid="BeIPUWQa">只有概念描述时，再考虑语义检索。</li>
</ol>
<p data-pid="e6FXxjEs">这个逻辑会优先消耗确定性信息。代码仓库已经提供路径和符号结构，没有必要先把问题转换成向量相似度。</p>
<p data-pid="OPMQfTLY">LSP 也有运行成本。语言服务器需要启动和预热，多语言仓库要维护多个进程，大型 monorepo 的全局引用查询可能很慢。它适合作为工具按需调用，不适合把所有结果常驻上下文。</p>
<p data-pid="sZAaCgGt">查询结果仍然要通过 ToolRuntime 写入 Session。这样模型读取过哪些定义、哪些引用，可以在后续回放中找到。</p>
<h3 style="font-weight: 500; font-style: inherit;">3. Web 检索</h3>
<p data-pid="nZ8WuNim">外部知识通过 <code>web_search</code> 和 <code>web_fetch</code> 进入模型。</p>
<p data-pid="oLWBQq9_">整体分为三层：</p>
<ul>
<li data-pid="Brje1A-X"><code>WebRuntime</code> 定义统一能力；</li>
<li data-pid="OcfNNWWT">search/fetch provider 负责具体请求；</li>
<li data-pid="8KrooT_R"><code>tool-web</code> 把能力注册成模型可见工具。</li>
</ul>
<p data-pid="i4EFcx3j">这种分层使工具 schema 与供应商解耦。模型只需要理解 <code>web_search</code> 和 <code>web_fetch</code>。底层可以选择 DeepSeek、Exa、Perplexity 或 HTTP fetch provider。</p>
<p data-pid="qH9a0PZA">Provider 选择规则保持严格：</p>
<ul>
<li data-pid="a_m7wBWJ">配置了 provider id，使用指定 provider；</li>
<li data-pid="W3mohG3w">未配置时，系统要求只有一个可用 provider；</li>
<li data-pid="2pwfGnET">多个 provider 同时可用时直接报错。</li>
</ul>
<p data-pid="QGXoBcxO">这可以避免插件加载顺序影响线上行为。</p>
<p data-pid="8ExFuhVE">Web 搜索结果会被格式化成模型可见文本，并带有外部内容不可信和引用 URL 的提示。Web fetch 会把 HTML 转换成 Markdown，普通文本则直接进入结果。</p>
<p data-pid="iPs_7DhE">提示只能影响模型行为，网络边界还要由 provider 控制。HTTP fetch provider 已经包含多项限制：</p>
<ul>
<li data-pid="n88b_gVf">只允许 HTTP(S)；</li>
<li data-pid="XJ6odxWr">拒绝 URL credentials；</li>
<li data-pid="keofVZlT">拒绝私网和非公网地址；</li>
<li data-pid="YuCc0-_J">控制同源重定向；</li>
<li data-pid="DxuIBNPR">限制重定向次数；</li>
<li data-pid="qRYiGZ8O">限制响应字节数；</li>
<li data-pid="I7hHqWHz">限制正文长度；</li>
<li data-pid="Xp7KWCPK">固定经过校验的 DNS 地址；</li>
<li data-pid="3SGk3Jfu">timeout 由部署配置管理。</li>
</ul>
<p data-pid="KecUMm0Q">模型生成的 URL也属于不可信输入。网页中的 Prompt Injection 可能诱导模型请求内网地址、云元数据地址或带有凭证的 URL。网络策略需要在模型之外执行。</p>
<h3 style="font-weight: 500; font-style: inherit;">4. 工具入库</h3>
<p data-pid="_xVJZrCj">文件读取、Web 搜索和 Web 抓取获得的信息，都通过同一条工具链进入模型。</p>
<p data-pid="shNKpQa9">流程可以概括为：</p>
<ol>
<li data-pid="jH3mCR8Q">ToolRuntime 把工具 schema 注册到 SystemPrompt；</li>
<li data-pid="daKDvxxC">模型返回 tool-call；</li>
<li data-pid="4yE2Ibqg">AgentLoop 调用 <code>executeToolCalls()</code>；</li>
<li data-pid="5KDj7Jtd">Session 写入 <code>tool/call</code>；</li>
<li data-pid="1u9a0Gmz">ToolRuntime 执行工具；</li>
<li data-pid="lvRGQ_EC">Session 写入 <code>tool/result</code>；</li>
<li data-pid="GZEqR6Gv">下一轮 <code>deriveMessages()</code> 把结果投影给模型。</li>
</ol>
<p data-pid="XjzIWmrO">参考实现中的调用路径如下：</p>
<div class="highlight">
<pre><code class="language-ts"><span class="c1" style="font-style: italic; color: #9196a1;">// 模型返回 tool-call
</span><span class="kr" style="font-weight: 600;">const</span> <span class="nx">toolCalls</span> <span class="o" style="font-weight: 600;">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">content</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">block</span> <span class="o" style="font-weight: 600;">=&gt;</span> <span class="nx">block</span><span class="p">.</span><span class="kr" style="font-weight: 600;">type</span> <span class="o" style="font-weight: 600;">===</span> <span class="s1" style="color: #d95350;">'tool-call'</span><span class="p">)</span>

<span class="c1" style="font-style: italic; color: #9196a1;">// Agent 执行工具
</span><span class="kr" style="font-weight: 600;">const</span> <span class="p">{</span> <span class="nx">concluded</span> <span class="p">}</span> <span class="o" style="font-weight: 600;">=</span> <span class="k" style="font-weight: 600;">await</span> <span class="nx">executeToolCalls</span><span class="p">(</span>
  <span class="k" style="font-weight: 600;">this</span><span class="p">.</span><span class="nx">loopCtx</span><span class="p">,</span>
  <span class="nx">turn</span><span class="p">,</span>
  <span class="nx">step</span><span class="p">,</span>
  <span class="nx">toolCalls</span><span class="p">,</span>
  <span class="nx">signal</span><span class="p">,</span>
  <span class="nx">context</span> <span class="o" style="font-weight: 600;">=&gt;</span> <span class="k" style="font-weight: 600;">this</span><span class="p">.</span><span class="nx">inbox</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="s1" style="color: #d95350;">'next-step'</span><span class="p">,</span> <span class="k" style="font-weight: 600;">this</span><span class="p">.</span><span class="nx">inbox</span><span class="p">.</span><span class="nx">nextStep</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="mi" style="color: #1772f6;">0</span><span class="p">,</span> <span class="p">[</span><span class="nx">context</span><span class="p">]),</span>
<span class="p">)</span>

<span class="c1" style="font-style: italic; color: #9196a1;">// 工具结果进入 session log，下一 step 被 deriveMessages() 投影给模型
</span></code></pre>
</div>
<p data-pid="NtNcofEY">工具结果不会只停留在当前执行栈，也不会直接修改临时 messages 数组。</p>
<p data-pid="Ax7Auz5q">这对 Web 信息尤其关键。搜索结果和网页内容会随时间变化。如果系统只记录搜索参数，重放时重新请求一次，模型得到的内容可能已经不同。</p>
<p data-pid="L7z0iJ-U">文件读取也一样。Agent 可能在读取后修改文件。历史推理需要保留当时真正发送给模型的内容。</p>
<p data-pid="c4pcStOq">当然，完整保存所有工具输出会增加 Session 体积。工程上可以保存模型实际看到的渲染结果，同时在 meta 中记录：</p>
<ul>
<li data-pid="9WnIPTWN">是否截断；</li>
<li data-pid="4UGG5Maw">原始内容长度；</li>
<li data-pid="LWXGzSlV">来源路径或 URL；</li>
<li data-pid="mNdu6kpE">content type；</li>
<li data-pid="uFAqNPwg">工具调用参数；</li>
<li data-pid="qsWaiZ8f">provider 信息。</li>
</ul>
<p data-pid="jBYSgmdP">模型没有看到的正文，无须全部伪装成上下文历史。回放能力关注的是当时的模型输入。</p>
<h2 style="font-weight: 500; font-style: inherit;">三者关系</h2>
<p data-pid="FUcSeOHW">上下文、记忆和知识获取分别控制模型输入的三个阶段。</p>
<ul>
<li data-pid="xcwQICsc">上下文决定当前输入： SystemPrompt 收集系统约束、动态状态和工具 schema。<code>preStep()</code> 判断动态内容是否变化，<code>buildRequest()</code> 构建最终模型请求。它解决的是「这一轮应该携带什么」。</li>
<li data-pid="CiEh5b89">记忆控制历史体积： Compaction 观察 token 压力，选择旧历史区域，生成结构化摘要，再替换当前 surface。它解决的是「历史太长以后保留什么」。</li>
<li data-pid="AGBLgN4N">知识补充外部信息：文件引用帮助定位路径，读取工具获得仓库内容，Web 工具获取外部信息，未来还可以用 LSP 提供符号级查询。它解决的是「当前会话缺少的信息从哪里获得」。</li>
</ul>
<p data-pid="gxdjQl2i">三者最终都回到 Session。</p>
<p data-pid="TWppKY0B">动态上下文通过 <code>user/message</code> 进入日志，工具信息通过 <code>tool/result</code> 进入日志，压缩通过 <code>compaction/*</code> 事件和 replacement message 改写 surface。</p>
<p data-pid="6ZIh_YDu">所以，DeepSeek Harness 的数据主线可以压缩为：</p>
<p data-pid="12oL3cl6">装配上下文，记录事件，派生消息，调用模型，执行工具，写回结果，必要时折叠 surface。</p>
<p data-pid="Oq91MSYk">插件可以增加新的上下文来源、新的工具和新的压缩策略，但不能绕过这条主线。</p>
<p data-pid="YvZljiWk">直接向 <code>GenerateOptions.messages</code> 塞内容，会产生无法回放的隐形上下文。</p>
<p data-pid="F8wOkw-6">检索模块绕过 ToolRuntime，会失去权限、审计和结果记录。</p>
<p data-pid="IWCMYGUD">压缩模块覆盖原始事件，会破坏历史追踪。</p>
<p data-pid="xdnpu4MF">这几条边界比具体使用哪种模型、搜索服务或向量数据库更影响系统的维护成本。</p>
<h2 style="font-weight: 500; font-style: inherit;">小结</h2>
<p data-pid="RHY4GYaJ">DeepSeek Harness 的三个模块可以归纳为三句话：</p>
<ul>
<li data-pid="EDKSSzOw">上下文管理负责装配当前输入。</li>
<li data-pid="6cl53pXr">Compaction 负责压缩当前会话历史。</li>
<li data-pid="aLrrUR15">文件和 Web 工具负责补充当前缺失的信息。</li>
</ul>
<p data-pid="Tru6Pjsy">三者共享 Session Log：</p>
<ul>
<li data-pid="Kr_tKYLj">模型看到的动态状态要进入日志；</li>
<li data-pid="xWGS9G8Y">模型获得的工具结果要进入日志；</li>
<li data-pid="rSsYcT0d">压缩只调整 surface，原始事件继续保留；</li>
<li data-pid="G3j7W_Pr">后续模型输入由 <code>Session.deriveMessages()</code> 统一派生。</li>
</ul>
<p data-pid="lkDcl9dP">现有设计的优势集中在可回放、可审计和模块边界。主要缺口也很具体：Compaction 存在语义损失，压缩调用会阻塞主流程，文件路径检索缺少符号级能力，跨 Session 长期记忆尚未形成独立体系。</p>
<p data-pid="LrBqFP7v">Session Log 继续保存事实，surface 控制模型当前看到的历史，工具负责按需获取信息。三个模块沿着这条数据链协作，系统才能在上下文窗口、运行延迟和信息完整性之间保持可控。</p>
<p data-pid="cDtiwkJV">以上。</p>
</div>
</div>
</div>
</div>
<div class="Reward" style="color: #81858f;"></div>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/09/an-analysis-of-deepseek-harnesss-context-management-memory-and-knowledge-base/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>DeepSeek Harness 和 Pi 的 Agent Loop 循环结束判断架构细节解析</title>
		<link>https://www.phppan.com/2026/08/deepseek-harness-and-pi-agent-loop-end/</link>
		<comments>https://www.phppan.com/2026/08/deepseek-harness-and-pi-agent-loop-end/#comments</comments>
		<pubDate>Sun, 30 Aug 2026 05:26:34 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[Agent Loop]]></category>
		<category><![CDATA[DeepSeek]]></category>
		<category><![CDATA[DeepSeekHarness]]></category>
		<category><![CDATA[harness]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2533</guid>
		<description><![CDATA[本篇文章代码基线采用 DeepSeek Harness 版本 0.1.2-alpha.1，提交时间为 2026 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">本篇文章代码基线采用 DeepSeek Harness 版本 <code>0.1.2-alpha.1</code>，提交时间为 2026 年 8 月 28 日；Pi 采用 <code>853a80d2</code>。这里的 Pi 指 <code>pi-agent-core</code> 中的官方 Agent Loop。</p>
<p data-tool="mdnice编辑器">就循环究竟在什么时刻结束这个问题，很多 Agent 框架把结束判断写成一个条件：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs"><span class="hljs-keyword">while</span> (hasToolCalls) {
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p data-tool="mdnice编辑器">在 Demo 中这种循环也够用。进入生产环境后，你会发现会很多问题：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>模型没有调用工具，是否代表任务完成？</section>
</li>
<li>
<section>工具执行完了，模型是否还需要解释结果？</section>
</li>
<li>
<section>用户在工具执行期间发来 steering，应该进入当前轮还是下一轮？</section>
</li>
<li>
<section>输出触达 token 上限后，工具参数还能不能执行？</section>
</li>
<li>
<section>某个工具宣布任务完成，其他并行工具怎么办？</section>
</li>
<li>
<section>Agent 进入 idle 后，Goal Driver 能不能再次唤醒它？</section>
</li>
<li>
<section>Plan mode 退出，是结束当前循环，还是切换下一次请求的策略？</section>
</li>
<li>
<section>进程崩溃后，日志里尚未闭合的 turn 应当如何解释？</section>
</li>
</ul>
<p data-tool="mdnice编辑器">DeepSeek Harness 和 Pi 的工程路线不同，或者说 DeepSeek Harness 更复杂，也更能满足复杂场景的需求。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 将结束拆成多层状态，并把事件日志作为重建依据。Pi 保留了更扁平的循环结构，把 steering、follow-up 和 post-turn stop 暴露成回调。前者适合多插件、持久恢复和长期任务；后者适合轻量嵌入，控制路径也更容易读懂。</p>
<p data-tool="mdnice编辑器">两种实现都能工作。它们承担的系统复杂度不同，结束语义自然不会相同。</p>
<h1 data-tool="mdnice编辑器"><span class="content">五层边界</span></h1>
<p data-tool="mdnice编辑器">讨论 Agent Loop 时，我们需要先定义好「结束」，否则代码里的 <code>completed</code>、<code>turn_end</code>、<code>agent_end</code> 很容易被混为一谈。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 实际存在五层边界。</p>
<p data-tool="mdnice编辑器">第一层是模型请求结束。</p>
<p data-tool="mdnice编辑器">Provider 流式输出完成，可能给出正常停止、工具调用、最大 token、错误或取消。这里的判断逻辑是：当前 HTTP 请求或模型流结束了吗？</p>
<p data-tool="mdnice编辑器">第二层是 step 结束。</p>
<p data-tool="mdnice编辑器">一个 step 包含一次模型调用，以及该响应发起的一整批工具执行。工具执行结束后，step 才算关闭。模型若调用了普通工具，系统通常还欠一次模型回访，因此 step 结束不代表 turn 结束。</p>
<p data-tool="mdnice编辑器">第三层是 turn 结束。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 中，一个 turn 可以包含多个 step，大概如下：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">用户输入
→ 模型调用
→ 工具批次
→ 模型回访
→ 工具批次
→ 模型最终输出
</code></pre>
<p data-tool="mdnice编辑器">以上为一个 turn。turn 结束需要同时满足两项条件：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>模型不再欠一次回复；</section>
</li>
<li>
<section><code>next-step</code> inbox 没有待处理输入。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">第四层是 driver activity 结束。</p>
<p data-tool="mdnice编辑器">一个 driver 可以连续处理多个 turn。当前 turn 关闭后，如果 inbox 里还有普通 follow-up，driver 会直接打开下一 turn。只有队列耗尽，driver 才回到 idle。</p>
<p data-tool="mdnice编辑器">第五层是长期工作流结束。</p>
<p data-tool="mdnice编辑器">Goal Driver 可以监听 idle，检查持久 Goal 状态，再调用 <code>followup()</code> 开启新 turn。Plan mode 也可能跨越多个 turn 持续生效。因此，idle 只描述当前 activity；它无法回答长期目标是否完成。</p>
<p data-tool="mdnice编辑器">Pi 的层级少一些。一次 assistant 响应与随后执行的工具批次，会对应一个 <code>turn_end</code>。工具回访模型时，Pi 再开启下一次 turn。内部队列耗尽后发出 <code>agent_end</code>，当前 invocation 至此结束。</p>
<p data-tool="mdnice编辑器">从语义上看：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>DeepSeek Harness 的 step 接近 Pi 的 turn；</section>
</li>
<li>
<section>DeepSeek Harness 的一次 driver activity 接近 Pi 的一次 Agent Loop invocation；</section>
</li>
<li>
<section>DeepSeek Harness 的 Goal Round 位于 driver activity 外层；</section>
</li>
<li>
<section>Pi 的长期 Goal 通常由宿主或扩展继续调度。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">术语相似，粒度相差一层。如果日志消费端忽略这一点，统计出的 turn 数、工具往返次数和任务完成率都会失真。</p>
<h1 data-tool="mdnice编辑器"><span class="content">驱动收敛</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 的入口：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs"><span class="hljs-keyword">while</span> (<span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>.turn()) {
}
</code></pre>
<p data-tool="mdnice编辑器"><code>turn()</code> 返回 <code>true</code>，driver 继续处理下一 turn；返回 <code>false</code>，driver 收敛；抛出异常时，异常会被 driver 边界容纳，随后进入 idle。</p>
<p data-tool="mdnice编辑器">短代码背后有一套完整的 phase 状态机：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">idle
maintenance
running
</code></pre>
<p data-tool="mdnice编辑器"><code>running</code> 保存：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>当前 turn；</section>
</li>
<li>
<section>当前 step；</section>
</li>
<li>
<section><code>AbortController</code>；</section>
</li>
<li>
<section><code>wakeRequested</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><code>maintenance</code> 同样持有取消信号和唤醒锁存，只是对外状态仍显示为 idle。这种设计解决了一个经常被忽略的竞态：Agent 处于维护任务时收到新消息，新消息不能立即启动第二个 driver，也不能静默丢失。系统先将唤醒意图锁存，维护结束后检查 inbox，再决定是否重启。</p>
<p data-tool="mdnice编辑器">取消路径也有类似处理。</p>
<p data-tool="mdnice编辑器">如果一个 waking input 到达时，现有 activity 已经被 abort，这条消息会被重分类到 <code>next-turn</code>。它不能加入一个正在收敛的旧 turn。<code>send()</code> 在插入 inbox 前读取 aborted 状态，避免 inbox observer 触发重入取消后改变分类结果。</p>
<p data-tool="mdnice编辑器">这段细节很像传统并发状态机，而不是常见的聊天循环。它处理的是输入归属问题：一条消息必须属于当前 step、下一 step 或下一 turn，重入事件不能改变已经作出的边界判断。</p>
<p data-tool="mdnice编辑器"><code>whenIdle()</code> 也没有简单等待某个固定 Promise。它反复比较 <code>activityDone</code>：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs"><span class="hljs-keyword">do</span> {
  <span class="hljs-keyword">await</span> (activity = <span class="hljs-keyword">this</span>.activityDone)
} <span class="hljs-keyword">while</span> (activity !== <span class="hljs-keyword">this</span>.activityDone)
</code></pre>
<p data-tool="mdnice编辑器">等待期间如果 activity 被新的 driver 替换，调用方会继续等待。否则 <code>whenIdle()</code> 可能在旧 driver 结束、新 driver 已经启动的窗口里提前返回。</p>
<p data-tool="mdnice编辑器">在多插件环境中，idle 是一个可观察状态，也是一种同步承诺。这个承诺需要覆盖重启竞态，不能只看某次异步任务是否 resolve。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Turn 状态机</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 在领取输入之前写入 <code>turn/start</code>。</p>
<p data-tool="mdnice编辑器">pre-step 可能拒绝输入，系统提示组装可能抛错，首步消息也可能被插件改写为空。无论发生哪种情况，日志中都存在一个完整 turn，并由唯一的 <code>turn/end</code> 收口。</p>
<p data-tool="mdnice编辑器">turn 内部维护两个变量：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>turnEnds</code>：当前已知的结束原因；</section>
</li>
<li>
<section><code>target</code>：本轮 pre-step 应领取 <code>next-turn</code> 还是 <code>next-step</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">第一个 step 从 <code>next-turn</code> 开始。后续 step 转向 <code>next-step</code>。普通用户 prompt 因此拥有独立 turn，工具附加上下文、运行时 context 和 steering 则可以进入当前 turn 的下一 step。</p>
<p data-tool="mdnice编辑器">一次循环大致经历以下过程：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">创建 step 编号
→ 从 inbox 领取消息
→ 组装系统提示和运行时 context
→ 执行 agent/pre-step
→ 记录 step/start
→ 写入 user/message
→ 调用模型并执行工具
→ 记录 step/end
→ 判断是否继续
</code></pre>
<p data-tool="mdnice编辑器">如果 <code>agent/pre-step</code> 返回 <code>reject</code>，turn 以 <code>blocked</code> 结束，模型不会被调用。</p>
<p data-tool="mdnice编辑器">如果首个 step 的消息为空，turn 以 <code>completed</code> 结束，也不会消耗模型请求。这覆盖了一个边缘场景：wakeup 已经打开 turn，随后消息被移除，或者 pre-step 插件把消息改写为空。边界既然已经建立，日志仍需闭合。</p>
<p data-tool="mdnice编辑器">如果某个 step 已经产生结束结果，但下一次 pre-step 没有消息，循环退出。若仍有消息，就继续执行。</p>
<p data-tool="mdnice编辑器">这里需要留意 <code>turnEnds</code> 与 <code>stepEnd</code> 的关系。<code>step()</code> 可以返回：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>completed</code>；</section>
</li>
<li>
<section><code>max-tokens</code>；</section>
</li>
<li>
<section><code>null</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><code>null</code> 表示工具执行完毕，模型还欠一次回复。它是继续循环的协议状态，和错误、未知结果没有关系。</p>
<p data-tool="mdnice编辑器">turn 的结束判断由「模型债务」与「消息债务」共同决定。模型债务来自 tool call，消息债务来自 inbox。只检查其中一项都会出错。</p>
<h1 data-tool="mdnice编辑器"><span class="content">停止钩子</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 在 turn 准备收口时触发 <code>agent/turn-stopping</code>。</p>
<p data-tool="mdnice编辑器">这个扩展点没有返回 <code>true</code> 或 <code>false</code>。插件如果希望 turn 继续，需要向 <code>next-step</code> 写入消息。hook 执行完成后，核心循环重新检查 inbox：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">turn 已经可以结束
→ next-step 为空
→ 触发 agent/turn-stopping
→ 再次检查 next-step
→ 为空则关闭
→ 非空则进入下一 step
</code></pre>
<p data-tool="mdnice编辑器">这是数据驱动的方式。</p>
<p data-tool="mdnice编辑器">多个插件都能监听 stopping。如果采用 <code>shouldContinue(): boolean</code>，组合规则会立刻变得棘手：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>任意插件返回 true 就继续？</section>
</li>
<li>
<section>所有插件都返回 true 才继续？</section>
</li>
<li>
<section>后执行的插件能否覆盖先执行的结果？</section>
</li>
<li>
<section>插件决定继续，却没有提供下一步输入，模型该看到什么？</section>
</li>
<li>
<section>插件决定停止，但另一个插件已经排入 steering，谁的优先级更高？</section>
</li>
</ul>
<p data-tool="mdnice编辑器">DeepSeek Harness 用 inbox 消除了大部分布尔值合并问题（这个设计和 Golang 的竞态处理逻辑类似，使用类似消息队列来处理竞态）。继续执行必须产生一条可消费的数据。最终判定依赖队列状态，而非插件调用顺序。</p>
<p data-tool="mdnice编辑器">当然，这也是需要代价的。</p>
<p data-tool="mdnice编辑器">插件作者需要理解 inbox target、turn 边界和 wakeup 语义。一个简单的「再跑一次」操作，需要构造 <code>UserMessage</code> 并写入正确队列。框架学习成本高于直接回调。</p>
<p data-tool="mdnice编辑器">这种成本在单体应用里可能显得繁琐。进入可恢复、多插件、可审计系统后，显式消息通常比隐式布尔控制更可靠。日志和调试工具能看到「为什么继续」，而不是只看到某个 hook 曾经返回 true。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Step 债务</span></h1>
<p data-tool="mdnice编辑器"><code>step()</code> 最关键的职责，是判断模型是否还欠一次回复。</p>
<p data-tool="mdnice编辑器">模型流结束后，<code>BlockAssembler</code> 给出 finish 状态。如果 finish 是 error 或 aborted，系统先触发 <code>agent/request-error</code> waterfall。插件可以选择 retry。没有 retry 动作时，代码抛出结构化 <code>LlmError</code>。</p>
<p data-tool="mdnice编辑器">正常组装出 assistant message 后，判断顺序如下：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>finish 为 <code>max-tokens</code>，返回 <code>max-tokens</code>；</section>
</li>
<li>
<section>没有 tool call，返回 <code>completed</code>；</section>
</li>
<li>
<section>存在 tool call，执行整批工具；</section>
</li>
<li>
<section>工具批次声明 concluded，返回 <code>completed</code>；</section>
</li>
<li>
<section>普通工具批次返回 <code>null</code>。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">顺序不能随便改。</p>
<p data-tool="mdnice编辑器">最大 token 状态在工具解析之前返回，因此响应中即使出现 tool call，也不会被执行。截断输出可能包含不完整的 JSON 参数。某些流式解析器会尝试补全 JSON，补全后甚至能通过 schema 验证，但语义字段可能已经缺失。执行这类调用会产生真实副作用。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 的选择偏保守：整轮以 <code>max-tokens</code> 收口，等待外部策略决定是否继续。</p>
<p data-tool="mdnice编辑器">Pi 采用另一种处理。<code>stopReason === "length"</code> 且消息包含工具调用时，它会给所有工具生成错误结果，说明参数可能被截断，没有实际执行，然后把这些结果交回模型。模型有机会在下一 turn 重发完整调用。</p>
<p data-tool="mdnice编辑器">两者的恢复能力不同。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 保留了清晰的 durable turn reason，运维侧能准确识别 token ceiling。自动继续需要 Goal Driver、用户输入或其他插件参与。Pi 更倾向在当前 invocation 内自我修复，模型可能马上重发工具调用。</p>
<p data-tool="mdnice编辑器">我在具有写操作的 Agent 中会选择 DeepSeek Harness 的策略。达到输出上限本身意味着模型状态不完整，自动延续可能重复前一批操作。只读研究型 Agent 可以采用 Pi 的方式，失败工具结果能减少一次人工干预。</p>
<h1 data-tool="mdnice编辑器"><span class="content">粘性结果</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 的 <code>max-tokens</code> 具有粘性。</p>
<p data-tool="mdnice编辑器">某个 step 达到上限后，插件仍可能在 <code>turn-stopping</code> 阶段补入 steering，让 turn 再执行几个 step。后续 step 即使正常完成，最终 <code>turn/end</code> 仍然保留 <code>max-tokens</code>。</p>
<p data-tool="mdnice编辑器">判断逻辑是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs"><span class="hljs-keyword">if</span> (
  turnEnds === <span class="hljs-literal">null</span> ||
  turnEnds.kind !== <span class="hljs-string">'max-tokens'</span>
)
  turnEnds = stepEnd
</code></pre>
<p data-tool="mdnice编辑器">这里记录的是整个 turn 的质量，而非最后一次 step 的结果。</p>
<p data-tool="mdnice编辑器">如果不做粘性处理，可能产生如下日志：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">step 1: max-tokens
step 2: 插件要求继续
step 3: completed
turn/end: completed
</code></pre>
<p data-tool="mdnice编辑器">消费端会丢失曾经发生的截断。监控系统无法统计 token ceiling，Goal Driver 也可能把这轮当成完整成功并继续自动调度。</p>
<p data-tool="mdnice编辑器">把 turn reason 视为聚合结果后，合并规则就需要专门设计。DeepSeek Harness 当前只对 <code>max-tokens</code> 做了优先保留。错误和取消会直接离开主流程，不参与后续合并。</p>
<p data-tool="mdnice编辑器">如果未来加入诸如 <code>partial</code>、<code>policy-warning</code>、<code>budget-exhausted</code> 等状态，简单覆盖会越来越脆弱。更稳妥的方向是为 reason 建立显式优先级或累积 facts，再由投影层生成最终 reason。当前 union 规模尚小，现有实现足够清楚。</p>
<h1 data-tool="mdnice编辑器"><span class="content">工具收口</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 允许工具通过结果上的 <code>concludesTurn</code> 宣布当前 turn 可以收口。</p>
<p data-tool="mdnice编辑器">工具批次采用 OR 聚合：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">任意一个已提交结果 concludesTurn === true
→ 整批 concluded === true
</code></pre>
<p data-tool="mdnice编辑器">它表达的是「模型默认不再欠一次工具结果回访」。</p>
<p data-tool="mdnice编辑器"><code>concludesTurn</code> 没有直接打断同批工具，也没有清空 <code>next-step</code>：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>已启动的并行工具继续运行；</section>
</li>
<li>
<section>结果按模型调用顺序提交；</section>
</li>
<li>
<section><code>additionalContexts</code> 写入 <code>next-step</code>；</section>
</li>
<li>
<section>同期到达的 steering 也写入 <code>next-step</code>；</section>
</li>
<li>
<section>只要 <code>next-step</code> 非空，turn 仍会进入下一 step。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">因此，工具结束能力属于软收口。它取消了自动回访债务，无法压过已经存在的消息债务。</p>
<p data-tool="mdnice编辑器">这套语义适合权威型工具。例如某个交互工具已经取得用户最终决定，或者某个提交工具已经完成不可逆事务，模型没有必要再生成一段中间解释。工具可以建议收口，同时保留系统上下文和用户 steering 的优先级。</p>
<p data-tool="mdnice编辑器">Pi 的工具终止采用 AND 聚合：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs"><span class="hljs-keyword">return</span> finalizedCalls.length &gt; <span class="hljs-number">0</span> &amp;&amp;
  finalizedCalls.every(
    <span class="hljs-function"><span class="hljs-params">finalized</span> =&gt;</span> finalized.result.terminate === <span class="hljs-literal">true</span>
  )
</code></pre>
<p data-tool="mdnice编辑器">只有非空批次中的每个 finalized result 都带有 <code>terminate: true</code>，整批才终止。</p>
<p data-tool="mdnice编辑器">并行批次中，一个工具不应单方面吞掉其他工具结果。Pi 要求整批达成一致，语义更保守。DeepSeek Harness 允许一个权威工具宣布收口，扩展能力更强，也更依赖工具契约。</p>
<p data-tool="mdnice编辑器">选择 OR 还是 AND，取决于 <code>terminate</code> 的业务含义。</p>
<p data-tool="mdnice编辑器">如果它代表「任一工具发现全局终止条件」，OR 合理，比如用户取消、权限拒绝、事务已完成。</p>
<p data-tool="mdnice编辑器">如果它代表「当前工具不需要模型处理自己的结果」，AND 更稳妥，因为其他工具可能仍需模型解释。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 通过 <code>next-step</code> 上下文缓和了 OR 的侵略性，但无法覆盖所有情况。假设两个并行工具都成功，一个写数据库并 concludes，另一个返回一份需要模型分析的报告，又没有追加 context。模型回访会被跳过。工具注册规范需要限制哪些工具可以 conclude，不能把它当成普通便利字段。</p>
<h1 data-tool="mdnice编辑器"><span class="content">顺序提交</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 的工具调度器还有一项容易被忽略的约束：执行可以并行，提交保持模型顺序。</p>
<p data-tool="mdnice编辑器">调度器维护：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>nextToStart</code>；</section>
</li>
<li>
<section><code>started</code>；</section>
</li>
<li>
<section><code>committed</code>；</section>
</li>
<li>
<section><code>inFlight</code>；</section>
</li>
<li>
<section>按调用位置排列的 <code>slots</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">并行工具完成时，只把结果放入对应 slot。<code>commitReady()</code> 从 <code>committed</code> 开始，连续提交已经就绪的结果。后面的工具先完成，也要等待前面的 slot。</p>
<p data-tool="mdnice编辑器">这会增加尾部等待时间，但能保证：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>tool/result</code> 顺序稳定；</section>
</li>
<li>
<section>additional context 顺序稳定；</section>
</li>
<li>
<section>replay 结果稳定；</section>
</li>
<li>
<section>插件观察顺序稳定；</section>
</li>
<li>
<section>同一模型响应在多次运行中的日志结构更一致。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">模型生成了 A、B、C 三个调用。若 C 最先完成，直接提交 C 会让后续上下文受网络时序影响。对于事件溯源系统，这类非确定性会扩大恢复和测试成本。</p>
<p data-tool="mdnice编辑器">Pi 的并行执行也使用 <code>Promise.all</code> 保留输入数组顺序，最终 tool result message 按原调用顺序发出。两套系统都接受并行执行带来的延迟收益，同时拒绝完成时序污染模型上下文。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 额外支持动态重分类。并行池补充新任务前，会重新读取工具当前 execution mode。前一个工具可能修改注册表，使后续工具从 parallel 变成 exclusive。调度器会停止补充，等待当前池排空，再把 exclusive call 留给下一个 barrier。</p>
<p data-tool="mdnice编辑器">这种灵活性会增加 scheduler 状态数量。适合「工具本身能改变工具环境」的 Harness。工具目录完全静态时，预先分组会更简单。</p>
<h1 data-tool="mdnice编辑器"><span class="content">取消语义</span></h1>
<p data-tool="mdnice编辑器">工具执行期间的取消，比模型流取消复杂得多。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 的原则可以概括成三条：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>停止补充尚未启动的调用；</section>
</li>
<li>
<section>清空已经启动的调用；</section>
</li>
<li>
<section>为因取消而跳过的工具写入合成错误结果。</section>
</li>
</ol>
<p data-tool="mdnice编辑器">已启动工具可能已经产生副作用，框架不能假装它没有运行。调度器等待这些调用 settle，并按模型顺序提交结果和 additional contexts。</p>
<p data-tool="mdnice编辑器">未启动工具则写入一对完整事件：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">tool/call
tool/result(error: aborted before dispatch)
</code></pre>
<p data-tool="mdnice编辑器">这样可以维持模型工具协议的配对关系。恢复和 replay 时，每个模型产生的 tool call 都有对应结果。</p>
<p data-tool="mdnice编辑器">内部 scheduler failure 的处理更克制。它停止新 dispatch，等待已经启动的调用结束，然后抛出第一个失败。它不会为剩余调用伪造结果。因为 scheduler failure 与用户取消不同，系统无法确认未提交调用处于什么语义状态。伪造恢复结果可能掩盖调度器缺陷。</p>
<p data-tool="mdnice编辑器">这里体现出事件日志的核心要求：日志不只服务 UI，它还是恢复协议。任何 synthetic event 都需要有确定语义。为了让数组长度好看而补事件，会污染后续决策。</p>
<p data-tool="mdnice编辑器">Pi 的实现更偏运行时事件流。工具执行异常会被转换成 error tool result，随后继续进入上下文。取消时顺序模式会在每次工具后检查 signal，平行模式也会停止准备后续调用。它的核心目标是把本次 invocation 的 message stream 完整交给调用方，持久日志的闭合约束没有 DeepSeek Harness 那么强。</p>
<h1 data-tool="mdnice编辑器"><span class="content">原因体系</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 的 <code>TurnEndReason</code> 包含六种核心结果：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>completed</code>；</section>
</li>
<li>
<section><code>blocked</code>；</section>
</li>
<li>
<section><code>max-tokens</code>；</section>
</li>
<li>
<section><code>aborted</code>；</section>
</li>
<li>
<section><code>error</code>；</section>
</li>
<li>
<section><code>interrupted</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些状态回答的是整个 turn 如何结束。</p>
<p data-tool="mdnice编辑器"><code>completed</code> 覆盖正常文本停止、工具要求收口，以及首步为空。它不承诺长期 Goal 完成。</p>
<p data-tool="mdnice编辑器"><code>blocked</code> 来自 pre-step reject。输入可能已经被 inbox 领取，但策略层拒绝进入模型调用。driver 当前会停止，其他尚未领取的普通 prompt 可以继续留在队列。</p>
<p data-tool="mdnice编辑器"><code>max-tokens</code> 表示至少一个 step 达到输出上限。它保留为 turn 级质量信号。</p>
<p data-tool="mdnice编辑器"><code>aborted</code> 携带取消原因，包括 user、parent、hook 和 disposed。持久导入还可能出现 legacy cause。</p>
<p data-tool="mdnice编辑器"><code>error</code> 保存结构化 <code>LlmFailure</code>。<code>LlmError</code> 的信息原样保留，其他异常会被压平为 <code>errorChain</code> 文本，并使用 <code>UNKNOWN</code> code。</p>
<p data-tool="mdnice编辑器"><code>interrupted</code> 由持久化恢复层产生。进程崩溃后，后端发现一个开放 turn，会用该状态闭合。live loop 不会主动写入它。</p>
<p data-tool="mdnice编辑器">把 crash repair 与 runtime abort 分开很有必要。abort 表示程序收到了取消请求，并有机会执行清理；interrupted 表示生命周期突然消失。两者对副作用审计、重试决策和用户提示都有不同含义。</p>
<p data-tool="mdnice编辑器">Pi 主要保留 assistant message 的 <code>stopReason</code>：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>stop</code>；</section>
</li>
<li>
<section><code>length</code>；</section>
</li>
<li>
<section><code>toolUse</code>；</section>
</li>
<li>
<section><code>error</code>；</section>
</li>
<li>
<section><code>aborted</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">它更接近最后一次模型调用的结束事实。DeepSeek Harness 的 reason 聚合了多 step turn 的结果。一个字段偏 provider，一个字段偏业务生命周期。</p>
<p data-tool="mdnice编辑器">从监控角度看，Pi 的 stopReason 更利于分析模型行为；DeepSeek Harness 的 turn reason 更利于分析任务执行。生产系统通常两类数据都需要。DeepSeek Harness 通过 <code>assistant/message</code>、request events 和 <code>turn/end</code> 同时保留两层事实，日志体积也会更大。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Goal 外循环</span></h1>
<p data-tool="mdnice编辑器">长期 Goal 无法靠核心 turn 的 <code>completed</code> 判断。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 采用外围 Round Driver。它监听 Agent 进入 idle，然后检查：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>Agent 实例是否仍然有效；</section>
</li>
<li>
<section>inbox 是否存在竞争中的普通 prompt；</section>
</li>
<li>
<section>Goal 是否存在；</section>
</li>
<li>
<section>Goal phase 是否为 active；</section>
</li>
<li>
<section>当前 activation 是否 armed；</section>
</li>
<li>
<section>已启动 round 数是否低于上限。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">满足条件后，Driver 构造一条来源为 goal 的 user message，并调用 <code>agent.followup()</code>。新消息进入 <code>next-turn</code>，打开全新的 turn。</p>
<p data-tool="mdnice编辑器">时序如下：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">turn/end(completed)
→ agent/status: idle
→ Goal Driver 读取持久状态
→ flush
→ followup(goal round)
→ turn/start
</code></pre>
<p data-tool="mdnice编辑器">这种结构给每个 Goal Round 一个完整、独立、可恢复的生命周期。进程在两个 Round 之间退出，恢复时可以从最后一个闭合 turn 继续判断。一个覆盖几十轮的巨大 while 会让持久化边界、用户抢占和异常恢复都更困难。</p>
<p data-tool="mdnice编辑器">Goal 还维护 phase 与 activation 两组状态。</p>
<p data-tool="mdnice编辑器">持久 phase 描述业务状态：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>active；</section>
</li>
<li>
<section>complete；</section>
</li>
<li>
<section>blocked；</section>
</li>
<li>
<section>paused。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">进程内 activation 描述自动续行授权：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>armed；</section>
</li>
<li>
<section>disarmed。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">把两者拆开可以处理 resume 和 fork。一个持久 Goal 仍然 active，不代表新进程应该自动继续消耗预算。恢复后的 active Goal 默认 disarmed，需要人类再次授权。否则某次服务重启可能让多个历史任务同时复活。</p>
<p data-tool="mdnice编辑器">达到最大 Round 数时，Goal 会进入 blocked，并记录 round-limit。<code>max-tokens</code>、Agent error、flush failure、driver failure 会 disarm。取消路径也会 pause 或 disarm，防止 idle 后被 Goal Driver 再次拉起。</p>
<p data-tool="mdnice编辑器">这里采用了 fail-closed 策略。长期自治任务遇到状态不确定时停止续行。对成本敏感、有写权限的 Agent，这是合理默认值。自动重试可以放在更高层，由持久预算和幂等策略约束。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Goal 收尾</span></h1>
<p data-tool="mdnice编辑器">Goal 状态改为 complete 或 blocked 后，当前 turn 通常还会再调用一次模型。</p>
<p data-tool="mdnice编辑器"><code>update_goal</code> 工具会修改持久状态，并通过 deferred context 注入收尾指令。工具结果与 <code>&lt;goal_complete&gt;</code> 或 <code>&lt;goal_blocked&gt;</code> context 进入下一 step，模型生成最终面向用户的说明。之后 turn 以 completed 结束。Agent 进入 idle，Goal Driver 读取到非 active phase，不再唤醒。</p>
<p data-tool="mdnice编辑器">这解决了状态完成与用户可见输出之间的时间差。</p>
<p data-tool="mdnice编辑器">如果 <code>update_goal(complete)</code> 直接 conclude turn，持久状态已经完成，用户界面可能只看到工具卡片，没有自然语言总结。若让模型先写总结再更新状态，模型或进程可能在总结期间失败，Goal 又会保持 active。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 先提交状态，再执行展示收尾。恢复时即便缺少最终文本，Goal 也不会重复工作。用户体验层可以根据 complete 状态补一条提示，或者提供重新生成总结的操作。</p>
<p data-tool="mdnice编辑器">代价是 complete 后多一次模型调用，也会增加少量 token 和延迟。我认为这笔成本合理。长期任务的最终输出属于交付结果，不能依赖 UI 猜测工具状态。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Pi 双循环</span></h1>
<p data-tool="mdnice编辑器">Pi 的 <code>runLoop()</code> 使用内外两层循环。</p>
<p data-tool="mdnice编辑器">内层条件为：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">hasMoreToolCalls || pendingMessages.length &gt; 0
</code></pre>
<p data-tool="mdnice编辑器">它处理工具回访和 steering。外层负责 Agent 本来要停止时的 follow-up。</p>
<p data-tool="mdnice编辑器">一次内层迭代包括：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs">prepareNextTurn
→ 处理 pending steering
→ 调用模型
→ 执行工具
→ 发出 turn_end
→ shouldStopAfterTurn
→ 拉取新的 steering
</code></pre>
<p data-tool="mdnice编辑器">内层耗尽后，系统调用 <code>getFollowUpMessages()</code>。存在 follow-up 时，将它们设置为 pending，重新进入内层。没有 follow-up 时发出 <code>agent_end</code>。</p>
<p data-tool="mdnice编辑器">Pi 提供三个高杠杆控制点：</p>
<ul data-tool="mdnice编辑器">
<li>
<section><code>getSteeringMessages()</code>；</section>
</li>
<li>
<section><code>getFollowUpMessages()</code>；</section>
</li>
<li>
<section><code>shouldStopAfterTurn()</code>。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这套接口很适合嵌入应用。</p>
<p data-tool="mdnice编辑器">steering 处理当前 activity 内的即时干预；follow-up 处理 Agent 自然收口后的补充工作；shouldStopAfterTurn 提供宿主级截停。扩展作者不需要理解持久 inbox，也不需要创建新的状态投影。</p>
<p data-tool="mdnice编辑器">复杂度被交给宿主。多个扩展共同提供 follow-up 时，需要上层决定合并方式。进程恢复后，回调内部的临时状态如何重建，也由应用实现。若不同扩展分别定义 Goal、Plan 和审批流程，结束语义会逐渐碎片化。</p>
<p data-tool="mdnice编辑器">Pi core 的目标是提供一个小而可组合的运行时。它没有承诺统一的长期任务协议。这个边界和 DeepSeek Harness 的产品定位并不相同。</p>
<h1 data-tool="mdnice编辑器"><span class="content">失败路径</span></h1>
<p data-tool="mdnice编辑器">Pi 遇到 assistant <code>error</code> 或 <code>aborted</code> 时，会发出 <code>turn_end</code> 和 <code>agent_end</code>，然后结束当前 invocation。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 会先区分流 finish error、显式 abort 和普通异常。</p>
<p data-tool="mdnice编辑器">请求错误可以进入 <code>agent/request-error</code> waterfall。插件能够依据 provider、failure、retry policy 和 signal 决定 retry。重试发生在同一个 step 内，重新创建 assembler，再发一次请求。外部 turn 和 step 边界保持不变。</p>
<p data-tool="mdnice编辑器">这种设计使日志更紧凑，但需要谨慎处理重复请求。前一次失败若已经在 provider 侧产生计费，session 中可能只有请求重试后的最终 assistant message。底层观测系统需要独立记录 attempt，不能只靠 conversation event log 统计模型调用次数。</p>
<p data-tool="mdnice编辑器">Pi 通常由 stream implementation 或上层配置承担 retry。Agent Loop 接收到的 final message 已经带有 stopReason。职责划分更轻，调用方也更容易替换模型层。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 将 request header、adapter defaults、request context 和 conversation surface 都纳入 session 语义，retry 与恢复自然更靠近 loop。架构更重，换来统一的请求重建能力。</p>
<h1 data-tool="mdnice编辑器"><span class="content">请求序列</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 每次构建请求时都会比较当前 header 与 session 中的 baseline。</p>
<p data-tool="mdnice编辑器">header 包含：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>provider 和 model；</section>
</li>
<li>
<section>reasoning effort、max tokens 等调用配置；</section>
</li>
<li>
<section>adapter materialized defaults；</section>
</li>
<li>
<section>system prompt；</section>
</li>
<li>
<section>tool schemas。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">首个 header 记录为 initial 或 resume。配置变化时记录 change。显式开始新消息序列或 surface replacement 后，会记录 series。</p>
<p data-tool="mdnice编辑器">结束判断和 request series 没有直接控制关系，但它影响「同一个上下文连续体」的解释。Goal 的自动 Round 可以标记 <code>startsRequestSeries: true</code>，让每轮长期任务拥有独立请求序列。缓存、展示和重放层因此能识别边界。</p>
<p data-tool="mdnice编辑器">如果只依靠 turn start 判断新序列，会把工具回访、用户 follow-up、Goal Round 和 compaction 后续请求全部归入一种语义。DeepSeek Harness 单独记录 series，是在为后续缓存和重建保留信息。</p>
<p data-tool="mdnice编辑器">成本是日志事件更多，header equality 与 canonicalization 也必须稳定。工具 schema 排序、空字段处理或 adapter defaults 若不规范，可能制造大量无意义 change 事件。代码使用 canonical header 和 deep freeze，正是在控制这种漂移。</p>
<h1 data-tool="mdnice编辑器"><span class="content">日志消费</span></h1>
<p data-tool="mdnice编辑器">消费 DeepSeek Harness 日志时，先问清业务方想判断哪一层结束。</p>
<p data-tool="mdnice编辑器">判断一次模型调用完成，应读取 step 内的 assistant message、stream finish 和 usage。</p>
<p data-tool="mdnice编辑器">判断 turn 是否闭合，应寻找配对的 <code>turn/start</code> 与 <code>turn/end</code>。只有 assistant message 不够。pre-step reject、空 turn、异常和 crash repair 都可能产生不同结构。</p>
<p data-tool="mdnice编辑器">判断当前 Agent 是否暂无活动，应读取 status 或等待 <code>whenIdle()</code>。最后一条 turn/end 仍不足以证明 idle，因为 driver 可能已经打开下一 turn。</p>
<p data-tool="mdnice编辑器">判断长期 Goal 是否结束，应读取 Goal phase。complete、blocked 和 paused 都会停止自动 Round，业务含义各不相同。</p>
<p data-tool="mdnice编辑器">判断 Plan 是否退出，应读取最新 <code>plan/mode.active</code>。看到 <code>exit_plan_mode</code> 调用，只能说明模型提出了退出请求。审批可能被拒绝，pending intent 也可能尚未在 pre-step 提交。</p>
<p data-tool="mdnice编辑器">判断会话以后是否还会运行，需要观察 Agent 是否 disposed、是否从 registry 移除，以及外围 scheduler 是否仍能发送消息。idle 和 completed 都没有永久性。</p>
<p data-tool="mdnice编辑器">Pi 的消费方式更简单：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>assistant <code>stopReason</code> 描述模型调用；</section>
</li>
<li>
<section><code>turn_end</code> 描述一次模型与工具批次；</section>
</li>
<li>
<section><code>agent_end</code> 描述当前 invocation；</section>
</li>
<li>
<section>Goal、Plan 和工作流完成度读取具体扩展状态。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">即使已经收到 <code>agent_end</code>，宿主仍然可以再次调用 Agent Loop。它同样不具备永久终止含义。</p>
<h1 data-tool="mdnice编辑器"><span class="content">工程取舍</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 为结束判断支付了较高复杂度。</p>
<p data-tool="mdnice编辑器">它需要：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>phase 状态机；</section>
</li>
<li>
<section>inbox 分类；</section>
</li>
<li>
<section>wake latch；</section>
</li>
<li>
<section>平衡的 turn 和 step 事件；</section>
</li>
<li>
<section>surface projection；</section>
</li>
<li>
<section>request header 重建；</section>
</li>
<li>
<section>crash repair；</section>
</li>
<li>
<section>Goal activation；</section>
</li>
<li>
<section>Plan boundary intent；</section>
</li>
<li>
<section>有序工具提交。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些机制会增加代码量、测试矩阵和插件开发门槛。一个简单聊天产品采用全套设计，投入可能超过收益。</p>
<p data-tool="mdnice编辑器">它适合以下场景：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>会话需要跨进程恢复；</section>
</li>
<li>
<section>Agent 拥有高风险工具；</section>
</li>
<li>
<section>多个插件会同时 steering；</section>
</li>
<li>
<section>需要长期 Goal 与预算控制；</section>
</li>
<li>
<section>日志需要审计和 replay；</section>
</li>
<li>
<section>Plan、审批和执行必须跨边界保持一致；</section>
</li>
<li>
<section>子 Agent 和父 Agent 存在取消传播。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Pi 的循环更适合：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>单次 invocation 生命周期清晰；</section>
</li>
<li>
<section>宿主已经拥有自己的持久化层；</section>
</li>
<li>
<section>扩展数量有限；</section>
</li>
<li>
<section>工作流由应用统一控制；</section>
</li>
<li>
<section>希望快速接入多模型与工具；</section>
</li>
<li>
<section>对 session event vocabulary 没有强约束。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Pi 的小核心减少了框架内部状态。业务一旦开始增加 Goal、审批、自动 follow-up、并发工具策略、恢复和预算，宿主会逐步补齐类似机制。复杂度没有消失，只是落在不同层。</p>
<p data-tool="mdnice编辑器">团队选择框架时，不应只比较 Agent Loop 文件有多少行。要看系统准备把「继续运行的权力」放在哪里。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 把权力分散给模型 finish、工具结果、inbox、生命周期 hook 和外围持久状态机，并通过事件日志协调。Pi 把权力集中在当前循环和几个宿主回调中，扩展层可以自行定义更高层协议。</p>
<h1 data-tool="mdnice编辑器"><span class="content">设计建议</span></h1>
<p data-tool="mdnice编辑器">如果我们团队要自行实现 Agent Loop，我会保留以下原则。</p>
<p data-tool="mdnice编辑器">第一，区分模型结束、工具批次结束、用户 turn 结束和长期任务结束。类型和事件名也要分开，避免一个 <code>done</code> 字段横跨所有层级。</p>
<p data-tool="mdnice编辑器">第二，继续执行需要携带数据。插件要求再跑一轮时，应提交 steering 或 follow-up message。单独返回布尔值会让调试和组合越来越困难。</p>
<p data-tool="mdnice编辑器">第三，工具调用后的模型回访应被建模为债务。普通工具结果产生一次回复债务，conclude 或 terminate 可以消除债务，steering 又会重新增加消息债务。用这套视角设计状态机，比堆叠 <code>if (hasToolCalls)</code> 更稳定。</p>
<p data-tool="mdnice编辑器">第四，最大 token 需要独立结果，且不要执行疑似截断的工具参数。自动修复可以存在，但必须显式记录恢复过程。</p>
<p data-tool="mdnice编辑器">第五，并行工具允许乱序完成，持久结果应按模型顺序提交。除非协议明确支持乱序 tool result，否则不要让网络时序改变上下文。</p>
<p data-tool="mdnice编辑器">第六，取消必须区分已启动与未启动调用。已启动工具要排空或记录未知结果，未启动工具需要完整的 synthetic result，具体选择取决于日志协议。</p>
<p data-tool="mdnice编辑器">第七，idle 不能充当长期完成信号。Goal phase、workflow state 和 Agent lifecycle 应拥有独立状态。</p>
<p data-tool="mdnice编辑器">第八，模式切换要提交在请求边界。Plan、权限、模型路由和工具目录的变化，都不应在一个已开始的请求批次中途生效。</p>
<p data-tool="mdnice编辑器">第九，恢复后的自动续行默认关闭。长期 Agent 会消耗资金，也可能修改外部系统。新进程接管旧任务时，应重新取得授权或租约。</p>
<p data-tool="mdnice编辑器">第十，结束原因需要兼顾机器消费。<code>completed</code>、<code>blocked</code>、<code>max-tokens</code>、<code>aborted</code>、<code>error</code>、<code>interrupted</code> 这类结构化 union，比一段自然语言错误更适合监控、重试和审计。</p>
<h1 data-tool="mdnice编辑器"><span class="content">架构落点</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 把结束判断分布在四个控制面上。</p>
<p data-tool="mdnice编辑器">模型输出控制 step：还有没有工具回访债务。</p>
<p data-tool="mdnice编辑器">inbox 控制 turn：还有没有当前轮待处理消息。</p>
<p data-tool="mdnice编辑器">driver 控制 activity：队列耗尽后是否进入 idle。</p>
<p data-tool="mdnice编辑器">Goal 与 Plan 控制工作流：idle 后是否开启新 Round，下一请求采用哪种策略。</p>
<p data-tool="mdnice编辑器">Pi 将更多判断放在同一个运行循环里。工具调用和 pending steering 驱动内层循环，follow-up 驱动外层循环，<code>shouldStopAfterTurn</code> 提供宿主截停点，队列耗尽后发出 <code>agent_end</code>。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 更接近一个事件溯源的 Agent Runtime。Pi 更接近一个可嵌入的 Agent 执行内核。</p>
<p data-tool="mdnice编辑器">评价两者没啥意义，我们需要关注的是其系统边界。</p>
<p data-tool="mdnice编辑器">如果产品只需要一次 prompt 驱动的工具循环，Pi 的结构更容易维护。若产品已经出现跨轮 Goal、审批、恢复、多插件 steering 和高风险工具，DeepSeek Harness 的分层会减少后期协议冲突。</p>
<p data-tool="mdnice编辑器">Agent Loop 最难处理的部分，从来都不是让模型继续调用工具。真正消耗工程时间的是：它为什么继续、谁允许它继续、失败后还能不能继续、恢复后应不应该继续，以及日志能否解释它曾经做过的每一次选择。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/08/deepseek-harness-and-pi-agent-loop-end/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>DeepSeek Harness 的 Cordis 插件架构</title>
		<link>https://www.phppan.com/2026/08/deepseek-harness-cordis/</link>
		<comments>https://www.phppan.com/2026/08/deepseek-harness-cordis/#comments</comments>
		<pubDate>Sun, 23 Aug 2026 03:36:05 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[Agent Harness]]></category>
		<category><![CDATA[AIAgent架构]]></category>
		<category><![CDATA[DeepSeek]]></category>
		<category><![CDATA[DeepSeekHarness]]></category>
		<category><![CDATA[harness]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2531</guid>
		<description><![CDATA[DeepSeek Harness 把模型、工具、会话、沙箱、文件系统、Agent 循环、调度和 UI 都交给插 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">DeepSeek Harness 把模型、工具、会话、沙箱、文件系统、Agent 循环、调度和 UI 都交给插件。</p>
<p data-tool="mdnice编辑器">系统没有一块承载业务能力的固定内核，Cordis 只维护上下文、服务注册、事件分发、依赖激活和副作用回收；Agent 能做什么，由启动时挂入的一棵插件树决定。</p>
<p data-tool="mdnice编辑器">传统的插件有注册容易，撤销困难；初始化容易，失败回滚困难；全局单例容易，局部覆盖困难等问题，而 Cordis 把这些困难收进了运行时语义，Harness 再用 session、agent 和 preset 的业务约束补齐它。</p>
<p data-tool="mdnice编辑器">今天聊了一下 DeepSeek Harness 的核心 Cordis 架构以及 Cordis 所谓「时空可组合」，究竟怎样落到代码里，又为何适合 Agent Harness 这类持续变化的运行时。</p>
<h1 data-tool="mdnice编辑器"><span class="content">插件的历史</span></h1>
<p data-tool="mdnice编辑器">插件架构的历史并不短。Eclipse 在二十多年前已经把 IDE 拆成插件、扩展点和扩展，宿主声明可扩展位置，其他插件通过清单贡献菜单、编辑器和处理逻辑。</p>
<p data-tool="mdnice编辑器">OSGi 更进一步，给 bundle 配置安装、启动、停止、更新和卸载生命周期，再通过共享服务注册表完成发布、查找和绑定。服务离开注册表时，依赖方必须跟着处理动态变化。</p>
<p data-tool="mdnice编辑器">今天看 Cordis，能找到这些设计的清晰痕迹：动态服务、生命周期、注册表、声明式依赖、事件通知都已有成熟先例。</p>
<p data-tool="mdnice编辑器">Cordis 没有发明插件系统。</p>
<p data-tool="mdnice编辑器">它简化并重新组合了这些概念，让它们适合 TypeScript 应用内的细粒度组装。OSGi 的部署单元是 bundle，模块、生命周期、服务和安全各有一层；Cordis 的执行单元是 Fiber，一个函数或一个 <code style="color: #ef7060;">Service</code> 子类就能成为插件。Eclipse 的扩展点通常由宿主定义结构化清单，Cordis 让服务和类型化事件直接成为扩展面。Agent 运行时里的工具、提示词片段、模型适配器和审批策略变化频繁，如果每次扩展都要引入重量级模块边界，团队很快会绕过框架，重新写回几个全局数组。</p>
<p data-tool="mdnice编辑器">Cordis 官方仓库把自己定位为「Meta-Framework of Spatiotemporal Composability」。</p>
<p data-tool="mdnice编辑器">从源码看，「元框架」表示它不规定 Agent、Web 服务或机器人该有什么组件，只提供构造框架所需的基础语义。DeepSeek Harness 在其上定义 <code style="color: #ef7060;">sessions</code>、<code style="color: #ef7060;">tools</code>、<code style="color: #ef7060;">llm</code>、<code style="color: #ef7060;">agents</code> 等服务，又用这些服务组装产品。Cordis 与 Harness 的关系更接近运行时机制和领域框架，而非通用插件平台与插件集合。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 还把 Cordis 源码直接放进 <code style="color: #ef7060;">vendor/</code>，固定在明确的上游提交，并将包名重映射到 <code style="color: #ef7060;">@deepseek-ai</code> 命名空间。</p>
<p data-tool="mdnice编辑器">项目维护的本地修改包含 Fiber 重入卸载加固、配置更新事务、HMR 精确监听、延迟配置解析等。这增加了维护成本，也换来了框架层的可审计性。Agent 能执行 Shell、修改文件、访问网络，插件生命周期出错会留下进程、监听器、终端模式或权限状态。</p>
<p data-tool="mdnice编辑器">此时依赖一个本地的代码白盒，会让人放心一些。</p>
<h1 data-tool="mdnice编辑器"><span class="content">调用链路图</span></h1>
<p data-tool="mdnice编辑器">简单的调用图如下所示：</p>
<figure data-tool="mdnice编辑器"><img src="https://files.mdnice.com/user/36365/c8ef3798-50e3-40a7-810e-c68c5f71ca03.png" alt="" /></figure>
<h1 data-tool="mdnice编辑器"><span class="content">最小内核</span></h1>
<p data-tool="mdnice编辑器">Cordis 的根对象是 <code style="color: #ef7060;">Context</code>。它同时承担依赖容器、插件挂载入口和事件入口。</p>
<p data-tool="mdnice编辑器">服务通过稳定名称出现在 <code style="color: #ef7060;">ctx</code> 上，例如 <code style="color: #ef7060;">ctx.tools</code>、<code style="color: #ef7060;">ctx.llm</code>、<code style="color: #ef7060;">ctx.sessions</code>。插件声明 <code style="color: #ef7060;">inject</code> 后，Cordis 只有在所需服务可用时才激活它；服务消失，依赖插件也会进入卸载或等待状态。配置文件中条目的先后顺序因此不承担启动顺序，依赖关系才承担。</p>
<p data-tool="mdnice编辑器">这种处理解决了传统插件系统的第一个顽疾：隐含初始化顺序。</p>
<p data-tool="mdnice编辑器">常见实现会遍历插件数组，依次调用 <code style="color: #ef7060;">init</code>。当工具依赖文件系统、文件系统依赖沙箱、UI 又依赖会话时，数组顺序就变成一套没有类型、没有诊断的依赖图。后来插入一个插件，顺序约束可能跨越几十个文件。</p>
<p data-tool="mdnice编辑器">Cordis 把需求写进 <code style="color: #ef7060;">inject</code>，Fiber 会为每项依赖保存当前实现；缺少依赖时保持 <code style="color: #ef7060;">PENDING</code>，依赖齐备后进入 <code style="color: #ef7060;">LOADING</code> 和 <code style="color: #ef7060;">ACTIVE</code>。启动失败则进入 <code style="color: #ef7060;">FAILED</code>。状态机至少让故障有了准确位置。</p>
<p data-tool="mdnice编辑器"><code style="color: #ef7060;">Context</code> 还是一个代理对象。</p>
<p data-tool="mdnice编辑器">插件直接读取 <code style="color: #ef7060;">ctx.tools</code> 时，反射层会检查它是否声明过依赖，并沿 Fiber 父链解析实现。未声明就读取会抛错；声明了但当前上下文不可用，也会抛出另一类错误。这种约束防止依赖藏在任意函数深处。源码里依然提供 <code style="color: #ef7060;">ctx.get(name)</code> 读取可选服务，区别在于调用方显式接受服务可能不存在。</p>
<p data-tool="mdnice编辑器">这里的「服务定义、服务提供方、消费方」三种角色被完整建模。比如文件系统能力不能只写一个接口，也不能只挂一个本地实现。定义方稳定调用协议，提供方接入本地目录或远程沙箱，消费方把能力变成模型可见工具。Harness 把这组关系称为 capability seam。替换沙箱时，Shell、PTY、LSP 只要依赖同一能力面，就能整体迁移到新的执行环境，消费方无需知道提供方运行在本机还是远端。</p>
<p data-tool="mdnice编辑器">「一切皆插件」有一个前提是：一切产品能力皆由插件贡献，Cordis 自身仍保留插件得以存在的机制。上下文代理、Fiber 状态机、服务存储、事件总线和 Loader 属于元层。</p>
<p data-tool="mdnice编辑器">这并不是系统里没有内核，更准确的说，应该是，内核不拥有模型、工具和循环等产品特权。</p>
<h1 data-tool="mdnice编辑器"><span class="content">空间组合</span></h1>
<p data-tool="mdnice编辑器">同一个进程里，服务名称必须稳定，实例又不能全局唯一。两个 Agent 可能选择不同模型、不同工具集、不同 persona 和不同沙箱。如果 <code style="color: #ef7060;">ctx.llm</code> 永远指向一个全局对象，插件替换只发生在进程级，无法满足多会话并存。</p>
<p data-tool="mdnice编辑器">Cordis 的 <code style="color: #ef7060;">Context.isolate</code> 为指定服务名创建一个 realm 标签。服务注册和查找都以该标签定位，同名服务可以在不同子上下文里各自存在。两个 <code style="color: #ef7060;">isolate</code> 调用传入同一标签时会加入同一 realm；使用新标签时彼此隔离。子上下文通过原型继承父上下文，隔离映射按层遮蔽，因此局部配置不需要复制整棵容器。</p>
<p data-tool="mdnice编辑器">这解释了「空间可组合」的第一层：组合具有位置。插件挂在哪个上下文，决定它提供的服务、注册的监听器和持有的副作用在哪个范围可见。传统依赖注入容器也有 singleton、request、session 等 scope，Cordis 的差异在于作用域与插件树、事件过滤、资源所有权使用同一个 Context 表达。开发者无需在四套 API 之间同步身份。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 又增加了 <code style="color: #ef7060;">dsh-scope</code>。它用不透明对象作为 scope key，维护父子关系，并创建带路由身份的事件 receiver。注册视图沿父链向下继承：Agent 能看到所属 preset 的提示词和工具。事件则沿链向上接纳：preset 级监听器能收到其下 Agent 的事件，兄弟 Agent 互不串线。这是第二层空间结构，解决的是领域身份，而非单个服务名的 realm。</p>
<p data-tool="mdnice编辑器">为何需要两层？<code style="color: #ef7060;">isolate</code> 处理「哪个 <code style="color: #ef7060;">tools</code> 服务实例」，<code style="color: #ef7060;">dsh-scope</code> 处理「同一工具注册表里，哪些注册项对当前 Agent 可见」。前者适合替换提供方，后者适合对注册内容分层。把所有差异都做成独立服务实例，会放大内存和初始化成本；把所有差异都塞进一个全局注册表，又会让过滤规则散落在调用点。这两层模型把实例隔离和内容路由分开了。</p>
<p data-tool="mdnice编辑器">Agent preset 是空间组合的完整应用。一个 preset 的 Cordis 配置会挂在一个长期存在的 scope 下，Agent 创建时把自己的 scope key 绑定到该 preset。相同 preset 的并发首次使用通过 single-flight 共享一次挂载，后续 Agent 复用这份组合。文件变化后，新会话加入新一代组合，已有会话保留原来的代际。这个策略避免会话运行中途突然更换工具或提示词，代价是旧代际要保留到整棵运行时退出，配置频繁变化时内存会按代际增长。</p>
<p data-tool="mdnice编辑器">源码还专门审计 preset 子树是否把服务发布到了 root realm。发生这种泄漏时，第二个会话挂载同一 preset 会与第一个冲突，所谓会话级组合也会退化成进程全局状态。DeepSeek Harness 在发布 Agent 前拒绝这种配置。空间隔离若只靠约定，迟早会被一个没有 <code style="color: #ef7060;">isolate</code> 的 provider 穿透；运行时审计比文档警告可靠。</p>
<p data-tool="mdnice编辑器">这套空间模型存在认知成本。插件作者要同时理解 Context 父链、服务 realm、业务 scope 父链和事件过滤方向（当然，在当前 Vibe Coding 盛行的时代，也可以作者不理解，直接让 AI 来搞）。</p>
<p data-tool="mdnice编辑器">在认知不清楚的时候，常见的错误往往表现为某项能力「看不见」或意外泄漏，类型系统无法证明运行时挂载位置。DeepSeek Harness 用 preset 挂载审计、包级 invariant 和真实组合测试降低风险，却没有消除模型复杂度。团队若只需要单进程单 Agent，直接引入整套空间语义会显得过重。</p>
<h1 data-tool="mdnice编辑器"><span class="content">时间组合</span></h1>
<p data-tool="mdnice编辑器">插件能挂载只是静态组合。运行期间服务上线、配置改变、插件失败或上下文销毁，系统还要回到一致状态。Cordis 用 Fiber 和 effect 管理这条时间轴。</p>
<p data-tool="mdnice编辑器">每次插件应用都会产生一个 Fiber。Fiber 记录父上下文、原始配置、解析后配置、依赖实现快照、生命周期状态和 disposables。插件调用 <code style="color: #ef7060;">ctx.effect()</code> 注册副作用，effect 的执行结果返回 disposer。事件监听、服务提供、子插件和访问器最终都进入这套所有权体系。Fiber 卸载时按注册的逆序执行清理，并等待异步清理达到静止状态。</p>
<p data-tool="mdnice编辑器">逆序本身不是一个特别要讲的事情，因为这是必须的。</p>
<p data-tool="mdnice编辑器">若插件先启动子进程，再注册输出监听，最后暴露服务，销毁时应先撤销服务，停止新请求，再移除监听，最后结束进程。资源创建顺序的反向通常就是依赖安全的拆卸顺序。</p>
<p data-tool="mdnice编辑器">传统插件常给出一个 <code style="color: #ef7060;">deactivate()</code> 钩子，把所有清理责任推给作者；漏掉一个定时器或事件监听，热加载几次便出现重复执行。Cordis 让每次注册同时产生撤销动作，框架持有所有权。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 的 vendor 版本进一步处理了重入场景：effect 在执行 setup 前先登记所有者包装；插件发布事件时，观察者可能同步卸载它；异步 cleanup 已经开始后，其他调用者仍能等待同一次清理；Fiber 处于 <code style="color: #ef7060;">UNLOADING</code> 时拒绝创建新 effect。这些代码看起来全是繁琐的边界处理，但为了保证生命周期并发的安全，一个都不能少。否则插件的热卸载就只是个玩具，没法真正落地。</p>
<p data-tool="mdnice编辑器">依赖变化也属于时间组合。某个服务被提供后，反射层通知所有声明该依赖的 Fiber 重新检查；条件满足便激活。服务撤销后，依赖方会卸载并回到等待。OSGi 早已采用动态服务注册表，Cordis 的新意主要在于把动态依赖、作用域上下文和 effect 所有权压进一个很小的进程内模型。代价同样继承自 OSGi：任何持有服务引用越过生命周期的代码，都可能在提供方撤销后继续调用过期对象。Cordis 保存加载时的实现快照，却无法替业务代码管理逃逸引用。</p>
<p data-tool="mdnice编辑器">Loader 把时间组合延伸到配置。DeepSeek Harness 的 profile 由多层 bundle patch 叠加：基础 bundle、模式 bundle、profile patch、home patch、命令行 overlay。patch 按 id 定位条目，配置采用整块替换。整块替换要求用户重述保留字段，使用上稍显笨重，却避免深度合并规则在数组、表达式和删除语义上制造歧义。</p>
<p data-tool="mdnice编辑器">配置热更新时，Loader 先导入候选插件，再卸载旧实例并应用候选；候选失败会恢复旧插件或旧配置。Group 对一批子条目并发启动，收集全部结果，只要一项失败就删除新增项并重建旧配置。Include 读取候选文件、在副本上应用 patch、完成树协调后才提交缓存。用户 patch 语法错误或新插件启动失败时，最后一棵可用树继续运行。</p>
<p data-tool="mdnice编辑器">这已经接近配置事务，却不能等同于数据库事务。</p>
<p data-tool="mdnice编辑器">插件 effect 可能调用外部 API、创建远端资源、发送消息；disposer 只能做补偿，无法保证外部世界回滚。Loader 能保证自己管理的树和服务注册恢复，无法撤回所有不可逆副作用。插件开发规范仍要限制初始化期行为，把外部写入延迟到真正的业务请求，或者设计幂等键和补偿路径。</p>
<p data-tool="mdnice编辑器">时间组合还带来可观的测试面积。挂载成功只是第一条路径，还要覆盖依赖晚到、依赖撤销、初始化失败、卸载重入、异步清理、热更新失败、回滚再次失败。DeepSeek Harness 要求注册项证明 disposal，产品可见插件还要通过真实 Loader 组合测试。这个成本无法靠框架消失，只能被框架集中暴露。相比线上出现幽灵监听器和半更新状态，还是愿意支付这部分测试费用。</p>
<h1 data-tool="mdnice编辑器"><span class="content">事件契约</span></h1>
<p data-tool="mdnice编辑器">插件之间只靠服务调用，会把所有扩展都变成接口方法。宿主每增加一项策略，就要修改服务定义。Cordis 同时提供类型化事件，并区分 <code style="color: #ef7060;">emit</code>、<code style="color: #ef7060;">parallel</code>、<code style="color: #ef7060;">serial</code>、<code style="color: #ef7060;">bail</code> 和 <code style="color: #ef7060;">waterfall</code>。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 最依赖的是 waterfall。它的监听器拿到 <code style="color: #ef7060;">next</code>，调用后把控制权交给下一层，不调用就截断链条。模型请求、工具执行和轮次控制都能被插件包裹。审批策略可以在工具执行前拒绝，重试插件可以包围模型流，日志插件可以观察前后状态。它与 Koa 一类中间件链相似，但事件名和 Context 过滤让同一个分发器覆盖多个能力域。</p>
<p data-tool="mdnice编辑器">waterfall 也最容易出错。一个只想记录日志的监听器忘记调用 <code style="color: #ef7060;">next()</code>，整条能力链便被短路。DeepSeek Harness 把「必须调用 <code style="color: #ef7060;">next()</code>」写进项目级规则，并在事件文档里记录 dispatch mode。类型能保证参数，却很难保证 continuation 一定执行。代码评审和组合测试仍是主要防线。</p>
<p data-tool="mdnice编辑器">事件的持久性也被刻意分层。<code style="color: #ef7060;">agent/*</code> 和 <code style="color: #ef7060;">tools/*</code> 事件用于活跃运行时的拦截；会话事件追加到日志，承担恢复、fork、UI 回放和模型历史投影。DeepSeek Harness 规定「模型可见即已记录」：进入模型请求的输入必须能从 session log 重建。插件若偷偷修改 prompt 却不产生会话事实，重放结果会漂移，问题也无法审计。</p>
<p data-tool="mdnice编辑器">这条约束说明，一切皆插件并不意味着一切皆事件。直接能力调用放进 Service，策略和拦截放进实时事件，需要持久化的事实进入 session log。三类通信各自承担调用、扩展和历史。将它们混成一个全局 event bus，短期代码更少，长期会失去时序语义和数据权威。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Agent 适配</span></h1>
<p data-tool="mdnice编辑器">Agent Harness 比普通 Web 服务更需要动态组合。模型供应商会变，工具权限随工作区变化，子 Agent 的执行环境可能与父 Agent 不同，UI 还要消费同一份流式过程。如果主循环直接 import 每个能力，任何替换都会改循环；循环逐渐成为依赖最多、风险最高的文件。</p>
<p data-tool="mdnice编辑器">DeepSeek Harness 的默认 agent loop 仍然存在，但它也是一个服务提供方。循环读取 session log 生成历史，组装插件贡献的 prompt 和 tool schema，通过 <code style="color: #ef7060;">agent/request</code> 进入模型适配器，再经过工具流水线记录结果。扩展点围绕步骤、请求、工具和停止阶段布置。新功能通常挂到这些事件或注册表，项目规则甚至要求修改 agent loop 时同步更新架构文档。</p>
<p data-tool="mdnice编辑器">我赞成这种限制。循环承担控制流，频繁承载产品策略后会迅速腐化。压缩上下文、重试、审批、工具超时、计划模式、子 Agent 调度都可以有自己的生命周期与配置。它们进入循环的接口必须少且稳定。插件化不会自动获得解耦，真正起作用的是 DeepSeek Harness 为能力选择了明确的 seam，并拒绝消费方特有逻辑污染定义层。</p>
<p data-tool="mdnice编辑器">UI 作为插件也有实际意义。Web 和 headless profile 在共享 base bundle 上增加不同条目，后者可以完全不启动服务器。UI 驱动 <code style="color: #ef7060;">ctx.agents</code>，订阅 <code style="color: #ef7060;">session/event</code> 渲染状态，没必要成为循环的一部分。服务端、CLI、ACP 和浏览器界面可以共享会话及 Agent 语义，同时保留自己的传输和展示逻辑。</p>
<p data-tool="mdnice编辑器">插件树还提供自省基础。Fiber 保留名称、状态、依赖和 effect 元数据，DeepSeek Harness 能实现查看、挂载、卸载自身插件的能力。对 Agent 而言，这比普通应用更敏感：模型可以通过工具改变运行时，错误配置可能直接扩大权限。源码中的 sandbox 和 approval 仍是独立能力，插件架构没有天然安全性。自修改必须受工具权限、配置校验和作用域审计约束。</p>
<h1 data-tool="mdnice编辑器"><span class="content">架构代价</span></h1>
<p data-tool="mdnice编辑器">第一项代价是启动与运行时开销。每个插件产生 Fiber，服务访问经过 Proxy 和反射解析，事件分发要执行作用域过滤，注册项还要保存 disposer 与诊断元数据。对于 LLM Agent，单次模型请求通常以百毫秒到秒计，这些 JavaScript 调度开销很难成为主要瓶颈；高频 token chunk、文件扫描或终端字节流若全部穿过通用事件总线，成本会被放大。DeepSeek Harness 把流式模型输出定义为能力语义，但大量数据处理仍应留在具体 provider 内，事件只承载必要扩展点。</p>
<p data-tool="mdnice编辑器">第二项代价是故障面扩大。插件初始化可以同步抛错、异步拒绝、等待缺失服务，也可能在 disposer 中失败。Group 并发启动缩短时间，却会带来多个兄弟同时失败的 AggregateError。DeepSeek Harness 的 boot 会等待 Loader 结算，审计所有启用条目是否加载与激活，失败时先 dispose 部分构造的上下文，再退出。少做任何一步，都可能让终端停留在 raw mode，或者让后台 watcher 继续运行。</p>
<p data-tool="mdnice编辑器">第三项代价是配置成为编程接口。<code style="color: #ef7060;">cordis.yml</code> 支持 <code style="color: #ef7060;">!!js</code> 表达式，条目可以按环境禁用，配置会在依赖激活后求值。灵活性很高，静态分析能力随之下降。表达式读取服务时，求值时机与上下文位置都会影响结果。DeepSeek Harness 只允许 <code style="color: #ef7060;">config</code> 和 <code style="color: #ef7060;">disabled</code> 使用插值，其他元数据保持字面量，并让错误尽早暴露。这仍要求运维人员理解插件依赖和 patch 覆盖语义，配置文件已经超出普通 YAML 参数表的复杂度。</p>
<p data-tool="mdnice编辑器">第四项代价是生态兼容。Cordis 的服务名和事件类型提供了源码级协议，插件版本升级仍可能修改配置、事件 payload 或生命周期假设。DeepSeek Harness 目前处于预发布阶段，仓库规则允许直接拒绝旧磁盘格式，也没有承诺 session 格式兼容。现在的可组合性主要服务于同一发行版内的替换和扩展，尚不能推导出跨版本插件 ABI 稳定。</p>
<p data-tool="mdnice编辑器">第五项代价是组织治理。一切都能成为插件后，团队容易把每段十几行逻辑都拆成包，形成依赖图膨胀、文档分散和测试启动缓慢。DeepSeek Harness 用 package 分组、Service Definition/Provider/Consumer 角色、每包 README、运行时 invariant 和真实组合测试控制边界。这些规范本身就是成本。小团队若没有维护扩展生态或多 profile 的需求，模块化函数加显式依赖可能更合适。</p>
<h1 data-tool="mdnice编辑器"><span class="content">历史对照</span></h1>
<p data-tool="mdnice编辑器">从 OSGi 看 Cordis，动态服务和生命周期联动已有先例。服务注册、发现、撤销后触发依赖变化，这条主线几乎一致。Cordis 值得学习的新点，是把资源清理统一成 effect，并让子插件、服务、监听器都归属于同一个 Fiber。OSGi 的 bundle 生命周期更完整，也更重；Cordis 选择应用内细粒度对象，失去了类加载隔离、标准版本解析和安全层，获得了低门槛组合。</p>
<p data-tool="mdnice编辑器">从 Eclipse 看 DeepSeek Harness，profile 与 bundle patch 很像部署时组装，Service 和事件类似扩展点。差异在于 Eclipse 扩展通常围绕稳定宿主能力，DeepSeek Harness 连 agent loop 和 UI 都可替换，核心插件与第三方插件共享同一挂载机制。这个平权减少了特权内核，风险也更集中到协议治理：循环能被替换，不代表任何替代循环都遵守 session log、权限和工具时序约束。</p>
<p data-tool="mdnice编辑器">从依赖注入容器看，Cordis 的服务解析并不陌生。新的组合来自 DI 与生命周期的绑定。普通容器负责构造对象，定时器、监听器和子进程仍由业务代码清理；Cordis 把注册动作收敛成 effect。空间 scope 与时间 ownership 同时存在，插件才能在某个 Agent 范围内挂载，并随该 Agent 完整撤销。</p>
<p data-tool="mdnice编辑器">从微内核看，DeepSeek Harness 的内核确实很轻，但其 Loader 和配置协调已经承担不少平台职责。它要解析模块、等待依赖、处理 HMR、执行事务式更新、保存最后可用树、输出诊断。这提醒我，微内核减少的是业务特权，不会减少生命周期复杂度。能力越动态，内核对失败语义的要求越高。</p>
<p data-tool="mdnice编辑器">Cordis 的贡献可以概括为一种紧凑的组合坐标：Context 决定插件位于哪里，Fiber 决定插件在何时有效，Service 负责直接能力，Event 负责横切协作，effect 负责撤销。DeepSeek Harness 在这个坐标系上加入 Agent scope、持久会话事件和配置事务，使其能承载多会话、多 profile 与热更新。</p>
<h1 data-tool="mdnice编辑器"><span class="content">工程取舍</span></h1>
<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>
<p data-tool="mdnice编辑器">能力切分要从消费方开始。先列出谁调用、调用时需要哪些稳定语义，再设计 Service Definition；随后实现 provider，最后把面向模型的 schema、展示和错误文本留给 consumer。接口只有一个内部调用者时，保留私有闭包，不急着升格为公共 service。可替换性必须由真实替代者证明。</p>
<p data-tool="mdnice编辑器">所有注册都要有明确所有者。注册工具、提示词片段、适配器、监听器时同步返回 disposer；卸载测试要观察注册项确实消失。涉及异步资源时，dispose 完成的定义要包含子进程退出、队列排空和 watcher 关闭，不能只发出取消信号。DeepSeek Harness 对 Fiber quiescence 的处理值得照搬。</p>
<p data-tool="mdnice编辑器">配置更新要区分内存一致性和外部副作用。Loader 可以恢复旧树，插件初始化若已经创建云资源，回滚需要业务补偿。规则可更保守一些：挂载阶段只做校验和本地注册，外部写操作放到带幂等标识的运行阶段。确实要在初始化创建资源时，必须让 disposer 能识别部分完成状态。</p>
<p data-tool="mdnice编辑器">作用域要控制在两种以内。实例隔离与注册可见性已经足够表达多数 Agent 场景，再增加租户、请求、工作区、角色四套独立 scope，调试成本会失控，需要根据实际业务需求和必要性来判断。当需要新增维度时，先判断它属于服务实例选择、注册过滤、持久数据权限还是请求参数。很多所谓新 scope，其实只是一次显式参数传递。</p>
<p data-tool="mdnice编辑器">事件也要克制。需要唯一返回值的调用用 Service，需要持久恢复的事实写 session log，需要多个插件观察或包裹的阶段才用事件。waterfall 必须把短路当成公共协议，监听器顺序也要有测试。事件名虽然能降低导入耦合，却会增加时序耦合；后者通常更难排查。</p>
<h1 data-tool="mdnice编辑器"><span class="content">小结</span></h1>
<p data-tool="mdnice编辑器">DeepSeek Harness 选择 Cordis，解决的主要矛盾是 Agent 能力变化速度与运行时一致性之间的冲突。模型、工具、沙箱和 UI 都可以替换并不稀奇；这些能力可以在局部作用域内共存，能随依赖变化启停，能在配置失败后保留旧树，卸载时还能回收监听器、服务和子插件，才构成可用的插件架构。</p>
<p data-tool="mdnice编辑器">它也没有抹平工程风险。Proxy 隐藏了查找路径，双层 scope 增加理解成本，热更新无法回滚任意外部副作用，预发布阶段也缺少跨版本兼容承诺。团队采用这套思路时，应复制它的生命周期纪律和能力边界，别只复制「一切皆插件」的目录结构。</p>
<p data-tool="mdnice编辑器">源码给我们一些启发，把插件设计的评审顺序倒过来：先问如何撤销，再问如何注册；先证明局部挂载不会泄漏，再讨论全局复用；先定义失败后保留哪一棵树，再讨论热更新速度。做到这些，时空可组合才是一组可以验证的运行时语义。</p>
<p data-tool="mdnice编辑器">这套架构个人理解是想往 Agent 的实时「自生长」，通过 AI 的能力在使用过程中让自己更强大。</p>
<p data-tool="mdnice编辑器">逼逼这么多，主要还是在这个过程中学习一下，说实话，有了 DeepSeek Harness，想自己开发一个专属的 Agent ，快捷方便了很多，eg且这是 MIT 协议的。</p>
<p data-tool="mdnice编辑器">以上。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">参考资料</span></h2>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><a style="font-weight: bold; color: #ef7060;" href="https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.zh.md">DeepSeek Harness 架构文档</a></section>
</li>
<li>
<section style="color: #010101;"><a style="font-weight: bold; color: #ef7060;" href="https://github.com/cordiverse/cordis">Cordis：Meta-Framework of Spatiotemporal Composability</a></section>
</li>
<li>
<section style="color: #010101;"><a style="font-weight: bold; color: #ef7060;" href="https://osgi.github.io/osgi/core/framework.service.html">OSGi Service Layer</a></section>
</li>
<li>
<section style="color: #010101;"><a style="font-weight: bold; color: #ef7060;" href="https://www.osgi.org/resources/architecture/">OSGi Architecture</a></section>
</li>
<li>
<section style="color: #010101;"><a style="font-weight: bold; color: #ef7060;" href="https://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html">Eclipse Plug-in Architecture</a></section>
</li>
</ul>
</section>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/08/deepseek-harness-cordis/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从 Pi 到 DSH：Agent Harness 如何从「可扩展」走向「自生长」</title>
		<link>https://www.phppan.com/2026/08/from-pi-to-dsh-how-agent-harness-evolves-from-scalable-to-self-growing/</link>
		<comments>https://www.phppan.com/2026/08/from-pi-to-dsh-how-agent-harness-evolves-from-scalable-to-self-growing/#comments</comments>
		<pubDate>Sun, 16 Aug 2026 05:16:50 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[DSH]]></category>
		<category><![CDATA[harness engineering]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2528</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 系统的竞争焦点正在发生变化。</p>
<p data-tool="mdnice编辑器">过去，我们主要关心模型能不能理解任务、调用工具、编写代码。现在，更重要的问题逐渐变成：<strong>模型之外的系统，能否为 Agent 提供一个可以持续变化的运行环境？</strong></p>
<p data-tool="mdnice编辑器">这个运行环境通常被称为 <strong>Agent Harness</strong>。</p>
<p data-tool="mdnice编辑器">Harness 负责把模型、提示词、工具、记忆、上下文、权限和外部环境连接起来。它决定模型能够看到什么、调用什么、保存什么，以及一次行动会产生怎样的系统影响。</p>
<p data-tool="mdnice编辑器">从这个角度看，Agent 的能力并不只来自模型，用一个公式来表达，大概如下：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Agent 能力
=
模型能力
× 上下文组织
× 工具系统
× 运行时结构
× 生命周期治理
</code></pre>
<p data-tool="mdnice编辑器">如果 Harness 是固定的，那么即使模型越来越强，Agent 的能力边界仍然主要由开发者预先决定。</p>
<p data-tool="mdnice编辑器">如果 Harness 可以扩展，用户和开发者就能不断为 Agent 增加工具、技能和工作流。</p>
<p data-tool="mdnice编辑器">但如果我们希望 Agent 进一步具备「自生长」能力，仅仅提供扩展接口还不够。系统还必须允许 Agent：</p>
<ol 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>
</ol>
<p data-tool="mdnice编辑器">Pi 与 DSH 可以被视为这条演进路径上的两个阶段。</p>
<p data-tool="mdnice编辑器">Pi 回答的是：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>如何构建一个足够小、足够开放，可以持续扩展的 Agent Harness？</p></blockquote>
<p data-tool="mdnice编辑器">DSH 进一步追问：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>如何让 Agent 不只是使用外部提供的扩展，而是安全地参与自身能力结构的形成？</p></blockquote>
<p data-tool="mdnice编辑器">这不是从一种插件格式切换到另一种插件格式，而是 Agent Harness 从<strong>开放扩展平台</strong>向<strong>能力生长环境</strong>的转变。</p>
<h1 data-tool="mdnice编辑器"><span class="content">1. 什么是 Agent Harness</span></h1>
<p data-tool="mdnice编辑器">一个最简单的 Agent，可以被表示为：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Model
  ↓
Prompt
  ↓
Tool Call
  ↓
Environment
</code></pre>
<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>
<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>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">                ┌─────────────┐
                │    Model    │
                └──────┬──────┘
                       │
                ┌──────▼──────┐
                │ Agent Loop  │
                └──────┬──────┘
                       │
       ┌───────────────┼────────────────┐
       │               │                │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│    Tools    │ │    Memory   │ │   Context   │
└─────────────┘ └─────────────┘ └─────────────┘
       │               │                │
       └───────────────┼────────────────┘
                       │
                ┌──────▼──────┐
                │ Environment │
                └─────────────┘
</code></pre>
<p data-tool="mdnice编辑器">Harness 并不是模型外面的一层简单包装。</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编辑器">因此，当我们讨论 Agent 是否可以扩展、重构甚至自生长时，本质上讨论的不是模型参数如何变化，而是 <strong>Harness 能否安全地改变自身结构</strong>。</p>
<h1 data-tool="mdnice编辑器"><span class="content">2. Pi：以最小内核换取最大扩展空间</span></h1>
<p data-tool="mdnice编辑器">Pi 的核心思想非常明确：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>不要预先替用户决定完整工作流，而是提供足够小的基础能力，让用户自己构建需要的 Harness。</p></blockquote>
<p data-tool="mdnice编辑器">Pi 将自己定义为一个最小化的 Agent Harness，并通过 Extensions、Skills、Prompt Templates、Themes 和 Packages 提供扩展能力。它允许用户修改工具、命令、模型提供商、工作流、上下文处理和终端界面，也支持将这些资源打包后通过 npm 或 Git 分发。(<a style="font-weight: bold; color: #ef7060;" href="https://pi.dev/?via=topaitools">pi.dev</a>)</p>
<p data-tool="mdnice编辑器">这背后是一种「原语优先」的设计哲学：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">不是：
内置所有功能

而是：
提供少量原语
+
开放扩展接口
+
允许用户自行组合
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">2.1 最小化不是能力不足</span></h2>
<p data-tool="mdnice编辑器">传统 Agent 产品通常倾向于内置更多功能：</p>
<ul data-tool="mdnice编辑器">
<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;">MCP 集成；</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;">Todo 系统。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Pi 选择了另一条路线：这些能力不一定需要成为内核的一部分，它们可以通过扩展或外部环境实现。</p>
<p data-tool="mdnice编辑器">Pi 当前提供文件读取、文件修改、内容搜索和命令执行等内置工具，同时允许通过命令行配置工具集合，也允许 Extension 注册新的工具或覆盖现有工具。(<a style="font-weight: bold; color: #ef7060;" href="https://pi.dev/docs/latest/usage?utm_source=openai">pi.dev</a>)</p>
<p data-tool="mdnice编辑器">这种设计的意义把稳定的内核和变化的能力分开：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">稳定部分：
模型调用、Agent Loop、会话、基础工具、扩展 API

变化部分：
工具、技能、上下文、工作流、权限策略、界面、模型路由
</code></pre>
<p data-tool="mdnice编辑器">只要扩展接口足够开放，功能就不必全部固化在核心代码中。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">2.2 Pi 的四类扩展资源</span></h2>
<p data-tool="mdnice编辑器">从 Harness 的角度看，Pi 的可扩展性主要体现在以下几个层面。</p>
<h3 data-tool="mdnice编辑器"><span class="content">2.2.1 Extensions：改变运行时行为</span></h3>
<p data-tool="mdnice编辑器">Extension 是可以进入 Agent 运行时的代码模块。</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;">实现自定义 UI；</section>
</li>
<li>
<section style="color: #010101;">覆盖内置工具；</section>
</li>
<li>
<section style="color: #010101;">连接外部系统。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">因此，Extension 改变的不只是模型“知道什么”，还可以直接改变 Harness“如何运行”。</p>
<h3 data-tool="mdnice编辑器"><span class="content">2.2.2 Skills：注入按需能力</span></h3>
<p data-tool="mdnice编辑器">Skill 更接近一种可复用的过程知识。</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编辑器">Pi 将 Skill 设计为按需加载的能力包，使 Agent 不必在每次会话开始时把所有知识都放进上下文。(<a style="font-weight: bold; color: #ef7060;" href="https://pi.dev/?via=topaitools">pi.dev</a>)</p>
<h3 data-tool="mdnice编辑器"><span class="content">2.2.3 Prompt Templates：固化交互模式</span></h3>
<p data-tool="mdnice编辑器">Prompt Template 将常见任务封装成可复用提示词，例如：</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编辑器">它改变的是任务入口和交互结构。</p>
<h3 data-tool="mdnice编辑器"><span class="content">2.2.4 Packages：分发完整能力</span></h3>
<p data-tool="mdnice编辑器">Package 可以将 Extensions、Skills、Prompt Templates 和 Themes 组合起来，通过 npm、Git 或本地路径安装。Pi 还支持临时加载 Package，使组件只对当前运行生效。(<a style="font-weight: bold; color: #ef7060;" href="https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/packages.md">github.com</a>)</p>
<p data-tool="mdnice编辑器">这使 Pi 不只是一个 Agent，也成为一个 Harness 能力分发平台：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Pi Package
├── Extensions
├── Skills
├── Prompts
└── Themes
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">2.3 Pi 的关键突破：Agent 可以参与修改 Harness</span></h2>
<p data-tool="mdnice编辑器">Pi 最值得关注的地方，不只是「插件多」。</p>
<p data-tool="mdnice编辑器">它的官方描述明确鼓励用户让 Pi 编写自己需要的扩展，并在修改后通过重新加载继续当前工作。(<a style="font-weight: bold; color: #ef7060;" href="https://pi.dev/?via=topaitools">pi.dev</a>)</p>
<p data-tool="mdnice编辑器">这意味着一种新的工作方式正在出现：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Agent 发现缺少某项能力
        ↓
Agent 编写 Extension
        ↓
用户检查或确认
        ↓
重新加载 Harness
        ↓
Agent 使用新增能力继续任务
</code></pre>
<p data-tool="mdnice编辑器">过去，Agent 与开发工具之间的关系通常是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">人类开发 Harness
Agent 使用 Harness
</code></pre>
<p data-tool="mdnice编辑器">在 Pi 中，它开始变成：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">人类定义目标
Agent 修改 Harness
Agent 使用修改后的 Harness
</code></pre>
<p data-tool="mdnice编辑器">这是从「可扩展」走向「自生长」的重要一步。</p>
<p data-tool="mdnice编辑器">但是，这还不等于完整的自生长。</p>
<h1 data-tool="mdnice编辑器"><span class="content">3. 行为自治不等于结构自治</span></h1>
<p data-tool="mdnice编辑器">理解 Pi 与 DSH 的关系，需要区分两种不同的自治。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">3.1 行为自治</span></h2>
<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编辑器">此时，Agent 的选择空间是动态的，但能力集合通常是预先确定的。</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">固定工具集合
    ↓
Agent 自主选择工具
    ↓
完成任务
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">3.2 结构自治</span></h2>
<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;">替换 Memory；</section>
</li>
<li>
<section style="color: #010101;">修改上下文策略；</section>
</li>
<li>
<section style="color: #010101;">调整模型 Router；</section>
</li>
<li>
<section style="color: #010101;">增加权限规则；</section>
</li>
<li>
<section style="color: #010101;">组合新的执行流程；</section>
</li>
<li>
<section style="color: #010101;">卸载无效组件。</section>
</li>
</ul>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">当前能力结构
    ↓
Agent 发现能力缺口
    ↓
生成候选组件
    ↓
改变 Harness
    ↓
形成新的能力结构
</code></pre>
<p data-tool="mdnice编辑器">Pi 已经为结构自治打开了入口。</p>
<p data-tool="mdnice编辑器">但它的扩展机制主要回答的是：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>如何把一项新能力加入系统？</p></blockquote>
<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>
<li>
<section style="color: #010101;">如何淘汰不再适用的组件？</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些问题将我们从扩展系统带到了组件运行时。</p>
<h1 data-tool="mdnice编辑器"><span class="content">4. DSH：为 Harness 自生长建立运行时基础</span></h1>
<p data-tool="mdnice编辑器">本文讨论的 DSH，不只是另一个插件系统。</p>
<p data-tool="mdnice编辑器">它试图将 Agent Harness 表达为一个可以在运行中不断组合和重组的组件系统。</p>
<p data-tool="mdnice编辑器">其基本结构可以概括为：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Component
=
Context
+
Effect
+
Cleanup
+
Coeffects
</code></pre>
<p data-tool="mdnice编辑器">这四个概念分别回答四个问题：</p>
<section class="table-container" data-tool="mdnice编辑器">
<table>
<thead>
<tr>
<th style="color: #000000;">概念</th>
<th style="color: #000000;">回答的问题</th>
</tr>
</thead>
<tbody>
<tr style="color: #000000;">
<td>Context</td>
<td>组件可以看到和使用什么？</td>
</tr>
<tr style="color: #000000;">
<td>Effect</td>
<td>组件加入系统后会产生什么影响？</td>
</tr>
<tr style="color: #000000;">
<td>Cleanup</td>
<td>组件退出时如何撤销影响？</td>
</tr>
<tr style="color: #000000;">
<td>Coeffects</td>
<td>组件依赖哪些外部能力或其他组件？</td>
</tr>
</tbody>
</table>
</section>
<p data-tool="mdnice编辑器">DSH 的目标不是让所有模块都能任意修改系统，而是让变化成为一种<strong>显式、可追踪、可逆的运行时操作</strong>。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">4.1 Context：为能力提供明确边界</span></h2>
<p data-tool="mdnice编辑器">一个组件不能直接访问所有系统状态。</p>
<p data-tool="mdnice编辑器">它应该通过 Context 获取所需能力，例如：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Context
├── Model Registry
├── Tool Registry
├── Memory
├── Event Bus
├── Session
├── Logger
├── Storage
├── Permission
└── Environment
</code></pre>
<p data-tool="mdnice编辑器">Context 的第一层作用是解耦。</p>
<p data-tool="mdnice编辑器">组件不需要知道某个能力的具体实现，只需要知道运行环境向它提供了什么。</p>
<p data-tool="mdnice编辑器">例如，一个模型路由组件可能只依赖：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Model Registry
Task Metadata
Usage Metrics
</code></pre>
<p data-tool="mdnice编辑器">一个长期记忆组件可能只依赖：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Session Events
Embedding Service
Storage
Context Injector
</code></pre>
<p data-tool="mdnice编辑器">一个工具组件则可能只依赖：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Tool Registry
Credentials
Network
Logger
</code></pre>
<p data-tool="mdnice编辑器">Context 的第二层作用是限制能力边界。</p>
<p data-tool="mdnice编辑器">如果组件只能通过 Context 访问环境，那么系统就可以决定：</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;">哪些操作只能在 Sandbox 中执行。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">因此，Context 不只是依赖注入，也是一种能力安全模型：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">组件能做什么
≈
Context 向它暴露什么
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">4.2 Effect 与 Cleanup：让变化可以被撤销</span></h2>
<p data-tool="mdnice编辑器">当组件加入 Harness 时，通常会产生副作用。</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>
<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编辑器">这些变化可以统一理解为 Effect：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Component
    ↓
Effect
    ↓
Harness 状态发生变化
</code></pre>
<p data-tool="mdnice编辑器">问题在于，大多数插件系统只强调如何创建 Effect，却没有把撤销 Effect 当作同等重要的能力。</p>
<p data-tool="mdnice编辑器">于是，组件卸载后可能出现：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">工具仍然留在 Registry 中；</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;">Context 中残留失效状态；</section>
</li>
<li>
<section style="color: #010101;">新旧路由规则同时生效。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">在 DSH 中，Effect 应该返回对应的 Cleanup：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Effect(Context) → Cleanup
</code></pre>
<p data-tool="mdnice编辑器">例如：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">activate</span>(<span class="hljs-params">context</span>) </span>{
  <span class="hljs-keyword" style="color: #c678dd;">const</span> unregister = context.tools.register(myTool)

  <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">cleanup</span>() </span>{
    unregister()
  }
}
</code></pre>
<p data-tool="mdnice编辑器">更复杂的组件可能产生多个副作用：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">activate</span>(<span class="hljs-params">context</span>) </span>{
  <span class="hljs-keyword" style="color: #c678dd;">const</span> stopTool = registerTool(context)
  <span class="hljs-keyword" style="color: #c678dd;">const</span> stopListener = registerListener(context)
  <span class="hljs-keyword" style="color: #c678dd;">const</span> stopWorker = startWorker(context)
  <span class="hljs-keyword" style="color: #c678dd;">const</span> closeConnection = connectDatabase(context)

  <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-keyword" style="color: #c678dd;">async</span> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">cleanup</span>() </span>{
    <span class="hljs-keyword" style="color: #c678dd;">await</span> stopWorker()
    stopListener()
    stopTool()
    <span class="hljs-keyword" style="color: #c678dd;">await</span> closeConnection()
  }
}
</code></pre>
<p data-tool="mdnice编辑器">这使组件具备完整生命周期：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">创建
  ↓
激活
  ↓
运行
  ↓
停用
  ↓
清理
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">4.3 时间可组合性：让 Agent 有资格试错</span></h2>
<p data-tool="mdnice编辑器">Effect 与 Cleanup 提供的是一种<strong>时间可组合性</strong>。</p>
<p data-tool="mdnice编辑器">一个组件可以在某个时刻进入系统，也可以在另一个时刻完整退出：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">t₀：Harness 原始状态

t₁：组件进入
    → 注册工具
    → 启动服务
    → 注入上下文

t₂：组件运行

t₃：组件退出
    → 移除工具
    → 停止服务
    → 撤销上下文

t₄：Harness 恢复到可预测状态
</code></pre>
<p data-tool="mdnice编辑器">这对普通插件系统很重要，但对自生长系统更重要。</p>
<p data-tool="mdnice编辑器">因为自生长必然包含试错。</p>
<p data-tool="mdnice编辑器">Agent 创建的新工具、新路由器、新记忆策略，不可能每次都正确。如果一次失败的尝试不能被完整撤销，那么每次实验都会向系统中留下残余状态。</p>
<p data-tool="mdnice编辑器">最终，所谓“生长”就会退化成不可控的复杂度积累。</p>
<p data-tool="mdnice编辑器">因此，自生长的基本过程不应该是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">生成组件
  ↓
永久安装
</code></pre>
<p data-tool="mdnice编辑器">而应该是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">生成候选组件
      ↓
执行受控 Effect
      ↓
验证运行结果
   ┌──┴──┐
 成功    失败
  │       │
保留   Cleanup
</code></pre>
<p data-tool="mdnice编辑器">Cleanup 的意义不只是“支持热卸载”。</p>
<p data-tool="mdnice编辑器">它赋予 Agent 一种更关键的能力：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>在不永久污染系统的前提下试验新的自身结构。</p></blockquote>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">4.4 Coeffects：让依赖关系成为运行时的一部分</span></h2>
<p data-tool="mdnice编辑器">时间可组合性解决的是组件何时进入、何时退出。</p>
<p data-tool="mdnice编辑器">但 Agent Harness 的组件并不是彼此孤立的。</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;">替换一个 Provider 会影响哪些组件？</section>
</li>
<li>
<section style="color: #010101;">哪些组件可以被安全卸载？</section>
</li>
<li>
<section style="color: #010101;">一个候选组件是否与当前环境兼容？</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Coeffects 用于显式表达组件所需的外部条件：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Effect：
组件对系统做什么

Coeffects：
组件需要系统提供什么
</code></pre>
<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> vectorMemory = {
  coeffects: [
    <span class="hljs-string" style="color: #98c379;">"embedding-model"</span>,
    <span class="hljs-string" style="color: #98c379;">"vector-storage"</span>,
    <span class="hljs-string" style="color: #98c379;">"session-events"</span>
  ],

  effect(context) {
    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 激活 Memory</span>
    <span class="hljs-keyword" style="color: #c678dd;">return</span> cleanup
  }
}
</code></pre>
<p data-tool="mdnice编辑器">运行时可以根据当前 Context 判断组件是否具备激活条件：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">依赖全部满足
    ↓
组件激活

依赖不完整
    ↓
组件保持未激活

依赖运行中消失
    ↓
执行 Cleanup
</code></pre>
<p data-tool="mdnice编辑器">这使 Harness 不再只是一个插件列表，而是一个动态依赖图：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Credential
    ↓
GitHub Provider
    ↓
GitHub Tool
    ↓
Repository Agent
</code></pre>
<p data-tool="mdnice编辑器">或者：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Embedding Model ─┐
                 ├→ Vector Memory → Context Injector
Vector Storage ──┘
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">4.5 空间可组合性：让生长不是无序堆积</span></h2>
<p data-tool="mdnice编辑器">Coeffects 提供的是一种<strong>空间可组合性</strong>。</p>
<p data-tool="mdnice编辑器">组件不是按照固定顺序硬编码在系统中，而是根据能力和依赖形成运行时拓扑。</p>
<p data-tool="mdnice编辑器">当一个能力出现时，依赖它的组件可以被激活；当这个能力消失时，相关组件可以被停用：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Sandbox 出现
    ↓
Code Executor 激活
    ↓
Test Runner 激活
    ↓
Coding Agent 获得安全执行能力
</code></pre>
<p data-tool="mdnice编辑器">如果 Sandbox 被移除：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Sandbox 消失
    ↓
Code Executor Cleanup
    ↓
Test Runner Cleanup
    ↓
相关工具从 Agent 能力集合中撤销
</code></pre>
<p data-tool="mdnice编辑器">这与传统插件系统有一个关键区别。</p>
<p data-tool="mdnice编辑器">传统插件系统更像：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">安装了什么
=
系统拥有什么
</code></pre>
<p data-tool="mdnice编辑器">而动态组件运行时更像：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">当前可用能力
=
当前组件
× 当前依赖
× 当前环境
× 当前权限
</code></pre>
<p data-tool="mdnice编辑器">这使 Harness 的结构可以随着任务和环境变化，而不是不断向全局系统中永久添加模块。</p>
<p data-tool="mdnice编辑器">真正的生长，不是组件数量持续增加，而是系统能够形成更适合当前任务的能力结构。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">4.6 从动态重组到自生长闭环</span></h2>
<p data-tool="mdnice编辑器">Context、Effect、Cleanup 和 Coeffects 为动态重组提供了基础。</p>
<p data-tool="mdnice编辑器">但必须强调：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>动态重组是自生长的必要条件，不是自生长的全部。</p></blockquote>
<p data-tool="mdnice编辑器">完整的自生长至少需要五个阶段。</p>
<h3 data-tool="mdnice编辑器"><span class="content">4.6.1 第一阶段：发现能力缺口</span></h3>
<p data-tool="mdnice编辑器">Agent 首先要判断自己缺少什么。</p>
<p data-tool="mdnice编辑器">例如：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">当前没有访问某类数据的工具；</section>
</li>
<li>
<section style="color: #010101;">现有 Tool 无法处理特定协议；</section>
</li>
<li>
<section style="color: #010101;">Memory 无法支持长期任务；</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编辑器">它还可能来自：</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编辑器">因此，自生长的起点不是生成代码，而是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">目标要求
-
当前能力
=
能力缺口
</code></pre>
<h3 data-tool="mdnice编辑器"><span class="content">4.6.2 第二阶段：生成或引入候选能力</span></h3>
<p data-tool="mdnice编辑器">识别缺口后，Agent 可以选择不同方式补齐能力：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">编写新的 Tool；</section>
</li>
<li>
<section style="color: #010101;">生成新的 Skill；</section>
</li>
<li>
<section style="color: #010101;">安装已有 Package；</section>
</li>
<li>
<section style="color: #010101;">包装外部 API；</section>
</li>
<li>
<section style="color: #010101;">组合已有组件；</section>
</li>
<li>
<section style="color: #010101;">替换 Memory Provider；</section>
</li>
<li>
<section style="color: #010101;">调整模型 Router；</section>
</li>
<li>
<section style="color: #010101;">创建新的工作流；</section>
</li>
<li>
<section style="color: #010101;">启动一个专用子 Agent；</section>
</li>
<li>
<section style="color: #010101;">为现有组件增加适配层。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这里的关键词是「候选」。</p>
<p data-tool="mdnice编辑器">Agent 生成的组件不能因为能够编译或加载，就自动成为系统的永久能力。</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">生成成功
≠
能力有效
≠
适合长期保留
</code></pre>
<h3 data-tool="mdnice编辑器"><span class="content">4.6.3 第三阶段：在受控环境中激活</span></h3>
<p data-tool="mdnice编辑器">候选组件应该先进入受限 Context。</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>
<li>
<section style="color: #010101;">哪些副作用必须登记。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这一点尤其重要，因为扩展代码通常拥有较高权限。</p>
<p data-tool="mdnice编辑器">以 Pi 为例，其官方安全文档明确说明，内置工具和 Extension 默认继承启动 Pi 的进程权限；第三方 Package 可能执行代码或指示模型执行操作，因此需要代码审查，并在需要时依赖容器或虚拟化提供真正的隔离边界。(<a style="font-weight: bold; color: #ef7060;" href="https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/packages.md">github.com</a>)</p>
<p data-tool="mdnice编辑器">如果 Agent 开始自行生成和加载组件，权限问题只会更加突出。</p>
<p data-tool="mdnice编辑器">所以，自生长必须建立在受控运行时上，而不能建立在「默认信任所有新代码」之上。</p>
<h3 data-tool="mdnice编辑器"><span class="content">4.6.4 第四阶段：验证、保留或回滚</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>
<li>
<section style="color: #010101;">是否造成明显的上下文膨胀；</section>
</li>
<li>
<section style="color: #010101;">是否与现有组件冲突；</section>
</li>
<li>
<section style="color: #010101;">Cleanup 是否完整。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">验证结果通常有三种：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">验证通过
    ↓
保留或固化

部分通过
    ↓
修改后重新试验

验证失败
    ↓
Cleanup + 回滚
</code></pre>
<p data-tool="mdnice编辑器">因此，自生长不是一次性的代码生成，而是一个选择过程：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Generate
→ Activate
→ Observe
→ Evaluate
→ Select
</code></pre>
<p data-tool="mdnice编辑器">没有评估的生成，只是变化。</p>
<p data-tool="mdnice编辑器">经过评估和选择的变化，才可能成为生长。</p>
<h3 data-tool="mdnice编辑器"><span class="content">4.6.5 第五阶段：沉淀、复用与淘汰</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>
<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>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Component
├── Implementation
├── Capability Description
├── Coeffects
├── Permissions
├── Validation Records
├── Version
├── Provenance
├── Metrics
├── Effect
└── Cleanup
</code></pre>
<p data-tool="mdnice编辑器">真正的自生长闭环应当是：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">发现缺口
  ↓
生成候选能力
  ↓
隔离激活
  ↓
观察与验证
  ↓
保留或回滚
  ↓
沉淀为可复用组件
  ↓
持续评估
  ↓
升级或淘汰
</code></pre>
<hr data-tool="mdnice编辑器" />
<h1 data-tool="mdnice编辑器"><span class="content">5. Pi 与 DSH 的核心差异</span></h1>
<p data-tool="mdnice编辑器">Pi 与 DSH 并不是简单的替代关系。</p>
<p data-tool="mdnice编辑器">它们关注的是两个不同层次的问题。</p>
<section class="table-container" data-tool="mdnice编辑器">
<table>
<thead>
<tr>
<th style="color: #000000;">维度</th>
<th style="color: #000000;">Pi</th>
<th style="color: #000000;">DSH</th>
</tr>
</thead>
<tbody>
<tr style="color: #000000;">
<td>核心目标</td>
<td>构建开放、可定制的 Agent Harness</td>
<td>构建支持动态变化的组件运行时</td>
</tr>
<tr style="color: #000000;">
<td>能力来源</td>
<td>Extensions、Skills、Prompts、Packages</td>
<td>运行时组件及其依赖关系</td>
</tr>
<tr style="color: #000000;">
<td>主要变化发起者</td>
<td>用户、开发者，也可以由 Agent 辅助</td>
<td>用户、系统或 Agent</td>
</tr>
<tr style="color: #000000;">
<td>组件组织方式</td>
<td>扩展资源与 Package</td>
<td>动态依赖图</td>
</tr>
<tr style="color: #000000;">
<td>激活方式</td>
<td>启动加载、临时加载或重新加载</td>
<td>根据 Context 与 Coeffects 激活</td>
</tr>
<tr style="color: #000000;">
<td>生命周期</td>
<td>以扩展加载和配置为中心</td>
<td>Effect 与 Cleanup 对称管理</td>
</tr>
<tr style="color: #000000;">
<td>依赖治理</td>
<td>主要由扩展代码和包依赖处理</td>
<td>将运行时能力依赖显式化</td>
</tr>
<tr style="color: #000000;">
<td>失败处理</td>
<td>依赖扩展实现、进程或会话边界</td>
<td>通过 Cleanup 支持组件级回滚</td>
</tr>
<tr style="color: #000000;">
<td>演化方向</td>
<td>从固定产品走向开放平台</td>
<td>从开放平台走向受控自生长</td>
</tr>
<tr style="color: #000000;">
<td>核心价值</td>
<td>让 Harness 容易被改变</td>
<td>让 Harness 可以安全地持续改变</td>
</tr>
</tbody>
</table>
</section>
<p data-tool="mdnice编辑器">可以把两者的关系概括为：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Pi：
稳定核心
+
开放扩展点
+
能力分发机制

DSH：
组件化 Context
+
显式 Coeffects
+
可逆 Effect
+
动态生命周期
</code></pre>
<p data-tool="mdnice编辑器">Pi 解决了「能力能否加入」的问题。</p>
<p data-tool="mdnice编辑器">DSH 更关注「能力如何进入、如何协作、如何退出，以及如何在运行时形成新的结构」。</p>
<h1 data-tool="mdnice编辑器"><span class="content">6. 从可扩展到自生长，中间还差什么</span></h1>
<p data-tool="mdnice编辑器">可以把 Agent Harness 的演进分为三个阶段。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">6.1 第一阶段：固定能力</span></h2>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">模型
+
固定提示词
+
固定工具
</code></pre>
<p data-tool="mdnice编辑器">系统能够执行任务，但能力边界在设计阶段已经确定。</p>
<p data-tool="mdnice编辑器">如果缺少能力，只能由开发者修改产品代码并重新发布。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">6.2 第二阶段：开放扩展</span></h2>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">稳定核心
+
Extensions
+
Skills
+
Packages
+
开放 API
</code></pre>
<p data-tool="mdnice编辑器">用户和开发者可以持续增加能力。</p>
<p data-tool="mdnice编辑器">Agent 也可以帮助编写扩展，但扩展的发现、安装、验证和维护通常仍然依赖人工流程。</p>
<p data-tool="mdnice编辑器">Pi 代表了这一阶段的重要方向：Harness 不再是一个封闭产品，而成为可以根据个人工作流持续定制的平台。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #ffffff;">6.3 第三阶段：受控自生长</span></h2>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">稳定内核
+
能力缺口识别
+
候选组件生成
+
动态组件运行时
+
显式依赖
+
可逆副作用
+
隔离验证
+
自动回滚
+
能力沉淀
+
持续淘汰
</code></pre>
<p data-tool="mdnice编辑器">Agent 不再只是等待外部为它安装能力。</p>
<p data-tool="mdnice编辑器">它可以根据任务识别不足，提出新的能力结构，在受控环境中试验，并根据结果决定保留、修改还是撤销。</p>
<p data-tool="mdnice编辑器">这时，Agent 的角色从能力消费者变成了能力形成过程的参与者。</p>
<h1 data-tool="mdnice编辑器"><span class="content">7. 自生长不等于无限增长</span></h1>
<p data-tool="mdnice编辑器">「自生长」并不是 Agent 不断为自己增加工具和代码。</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>
<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;">Agent 无法判断应该使用哪个工具；</section>
</li>
<li>
<section style="color: #010101;">系统行为逐渐不可预测。</section>
</li>
</ul>
<p data-tool="mdnice编辑器">因此，自生长必须同时包含四种能力：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">增加
+
选择
+
清理
+
遗忘
</code></pre>
<p data-tool="mdnice编辑器">可以进一步表示为：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">可持续生长
=
生成新能力
-
淘汰无效能力
+
重组已有能力
</code></pre>
<p data-tool="mdnice编辑器">这也是 Cleanup 和生命周期管理的重要性所在。</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编辑器">遗忘不是生长的反面。</p>
<p data-tool="mdnice编辑器"><strong>受控遗忘本身就是生长的一部分。</strong></p>
<h1 data-tool="mdnice编辑器"><span class="content">9. Pi 与 DSH 不是替代，而是演进</span></h1>
<p data-tool="mdnice编辑器">Pi 的价值在于证明了一件事：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>Agent Harness 不需要是一个功能固定的封闭产品。</p></blockquote>
<p data-tool="mdnice编辑器">通过开放 Extension、Skill、Prompt 和 Package，Harness 可以被用户重新塑造，也可以让 Agent 辅助编写自身需要的扩展。</p>
<p data-tool="mdnice编辑器">这已经比传统 Agent 产品向前迈出了一大步。</p>
<p data-tool="mdnice编辑器">DSH 所代表的方向，则是在此基础上进一步抽象：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>如果 Agent 可以创建新能力，那么这些新能力应该如何成为运行时的一等公民？</p></blockquote>
<p data-tool="mdnice编辑器">答案不能只是把更多代码放进扩展目录。</p>
<p data-tool="mdnice编辑器">系统还需要：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">明确组件所处的 Context；</section>
</li>
<li>
<section style="color: #010101;">显式声明组件依赖的 Coeffects；</section>
</li>
<li>
<section style="color: #010101;">管理组件产生的 Effect；</section>
</li>
<li>
<section style="color: #010101;">在组件退出时执行 Cleanup；</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>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">Pi：
让 Harness 可以被修改

        ↓

DSH：
让修改具有显式生命周期

        ↓

自生长 Agent：
让 Agent 可以发起、验证并沉淀修改
</code></pre>
<p data-tool="mdnice编辑器">从这个角度看，DSH 不是对 Pi 的否定。</p>
<p data-tool="mdnice编辑器">它是在 Pi 所代表的开放扩展思路上，将“变化”进一步提升为运行时的核心抽象。</p>
<h1 data-tool="mdnice编辑器"><span class="content">10. Harness 正在成为 Agent 的生长环境</span></h1>
<p data-tool="mdnice编辑器">如果说 Pi 的核心贡献，是把 Agent 从一个封闭产品变成一个可编程、可扩展的平台，那么 DSH 所探索的下一步，就是让 Agent 逐渐成为自身能力变化的参与者。</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>
</ul>
<p data-tool="mdnice编辑器">但自生长不等于无限增加。</p>
<p data-tool="mdnice编辑器">一个只会安装新工具、写入新记忆、叠加新策略的 Agent，最终只会被自己积累的复杂度拖垮。</p>
<p data-tool="mdnice编辑器">真正可持续的生长，必须同时包含：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">发现
→ 生成
→ 激活
→ 验证
→ 选择
→ 清理
→ 沉淀
→ 淘汰
</code></pre>
<p data-tool="mdnice编辑器">Pi 回答了：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>如何让 Agent Harness 可以被持续扩展？</p></blockquote>
<p data-tool="mdnice编辑器">DSH 则进一步追问：</p>
<blockquote class="custom-blockquote multiquote-1" data-tool="mdnice编辑器"><p>如何让 Agent 安全地参与自身能力的形成？</p></blockquote>
<p data-tool="mdnice编辑器">从 Pi 到 DSH，变化的不只是扩展机制，而是 Harness 的角色。</p>
<p data-tool="mdnice编辑器">它正在从承载工具与插件的工程框架，转变为支持能力试验、筛选、重组和沉淀的运行环境。</p>
<p data-tool="mdnice编辑器">未来的 Agent Harness 可能不再只是模型与工具之间的连接层。</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编辑器">只有当 Agent 既能增加能力，也能验证、回滚、清理和遗忘时，它才真正具备从「可扩展的软件」走向「可持续生长的系统」的可能。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/08/from-pi-to-dsh-how-agent-harness-evolves-from-scalable-to-self-growing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>历史总是在重演，AI 时代的我们应该做些什么</title>
		<link>https://www.phppan.com/2026/08/history-always-repeats-itself-what-should-we-do-in-the-ai-%e2%80%8b%e2%80%8bera/</link>
		<comments>https://www.phppan.com/2026/08/history-always-repeats-itself-what-should-we-do-in-the-ai-%e2%80%8b%e2%80%8bera/#comments</comments>
		<pubDate>Sun, 09 Aug 2026 08:47:15 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2526</guid>
		<description><![CDATA[很多公司以为自己接入了几个大模型，采购了一批智能体工具，员工就获得了某种近乎无限的生产力。 换个角度看：每个人 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">很多公司以为自己接入了几个大模型，采购了一批智能体工具，员工就获得了某种近乎无限的生产力。</p>
<p data-tool="mdnice编辑器">换个角度看：每个人突然多出了一百万名能力不稳定、缺乏上下文、不会主动澄清目标、偶尔虚构进度、还能高速消耗预算的下属。</p>
<p data-tool="mdnice编辑器">这些下属不需要工位，也不会请假。它们一天可以提交上千次结果，看起来永远在工作。管理者很容易被这种吞吐量迷惑，误把生成速度当成有效产出，误把调用次数当成自动化程度，误把一段流畅的回答当成任务已经完成。</p>
<p data-tool="mdnice编辑器">AI 把执行成本压得很低，同时把管理问题放大了。</p>
<p data-tool="mdnice编辑器">今天的智能体系统可以被看成一种新型组织。模型是员工，token 是人力预算，上下文是岗前培训，工具权限是组织授权，工作流是汇报关系，评估体系是绩效制度，人工验收则对应管理责任。</p>
<p data-tool="mdnice编辑器">按照这个视角观察，很多 AI 项目的失败也是情里之中。它们把模型能力当成唯一变量，却没有建立与之配套的管理系统。结果类似一家突然扩张到百万人的公司：招聘极快，职责模糊，没有培训，没有绩效标准，管理者还希望它自行涌现出秩序。</p>
<p data-tool="mdnice编辑器">历史上，这类问题出现过。</p>
<h1 data-tool="mdnice编辑器"><span class="content">铁路旧事</span></h1>
<p data-tool="mdnice编辑器">十九世纪三十年代的铁路热潮带来了前所未有的运输能力。铁路解决了距离和速度问题，也制造了规模、调度、协作和安全问题。</p>
<p data-tool="mdnice编辑器">单条铁路容易运营。线路变长、车次增加、岗位分化以后，系统复杂度迅速上升。信息必须跨地区传递，车辆必须统一调度，责任必须分层，事故必须被记录和复盘。原有的管理方式无法支撑新基础设施的吞吐量，事故随之发生。</p>
<p data-tool="mdnice编辑器">铁路公司最终补上的，是现代管理体系。</p>
<p data-tool="mdnice编辑器">技术增加了系统能力，管理制度把能力组织成可持续的产出。铁路后来成为第一个十亿美元产业，支撑它的并非轨道和机车本身，还包括围绕大规模协作建立的组织结构。</p>
<p data-tool="mdnice编辑器">AI 正在重演类似的路径。</p>
<p data-tool="mdnice编辑器">过去的软件系统严格服从预定义逻辑。输入符合条件，程序进入对应分支。工程团队可以通过代码结构、类型系统、测试和运行指标逐层收紧系统行为。</p>
<p data-tool="mdnice编辑器">智能体的行为边界宽得多。它可以搜索、分析、生成、调用工具、保存记忆、继续拆解任务。它也可能误解任务、忽略约束、重复调用、把猜测写成事实，在结果看似完整时掩盖中间步骤的错误。</p>
<p data-tool="mdnice编辑器">当模型调用规模扩大，问题便从「它会不会回答」变成「我们怎样管理一支由概率模型构成的劳动力」。</p>
<p data-tool="mdnice编辑器">许多企业仍在用采购软件的思路处理这件事：买更强的模型，开放更多上下文，增加 token 预算，再连接更多工具。</p>
<p data-tool="mdnice编辑器">这很容易走向失控。</p>
<h1 data-tool="mdnice编辑器"><span class="content">人海复现</span></h1>
<p data-tool="mdnice编辑器">遇到复杂任务就增加 token，和过去遇到项目延期就增加人手，逻辑高度相似。</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编辑器">给它更多 token，只会让偏离过程更长。</p>
<p data-tool="mdnice编辑器">这和组织扩张中的人海战术没有本质差异。需求尚未稳定时增加开发人员，沟通路径随人数增长，信息损耗也随之增长。新增人员首先要理解问题，再理解已有方案，然后处理方案之间的冲突。最后，大量时间花在协调新增人力本身。</p>
<p data-tool="mdnice编辑器">多智能体系统也会出现同样的问题。</p>
<p data-tool="mdnice编辑器">一个规划智能体拆任务，多个执行智能体并行处理，汇总智能体负责合并，审查智能体再做验证。架构图确实很漂亮。但实际运行时，规划阶段的歧义被复制到所有分支，各个执行智能体基于不同假设工作，汇总阶段只能用更多 token 消解冲突。</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">上下文债务</span></h1>
<p data-tool="mdnice编辑器"><strong>几乎没人会给 AI 恰当的语境。</strong></p>
<p data-tool="mdnice编辑器">这句话听起来像提示词问题，实际涉及企业知识怎样存在、怎样更新、怎样被验证。</p>
<p data-tool="mdnice编辑器">当一个人在公司里工作几年，会形成大量隐性知识。他知道某个字段名与实际含义不同，知道某项流程在文档里有三步、落地时有七步，知道某个客户的特殊约定，知道哪些数据能够使用，哪些结论虽然算得出来却不能直接发布。</p>
<p data-tool="mdnice编辑器">这些知识分散在人的记忆、聊天记录、会议结论、历史工单和代码注释中。公司过去可以容忍这种状态，因为熟练员工承担了知识路由器的角色。谁遇到问题，找对人问一句即可。</p>
<p data-tool="mdnice编辑器">AI 无法稳定获取这种语境。</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>
<p data-tool="mdnice编辑器">第四类是任务绑定。知识必须与执行步骤建立关系。把二十份文档交给智能体，让它自行判断何时使用，等于把一摞制度放到新员工桌上，然后要求他当天独立处理高风险业务。</p>
<p data-tool="mdnice编辑器">上下文管理本身就是管理工作。它对应人类组织中的培训、制度建设和岗位说明。</p>
<p data-tool="mdnice编辑器">企业在这里还会遇到政治阻力。</p>
<p data-tool="mdnice编辑器">熟练员工掌握的独门知识，往往构成他在组织中的议价能力。让他把这些知识结构化地交给 AI，等于要求他亲手降低自身稀缺性。员工可能口头支持，实际只提交表层流程；关键判断仍保留在脑中，文档写得正确却无法操作。</p>
<p data-tool="mdnice编辑器">靠宣传无法解决这种冲突。</p>
<p data-tool="mdnice编辑器">知识沉淀必须进入正式职责，并与岗位价值重新绑定。贡献高质量规则、案例和评估集的人，需要获得与业务产出相当的认可。否则，组织一边要求员工共享知识，一边继续按照「不可替代程度」奖励个人，制度会驱动所有人保留信息。</p>
<p data-tool="mdnice编辑器">AI 转型推进到深处，技术阻力通常会下降，组织阻力会持续上升。</p>
<h1 data-tool="mdnice编辑器"><span class="content">冗员变形</span></h1>
<p data-tool="mdnice编辑器">传统组织里的冗员不一定完全不工作。他可能参加会议、整理材料、同步状态、反复确认，把时间消耗在缺少业务增量的活动上。</p>
<p data-tool="mdnice编辑器">智能体也能制造同类繁忙。</p>
<p data-tool="mdnice编辑器">大量 token 用于复述任务、生成计划、解释已经执行的步骤、重写已有答案。输出篇幅持续增长，新增信息却接近于零。调用记录看起来很丰富，真正影响最终决策的内容只占很小一部分。</p>
<p data-tool="mdnice编辑器">「八成 token 什么都没做」虽然带有夸张色彩，却很接近许多智能体工作流的结构性问题。</p>
<p data-tool="mdnice编辑器">衡量 token 效率不能只看输入输出数量。更合适的单位是有效决策增量：一次调用是否增加了新证据，是否排除了一个错误方向，是否完成了可验证的执行动作，是否降低了后续人工判断成本。</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>
<p data-tool="mdnice编辑器">需要被放大的，是能够显著改变结果的「100 倍 token」。某次调用拿到了决定性证据，识别了关键矛盾，修正了任务边界，或者发现了会导致整条工作流失败的风险。这类调用数量很少，贡献却远高于普通生成。</p>
<p data-tool="mdnice编辑器">寻找它们依赖调用追踪和结果归因。</p>
<p data-tool="mdnice编辑器">每个工作流至少要记录：调用目的、输入来源、使用工具、产生的新信息、对最终结果的影响、失败类型以及人工修改位置。没有这套记录，团队只能看到账单和最终文本，无法分辨哪些调用有效，哪些调用只是增加长度。</p>
<p data-tool="mdnice编辑器">这类似分析一个大型团队。只看员工人数和工作时长，无法判断组织效率。必须知道关键决策在哪里发生，哪些岗位形成瓶颈，哪些汇报关系制造重复劳动。</p>
<h1 data-tool="mdnice编辑器"><span class="content">评估先行</span></h1>
<p data-tool="mdnice编辑器">管理智能体最有效的抓手是评估标准，也就是 evals。</p>
<p data-tool="mdnice编辑器">编程成为 AI 当前最成功的应用场景，与代码天然具备可评估性有很大关系。程序可以编译或运行，测试可以通过或失败，性能可以测量，回归可以复现。模型生成的内容即使风格不同，也能被放入相对稳定的验证框架。</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>
<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>
<p data-tool="mdnice编辑器">第一层检查任务完成度。要求提取的字段是否齐全，要求执行的动作是否发生，输出格式是否符合下游接口。</p>
<p data-tool="mdnice编辑器">第二层检查事实和证据。结论能否追溯到输入材料，引用是否支持对应判断，模型有没有用常识填补数据缺口。</p>
<p data-tool="mdnice编辑器">第三层检查业务质量。结果是否符合领域规则，是否覆盖关键风险，是否达到能够进入下一流程的标准。</p>
<p data-tool="mdnice编辑器">第四层检查运行效率。完成任务使用了多少 token、多少工具调用、多少时间，发生了多少次重试，人工修改比例是多少。</p>
<p data-tool="mdnice编辑器">只评结果质量，系统可能通过十倍成本换取一点点效果提升。只评成本，系统又会通过减少分析步骤制造隐蔽错误。质量、成本和风险必须放在同一套评估里。</p>
<p data-tool="mdnice编辑器">评估标准也不能永久固定。业务规则会变化，模型行为会变化，工具返回的数据结构也会变化。每次线上事故都应转化成新的评估案例。没有进入评估集的事故，后续很可能以相似形式再次出现。</p>
<p data-tool="mdnice编辑器">这与人类团队的管理方式一致。OKR、质量标准、事故复盘和晋升要求，共同塑造员工行为。只给方向、不定义验收，最后只能依赖主管逐项盯人。</p>
<p data-tool="mdnice编辑器">智能体同样需要制度化约束。</p>
<h1 data-tool="mdnice编辑器"><span class="content">思想主权</span></h1>
<p data-tool="mdnice编辑器">AI 编程带来的另一个变化，是程序员的工作重心开始从逐行控制代码迁移到控制软件背后的想法。</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编辑器">数据结构为什么这样选择，状态由谁拥有，哪些不变量必须维持，失败后怎样恢复，性能目标是什么，哪些路径允许降级，哪些行为属于系统禁区。这些内容如果只存在于开发者脑中，AI 会用自己的概率偏好填补空白。</p>
<p data-tool="mdnice编辑器">结果可能能运行，也可能通过局部测试，却难以演进。</p>
<p data-tool="mdnice编辑器">控制思想要求开发者拥有足够深的系统理解。他需要判断某种设计会怎样影响内存布局、并发行为、故障边界和长期维护成本。只会写提示词无法完成这项工作。</p>
<p data-tool="mdnice编辑器">vibe coding 最大的问题不在生成速度，而在责任链断裂。使用者描述一个模糊目标，模型生成实现，程序恰好能够运行，于是产物被视为完成。系统内部怎样工作、错误会在哪里积累、性能上限由什么决定，没有人掌控。</p>
<p data-tool="mdnice编辑器">这种开发方式可以用于低风险原型。进入长期维护或高性能系统后，代价会快速暴露。需求变化一次，开发者无法判断应该修改哪一层；性能下降时，也不知道问题来自算法、数据结构还是调用关系。每次修改只能继续依赖模型猜测，系统逐步变成无人理解的代码堆积。</p>
<p data-tool="mdnice编辑器">antirez 在开发本地 LLM 推理软件 DwarfStar 时，面对的是大量细微且会累积的错误。这类项目不能只看单个函数是否合理。一个局部误差、一处内存选择、一个数据结构上的偏差，都可能沿执行路径放大。</p>
<p data-tool="mdnice编辑器">AI 在严谨设计和测试中可以提供很大帮助。前提是人掌握系统意图，知道该要求 AI 验证什么，也知道哪些结果不能接受。</p>
<p data-tool="mdnice编辑器">代码审查也需要重新分配精力。</p>
<p data-tool="mdnice编辑器">对于 Redis 这类大量用户会直接阅读和修改源码的项目，保持代码质量是对用户的尊重。审查仍然有意义。但如果开发者把全部时间用在检查每一行 AI 输出，QA、性能分析和下一步设计就会被挤压。</p>
<p data-tool="mdnice编辑器">有限的人力应该优先投入故障模式、系统不变量、边界条件、性能退化和可维护性。代码风格可以交给自动化工具，重复性实现可以交给模型，局部错误可以由测试覆盖。设计责任无法外包。</p>
<h1 data-tool="mdnice编辑器"><span class="content">设计文档</span></h1>
<p data-tool="mdnice编辑器">DESIGN.md 在 AI 编程环境中的地位会持续上升。</p>
<p data-tool="mdnice编辑器">过去很多设计文档写于项目启动阶段，代码落地后很快过期。开发者最终以代码为准，文档只保留历史价值。AI 参与开发后，设计文档需要变成持续维护的系统接口。</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编辑器">如果代码改动影响数据结构、状态模型或失败处理，设计文档必须同步更新。评审时需要检查实现与文档是否一致。否则，AI 会依据旧设计修改新代码，错误会披着「遵循项目规范」的外衣进入系统。</p>
<p data-tool="mdnice编辑器">设计文档可以视为给未来开发者和未来智能体准备的上下文。它承担组织记忆，减少关键思想只存在于少数人脑中的风险。</p>
<p data-tool="mdnice编辑器">企业业务流程也需要同类文档。</p>
<p data-tool="mdnice编辑器">流程说明不能停留在步骤列表。它要写清每一步的目标、输入、输出、判断依据、异常路径、升级条件和验收标准。只有这样，智能体才有可能承担稳定执行。</p>
<p data-tool="mdnice编辑器">很多所谓 AI 落地难题，本质上暴露了企业从未把业务讲清楚。过去依赖熟练员工临场判断，这些模糊地带被人的经验掩盖。模型进入流程后，所有未定义部分同时暴露出来。</p>
<p data-tool="mdnice编辑器"><strong>AI 没有制造这些混乱。它提高了混乱被复制的速度。</strong></p>
<h1 data-tool="mdnice编辑器"><span class="content">人的边界</span></h1>
<p data-tool="mdnice编辑器">AI 可以承担高吞吐的搜索、执行、分析和记忆管理。</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>
<p data-tool="mdnice编辑器">最终验收意味着人要对结果负责。验收不能退化成快速扫一眼。模型输出量超过人的审查能力时，系统应限制吞吐量、增加自动评估或降低自动化范围。堆积大量无人验收的输出，没有形成生产力，只是形成了信息库存。</p>
<p data-tool="mdnice编辑器">人机分工也不应按照「模型能做什么」设计。这个问题会随着模型升级不断变化。更稳定的划分方式是看责任和可验证性。</p>
<p data-tool="mdnice编辑器">可验证、可回滚、低风险的任务，可以给智能体更大自治权。</p>
<p data-tool="mdnice编辑器">结果可验证但动作难以回滚时，应增加审批点。</p>
<p data-tool="mdnice编辑器">结果难以完整验证、错误影响又高的任务，AI 更适合作为分析和建议工具。</p>
<p data-tool="mdnice编辑器">这套边界需要写进系统权限。只靠提示词要求模型谨慎，约束强度远远不够。工具层要限制可调用范围，工作流层要设置审批，运行层要保留审计记录，评估层要持续检查越权行为。</p>
<p data-tool="mdnice编辑器">提示词属于沟通机制。权限系统才承担治理责任。</p>
<h1 data-tool="mdnice编辑器"><span class="content">年轻工程师</span></h1>
<p data-tool="mdnice编辑器">我对年轻程序员是有些担心的，并非担心他们使用 AI，而是他们在建立心智模型之前就把实现过程全部交给 AI。</p>
<p data-tool="mdnice编辑器">一个人没有亲手实现过解释器、数据库或哈希表，很难判断模型生成的实现在哪些地方存在结构性问题。他可能读得懂语法，也能运行测试，但无法推演数据怎样流动、状态怎样变化、性能为什么退化。</p>
<p data-tool="mdnice编辑器"><strong>核对 AI 输出不能替代基础训练。</strong></p>
<p data-tool="mdnice编辑器">逐行检查一段自己并不理解的代码，只会制造虚假的参与感。更有效的训练方式是亲手实现规模受控的系统，遇到内存、并发、解析、索引和错误恢复问题，再用 AI 帮助验证思路、补充测试、寻找遗漏。</p>
<p data-tool="mdnice编辑器">基础能力的价值还会上升。</p>
<p data-tool="mdnice编辑器">代码生成越来越便宜以后，企业不会继续为普通代码行支付高溢价。能够定义系统、识别风险、设计评估、分析性能和承担技术决策的人，会掌握更大的杠杆。</p>
<p data-tool="mdnice编辑器">年轻工程师如果只学习怎样让模型快速产出，能力会紧贴当前工具。模型更新一次，原有技巧可能迅速贬值。数据结构、操作系统、网络、数据库和编程语言建立的心智模型变化慢得多，它们决定一个人能否判断 AI 给出的方案。</p>
<p data-tool="mdnice编辑器">高级工程师也不能因此停留在旧习惯里。</p>
<p data-tool="mdnice编辑器">坚持所有代码都必须人工逐行编写，会把大量时间消耗在机器已经可以低成本完成的工作上。经验应该投入架构、约束、验证和复杂问题拆解。继续用代码行数证明专业性，和管理者用团队人数证明影响力没有太大差别。</p>
<h1 data-tool="mdnice编辑器"><span class="content">管理重构</span></h1>
<p data-tool="mdnice编辑器">企业引入 AI 时，组织结构也要调整。</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>
<p data-tool="mdnice编辑器">上线评审需要检查成本上限和停止条件。任何允许递归、自我调用或动态扩展任务的系统，都必须有预算约束。</p>
<p data-tool="mdnice编辑器">事故复盘需要分析模型为什么做出对应行为，输入中缺少了什么，评估为什么没有拦截，以及权限设计是否允许错误继续扩大。把问题归结为「模型偶尔会犯错」，相当于铁路事故后只说机车存在风险。</p>
<p data-tool="mdnice编辑器">采购更强的模型可能提升基线能力，却不会补齐管理缺口。模型越强，执行范围越大，错误的影响半径也越大。</p>
<p data-tool="mdnice编辑器">AI 项目的成熟度最终会体现在几个问题上：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>团队能否精确描述智能体负责的任务？</section>
</li>
<li>
<section>能否量化什么叫完成？</section>
</li>
<li>
<section>能否追溯结论来自哪些输入？</section>
</li>
<li>
<section>能否限制循环、重试和预算？</section>
</li>
<li>
<section>能否知道哪些 token 改变了结果？</section>
</li>
<li>
<section>能否在高风险动作前强制人工介入？</section>
</li>
<li>
<section>能否把线上失败转化成评估案例？</section>
</li>
<li>
<section>能否让设计思想脱离个人记忆，进入可维护的组织资产？</section>
</li>
</ol>
<p data-tool="mdnice编辑器">这些问题回答不出来，再复杂的智能体架构也只是一次昂贵的演示。</p>
<p data-tool="mdnice编辑器">铁路时代的企业最终学会了管理速度、规模和复杂性。<strong>AI 时代需要处理的是概率行为、无限执行能力和有限人类判断之间的冲突。</strong></p>
<p data-tool="mdnice编辑器">我们已经拥有了数量近乎无限的数字员工。它们勤奋、便宜、速度极快，也缺少组织记忆、责任意识和稳定判断。</p>
<p data-tool="mdnice编辑器">接下来的工作落在人身上：把问题讲清楚，把标准写出来，把权限收紧，把思想保存下来，对最后的结果签字。</p>
<p data-tool="mdnice编辑器">这些都是基于当前模型能力和个人认知所作出的判断，随着时间的推移，也可能会涌现其它我当前所认知不到的，那又将是另一个世界。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/08/history-always-repeats-itself-what-should-we-do-in-the-ai-%e2%80%8b%e2%80%8bera/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用 Vibe Coding 的这6 种后遗症，你有吗？</title>
		<link>https://www.phppan.com/2026/08/do-you-have-any-of-these-6-side-effects-of-using-vibe-coding/</link>
		<comments>https://www.phppan.com/2026/08/do-you-have-any-of-these-6-side-effects-of-using-vibe-coding/#comments</comments>
		<pubDate>Sun, 02 Aug 2026 10:39:08 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[VibeCoding]]></category>
		<category><![CDATA[焦虑]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2523</guid>
		<description><![CDATA[我已经很久没有沉浸式写代码了。 以前碰到一个复杂问题，我会花几个小时读调用链、画状态变化、跟踪异常路径。刚开始 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">我已经很久没有沉浸式写代码了。</p>
<p data-tool="mdnice编辑器">以前碰到一个复杂问题，我会花几个小时读调用链、画状态变化、跟踪异常路径。刚开始很慢，脑子里只有一些零散信息。随着上下文逐渐完整，代码会形成一张连续的结构图。再往后写，很多判断不需要反复查找，心流也就出现了。</p>
<p data-tool="mdnice编辑器">现在的工作节奏完全变了。</p>
<p data-tool="mdnice编辑器">我先给 Agent 描述需求，等它执行。等待期间又觉得空着可惜，于是打开第二个窗口，让另一个 Agent 补测试。看到还有时间，再开第三个任务处理重构。</p>
<p data-tool="mdnice编辑器">十几分钟后，几个 Agent 陆续返回结果。我开始查看摘要、检查 diff、运行测试、补充要求、处理冲突。一个任务还没看完，另一个任务已经弹出完成通知。</p>
<p data-tool="mdnice编辑器">一天下来，代码生成了很多，我却很少在同一个问题里连续停留半小时。</p>
<h1 data-tool="mdnice编辑器"><span class="content">心流没了</span></h1>
<p data-tool="mdnice编辑器">Vibe Coding 最先改变的是注意力结构。</p>
<p data-tool="mdnice编辑器">写代码原本是一种连续活动。理解需求、寻找入口、建立模型、设计实现、发现问题、修正判断，这些环节彼此相连。Agent 把它切成了很多轮对话：描述任务，等待结果，检查结果，再描述下一步。</p>
<p data-tool="mdnice编辑器">每轮都很快，每轮都带来一点进度。</p>
<p data-tool="mdnice编辑器">大脑很容易适应这种高频反馈。问题一旦需要长时间推演，我就会产生阻力。以前遇到难点，会继续读代码、查日志、进调试器。现在的第一反应经常是把问题发给 Agent，让它先分析一下。</p>
<p data-tool="mdnice编辑器">思考开始变得外包化。</p>
<p data-tool="mdnice编辑器">这不会让工程师立刻失去能力。更常见的变化是耐心下降。看十分钟调用链就想问 AI，调试几次没有结果就想换提示词，设计还没收敛就开始生成代码。</p>
<p data-tool="mdnice编辑器">慢思考越来越难维持。</p>
<p data-tool="mdnice编辑器">复杂工程问题偏偏依赖这种能力。并发状态、数据一致性、故障恢复和性能瓶颈，很少能靠几轮快速问答解决。它们需要人在同一套上下文里待得足够久，直到各种局部信息连成一体。</p>
<p data-tool="mdnice编辑器">Agent 使用频率越高，我越忙，也越难进入这种状态。</p>
<h1 data-tool="mdnice编辑器"><span class="content">Token 焦虑</span></h1>
<p data-tool="mdnice编辑器">额度快用完会焦虑，这很正常。额度没用完也会焦虑，就就有点奇怪了。</p>
<p data-tool="mdnice编辑器">买了订阅，看到 token 还剩很多，会产生一种浪费感。离开工位之前，总想再布置一个任务。准备去开会，让 Agent 先跑一轮测试；准备吃饭，让它顺手整理模块；睡觉之前更容易上头，总觉得应该给 AI 加个通宵夜班。</p>
<p data-tool="mdnice编辑器">真的是如群友所说：床前 token 光。</p>
<p data-tool="mdnice编辑器">这种焦虑会慢慢改变任务优先级。原本没有必要立即处理的工作，因为 Agent 正好空闲，被提前塞进了队列。需求还没有想清楚，先做一个版本看看；某段代码虽然还能维护，先让 Agent 重构一下；测试已经够用，再补几十个用例似乎也没坏处。</p>
<p data-tool="mdnice编辑器">Agent 忙起来了，人的待验收任务也堆起来了。</p>
<p data-tool="mdnice编辑器">第二天早上打开电脑，几个任务全部显示完成。看起来像是白捡了一夜生产力。仔细检查才会发现，每个结果都需要重新加载上下文，都要判断实现边界，还要验证它有没有把局部问题扩展成新的抽象。</p>
<p data-tool="mdnice编辑器">AI 上完夜班，我们开始加班验收。</p>
<p data-tool="mdnice编辑器">更麻烦的是，等待结果本身也会占据注意力。人已经离开电脑，脑子里还惦记着任务有没有跑完、会不会失败、明天要看多少改动。休息时间变成了一段漫长的异步等待。</p>
<h1 data-tool="mdnice编辑器"><span class="content">越用越累</span></h1>
<p data-tool="mdnice编辑器">刚开始用 Vibe Coding，我以为自己会轻松很多。实际体验是产出提高以后，疲惫感也跟着上升。</p>
<p data-tool="mdnice编辑器">过去一天写两百行关键代码，我基本知道每一行为什么存在。现在几个 Agent 可以快速生成几千行改动。我的工作量没有消失，它从编写转移到了理解、判断和验收。</p>
<p data-tool="mdnice编辑器">生成可以并行，理解通常只能串行。</p>
<p data-tool="mdnice编辑器">一个 Agent 修改接口，一个 Agent 补充测试，另一个 Agent 调整调用方。它们各自都能完成任务，最后所有结果汇集到我这里。我需要判断接口语义有没有变化，测试是否沿用了错误假设，调用方有没有掩盖异常，几个改动能否同时成立。</p>
<p data-tool="mdnice编辑器"><strong>机器的带宽增加了，人的带宽没有同步扩展。</strong></p>
<p data-tool="mdnice编辑器">当输出超过处理能力，审核质量会自然下降。最初还会逐行看 diff，后来只看摘要；最初会认真检查测试内容，后来看到绿色就继续；最初要求自己理解每个关键决策，后来开始依赖 Agent 提供的变更说明。</p>
<p data-tool="mdnice编辑器">疲惫带来的危险，很少表现为明显的错误决定。它更像一种缓慢降低的验收标准。</p>
<p data-tool="mdnice编辑器">每一次都只少看一点，每一次都觉得风险可控。几个月以后，代码库里已经出现大量没人完整理解的实现。</p>
<h1 data-tool="mdnice编辑器"><span class="content">不想 review</span></h1>
<p data-tool="mdnice编辑器">我现在越来越不想看 AI 写的代码。</p>
<p data-tool="mdnice编辑器">人工审核 AI 代码，有时比自己重写还累。自己写代码时，设计过程和实现过程是连续的。我知道哪些地方经过权衡，哪些地方只是临时处理，哪些假设需要后续验证。</p>
<p data-tool="mdnice编辑器">面对 AI 生成的代码，这些上下文都要重新推导。</p>
<p data-tool="mdnice编辑器">最让人疲惫的代码，往往还写得挺像样。命名规范，结构完整，注释齐全，测试也有。逐段看都有合理性，放回整个系统却经常显得多余。</p>
<p data-tool="mdnice编辑器">一个边界条件被扩展成通用框架，一次局部修复引入新的中间层，三个表面相似的流程被强行抽象到一起。代码能运行，测试也能通过，但维护成本被悄悄推高了。</p>
<p data-tool="mdnice编辑器">审核者需要花很大力气，才能解释为什么一份「正确」的代码不适合进入当前系统。</p>
<p data-tool="mdnice编辑器">重复几次以后，人会开始逃避。既然测试过了，先合进去。出了问题，再让 Agent 修。下一次 Agent 又基于上一次生成的结构继续扩展，代码越来越多，理解越来越少。</p>
<p data-tool="mdnice编辑器">最后会出现一种荒诞状态：人不愿意维护 AI 写的代码，于是继续让 AI 维护。</p>
<h1 data-tool="mdnice编辑器"><span class="content">理解变浅</span></h1>
<p data-tool="mdnice编辑器">亲手写代码的过程，本身也是建立系统认知的过程。</p>
<p data-tool="mdnice编辑器">为了实现一个功能，我需要找到入口，理解依赖，处理编译错误，观察测试失败，再确认数据如何穿过整个链路。实现完成以后，我对这部分系统已经形成了自己的判断。</p>
<p data-tool="mdnice编辑器">Vibe Coding 跳过了其中大量过程。</p>
<p data-tool="mdnice编辑器">Agent 告诉我改了哪些文件、采用了什么方案、测试结果如何。我可以很快获得一份完整说明，却没有经历那些失败、试探和修正。知道结果，与掌握系统之间，仍然隔着很长一段距离。</p>
<p data-tool="mdnice编辑器">这种认知缺口在日常迭代中不容易暴露。Agent 还能继续修改，功能也能继续上线。等到线上故障、复杂重构或性能退化时，团队需要在压力下定位根因，欠下的理解成本才会集中出现。</p>
<p data-tool="mdnice编辑器">每个人都参与过系统开发，问到关键链路却只能讲出局部。继续追问异常如何传播、数据如何恢复、容量边界在哪里，回答逐渐变成「让 Agent 分析一下」。</p>
<p data-tool="mdnice编辑器">工具还在，工作可以继续。工程师对系统的控制感却在下降。</p>
<h1 data-tool="mdnice编辑器"><span class="content">调度上瘾</span></h1>
<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编辑器">技术管理者更容易陷进去。我们的时间本来就被会议、消息和协作切碎，Agent 又提供了一种产出感很强的碎片化工作。它比回复消息更像研发，比真正做架构设计轻松，还能持续制造「事情正在推进」的反馈。</p>
<p data-tool="mdnice编辑器">久而久之，启动任务替代了处理问题，完成数量替代了工程质量。</p>
<h1 data-tool="mdnice编辑器"><span class="content">代码还在，人却远了</span></h1>
<p data-tool="mdnice编辑器">Vibe Coding 让我写得更快，也让我离代码更远。</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>
<p data-tool="mdnice编辑器">这些问题比 token 使用量更让我焦虑。</p>
<p data-tool="mdnice编辑器">我仍然每天使用 Vibe Coding，也很难再回到完全手写的状态。它确实提高了生产速度，只是这份速度附带了一组后遗症：心流消失、注意力碎片化、token 焦虑、审核疲劳、理解变浅，以及对调度和即时反馈的依赖。</p>
<p data-tool="mdnice编辑器">以前写代码会累，累在实现。</p>
<p data-tool="mdnice编辑器">现在也累，累在持续接收、持续判断、持续验收。机器一直在输出，我的大脑一直没有真正下班。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/08/do-you-have-any-of-these-6-side-effects-of-using-vibe-coding/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>做 Agent 架构时，OpenViking 的几个可以学习的点</title>
		<link>https://www.phppan.com/2026/07/agent-openviking/</link>
		<comments>https://www.phppan.com/2026/07/agent-openviking/#comments</comments>
		<pubDate>Sun, 26 Jul 2026 03:59:43 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[OpenViking]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2521</guid>
		<description><![CDATA[设计 Agent 架构时，有一个模块的边界经常画不清晰：上下文。规划、工具调用、执行循环这些组件的职责都好切分 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p style="color: #191b1f;" data-first-child="" data-pid="EAu8Ffnz">设计 Agent 架构时，有一个模块的边界经常画不清晰：上下文。规划、工具调用、执行循环这些组件的职责都好切分，唯独「Agent 该知道什么」这件事，散落在 prompt 模板、RAG 管道、记忆插件和会话历史四个地方，各管一段，互不通气。</p>
<p style="color: #191b1f;" data-pid="aMUyz_Cj">比较常见的状态是知识走一条 RAG 管道，用户记忆走一个独立的 memory 服务，工具使用经验干脆没有沉淀机制，靠 system prompt 里手写的几条注意事项撑着。跑长周期任务时问题集中爆发：第三十轮时 Agent 忘了第五轮的约束；召回了一段错误文档，事后想归因，向量库只给一个相似度 0.83，别的什么都没有。</p>
<p style="color: #191b1f;" data-pid="el4iar4w">这两个 case 背后是同一个架构缺陷：上下文没有被当作一个独立子系统来设计，它只是若干条数据管道的松散集合。</p>
<p style="color: #191b1f;" data-pid="-LSVd0qA">最近把火山引擎开源的 OpenViking 看了一遍，它把自己定位成「面向 AI 智能体的上下文数据库」，GitHub 上 27.2k star。这个定位本身就是一个架构主张：上下文应该是 Agent 架构里一个有统一接口、有存储模型、有检索语义、可观测的独立组件，地位类比业务系统里的数据库。</p>
<p style="color: #191b1f;" data-pid="cvQ_eRuL">沿着这个主张往下拆，它有几个设计决策对做 Agent 架构的人有一些参考价值。</p>
<h2 style="font-weight: 500; color: #191b1f;">统一存储模型</h2>
<p style="color: #191b1f;" data-pid="OXbI9IFm">Agent 架构里第一个要回答的问题：记忆、知识、技能这三类上下文，存储模型是分开还是统一。</p>
<p style="color: #191b1f;" data-pid="_C15LbGR">分开是多数团队的现状，也是我们踩过的坑——三套 schema、三套检索接口、三套更新逻辑，Agent 侧要维护三种访问心智。OpenViking 的选择是统一：全部抽象成文件，挂在 <code>viking://</code> 协议下的虚拟文件系统里，每个条目一个唯一 URI。它给出的目录结构：</p>
<div class="highlight" style="color: #191b1f;">
<pre><code class="language-text">viking://
├── resources/              # 资源：项目文档、代码库、网页等
│   └── my_project/
│       ├── docs/
│       │   ├── api/
│       │   └── tutorials/
│       └── src/
└── user/
    └── {user_id}/
        ├── memories/
        │   └── preferences/
        │       ├── writing_style
        │       └── coding_habits
        ├── resources/
        │   └── private_project/
        ├── skills/
        │   ├── search_code
        │   └── analyze_data
        └── peers/
            └── web-visitor-alice/</code></pre>
</div>
<p style="color: #191b1f;" data-pid="vT6qFbFR">Agent 用 <code>ls</code>、<code>tree</code>、<code>find</code> 浏览自己的上下文。</p>
<p style="color: #191b1f;" data-pid="2Yhb04Wj">从架构角度来看，这个统一带来两个收益。第一，定位从概率性变成确定性。扁平向量库里想精确更新某个用户的编码习惯记忆，只能靠 metadata filter 打补丁；文件系统里就是一个路径寻址，<code>viking://user/{user_id}/memories/preferences/coding_habits</code>，增删改查全部确定。多租户隔离也顺带解决了——<code>user/{user_id}</code> 这层目录天然就是隔离边界，商业版文档里明确把多租户共享和隔离列为核心场景。</p>
<p style="color: #191b1f;" data-pid="PgrcTj5b">第二，Agent 与上下文之间的接口收敛成了一套文件操作原语。这一点对架构的影响比看起来大。我们之前自研记忆系统时设计过一套 <code>memory.query(scope, filter, ...)</code> 风格的 API，模型调用的出错率明显高于让它直接操作路径。文件系统是 LLM 预训练语料里高频出现的心智模型，<code>ls</code> 和 <code>tree</code> 的语义模型天然理解，不需要在 prompt 里教。接口设计顺着模型先验走，省下的是持续的 prompt 工程成本。</p>
<p style="color: #191b1f;" data-pid="bMOXDRk6">代价在写入侧的分类决策。内容归置到哪个路径，做错了后续检索会系统性偏航，扁平向量库至少没有「放错文件夹」这种失败模式。OpenViking 靠写入时的自动语义处理来做归置，这个环节的质量上限决定了整棵文件树的可用性，我认为是它工程上最脆弱的一环。</p>
<h2 style="font-weight: 500; color: #191b1f;">预算分层供给</h2>
<p style="color: #191b1f;" data-pid="mHGZ2zN3">第二个架构问题：上下文以什么粒度供给给模型。</p>
<p style="color: #191b1f;" data-pid="H33IvGor">传统 RAG 的答案是 chunk 全量——检索 top-k 塞进 prompt，k 靠拍脑袋。这在架构上等于把上下文预算的管理责任丢给了一个魔法数字。OpenViking 的方案是写入时预计算三层表示：</p>
<ul style="color: #191b1f;">
<li data-pid="Tmtm70Kr">L0（摘要）：约 100 tokens，一句话总结，快速判断相关性</li>
<li data-pid="4oKtO6wj">L1（概览）：约 2k tokens，核心信息和使用场景，供规划阶段决策</li>
<li data-pid="-APyM9Yd">L2（详情）：完整原始数据，确有必要时才读</li>
</ul>
<p style="color: #191b1f;" data-pid="zBH3NtWe">每个目录也带自己的 <code>.abstract</code> 和 <code>.overview</code>，Agent 读任何完整文件之前，先花 100 个 token 判断这个方向值不值得深入。</p>
<p style="color: #191b1f;" data-pid="4-aXPxb3">放到 Agent 的执行循环里看，这三层恰好对应了三个阶段的信息需求：候选筛选阶段用 L0 扫一遍，规划阶段对少数目标加载 L1，执行阶段只对必要条目读 L2。上下文供给从一次性的赌博变成了跟随 Agent 决策流程的渐进加载，粒度和阶段对齐了。</p>
<p style="color: #191b1f;" data-pid="obq4ONV6">数据上，OpenViking 0.3.22 在 LoCoMo 长对话记忆评测里，三种 Agent 集成的准确率到 80–83%，原生记忆只有 24–57%；输入 token 减少 34.3%–91.0%，查询时延降低 58.45%–66.10%。token 降幅区间这么宽，说明收益高度依赖任务形态：判断型任务能省九成，需要全量细节的任务省不了多少。评测脚本在仓库 <code>./benchmark</code> 目录，可自行复现。</p>
<p style="color: #191b1f;" data-pid="ZChgUhTf">架构上的 trade-off 转移到了写入路径。L0/L1 由模型生成，每次数据摄入都有一笔推理开销，且是异步的——README 明确提示 <code>add-resource</code> 不加 <code>--wait</code> 时语义处理需要等待。写入吞吐敏感或数据高频变更的场景，这条路径会成为瓶颈。更隐蔽的风险是摘要写歪：L0 错了，后续所有基于它的相关性判断连带出错，等于在检索链路最上游埋单点。官方提供 <code>ov reindex &lt;uri&gt; --mode semantic_and_vectors</code> 重新生成语义产物再刷新向量，说明他们自己也预期摘要需要返工。</p>
<p style="color: #191b1f;" data-pid="zcid3ZaV">即便不引入 OpenViking，「写入时预计算多粒度表示、按 Agent 决策阶段供给」这个模式可以直接抄进自建架构。</p>
<h2 style="font-weight: 500; color: #191b1f;">检索即导航</h2>
<p style="color: #191b1f;" data-pid="kSImNmZF">检索组件在 Agent 架构里通常被实现成一个无状态函数：query 进，top-k 出。OpenViking 把它改成了一个有过程的导航。</p>
<p style="color: #191b1f;" data-pid="FMfkO849">先通过意图分析生成多个检索条件；向量检索定位初始切片所在的高分目录；在该目录下二次检索，高分结果更新进候选集；有子目录就逐层递归；最终返回最相关的上下文，连同周边语境一起。</p>
<p style="color: #191b1f;" data-pid="JeNy67s-">一句话概括：先锁定高分目录，再向下精细探索。</p>
<p style="color: #191b1f;" data-pid="QKso_oQd">这解决的是扁平 top-k 在 Agent 场景下的一个结构性缺陷——碎片没有语境。命中一个 chunk，Agent 不知道它前后是什么、属于哪份文档的哪个章节，拿到的是信息孤岛。导航式检索的返回结果自带位置和邻居：这段内容出自 API 文档的鉴权章节，同目录下还有 endpoints 说明。对代码库问答这类强依赖结构的任务，这个差异是决定性的。</p>
<p style="color: #191b1f;" data-pid="cH_9ytDz">代价：多轮向量查询加上可能的多次判断，单次检索的绝对延迟一定高于一把梭的 ANN。前面提到的时延降低 58%–66%，我的理解是端到端口径——省下的 token 让生成阶段变快，摊平了检索阶段的开销。架构选型时要按流量形态判断：Agent 任务型场景，单次任务价值高、容忍秒级检索，划算；高 QPS 低延迟的在线检索，这套递归策略不合适。</p>
<h2 style="font-weight: 500; color: #191b1f;">全链路留痕</h2>
<p style="color: #191b1f;" data-pid="lmAUpM13">Agent 架构的可观测性讨论，通常止步于工具调用日志。OpenViking 把留痕做进了检索内部：每次查询都保留完整的目录浏览轨迹，结果不对时能看到它出自哪条路径。</p>
<p style="color: #191b1f;" data-pid="dfFSb1CA">做过 RAG 生产运维的人知道 bad case 归因有多难。用户投诉答案错了，排查下来只能看到「这几个 chunk 相似度最高所以被召回」，正确内容为什么没进 top-k，向量空间不会解释。最后退化成调 embedding、调 chunk 大小、调 k 值的炼丹循环。</p>
<p style="color: #191b1f;" data-pid="sEX8qK4H">轨迹留存把归因变成可逐步 debug 的过程：检索在哪个目录拐错了弯、哪一层的 L0 摘要误导了判断，路径上每一步可查。修复动作随之有了着力点——重写某个目录的 abstract，或者调整文件归置，都对应到具体操作。配套的 OpenViking Helper 桌面端（Beta，支持 macOS 和 Windows x64）能解析 Claude Code、Codex、Trae 的会话，展示召回、Prompt 注入、MCP 调用、捕获和提交事件，Agent 与上下文系统之间的全部交互摆在明面上。</p>
<p style="color: #191b1f;" data-pid="wMsAyqnE">这一条我建议所有做 Agent 基础设施的团队照抄，无论用不用 OpenViking：把「检索决策可回放」写进架构的非功能需求。存储和埋点的成本，第一次生产事故排查时就能收回来。</p>
<h2 style="font-weight: 500; color: #191b1f;">经验回写闭环</h2>
<p style="color: #191b1f;" data-pid="uIPUFTYq">多数 Agent 架构是开环的：任务执行完，除了对话历史什么都不留，下次遇到同类任务从零开始。OpenViking 在架构里补了一条回写路径：会话通过 <code>session.commit()</code> 显式提交后，系统异步提取两类内容写入长期记忆——用户偏好，和 Agent 经验，落到对应的 <code>/memory</code> 目录下。</p>
<p style="color: #191b1f;" data-pid="NcKzWSYU">用户偏好各家都在做。Agent 经验这条更值得看：从任务执行结果里提取操作技巧、工具使用经验，供后续同类任务复用。tau2-bench 的数据：经验记忆让任务成功率在 Retail 提升 6.87 个百分点，Airline 提升 11.87 个百分点，对比同一 LLM 无记忆基线。模型没动，纯靠架构里加一条经验沉淀回路，拿到两位数以内的成功率增量。</p>
<p style="color: #191b1f;" data-pid="8zzpDbGT">对比另一条提升路线——攒数据做 SFT 或 RL 微调，周期以周计，成本以万计。经验记忆改的是上下文而非权重，迭代周期是每次会话。两条路线不互斥，但在架构演进的排序上，外置经验回路的边际成本低得多，应该排在微调前面。</p>
<p style="color: #191b1f;" data-pid="IVvbBGO8">两处要留神。一是提取质量：异步抽取本身是 LLM 调用，抽错等于往长期记忆注入脏数据，且脏记忆会在后续任务里持续生效，危害大于一次性的错误回答。二是显式 commit 这个设计——记忆更新由开发者主动触发而非全自动，「哪些会话值得沉淀」的裁量权留在应用层。我们内部一度想做全自动沉淀，现在倾向于保留这个开关，至少在提取质量有验证机制之前。</p>
<h2 style="font-weight: 500; color: #191b1f;">接入成本</h2>
<p style="color: #191b1f;" data-pid="65NYtME-">把这套东西放进现有 Agent 架构的成本，不高。</p>
<p style="color: #191b1f;" data-pid="7MYbVe1l">Python 3.10 以上，<code>pip install openviking --upgrade</code>，<code>openviking-server init</code> 走交互式向导——支持火山引擎、OpenAI、Codex OAuth、Kimi、GLM 和本地 Ollama，选 Ollama 还能按硬件拉合适的模型。<code>openviking-server doctor</code> 启动前体检配置、Python 版本、提供商连通性和磁盘空间。有官方 Docker 镜像和 Helm 部署。</p>
<p style="color: #191b1f;" data-pid="8KfHXdrJ">集成面很宽：Claude Code、Codex、OpenClaw、Hermes、Cursor、Trae、OpenCode、pi、MCP 客户端、LangChain / LangGraph 都有官方接入，集成做的事是把召回注入 Agent 上下文并自动提交会话记忆。如果你的架构本来就走 MCP 或 LangGraph，接入成本主要在配置层。</p>
<p style="color: #191b1f;" data-pid="sC2OiHZG">许可证要过法务。主项目 AGPLv3，<code>crates/ov_cli</code> 和 examples 是 Apache 2.0。AGPLv3 自用没问题，基于它对外提供网络服务会触发传染性条款的开源义务。商业版 OpenViking Service 承诺与开源版共享同一套代码内核，差异在托管运维、基于 VikingDB 的规模（宣称万亿级向量、百亿数据毫秒级检索）、SLA 和企业级安全合规，有至多 50 个文件的免费试用和迁移工具。开源版默认本地存储加单机向量引擎，数据规模上去后的可靠性要自己扛，架构容量规划时要计入。</p>
<p style="color: #191b1f;" data-pid="e6027xiq">背后有论文支撑：VikingMem（arXiv:2605.29640），已被 VLDB 2026 接收，开源版实现了论文描述的部分核心能力。团队在按数据库系统的标准做存储和检索设计，这在 Agent 记忆类项目里少见。</p>
<h2 style="font-weight: 500; color: #191b1f;">需要注意的点</h2>
<p style="color: #191b1f;" data-pid="NtGLKL_E">三点。</p>
<p style="color: #191b1f;" data-pid="XnhJGLus">其一，整个体系的地基是写入时的自动语义处理——摘要、概览、目录归置全靠模型生成，这些产物没有质量下界。LoCoMo 和 tau2-bench 覆盖的场景有限，换到术语密集的企业知识库，L0 摘要的准确率会不会崩，要拿自己的真实数据验证，别信 benchmark 直接上生产。</p>
<p style="color: #191b1f;" data-pid="GQmCvP6C">其二，导航式检索的延迟特性划定了适用边界，架构选型时按流量形态判断，前面说过了。</p>
<p style="color: #191b1f;" data-pid="wNBh-R4r">其三，项目还早，README 自己承认「要做的事还很多」。1813 次 commit、92 个 open issue、320 个 PR，迭代活跃，反面是 API 稳定性存疑，核心链路重度依赖它要做好跟版本的准备。</p>
<p style="color: #191b1f;" data-pid="89X_UDoy">回到 Agent 架构本身。「上下文作为独立子系统」「统一文件存储模型」「按决策阶段分层供给」「检索决策可回放」「经验回写闭环」，这五个设计每一个都能脱离 OpenViking 独立存在。</p>
<p style="color: #191b1f;" data-pid="bVp_k3oJ">以上</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/07/agent-openviking/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RAG 的新思路：SAG 和 OpenViking</title>
		<link>https://www.phppan.com/2026/07/rag-sag-openviking/</link>
		<comments>https://www.phppan.com/2026/07/rag-sag-openviking/#comments</comments>
		<pubDate>Sat, 18 Jul 2026 01:23:09 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[OpenViking]]></category>
		<category><![CDATA[RAG]]></category>
		<category><![CDATA[SAG]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2518</guid>
		<description><![CDATA[最近在看 RAG 的两个新思路，其实也不是说特别新，有点新瓶装旧酒的意思。 对于 RAG 的优化，一种线路是继 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p style="color: #191b1f;" data-first-child="" data-pid="1fXSVj0D">最近在看 RAG 的两个新思路，其实也不是说特别新，有点新瓶装旧酒的意思。</p>
<p style="color: #191b1f;" data-pid="RUmjD_UP">对于 RAG 的优化，一种线路是继续优化传统 RAG：切块更细一点，召回器多堆几层，重排再优化，最后把 prompt 拼得更精致。另一条线路是开始换问题定义：如果知识检索本身的建模方式就不对，后面那一长串补丁是不是从第一步就已经输了。</p>
<p style="color: #191b1f;" data-pid="_olrvRH0">SAG 和 OpenViking 属于第二种。</p>
<p style="color: #191b1f;" data-pid="HeY8Xerb">这两个项目看起来也不在一个层面上。SAG 讨论的是检索架构，OpenViking 讨论的是 Agent 的上下文系统。但我看下来，它们都是在解决同一类问题：RAG 之所以越来越重，往往不是因为模型不够强，而是因为我们把「知识」和「上下文」这两个对象建模得太糙。</p>
<p style="color: #191b1f;" data-pid="LjSDkiy4">过去一年，团队在 RAG 上花的钱和精力，主要耗在三个地方：</p>
<ul style="color: #191b1f;">
<li data-pid="Q_FzUCM0">文档切块以后，语义边界被打碎；</li>
<li data-pid="YMGHx5Q9">多跳关联靠 embedding 硬撞，召回经常断链；</li>
<li data-pid="g7T8fwry">想补结构化能力，就往 GraphRAG 走，结果离线构图、实体归一、增量更新把系统拖住。</li>
</ul>
<p style="color: #191b1f;" data-pid="onKtqQkj">这里的问题是这条路线会把系统带向复杂化。</p>
<h2 style="font-weight: 500; color: #191b1f;">SAG</h2>
<p style="color: #191b1f;" data-pid="y1azbgYe">SAG 的核心论文《SAG: SQL-Retrieval Augmented Generation with Query-Time Dynamic Hyperedges》提出了一种非常精妙的架构。它不是传统 RAG 与 GraphRAG 的简单拼接，而是一套用自己的数据模型和执行路径替代二者的原创检索架构。</p>
<p style="color: #191b1f;" data-pid="SVySmZQU">它的核心思想可以总结为八个字：轻量离线，动态建图。</p>
<h3 style="font-weight: 500; color: #191b1f;">1. 数据模型</h3>
<p style="color: #191b1f;" data-pid="_rlXvXFJ">传统 GraphRAG 把文本切碎，试图把所有知识都抽象成 <code>(Subject, Predicate, Object)</code> 的三元组。这种做法丢失了文本原本的上下文语义，导致最后召回出来的都是一些干瘪的关系链，LLM 很难根据这些碎片生成高质量的回答。</p>
<p style="color: #191b1f;" data-pid="Z214xO2o">SAG 重新设计了数据模型：</p>
<ul style="color: #191b1f;">
<li data-pid="_NHVfHuX">Chunk  Event（事件）：一个语义完整的文本块（Chunk）被映射为一个 Event。Event 承载该 Chunk 的完整上下文，它是最终输出和溯源的边界，不再被拆成彼此独立的三元组。</li>
<li data-pid="H7aHZdBM">Chunk  Entities（实体）：从该 Chunk 中抽取多个 Entities。这些实体只负责索引和扩展，不替代事件本身所承载的完整含义。</li>
<li data-pid="xVaNvZ__">Event  Entities：建立事件与实体之间的关联，共同定义了一条潜在的超边（Latent Hyperedge）。</li>
</ul>
<div class="highlight" style="color: #191b1f;">
<pre><code class="language-text">+-------------------------------------------------------------+
|                         Original Chunk                      |
+-------------------------------------------------------------+
                               |
                               v
+-------------------------------------------------------------+
|                         Event (完整语义)                     |
+-------------------------------------------------------------+
            /                  |                  \
           v                   v                   v
+------------------+   +------------------+   +------------------+
|     Entity A     |   |     Entity B     |   |     Entity C     |
+------------------+   +------------------+   +------------------+</code></pre>
</div>
<p style="color: #191b1f;" data-pid="eQlN2Sne">这个设计让事件和实体各司其职。实体只用来做连接的「钩子」，而事件负责保留完整的语义。</p>
<h3 style="font-weight: 500; color: #191b1f;">2. 查询时动态超边</h3>
<p style="color: #191b1f;" data-pid="54N6Zm0s">SAG 不预先构建、也不全局维护任何静态图谱。所有的关系推理，都是在检索发生的那一瞬间，通过关系型数据库的 SQL JOIN 动态计算出来的。</p>
<p style="color: #191b1f;" data-pid="ZTUpxhw-">当一个用户查询（Query）进来时，在线检索流程如下：</p>
<ol style="color: #191b1f;">
<li data-pid="pRr9Aued">种子定位：通过语义（向量）与词法（全文检索）信号，找到与查询最相关的「种子实体」和「种子事件」。</li>
<li data-pid="YnTQrQq0">SQL 动态扩展：利用关系型数据库的 SQL JOIN 查询，沿着这些种子实体，将共享相同实体的事件连接起来。</li>
<li data-pid="yBjXbXdY">局部超边实例化：在查询时，仅针对当前查询所需的局部结构实例化「超边」，不进行任何全局图遍历。</li>
<li data-pid="SWgJFUPa">证据还原：将筛选出的事件映射回原始 Chunk，去重后作为最强证据提供给 LLM 生成带引用的回答。</li>
</ol>
<p style="color: #191b1f;" data-pid="tzLR-tAW">在这个过程中，我们不需要 Neo4j，不需要复杂的图算法，底层的存储与查询完全依赖标准的数据库基础设施（如 SQLite、PostgreSQL、LanceDB 等）。</p>
<p style="color: #191b1f;" data-pid="ifTLLvmO">因为超边是查询时动态计算的，所以当有新文档导入时，不需要重算或重构全局图谱。新 Chunk 只需要并行抽取自身的 Event、Entities 并写入数据库即可。这让系统天然支持了高并发的增量写入。</p>
<p style="color: #191b1f;" data-pid="OlIwqZMy">从跑分数据来看，在相同的 <code>BGE-Large-EN-v1.5</code> Embedding 与 <code>Qwen3.6-Flash</code> LLM 配置下，SAG 在 HotpotQA、2WikiMultiHopQA 和 MuSiQue 的 9 项 Recall@1/2/5 指标中取得了 8 项最佳成绩。其平均 Recall@2/Recall@5 达到了 79.30%/88.18%，而 HippoRAG 2 仅为 68.14%/83.28%。</p>
<h3 style="font-weight: 500; color: #191b1f;">一些问题</h3>
<p style="color: #191b1f;" data-pid="EdHNLWO9">SAG 不是银弹，还存在一些问题。</p>
<p style="color: #191b1f;" data-pid="9C1pCkMN">抽取质量依然依赖 LLM。Event 的切分和 Entity 的抽取用的还是大模型，模型抽错了，后面的动态连接就是在错误的实体上做 JOIN。SAG 降低了实体的语义负担，但没消除抽取本身的不确定性。这块的鲁棒性得拿自己的语料实测。</p>
<p style="color: #191b1f;" data-pid="exFuuSpu">查询时的实体提取是另一个隐患。动态超边的起点是从 Query 里提出核心实体，如果用户的提问很口语、实体表达很模糊，种子定位偏了，后面整条链就歪了。这个环节的召回率我认为是整个系统最脆弱的地方，值得单独压测。</p>
<p style="color: #191b1f;" data-pid="ZpLngGcn">还有并发下的延迟分布。JOIN 在数据量大的时候，SQL 的执行计划、索引命中情况会直接影响尾延迟。其底层继承了数据库的并发能力，在海量数据情况下需要观察其延迟分布。</p>
<h2 style="font-weight: 500; color: #191b1f;">OpenViking</h2>
<p style="color: #191b1f;" data-pid="PtUfIrTD">聊完 SAG，看下 OpenViking。它跟 SAG 不是同一个层面的东西，我把它俩放一篇文章里，是因为它们代表了同一个方向上两种不同深度的探索。</p>
<p style="color: #191b1f;" data-pid="j76welwA">SAG 解决的是「怎么把知识检索得更准」。OpenViking 解决的是「Agent 的上下文该怎么组织」。前者是检索问题，后者是上下文工程问题。</p>
<p style="color: #191b1f;" data-pid="2IRiyc_l">火山引擎把它定位成一款面向 AI Agent 的上下文数据库，基于开源的 OpenViking 内核构建的全托管云服务。核心思想它自己概括成四句：虚拟文件系统、分层上下文、目录递归检索、可观测与自迭代。</p>
<h3 style="font-weight: 500; color: #191b1f;">万物皆文件</h3>
<p style="color: #191b1f;" data-pid="wM7uXlUR">OpenViking 把 Memory、Resource、Skill 三样东西统一抽象成文件，全部映射到 <code>viking://</code> 协议下的虚拟目录，每个条目有唯一的 URI。</p>
<p style="color: #191b1f;" data-pid="gc32jwaL">这个抽象是 Agent 发展到现在的一种沉淀，这里最早是 Manus 在实践的逻辑。Agent 领域现在最乱的就是上下文管理。你有用户的长期记忆、有外部知识资源、有工具和技能定义，这三样东西各自用各自的存储、各自的检索方式，代码里到处是特判。OpenViking 把它们收敛成同一套文件语义之后，Agent 就能用 <code>list</code>、<code>find</code> 这种统一指令去操作所有上下文。</p>
<p style="color: #191b1f;" data-pid="_3LYlKmg">从模糊的语义匹配变成确定性的文件操作，这个转变对调试很有帮助。向量检索是个黑盒，你不知道为什么这次召回了这几条、漏了那几条。文件系统是白盒，路径、目录结构摆在那，Agent 走了哪条路一目了然。</p>
<h3 style="font-weight: 500; color: #191b1f;">分层加载</h3>
<p style="color: #191b1f;" data-pid="sDzTcpAv">OpenViking 在上下文写入的时候，就自动把它处理成三层。</p>
<p style="color: #191b1f;" data-pid="F4c1zBn0">L0 是一句话摘要，用来快速判断。L1 是概述，包含核心信息和使用场景，供 Agent 在规划阶段决策。L2 是完整原始数据，Agent 确有必要时才深入读取。</p>
<p style="color: #191b1f;" data-pid="cjBxcrRk">这个设计针对的是一个很现实的成本问题。把海量上下文一次性塞进提示词，token 烧钱、容易爆窗口、还引入噪声。分层之后，Agent 在规划阶段只看 L0 和 L1 就能做大部分决策，只有真正要用某份资料的时候才去拉 L2。</p>
<p style="color: #191b1f;" data-pid="K3tDsg_L">我算过一笔账。一个复杂的多步 Agent 任务，如果每一步都把候选上下文的全文塞进去，token 消耗是指数级的。分层供给相当于给 Agent 一个「先看目录再翻正文」的能力，规划阶段的 token 成本能压下去一大截。具体压多少取决于你的任务形态，但这个方向省的钱是实打实的。</p>
<h3 style="font-weight: 500; color: #191b1f;">目录递归检索</h3>
<p style="color: #191b1f;" data-pid="WnkGa_L1">OpenViking 的检索不是单次向量召回。流程是这样：先通过意图分析生成多个检索条件，用向量检索快速定位初始切片所在的高分目录，然后在这个目录下做二次检索，把高分结果更新到候选集合；如果目录下还有子目录，就逐层递归重复二次检索；最后拿到最相关的上下文返回。</p>
<p style="color: #191b1f;" data-pid="xIoODZuX">「先锁定高分目录，再精细探索内容」，这套策略把向量检索的语义定位能力和文件系统的层次结构结合起来了。单纯的向量检索找到的是孤立的片段，它不理解片段所在的完整语境。目录递归能顺着文件的组织结构，把一个片段周围的语境一起带出来。</p>
<p style="color: #191b1f;" data-pid="RIxwplIN">这个思路和 SAG 的动态超边其实有相通之处。两者都意识到孤立的语义片段不够用，都想在检索时把「关联」这层信息补回来。SAG 用的是共享实体的 SQL JOIN，OpenViking 用的是目录层级的递归遍历。一个走关系代数，一个走树形结构。解决的底层痛点是同一个。</p>
<h3 style="font-weight: 500; color: #191b1f;">可观测与自迭代</h3>
<p style="color: #191b1f;" data-pid="WRaFPZaF">OpenViking 的检索过程，每次的目录浏览、文件定位轨迹都被完整留存，能看清问题根源、指导检索逻辑优化。</p>
<p style="color: #191b1f;" data-pid="ihfugwnx">对做过 RAG 调优的人来说，这个能力的分量不用多解释。传统向量检索出了问题，你几乎无从下手，只能瞎调 embedding、瞎调 chunk size。有了完整的检索轨迹，你能精确复盘 Agent 每一次信息获取走了哪条路径，哪一步偏了。</p>
<p style="color: #191b1f;" data-pid="j5hK7ZY_">自迭代那部分是通过 <code>session.commit()</code> 主动触发的。会话结束时，系统异步分析任务执行结果和用户反馈，自动更新到 User 和 Agent 的 <code>/memory</code> 目录下。它既更新用户偏好，也从任务执行经验里提炼操作技巧和工具使用经验。</p>
<p style="color: #191b1f;" data-pid="IedPQcM9">这个闭环我持谨慎乐观。自动提炼记忆听着很美，但提炼的质量、更新的边界、错误记忆的污染问题，都是需要长期跑才能暴露的。我不会一上来就把它当成生产依赖，会先在低风险场景里观察它更新出来的记忆到底靠不靠谱。</p>
<h2 style="font-weight: 500; color: #191b1f;">SAG 和 OpenViking</h2>
<p style="color: #191b1f;" data-pid="67yBB8an">写到这，我把 SAG 和 OpenViking 放到一起，讲讲我看到的相同点。</p>
<p style="color: #191b1f;" data-pid="pbu5OKNS">传统 RAG 的世界观是扁平的。所有知识被切成等长的 Chunk，拍平进一个向量空间，检索就是在这个空间里找最近邻。这套世界观简单、好扩展，但它主动丢弃了知识之间的结构信息。Chunk 和 Chunk 之间是什么关系，谁包含谁，谁引用谁，谁是谁的上下文，全都被拍平的过程碾掉了。</p>
<p style="color: #191b1f;" data-pid="S-ZRS-D0">SAG 和 OpenViking 从两个不同的方向，试图把这层被碾掉的结构信息找回来。</p>
<p style="color: #191b1f;" data-pid="qXo03YTa">SAG 找回的是「实体关联」这层结构。它承认知识点之间存在通过共享实体建立的隐式关联，并且用查询时的 SQL JOIN 把这层关联即时重建出来。它的贡献在于证明了这层结构不需要离线物化成一张昂贵的全局图，用关系型数据库现成的关联能力就能在查询时高效算出来。</p>
<p style="color: #191b1f;" data-pid="JiaAO3JE">OpenViking 找回的是「层次组织」这层结构。它承认知识天然有目录、有层级、有从属关系，并且用虚拟文件系统把这层组织显式地保留下来，检索的时候顺着这个层级递归下去。它的贡献在于把 Agent 的上下文从一堆散落的切片，重新组织成了一个可导航、可追溯的结构。</p>
<p style="color: #191b1f;" data-pid="HKLRQQ1F">两者都在做同一件事：扁平化的向量检索丢失了太多结构，而这些结构恰恰是复杂查询和长程任务真正需要的东西。它们的分歧只在于用什么载体去承载这层结构。SAG 选了关系表和 SQL，OpenViking 选了文件系统和目录树。</p>
<h2 style="font-weight: 500; color: #191b1f;">怎么选</h2>
<p style="color: #191b1f;" data-pid="NZfpFAuE">落到实际决策，可以分两个场景。</p>
<p style="color: #191b1f;" data-pid="cch9De1d">如果核心诉求是知识库的多跳检索精度，语料相对静态、增量频繁、又要给用户看得见的原文引用，建议评估 SAG。它的动态超边在多跳上的数据摆在那，增量友好和溯源能力恰好解决了之前方案的问题。部署门槛低，本地就能验，评估成本几乎为零。</p>
<p style="color: #191b1f;" data-pid="JVXLBY-2">如果做的是 Agent 产品，痛点在长程任务的上下文管理、记忆沉淀、多 Agent 协作，建议去评估 OpenViking。它解决的是比检索更上一层的问题，文件系统抽象和分层加载对控制 Agent 的 token 成本、提升可调试性比较有用。</p>
<p style="color: #191b1f;" data-pid="qmo6Q85T">这两个东西不冲突。理论上一个成熟的 Agent 系统，底层完全可以用 SAG 这类引擎做精准的知识检索，上层用 OpenViking 这类框架做上下文的组织和调度。一个管「检索得准」，一个管「组织得清」，各司其职。</p>
<p style="color: #191b1f;" data-pid="m6gM23j_">检索这条路走到今天，值得关注的方向已经从「怎么把向量算得更快」转到了「怎么把结构找回来」。SAG 和 OpenViking 是这个转向上两个具体的答案，一个在检索层，一个在上下文层。</p>
<p style="color: #191b1f;" data-pid="aKBEeaKX">以上。</p>
<div id="gtx-trans" style="position: absolute; left: 436px; top: 223px;"></div>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/07/rag-sag-openviking/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Session First 和 Agent First，本身并没有高低之分</title>
		<link>https://www.phppan.com/2026/07/session-first-and-agent-first/</link>
		<comments>https://www.phppan.com/2026/07/session-first-and-agent-first/#comments</comments>
		<pubDate>Sun, 05 Jul 2026 14:49:59 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Agent]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2515</guid>
		<description><![CDATA[最近在思考一个问题：系统究竟把谁当成第一类公民。 现在我们看到很多的 AI 产品，，本质上还是「把聊天框做得更 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p style="color: #191b1f;" data-first-child="" data-pid="hn-QCbaX">最近在思考一个问题：系统究竟把谁当成第一类公民。</p>
<p style="color: #191b1f;" data-pid="ptsNuLrP">现在我们看到很多的 AI 产品，，本质上还是「把聊天框做得更厚一点」。用户打开一个窗口，对着通识大模型说需求，模型在一个长 session 里记上下文、调几个工具、写几段内容、偶尔执行点操作。这个模式我称它为「Session First」。</p>
<p style="color: #191b1f;" data-pid="LXs8VSfS">「Session First」的模式跑得起来，落地也快。过去一段时间，桌面端的很多产品，包括一些 codex cc 一类的形态，都是这么长出来的。先有聊天，再把能力缝进去。先有 session，再把工具挂上去。整个产品又快又省，因为它继承的是大模型最自然的交互方式：对话。</p>
<p style="color: #191b1f;" data-pid="xYQONgUk">但如果把时间线拉长，我觉得 Session First 像是一个过渡层。它把大模型带进软件，却没有真正重写软件。</p>
<p style="color: #191b1f;" data-pid="KEgmjjTl">另一个逻辑是 「Agent First」。</p>
<p style="color: #191b1f;" data-pid="7RKbj9VV">这里说的 Agent First，不是把 prompt 包一层 workflow 就算 agent。这里说的是一种更彻底的软件设计取向：系统从一开始就假设调用者不只是人，也包括 agent；系统的能力边界、接口形态、文档组织、安全机制、运行时观测，都优先围绕 agent 来设计。聊天只是入口之一，不再是核心结构。</p>
<p style="color: #191b1f;" data-pid="VVmbLg7b">过去我们是在和「通识大模型」对话，未来更多时候，我们是在和「带有领域知识、工具能力、任务规划能力的专用 agent」协作。再往前走，单一 agent 很可能都不够，基于 MaaS 的 agent teams 会逐步成为复杂任务的常态。Sakana AI 这类工作给了行业一个很有代表性的信号：多智能体协作，不只是研究兴趣，它在复杂探索任务上确实可能优于单体。</p>
<p style="color: #191b1f;" data-pid="MfTI3kQd">这种两种不同的范式，其实也没有高级和低级之分。二者背后的系统假设不同，工程代价不同，性能瓶颈不同，能解决的问题类型也不同。</p>
<h2 style="font-weight: 500; color: #191b1f;">Session First 和 Agent First 的不同</h2>
<p style="color: #191b1f;" data-pid="NZsnKq2A">Session First 和 Agent First，表面上都可能长得像「用户输入一句话，系统输出一个结果」。很多人可能会觉得两者只是包装差异。其实差异蛮大的。</p>
<p style="color: #191b1f;" data-pid="ab7mBUZ1">Session First 的中心对象是「会话」。系统假设一切能力都围绕一个持续增长的上下文展开。用户说一句，模型接一句；模型靠历史消息理解意图，靠当前 prompt 决定行为。工具调用存在，但工具通常是会话的附属物。它们由模型在 session 内临时选择、临时编排、临时解释。</p>
<p style="color: #191b1f;" data-pid="nSHdRCaa">所以在 Session First 里，能力组织方式通常是这样的：</p>
<ol style="color: #191b1f;">
<li data-pid="5eq_-0Tu">先有一个大而全的聊天入口；</li>
<li data-pid="9jCgn5ps">再有一组工具函数；</li>
<li data-pid="BPUP9XxE">再在 system prompt 或 tool spec 里告诉模型什么时候调用它们；</li>
<li data-pid="wYj2UQTX">复杂任务靠更长的上下文、更复杂的提示词、更精细的 few-shot 去兜。</li>
</ol>
<p style="color: #191b1f;" data-pid="Jvrm8KC3">这个模式最大的好处，是产品和研发都能快速起跑。我们不需要先把系统抽象成可组合能力，也不需要先考虑 agent 的长期运行、恢复、权限边界、状态持久化。只要模型够强、上下文够长、工具挂得上去，很多事情都能先做出来。</p>
<p style="color: #191b1f;" data-pid="eiiIaM52">Agent First 的中心对象则是「行动体」。系统假设执行任务的主体是 agent，它会自己读取规范、规划步骤、调用工具、检查结果、重试修正。人在这个系统里依然重要，但人不再是唯一的控制中心。更准确地说，系统从「为人类交互而生」转向「为可委托执行而生」。</p>
<p style="color: #191b1f;" data-pid="8Fh6-t08">这个变化会连锁改写很多设计决策。</p>
<p style="color: #191b1f;" data-pid="NLaUuQ5D">传统软件里，人是第一类公民。系统主要通过 UI 提供能力，文档写给人看，按钮给人点，异常信息也默认人会读。Agent First 里，agent 变成第一类公民。系统的关键界面不再是页面和按钮，而是 API、工具协议、语义化描述、状态机、权限模型、回调事件、执行日志。</p>
<p style="color: #191b1f;" data-pid="pgeDZ4to">传统模式是：</p>
<ul style="color: #191b1f;">
<li data-pid="J6fBCe4D">人读文档；</li>
<li data-pid="TGKaTkKs">人理解业务逻辑；</li>
<li data-pid="L5PkGFvA">人决定点哪个按钮；</li>
<li data-pid="SQC_RLB3">人承担串联流程的责任。</li>
</ul>
<p style="color: #191b1f;" data-pid="MGPdMjHs">Agent 模式是：</p>
<ul style="color: #191b1f;">
<li data-pid="guhaOQ-O">Agent 读取规范；</li>
<li data-pid="PD18qokp">Agent 形成计划；</li>
<li data-pid="muoTtpZi">Agent 调用工具；</li>
<li data-pid="vzGt_6af">Agent 分析返回值；</li>
<li data-pid="5TH-VB1A">Agent 根据反馈继续执行或者回滚。</li>
</ul>
<p style="color: #191b1f;" data-pid="zkoP7tjz">这不是交互方式的小修小补，这是控制权和复杂性承载位置的转移。</p>
<p style="color: #191b1f;" data-pid="JtbYA5eh">Session First 把复杂性藏在会话里。<br />
Agent First 把复杂性显式地放进系统结构里。</p>
<p style="color: #191b1f;" data-pid="Q0qwK4Ck">我更倾向后者。因为在规模化阶段会 Session First 开始需要大量的修补并且还不合脚。</p>
<h2 style="font-weight: 500; color: #191b1f;">Session First 的红利</h2>
<p style="color: #191b1f;" data-pid="K2rl4doQ">Session First 也不是说不行了，落后了，它只是有自己的舒适区或边界。</p>
<p style="color: #191b1f;" data-pid="Poz6VWWp">它为什么会成为大多数团队的第一个选择？因为它天然适合模型的原生能力。</p>
<p style="color: #191b1f;" data-pid="5bGlRG3o">大模型最成熟的接口就是聊天接口。你给它上下文，它续写；你给它 instruction，它服从；你给它工具定义，它在概率空间里学着调用。这是当前已经成熟了的，并且对于大家的谁知来说没有门槛的。。产品经理能理解，前端能接，后端能包，用户也能马上用。</p>
<p style="color: #191b1f;" data-pid="gm_4ZgMy">从落地顺序看，Session First 有三个明显优势。</p>
<h3 style="font-weight: 500; color: #191b1f;">交付速度快</h3>
<p style="color: #191b1f;" data-pid="hRxCUQ2-">最早一批 AI 应用能起量，基本都吃到了这个红利。做一个对话框，叠一层历史上下文，挂几类工具，外加 prompt 工程和少量业务逻辑，一个可卖的产品就出来了。</p>
<p style="color: #191b1f;" data-pid="z86qAnpw">如果团队处在探索期，需求还没稳定，任务边界也不清晰，Session First 的性价比很高。因为我们可以把大量未定型的业务规则临时编码进 prompt，而不是过早地固化到接口和状态机里。说白了，它适合试错。</p>
<h3 style="font-weight: 500; color: #191b1f;">交互弹性高</h3>
<p style="color: #191b1f;" data-pid="iPte5fGM">很多需求在早期根本说不清。用户自己也不知道该点哪个按钮，只知道「我想把这堆信息处理一下」。这时聊天入口比传统 UI 更自然。Session First 天然适合承接模糊需求，尤其适合开放式任务、咨询类任务、内容类任务。</p>
<h3 style="font-weight: 500; color: #191b1f;">对通识模型友好</h3>
<p style="color: #191b1f;" data-pid="EtKTSjEI">Session First 依赖的是模型在语言理解和上下文整合上的强项。很多场景下，不需要精细建模世界状态，只需要让模型在一个较长的 session 里维持语义连贯，效果就已经够用了。</p>
<p style="color: #191b1f;" data-pid="M-m8vnO9">所以如果一个产品主要解决的是下面这些问题，Session First 我认为完全合理：</p>
<ul style="color: #191b1f;">
<li data-pid="pClrcdBU">单轮或短链路任务；</li>
<li data-pid="u04g5i-D">任务结果主要是文本、建议、草稿、分析；</li>
<li data-pid="EMgZGE3l">工具调用数量少，失败代价低；</li>
<li data-pid="esYI4-QV">用户愿意在回路中持续确认；</li>
<li data-pid="aeA4V2r7">业务状态变化弱，对幂等性和恢复能力要求不高。</li>
</ul>
<p style="color: #191b1f;" data-pid="DQ0eBCYv">比如代码解释、文档问答、简历润色、轻量报表分析、知识库检索助手，这些都很适合。</p>
<p style="color: #191b1f;" data-pid="GG7xR8lI">问题出在很多团队做着做着，把它用到了不该用的地方。</p>
<h2 style="font-weight: 500; color: #191b1f;">Session 的代价</h2>
<p style="color: #191b1f;" data-pid="_IUlwReU">Session First 最大的问题，不是效果问题，而是系统复杂性的位置不对。</p>
<p style="color: #191b1f;" data-pid="b-58aYYB">我们可以把 session 理解成一个不断膨胀的黑盒。任务描述、历史记录、工具调用痕迹、失败重试信息、用户偏好、临时约束，全都塞进去。模型在黑盒里靠注意力机制和 token 预算自行判断什么重要、什么该忽略、什么该继续执行。</p>
<p style="color: #191b1f;" data-pid="35i3DpTY">短任务还行。链路一长，问题就开始多了。</p>
<h3 style="font-weight: 500; color: #191b1f;">状态污染</h3>
<p style="color: #191b1f;" data-pid="WkpeY-5P">会话越长，状态越容易脏。一个典型问题是历史上下文对当前决策的隐性干扰。模型没有传统意义上的干净状态管理，它只有一段被不断续写的上下文。早期的一句错误假设、一次失败调用、一个过时约束，都可能在后续步骤中持续影响行为。</p>
<p style="color: #191b1f;" data-pid="IkL8j6V6">工程上我们会看到一些很烦的现象：</p>
<ul style="color: #191b1f;">
<li data-pid="ypgoyL0L">明明用户已经修改目标，模型还沿着旧计划跑；</li>
<li data-pid="OY4uWoRi">明明工具返回了失败，模型把失败结果当成成功上下文继续推理；</li>
<li data-pid="KVdGdpqP">明明当前任务和上个任务无关，模型还把旧 session 里的偏好带进来。</li>
</ul>
<p style="color: #191b1f;" data-pid="o8pRGOZH">这些都不是 prompt 多写两句能解决的。因为根因在于 session 本身不是一个严谨的状态容器。</p>
<h3 style="font-weight: 500; color: #191b1f;">上下文成本失控</h3>
<p style="color: #191b1f;" data-pid="ISTL4CkN">Session First 很吃上下文。任务越复杂，历史越长，系统 prompt 越复杂，tool spec 越多，token 消耗越惊人。很多团队前期盯着模型单价，后期才发现账单真正膨胀的是「无效上下文」。</p>
<p style="color: #191b1f;" data-pid="AAb4brHY">更糟的是，这部分成本并不总能换来线性收益。超过某个长度以后，模型对上下文的利用率明显下降。你花了更多 token，只换来更模糊的关注分布、更高的遗漏概率。</p>
<p style="color: #191b1f;" data-pid="HaBtPlMi">有一些系统，一次复杂任务真正有价值的工作 token 可能只占总 token 的 20% 到 30%，剩下的都在重复喂历史、喂规则、喂工具描述、喂先前失败记录。这样的系统，成本结构很难看。</p>
<h3 style="font-weight: 500; color: #191b1f;">工具选择不稳定</h3>
<p style="color: #191b1f;" data-pid="g8QZRvno">在 Session First 里，工具往往是通过提示词暴露给模型。模型根据自然语言描述决定什么时候调哪个工具。这种方式灵活，但稳定性一般。尤其当工具数量上来以后，工具间语义重叠、参数边界相近、返回格式不一致，模型的选择质量会迅速下降。</p>
<p style="color: #191b1f;" data-pid="4qfGTgK1">一个常见误区，是以为给工具写更长更详细的 description 就能解决。实际经验正相反：description 越长，竞争工具越多，模型越容易在语义相邻区域摇摆。最终你会发现，问题不是模型笨，而是整个工具层根本没有被设计成适合被模型消费。</p>
<h3 style="font-weight: 500; color: #191b1f;">可恢复性差</h3>
<p style="color: #191b1f;" data-pid="11wrlEFs">Session 是连续流，不擅长离散恢复。任务执行到一半，模型挂了、超时了、工具限流了、用户关闭页面了，系统怎么从中间恢复？很多 Session First 产品的恢复策略要么是重放整个对话，要么让模型读历史「自己想起来」。</p>
<p style="color: #191b1f;" data-pid="lbhAfa1q">这个策略在低风险任务里还能接受，在执行型任务里就很危险。因为它没有明确的检查点，没有确定的已完成步骤，没有结构化的执行日志，恢复质量完全依赖模型在长上下文里的自我理解。</p>
<h3 style="font-weight: 500; color: #191b1f;">可观测性弱</h3>
<p style="color: #191b1f;" data-pid="1BwIcWo5">你问一个 Session First 系统：「为什么它刚才这么做？」答案通常很难给。因为真正的决策过程埋在 session 和模型隐状态里。你最多能看到 prompt、工具调用记录和输出，但很难形成稳定的、可归因的行为分析。</p>
<p style="color: #191b1f;" data-pid="aMr0GfSw">这会直接影响调试、评估、审计和优化。团队很容易陷入一种很熟悉的工作流：改 prompt、跑样例、感觉好一点、上线、再出新问题、继续改 prompt。系统像在「训一头很聪明但脾气不稳定的动物」，而不是在维护一个可控软件。</p>
<h2 style="font-weight: 500; color: #191b1f;">转向 Agent</h2>
<p style="color: #191b1f;" data-pid="_2FvNRl2">Agent First 出现，本质上是在回答一个问题：当任务不再是聊天，而是委托执行时，系统该怎么设计？</p>
<p style="color: #191b1f;" data-pid="FLwpnuCC">用一名话来概括：Session First 优先组织对话，Agent First 优先组织能力、状态和约束。</p>
<p style="color: #191b1f;" data-pid="WCXxazki">这两者的差别，在简单场景里不明显；一旦任务变成多步骤、长周期、高风险、强工具依赖，这个差别会迅速放大。</p>
<p style="color: #191b1f;" data-pid="KpP65M8H">Agent First 的核心变化有三层。</p>
<p style="color: #191b1f;" data-pid="wsX8jMc9">第一层，agent 拥有独立的任务身份。<br />
它不再只是当前聊天窗口里的一个响应函数，而是一个可启动、可暂停、可恢复、可审计的执行单元。它有自己的目标、记忆、工具权限、运行环境和生命周期。</p>
<p style="color: #191b1f;" data-pid="FnF9sNEW">第二层，系统能力被显式结构化。<br />
什么能力能调用，输入输出是什么，失败怎么表示，重试边界在哪，副作用怎么隔离，全部要写清楚。因为 agent 不是靠「猜」来用系统，它得靠规范来用系统。</p>
<p style="color: #191b1f;" data-pid="80wVgyqQ">第三层，任务执行被流程化和可观测化。<br />
agent 的计划、步骤、结果检查、异常处理、人工介入点，都需要成为系统的一部分，而不是 prompt 里的一段希望。</p>
<p style="color: #191b1f;" data-pid="H72jwr7d">这就是为什么我说 Agent First 更像软件设计理念，而不只是交互升级。它要求开发者从一开始就考虑 agent 的接入体验。注意，这里的「接入体验」不是 SDK 文档写得漂不漂亮，而是系统有没有把 agent 当成真正的使用者来对待。</p>
<p style="color: #191b1f;" data-pid="qkJK4dl5">对人友好的系统，重点是 UI。<br />
对 agent 友好的系统，重点是 API、协议、文档、沙箱、日志。</p>
<p style="color: #191b1f;" data-pid="KhUTKVDc">这个顺序一换，整套架构都会变。</p>
<h2 style="font-weight: 500; color: #191b1f;">第一类公民</h2>
<p style="color: #191b1f;" data-pid="U_W5I-Ey">传统软件的第一类公民是人。因为系统操作链条默认由人承担：读页面、理解状态、做选择、点按钮、确认风险、处理异常。</p>
<p style="color: #191b1f;" data-pid="HpE1H8hw">Agent First 的变化在于，这条链路开始迁移给 agent。系统如果还保留「很多关键信息只藏在页面里、很多操作只能靠人脑理解、很多异常只有人看得懂」的设计，那 agent 就只能在外面绕路，最后产品体验会非常拧巴。</p>
<p style="color: #191b1f;" data-pid="nyiOzfI0">所以所谓「agent 是第一类公民」，落到工程上至少意味着四件事。</p>
<h3 style="font-weight: 500; color: #191b1f;">能力必须接口化</h3>
<p style="color: #191b1f;" data-pid="q6mfDNhF">页面点击不是能力，API 才是。<br />
表格展示不是能力，结构化查询和变更才是。<br />
人工读懂的描述不算完成，机器可消费的 schema 才算完成。</p>
<p style="color: #191b1f;" data-pid="IT9qecwX">很多团队表面上说在做 agent，实际上只是让模型去模拟用户点页面，或者让 Playwright 去跑浏览器自动化。这能用，但我通常把它看成过渡手段，不会把它当成长期基建。因为 UI 自动化的脆弱性太高，成本也太高。一旦页面变了、字段换了、弹窗多了、权限策略改了，整个链路就坏了。</p>
<p style="color: #191b1f;" data-pid="z04KhJFd">如果一项业务能力值得被 agent 使用，那它应该先被抽成稳定接口。</p>
<h3 style="font-weight: 500; color: #191b1f;">语义必须外显</h3>
<p style="color: #191b1f;" data-pid="sv-argzO">我们靠经验猜按钮含义，agent 不行。我们得把操作语义、字段含义、约束条件、异常语义都写出来。很多传统系统文档的问题，不是文档少，而是文档默认阅读者是熟悉业务的人。里面有大量省略、上下文跳跃、术语别名、口头约定。</p>
<p style="color: #191b1f;" data-pid="AbUvlfL-">人能脑补。agent 不会脑补，它会误解。</p>
<p style="color: #191b1f;" data-pid="8vz62jG8">机器友好的文档，不是把原文档丢给 RAG 就结束了。它要求内容可索引、可切片、可定位、可引用、可验证。最好还能区分「定义」「约束」「样例」「反例」「危险操作」「返回码语义」。</p>
<h3 style="font-weight: 500; color: #191b1f;">权限必须细粒度</h3>
<p style="color: #191b1f;" data-pid="BiX7vv2-">给人开的权限，往往是按角色开的。给 agent 开权限，粒度要更细。因为 agent 的调用频率高、组合能力强、自动化程度高，任何一个权限放大，都可能把小问题变成系统性事故。</p>
<p style="color: #191b1f;" data-pid="wyE2MMGJ">我见过一些团队初期为了快，直接给 agent 一个「管理员 token」，想着先把链路跑通。跑通确实跑通了，后面风控和审计基本没法收场。Agent First 里，权限模型必须从 day 1 就设计进去。至少要做到工具级、资源级、动作级的边界清晰。</p>
<h3 style="font-weight: 500; color: #191b1f;">结果必须可审计</h3>
<p style="color: #191b1f;" data-pid="5uGts6Js">如果 agent 能执行动作，那所有关键动作都要留痕，谁发起、为什么发起、使用了哪些上下文、调用了哪些工具、拿到了哪些结果、做了什么决策、是否有人确认，都得能追出来。</p>
<p style="color: #191b1f;" data-pid="mGwZ_bFC">这不是为了满足审计部门，而是为了我们自己能把系统维护下去。没有可审计性，复杂 agent 系统很快会变成「偶尔非常惊艳，偶尔完全失控」的黑箱。</p>
<h2 style="font-weight: 500; color: #191b1f;">四层结构</h2>
<p style="color: #191b1f;" data-pid="r--hVVP9">如果我们把 Agent First 的工程原则压缩成一个递进结构，它可以拆成四层：能力层、理解层、连接层、信任层。</p>
<h3 style="font-weight: 500; color: #191b1f;">API 优先</h3>
<p style="color: #191b1f;" data-pid="A4igmFYN">最底下是能力层，也就是 API 优先。它解决的是「Agent 能做什么」。</p>
<p style="color: #191b1f;" data-pid="4JihUE2D">很多 AI 团队容易犯一个错误：先做 prompt，再做工具，再补 API。顺序反了。只要你准备认真做 agent，API 就应该先于 prompt 存在。</p>
<p style="color: #191b1f;" data-pid="2iHv2A0t">因为 prompt 负责引导决策，API 才负责承载能力。没有稳定能力面，agent 的执行质量永远靠运气。</p>
<p style="color: #191b1f;" data-pid="elb7wRm_">我看一个系统适不适合 Agent First，第一眼就看它的 API 长什么样。重点不在 REST 还是 GraphQL，也不在用不用 MCP，而在这几个问题：</p>
<ul style="color: #191b1f;">
<li data-pid="2s6K-9ML">接口语义是否单一清晰；</li>
<li data-pid="ZOsm0FTZ">参数是否结构化且有约束；</li>
<li data-pid="M7y6QpEB">返回值是否可判定成功失败；</li>
<li data-pid="DlzhbZEV">幂等性是否明确；</li>
<li data-pid="cmD_93sl">长任务是否支持异步和回调；</li>
<li data-pid="qBP6SLYm">副作用操作是否支持 dry-run 或预检查；</li>
<li data-pid="PfxPltf2">错误码是否可用于 agent 自恢复。</li>
</ul>
<p style="color: #191b1f;" data-pid="AO9osijd">举个很实际的坑。很多内部系统的 API 是给前端页面写的，不是给 agent 写的。于是你会看到：</p>
<ul style="color: #191b1f;">
<li data-pid="lA_9Gzz2">一个接口返回几十个业务无关字段；</li>
<li data-pid="J3QyM1Sf">失败时只返回「操作失败，请联系管理员」；</li>
<li data-pid="x9A4Ttqr">同一个字段在不同接口里名字还不一样；</li>
<li data-pid="Gu2HXo6H">创建和更新共用一个入口，副作用混杂；</li>
<li data-pid="jQpa3MR4">数据查询支持模糊匹配，但没有稳定过滤条件。</li>
</ul>
<p style="color: #191b1f;" data-pid="8sN_fbkf">这种 API 给前端开发凑合能用，给 agent 基本就是灾难。因为 agent 消费接口时最怕三件事：语义不稳定、返回不可判定、失败不可恢复。</p>
<p style="color: #191b1f;" data-pid="z6FsivtG">API 优先不是一句口号，它意味着你要为了 agent 重写一部分服务边界。代价不小，但省下的是后面无数轮 prompt 补丁。</p>
<h3 style="font-weight: 500; color: #191b1f;">机器文档</h3>
<p style="color: #191b1f;" data-pid="y-sS3fH3">有了能力层，还不够。Agent 知道系统「能做什么」，不代表它知道「怎么正确地做」。</p>
<p style="color: #191b1f;" data-pid="tIfhwniQ">这就是理解层，也就是机器友好文档。</p>
<p style="color: #191b1f;" data-pid="5Yrlt4GA">很多团队对文档的理解还停留在「给模型塞进知识库」。坦白说，这一步最多算资料接入，不算文档工程。机器友好文档要求内容本身就是为 agent 理解和执行设计的。</p>
<p style="color: #191b1f;" data-pid="ZRf9ulQZ">我们写文档喜欢写背景、写故事、写注意事项穿插在长段落里。机器文档要反过来，尽量消除叙事性，强化检索和判定性。什么场景能用哪个接口，前置条件是什么，参数组合有什么限制，成功条件是什么，失败后应该重试还是终止，全部要能被定位出来。</p>
<p style="color: #191b1f;" data-pid="oECaLAXY">我比较推崇一种写法：把文档拆成五类最小单元。</p>
<ol style="color: #191b1f;">
<li data-pid="MW6izXr4">定义单元：术语、对象、字段、状态含义。</li>
<li data-pid="PESSY8gQ">操作单元：动作描述、输入输出、前置条件、后置条件。</li>
<li data-pid="7Dp5JAcB">约束单元：权限要求、配额、时序、互斥关系。</li>
<li data-pid="Okrzq469">异常单元：错误码、成因、恢复建议、是否可重试。</li>
<li data-pid="aAcF6hWc">样例单元：正确示例、错误示例、边界示例。</li>
</ol>
<p style="color: #191b1f;" data-pid="TowYNEzN">这样写出来的文档，对人读可能不友好，但对 agent 非常友好。因为 agent 需要的不是阅读体验，而是最短路径上的高密度语义。</p>
<p style="color: #191b1f;" data-pid="DWHac5Lh">还有一个容易被忽略的点：文档版本化。<br />
如果系统能力在变，而 agent 依赖旧文档决策，事故几乎是必然的。机器文档必须带版本、带生效范围、带弃用说明。更进一步，文档变更最好能够被 agent 订阅或者被平台主动推送。否则你会得到一堆「以前能跑今天突然不行」的鬼问题。</p>
<h3 style="font-weight: 500; color: #191b1f;">协议层</h3>
<p style="color: #191b1f;" data-pid="2vQVOH_W">再往上一层是连接层，也就是标准化协议。我们都知道 MCP，它当前重要性确实在上升。</p>
<p style="color: #191b1f;" data-pid="w7t9LhTp">Agent First 一旦进入平台化阶段，连接成本会成为瓶颈。每接一个系统都重新对工具做 schema 包装、鉴权对接、错误语义映射、流式交互适配，团队很快就会被集成工作拖死。标准协议的价值就在这里：让 agent 对外部能力做到尽可能低摩擦的即插即用。</p>
<p style="color: #191b1f;" data-pid="5BuRoxJ-">协议不是为了优雅，是为了降低耦合和重复劳动。它至少解决三个问题：</p>
<ul style="color: #191b1f;">
<li data-pid="42aDrj4i">能力发现：agent 如何知道外部提供了哪些工具和资源；</li>
<li data-pid="mWEcxg_n">调用协商：参数 schema、返回 schema、流式能力、状态反馈如何统一；</li>
<li data-pid="qBc-jFf5">安全边界：认证、授权、调用隔离、资源限制如何标准化表达。</li>
</ul>
<p style="color: #191b1f;" data-pid="Io6gZEhK">MCP 这类协议的意义，不只是统一 tool calling 的表面格式，更重要的是把「能力元数据」变成平台可理解的对象。一旦元数据标准化，很多平台能力才能长出来：自动装配、权限编排、调用治理、能力市场、兼容性检查、离线评测。</p>
<p style="color: #191b1f;" data-pid="UD9KwzhD">协议统一不会自动带来效果统一。现实里最常见的问题是：大家都说自己支持标准，结果 schema 质量参差不齐，语义粒度不一致，错误处理风格也不同。最后 agent 虽然能连上，依旧很难稳定用好。</p>
<p style="color: #191b1f;" data-pid="E3V0zbBN">所以协议层只是接入下限，不是体验上限。系统方如果指望「支持 MCP 了，agent 就能用了」，十有八九会失望。</p>
<h3 style="font-weight: 500; color: #191b1f;">信任成本</h3>
<p style="color: #191b1f;" data-pid="RfQwCW9C">最上面一层是信任层：安全、沙箱、可观测性。它解决的是「Agent 如何被安全地使用」。</p>
<p style="color: #191b1f;" data-pid="z4X0EalX">这层往往是被低估的。因为很多团队在前期更关心效果演示，安全和观测总想放后面补。等 agent 真开始执行动作，补起来就很痛苦。</p>
<p style="color: #191b1f;" data-pid="EfUj4D2a">Agent 系统的风险和传统自动化脚本不完全一样。脚本通常路径固定、输入有限、行为可枚举。Agent 的输入是开放的，计划是动态的，工具组合是可变的，自修正带来恢复能力，也带来行为不可预测性。这种系统如果没有信任层，部署规模一上来迟早出事。</p>
<p style="color: #191b1f;" data-pid="Rq3kFvBu">信任层可以分为三块。</p>
<h3 style="font-weight: 500; color: #191b1f;">安全边界</h3>
<p style="color: #191b1f;" data-pid="hzeX45vF">包括权限控制、数据隔离、密钥管理、配额限制、危险操作确认机制。所有高风险动作都要能分级：只读、可写、可执行、不可逆。不同级别的动作，对应不同的确认和审计策略。</p>
<p style="color: #191b1f;" data-pid="sys4IsBL">还有一件事必须单独说：prompt injection。<br />
只要 agent 会读取外部内容、再基于内容调用工具，prompt injection 就不是附加风险，而是基本风险。你不能假设模型会自己免疫。系统层必须有输入隔离、指令优先级控制、工具调用白名单、敏感动作二次确认、结果验证。</p>
<h3 style="font-weight: 500; color: #191b1f;">沙箱执行</h3>
<p style="color: #191b1f;" data-pid="1qt69UP0">如果 agent 能运行代码、操作文件、访问网络，就必须有沙箱。别指望靠「模型会守规矩」来兜底。资源限制、网络出口策略、文件系统隔离、进程生命周期管理，这些都是必须项。</p>
<p style="color: #191b1f;" data-pid="P0jKIeFS">很多桌面端产品今天还在用一个比较重的本地 session 容器去承接 agent 行为，这在早期合理，因为本地环境天然带着用户上下文和工具可得性。但一旦平台化，执行环境一定要可控、可回收、可复制。否则你会发现 bug 根本没法稳定复现。</p>
<h3 style="font-weight: 500; color: #191b1f;">可观测性</h3>
<p style="color: #191b1f;" data-pid="Pl7zc4zu">这一块经常被做得太浅。普通日志不够。你需要的是面向 agent 的运行时观测：任务级 trace、步骤级事件、工具调用链、上下文快照、计划变更、重试原因、人工接管点、最终结果评估。</p>
<p style="color: #191b1f;" data-pid="_sQQ-OmB">有了这些数据，很多事情才有可能做：行为分析、失败归因、离线回放、A/B 对比、策略优化、合规审计。</p>
<p style="color: #191b1f;" data-pid="VlCSTrO4">我甚至会说，没有可观测性，Agent First 根本不成立。因为你没法持续优化一个你看不见的系统。</p>
<h3 style="font-weight: 500; color: #191b1f;">单体与团队</h3>
<p style="color: #191b1f;" data-pid="KbTr2nmc">再往前一步，Agent First 还会带来一个自然演进：从单一 agent 走向 agent teams。</p>
<p style="color: #191b1f;" data-pid="F3isRc-L">这个趋势不难理解。单体 agent 的优势是简单、上下文统一、决策路径短。缺点也明显：任务一复杂，规划、执行、验证、知识检索、异常恢复全压在一个主体上，容易出现上下文拥堵和角色冲突。模型一边要想战略，一边要写细节，一边还要检查自己，稳定性会下降。</p>
<p style="color: #191b1f;" data-pid="K88LyqEo">多 agent 协作的思路，是把不同职责拆开。比如规划 agent 负责分解任务，执行 agent 负责调用工具，验证 agent 负责检查输出，审计 agent 负责看风险和合规。你提到 Sakana AI，我觉得它给行业的启发就在这里：复杂问题的效果上限，未必来自更大的单体，而可能来自更合理的协作结构。</p>
<p style="color: #191b1f;" data-pid="_9PvI3l1">但别高估 agent teams 的短期收益。它的工程成本很高。</p>
<p style="color: #191b1f;" data-pid="GBQ5As-1">第一，通信成本会增加。<br />
agent 之间传递的信息如果不够压缩，token 成本会迅速膨胀。很多团队做多 agent，最后账单翻倍，效果提升却不明显，问题就出在这里。</p>
<p style="color: #191b1f;" data-pid="k2LlSq6C">第二，错误归因更难。<br />
单体 agent 出错，你还知道看一条链。多 agent 出错，很可能是上游规划有偏差、中游执行误解了任务、下游验证规则又太松。没有细致的 trace，很难定位。</p>
<p style="color: #191b1f;" data-pid="XSuoPjJN">第三，协作协议本身就是复杂度。<br />
谁能给谁发任务，谁对谁有覆盖权，冲突怎么解决，结果以谁为准，失败是否回滚，人工在什么点介入，这些全得定规则。规则一多，系统会变得很重。</p>
<p style="color: #191b1f;" data-pid="sEiM2vWO">它是复杂任务的方向，但不是所有产品都该急着上。很多团队连单体 agent 的能力边界、观测体系、权限模型都没建好，就直接跳多 agent，最后只会把问题放大。</p>
<h2 style="font-weight: 500; color: #191b1f;">个人的判断</h2>
<p style="color: #191b1f;" data-pid="PygqIP5D">如果今天让我给团队定路线，我不会在所有场景里一刀切推 Agent First，也不会继续把 Session First 当主架构。</p>
<p style="color: #191b1f;" data-pid="MBtLFSYJ">我的判断是这样的：</p>
<p style="color: #191b1f;" data-pid="pJoRgTgx">凡是以「理解、生成、陪伴、咨询」为主，任务链条短，用户始终在线，副作用低的场景，Session First 依然是最有效率的方案。别把简单问题搞复杂。一个高质量 session 产品，照样能有非常强的竞争力。</p>
<p style="color: #191b1f;" data-pid="j575XQI9">凡是以「委托执行、跨系统操作、长周期任务、可恢复流程、强审计要求」为主的场景，应该尽早转向 Agent First。拖得越久，后面迁移成本越高。因为你的 prompt、工具、日志、权限、数据结构都会按 Session First 的惯性越长越歪，最后很难矫正。</p>
<p style="color: #191b1f;" data-pid="3EZ1NUZd">从行业演进看，我认为我们会经历三个阶段：</p>
<p style="color: #191b1f;" data-pid="0YVja-AL">第一阶段，通识模型 + 聊天入口主导。<br />
第二阶段，聊天入口还在，但底层逐步 agent 化，能力和状态开始结构化。<br />
第三阶段，很多系统默认面向 agent 开放，我们反而通过 agent 间接使用软件。</p>
<p style="color: #191b1f;" data-pid="r5BSjN4m">今天大多数团队还处在第一阶段和第二阶段之间。很多产品表面上已经在说 agent，骨子里还是 session 产品。这个阶段没什么丢人的，行业本来就处在过渡期。问题在于，团队自己得知道自己在哪，不要把一个 prompt-heavy 的聊天系统误以为已经完成了架构升级。</p>
<h2 style="font-weight: 500; color: #191b1f;">小结</h2>
<p style="color: #191b1f;" data-pid="z5MFKqKb">Session First 帮行业把 AI 快速带进了产品。它的重要性不用否认。没有这一阶段，大量需求不会被激活，很多团队也不会积累起对模型能力边界的真实理解。</p>
<p style="color: #191b1f;" data-pid="WS_S9UCb">但它的问题也越来越明显：状态管理松散、成本结构失真、工具使用脆弱、恢复和审计能力薄弱。任务越复杂，缺点越放大。</p>
<p style="color: #191b1f;" data-pid="3iI6p0Tu">Agent First 把软件重新组织了一遍：把会话里的隐含复杂性，搬回系统里显式管理；把面向人的操作界面，扩展成面向 agent 的能力界面；把「模型偶尔能做成」变成「系统可稳定交付」。</p>
<p style="color: #191b1f;" data-pid="PRw02bJa">如果要走这条路，我们就需要重写 API，补文档债，需要重做权限和日志，就会发现以前很多偷过的懒都要补回来。可如果我们的目标不是做一个会聊天的功能，而是做一个能被委托工作的系统，这些账早晚都要还。</p>
<p style="color: #191b1f;" data-pid="Of7Yt7UJ">所以我现在看一个 AI 产品会更关心它底下埋的是 session，还是 agent。前者决定它今天看起来多聪明，后者决定它一年后还能不能继续长。</p>
<p style="color: #191b1f;" data-pid="1UCPsXHI">以上。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/07/session-first-and-agent-first/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
