07 会话日志与可观测性#

DeepSeek Harness 有一条贯穿整个设计的硬性规则:模型看到的一切,必须能从日志里还原出来。基于这条规则设计的会话日志系统,以及配套的 Trajectory 轨迹视图,是 dsh 区别于其他闭源 Agent 产品最显著的特征之一,被 Hacker News 社区评价为「killer feature」。

硬性规则:模型看到的必须写进日志#

这不是一个建议,也不是一个可选功能——这是写死在框架里的强制约束。

规则内容#

任何进入模型请求的内容,都必须先写入会话日志;任何从模型输出的内容,也必须完整记录在日志里。具体来说,必须记录的内容包括:

类别

必须记录的内容

系统提示词

所有系统消息、角色设定、能力说明、格式要求

用户输入

原始用户消息、附件内容、工作区上下文注入

思维链

模型的 Chain of Thought 推理过程(如果模型输出)

工具调用

每一次工具调用的名称、参数、调用时间

工具结果

工具返回的完整输出、错误信息、退出码

子 Agent 调度

委派给子 Agent 的任务、子 Agent 的执行轨迹、返回结果

上下文注入

每一次 RAG 检索结果、记忆模块注入的内容、计划/目标状态

流式输出

完整的 assistant/chunk 流式事件,保留输出顺序和时间戳

元数据

每一步的模型版本、参数设置、Token 用量、耗时

为什么这是硬性规则#

这条规则的核心目的是保证可观测性和可复现性

  • 你可以精确知道模型为什么做出某个决策,因为你看到了它看到的一切

  • 任何会话都可以精确回放,因为所有输入输出都有完整记录

  • 出问题时可以精准定位——是提示词有问题?工具返回错了?还是模型理解错了?

  • 没有「黑盒」,没有隐藏的上下文注入,没有「暗箱操作」

对比许多闭源 Agent 产品:

  • 你看不到系统提示词是什么

  • 你不知道它偷偷注入了什么额外上下文

  • 工具调用的完整参数和返回结果可能被隐藏

  • 出错时你只能猜测「它为什么这么做」

  • 没有办法回放和复盘整个过程

dsh 的设计哲学是:Agent 做的每一件事都应该是可审计、可解释、可复现的

Append-only SessionEvent 流设计#

会话日志的底层是一份仅追加(append-only)的 SessionEvent 流。

什么是 append-only#

Append-only 意味着:

  • 事件只能被追加到日志末尾,不能修改、删除已有事件

  • 日志是不可变的(immutable),一旦写入就不会改变

  • 新事件总是按发生顺序追加,有单调递增的序号和时间戳

这种设计看似简单,但带来了几个极其重要的特性:

  1. 没有并发冲突:写入只需要追加,不需要锁和复杂的事务

  2. 天然支持审计:你无法「篡改历史」,所有操作都留下永久记录

  3. 时间旅行调试:你可以回到任意时间点的状态

  4. 派生能力统一:所有功能(回放、分叉、视图)都从同一条事件流派生

SessionEvent 结构#

每个 SessionEvent 是一个结构化的记录,典型字段包括:

interface SessionEvent {
  id: string;           // 事件唯一 ID
  sequence: number;     // 单调递增序号
  timestamp: number;    // 事件发生时间戳
  type: string;         // 事件类型(user/assistant/tool-call/tool-result/...)
  payload: unknown;     // 事件内容(类型由 type 决定)
  metadata: {
    stepId?: string;    // 所属 Step ID
    turnId?: string;    // 所属 Turn ID
    parentEventId?: string; // 父事件(如子 Agent 事件)
    tokens?: { input: number; output: number };
    duration?: number;
  };
}

事件类型枚举#

核心事件类型包括:

事件类型

说明

session/start

会话开始

user/message

用户消息

system/message

系统提示词注入

assistant/chunk

模型流式输出的一个 chunk

assistant/message

模型完整消息(聚合后的)

assistant/tool-call

模型发起工具调用

tool/result

工具执行结果

agent/subagent-spawned

子 Agent 被创建

agent/subagent-joined

子 Agent 完成并返回结果

context/injected

外部上下文被注入(RAG、记忆等)

checkpoint/created

检查点创建

session/end

会话结束

所有事件严格按发生时间顺序排列在流中。

deriveMessages() 投射机制#

原始 SessionEvent 流是「发生了什么」的完整事实记录,但模型实际看到的是按特定格式组装的消息列表(OpenAI 格式的 messages 数组)。deriveMessages() 函数负责从事件流中投射出模型在任意 Step 看到的消息历史。

投射过程#

deriveMessages(events, upToStepId) 的工作是:

  1. 接收完整的 append-only 事件流

  2. 接收一个目标点(通常是某个 Step 的 ID)

  3. 按顺序重放事件,直到目标点

  4. 将事件转换为模型 API 要求的消息格式(system/user/assistant/tool)

  5. 返回组装好的 messages 数组

这个投射过程是纯函数——给定相同的事件流和相同的目标点,总是返回完全相同的 messages 数组,没有副作用。

为什么用投射而不是快照#

很多框架选择「定期保存消息历史快照」,但 dsh 选择从事件流投射,原因是:

  • 单一真相源:只有事件流是真实的,快照只是派生品,不会出现「快照和真实状态不一致」

  • 任意时间点:你可以投射出任意 Step 时模型看到的历史,而不仅仅是保存快照的几个点

  • 天然支持分叉:从任意事件点分叉,只需要从该点开始追加新事件,不需要复制整个历史

  • 容错性好:如果进程崩溃,只需要从最后一个事件开始重放,不需要担心快照损坏

原始事件流是唯一的真相来源,所有其他视图都是从它派生出来的。 这是 dsh 会话日志设计最核心的洞见。

Trajectory 轨迹视图使用方法#

Trajectory(轨迹)视图是 dsh Web UI 中基于事件流提供的可视化工具,也是普通用户最常接触的可观测性功能。

如何打开 Trajectory 视图#

有两种打开方式:

  1. 单条消息轨迹:在任意一条 Agent 回复消息的右上角,点击 Trajectory 按钮,查看这条消息对应的执行轨迹

  2. 整个会话轨迹:在会话侧边栏或会话菜单中,选择 View Full Trajectory,查看整个会话从开始到现在的完整轨迹

Trajectory 里能看到什么#

打开 Trajectory 视图后,你会看到按时间顺序展开的完整执行树,每一个节点都是一个事件:

节点类型

展示内容

系统提示词

可以展开查看完整的系统提示词内容,包括所有能力说明、格式要求、角色设定

用户输入

原始用户消息,以及自动注入的工作区上下文

思维链(CoT)

如果模型输出了推理过程,这里会完整展示(通常是折叠的,点击展开)

工具调用

每次工具调用的名称、完整参数、调用时间、耗时

工具结果

工具返回的完整输出,包括 stdout/stderr/退出码/错误信息

上下文注入

RAG 检索到的文档片段、记忆模块召回的内容、自动注入的其他上下文

子 Agent

子 Agent 的独立轨迹可以展开查看,形成嵌套树结构

流式输出

可以回放模型输出的流式过程,逐字看到回答是如何生成的

元数据面板

每个节点都有元数据面板,显示 Token 用量、模型版本、耗时等信息

每个节点旁边标注了来源(是哪个插件注入的、哪个工具产生的),让你清楚知道每一段内容从何而来。

Trajectory 里能做什么#

在轨迹视图中,你可以执行以下操作:

  1. 检视(Inspect):点击任意节点展开查看完整内容,长内容不会被截断

  2. 搜索(Search):在轨迹中搜索关键词,快速定位到特定事件

  3. 过滤(Filter):按事件类型过滤(比如只看工具调用、只看错误)

  4. 分叉(Fork):从任意节点分叉出一个新会话(详见下一节)

  5. 恢复(Resume):跳回历史上的任意节点,从那里继续执行(相当于「时间旅行」)

  6. 回放(Replay):自动回放整个执行过程,按原时间间隔展示事件如何一步步发生

  7. 导出(Export):将轨迹导出为 JSON 或 Markdown 格式,用于分享或离线分析

Fork 分叉 / Resume 恢复 / 回放机制#

基于同一条 append-only 事件流,dsh 实现了三个强大的调试功能。

Fork(分叉)#

分叉允许你从历史上的任意一个节点开始,创建一条独立的新会话分支:

使用场景:

  • Agent 走了一条错误的路,你想「如果当时我给它补充点信息,会怎么样?」

  • 你想试验不同的选择——「如果当时它选了工具 A 而不是工具 B,结果会怎样?」

  • 你想基于某个中间状态尝试不同的提示词策略

操作方法:

  1. 在 Trajectory 视图中找到你想要分叉的节点

  2. 点击节点菜单,选择 Fork from here

  3. dsh 会创建一个新会话,历史到该节点为止与原会话完全一致

  4. 从这个点开始,你可以输入新的指令,新的事件会追加在分叉后的事件流中,不影响原会话

分叉是极其轻量的——因为事件流是 append-only 的,分叉不需要复制任何历史事件,只需要记录「新分支从事件 X 开始」即可。你可以从同一个点分叉出无数个分支做试验,几乎不占额外存储空间。

Resume(恢复)#

恢复允许你跳回当前会话历史上的任意节点,从那里继续执行:

与分叉的区别:

  • 分叉:创建新会话,原会话保持不变

  • 恢复:在当前会话中「回退」到某个点,后续事件会追加在该点之后,该点之后原有事件被标记为废弃但不删除

使用场景:

  • Agent 已经走了 10 步,你发现第 3 步就错了,不想从头重新输入,直接从第 3 步重新来

  • 你想打断 Agent 的当前执行,调整一下提示词,然后让它从打断点继续

  • 修复了某个工具的 bug,想从工具调用失败的点重试,不需要重跑前面的步骤

操作方法:

  1. 在 Trajectory 视图中找到你想要恢复到的节点

  2. 点击节点菜单,选择 Resume from here

  3. 确认后,会话会回退到该节点的状态

  4. 输入新的指令,或者让 Agent 重新执行后续步骤

被「回退」掉的事件并没有真正删除——它们仍然在事件流中,只是被标记为 deprecated: true,不会被 deriveMessages() 投射给模型。你随时可以再回到原来的分支。

Replay(回放)#

回放允许你按原时间顺序重新「播放」一遍整个会话的执行过程:

使用场景:

  • 向同事演示 Agent 是如何一步步解决问题的

  • 录制教程或演示视频

  • 仔细观察某个容易被忽略的中间步骤

  • 复盘长时间运行的任务,不需要实时盯着

操作方法:

  1. 打开 Trajectory 视图

  2. 点击顶部的 Replay 按钮

  3. 可以调整回放速度(0.5x / 1x / 2x / 5x)

  4. 事件会按原始发生的时间间隔依次展示,流式输出会逐字回放,就像你重新跑了一遍一样

回放也可以从任意节点开始播放,不需要从头开始。

调试价值:为什么这很重要#

这套可观测性设计在实际使用中价值巨大,尤其体现在以下场景:

长任务 Debug#

对于需要运行几十步、几十分钟的长任务:

  • 没有 Trajectory:如果最后一步失败了,你可能完全不知道中间哪一步出了问题,只能从头重跑一遍,再试一次

  • 有 Trajectory:你可以回到失败点,查看当时工具返回了什么、模型看到了什么上下文,直接定位问题原因,从失败点重试而不用重跑前面所有步骤

长任务调试的时间成本可以降低一个数量级。

决策复盘#

当 Agent 做出了一个意料之外的错误决策时:

  • 没有 Trajectory:你只能猜测「它为什么会这么想」,可能是提示词问题、可能是工具返回错了、可能是上下文太长它忘了前面的内容——你无法确定

  • 有 Trajectory:你可以展开决策前的系统提示词、查看当时注入了什么上下文、看工具返回的完整结果、甚至看它的思维链,精确理解决策是怎么一步步产生的

这对于改进提示词、修复工具 bug、优化 Agent 行为至关重要。

失败分析与迭代#

当你在开发自己的插件或优化提示词时:

  • 每一次失败都是一次「测试用例」,被完整记录下来

  • 你可以对比修复前后的轨迹差异,确认问题是否真的解决了

  • 你可以收集失败案例,建立测试集,做回归测试

  • 你可以把失败轨迹分享给别人,请求帮助排查问题——对方不需要重跑,看轨迹就能知道发生了什么

对于严肃的 Agent 开发工作,这种级别的可观测性不是「nice to have」,而是「must have」。

Hacker News 评价:Killer Feature#

DeepSeek Harness 开源后,Trajectory 和会话日志设计在 Hacker News 上引发了大量讨论,被许多开发者称为「真正的 killer feature」。

讨论中被反复提及的对比点:

方面

许多美国厂商闭源产品

dsh

系统提示词

作为商业秘密隐藏,用户看不到

完全开放,Trajectory 里随时可以看

工具调用参数

可能被截断或隐藏部分字段

完整记录,一字不差

工具返回结果

经常只展示摘要,原始数据不公开

完整 stdout/stderr 都记录

上下文注入

偷偷注入 RAG 结果或内部指令,用户不知道

每一次注入都在事件流中有记录,标注来源

思维链

很多产品不展示,或者只展示「整理后」的版本

完整记录,原样展示

分叉/回放

不支持,或者只支持简单的「重新生成」

从任意点分叉、恢复、回放,轻量高效

可复现性

同一条指令多次运行结果可能不同且无法解释

相同事件流一定投射出相同消息历史,执行过程完全可复现

正如一位开发者在评论中所说:

「当你用 Claude Code 或 Cursor 遇到一个奇怪的 bug,你花了两个小时还搞不清它为什么要删掉你的重要文件时,你就会明白——能看到模型看到的一切,不是一个功能,这是基本人权。」

DeepSeek Harness 选择把一切都摊开给你看,而不是把用户当不需要知道细节的「傻瓜用户」。这种透明化的设计选择,本身就是对开发者用户最大的尊重。

理解了会话日志和 Trajectory,你就掌握了 dsh 调试和复盘能力的核心。下一章我们将介绍如何在 dsh 中配置和切换不同模型。


06 Agent 循环 | → 08 模型配置