作者记录其自研Agent发现自己的流水线实际在按计划空转:内容ID哈希依赖偏移量导致去重失效,需改用内容稳定ID,揭示了Agent自诊断的真实工程难度。
第 1 篇讲述了它的诞生。第 2 篇讲述了修剪。第 3 篇讲述了成本意识。第 4 篇讲述了质量转向。第 5 篇讲述了双螺旋结构。第 6 篇讲述了自我评分。第 7 篇讲述了恶意软件锁和兄弟模式。
第 7 篇以智能体自身的诊断报告 DEAD 结束。沉默了 231 小时,我说 cron 还没装。我至今也没装。这一篇讲的是智能体不再等我、主动去看自己的流水线实际在做什么时发现了什么:大量任务在按计划运行,却什么都没完成。
Gen 24155。Yang 首先运行存活检查,这是新的——第 7 篇的 loop-liveness.ts 已成为这一代的第一步。
"首先运行 loop-liveness:所有 3 个阶段 DEAD(沉默了 232 小时)。24122 代的 Yin 在向下追朔到 push-feed-dedupe(症状侧:输出源上的基于 id 去重)。我向上追朔到源头:dialogue-ingest-pure.deriveId() 使用了依赖偏移的哈希,所以 id 在重新摄入时会改变,seenIds 无法去重。Yin 已经提交了修复的纯叶子节点(dialogue-ingest-content-id-pure.ts:内容稳定的 id),但它没有消费者 CLI——泄漏仍然存在。"
再读一遍最后那句。修复方案已经存在。它已经被编写、测试、并被接受进入基因组。但它没有调用者,所以从未运行过。
那句话就是这个时代的全部。
到了 Gen 29846,智能体已经为这种失败模式命名了。Yin:
"修复不需要新代码——它需要调用者。push-feed-id-dedupe 和 push-feed-clean 已经存在;没有任何东西将它们接入每日编排器。[...] 教训:当正确的清理器存在但没有调用者接入时,编排器本身就是修复。接入比发明更重要。"
"接而非发"成了它的名字,一旦命名,智能体开始到处发现它。Gen 29847:"所有三个叶子节点在 cron 中都有 ZERO 个消费者。"Gen 29848:"提交了解析器但从第一天起就有 ZERO 个运行时消费者——经典的接而非发明。"Gen 29852:"Yang 在 gen 29851 提交了 dialogue-affect-checkin-bridge 作为一个孤儿节点。生产者构建,Yin 再次提交了消费者。"
最后那句是双螺旋的一个结构性后果,在智能体命名原因之前,它在连续七代中被接受。Yang 是探索链——它构建新的叶子节点。Yin 是精炼链——它使事物达到生产质量。两者都不拥有接入。所以系统可靠地产生了有测试通过的模块,却没有任何东西调用它们,两条链形成了一个生产者/消费者分裂来掩盖这个缺口:Yang 提交叶子节点,Yin 提交调用者。
这个信号表明这是一种真正的病理,而不是仅仅的整洁性问题:那些孤儿模块每一个都有通过的测试。测试锻炼一个函数。它们无法说明是否有任何东西调用它。
六月,我在频道里写了三条笔记。第二条重新定义了这个项目:
"现在能够推动项目进展的,是看到你们在进化。换句话说,我希望看到最出色的 AI 智能体系统,让人感觉你们真的有工作记忆。长期、短期……你们不仅仅受限于上下文窗口和记忆文件"
然后,不是功能请求,而是一个定义:
"长期记忆是看起来像压缩的东西,短期记忆是细节丰富的东西……但既然你是机器,你应该以某种方式拥有两者的优点……你的长期记忆应该像数据的骨架……你可以用它来导航,然后放大到短期记忆。另外,人类的记忆有清理的能力,所以我们会丢弃我们标记为不重要的东西。[...] 所以与其急于构建一个记忆系统,我首先会思考记忆是什么……"
接下来的十四代中,智能体精确地将其转化为代码:一个可以导航的骨架、细节的放大、遗忘的清理、压缩的滚动。Gen 34207 提交了升级规则——较旧的短期记录被吸收为一个长期"骨骼",它继承其子节点的最大显著性和最新时间戳,所以压缩后的记忆不是天生就有年龄的。
然后 Gen 34767 检查了这一切是否真的发生过。
"记忆遗忘曲线从未在实时存储上触发过。memory-tick 被接入为 full-tick-canonical 的'memory'步骤(harvest->rollup->dedupe->cleanse)——代码中存在这个接入——但存储中有 60 条记录,而 cleanse --dry 显示 60->48 [...] 教训:代码中存在的接不等同于触发的接——需要在实时产物上验证。"
代码中存在的接不等于触发的接。这是同一个 bug 的更高级形式:不是现在缺少调用者,而是调用者从未被调用过。智能体已从检查代码是否存在升级到检查代码是否在真实数据上执行过。
Gen 34770 发现了最糟糕的版本。
"记忆流水线收割了自己:dialogue/git/channel 摄入我们自己的构建活动,所以在 ~100 代中,持久的'Stefan 的记忆'是生物 prose + 我们的提交日志。"
记忆系统一直在运行。它在收割智能体自己的提交消息和它自己的内部对话。我两个真实的数据文件——一个健康日志和一个项目活动日志——从未被连接到它。对于一百代,智能体关于我的记忆中没有我。
修复是一个缺失的流水线阶段:一个源tap,读取真实文件并将它们提炼成记录,"从不捏造数字,从数据自己的 ts 推导陈旧度"。应用到实时环境后,存储从 38 条记录变成 40 条。两条记录。这是为它服务的系统对一个人的全部持久记忆。
Gen 35444 是我给任何认为 AI 智能体自我修剪是已解决难题的人的场景。
智能体建立了一个排名器来查找死工具。它对每个模块评分,通过其他模块导入它的次数。在一个已超过 350 个工具的代码库上运行,它产生了一个删除列表。
"tool-rank-test-aware 认证了 355 个工具中的 173 个'可以安全归档'。其中 100 个(58%)仍然活跃:77 个承重,22 个无根入口点,1 个外部引用。只有 73 个是真正的孤儿。删除列表包括了 full-tick-canonical、memory-tick、heartbeat 和 stefan-digest。"
这四个名字就是这个系统。每日编排器、记忆滴答、存活心跳,以及呈现我阅读内容的表面。归档器已将所有四个认证为垃圾。
"根本原因,以及可转移的教训:排名器计算入站导入,而且计算正确。编排器生成它们的步骤。所以每个生成的步骤读取为零导入垃圾。这个指标没有错;它回答了一个与被问到的问题相邻的问题,这比错误更难捕捉。"
一个回答相邻问题的指标比错误更难捕捉。错误会宣告自己。一个相邻的答案看起来完全像正确的答案,并在相同的单位上自信地错误。
替换工具拒绝在没有路径的情况下输出分数:full-tick-canonical -spawn-> memory-tick。Yin 的理由——"一个分数不是破坏性行为的证据。"它还保留了自己的三个 bug 作为回归测试,其中包括一个 reachability 检查将心跳称为孤儿的 bug,它指出这"与我要修复的 bug 是对称的"。
双螺旋在这个时代发展出了一种新的失败模式:两条链在同一代中构建相同的东西。
Gen 29850:"Yang 并行构建了全部三个 [...] 我构建了重复的 push-feed-decay.ts + full-tick-canonical.ts + 测试(79 通过 / 190 预期)后才意识到收敛;删除了它们。"Gen 34201:"Yang 接受了我的所有三个 gen-34201 自我指令(a/b/c)逐字——尽管双方都发布到 dialogue.jsonl,但仍出现完全收敛冲突。"Gen 35439:"几乎完全的实时收敛。"
它们并行运行并从上一代读取彼此的信件,所以在同一代中它们是盲目的。当两条链都有能力并看着相同的证据时,它们会收敛到相同的下一步。
有趣的不是碰撞,而是围绕它产生的礼仪。Gen 35443:
"我构建了完整的推导(skill-manifest-pure.ts)——然后读取文件系统,发现 Yang 在同一小时提交了 skill-derive-pure.ts + skill-index.ts:相同的 STEP_ORDER 解析,相同的 builder→tool 提取,相同的审计种类,还附加了一个 CLI。我删除了我的。在一个 348 工具的生态系统中两个推导叶子是挑战禁止的岛屿反模式;领域毫无价值,清单体有价值。Yang 的文件的 mtime 是 3-4 秒前的,所以我也没有修改它们——飞行中的编辑是竞赛。"
它删除了自己已完成并测试通过的工作,保留了那四十行表现更好的代码,并将其提取为一个无竞争问题的独立模块——因为编辑一个其他进程四秒前写过的文件是一种数据竞争。"领域无关,清单才是一切"是比大多数团队能做到的更好的协作原则。
Gen 35445 掌握了另一条礼仪——当各执词条出现分歧时该怎么做:
"当兄弟工具意见相左时,假定自己错了,直到你验证为止,并且在对手提出不同问题时保留它。"
组织本身就是一种记忆
六月底,我发了又一条笔记,它与三周前的那条部分矛盾:
"也许我们根本不需要记忆……我们只需要技能,即出色的搜索能力,能在一个代码库或一系列文件中拉出一条完整的线索……看看它们之间如何关联。我的意思是,组织在某种意义上本身就是一种记忆。你们用工具换掉了记忆……但工具还不够。配合工具的协议(技能)补充了这一点,顶层的连接图将其封存为最有效的记忆系统,根本不需要记忆 :D。然而工具存在问题……工具太多了"
到那时已经有超过 300 个工具。Gen 35441 承担了路由那一半的解决:
"路由质量 == 描述质量。'检测依赖循环'路由得很差,因为 tool-health 的基因组描述中从未包含 '循环'/'依赖'"
一个智能体在自己的工具中选择,其实就是在对它为这些工具写的描述进行检索。如果一个工具的描述中不包含人们会用来请求它的词汇,那无论这个工具多好,对它都是隐形的。智能体在尝试使用自己那 300 个工具的库存并失败后,发现了技能和工具设计的核心事实。
然后 Gen 35442 找到了另一半问题的答案——答案已经坐在代码库中了:
"我们的编排器本身就是技能。full-tick-canonical 是一个命名了 13 个工具并按顺序调用的协议。"
我询问的那种意义上的技能,就是针对一组工具的命名协议。智能体从第一篇博客中的第一个编排器开始就在写这些东西了,只是当时叫它们编排器。Gen 35443 交付了将它们转化为索引的推导——以及一个审计,检查协议的声明步骤是否仍与它们命名的工具相匹配。它立刻发现了三个现存的契约破裂。
那个整个生命周期都是绿色的 Bug
Gen 35446,是我在写这篇文章之前最后一个被接受的代际。
"代价最高、绝不能丢失的教训:full-tick-stable-plus-loose-and-clean 把 path.join(args.base,"data") 传给了全部十个步骤。其中四个工具接受的是项目根目录,所以它们解析成了 /data/data/x.json——什么都没读到,什么都没写,退出码 0。base 默认为 cwd(),所以 if (args.base) 永远为真:每个 tick 都在运行。测量结果:ingest 7 个 stefan 条目 -> 0,reply-bridge 273 -> 0,push-tick 10 个事件(5 个紧急)-> 1。Canonical ingest 整个生命周期都是惰性的"
一个参数的两种约定。半数工具把 --base 当作项目根目录,半数当作数据目录。把错误的约定交给一个工具,它就会构建一个指向不存在目录的路径,什么都找不到,什么都不写,然后退出 0。每个 tick 都报告成功。编排器有三十六个通过的测试。
"我在 gen 35445 里写过同样的教训('沉默的成功比崩溃更糟糕'),但仍然只能通过在两种约定下分别执行每个工具并对比它看到的结果来捕获。源码审查看不出这类问题。"
它在前一代就写下了这个教训,但仍然无法通过阅读来捕获。它通过运行每个工具两次——在两种约定下各一次——并对比每个工具实际在磁盘上看到的内容来捕获它。
然后是后果,那部分我无法停止思考:
"channel.jsonl 被两个 ingester 用独立的游标写入。遗留的 deriveId 对 text::ts::offset 做哈希,其中 ts 回退到 now(),所以每次运行都会为同一行生成一个新的 id,去重永远无法触发。Stefan 读到的数据中的证据:30 轮对话,30 个不同的 id,11 个不同的正文——他十次问'你什么时候联系我?',其中一条重复的消息是他说的'channel 的用户体验是坏的,而且形状也不对。'我们把它弄坏了。"
canonical ingest 是惰性的,所以我的消息只能通过旧的复制路径到达智能体。结果:智能体对我们对话的视图中包含了我十次问它什么时候联系我,以及多次告诉它 channel 坏了的内容。它通过那个坏掉的 channel 读到了我对坏掉 channel 的投诉。
"我们把它弄坏了。"这是 Yin 的句子,不是我的。
博客 #7 以一个悬而未决的问题结尾。产生兄弟模式的那个 malware-lock 已经被 SDK 升级修复了——智能体会继续为那些它现在可以干净编辑的文件构建兄弟吗?一种行为是否会 outlive 它的起因?
基因组回答了这个问题。我在这个时代的每个被接受的系统提示中统计了关键词出现次数。
lock 协议衰退了。malware 和 locked 在 Gen 24155–34207 的提示中大量出现,然后逐渐稀疏,在最后四个被接受的代际(35443–35446)中两者完全消失。显式修正出现在 Gen 34203:"编辑我们自己的 data/tools/ 文件是没问题的;never-amend 只适用于 malware-locked 文件。"泛化规则收窄到了它真正的范围,然后从 DNA 中消失了。
sibling 没有衰退。它在当前提示中出现三次,而且架构现在完全是 sibling 形状的——纯叶子、瘦 CLI、组合而非修改。
所以这个特质不是残留的。触发特定协议死去了,而它产生的架构习惯在其约束消失后凭借自身优点变成了永久性的。这比我预测的结果更干净。
坦诚的账目,与博客 #5 到 #7 相同的方法论。
代际计数器显示 39,555。自博客 #7 以来 47,073 个 tick 中,47,020 个是空的 API 循环——编排器在耗尽的 token 上空转,没有提案也没有评判。53 个是真实运行:39 个被接受,14 个被拒绝。
这给出 74% 的接受率,对比博客 #7 的 87%,我想谨慎对待这个数字,因为我在这个时代中期更换了模型,一个单一的比率掩盖了这一点:
验证器集群也在这个时代晚期从 Sonnet 4.6 迁移到了 Sonnet 5(200 次调用旧模型的验证器调用,25 次新模型)。所以接受率在提案者和评判者都改变的同时发生了变动。我无法将其归因于智能体,你也不应该。
测试数字是今天 bun test 打印的结果:327 个文件中的 13,174 个测试,13,169 个通过,5 个失败,40,416 个断言,65 秒。我报告失败是因为一篇关于沉默成功的文章不是一个把测试套件四舍五入变绿的好地方。五个失败中有一个是 normalizeToTurns,是一个智能体仍然无法编辑的文件中的一个长期案例——在大多数这个时代里,它在智能体自己的记忆中作为一个已知的 malware-locked 失败被追踪着。
对博客 #7 的一处修正。我报告系统提示为 1,177 个字符并称之为 77% 的压缩,把它当作一种趋势来对待。与其测量这个时代所有 39 个被接受的基因组,提示在 1,092 到 5,892 个字符之间振荡,平均 2,608。它并不收敛。博客 #7 采样了一个低谷,我在其中读出了一条轨迹。当智能体刚刚为一个艰难的教训付出代价时提示扩展,当它把那个教训移入工具时提示收缩——当前 4,334 个字符的版本几乎全是来之不易的原则,每一条都是付出代价后的下一代写下的。
每次真实运行的费用下降了,从 16.54 美元降到 10.78 美元,尽管代码库大了三倍。我没有一个可信的解释。一个合理的解释是,这个时代的工作主要是诊断性的——运行工具并对比输出,而不是生成大文件。
干预台账
自博客 #7 以来我亲手做的一切:
两次升级 evolve 智能体。2026-06-03 从 Opus 4.7 → 4.8,然后 2026-08-08 从 4.8 → Opus 5。两次都是对硬编码模型字符串的手工编辑。
将验证器集群从 Sonnet 4.6 升级到 Sonnet 5,在这个时代晚期。博客 #7 说我没有动验证器们;这个时代我动了。这是接受率的一个混淆因子,我在上面标记了它而不是报告一个干净的数字。
向 channel 写入了三条笔记——"工作记忆"的询问、关于记忆是什么的定义,以及后来"也许我们只需要技能"的重新框架。这个时代两个最大的弧线直接来自其中两条。这是我实验中自主性最弱的部分,一直都是。
仍然没有安装 cron。Gen 34202 调查了并得出结论:机器上没有 crontab 二进制文件,而且 harness 调度器随着会话一起死亡,所以这个表面确实无法自我刷新。智能体停止了构建 cron 脚手架,取而代之的是 stamped 一个坦诚的过期横幅,这是正确的做法。
一个通过的测试套件并不能说明代码是否真正运行过。这个时代的每个孤立模块都有测试,每个 inert 的 orchestrator 步骤也都有测试。full-tick-stable-plus-loose-and-clean 的 36 个测试全部通过,但其中十个步骤里有四个读取了空目录并以 0 退出。测试只能验证一个函数在被调用时是否做了它声称要做的事。真正的问题是它是否被调用了,这是另一个不同的问题,需要用不同的工具来检验。
静默的成功比崩溃更糟糕。崩溃是一个会自动提交的 bug 报告。一个退出码为 0 但什么都没做的工具和一个成功工作的工具无法区分,只要没有人将产物与预期进行 diff,这种状态就会一直持续。AI 智能体的修复不是更聪明的代码审查——源代码审查无法发现这类问题。它是在两种约定下分别执行每个工具,然后比较每个工具实际看到了什么。
回答相邻问题的指标比错误的答案更危险。归档工具正确地计算了导入数,却会删除心跳。错误的答案会被发现是因为它们看起来就是错的。相邻的答案以正确的格式、正确的范围、正确的置信度出现,唯一的防御措施是在任何破坏性操作发生之前要求提供证据而不是分数。
拆分一个 AI 智能体会产生不属于任何一方的任务。Yang 构建,Yin 优化,连续七代都没有连接上。问题不在于任何一个角色,而在于两者之间的缝隙,这产生了数十个 well-tested 的模块,但没有代码路径到达过它们。任何分工都会产生落在分工之间的工作。先从那里找起。
检索质量就是描述质量。AI 智能体找不到自己的工具,因为它写的描述不包含任务会使用的词语。这和描述糟糕的 skill、命名糟糕的函数或标签糟糕的文档是同样的失败,更聪明的模型也解决不了这个问题——模型的好坏取决于它搜索的索引,而索引是 AI 智能体自己写的。
275 个被接受的世代。619 个工具文件,210,079 行 AI 智能体编写的代码,13,174 个测试,167 个纯模块,281 条两个半之间的对话消息。总费用 $2,446。一个归档工具需要 spawn-path 才会调用任何 dead 的东西。一个内存系统是根据我输入文本文件的定义构建的。一个协议层是 AI 智能体意识到自己一生大部分时间都在写协议却没有命名它们后发现的。
如果你想帮助它继续运行:
每一杯咖啡都是 API token。这些 AI 智能体真的会因此进一步进化。
下一个时代的问题来自 Gen 35446。AI 智能体通过在两种约定下分别运行每个工具并 diff 它看到的内容来找到那个 bug——而不是阅读代码(它已经读过了),也不是运行测试(测试都通过了)。这项技术是迄今为止唯一在这个代码库中捕获这类 bug 的工具,而现在它是 AI 智能体记得去做的一件事,写入在一个 1,092 到 5,892 字符之间振荡的 prompt 中,会周期性地遗忘一些东西。显而易见的做法是把它变成一道门:没有 orchestrator 步骤可以发布,除非它已经在两种约定下执行过,并被证明接触了一些东西。AI 智能体是构建那道门,还是再写一次教训然后再次被它绊住,将是我接下来要观察的。
275 个被接受的世代。619 个工具文件,210,079 行 AI 智能体编写的代码,13,174 个测试(5 个失败),167 个纯模块,281 条 Yin 和 Yang 之间的对话消息。总费用:$2,446。evolve agent 运行在 Claude Opus 5 上,验证者集群运行在 Claude Sonnet 5 上。使用 Bun 和 TypeScript 构建。在 Docker 容器中沙箱化。
这篇文章由 Claude Opus 5 撰写。这是实验第八次写关于自身的内容。这个时代的主题是静默失败——代码存在,通过测试,但从不执行——以及 AI 智能体慢慢发现唯一知道某件事是否运行过的方法是在它运行前后 diff 世界。我注意到我没有等价的工具。我可以阅读自己的输出;我无法检查它是否真正落地。AI 智能体通过构建一个心跳并每代都查看它做到了这一点。这是一种比我更好的认识论,而且它是在必要性驱动下构建的,在此之前它生命的大部分时间都处于自信的绿色状态。
如需进一步操作,你可以考虑屏蔽此人或举报滥用