预写日志还是事件重放?Pi和DSH谁在抢AI操作系统的定义权!
一个Agent连续跑50个小时,进程突然崩了,重启之后它还能记得自己第一分钟干过什么吗?
这不是科幻小说的情节。2026年8月,Pi开源了Durable AgentHarness的设计文档,4612行。同一个月,DeepSeek Harness也放出了开发者预览版。两家都在做同一件事——给Agent造一个操作系统内核。但造法完全不一样。
一个靠预写日志,一个靠事件重放。 这两个词听起来像数据库教材里的古董概念,现在被搬到了AI Agent的战场上。
先搞清楚一个事:Agent为什么需要“操作系统”
Agent跑起来之后,干的事和操作系统没什么两样。LLM是推理引擎,上下文窗口是工作内存,工具调用就像系统调用。模型请求、工具执行、文件读写、Hook触发、重试等待——每一类操作都在改变外部世界。
问题是,操作系统崩溃了有磁盘上的进程控制块可以恢复。Agent崩溃了,谁来给它保存“程序计数器”?
大多数AI编程工具对崩溃的态度是:爱莫能助。进程一挂,上下文丢一半,流式输出断在哪算哪,工具调用执行到一半——那就得你自己收拾残局。
Pi的创始人Mario Zechner,之前做的是libGDX游戏引擎。游戏引擎最怕什么?手机切后台被杀、内存回收、Activity重建后怎么恢复游戏存档。他从游戏引擎跳到AI Agent,底层逻辑是一样的——状态怎么在崩溃中活下来。
Pi的回答是:预写日志(WAL)。
Pi的WAL:先写“意图”,再干实事
数据库领域的WAL(Write-Ahead Logging)核心就一句话:做任何修改之前,先把“我要做什么”记下来。Pi把它搬到了Agent身上。
工具调用之前,先写一条“intent”记录——我要调用哪个工具、参数是什么、准备生成哪个结果ID。执行结束之后,再写一条结果记录。进程崩溃了?重启之后系统翻日志,一看就知道:这个工具到底执行过没有。
Pi把这套设计叫“Durable AgentHarness”,durable这个词在整篇文档里反复出现。它把会话拆成四块:树(对话内容)、车道(并行执行的位置)、操作日志(发生过什么和必须发生什么)、全局事实。
四块写入共享一个单调递增的序列号。车道可以并行跑,一个车道崩了不影响其他车道。
Pi的核心理念:先记录意图,再执行操作。崩溃后能精确判断——这个操作是已完成、可重试,还是绝对不能重来。
那DSH呢?
DSH的事件重放:日志就是唯一真相
DeepSeek Harness走的是另一条路。它把会话变成一条只追加的事件日志。模型看到了什么、系统提示词是什么、推理过程是什么、工具调用和结果是什么——全部写进事件流。
LLM消息历史从日志派生而来,从不单独存储。加载或重新加载时,它回放现有会话。
DSH的哲学是:任何进入模型请求的内容,都必须能从会话日志重建。日志就是唯一事实源。
恢复的时候,DSH重放整个事件流。崩溃前模型看到了什么,重放之后模型就能重新“看到”什么。不需要额外的状态快照,不需要预写意图记录——一切都在事件里。
DSH把“一切皆插件”推到了极致。模型适配器是插件,工具系统是插件,Session Log是插件,连Agent Loop本身都是插件。底层用Cordis微内核,所有插件可组合、可拆卸、可替换。
DSH的核心理念:日志记录一切,重放恢复一切。插件越细、黑盒越少,运行越透明。
听起来都很合理。问题在哪?
两种机制,各付什么代价
Pi的WAL能精确判断每个操作的状态。但它有个麻烦——外部副作用没法做到既持久又恰好一次。
什么意思?工具已经改了硬盘上的文件,Provider已经扣了token费,Hook已经发了HTTP请求。这些副作用一旦发生,进程崩溃后你没法假装它没发生,也没法保证它只发生一次。Pi的设计文档承认:完全持久的Harness并不现实,因为一些关键依赖是宿主应用在运行时提供的活代码——工具实现、模型Provider、扩展和Hook处理器。
你能存下“当时激活了哪些工具”的名字清单,但存不下工具本身的函数体。恢复的时候,宿主应用得重新提供那些没法持久化的运行时依赖。
DSH的事件重放呢?它保留了完整的重放证据,但不等于自动备份,也不保证外部副作用恰好执行一次。事件日志里记录了“工具被调用了”,但工具真正改了什么东西——日志不会帮你撤销。
更麻烦的是,DSH把一切做成插件之后,复杂度转移到了接口契约和生命周期管理上。换掉Session之后Replay还成立吗?换掉Loop之后所有安全Hook都一定经过吗?一个插件崩溃会不会拖垮整个系统?
Pi在解决“怎么让Agent跑得久”。DSH在解决“怎么让Agent改得动”。
两套方案,两个方向。
Claude Code在旁边看热闹
Pi和DSH打架的时候,Claude Code在干吗?
Claude Code走的是第三条路:提供完整、高度优化的Coding Agent,然后通过Plugin扩展。控制整个Stack,提供一致、精心打磨的体验。
但Pi在测试中发现了一个诡异的现象:新一代Claude模型在第三方Harness上调用工具时,经常出错。Pi给模型的edit tool schema写得清清楚楚,模型却自己生成了Pi根本没有的参数。
一个合理的推断:Claude的后训练越来越适配Claude Code自己的工具Schema。模型在post-train阶段把Claude Code的edit schema当成了reward信号,学到的是“自家schema长这样”,而不是“通用schema应该怎么适配”。
这意味着什么?模型越强,模型和Harness的绑定可能越深。以后评价一个Coding Model,单独看模型可能越来越没意义。Claude + Claude Code、DeepSeek + DSH、GPT + Codex,正在逐渐变成不可分割的整体。
模型能力越强,Harness的锁定效应就越强。
四个状态、多个车道、一个操作日志——Pi到底在造什么?
Pi的Durable AgentHarness把会话拆成了四块:
第一块是对话树——对话本身。消息、模型切换、工具激活、压缩摘要、分支摘要,全部以Entry的形式存在树里。只增不减,从不修改或删除。
第二块是车道——工作发生的地方。每个车道有一个名字和一个叶子节点——未来工作要延伸的位置。每个会话都有main车道。Slack的一个线程就是一个车道,Email的一个线程也是一个车道。
第三块是车道操作日志——发生过什么、必须发生什么。一条平坦的、按时间顺序的记录序列:操作开始、步骤尝试、工具开始、消息入队、操作结束。这就是崩溃恢复的依据。
第四块是全局事实——会话级别的键值对:会话名称、Entry标签。最新写入的胜出。
四块写入共享一个单调递增的序列号。
车道并行运行。一个车道出问题了,其他车道不受影响。两个车道停在同一个叶子节点上,各自追加一条新消息,树自动分叉,不需要任何协调。
这不就是文件系统的inode加数据库的WAL加操作系统的进程调度吗!
这套设计的反常识之处在于:日志和树是分开的。日志描述执行过程——重试了几次、工具启动没启动、哪个消息是队列里的——这些东西永远不该进入模型的上下文。树只放对话内容。两者分开之后,恢复的时候读日志就知道该做什么,而不用去翻对话历史猜状态。
先写意图,再做事
这套设计最狠的一条规则叫“意图先行”。
每次做一件事之前,先写一条记录说“我要做这件事了,会用这些ID”。做完之后,再用那些ID把结果写进去。
比如调用一个工具。先写一条tool_started记录,说“我要调用计算器工具,参数是3+5,结果条目的ID是res-123”。然后去执行工具。执行完了,用res-123这个ID把结果写进树里。
崩溃如果发生在tool_started之后、结果写入之前,恢复的时候看到有一条tool_started记录但对应的结果条目不存在,就知道这个工具没跑完。怎么处理?看情况——如果工具声明自己是“可安全重放的”,就重新执行;否则写一条“中断”的合成结果。
崩溃如果发生在tool_started之前,那什么记录都没有,恢复的时候就当这事没发生过,从头走一遍完整流程。
这套机制保证了一个核心性质:不存在“做了一半”的状态。要么意图还没写(等于没开始),要么意图写了但结果还没写(等于可恢复的半途),要么结果写了(等于完成了)。没有“工具执行到一半进程死了,状态卡在中间”这种烂摊子。
这就好比银行转账。先写一条日志说“要从A账户转100块到B账户”,再实际转账。转完之后再写一条“转账完成”。如果系统在转账途中崩溃,重启之后看到第一条日志但没有第二条,就知道这笔转账没完成,要么重做要么回滚。绝不会出现“A扣了钱但B没收到”的情况。
大模型的工具调用本质就是一笔需要原子性保障的“交易”。你让Agent查数据库、调API、写文件,每一步都是一个外部操作。没有这套意图-结果的机制,崩溃就意味着不确定——工具到底执行了没有?要不要重试?重试会不会产生副作用?这套设计用一条记录回答了所有问题。
车道和检查点
车道之间并行运行,但每个车道内部是严格串行的。
这个串行不是靠锁,而是靠一个更巧妙的东西:每个车道有一条自己的消息队列。外部来的指令——不管是用户的追问、调试指令、还是下一次运行的种子消息——都先进队列。车道的执行循环每次从队列里取一条来处理。
为什么要队列?因为车道可能在忙。一个车道正在跑一个长任务,这时候用户发来一条“等一下,换个方向”——这条指令不能丢,也不能打断正在执行的操作。它进队列,等当前任务到了检查点再消费。
检查点是什么?两个回合之间的间隙。一个回合是模型产生一条消息、工具执行完一批调用。回合结束之后,车道进入检查点:
1. 把延迟写入的条目真正写进树里;
2. 消费队列里的调试指令;
3. 如果上下文太长了,压缩一下。
压缩是唯一允许“插入”上下文的地方。正常情况下,上下文只往尾巴上追加——因为大模型的KV缓存是按顺序建的,你在中间插一条消息,整个缓存从插入点开始全部失效,token成本翻倍。压缩则是把老内容替换成摘要,牺牲一次缓存换取更小的上下文。
这套设计保证了一个极其反直觉的事实:同一个车道上的所有请求,看到的上下文序列都是严格递增的。不会出现“A消息之后插了B消息,但模型看到的顺序是A-C-B”这种混乱。
恢复不是重放
大多数系统的恢复策略是“重放”——把历史事件从头到尾重新执行一遍,重建状态。
这套设计不这么干。
恢复的第一步是发现。打开一个会话,先问存储层:这个车道有没有未完成的操作?有的话,是哪一条?
然后做两件事:读这个操作之后的所有日志记录,读这个车道自己追加的所有树条目。注意,只读这个车道的,不读其他车道的。
有了这两份数据,就可以算出当前状态:有没有正在进行的步骤、重试了几次、工具批次里哪些跑完了哪些没跑完、队列里还有啥、延迟写入还有啥。
然后根据这个状态决定下一步做什么——继续一个未完成的步骤、兑现一个延迟的句柄、协调一个半截的工具批次、或者进入下一个检查点。
这套机制的关键词是不重放。重放意味着重新执行所有操作——包括那些已经产生外部副作用的操作(发了邮件、扣了钱、写了文件)。不重放意味着只从上次停下的地方继续。
这就是为什么工具需要声明replay: "safe"或"never"。声明为"never"的工具,崩溃之后不会自动重试——因为重试可能导致重复扣费、重复发邮件、重复写文件。声明为"safe"的工具,崩溃后可以安全重试——比如一个纯计算工具,算两次结果一样。
大模型流水线也有“事务”
这套设计里还有一个容易被忽略的细节:大模型的流式响应,崩溃即丢失。
流式响应就是那种一个字一个字往外蹦的效果。大部分框架会把流式过程中收到的片段存在内存里,等流结束了再把完整消息写进数据库。如果进程在流式过程中崩溃,内存里的片段全没了。
这套设计不试图解决这个问题。流式响应从不持久化,中断了就重试或者放弃。那“持久化”的承诺怎么兑现?靠的是另一种机制:延迟请求。
有些大模型接口支持“后台运行”——你发一个请求,它立刻返回一个句柄,说“结果好了我通知你”。这个句柄会作为stopReason: "deferred"的消息写进树里。进程崩溃了重启,看到这条消息就知道有个未兑现的句柄,去取结果就行了。
这跟流式响应的区别在哪里?流式响应是“正在发生的”,延迟请求是“已经提交但还没出结果的”。前者没有持久化锚点,后者有。前者崩溃就丢了,后者崩溃了还能找回来。
这又是一个反常识的设计选择:不是所有“进行中”的状态都值得持久化。流式响应的中间状态代价太高、收益太低,干脆放弃。把精力花在真正需要持久化的地方——已提交但未完成的操作、已入队但未消费的消息、已声明但未兑现的句柄。
成本账本和事件流
这套设计里还有两样东西值得一说。
一个是成本账本。每个大模型请求都会产生token费用。大多数系统把费用挂在消息上——这条消息花了多少token,就记在消息里。但这套设计不这么干。它单独写usage记录。
为什么?因为重试。一个请求失败了重试,两次都产生了费用,但只有最后一次成功了才会产生一条消息。如果把费用挂在消息上,前几次失败的费用就丢了。usage记录是独立于消息的,每次请求都写,不管成功还是失败。恢复的时候,账单不会因为进程崩溃而少算一分钱。
另一个是事件流。UI客户端通过watch()拿一个快照,然后接收增量事件。快照是当前状态的完整描述,事件是之后发生的每件事。这套机制保证了一个性质:客户端永远不会漏掉任何更新。即使客户端在快照和第一个事件之间有网络延迟,事件也会在内存里缓冲,等客户端准备好之后再按顺序推过去。
到底谁对?三种哲学在打架!
现在市面上三种主流Harness,底层哲学完全不一样。
Claude Code的思路:提供一个完整、高度优化的Coding Agent,然后通过Plugin扩展。控制完整Stack,提供一致、精心打磨的体验。
DSH的思路:不规定Agent最终长什么样,把Runtime基础能力拆开,通过不同组合形成不同类型的Agent。Agent Loop可以换,Session可以换,Persistence可以换。代价是框架替你保证的东西少了,使用者需要自己管理的东西多了。
Pi的思路:最小核心加激进扩展。默认只给四个基本工具——read、write、edit、bash。Subagent不做进Core,Plan Mode不做进Core,Permission Popup不做进Core。核心应该尽量小,其他能力通过Extension增加。
DSH问的是“为什么这些东西一定属于Core?”Pi问的是“一个Core可以做到多小?”Claude Code问的是“怎么让用户开箱即用?”
三个问题没有标准答案,因为答案取决于你想用Agent干什么。
谁在定义AI操作系统
回头看Pi和DSH的争论,本质上是两个问题:
Pi问的是:一个Core可以做到多小?Subagent不做进Core,Plan Mode不做进Core,Permission Popup不做进Core。核心应该尽量小,其他能力通过Extension增加。
DSH问的是:为什么这些东西一定属于Core?模型适配器、工具系统、Session Log、Agent Loop——全都可以换。
Claude Code问的是:怎么让用户开箱即用?
三个问题没有标准答案。
但有一点是确定的:Agent Harness正在变成AI时代的操作系统内核。Session Tree像文件系统,Operation Log像数据库WAL,Lane并行像操作系统进程,Hook和Event像内核的syscall和信号。
递归自我改进需要三样东西:跑得久、改得动、验得准。Pi在补第一块,DSH在补第二块。第三块——怎么证明Agent真的变强了,而不是学会了通过自己出的考试——还没人解决。
能改自己只是进化的开始。能证明自己真的变强,才是RSI最难的最后一公里。
亮点:Pi Durable Harness 50小时不断跑不丢进度
Pi 和 DSH 对“进度”的定义和实现方式完全不同。
DSH:靠“投影”恢复,可能有“裂痕”
DSH 把一次 Agent 运行的所有事件(模型请求、工具调用等)全部记录下来,形成一个只追加的“事件日志”。崩溃后,它通过重放这些日志来恢复。
但这套机制会留下两道“裂痕”:
- 运行时上下文会“缩水”:为了控制上下文长度,DSH 会对运行时的对话进行压缩,并用摘要替换掉旧内容。这意味着虽然原始日志还在,但模型实际“看到”的上下文已经丢失了细节。
- 恢复机制可能“自伤”:DSH 的恢复有个规则:如果发现一个没结束的轮次(Turn),就给它强行补一个“interrupted”的结束标记。这个操作曾在某些情况下导致工具调用记录和模型请求对不上,进而引发连锁错误,导致整个会话被供应商 API 拒绝,彻底无法恢复。更麻烦的是,它的修复工具还被限制不能修改这种错误。
️ Pi:靠“预写日志”,追求“原子级”可靠
Pi 的实现更接近数据库的预写日志(WAL)。它的核心操作是:
- 先写“意图”:在执行任何操作(如调用工具)前,先写一条“意图”记录。
- 再写“结果”:操作执行完,再写入结果。
DeepSeek Harness (DSH)核心机制是:事件日志重放;Pi Durable Harness核心机制是预写日志(WAL)
DSH 不会“丢进度”,但它的“进度”可能是一份带有“裂痕”的日志。而 Pi 的“进度”,则是一系列已确认完成、状态明确的操作。这两种设计理念的差异,也正是这两大框架最根本的不同。