第 1 卷
张嘉熙 · DeepSeek Harness 前沿工程实践




你好,我是张嘉熙。
让一个 Agent 干一件要跑很多轮的活,比如把一整本书的错别字都改完,这个“要做什么”得一直记着不能丢,还得一轮一轮自动往下续跑,直到干完。
完成这类任务需要靠三个包:goal(目标状态服务)、goal-round-driver(续跑驱动器)、 tool-goal(模型工具)。我们把它拆成三个问题:
怎么记目标状态?
怎么自动续跑?
怎么停下来?
答案就是:goal 管“记”,把目标状态作为事件写进会话日志;goal-round-driver 管“续”,
关于环境:本讲新增三个包:@deepseek-ai/dsh-goal(目标状态服务, ctx.goals)、@deepseek-ai/dsh-tool-goal(面向模型的 get_goal /
create_goal / update_goal 工具)、@deepseek-ai/dsh-goal-round-driver (续跑驱动器)。另有面向用户的 /goal 命令 @deepseek-ai/dsh-command-goal
(输入 /goal 手动控制目标;和 tool-goal 一样,都是 ctx.goals 的门面),本讲不展开。npm install --legacy-peer-deps 装上即可。
关于示例:npm run start:ch15 用一个脚本假模型跑,先定义一个目标,模型第 1 轮只干活、第 2 轮调用 update_goal 把目标标成完成,看驱动器和日志怎么记。不联网、
不需要 API Key。
怎么记目标状态:goal 的持久状态
一个会话里最多有一个当前目标(不支持并行目标)。这个目标的状态是持久的:不是藏在内存里、进程一重启就丢的变量,而是记在会话日志里,具体方式是事件溯源(event
sourcing),每次变更都追加一条 goal/change 事件(带变更后的完整状态),当前状态由这些事件回放出来,日志因此是唯一持久的真相来源。
这个完整状态(代码里叫 GoalView)有哪些字段:
字段含义
| id / revision | 稳定标识+每次变更递增的版本(比较后写入用) |
|---|---|
| objective | 人类请求的完成目标,一段文字 |
| phase | 目标状态:active / paused / blocked / complete |
| roundsStarted / maxGoalRounds | 已准入的续跑轮次数/上限(不传默认256) |
| blockedReason | 仅在blocked时存在(code + message) |
armed(启用)/ disarmed(停用):进程本地的续
理成了一张表格,你可以看一下。
| 操作 | 作用 | activation(续跑开关) |
|---|---|---|
| create | 建一个目标(revision 1、phase active) | armed |
| edit | 改objective / maxGoalRounds,phase不变 | 不变 |
| pause / resume | 暂停/重新启用 | disarmed / armed |
| complete | 标记完成 | disarmed |
| block | 标记阻塞(带code + message原因) | disarmed |
| clear | 清除(留下删除标记) | disarmed |
把 phase 和这七个操作合起来看,goal 就是一台简单的状态机:phase 是目标所处的状态,操作是状态转移。phase 有四种:
| 状态 | 含义 | 出边 |
|---|---|---|
| active | 正在跑 | pause→paused;block→blocked |
| paused | 暂停 | resume→active |
| blocked | 卡住(带原因) | resume→active |
| complete | 完成 | —(终点) |
另外,complete 操作能把任何未完成的状态(active / paused / blocked)直接标成完成。
clear 则把当前目标整个清掉,只留一个删除标记(tombstone),清掉之后目标就没了,也就不属于任何一个 phase。