fix(ralph): harden execution boundaries

This commit is contained in:
Tianyi Cui
2026-07-20 12:56:09 +08:00
parent c6f8247ac1
commit 60eea35df5
37 changed files with 535 additions and 81 deletions
@@ -18,29 +18,35 @@ Status: implemented
### 每次运行的工作流 provider 路由
`WorkflowStartRequest` 新增可选的 `subagentProvider`。工作线程引擎先解析这个显式的每次运行值,再回退到引擎配置的 provider,并把结果用于该运行中的每次 `agent()` 调用。脚本无法观察或替换此路由。普通 `workflow` 工具不设置该字段,也不暴露新的模型参数,因此通用工作流行为和 provider 策略保持不变。
`WorkflowStartRequest` 新增可选的 `subagentProvider`。工作线程引擎先解析这个显式的每次运行值,再回退到引擎配置的 provider;在发布运行前,它要求所选规范化路由已注册,并把结果用于每次 `agent()` 调用。脚本无法观察或替换此路由。普通 `workflow` 工具不设置该字段,也不暴露新的模型参数,因此通用工作流行为和 provider 策略保持不变。
Ralph 插件的 `subagentProvider` 默认为 `spawn`。每次调用前,它要求具名 provider 已存在、支持结构化输出且报告 `inheritsParentContext: false`;类似 fork 或能力不足的 provider 会在工作流启动前响亮失败。provider 查找保留在调用期,因为效果作用域内的 provider 注册可能随 HMR 改变。
### 每次运行的工作流子 agent 上限
`WorkflowStartRequest` 还新增可选的 `maxTotalAgents`。工作线程引擎要求它是正安全整数且不高于已配置的部署上限,并在发布运行前把解析值装入该运行的工作线程限制。Ralph 把解析后的 `maxRounds` 作为此上限,因此固定循环的轮次预算不会与通用失控子 agent 后备限制冲突。普通工作流工具不设置该字段并保留引擎默认值。
### Ralph 轮次与交接
层级为 Ralph 运行 → Ralph 轮次 → 全新子 agent 回合 → 步骤。每个 Ralph 轮次恰好通过所选 provider 创建一个子 agent。Spawn 给该子 agent 一个没有种子的独立会话,同时保留父级 cwd,因此共享工作树是持久权威,父对话和先前子 agent 历史都不会进入请求。
固定提示只传递不可变目标、当前轮次与上限、以工作区为权威的指令,以及上一份结构化报告。`RalphRoundReport` 包含 `status: continue | complete | blocked`、`summary`、`evidence`、`nextSteps` 和 `blocker`。字符串必须规范化;`continue` 要求存在下一步且没有阻塞项,`complete` 要求存在证据且没有下一步或阻塞项,`blocked` 要求具体阻塞项。报告成为下一次交接前,脚本会验证语义与序列化大小;消费者还会跨工作流接缝再次验证实体化的终止值。
`maxRounds` 默认为 `256`,同时也是调用覆盖值的部署上限。`maxHandoffChars` 默认为 `16384`。两者都是正安全整数配置值;过大的报告会失败,而不会被静默截断。最后一个允许轮次报告 `continue` 后,固定脚本返回 `budget-limited`;`complete` 和 `blocked` 会立即返回最终报告与已启动轮次数。
`maxRounds` 默认为 `256`,同时也是调用覆盖值的部署上限。`maxHandoffChars` 和 `maxResultChars` 均默认为 `16384`。三者都是正安全整数配置值。过大的交接会失败,而不会被静默截断;`maxResultChars` 单独限制面向父级的完整成功文本,包括外层文本和截断标记,并且不会改变跨轮状态。最后一个允许轮次报告 `continue` 后,固定脚本返回 `budget-limited`;`complete` 和 `blocked` 会立即返回最终报告与已启动轮次数。
工作流语言会把正常结束但未成功的子 agent 映射为 `null`。固定脚本会在报告验证前检测该值,并返回 `round-failed`,其中包含失败轮次,以及存在时的上一份成功交接;工具会把它转成错误,而不会误判为畸形报告或预算耗尽。Ralph 不添加重试策略。致命的 provider 启动、传输、工作线程和工作流错误仍是通用工作流失败,因为这些路径上的工作流接缝不携带可恢复的子报告。
### 模型与 UI 表面
模型只能提供 `objective` 和可选的 `maxRounds`;provider 选择、报告 schema、交接上限和脚本都由部署拥有。固定提示区段说明,只有直接人类明确要求 Ralph 或全新 agent 迭代时才使用 `ralph`,并将其与同会话目标、有界委派和通用扇出工作流区分开。这是指导,而不是新的目标 UX 状态机。
ACP 和终端展示使用通用 `ralph` 卡片,并把目标作为原始输入。父转录只保留原始工具调用和一份有界终止报告,不包含中间子 agent 消息。发布的无头、REPL/TUI 与 ACP 组合会在现有工作流引擎旁加载该插件;JSON-RPC 保持不变,因为其默认组合不暴露工作流。
ACP 和终端展示使用通用 `ralph` 卡片,并把目标作为原始输入。成功完成与阻塞的外层文本会说明结果由工作者报告,而不会把它呈现为独立认证。父转录只保留原始工具调用,以及一份有界成功终止报告或一个错误,不包含中间子 agent 消息。发布的无头、REPL/TUI 与 ACP 组合会在现有工作流引擎旁加载该插件;JSON-RPC 保持不变,因为其默认组合不暴露工作流。
## 测试
单元测试覆盖配置与调用上限解析、provider 能力拒绝、固定启动请求路由、三种终止结果、畸形及过大边界值、中止时序、处置、渲染意图、提示生命周期和命名空间插件形状,并达到逐文件 100% 覆盖率。工作流引擎测试证明,每次运行的 provider 覆盖会选择每个子 agent 且不改变配置默认值,其中包括普通 Node 下构建后的 `lib/worker.cjs`。
单元测试覆盖配置与调用上限解析、provider 能力拒绝、固定启动请求路由与子 agent 上限、全部成功终止结果、普通子 agent 失败外层值、畸形及过大边界值、成功结果精确截断、中止时序、处置、渲染意图、提示生命周期和命名空间插件形状,并达到逐文件 100% 覆盖率。工作流引擎测试证明 provider 路由会同步验证、每次运行的子 agent 上限可低于部署上限,并且 provider 覆盖会选择每个子 agent 且不改变配置默认值,其中包括普通 Node 下构建后的 `lib/worker.cjs`。
一项无密钥真实栈集成测试通过实际工作线程引擎、spawn provider、结构化输出运行时和 agent loop 驱动固定脚本。它证明子 agent 标识不同、没有 `seedLength`、继承 cwd、两个子请求都不含父历史标记、上一份报告只精确出现在下一轮交接中、终止完成以及两个子 agent 都被处置。工具测试固定通用调用/结果展示,而 ACP 重放请求头快照固定发布的 schema 与提示指导转录表面。
一项无密钥真实栈集成测试通过实际工作线程引擎、spawn provider、结构化输出运行时和 agent loop 驱动固定脚本。它证明子 agent 标识不同、没有 `seedLength`、继承 cwd、两个子请求都不含父历史标记、上一份报告只精确出现在下一轮交接中、只产生一个阶段事件、终止完成以及两个子 agent 都被处置。同一真实栈还覆盖阻塞与轮次上限结果、未规范化及语义无效报告、过大交接、保留上一份有效交接的普通子 agent 失败,以及取消后达到子 agent 静止状态。工具测试固定通用调用/结果展示,而 ACP 重放请求头快照固定发布的 schema 与提示指导转录表面。
## 考虑过的替代方案
@@ -56,7 +62,7 @@ ACP 和终端展示使用通用 `ralph` 卡片,并把目标作为原始输入
- 目标轮次与 Ralph 轮次保持不同概念:前者是一次同会话续行回合,后者是前台工作流中的一个全新子 agent。
- 工作区成为权威跨轮记忆,因此工作者必须检查和验证工作区,而不能信任叙事性交接。
- 宽裕的轮次上限允许大量自治工作,而部署配置仍会限制子 agent 数量,并且每次交接始终受大小约束。
- provider 路由成为显式的工作流启动关注点,但不扩展脚本或普通工作流工具表面。
- provider 路由与可降低的每次运行子 agent 上限成为显式的工作流启动关注点,但不扩展脚本或普通工作流工具表面。
## 已知限制与推迟工作
@@ -64,4 +70,5 @@ ACP 和终端展示使用通用 `ralph` 卡片,并把目标作为原始输入
- 运行位于前台且只存在于进程内。后台收集、持久化/恢复、调度和重启恢复均不存在。
- 轮次数是唯一聚合预算。token、货币、耗时和 provider 用量预算仍属于未来的独立策略。
- 每轮创建一个子 agent。轮内扇出、evaluator/工作者角色分离、动态 provider 或模型选择,以及跨运行日志均被推迟。
- 普通子 agent 失败会结束运行且不重试,同时保留失败轮次与上一份成功交接。致命工作流基础设施错误可能在固定脚本返回该状态前结束;增加重试或更丰富的失败传输需要独立的策略与接缝设计。
- 提示指导模型不要递归调用 Ralph;结构化的子 agent 工具限制需要另行设计工作流子策略表面。