这篇拆解 DeepSeek Harness 的 Session Log:把会话做成一条只追加的事件流,为什么能同时解决"记忆丢失"和"多视图不一致"这两个问题。
0. 一个很多人踩过的坑
你有没有遇到过这种情况:跟 AI 聊得好好的,它突然"失忆"------上一句你刚说的话,下一句它就不认了。
第一反应通常是:这模型不行,记性太差。
但问题大概率不在模型。先说结论:大多数 Agent 框架里的模型,根本没有"记忆"这件事。 它每轮能看到的上下文,都是临时从一条事件日志里投影出来的。那段一旦被压缩、替换,它就再也看不见了------它表现出的"失忆",只是因为那一段已经不在它看得见的上下文里。
下面拆开看:为什么大多数框架会丢记忆,Harness 又是怎么解决的。
1. 大多数框架的记忆是怎么丢的
很多框架图省事:把整个会话塞进一个会溢出的数组,当作模型的上下文。
这在短对话里没问题。可一旦任务跑得久------比如一个多轮、带工具调用的长篇任务------前面的对话就会因为数组满了被静默挤掉。没有报错,没有提示,旧内容就这么没了。
模型不是"忘了"。它本来就看的是那份上下文;那段被挤掉之后,它对发生过什么一无所知。你问它"刚才那步的结果呢",它只能含糊其辞,因为它手里真的没有。
根子上的设计选择是:上下文是唯一的事实载体,而上下文又是会变、会丢的。

2. Harness 的做法:真相只在一条日志里
DeepSeek Harness 换了个思路。它不把真相装进上下文,而是写进一条只追加(append-only)的事件流------Session Log。
几个关键点:
- 会话是一串事件。每条事件有类型、序号和负载;turn、step、用户消息、工具调用、工具结果,全按发生顺序记下来。
- 模型看到的是"投影",不是"存储" 。系统跑一次
deriveMessages(),按规则把日志映射成模型这轮能读的消息列表。模型不持有任何记忆,它每轮只是拿到一份刚从日志投影出来的上下文。 - 一条硬性不变量 :
model-visible means logged------模型能看到的,必然已经写进日志。想给模型加一种新的可见输入?正确的做法是先加一种新事件类型,再从日志里渲染出来。而不是在某个角落偷偷塞一段。
这就引出了"单一事实源"的含义:
真相只存一份,就躺在日志里。模型要看、界面要显示、调试要回放------谁需要,都从这份日志投影出来,而不是各处各存一份。
为什么强调"不另存副本"?因为如果真相在日志里只留一份,那么模型视图、UI 视图、调试回放、审计记录,全是从同一份投影出来的,不存在"两份对不上"的中间态------要么日志有,要么没有,没有模棱两可。

3. surface 与 log-only:什么算对话,什么只是记录
并不是所有事件都该进模型的上下文。Harness 把事件分成两类:
- surface 类:用户消息、助手消息、工具结果------这些会进模型历史,是真正"对话"的一部分。
- log-only 类:只进持久化、UI 或审计,不进模型历史。
这条线划清了一件事:什么算对话,什么只是记录。 把日志当成单一事实源,不等于把所有日志都喂给模型;投影规则决定哪些事件该露出来、哪些只留在底层。

4. 因为一切都是投影,fork 变得不可思议地简单
这是整件事里最反直觉、也最有用的一点。
既然模型上下文、回放、持久化全都是从同一条事件流投影出来的,那么派生出一个新会话,就只是从这条事件流里选个边界、重建出一份新的投影------不需要复制状态,不需要迁移数据库。
由此派生出来的一整套能力都变得轻量:
- 回放(replay):重新投影同一段事件;
- resume(续跑):从某个边界继续;
- 转写、遥测:同一种数据源的不同视角。
这里有一个很关键的纪律:未识别的事件类型,默认必须拒绝重建,而不是悄悄丢弃。 日志是向前兼容设计的------遇到将来才定义的事件类型,老 reader 可以标记为 ignorable 跳过;但在做"重建会话"这种严肃操作时,不认识的类型必须显式报错,绝不把一个残缺的会话悄悄续上。跳过和拒绝,是两件事。

5. 回到开头的"失忆"
回到第 0 节那个场景。在 Harness 里,它不会再以那种"上一句不认"的方式失忆了------因为真相只存一份,谁都没法偷偷改上下文而不留下记录。
对开发者,这意味着三件具体的事:
- 可复现:同样的事件流,投影出同样的上下文;
- 可调试:出了 bug,翻日志就知道模型当时到底看到了什么,而不是去猜;
- 可审计:谁在什么时候做了什么,日志里全在。
一句话收束这套设计:只要真相只留一份,出错的机会就少一份。
6. 一个容易被误会的点:日志不可变,但细节仍会消失
前面用"失忆"打比方,容易让人误以为用户"亲手删了话"。这里需要澄清一个边界:
日志本身是不可变 的,事件一旦写入就不会被修改或删除。真正让模型的视野里少了某些细节的,是compaction(上下文压缩)------用摘要替换掉原始的 surface 节点------以及显式的编辑操作。细节不是被删除了,而是被压缩、替换了;而模型只看得到投影后的结果,所以表现得像失了忆。
换句话说:"失忆"不是记忆被抹除,而是投影源被压缩后,细节不再可见。 这两者的区别,决定了调试时是去查日志(记录还在),还是去查压缩策略(内容被替换了)。
