预写日志还是事件重放?Pi和DSH谁在抢AI操作系统定义权


预写日志还是事件重放?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 运行的所有事件(模型请求、工具调用等)全部记录下来,形成一个只追加的“事件日志”。崩溃后,它通过重放这些日志来恢复。
但这套机制会留下两道“裂痕”:

  1. 运行时上下文会“缩水”:为了控制上下文长度,DSH 会对运行时的对话进行压缩,并用摘要替换掉旧内容。这意味着虽然原始日志还在,但模型实际“看到”的上下文已经丢失了细节。
  2. 恢复机制可能“自伤”:DSH 的恢复有个规则:如果发现一个没结束的轮次(Turn),就给它强行补一个“interrupted”的结束标记。这个操作曾在某些情况下导致工具调用记录和模型请求对不上,进而引发连锁错误,导致整个会话被供应商 API 拒绝,彻底无法恢复。更麻烦的是,它的修复工具还被限制不能修改这种错误。

️ Pi:靠“预写日志”,追求“原子级”可靠
Pi 的实现更接近数据库的预写日志(WAL)。它的核心操作是:

  1. 先写“意图”:在执行任何操作(如调用工具)前,先写一条“意图”记录。
  2. 再写“结果”:操作执行完,再写入结果。
这套机制保证了每个操作要么完全没发生,要么完整结束,不存在中间状态。重启后,Pi 能精确判断每个任务是已完成、可重试,还是因有副作用而绝对不能重来。


DeepSeek Harness (DSH)核心机制是:事件日志重放;Pi Durable Harness核心机制是预写日志(WAL)

DSH 不会“丢进度”,但它的“进度”可能是一份带有“裂痕”的日志。而 Pi 的“进度”,则是一系列已确认完成、状态明确的操作。这两种设计理念的差异,也正是这两大框架最根本的不同。