推广 热搜: 采购方式  滤芯  带式称重给煤机  甲带  气动隔膜泵  减速机型号  无级变速机  链式给煤机  履带  减速机 

DSH「Loop 长程任务」实现机制调研报告

   日期:2026-08-15 12:26:28     来源:网络整理    作者:本站编辑    评论:0    
DSH「Loop 长程任务」实现机制调研报告

调研对象:DeepSeek Harness(deepseek-harness 仓库,下称 dsh) 调研范围:agent-loop 轮次引擎、同会话 goal 体系、Ralph 新鲜代理循环、以及支撑它们的 workflow / subagent / session 事件源等机制 调研方法:源码精读(packages/*)+ 决策记录交叉验证(.agents/notes/implemented/*)+ 文档核对(docs/) 结论前置:dsh 没有也不打算做一个通用 loop 服务。 它把"长程任务"显式拆成两个正交策略——同会话 Goal 延续与新鲜代理 Ralph 循环——叠加在一个可替换的 turn/step 引擎(dsh-agent-loop)之上,全部以插件形式挂在公共扩展点上。


1. 摘要

dsh 对"长程任务"的处理可以概括为三句话:

  1. 底层只有一个可替换的 turn/step 引擎(packages/core/agent-loop)。它拥有一轮 turn:排空已受理输入、执行若干"模型请求+工具调用"的 step、然后停止。它不关心"目标"这种外层概念。
  2. 长程目标 = 引擎之上的两种显式策略,彼此独立: 同会话 Goal:在一个 session 内保留一个持久目标(packages/goal/goal),由轮次驱动插件(packages/goal/goal-round-driver)在条件满足时通过公共 Agent.followup() 自动再开一轮 turn;模型通过 get_goal / create_goal / update_goal 三个工具读写生命周期,人类通过 /goal 命令直接控制。 Ralph 循环:一个前台、固定脚本的 workflow(packages/workflow/tool-ralph),每轮生成一个全新、无会话种子的子代理,只携带不可变目标与上一轮的结构化交接报告;共享工作区是跨轮长期记忆。
  3. 贯穿性设计原则:everything-is-a-plugin、模型可见 ⟺ 已记录(事件源 session log)、持久状态与进程内"执行授权"分离、执行期权威校验(人类消息 vs 自动轮)、慷慨但受部署约束的轮数预算、以及"完成/阻塞是声明而非认证"的显式信任边界。

一个目标层级 Goal → Goal Round → Turn → Step 用于同会话延续;Ralph Run → Ralph Round → fresh child Turn → Step 用于新鲜代理迭代。"Round"是外层策略迭代,不是每个会话轮次的同义词。


2. 术语与层级(词典先行)

词
定义
出处
turn
一次排空已受理输入的过程,模型与工具停止或终态策略介入后结束
docs/glossary.md
step
一次模型请求 + 该响应引发的工具执行;一个 turn 含零或多个 step
同上
round
包含一个 turn 的外层策略迭代(goal round / Ralph round);轮数计数器属于该策略,不算 session 里的每个 turn
同上
goal
挂在既有 session 上的一个持久完成目标,带 revision 化的 active/paused/blocked/complete 相位与轮数上限;blocked 保留策略码与解释
同上
goal activation
进程内、不持久化的"是否允许自动续轮"开关:armed / disarmed
同上
Ralph loop
一个前台 fresh-agent workflow 运行,向不可变目标迭代;是模型工具策略,不是 goal、不是 agent-loop 模式、不是调度器
同上
Ralph handoff
一轮继续的 Ralph round 传给下一轮的规范化、有界结构化报告(status/summary/evidence/nextSteps/blocker)
同上

3. 总体架构:为什么没有 packages/loop/

3.1 决策源头

核心决策记录是 .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md("Harness-level goal-based execution")。它明确否决了一个"推测性的通用 loop 服务",因为 Evaluator、BudgetPolicy、RoundHandoff、GoalReflector、后台任务所有权、持久化、调度这些不是一个必须被揉在一起的一致性抽象。仓库里不存在 LoopDriver、LoopId、通用 StopCondition 或模型可见的通用 loop 工具。

两种策略共享仓库里普通的 agent、session、tools、workflow、subagent、UI 扩展点,但不假装一个生命周期能同时服务两者。第三种策略(定时 /loop / 调度执行)明确不在该决策内——它属于 scheduler 而非 goal 家族(注:仓库后来确实有了 packages/schedule,但定位是"面向未来根代理的持久提醒",见 §10.3)。

3.2 包拓扑与各自拥有的动词

包
角色
拥有的结构与动词
@deepseek-ai/dsh-goal
(packages/goal/goal/)
领域服务
GoalId
、compare-and-set GoalRef、GoalSnapshot、四态 GoalPhase、结构化 GoalBlockReason、进程内 GoalActivation、重放折叠,以及 get/create/edit/pause/resume/complete/block/clear/disarm
@deepseek-ai/dsh-tool-goal
(packages/goal/tool-goal/)
模型面工具
独占注册 get_goal/create_goal/update_goal;要求"当前 live 根代理轮次中的直接人类消息";把自动轮权限收窄为只能报 complete/blocked
@deepseek-ai/dsh-goal-round-driver
(packages/goal/goal-round-driver/)
延续策略
为同会话 goal round 做预留、围栏、准入、属性、结算、取消、静默排空,不 import 具体 loop
@deepseek-ai/dsh-command-goal
(packages/goal/command-goal/)
人类命令
注册 /goal 状态/创建/编辑/暂停/恢复/清除
@deepseek-ai/dsh-tool-ralph
(packages/workflow/tool-ralph/)
固定 workflow 消费者
注册 ralph({ objective, maxRounds? }),校验 fresh provider 与有界 RalphRoundReport,返回 complete / blocked / budget-limited

底层的 dsh-agent-loop 仍是唯一的 turn/step 引擎;同会话驱动只用了公共 agent/session 事件,它向核心的唯一新增是一个通用的"取消前观察"通知 agent/cancel-requested(任何生命周期策略安全地结算取消都需要它)。

3.3 两种策略的本质差异

维度
同会话 Goal
Ralph
上下文
保留同一会话(对话、工具结果、持久状态)
每轮彻底丢弃会话上下文
跨轮状态
session log + 工作区
共享工作区 + 一个结构化交接报告
生命周期
持久(事件源),重启可恢复
前台、进程内,不可恢复
轮预算
maxGoalRounds
(默认 256)
maxRounds
(默认 256,base bundle 部署上限 64)
谁在跑
同一 Agent,driver 自动开新 turn
每轮一个新的 fresh child agent
授权模型
人类消息 vs 精确 goal round 的权威校验
完成/阻塞是 worker 自报

4. 基础层:dsh-agent-loop 的 turn/step 引擎

4.1 职责边界

packages/core/agent-loop/src/index.ts(713 行)是具体驱动:AgentLoop extends Service implements AgentFactory,暴露为 ctx.agentLoop。它创建 ReactLoopAgent、把 agent 与 session 发布进注册表、并拥有有序 teardown。turn/step 推进完全由 ReactLoopAgent 内部状态机(Phase = idle | maintenance | running{turn, step, wakeRequested})驱动。

抽象层 packages/core/agent/ 只定义 Agent 接口、AgentRegistry(get/list/roots/enter/announce)与进程内 initiator 作用域(AsyncLocalStorage),不实现任何驱动逻辑——"Plugins, not loop changes"。

4.2 输入:唯一的消息载体与两条队列

所有输入都是 UserMessage(role: user),带 MessageSource 归属(merge-extensible:user / plugin / model / tool / goal / user-rpc…)。ReactLoopAgent 暴露三个入口,落在 inbox 的两条持久队列上(packages/core/agent/src/inbox.ts):

入口
队列
唤醒
用途
followup()next-turn
是
开一个新 turn(goal 延续、Ralph 等都用它)
steer()next-step
是
在当前 turn 内插入 step(agent/turn-stopping 续跑、人类中途介入)
inject()next-step
否
注入上下文,等别的消息顺带带进下一请求

inbox 是可重放的持久投影:每次 splice 都落一条 durable agent/inbox/spliced 事件(先落日志、后改投影),取消/清除以 canceled splice 记录。claim(target, turn) 批量取走 next-step、并在 turn 边界再取 1 条 next-turn,逐条发 agent/inbox/claimed。

4.3 turn/step 流程(与事件对应)

turn/start  claim next-step 输入 + 1 条 next-turn 消息  组装 systemPrompt + 工具 schema  -> agent/pre-step waterfall           reject | enter(messages)     reject 或首个 enter 被改空 -> 无 step 关轮(仍留 turn 记录)     step/start     append user/message(surfaceOp: append)     从日志派生模型历史     agent/request -> llm/stream -> assistant/chunk* -> assistant/message     tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*     step/end     工具要求再发请求,或 next-step 又来了 -> claim -> 下一个 step  -> agent/turn-stopping(serial,可 steer 续跑)turn/end

turn/*、step/*、user/message、assistant/*、tool/* 都是 durable session 事件;其余是三个域上的 live 扩展点。agent/pre-step、agent/request、agent/request-error、tools/* 是 waterfall(监听器必须 next() 委托),agent/turn-stopping 是 serial。

4.4 取消:一个 typed cause,一个每轮 AbortSignal

  • AgentCancelCause = {kind:'user'} | {kind:'parent'} | {kind:'hook', reason} | {kind:'disposed'}。
  • agent.cancel(cause) 是宽取消:默认先清 inbox(产生 canceled splices),再 abort 当前活动阶段的 controller;首个 cause 生效。keepInbox 可只中止活动轮、保留排队输入。
  • 每个 prospective turn 私有持有一个 TurnCancellation(单个 AbortController),从 inbox claim、pre-step、prompt 组装、每个 step、模型与工具执行到 agent/turn-stopping 全程传递;turn/end 发布前立即清除——终端事件观察者与随后的持久化 flush 无法取消已完成的工作。
  • 协作式取消:loop 在 await 边界前后检查中断,但不 Promise.race 抛弃 in-process 监听器/适配器/工具 Promise;忽略信号的工作必须在 whenIdle() 前自行收敛。
  • agent/cancel-requested 是 observe-only 通知(进程内、不持久),其监听器可同步排队替代工作(会被本次 clear 一并清掉),但不能 veto 停止。
  • 落盘只记粗粒度的 durable {kind:'aborted'} 结局,不记 user/parent 等运行时原因。

4.5 turn 结局分类(TurnEndReasonMap,merge-extensible)

completed / aborted{reason}(取消)/ blocked(pre-step reject)/ error{LlmFailure} / max-tokens(粘性:后续正常 step 不能降级结局)/ interrupted(持久化后端修复崩溃孤儿轮专用,loop 从不发)。max-tokens 与 RATE_LIMIT/QUOTA 等结构化失败对 goal driver 的结算至关重要(见 §6.5)。

4.6 工具调度(tool-calls.ts)

按 ctx.tools.executionMode 分组:独占调用形成屏障,并行调用走 maxParallelToolCalls 有界滚动池(默认值见 DEFAULT_MAX_PARALLEL_TOOL_CALLS,运行时可热更新,下一分组生效)。结果按模型顺序提交,tool/result 以 sourceEventSeqs 引用对应 tool/call。取消先排干已启动调用并接受其 context,再为未启动调用补合成 tool/call+tool/result 对(TOOL_ABORTED_BEFORE_DISPATCH)——日志无洞,重放合法。长程工具通过共享的 step AbortSignal(exec.signal)取消。

4.7 事件源 session(packages/core/session)

  • SessionEventMap 是 merge-extensible 的:各包 declare module '@deepseek-ai/dsh-session/types' 并入新事件类型(goal 包并入 goal/change、agent 包并入 agent/inbox/spliced…),共 45 个已知事件类型。
  • seq 恒等于 log.length(连续无洞);每条事件是 deep-frozen 的 lossless JSON;Session.append 同步热路径、不阻塞 I/O。
  • 模型可见 ⟺ 已记录:deriveMessages() 从 surface 有序节点(user/message / assistant/message / tool/result 带 surfaceOp/sourceEventSeqs)增量派生模型历史;任何新的模型可见输入必须是一个新的 session 事件。
  • SessionStore.flush(session) 是唯一 flush 入口,parallel 分发 session/flush、allSettled 后抛首个失败;loop 不在 turn 边界主动 flush(由 dsh-session-checkpoint-policy 之类的插件负责)。
  • fork(source, boundary) 禁止在开着 turn 中间 fork;持久化、resume、标题、遥测全部从这条流派生。

长程闭环的最小示例:goal 域 append goal/change → 发 scope-filtered goal/changed → goal-round-driver 监听后用 agent.followup(createUserMessage({ source: { kind: 'goal', goalId, revision, round } })) 开新轮。长程循环 = 一个带 goal 归属 source 的普通 followup turn,引擎本身零改动。


5. 同会话 Goal 领域(packages/goal/goal)

5.1 领域模型

  • GoalId(branded)、GoalRef { id, revision }(compare-and-set 栅栏:每次接受的持久变更 +1 revision)。
  • GoalSnapshot extends GoalRef:objective、phase、maxGoalRounds、blocked 时 blockedReason { code, message }。
  • GoalView extends GoalSnapshot:加 roundsStarted、createdAt、updatedAt、activation(进程内观察)。
  • GoalPhase 只有四个持久态:active | paused | blocked | complete。用量受限、轮数耗尽、模型失败、策略拒绝都是 blocked 的 reason code(round-limit / usage-limited / turn-error / max-tokens / prompt-rejected / queue-failed / model-reported…),而不是新增生命周期相位。
  • GoalActivation = armed | disarmed:进程内开关,永不持久化。

5.2 持久化:事件源 + 严格重放

  • 每次变更 append 一条 durable goal/change 事件,负载是完整的 post-mutation 快照(或 clear 的 revisioned tombstone)。session log 是唯一事实源——没有第二个数据库,也没有"人工取消记录"。
  • 严格重放 fold(fold.ts)只从 goal/change 派生生命周期状态,校验:JSON 形状(逐字段精确键集合)、revision 连续性、生命周期转移合法性、计数器、每目标单调时间戳、以及轮次只在"顺序正确、revision 匹配、未超上限"的 goal-sourced user/message 上递增。畸形当前版本记录让重放失败(fail-loud),而不是忽略或修复。
  • roundsStarted 只由已准入的正轮次推进;普通人类轮次、inbox splice 事件都不改变 goal 状态(inbox 状态与 goal 持久化解耦——2026-07-31-goal-owned-durable-events.md)。
  • 增量重放把游标停在第一个损坏事件处;每个 agent/session-start 边界都 disarm 激活。
  • ./invariant 伴生插件用独立 fold 在事件进入持久日志之前校验候选事件(internal/dispatch 预检 + session/event 提交)。

5.3 服务语义(GoalService,ctx.goals)

  • 只接受注册表里的精确 live Agent 实例(assertLive),不接受"id 相同"的对象。
  • create:没有非 complete 的当前 goal 时创建 revision-1 active goal 并 arm;defaultMaxGoalRounds 是部署配置(默认 256),create() 内部物化默认值。
  • edit:至少一个替换字段,保留 phase/blocker/activation。
  • resume:接受 paused/blocked/disarmed-active,仅当轮数预算有余量;清掉旧 blocker;arm。
  • complete/pause/block:相应相位转移 + disarm。
  • clear:留 revisioned tombstone。
  • disarm(agent):只移除进程内授权——不写事件、不改 revision、不发 goal/changed;生命周期所有者在 unload 前、持久化不确定时调用。
  • 每次接受的变更在 durable 事件提交后发 scope-filtered goal/changed,监听器失败被包含。

5.4 关键设计张力:持久 vs 授权

"重新打开 session 看到 active goal"不等于"授权自动干活"。缓存以 disarmed 起步、session-start 重新 disarm;resume、fork、driver 替换都保留目标与历史但绝不自行开工。恢复后的续跑路径只能是:人类说"继续"(任意语言)→ 模型新开一轮 → 读 get_goal → 调 update_goal(action:'resume') 记录新 revision 并 arm。字面命令路径是 /goal resume。


6. 同会话轮次驱动(packages/goal/goal-round-driver)

这是整个"自动续轮"最复杂的部分(445 行):它决定何时再开一轮模型 turn,并处理所有并发与持久化竞态。

6.1 角色与状态

  • 对每个精确 live Agent 拥有进程内调度状态(DriverState):最多一个 pending 预留(RoundAttempt)、competing 标记、checkpoint 标记、串行化 driver run。
  • 每个 RoundAttempt 记录精确身份 { goalId, revision, round } + 完整渲染 prompt + MessageId + 阶段(queued/claimed/admitted)+ cancelled/stale 标记。

6.2 预留与准入

当 agent idle、无竞争排队工作、goal 为 active + armed 时,driver 先 ctx.sessions.flush(session) 做持久化 checkpoint,await 后重查所有谓词(readyAfterCheckpoint)。若 roundsStarted 已达 maxGoalRounds,直接 block(code:'round-limit')。否则预留精确身份 + 完整 prompt,然后 agent.followup() 投递带 GoalMessageSource 的消息。

agent/pre-step waterfall 是入口围栏:goal 正源消息必须精确匹配 driver 的 pending 身份与内容、live goal 仍是该 id/revision、激活仍 armed、round 仍是下一个数。插件在委托 next() 前检查一次、下游监听器返回后再检查一次(第二道检查防止异步监听器在进入旧 prompt 期间 edit/pause 了 goal)。只有最终落成的 user/message 才算"进入的轮次"并推进 goal fold;过期预留关闭一个无 step 的 blocked turn、不计轮。

6.3 人类工作与 revision 竞态

  • 预留前已排队的普通工作 → 阻止调度。
  • 自动 prompt 待处理期间来了普通工作 → 预留标记 stale,混合批次拒绝自动提议。
  • goal 轮内发生 mutation → revision 前进;旧 revision 的结算不能覆盖该 mutation:driver 丢弃旧尝试结果、读新投影、仅在新 revision 仍 active+armed 时继续。
  • 普通工作到达被标记 competing,等 agent 再次 idle 时重新考虑延续。

6.4 结算分类表(一个 goal-owned turn 关闭后)

turn 结果
driver 动作
durable completed
若仍 active+armed 且未超上限则继续下一轮
对预留/已准入轮次的取消,或 aborted
pause + disarm
error
 且 code 为 RATE_LIMIT/QUOTA
block usage-limited
其他 error
block turn-error
max-tokens
block max-tokens
持久化 checkpoint 失败
仅 disarm,不改持久相位
disposed
 / interrupted
disarm
插件新增的未知结果
block 待查

没有一种异常结局会请求自动重试。 之后人类可任意语言要求继续,模型读停止的 goal 并 resume(新 revision + arm)。

6.5 取消与卸载

  • 每个 goal/changed 通知产生 checkpoint 义务:先 flush 再预留;turn 结束时的 flush 失败由 agent/error 上报,driver 把失败关联到精确关闭的那一轮。
  • 宽取消清 inbox、abort 活动 phase;driver 沿预留消息的 claim/discard 事件与 durable aborted turn 结算。取消无匹配 goal 尝试时只移除进程内激活;pause mutation 抛错时回退为 disarm(绝不让被取消的自动工作重启)。
  • 卸载:监听器与清理嵌套在一个有序 Cordis effect 里——先关闭准入、disarm、取消已准入尝试、await agent 与 driver 双静默,最后才注销监听器(防止并发卸载兄弟 effect 时围栏先消失)。
  • 承认一个微任务竞态:inbox 接受可能正好赢在 unload 开始前——那一轮照常持久计费,但一旦 unload 开始即被取消、不再调度后续轮,goal 保持 active+disarmed。假装没发生过会让重置计数失真。

7. 模型工具与执行期权威(packages/goal/tool-goal)

7.1 工具面

  • get_goal():返回当前 goal 或 null(含 id/revision、objective、phase、roundsStarted/maxGoalRounds、blocker、进程内 activation 观察)。
  • create_goal(objective, max_goal_rounds?):从直接人类请求推断"长程目标"意图(任意语言),禁止把一次性活变成 goal。
  • update_goal(goal_id, revision, action, ...):edit | pause | resume | complete | blocked;严格 schema 的空串/零值填充按"省略"处理;blocked_reason 只在 blocked 时必填、持久为 model-reported code。
  • 三个工具都独占执行(exclusive scheduling),保证模型批次内先读后写看到新 revision。UI 呈现是 args 的纯函数(generic read/mutation cards)。

7.2 权威校验(authority.ts,执行期、不可被 prompt 注入绕过)

goalToolExecution 要求:exec.agent 存在、是 AgentRegistry 里精确 live 的对象、状态 running、且是 ctx.agents.currentInitiator()(当前继承的 driver initiator)——同时存在一个开着的 turn(在 session 事件里倒查 turn/start 边界)。

  • create / edit / pause / resume:额外要求当前轮次里有宿主证明的直接人类消息(user/message 且 source.kind === 'user'),且调用者是运行时根代理(ctx.agents.roots(),从 live agent 图推导,不是持久 fork 祖先——被 fork 后独立 resume 的顶层会话可重新获得人类权威,live child 永远是 subagent 不可变更状态)。
  • complete / blocked:要么直接人类权威,要么精确当前 goal round(goal-sourced user/message 的 goalId/revision/round 与折叠的当前 goal 全等)。goal-round 权威只授予这两个终态报告——自动轮不能 edit/pause/resume/替换人类目标。
  • blockedAfterConsecutiveRounds(配置,默认 3):自动轮自报 blocked 前机械性要求至少这么多已准入轮次 + 非空解释;语义等价性(是不是同一个阻塞)仍是模型判断。
  • 自动轮报 complete/blocked 后,工具结果上挂一条 deferContext(wrapup.ts 的 <goal_complete>/<goal_blocked> 指令),让模型在 turn 结束前照常给人类写收尾消息(替代早先的硬停)。

7.3 与模型面的 prompt 政策

systemPrompt section tool:goal(order 114):可以任意语言推断意图、get_goal 先于 update_goal、恢复/续跑后 resume 重新 arm、只在真正达成时 complete、同一阻塞持续 ≥ 阈值才 blocked、困难/不确定不是 blocker。


8. 人类命令与 UI

8.1 /goal(packages/goal/command-goal)

Codex 形状的紧凑语法:空 /goal 显示状态;/goal <objective> 创建;/goal edit <objective> / pause / resume / clear 直接生命周期操作。控制词仅在全后缀时才是控制(/goal pause after verification 是目标文本)。不消耗模型轮次,直接经 ctx.commands 分派(human command 平面,输出不进模型历史);成功变更仍由 goal 域记录为 durable 事件(模型可见 ⟺ 已记录)。状态输出区分持久 phase 与 live activation,blocked 时展示策略 code + 人类可读原因;隐藏 branded id 与 revision。

8.2 Web UI(packages/client/ui-goal)

GoalBar 停靠在输入 dock:显示目标 glyph、phase 标签、截断的 objective,以及按状态推导的图标动作(active→pause,paused→resume,edit、clear);创建走 /goal 命令不在这里。数据来自 goal session projection(host 计算的全量快照),动词经注入的 actions(Remote 到 host GoalService)——CAS ref 由 UI 持有,快速连点不会重复提交同一 revision。

8.3 组合矩阵

组合
goal 域/驱动
模型工具
/goal
 命令
备注
base bundle
✅(默认挂载)
✅ tool-goal
✅ command-goal
tool-ralph 也在此挂载,maxRounds: 64
web-app
host 平面
gateway/Remote 平面(preset 内)
host 平面
加 ui-goal 的 GoalBar
ACP(agent-spine-demo)
可选(goals 配置,默认开)
✅
❌ 刻意省略命令平面
headless CLI / JSON-RPC
不消费命令平面
视组合
❌
一次性调用不会悄悄变成多轮操作(spine 显式 opt-in)

9. Ralph:新鲜代理循环(packages/workflow/tool-ralph)

9.1 定位

Ralph 是一个模型工具,也是一个"复杂固定执行策略可以完全作为插件组合出来"的示范:它不创建 goal 状态、不给 dsh-agent-loop 加分支、不继承父对话、不写持久轮次记录。每个 round 通过选中 provider 生成一个全新子代理(spawn:无 seed、无父上下文、保留 cwd/lineage),父子之间只传不可变目标 + 上一轮的有界结构化报告;共享工作区是权威跨轮记忆(提示明确要求子代理先检查工作区、别信叙述性交接)。

9.2 固定脚本与报告协议

RALPH_SCRIPT(部署方持有,模型只给数据)在 workflow worker 里跑:

for round in 1..maxRounds:  prompt = 不可变目标 + 轮次/上限 + 工作区即权威 + 上一轮报告  report = await agent(prompt, { schema: reportSchema })   # 结构化输出  validateReport(report)   # 语义 + 序列化大小(maxHandoffChars)  complete -> return complete  blocked  -> return blocked  continue -> previous = reportreturn budget-limited

RalphRoundReport = { status: continue|complete|blocked, summary, evidence[], nextSteps[], blocker }:

  • continue:必须有 nextSteps、blocker 为空;
  • complete:必须有 evidence、无 nextSteps、blocker 为空;
  • blocked:必须有具体 blocker。 字符串必须规范化(trim 且非空)。超限报告直接失败而非静默截断(截断可能吃掉 status/evidence 却仍像权威交接)。

脚本内校验一次,消费者在 workflow seam 另一侧再防御性解码一次(readRunResult/readReport:精确键集合、normalized 字符串、budget-limited 必须等于轮限、round-failed 必须带 lastReport)。maxRounds 默认 256 且是调用覆盖的部署上限;maxHandoffChars/maxResultChars 默认各 16384。

9.3 终止语义

  • complete/blocked:立即返回最终报告 + roundsStarted。
  • 最后一轮继续后:budget-limited。
  • 普通子失败(workflow 语言把"正常落定但不成功"的子映射为 null):返回 round-failed + 失败轮 + 最后一个成功 handoff(如有),工具报错而非误分类为畸形报告或预算耗尽;无重试策略。
  • 致命 workflow 基础设施失败(provider start、传输、worker、结果物化)走通用 workflow 错误路径。
  • 结果信封明确说"worker 报告了该结果"而不是独立认证;父 transcript 只保留一次调用 + 一个有界终态文本(截断带 … [truncated] 标记),中间子对话不进父 transcript。

9.4 与 workflow 引擎的接缝:provider 路由与子上限

WorkflowStartRequest 增加两个可选字段(workflow/src/runtime-types.ts):

  • subagentProvider:per-run provider 覆盖(override ?? 配置默认)。Ralph 要求所选 provider 已注册、capabilities.outputSchema 为真、**inheritsParentContext === false**(fork 之类的继承型 provider 在 workflow start 前就大声失败)。
  • maxTotalAgents:per-run 子代理总数上限,worker 引擎要求 ≤ 其配置 ceiling(默认 1000)并装进该 run 的 WorkerLimits。Ralph 把自己的 maxRounds 直接作为这个上限——固定循环的轮数预算与通用 runaway 子代理兜底不可能不一致。

9.5 前台与取消

Ralph 工具是前台且进程内:父工具调用等完整 run 结束;exec.signal abort → run.cancel('parent step aborted');**每条路径上都 await run.dispose()**,保证取消到达 worker 引擎的有界结算与子静默之后调用才返回。


10. 支撑设施

10.1 workflow 引擎(packages/workflow)

  • 契约层(workflow/):WorkflowEngine Service Definition、WorkflowStartRequest/WorkflowRun/WorkflowResult(result 永不 reject;stopReason 闭集 completed|cancelled|error)、WorkflowError(机器可路由 code + fatal 标志)、workflow/* 生命周期事件(start/phase/log/agent-start/agent-end/end)。
  • 执行层(workflow-worker-thread/):每个 run 在一个全新 worker 线程的 vm 上下文里执行模型写的脚本(agent/pipeline/parallel/phase/log/args 钩子);worker 是宿主循环隔离 + 可强制终止,不是安全边界(vm 里 getter/proxy 可执行,跨 realm 值按无损 JSON 物化)。启动校验全同步:meta 形状 → host 侧同 wrapper 预解析脚本体(SCRIPT_PARSE)→ provider 路由 → cap(INVALID_ARGUMENT),无效请求永不发布 run。引擎默认:provider spawn、maxConcurrentAgents 自动解析为 min(16, max(1, cores-2))、maxTotalAgents 1000(deployment ceiling)、syncTimeoutMs/disposeGraceMs 各 5000。
  • 取消双通道:post Cancel RPC(worker 端每个钩子入口抛 CANCELLED)+ 中止所有子共用的 AbortController + disposeGraceMs 宽限 timer(unref)force-settle + worker.terminate()。dispose 幂等、有界:host 立即驱动子回收(即使 worker 卡死),Promise.race 以 grace 为界,worker 线程绝不比 run 活得久。
  • workflow/agent-end 账本保证每条停止路径(正常/取消/宽限强结/worker 死亡/exit)上每个 started 子恰好一个 end、且先于 workflow/end。worker 环境被 scrub(无凭据)。
  • 模型面 workflow 工具(tool-workflow):模型自己写 script/meta/args,用于大规模 fan-out;与 Ralph 的关键差异就是脚本来源、provider 默认、maxTotalAgents 传递与防御性解码。非 completed stopReason → isError;top-level 调用向父 Session 写 tool-workflow/* 持久记录(run-end 在 quiescence 后写)。

workflow 工具 vs ralph 工具(模型面差异一览)

维度
workflow
 工具
ralph
 工具
脚本来源
模型在调用里写 script/meta/args
部署方固定 RALPH_SCRIPT,模型只给 objective + 可选 maxRounds
provider
不传 subagentProvider(引擎默认)
传 subagentProvider(默认 spawn),且要求已注册 + 结构化输出 + inheritsParentContext === false
子代理上限
不传 maxTotalAgents(引擎默认 1000)
传 maxTotalAgents: maxRounds(轮数预算 = 子总数上限)
结果处理
直接渲染脚本返回值(截断到 maxResultChars)
host 侧防御性解码(精确键集合、normalized 字符串、budget-limited 必须等于轮限、round-failed 必须带 lastReport),区分 complete/blocked/budget-limited/round-failed
语义定位
大规模 fan-out 编排
面向单一不可变目标的 fresh-agent 迭代;完成/阻塞是 worker 自报
持久记录
top-level 调用写 tool-workflow/*
无独立记录

10.2 subagent 能力(packages/subagent)

  • 能力缝:Service Definition(SubagentRuntime,ctx.subagents)+ 多个命名 Provider 共存(spawn / fork / ACP / Codex / Claude Code / dsh-sdk,类似 LLM adapter 注册表)+ Consumer(subagent、subagent_fork、send_message/interrupt_agent/list_agents、子作用域 report)。
  • SubagentCapabilities = { outputSchema, depthLimit, toolFilter, persona }(types.ts:86):start-time 特性声明,服务在 start 前逐项校验,缺能力就 UNSUPPORTED_CAPABILITY 抛错,绝不 accept-then-ignore(out-of-process.ts 的四个远程 provider 全部 NO_START_CAPABILITIES,带 outputSchema 的请求直接拒绝)。
  • SubagentProvider.inheritsParentContext(types.ts:295)与实现差异: spawn(subagent-spawn-in-process)= false:无 seed,全新子会话;prepareContinuable() 返回 {};保留父 cwd/lineage/深度。 fork(subagent-fork-in-process)= true:seed = 父会话最后一个 turn/end 之前的平衡前缀(completedTurnPrefix:当前进行中工具轮不平衡、不可作为合法子会话重放);fork 前缀成为子自身持久 transcript 的一部分。fork 保持 one-shot 定位(2026-08-10-fork-children-stay-one-shot;前缀一次性捕获、续聊复用仍是 TODO)。 两者共用 startInProcessRun(subagent-in-process-driver),子 header 继承 cwd/parentSession/origin/delegationDepth/seedLength/preset;delegationDepth 持久化且单调(resume 的 child 不会被重算成顶层)。
  • 结构化输出链:服务层 assertObjectJsonSchema(子集)→ capability 门控 → in-process 驱动在子创建窗口装一个子作用域的 structured_output 工具(参数即真实 schema)+ 尾部提示段 + 单调终结守卫 + 两阶段提交(staged → 权威 tools/result 成功才 captured)→ SubagentResult.structured。completed 但无捕获 = 子失败。
  • continuable 子代理(continuation.ts,现行设计 2026-07-28-continuable-subagent-conversations):弃用"每 activation 一个 Task"模型,改为 一个持久 Session + 至多一个进程内 Activation(residency epoch);Agent inbox 是唯一 FIFO 队列,无第二个队列。prepareContinuable 方法存在即能力,只返回 detached spec({ seed? })。 followup(send_message 的薄适配器)按 residency 路由:running 入队 / waiting 唤醒 / 无 Activation 冷恢复(coldResume:persistence.inspect → 授权校验 → foldSubagentDescriptor(只认子自身后缀的 continuable 描述符,不经过 provider,descriptor 是全部重建输入)→ agents.resume())。只接受精确 live 的直接父授权。 interrupt(interrupt_agent 工具):同步授权 + 异步生效——Agent.cancel(cause, { keepInbox: true }),只停当前 turn,保留未认领 inbox 工作/Activation/已发布后代;目标不存在是接受的无操作。 完成通知:continuation manager 自己发 notifySettlement(不是外部 subagent/end 监听器)——对每个拿到过 child id 的 child 无条件发送(含 token 上限/模型失败/取消/teardown 等 child 来不及主动 report 的结局),在释放所有权之前投递;措辞区分"已完成将不再工作除非你再发消息";idle 父 → followup(普通 later turn),busy 父 → steer(多个 child 同时 settle 只花一个 step),draining 父 → inject(不唤醒);投递失败仅记日志、绝不阻塞 disposal。 后台默认:continuable 模式省略 run_in_background 即后台(立即返回 durable child id,无 Task),显式 false 才前台;one-shot 模式默认前台、显式后台走通用 Jobs Task(settleRun 映射 stop reason:completed→completed、aborted→killed、error/max-tokens/refusal→failed;无 readOutput,中间详情留在子会话)。 list_agents 只向模型投影 continuable 子(one-shot 不可被 send_message 续聊),status 用 live registry 细化为 running/idle/ready(无 live Agent = ready,可 resume 但非终态)。
  • 子会话持久化:每个 in-process 子都是普通持久 Session(header origin:'subagent');身份由 versioned、model-hidden 的 subagent/descriptor 事件记录(log-only、无 surfaceOp、跨 compaction 保留);父侧经 scoped 事件 + subagent/subagentTiming 两个 projection unit + listChildren 三级身份阶梯(live registry → projection-cache 行 → persistence.inspect 冷读)观察,全程不加载/恢复 Agent。
  • 相关决策记录:2026-07-08-background-subagent-tasks(one-shot 后台复用通用 Jobs)、2026-07-28-continuable-subagent-conversations(现行:持久 Session + Activation)、2026-08-06-continuable-subagent-interrupt、2026-08-11-background-first-continuable-delegation(后台优先 + 完成唤醒 idle parent)等。

小结(与"长程任务"的关系):subagent 体系给出"后台长程任务"的两条正交路径——one-shot 后台 = 通用 Jobs Task(结果/取消/通知由 job 体系承担);continuable 后台 = 无 Task 的持久 Session + 进程内 Activation(send_message 续聊、interrupt 只停当前 turn、settlement notice 由 manager 无条件投递、list_agents 只投影 continuable 子)。continuable 是"长程对话"的正主:子会话持久化到 session log、身份经 projection 折叠、冷恢复不依赖 provider(descriptor 是全部重建输入),天然支撑跨 turn/跨进程恢复。它是 Ralph 与 workflow 子代理的执行底座(agent(prompt) 的 child 都由这些 provider 启动),但不是 goal 的延续机制——goal 在同一会话内复用父 agent 本体。

10.3 相邻但不同的机制

  • schedule(packages/schedule):未来根代理的持久提醒(after_seconds / 绝对 at / ≥5 分钟的 every_seconds),状态在 session log(schedule/change),到点经 runMaintenance() + followup() 开一个普通后续 turn。它不是 goal、不是 Ralph、也不是通用 /loop cron——但补上了"时间触发"这个第三策略的一角。
  • jobs(packages/jobs):宿主平面后台任务注册表 + job_* 工具(与 goal/ralph 不同,jobs 是宿主级、跨会话的)。
  • compaction(packages/compaction):用 surfaceOp: replace 折叠无关节点;goal 历史作为普通 session 数据可被保留。
  • todo(packages/todo):todo/write 全量快照,纯 UI 状态,不进模型历史派生。

11. 设计原则提炼(从代码里读出来的规律)

  1. Everything is a plugin,改插件不改 loop:goal、Ralph、schedule 全部是挂在公共扩展点(agent/* 事件、ctx.tools、ctx.workflowEngine、ctx.subagents)上的插件;核心 loop 的唯一新增是通用的 agent/cancel-requested 观察通知。docs/architecture.md 明确:改 loop 必须更新架构文档。
  2. 模型可见 ⟺ 已记录:任何到达模型请求的输入都能从 session log 重建。goal/change 全量快照、agent/inbox/spliced、tool/call+result 无洞取消都是这条规则的实例化。
  3. 持久状态与执行授权分离:durable phase 记录"发生了什么",进程内 activation 决定"现在能不能自动干活"。恢复/续跑绝不自动开工。
  4. 执行期权威,不靠 prompt:人类消息是宿主 attestation(source.kind),live 根代理 + 开着的 turn + 精确 revision 是运行时校验,prompt 注入伪造不了。
  5. Compare-and-set 一切可变的跨边界身份:GoalRef、revision、RoundIdentity(goalId/revision/round)、子 callId——旧结算不能覆盖新状态。
  6. 预算慷慨但受部署约束,且只数一种东西:maxGoalRounds/maxRounds 只数已准入轮次,不管 token/金额/墙钟/提供商用量;blocked 用一个 reason code 承载所有停止原因。
  7. 显式信任边界:完成/阻塞是模型或 worker 的声明,不是独立评估/认证;信封措辞与 prompt 政策都明说这一点(独立 evaluator 被明确推迟)。
  8. 失败保守、可观察:没有自动重试(异常结局只停 + 等人类 resume);持久化不确定就 disarm 而不是继续;畸形/超限交接直接失败而非截断。
  9. 有界 quiescence:取消/卸载/子回收全部有界结算,whenIdle()/dispose()/grace timer 是收敛手段,绝不无限等。
  10. 来源克制:外部产品(Codex 的 goal UX、Claude Code 的 goal 政策)是比较对象,不是兼容目标;实现遵循仓库自己的插件、事件源、显式默认解析规则。

12. 已知限制与未决工作(来自决策记录原文)

  • 无独立评估:同会话完成/阻塞与 Ralph 终态都是模型/worker 声明;独立 evaluator、完成证书、对抗性验证器、criteria/executor/isolation 契约全部推迟。
  • 无聚合预算:只有轮数上限;token、货币、墙钟、提供商用量、每轮价格准入政策缺失。
  • 无持久自动运行器:goal 事实持久但激活/调度进程内,重启后等人类输入;Ralph 前台、进程丢失不可恢复;后台收集、重启恢复、无人值守常驻执行推迟。
  • 无时间调度器(完整版):interval /loop、cron、主动维护、云端/桌面调度在 goal 决策之外(schedule 包只覆盖提醒)。
  • 无通用 loop 日志 / 执行世界回卷:session 重放重建 goal 历史,不重建文件/进程/环境/凭据/外部副作用。
  • 无 goal reflector:concern 事件、自动无进展启发、独立反射器改目标、卡死模式检测、loop_split 未实现。
  • Ralph 政策窄:一轮一子;轮内 fan-out、evaluator/worker 角色分离、动态 provider/model 选择、结构化的递归 Ralph 禁用需要独立政策 API(现在只是 prompt 引导)。
  • Ralph 不重试失败子:普通失败保留失败轮 + 最后好 handoff;致命基础设施失败可能早于该状态可用。
  • 可移植 UI 有限:TUI 是纯文本状态 + generic Ralph card;ACP/headless/JSON-RPC 没有连续状态 widget、可重连命令输出、模态 goal 编辑器。

13. 验证与测试覆盖(证据概览)

  • goal 域:创建默认值、exact-live-agent 校验、CAS 拒绝、每个生命周期转移、blocker 校验与保留、resume 上限执行、clear/替换、seed 重放与 SessionStore.fork() 继承、session-start 与生命周期所有者 disarm、active 重 arm、durable 事件折叠、inbox 独立性、稳定损坏事件重放、顺序轮次归属;keyless Loader/stdio 进程测试外部读取持久 JSONL 验证 goal 记录与"创建不自动开轮"。
  • 轮次驱动:真实 agent loop + 脚本化模型:顺序准入与上限执行、load/resume 惰性、每个结局分类、限速/请求错误/max-tokens、下游 prompt veto、准入前与在途取消、无关人类取消、failed-pause 回退、人类输入顺序、排队与下游 revision 竞态、伪造 goal 归属、失败 mutation/turn checkpoint(含后来的一次性注入)、scheduler/自定义 agent 失败、session-start 重置、精确生命周期退役、排队/运行中插件 teardown;keyless ACP snapshot(同一会话、round sources 1,2、取消第二个停滞轮记录 durable pause)。
  • 模型工具:注册/独占/提示政策、填充安全 generic 呈现、非英文直接人类创建、exact/stale/non-running agent 与 driver 检查、live-child 拒绝、resumed-fork 根权威、steering、initiator 不匹配、严格 schema 填充、条件 blocker 解释、session-start 后重 arm、authority-before-conditional-arguments、精确 goal-round complete、自动轮仅终态、配置的阻塞阈值、人类立即阻塞;keyless headless replay snapshot 走真实 loop + 持久化栈并外部检查 goal change。
  • Ralph:config 与调用上限解析、provider 能力拒绝、固定 start-request 路由与子 ceiling、全部成功终态、普通子失败信封、畸形/超限边界值、精确成功结果截断、abort 时序、dispose、呈现意图、prompt 生命周期、HMR;真实栈集成(worker-thread 引擎 + spawn provider + 结构化输出 + agent loop):不同子身份、无 seed、继承 cwd、无父历史标记、上一轮报告只在下一轮出现、一轮 phase 事件、终态 complete、两子 dispose;headless snapshot:两轮、无 seed 子会话、round-one handoff 只出现在 round-two。
  • workflow 引擎:provider 路由覆盖与回退、per-run ceiling ≤ 部署 ceiling、取消(worker 钩子全抛 CANCELLED / 排队 waiter 拒绝 / grace force-settle + terminate / 完成 vs 取消竞速取消胜)、dispose 幂等有界、worker 死亡 first-wins、agent-end 恰好一次、realm 物化(__proto__ 防污染、环/稀疏/符号拒绝)、监听器隔离。
  • invariant 伴生:goal 流、workflow 事件配对等运行时契约在事件进入日志前/后双重校验。

14. 参考文件索引

决策记录(Agent Notes,.agents/notes/implemented/)

  • feature/2026-07-16-harness-level-loop.md — 总决策:两种显式策略,拒绝通用 loop
  • feature/2026-07-19-persisted-same-session-goal-domain.md — goal 域
  • feature/2026-07-19-same-session-goal-round-driver.md — 轮次驱动
  • feature/2026-07-19-model-facing-goal-tools.md — 模型工具与权威
  • feature/2026-07-19-human-goal-command.md — /goal 命令与组合
  • feature/2026-07-19-fresh-agent-ralph-workflow-tool.md — Ralph
  • architecture/2026-07-31-goal-owned-durable-events.md — goal/change 独立于 inbox
  • architecture/2026-07-16-explicit-turn-cancellation.md — 每轮 AbortSignal 与取消契约
  • architecture/2026-07-15-agent-initiator-scope.md、2026-06-18-agent-lifecycle-and-ownership-contracts.md — agent 归属/initiator
  • 后台/延续子代理系列(2026-07-08、07-21、07-28、08-06、08-11)
  • feature/2026-07-05-dynamic-workflows.md、feature/2026-08-10-durable-workflow-runs-in-chat.md

源码

  • packages/core/agent-loop/src/{index,agent,tool-calls}.ts
  • packages/core/agent/src/{index,inbox,dispatch,runtime-types}.ts
  • packages/core/session/src/{index,types}.ts
  • packages/goal/goal/src/{index,domain,fold,types,runtime,invariant}.ts
  • packages/goal/goal-round-driver/src/{index,prompt,invariant}.ts
  • packages/goal/tool-goal/src/{index,authority,wrapup}.ts
  • packages/goal/command-goal/src/index.ts
  • packages/workflow/tool-ralph/src/index.ts
  • packages/workflow/workflow/src/{index,types,runtime-types}.ts
  • packages/workflow/workflow-worker-thread/src/(index/runtime/host/protocol/realm/meta/session/worker)
  • packages/subagent/subagent/src/{index,types,continuation}.ts、subagent-spawn-in-process、subagent-fork-in-process
  • packages/schedule/schedule/src/、packages/jobs/
  • packages/client/ui-goal/src/client/GoalBar.tsx

文档

  • docs/architecture.md(turn flow、事件、扩展点表)、docs/glossary.md(loop hierarchy 词条)、docs/subsystems/goal.md(生成目录)、docs/config-catalog.md(goal/ralph 配置项)、docs/tool-catalog.md

附:一次完整的长程任务生命周期(同会话 goal)

  1. 人类在 TUI/Web 输入 创建一个目标:xxx(或 /goal xxx)。
  2. 模型在 turn 内调 create_goal → GoalService 校验直接人类权威 → append goal/change(active, revision 1, rounds 0, armed)→ 发 goal/changed。
  3. driver 收到通知 → checkpoint flush → 重查谓词 → 预留 round 1 → followup(<goal_round> 目标…)。
  4. 引擎 claim 该消息 → pre-step 围栏双重校验 → 进入 goal-sourced user/message(fold 计 round 1)→ 模型干活(若干 step)。
  5. 模型判断没做完 → 正常 completed 关轮 → driver 见 active+armed 未超上限 → 再来一轮(round 2…)。
  6. 模型判断做完 → update_goal(action:'complete')(goal-round 权威)→ append goal/change(complete, disarmed)→ deferContext 收尾指令 → 模型给人类写总结 → turn 正常结束,driver 不再续轮。
  7. 中途人类点暂停//goal pause 或模型遇 RATE_LIMIT → 相应 pause/blocked + disarm;人类说"继续" → 模型 resume 重新 arm,从断点继续。
  8. 进程重启后重开 session:goal 仍 active(重放 goal/change)但 disarmed——模型看到的是"目标在、未授权",直到人类再次表达继续意图。

(若换 Ralph:1′. 模型调 ralph({ objective }) → 前台 workflow 每轮 spawn 一个无 seed 子代理 → 工作区 + 上一轮报告跨轮 → complete/blocked/budget-limited 返回一个父面结果,中间子对话不进父 transcript。)

 
打赏
 
更多>同类资讯
0 条相关评论

推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  皖ICP备20008329号-18
Powered By DESTOON