Harness-level goal-based execution
Harness 层目标式执行
The concrete agent loop owns one turn: it drains admitted input, performs one or more model-and-tool steps, and stops. Substantial objectives often need an outer policy that can begin another turn, retain progress, stop at a budget, and remain intelligible to humans. A timed prompt, a same-session continuation, and a fresh-agent Ralph attempt all repeat work, but they do not share the same state, authority, memory, o
English
Problem
The concrete agent loop owns one turn: it drains admitted input, performs one or more model-and-tool steps, and stops. Substantial objectives often need an outer policy that can begin another turn, retain progress, stop at a budget, and remain intelligible to humans. A timed prompt, a same-session continuation, and a fresh-agent Ralph attempt all repeat work, but they do not share the same state, authority, memory, or lifecycle.
Treating every repeated action as one generic “loop” obscures those differences. Same-session work must persist the human objective in the existing transcript while preserving conversation context. Ralph work must intentionally discard conversation context and use the workspace plus a bounded handoff. Human-facing status must not imply that reopening a session silently authorizes more work. Completion and blocker claims also need an explicit trust boundary rather than being smuggled into a scheduler abstraction.
The repository therefore needs goal-based execution above the turn/step loop, but it does not need a speculative universal loop service that combines persistence, evaluation, budgeting, scheduling, handoff, background jobs, and UI.
Decision
Two explicit plugin policies over existing seams:
- Same-session goals retain one durable objective in the current session and admit goal-attributed continuation turns only while live activation is armed.
- Fresh-agent Ralph runs execute a fixed foreground workflow whose rounds each spawn a new structured child with no conversation seed.
There is no packages/loop/ family, LoopDriver, LoopId, universal StopCondition, or model-facing generic loop tool. The two policies share the repository's ordinary agent, session, tools, workflow, subagent, and UI extension points, but they do not pretend that one lifecycle fits both.
Vocabulary and policy boundary
The same-session hierarchy is Goal → Goal Round → Turn → Step. A goal round is one continuation cycle admitted for the current goal and materialized as one goal-sourced turn. Human or unrelated turns in the same session do not consume the goal-round cap, and a turn may still contain multiple model/tool steps.
The fresh-agent hierarchy is Ralph Run → Ralph Round → fresh child Turn → Step. One Ralph round creates one child session. The parent transcript and prior child transcripts are not seed context; the shared workspace and one bounded structured report carry cross-round state.
“Round” is therefore an outer policy iteration, not a synonym for every session turn. The concrete dsh-agent-loop remains the turn/step engine. The same-session driver uses public agent and session events; its only core addition is the generic observe-before-cancel agent/cancel-requested notification needed by any lifecycle policy that must settle cancellation safely.
Time-based /loop or scheduled execution is a third policy and is not implemented by this decision. It belongs with a scheduler rather than either goal family.
Package topology and owning verbs
| Package | Repository category | Owned structures and verbs |
|---|---|---|
@deepseek-ai/dsh-goal | packages/goal/goal/, domain service | Owns GoalId, compare-and-set GoalRef, GoalSnapshot, four-state GoalPhase, structured GoalBlockReason, process-local GoalActivation, replay folding, and get, create, edit, pause, resume, complete, block, clear, and disarm verbs. |
@deepseek-ai/dsh-tool-goal | packages/goal/tool-goal/, model-facing consumer | Registers exclusive get_goal, create_goal, and update_goal; requires a direct human message in a live root-agent turn and narrows autonomous-round authority to completion or blocking reports with machine-routable reason codes. |
@deepseek-ai/dsh-goal-round-driver | packages/goal/goal-round-driver/, continuation policy | Reserves, fences, admits, attributes, settles, cancels, and quiescently drains same-session goal rounds without importing the concrete loop. |
@deepseek-ai/dsh-commands | packages/interaction/commands/, UI registry | Owns CommandDefinition, discovery, scoped registration, direct dispatch, CommandResult, and request cancellation for human-only commands. |
@deepseek-ai/dsh-command-goal | packages/goal/command-goal/, human-command producer | Registers /goal status, creation, edit, pause, resume, and clear over the goal domain for TUI. |
@deepseek-ai/dsh-tool-ralph | packages/workflow/tool-ralph/, fixed workflow consumer | Registers ralph({ objective, maxRounds? }), validates the fresh structured provider and bounded RalphRoundReport, and returns complete, blocked, or budget-limited. |
The detailed contracts live in the goal-domain, goal-owned event, model goal-tools, goal-round driver, command registry, human goal-command, and Ralph workflow-tool Agent Notes.
Durable goal state and live authority
One session has at most one current goal. Every mutation commits through a durable goal/change event carrying a full versioned snapshot or revisioned clear tombstone; inbox state does not participate. The session log is the only durable source of truth, so normal persistence, resume, and SessionStore.fork() carry the goal without a second database or an artificial cancellation record.
Durable phases are only active, paused, blocked, and complete. A blocked goal carries a required GoalBlockReason with a stable lower-kebab-case code and a non-empty human-readable message; usage limits, round exhaustion, model failures, and policy rejection are reason codes rather than extra lifecycle phases. Separate activation is armed or disarmed and is never persisted. Creation and explicit resume arm a goal; stop transitions, session start, fork replay, driver replacement, and driver teardown leave it disarmed.
This separation makes session restoration observable and unsurprising. Reopening a session never starts goal work by itself. A later human prompt such as “continue”, “resume the goal”, or an equivalent request in any language gives the runtime-root model a new turn in which it may read the goal and call update_goal(..., action: 'resume'). /goal resume is the direct human-command path. The runtime authenticates that the request came from a live direct-human turn; prompt policy lets the model interpret whether the wording semantically authorizes creation or resumption.
Forked sessions inherit the durable goal prefix because that is the natural replay result. The fork starts disarmed, so inheritance does not imply execution authority and no synthetic goal cancellation is inserted into history.
defaultMaxGoalRounds is configurable and defaults to 256. The cap counts only admitted goal rounds. blockedAfterConsecutiveRounds is separately configurable in the model-tool policy and defaults to 3; it is a mechanical lower bound before an autonomous round may report a repeated blocker, not an evaluator of semantic sameness.
Same-session continuation
The goal-round driver owns at most one pending reservation per exact live agent. It admits a reservation only when the goal is active and armed, the agent is idle, no competing human work exists, the latest mutation has passed its durability checkpoint, the exact goal id/revision/round still matches, and downstream pre-step policy accepts it. Its agent/pre-step fence checks those facts both before and after downstream listeners, preventing an edit, pause, human message, or unload race from admitting obsolete work.
Only an admitted positive-round goal-sourced user/message charges a round. A stale reservation closes a blocked no-step turn without consuming the cap. A concurrent goal revision wins over settlement from an older round.
Normal turn completion schedules another round only while the goal remains active, armed, and below its cap. Cancellation pauses. Rate limiting or quota exhaustion blocks with code usage-limited; cap exhaustion blocks with round-limit; queue failure uses queue-failed; turn errors, max-token stops, policy rejection, and unknown terminal results use their corresponding blocker codes. An independently composed request-recovery plugin may retry transient provider failures within that same turn; the goal driver never invents another round after an abnormal terminal outcome. A human can later authorize resume through ordinary language or /goal resume.
Human and model interactions
The human UX follows the compact Codex shape in the public OpenAI Codex TUI dispatcher at commit 678157a: /goal shows status, /goal <objective> creates, and edit, pause, resume, or clear perform direct lifecycle actions. The commit permalink keeps the researched grammar verifiable as Codex evolves. Status includes durable phase, admitted/capped rounds, and live armed/disarmed activation. Direct status and command output do not enter model history; accepted domain mutations remain reconstructable because the goal service records them.
The model receives only get_goal, create_goal, and update_goal. It may create a goal when a direct human request clearly asks for substantial multi-round work, and it may infer that intent in any language. It must not turn routine one-turn work into a goal. Code requires a direct human message in the current live root-agent turn; semantic interpretation remains model judgment. An autonomous goal round may report complete or blocked for the exact current goal round but cannot edit, pause, resume, or replace the human objective.
TUI mounts the shared command registry and complete goal stack by default and exposes /goal through one producer. ACP mounts the goal domain, model tools, and same-session driver but deliberately omits the human command plane. Every effective registered command is discoverable and invocable through every composed command adapter; a plugin incompatible with an application omits its command producer from that composition rather than relying on registry-level surface masks. The UI-less agent spine is opt-in so one-shot callers do not silently become multi-round operations. The headless CLI and JSON-RPC entry points do not consume the command plane; ordinary human text can still authorize model goal tools when that stack is composed.
Fresh-agent Ralph execution
Ralph is a first-class model tool in its own plugin, demonstrating that a sophisticated fixed execution policy can be composed without a new loop core. The plugin owns a fixed workflow script over ctx.workflowEngine and ctx.subagents; it does not create session-goal state or add a branch to dsh-agent-loop.
Each round uses an explicit WorkflowStartRequest.subagentProvider, defaulting to spawn. The provider must exist, support structured output, and declare that it does not inherit parent context. Ralph also passes its resolved round cap as WorkflowStartRequest.maxTotalAgents; the worker engine validates both per-run policies before publishing work, so provider misconfiguration or an engine ceiling below the requested Ralph scale fails before a run exists. The child inherits cwd and lineage but receives only the immutable objective, round/cap, workspace-as-authority instruction, and previous normalized report.
A report contains status, summary, evidence, next steps, and blocker text. Status-specific invariants and serialized size are validated inside the fixed script and again at the consumer boundary. maxRounds is configurable, defaults to 256, and is the ceiling for a call override. maxHandoffChars defaults to 16384; oversized reports fail rather than being silently truncated. maxResultChars separately defaults to 16384 and bounds the complete successful parent-facing text, including its envelope and truncation marker.
An ordinary child failure ends the run without retry. The fixed script reports the failed round and last successful handoff when one exists, and the tool returns that state as an error instead of misclassifying it as a malformed report or budget exhaustion. Fatal workflow infrastructure failures can settle before the script returns that state; richer reason transport and retry policy remain deferred.
The tool is foreground and process-local. The parent tool call waits for the terminal result, propagates cancellation into the worker engine, and awaits run.dispose() so child work is quiescent before return. The model sees one call and one bounded successful terminal result or an error; completion and blocker envelopes explicitly say that a worker reported the outcome rather than presenting it as independent certification. Intermediate child conversations remain outside the parent transcript.
External design lineage
Codex provides the minimal observable goal UX used here: a persistent chat-attached target with set, view, edit, pause, resume, and clear controls. This implementation adopts that discoverability while using this repository's event-sourced goal record, plugin scopes, and runtime authority checks.
Current Claude Code goals reinforce the distinction between a goal that starts another turn after the previous turn and a timed /loop. Claude Code also uses a separate small-model evaluator after each turn. This implementation adopts the policy distinction but intentionally does not copy that evaluator: evaluator inputs, tool access, deterministic checks, provider choice, isolation, and authority need a separately designed plugin contract rather than an implicit self-certification layer.
External products are comparators, not compatibility targets. The local source studies informed the boundaries, while the shipped interfaces follow this repository's “everything is a plugin”, model-visible-is-logged, explicit default resolution, and quiescent teardown rules.
Verification
The six owning Agent Notes record unit, integration, process, snapshot, cancellation, replay, and built-runtime coverage. The stack exercises strict goal-record folding, compare-and-set races, session fork inheritance, disarmed restoration, natural-language direct-human authority, configurable caps and blocked thresholds, exact goal-round attribution, adapter-wide command discovery, and transcript isolation. Shipped keyless snapshots cover model goal creation/inspection through the headless app, multi-round same-session lifecycle and cancellation through ACP, and two real Ralph rounds through the headless app; focused command tests pin direct /goal status without a model turn. The Ralph snapshot boots the worker-thread engine, spawn provider, structured-output runtime, and agent loop, then inspects distinct unseeded child logs and exact one-way bounded handoff while pinning the parent stream. Focused real-stack tests additionally cover completion, blocker and round-limit outcomes, malformed and oversized reports, ordinary child failure with the last good handoff, one phase event, and cancellation to child quiescence. Package sources remain under the repository's per-file 100% coverage gate, and built-binary tests cover installed-artifact resolution. The implementation experience is recorded in the root testing policy: every non-trivial model- or human-visible change must carry a real-example keyless snapshot in the same PR rather than relying on package-only or mock-only fixture coverage.
Alternatives considered
- Implement the original universal loop capability — rejected because
Evaluator,BudgetPolicy,RoundHandoff,GoalReflector, background job ownership, persistence, and scheduling do not form one coherent mandatory abstraction. Building all of them before their first concrete consumers would create broad speculative surface and duplicate existing session, workflow, subagent, and task machinery. - Implement only same-session goals — rejected because fresh-context iteration is materially different and is a valuable demonstration of the plugin architecture. Ralph belongs as a fixed workflow consumer with explicit context reset.
- Put Ralph inside the goal-round driver — rejected because same-session goals deliberately preserve one conversation while Ralph deliberately removes it. Combining them would make activation, replay, handoff, and UI state ambiguous.
- Treat a fork as a fresh Ralph child — rejected because a fork carries a conversation prefix. Fresh children plus workspace state and one explicit report are easier to bound and replay without a synthetic cancel record.
- Copy Claude Code's evaluator into the first goal implementation — rejected because a transcript-only model evaluator is one useful policy, not a generally trustworthy completion certificate. Deterministic evaluation and isolation must remain possible, so the evaluator is deferred until its authority and provider contract are designed.
- Automatically continue after session restore — rejected because opening a session is observation, not authority to spend resources. Durable state is restored while activation waits for a new human prompt.
- Route
/goalthrough the model — rejected because status and explicit lifecycle controls should be deterministic, token-free UI actions; ordinary natural-language prompts remain the semantic model path. - Modify the concrete agent loop with goal or Ralph modes — rejected because public queue, prompt, session, cancellation, workflow, and subagent seams already support both policies. The generic cancel-requested observation is the only core coordination addition.
Consequences
- Goal-based execution ships without one overloaded “loop” object: same-session continuation and fresh-agent iteration have explicit, separately testable contracts.
- Durable goal history is replayable and forkable, while process-local activation prevents accidental work on resume.
- Humans receive a small Codex-shaped UX; models receive a compact tool set whose mutating calls require a direct human message in the current live root-agent turn; deployments can remove either independently.
- Ralph demonstrates a nontrivial fixed policy entirely as a plugin over existing workflow and subagent primitives.
- Round limits are generous by default but remain deployment-controlled. They bound iterations, not tokens, price, elapsed time, or external side effects.
- The original proposal's evaluator, budget, reflector, background-job, CLI, and generic loop-session architecture is intentionally not part of the implemented public API.
Known limitations and deferred work
- Independent evaluation — same-session completion/blocking and Ralph terminal status are model or worker declarations. A separate evaluator, evaluator-driven feedback round, completion certificate, deterministic checker, adversarial verifier, and criteria/executor/isolation contract remain deferred.
- Aggregate budgets —
maxGoalRoundsand RalphmaxRoundsare the only aggregate effort limits. Token, currency, elapsed-time, provider-usage, and per-round price admission policies are absent. - No persistent autonomous runner — same-session goal facts persist, but activation and scheduling are process-local and deliberately wait for human input after restore. Ralph runs are foreground and cannot resume after process loss. Background collection, restart recovery, and unattended resident execution are deferred.
- No time scheduler — interval
/loop, cron, proactive maintenance, and cloud or desktop scheduling are outside this decision. - No generic loop journal or execution-world rewind — session replay reconstructs goal history, not prior files, processes, environment, credentials, or external side effects. Ralph treats the current workspace as authority and carries no cross-run journal.
- No goal reflector — concern events, automatic no-progress heuristics, goal revision by an independent reflector, stuck-pattern detection, and
loop_splitare not implemented. Humans can edit, pause, clear, or resume the goal directly. - Ralph policy remains narrow — one round creates one fresh child; within-round fan-out, evaluator/worker role separation, dynamic provider/model selection, and structural recursive-Ralph tool denial need separate policy APIs. Prompt guidance is not enforcement.
- Ralph does not retry a failed child — an ordinary failure preserves the failed round and last good handoff, while fatal workflow infrastructure failures can end before that state is available. Retry count, backoff, and richer failure transport need separate policy and boundary design.
- Portable UI remains modest — TUI renders plain-text goal status and generic Ralph cards. ACP carries only committed assistant text; there is no continuous status widget, reconnectable command output, modal goal editor, or command plane in ACP, the headless CLI, or JSON-RPC.
中文
问题
具体 agent loop(智能体循环)只拥有一个轮次:它排空已接纳输入,执行一个或多个模型与工具步骤,然后停止。大型目标通常需要一项外层策略来开始另一个轮次、保留进度、在达到预算上限时停止,并让人类能够理解其状态。定时提示词、同会话续行和全新 agent Ralph 尝试都会重复工作,但它们并不共享相同的状态、权限、记忆或生命周期。
若把每种重复动作都称为一个通用「loop」,就会掩盖这些差异。同会话工作必须在现有 transcript(文本记录)中持久化人类目标,同时保留对话上下文。Ralph 工作必须有意丢弃对话上下文,只使用工作区和一份有界交接。面向人类的状态不能暗示重新打开会话就会静默授权更多工作。完成与阻塞声明也需要显式信任边界,而不能被偷渡进调度器抽象。
因此,本仓库需要位于轮次/步骤 loop 之上的目标式执行,但不需要一个把持久化、评估、预算、调度、交接、后台任务和 UI 组合在一起的推测性通用 loop 服务。
决策
构建在现有 seam 之上的两项显式插件策略:
- 同会话目标在当前会话中保留一个持久目标,并且只在实时激活态已激活时接纳带目标归属的续行轮次。
- 全新 agent Ralph 运行执行一个固定前台工作流,其中每个 Round 都生成一个不带对话种子的全新结构化子 agent。
系统中没有 packages/loop/ 包族、LoopDriver、LoopId、通用 StopCondition 或面向模型的通用 loop 工具。两项策略共享本仓库普通的 agent、会话、工具、工作流、subagent 与 UI 扩展点,但不会假装一种生命周期可以同时适配两者。
词汇与策略边界
同会话层级是 Goal → Goal Round → 轮次 → 步骤。一个 Goal Round 是为当前目标接纳的一次续行周期,并实体化为一个带目标来源的轮次。同一会话中的人类轮次或无关轮次不会消耗 Goal Round 上限,而一个轮次仍可包含多个模型/工具步骤。
全新 agent 层级是 Ralph Run → Ralph Round → 全新子 agent 轮次 → 步骤。一个 Ralph Round 创建一个子会话。父 transcript 和此前子 transcript 都不是种子上下文;共享工作区与一份有界结构化报告承载跨 Round 状态。
因此,「Round」是外层策略迭代,不是每个会话轮次的同义词。具体 dsh-agent-loop 仍是轮次/步骤引擎。同会话驱动器使用公开 agent 与会话事件;它对核心唯一的新增项是通用的取消前观察通知 agent/cancel-requested,任何需要安全收敛取消的生命周期策略都可以使用它。
基于时间的 /loop 或定时执行是第三种策略,本决策不实现它。它应归属于调度器,而不是任一目标包族。
包拓扑与所属动词
| 包 | 仓库类别 | 所属结构与动词 |
|---|---|---|
@deepseek-ai/dsh-goal | packages/goal/goal/,领域服务 | 拥有 GoalId、比较并交换 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;要求实时根 agent 轮次中有一条人类直接发送的消息,并把自治 Round 权限收窄到带机器可路由原因代码的完成或阻塞报告。 |
@deepseek-ai/dsh-goal-round-driver | packages/goal/goal-round-driver/,续行策略 | 在不导入具体 loop 的情况下,预留、设围栏、接纳、归属、结算、取消并排空同会话 Goal Round,直至完全停稳。 |
@deepseek-ai/dsh-commands | packages/interaction/commands/,UI 注册表 | 拥有面向人类专用命令的 CommandDefinition、发现、作用域注册、直接分发、CommandResult 与请求取消。 |
@deepseek-ai/dsh-command-goal | packages/goal/command-goal/,人类命令生产方 | 为 TUI 注册构建在目标领域之上的 /goal 状态、创建、编辑、暂停、恢复与清除。 |
@deepseek-ai/dsh-tool-ralph | packages/workflow/tool-ralph/,固定工作流消费方 | 注册 ralph({ objective, maxRounds? }),验证全新结构化提供方与有界 RalphRoundReport,并返回 complete、blocked 或 budget-limited。 |
详细约定见目标领域、目标自有事件、模型目标工具、Goal Round 驱动器、命令注册表、人类目标命令与 Ralph 工作流工具 Agent Note。
持久目标状态与实时权限
一个会话至多有一个当前目标。每次变更都通过持久的 goal/change 事件提交;该事件携带带版本的完整快照或带修订号的清除墓碑,收件箱状态不参与其中。会话日志是唯一持久真源,因此普通持久化、恢复与 SessionStore.fork() 会携带目标,无需第二个数据库或人为取消记录。
持久阶段只有 active、paused、blocked 与 complete。阻塞目标必须携带 GoalBlockReason,其中包含稳定的小写 kebab-case code 与非空的人类可读 message;用量限制、Round 耗尽、模型失败与策略拒绝都是原因代码,而不是额外生命周期阶段。独立激活态是 armed 或 disarmed,且永不持久化。创建与显式恢复会激活目标;停止转换、会话启动、fork 回放、驱动器替换和驱动器拆卸都会让目标保持未激活。
这种分离让会话恢复可观察且符合直觉。重新打开会话绝不会自行开始目标工作。随后的人类提示词,例如「继续」、「恢复目标」或任何语言中的等价请求,会给运行时根 agent 的模型一个新轮次;模型可在其中读取目标并调用 update_goal(..., action: 'resume')。/goal resume 是直接人类命令路径。运行时认证请求来自实时直接人类轮次;提示策略让模型解释措辞在语义上是否授权创建或恢复。
fork 会话会继承持久目标前缀,因为这是自然的重放结果。fork 从未激活状态开始,因此继承不等于执行权限,历史中也不会插入合成目标取消。
defaultMaxGoalRounds 可配置且默认为 256。该上限只计算已接纳的 Goal Round。blockedAfterConsecutiveRounds 在模型工具策略中单独配置且默认为 3;它只是在自治 Round 报告重复阻塞前的机械下限,不是对语义相同性的评估器。
同会话续行
Goal Round 驱动器为每个特定的实时 agent 至多拥有一个待定预留。只有目标处于活跃且已激活状态、agent 空闲、不存在竞争性人类工作、最新变更已经通过持久性检查点、确切的目标 id/修订号/Round 仍匹配,并且下游步骤前策略接受时,它才会接纳预留。其 agent/pre-step 围栏会在下游监听器前后检查这些事实,防止编辑、暂停、人类消息或卸载竞争接纳过时工作。
只有已接纳、Round 为正数且带目标来源的 user/message 会计入一个 Round。陈旧预留会结束一个阻塞的零步骤轮次,不会消耗上限。并发目标修订会胜过旧 Round 的结算。
普通轮次完成后,只有目标仍活跃、已激活且低于上限时才会安排另一个 Round。取消会暂停。速率限制或配额耗尽以代码 usage-limited 阻塞;上限耗尽使用 round-limit;队列失败使用 queue-failed;轮次错误、max-token 停止、策略拒绝与未知终止结果使用各自对应的阻塞代码。独立组合的请求恢复插件可以在同一个轮次内重试暂时性提供方失败;目标驱动器绝不会在异常终止结果后凭空发起另一个 Round。人类随后可以通过普通语言或 /goal resume 授权恢复。
人类与模型交互
人类 UX 遵循 OpenAI Codex 在提交 678157a 时的公开 TUI 分发器中的紧凑形态:/goal 显示状态,/goal <objective> 创建目标,而 edit、pause、resume 或 clear 执行直接生命周期操作。该提交永久链接让研究所得语法在 Codex 演进时仍可验证。状态包含持久阶段、已接纳/上限 Round 数以及实时已激活/未激活状态。直接状态与命令输出不会进入模型历史;已接受领域变更仍可重建,因为目标服务会记录它们。
模型只接收 get_goal、create_goal 和 update_goal。当直接人类请求清楚要求大量多 Round 工作时,模型可以创建目标,并且可以从任何语言推断该意图。它不得把日常单轮次工作变成目标。代码要求当前实时根 agent 轮次中有一条人类直接发送的消息;语义解释仍是模型判断。自治目标 Round 可以为确切的当前 Goal Round 报告 complete 或 blocked,但不能编辑、暂停、恢复或替换人类目标。
TUI 默认挂载共享命令注册表和完整目标栈,并通过一个生产方暴露 /goal。ACP(Agent Client Protocol)挂载目标领域、模型工具和同会话驱动器,但有意省略人类命令平面。每条有效已注册命令都能被每个已组合的命令适配器发现和调用;若插件与某应用不兼容,该应用组合会省略其命令生产方,而不是依赖注册表层面的表面掩码。无 UI 的 agent 主干要求显式选择加入,以免单次调用方静默变成多 Round 操作。无头 CLI(命令行界面)与 JSON-RPC 运行入口不消费命令平面;挂载目标栈后,普通人类文本仍可授权模型目标工具。
全新 agent Ralph 执行
Ralph 是位于自有插件中的一等模型工具,展示了复杂固定执行策略可以在没有新 loop 核心的情况下组合完成。该插件拥有构建在 ctx.workflowEngine 与 ctx.subagents 之上的固定工作流脚本;它不会创建会话目标状态,也不会为 dsh-agent-loop 增加分支。
每个 Round 都使用显式 WorkflowStartRequest.subagentProvider,默认为 spawn。该提供方必须存在、支持结构化输出,并声明不继承父上下文。Ralph 还会把解析后的 Round 上限作为 WorkflowStartRequest.maxTotalAgents 传递;工作线程引擎会在发布工作前验证两项每次运行策略,因此提供方配置错误或低于所请求 Ralph 规模的引擎上限会在运行创建前失败。子 agent 继承 cwd 与谱系,但只接收不可变目标、当前 Round/上限、以工作区为权威的指令和上一份规范化报告。
报告包含状态、摘要、证据、下一步与阻塞文本。固定脚本内部和消费方边界都会验证状态专用不变量与序列化大小。maxRounds 可配置,默认为 256,并作为调用覆盖值的上限。maxHandoffChars 默认为 16384;过大报告会失败,而不会被静默截断。maxResultChars 单独默认为 16384,并限制面向父级的完整成功文本,包括外层文本与截断标记。
普通子 agent 失败会结束运行且不重试。固定脚本会报告失败 Round,并在存在时带回上一份成功交接;工具会把该状态作为错误返回,而不会误判为畸形报告或预算耗尽。致命工作流基础设施错误可能在脚本返回该状态前结算;更丰富的原因传输与重试策略均予以延期。
该工具位于前台且只存在于进程内。父工具调用等待终止结果,把取消传播到工作线程引擎,并等待 run.dispose(),因此返回前子工作已完全停稳。模型只看到一次调用,以及一份有界成功终止结果或一个错误;完成与阻塞的外层文本会明确说明结果由工作者报告,而不会呈现为独立认证。中间子 agent 对话不会进入父 transcript。
外部设计谱系
Codex 提供了这里采用的最小可观察目标 UX:一个附着于聊天的持久目标,以及设置、查看、编辑、暂停、恢复与清除控制。本实现采用这种可发现性,但使用本仓库的事件溯源目标记录、插件作用域与运行时权限检查。
当前 Claude Code goals 进一步验证了「前一轮次后启动另一轮次的目标」和定时 /loop 之间的区别。Claude Code 还会在每个轮次后使用独立小模型评估器。本实现采用策略区分,但有意不复制该评估器:评估器输入、工具访问、确定性检查、提供方选择、隔离与权限需要单独设计的插件约定,而不是隐式自我认证层。
外部产品只是比较对象,不是兼容目标。本地源码研究帮助确定边界,而交付接口遵循本仓库「一切皆插件」、模型可见内容均记入日志、显式解析默认值与完全停稳后拆卸规则。
验证
六份所属 Agent Note 记录了单元、集成、进程、快照、取消、重放与构建后运行时覆盖率。该栈验证严格目标记录折叠、比较并交换竞争、会话 fork 继承、恢复后未激活、自然语言直接人类权限、可配置上限与阻塞阈值、确切的 Goal Round 归属、适配器范围的命令发现与 transcript 隔离。已发布的无密钥快照覆盖通过无头应用创建/检查模型目标、通过 ACP 执行多 Round 同会话生命周期与取消,以及通过无头应用执行两个真实 Ralph Round;聚焦的命令测试固定了无需模型轮次的直接 /goal 状态。Ralph 快照会启动工作线程引擎、spawn 提供方、结构化输出运行时与 agent loop,随后检查互不相同且无种子的子日志和精确的单向有界交接,同时固定父级事件流。聚焦的真实栈测试还覆盖完成、阻塞与 Round 上限结果、畸形及过大报告、保留上一份有效交接的普通子 agent 失败、单个阶段事件,以及取消后子 agent 完全停稳。包源码继续受仓库逐文件 100% 覆盖率门禁约束,构建后二进制测试覆盖已安装产物解析。实现经验已记录进根测试策略:每项非平凡的模型或人类可见变更都必须在同一 PR(Pull Request)中携带真实示例无密钥快照,而不能依赖仅包级或仅 mock fixture(测试前置数据)的覆盖率。
考虑过的替代方案
- 实现原始通用 loop 能力——不予采纳,因为
Evaluator、BudgetPolicy、RoundHandoff、GoalReflector、后台任务所有权、持久化与调度并不构成一项一致的必选抽象。在出现首个具体消费方前全部构建,会产生宽泛的推测性接口,并重复现有会话、工作流、subagent 与 task 机制。 - 只实现同会话目标——不予采纳,因为全新上下文迭代在实质上不同,也是插件架构的重要示范。Ralph 应作为带显式上下文重置的固定工作流消费者。
- 把 Ralph 放进 Goal Round 驱动器——不予采纳,因为同会话目标有意保留一段对话,而 Ralph 有意移除对话。合并两者会让激活、重放、交接与 UI 状态含糊不清。
- 把 fork 当成全新 Ralph 子 agent——不予采纳,因为 fork 会携带对话前缀。全新子 agent 加工作区状态与一份显式报告更容易限制和重放,并且无需合成取消记录。
- 把 Claude Code 评估器复制进首个目标实现——不予采纳,因为只读取 transcript 的模型评估器是一项有用策略,但不是普遍可信的完成证书。系统必须仍能支持确定性评估与隔离,因此评估器延期到其权限与提供方约定完成设计之后。
- 会话恢复后自动续行——不予采纳,因为打开会话是观察行为,不是花费资源的权限。系统恢复持久状态,而激活态等待新的人类提示词。
- 通过模型路由
/goal——不予采纳,因为状态与显式生命周期控制应是确定性且零 token 的 UI 操作;普通自然语言提示词仍是语义模型路径。 - 为具体 agent loop 增加目标或 Ralph 模式——不予采纳,因为公开队列、提示词、会话、取消、工作流与 subagent seam 已经支持两项策略。通用 cancel-requested 观察是唯一核心协调新增项。
后果
- 目标式执行在没有单个过载「loop」对象的情况下交付:同会话续行与全新 agent 迭代拥有显式、可独立测试的约定。
- 持久目标历史可以重放和 fork,而进程本地激活态会防止恢复时意外开始工作。
- 人类获得小型 Codex 形态 UX;模型获得一组紧凑工具,其中的修改操作要求当前实时根 agent 轮次中有一条人类直接发送的消息;部署可以独立移除任一能力。
- Ralph 展示了非平凡固定策略可以完全作为现有工作流与 subagent 原语之上的插件实现。
- Round 上限默认宽裕,但仍由部署控制。它限制迭代次数,不限制 token、价格、耗时或外部副作用。
- 原始提案中的评估器、预算、反思器、后台任务、CLI 与通用 loop-session 架构有意不进入已实现的公开 API。
已知限制与暂缓事项
- 独立评估——同会话完成/阻塞和 Ralph 终止状态都是模型或工作者声明。独立评估器、评估器驱动反馈 Round、完成证书、确定性检查器、对抗式 verifier 与标准/执行器/隔离约定均予以延期。
- 聚合预算——
maxGoalRounds与 RalphmaxRounds是唯一聚合工作量限制。token、货币、耗时、提供方用量与逐 Round 价格准入策略均不存在。 - 没有持久自治运行器——同会话目标事实会持久化,但激活与调度只存在于进程内,并且有意在恢复后等待人类输入。Ralph 位于前台,进程丢失后无法恢复。后台收集、重启恢复与无人值守常驻执行均予以延期。
- 没有时间调度器——间隔
/loop、cron、主动维护以及云端或桌面调度不在本决策范围内。 - 没有通用 loop 日志或执行世界回退——会话重放会重建目标历史,而不会恢复此前文件、进程、环境、凭据或外部副作用。Ralph 把当前工作区作为权威,并且没有跨运行日志。
- 没有目标反思器——concern 事件、自动无进展启发式、由独立反思器执行的目标修订、卡住模式检测与
loop_split均未实现。人类可以直接编辑、暂停、清除或恢复目标。 - Ralph 策略仍然狭窄——一个 Round 创建一个全新子 agent;Round 内扇出、评估器/工作者角色分离、动态提供方/模型选择与从结构上禁止递归调用 Ralph 工具都需要独立策略 API。提示词指导不是强制执行。
- Ralph 不会重试失败的子 agent——普通失败会保留失败 Round 与上一份有效交接,而致命工作流基础设施错误可能在该状态可用前结束。重试次数、退避与更丰富的失败传输需要独立的策略与边界设计。
- 可移植 UI 仍较朴素——TUI 渲染纯文本目标状态和通用 Ralph 卡片。ACP 只承载已提交的助手文本;系统没有持续状态组件、可重连命令输出、模态目标编辑器,ACP、无头 CLI 与 JSON-RPC 也没有命令平面。