跳到主要内容
路径文档

长任务自动推进:goal-round-driver 与 Ralph

一句话版:DSH 有两条"没人盯着它也能持续向前"的路径。goal-round-driver 在同一会话里反复追加 <goal_round> 提示,复用前缀 / KV cache / 工具结果,是有持久化检查点的同会话续推Ralph 每轮开一个全新 Agent,只用共享工作区 + 一份有界报告跨轮,是无会话血缘的换新推进。多数长目标用 goal-round-driver,只有用户明确要"换新 agent 迭代试错"时才用 Ralph。

两种机制都回答同一个问题:"目标没完成,谁继续往下做?",但答案的心智模型完全不同:

维度goal-round-driverRalph
轮次载体同一条会话,追加 <goal_round> 用户角色提示每轮一个全新子 Agent,无父会话 seed
跨轮记忆会话历史(KV cache / 工具结果 / 状态)共享工作区 + 一份有界结构化报告
状态归属ctx.goals 持久 goal/change 事件workflow + ctx.subagents(无独立 goal 域)
上限maxGoalRounds,到顶自动 blockround-limitmaxRounds,到顶返回 budget-limited
完成/阻塞update_goal 自主 complete/blocked子 Agent 报告 status: continue/complete/blocked
收尾concludeTurn() + <goal_complete>/<goal_blocked> 闭场父拿到子报告,无闭场上下文注入
权威判定同会话一次性 agent 的自动续推需 driver 授权轮次子 Agent 只是父的普通子代理

一、goal-round-driver:同会话续推

@deepseek-ai/dsh-goal-round-driver 是一个 Cordis 插件,挂在公共的 agents / goals / sessions 服务之上,把 ctx.goalsactive + armed 的目标转成连续的 goal round。它调用私有 agent-loop,只用公共 Agent 接口和会话事件。

组合

- id: goal
name: '@deepseek-ai/dsh-goal'

- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'

- id: goal-round-driver
name: '@deepseek-ai/dsh-goal-round-driver'

goal-round-driver 自身没有可调配置;轮次上限 maxGoalRounds 属于目标定义(tool-goalcreate_goal / 部署 defaultMaxGoalRounds),模型自我判 blocked 的门槛属于 tool-goalblockedAfterConsecutiveRounds。源码 README 原话:把任一值复制进 driver 会造成策略分歧。

round 契约

当某个确切的 live agent 处于 idle、目标 active + armed、且还有剩余轮次时,driver 先持久化待提交的 goal 变更,再为当前 { goalId, revision } 预定第 roundsStarted + 1 轮。它通过 Agent.followup() 入队一条带 GoalMessageSourcekind: 'goal')的 <goal_round> 提示。真正被 admit 的 user/message 才推进 roundsStarted;被判定 stale 的预定不消耗轮次编号

// packages/goal/goal-round-driver/src/prompt.ts(摘)
export function renderGoalRoundPrompt(goal: GoalView, round: number): ContentBlock[] {
return [{
type: 'text',
text: '<goal_round>\n'
+ `Objective: ${JSON.stringify(goal.objective)}\n`
+ `Round: ${round}/${goal.maxGoalRounds}\n\n`
+ 'Continue working toward the objective in this same session. Treat the current workspace, '
+ 'tool results, and durable session state as authoritative; inspect them instead of assuming '
+ 'earlier narration is still current. …',
}]
}

提示进入会话历史后,同一会话里既有人类消息、目标快照,也有助手输出与工具记录。这是增量追加:每一轮在前缀上继续,而不是复制一份会话前缀或派生历史。

每轮入队前 await sessions.flush()

goal/changed 事件会产生一个持久化义务。真正排队工作之前,driver 会 await ctx.sessions.flush(agent.session),并在 await 返回后重新核对目标 revision 与是否有竞态输入:

// packages/goal/goal-round-driver/src/index.ts(摘,checksum 前)
if (state.needsCheckpoint) {
state.needsCheckpoint = false
try {
await ctx.sessions.flush(agent.session)
} catch (error) {
ctx.logger.warn(`…durability checkpoint failed for agent "${agent.id}"…`)
disarm(state) // 检查点失败 → 关闭续推,绝不带病推进
return
}
if (!readyAfterCheckpoint(state)) return // flush 期间可能有新输入/变更
}
  • 检查点失败会走 agent/errordisarm(),先于下一轮开始就撤销续推
  • 持久化是检查点而非每个 step —— ctx.sessions.flush() 的语义是"把待写事件落到 durable 日志"
  • flush 期间若有普通人类消息或 mutation 到达,driver 会退让,给这条输入一个自己的检查点/轮次后再续推

round 上限:到顶自动 block

// packages/goal/goal-round-driver/src/index.ts(摘)
if (goal.roundsStarted >= goal.maxGoalRounds) {
ctx.goals.block(agent, goalRef(goal), {
code: 'round-limit',
message: `Goal reached its configured limit of ${goal.maxGoalRounds} rounds.`,
})
return
}

到顶后进入 block(durable phase),code 为 round-limitmaxGoalRounds 只计被 admit 的 goal-sourced 轮次;人类消息不计入上限。

不 spawn 新 agent、不 fork 会话前缀

源码 README 明确把这点列为设计边界:

Same-session execution only — this package deliberately does not spawn a fresh agent, fork a session prefix, or implement Ralph-style independent attempts.

每个被 admit 的轮次只增加一个固定指令块 + 目标文本;KV cache 是同 epoch 内只追加:每轮扩展既有会话的可复用前缀,压缩才可能移动此前缀边界。这是它比 Ralph 省 token/KV 的根本原因。

竞态栅栏(简短)

driver 精心处理"预定 vs 入队 vs admit"的竞态,超出本文范围,但要点是它的 agent/pre-step 监听器会在下游监听器之前和之后各校验一次完整的 claimed 记录与当前目标;只有 enter 的 user/message 才累加 roundsStarted;一个 pending 的自动 prompt 若落进混有人类消息的批次,则被 reject 并在检查点后重新预定。

二、tool-goal 的 wrap-up:自主收尾

@deepseek-ai/dsh-tool-goal 暴露 get_goal / create_goal / update_goal。在自主 goal round 中,模型调用 update_goalcompleteblocked 成功后,tool-goal 会用 exec.deferContext() 注入一段 <goal_complete> / <goal_blocked> 闭场上下文,并标记该次工具执行 concludeTurn(),让这一个物理轮次在 step 后停止——但模型仍会先给用户写一句收尾(源码原话:replacing the former hard turn stop)。

// packages/goal/tool-goal/src/index.ts(摘)
if (authority.kind === 'goal-round') {
exec.deferContext(createUserMessage({
content: args.action === 'complete'
? renderWrapupContext(goal.objective)
: renderWrapupContext(goal.objective, args.blocked_reason as string),
source: { kind: 'plugin', plugin: 'tool-goal', form: 'notice',},
}))
}

闭场上下文要求 grounding(只报告本会话里实际建立的事实)、直接对用户讲话、且不再调用更多工具。

update_goalblocked目标轮次权威下受硬门槛约束:

// packages/goal/tool-goal/src/index.ts(摘)
if (args.action === 'blocked' && authority.kind === 'goal-round'
&& authority.goal.roundsStarted < resolved.blockedAfterConsecutiveRounds) {
throw new HarnessError(
`blocked requires at least ${resolved.blockedAfterConsecutiveRounds} consecutive goal rounds; …`,
'GOAL_TOOL_BLOCK_THRESHOLD')
}
  • blockedAfterConsecutiveRounds 默认 3z.number().step(1).min(1).default(3)
  • 它是模型自我判 block 的硬下界:未到 3 个 admit 轮次就 self-block 会被机械拒绝
  • 语义"同一条件是否真持续"仍是模型判断——driver 只数不同的 admitted round 数,并持久化 code: 'model-reported'

三、Ralph:每轮全新 Agent

@deepseek-ai/dsh-tool-ralph 是模型面向的 fresh-agent iterative loop。它没在 agent-loop 里加任何 Ralph 模式,只是基于 ctx.workflowEnginectx.subagents 的一个普通插件:

Use the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent
iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses
the shared workspace as durable memory. Completion and blockers are worker reports, not
independent evaluation. Use same-session goal tools for ordinary long-running objectives…

每个 Ralph round 通过 subagentProvider 起一个不带父会话 seed 的全新子 Agent;父会话与之前的子会话都不 seed。跨轮的唯一记忆是共享工作区 + 一份有界结构化报告status: continue | complete | blocked + summary / evidence / next steps / blocker text)。

  • 报告超限 / 缺字段 / 无效 → fail workflow,不是截断或当成到顶
  • 子失败给出带轮次号的错误并保留最后一份成功 handoff;Ralph 不重试该轮
  • 普通子失败与 provider/transport 失败都是错误,部分输出永不当作成功
  • 完成是 worker 自报:子 Agent 报告 complete,父侧只是转述,无独立 evaluator 认证

四、什么时候用哪个

场景
用户提了一个长期目标,希望它在同一条会话里被反复推进goal-round-driver(create_goal)
目标是"完成 X",需要复用已累积的工具结果、KV cache、会话状态goal-round-driver
需要持久化检查点:推进到一半重启也要接续goal-round-driversessions.flush 检查点)
用户明确要求"Ralph 循环 / 换新 agent 迭代执行"ralph
目标稳定、需要反复试错推进,且每轮都希望从干净上下文开始ralph
一两个独立委派 / 大规模 fan-out普通 subagent / workflow(别上 goal / ralph)

源码里给模型的两段 guidance 也以此为界:Ralph 只在该直接人类明确要求 fresh-agent 迭代时用;目标工具用于普通的同会话长目标。

五、验证

# 断言"同一会话":goal round 是 user/message 里 source.kind 为 goal 的事件
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd \
| grep -E '"source":\{"kind":"goal"' | head

# 看 goal 变更与 round 上限阻塞
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -E 'goal/|"round-limit"' | head

# 组合树里看三个 goal 插件是否都装载
dsh web --dump-config | grep -iE "goal-round-driver|tool-goal|'goal'" | head

# Ralph 走 workflow run 事件(run-start/成员/run-end)
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -E 'workflow|"run/' | head

对比两条路径,goal/change + user/message(source.kind=goal) 是同会话续推的指纹;workflow/run* 与 child session 是换新推进的指纹。

下一步