DeepSeek Harness Trajectory 实战:录下 Agent 每一步,随时回放复盘
生命不息,折腾不止。今天教你读懂 dsh 那张「只增不改」的会话日志——Agent 每一步干了啥、烧了多少 token、哪里跑偏了,全都有据可查。
一、先搞清楚一个核心:dsh 的会话是「只追加」的账本
很多人用 DeepSeek Harness(dsh)跑 Agent,跑完就完事了,从没想过「它刚才到底怎么想的」。其实 dsh 从底层就把每一次会话都当成一本只追加(append-only)的账本在记——这正是官方反复强调的那句「Every run is traceable(每次运行都可追溯)」的底气所在。
什么意思?你发一句话,Agent 思考、调工具、改文件、回结果,这一连串动作会被拆成一条条事件(event),按顺序(seq 从 0 连续编号,events[i].seq === i)追加进日志里。关键点在于:
- 只追加、不改写:历史事件写进去就永远在那里,不会被覆盖。这跟 git 的 commit 是一个思路,所以它天然支持「回放」「分叉」「回溯」。
- resume、fork、search、replay 全都操作同一条事件流:这是 dsh 官方原话。恢复会话、从中间分叉、搜索历史、回放,本质都是在这本账本上做文章,而不是另起炉灶。
这套设计在工程上叫「事件溯源(event sourcing)」。好处是:只要日志还在,任何时刻的会话状态都能重建——哪怕程序崩了、界面卡了,账本没丢,事情就没白干。
二、日志到底存在哪、长什么样
日志默认落在 ~/.dsh 目录下(可以用环境变量 DSH_HOME 改位置)。每个会话一个独立目录,核心文件是压缩过的 JSONL:
1 | ~/.dsh/sessions/ |
版本迭代比较快,你可能会看到
session.v2.jsonl.zstd、session.v3.jsonl.zstd这类带版本号的变体,不影响理解——最新的那份就是当前会话日志。具体布局以官方文档为准。
想亲眼看看账本长啥样?机器上装了 zstd 的话,直接解压读:
1 | # 找到某个会话的日志文件后,解压看原始事件流 |
你会看到第一行是一条 SessionHeader(会话头),记着会话 id、创建时间、工作目录 cwd、父会话 parentSession、seedLength、agentPreset 等元信息;后面每一行就是一条事件,比如 turn/start(一轮开始)、step/start(一步开始)、工具调用、assistant/message(模型回复)、session/end-seed(分叉/恢复的边界)…… 串起来就是 Agent 完整的一生。
三、内置 Trajectory 视图:不用碰文件也能看全过程
命令行读日志有点硬核,日常调试其实靠 Web UI 内置的 Trajectory(轨迹)视图就够了。你在会话页顶部切到 Trajectory 标签,能看到一个按轮次/步骤展开的树,每层都有:
- 模型想了什么、回了什么(思考过程 + 最终回复)
- 调用了哪些工具、传了啥参数、返回了啥结果——嵌套的子工具会以
SUBTOOL: xxx的形式标出来,一眼看出它是自己内部又去调了什么 - 每一步的耗时和 Token 用量:模型时间、工具时间、首 Token 延迟、解码时间,都能在这里对得上
这玩意儿调试 Agent 特别实用。Agent 跑偏了,别再对着结果瞎猜,直接进 Trajectory 看它在第几步、调了哪个工具、拿到了什么结果才走岔的。我自己的经验:90% 的「Agent 犯蠢」都能在这棵树里定位到具体那一步。
**恢复(resume)**也在这条链上:重启 dsh 后,左侧会话列表里点开旧会话,日志会被恢复、上下文原样接上,你可以让它接着上回没干完的活继续。**分叉(fork)**则是从某个历史节点开一条新枝,新会话会记录 parentSession 指向老会话——「我先这么试一条路,不行再回头走另一条」,就是这么来的。
四、进阶:装个插件,把日志变成「时光机」
内置 Trajectory 能看单会话的步骤,但社区插件能把账本玩出花。推荐两个我用过觉得值的:
① @mingozhou/dsh-replay——会话时光机
一句话:把 append-only 日志变成可交互的回放。装上之后侧边栏会多一个 Session Replay 入口,每个会话里也多一个 Replay 标签,能:
- 时间线回放:按 1–16 倍速拖动回看每一轮、每一步、每条工具调用,点任意事件看原始 prompt / 参数 / 结果 / 耗时
- 逐步 token 账单 + 成本估算:累计 token 曲线、工具耗时排行,还能按 DeepSeek / Claude / OpenAI / Gemini 的价目表估算这一会话大概花了多少钱(价目表可改)
- 安全审计:规则化扫描危险操作(
rm -rf、sudo、curl | sh之类)、敏感路径(.env、~/.ssh)、权限/沙箱变更、被拒绝的审批,按严重程度排好、可点击下钻 - fork 血缘树:整条会话的血缘关系画成可点树,分叉边界标得清清楚楚,子 Agent 单独标记
- 双会话对比:任意两个会话并排比 token 用量、工具组合,还能精确标出它们从哪一条事件开始分道扬镳
- 一键导出 HTML:把整个会话烤成一个离线
.html,发给队友或贴进 bug 报告,对方零安装就能看完整回放
装起来一条命令(带 dsh CLI、用 profile 方式安装):
1 | dsh plugin --profile web add @mingozhou/dsh-replay |
插件作者在 GitHub 上放了零安装的在线 demo(https://mingozhou.github.io/dsh-replay/ ),不想装可以先点进去体验三份样例会话。
② dsh-retrace——撤回 / 编辑重发 / 重新生成 + 版本回退
dsh 的日志只追加、本身没有「撤销」。dsh-retrace 补上聊天本该有的三个操作,再往前一步做版本化:每次回退记成一个版本,有时间线、有产物文件回退(git 优先 + 快照兜底),还能一键跳回对话的任意位置。注意它的哲学:撤回删掉的是「视图和上下文」,底层日志永不改写——它只是追加一条合法的替换事件把对话表面回退掉,审计痕迹全程保留。
1 | dsh plugin add dsh-retrace |
五、几个实操心得(含踩坑)
折腾下来,这几个点值得记:
token 账单能帮你核对成本。如果你接的是中转站(比如
ai.aklibk.com这类按量付费的),配好价目表后 dsh-replay 的「成本估算」就能直接跟你后台的账单对一下,Agent 跑一晚上的钱花哪了、哪个工具最烧 token,一目了然。省得月底看账单一脸懵。seq连续性 = 日志健康度。健康日志的seq是连续无缺的(events[i].seq === i)。如果你自己写脚本解析日志,发现seq跳号,多半是多进程并发写同一会话日志了——这属于已知坑,别在多个进程里同时 append 同一个会话。冷启动空白是已知 bug。重启 dsh 后点开旧会话,对话区偶尔空白(数据其实在),切到 Trajectory 再切回来就正常了。不是日志丢了,别慌着去删数据。
流式输出是「打包 chunk」存的。为了省空间,连续的流式增量会打包成
text-chunks/reasoning-chunks/tool-call-chunks这类行(带seq0+ 每个成员的时间差dt),解析时注意解包,别把一条当一条真事件读。想自己写工具直接 importdsh-replay/core,它已经帮你处理好了打包行、崩溃截断尾、分叉边界这些脏活。日志 = 账本,但记得别乱删。
~/.dsh会越积越大,清理前想清楚:删了账本就意味着那段历史彻底没了,resume/fork/回放全都失效。要省空间优先删旧的、已经收尾的会话,别动正在用的项目目录。
六、写在最后
「把 Agent 每一步都录下来」这事,看着是给调试用的,其实它是 dsh 整个框架的地基——因为日志是只追加、可重建的,所以才能做分叉、回放、恢复、审计这些高级玩法。搞清楚这本账本,你对 dsh 的理解会从「会用」直接跳到「能诊断」。
生命不息,折腾不止。下一期我准备讲 《DeepSeek Harness 无头自动化:不开浏览器,用命令行让 Agent 自己跑批量任务》——很多兄弟想让它半夜自动干活、或者塞进脚本里流水线化,下一期就带你玩
dsh的 CLI 模式和无头跑法,把「人工守着点」变成「写完睡觉,明早收结果」。