ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

oh-my-openagent:senpi-task 中 task_send 的 always-steer 契约设计与验证方法

2026/9/18 18:04:04 拓冰建站 浏览量
oh-my-openagent:senpi-task 中 task_send 的 always-steer 契约设计与验证方法 oh-my-openagentsenpi-task 中 task_send 的 always-steer 契约设计与验证方法【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent本文基于仓库内的验证证据文档 .omo/evidence/20260727-senpi-task-always-steer/README.md 展开。它记录了 oh-my-openagent 的senpi-task包一次公共 API 行为变更task_send工具从可选投递模式收敛为无条件 steer转向注入契约。读完本文你将理解这次变更动机的契约层含义、deliver_as参数被移除后的源码级实现路径以及如何用 RED/GREEN 焦点测试加真实任务表面real task surface两阶段场景对一项工具行为变更做可复现的验证。1. 变更范围为什么这是一次 HEAVY 级改动证据文档的 Scope 一节给出了改动的定级与上下文Tier: HEAVY——定级理由写得很明确public task_send schema and task-session message delivery semantics change即公共的task_send参数 schema 与任务会话的消息投递语义同时发生了变化。对 Agent 工具而言schema 是模型可见的公共 API语义变更直接影响模型侧的调用行为因此归为最高验证强度档。分支fix/senpi-task-always-steer基线origin/dev的f2ae25041。文档随后列出了四条成功判据Success criteria这也是整个验证方案的验收标准task_send不再暴露deliver_as属性且普通的子任务消息无条件请求 steer运行中子任务running-child与已完成的驻留子任务finished-resident两种行为能通过真实任务工具表面而非 mock工作且不依赖任何投递选项焦点测试、senpi 兼容性、typecheck、build 与改动文件的诊断全部干净评审者批准并按仓库要求的 merge commit 方式合并 PR。后两条属于工程流程要求前两条才是技术核心它们把schema 收敛与语义统一拆成了两个独立可验证的断言——参数消失是静态契约无条件 steer 是动态行为。证据文档把两类断言分别映射到了不同的验证场景见第 4、5 节这个拆分方式本身对做工具 API 收敛的团队有参考价值。2. 背景senpi-task 的任务工具面在深入变更之前需要先理解task_send所处的位置。senpi-task包是omo-senpi的任务引擎一个持久化任务状态机 记录存储 两种子进程 runner 转向steering引擎 工具面。包的完整解剖可见 packages/senpi-task/AGENTS.md其工具面由 4 个任务工具构成工具工厂函数位置taskcreateTaskTooltools/task/tool.tstask_sendcreateTaskSendTooltools/control/send.tstask_cancelcreateTaskCancelTooltools/control/cancel.tstask_outputcreateTaskOutputTooltools/output/output.ts分工规则是task只负责派生转向、驻留会话复活、团队消息与 shutdown 审批统一走task_send子任务输出读取走task_output。task_send的变更之所以被定为 HEAVY正是因为它同时承载转向消息投递这一核心语义。3. 核心变更deliver_as从公共 schema 中消失3.1 公共参数 schema投递选项不复存在变更后的task_send公共参数定义在 send-schema.tsTypeBox schemaexport const TaskSendParams Type.Object({ to: Recipient, message: Type.Optional(Type.Union([PlainMessage, StructuredMessage])), team_run_id: Type.Optional(Type.String({ description: Team run id for lead-to-member messages or shutdown messages. })), summary: Summary, all_scope: Type.Optional( Type.Boolean({ description: Allow messaging a child owned by another session. Off by default. }), ), })对照证据文档的成功判据 1——task_sendexposes nodeliver_asproperty——当前 schema 中只有to、message、team_run_id、summary、all_scope五个属性deliver_as确实不存在。其中message是普通字符串 | 结构化 shutdown 对象的联合类型shutdown_request/shutdown_response结构化消息走的是完全不同的 shutdown 路由与投递模式无关。3.2 运行时行为普通消息硬编码为 steerschema 去掉参数只是模型看不见真正的语义变更发生在执行路径。send.ts 中runTaskSend处理普通字符串消息时向 steering 引擎的调用把投递模式写死为steerif (typeof params.message string) { const outcome await manager.sendToTask({ idOrName: params.to, message: params.message, deliverAs: steer, ...(callerSessionId ! undefined ? { callerSessionId } : {}), ...(params.all_scope true ? { allScope: true } : {}), }) ... }也就是说工具层永远以 steer 身份向引擎发消息调用方模型没有任何选择空间。同时工具描述send.ts 的DESCRIPTION常量也同步改口Plain-text messages always steer a running child immediately.并声明了复活语义——发往已停泊的 in-process 或 detached RPC 子任务带转录和已记录的启动规格的普通文本消息会在符合条件的 finished 运行之后恢复该会话而 killed/cancelled/lost 的子任务永不复活。3.3 内部引擎仍保留两种投递模式——收敛的边界一个值得注意的细节deliverAs并没有从系统内部被删除它只是从公共面退到了内部面。steering 引擎的类型定义steering/types.ts保留了完整的双模式export type SendDelivery steer | followUp export type SendInput { readonly idOrName: string readonly message: string readonly deliverAs?: SendDelivery readonly callerSessionId?: string readonly allScope?: boolean } // The SEND DEFAULT is followUp: codexs followup_task routes a send to a running child as a // follow-up prompt, not an interrupting steer. steer is opt-in for polite mid-turn injection. export const DEFAULT_SEND_DELIVERY: SendDelivery followUp从源码结构看这次变更的准确含义是引擎级默认仍是followUp兼容 codex 风格的追补提示语义但task_send这个工具面把礼貌的回合内注入固定为唯一路径。引擎内部followUp分支仍然存在服务于不经由task_send的调用路径例如测试或其他内部路由。在 steering/engine.ts 的steerRunning中可以验证两条分支if (deliverAs steer) await handle.steer(message) else await handle.followUp(message)steer走handle.steer()打断当前回合、立即注入followUp走handle.followUp()排队为追补提示。工具面永远命中前者这正是always-steer契约的动态含义。此外引擎对pending状态的子任务会把消息写入持久化的pending_steering队列engine.ts 的enqueuePending每条队列项落盘deliver_as字段子任务真正启动时notifyStartedengine.ts按持久化顺序逐条排空deliver_as steer的条目同样调用handle.steer()。由于工具面现在只产生steer消息预发射排队prelaunch steering这条路径上的投递模式也事实上统一了。4. 验证场景一RED/GREEN 焦点测试证据文档的 Planned exact scenarios 第一部分给出了精确的焦点测试命令bun test packages/senpi-task/src/tools/control/send-always-steer.test.ts packages/senpi-task/src/tools/control/renderers.test.ts判据写明PASS 是实现完成后退出码为 0RED 的形态是生产代码修改前断言失败因为deliver_as仍存在、默认发送以followUp抵达引擎、或渲染器打印deliver:。这正是经典的 TDD 证据链先写测试钉住目标契约测试在旧代码上必然红再动生产代码转绿。对应的测试文件 send-always-steer.test.ts 包含三个测试逐一映射成功判据 1 的三个侧面静态契约createTaskSendTool返回的parameters.properties键列表中不含deliver_as工具description中不出现deliver_as、followUp、interrupt任何一个词——把模型可见面的收敛做到了字符串级断言动态行为用记录型SendManager假件调用runTaskSend(manager, { to: st_1, message: new direction }, parent-1)断言到达引擎的SendInput恰好是{ idOrName: st_1, message: new direction, deliverAs: steer, callerSessionId: parent-1 }——调用方没传任何投递选项引擎收到的却必须是deliverAs: steer渲染面renderTaskSendCall渲染出的行包含task_send to:st_1与消息摘要但不包含deliver:token——终端 UI 上同样不能泄露已移除的选项。渲染实现印证了这一点renderers.ts 的taskSendCallLine只拼接task_send、to:目标与消息摘录三段代码路径上根本不存在投递 token 的分支。5. 验证场景二真实任务表面real task surface证据文档强调第二类场景必须through the real task surface——即通过task/task_send/task_output工具链的真实调用来验证覆盖两种子任务状态5.1 运行中子任务running childtask({ prompt: Report WORKING, wait for a steering instruction, then return the exact steered text., subagent_type: explore, run_in_background: true, name: always-steer-running }) task_send({ to: always-steer-running, message: STEER-PROBE-RUNNING }) task_output({ name: always-steer-running, mode: full })PASS 判据send 调用不包含任何投递选项结果报告 steer 投递steer delivery子任务转录中包含STEER-PROBE-RUNNING。这个场景验证的是steer 真的注入进了运行中的回合子任务被指示先报告WORKING并等待转向指令随后task_send注入探针文本若投递模式仍是followUp追补提示探针会出现在下一个回合而非打断等待——转录中子任务返回被转向的确切文本这一行为只有 steer 语义才能解释。5.2 已完成但驻留的子任务finished residenttask({ prompt: Return RESIDENT-FIRST and stop., subagent_type: explore, name: always-steer-resident }) task_send({ to: always-steer-resident, message: Return RESIDENT-REVIVED and stop. }) task_output({ name: always-steer-resident, mode: full })PASS 判据第二次调用不含投递选项复活同一个 task id / session转录包含RESIDENT-REVIVED。这条对应工具描述中的复活语义子任务 finished 后其会话仍是驻留态residenttask_send应触发复活而非拒绝或新建。从源码看引擎sendToTask的主分支先经messageability判定engine.ts非 steer 模式走reviveTerminal路径engine.ts结果 kind 为revived并携带新的run_epoch。5.3 配套读取面task_output 的模式两个场景都以task_output({ ..., mode: full })收尾。output.ts 的 schema 定义mode为status | tail | full三值联合缺省statusstatus返回记录快照加最终响应tail返回转录末尾tail_lines行缺省 60full返回整个转录有上限带头尾省略标记。验证场景选full是为了让STEER-PROBE-RUNNING/RESIDENT-REVIVED探针在完整转录中可被机器校验。6. 变更的边界与不改动的部分从 send.ts 的完整执行路径可以界定这次always-steer到底改了什么、没改什么改普通字符串消息到子任务child路径的投递模式——从可选deliver_as变为硬编码steer不改结构化 shutdown 消息shutdown_request/shutdown_response仍由routeStructuredMessage独立路由send.ts与投递模式无关不改子任务未找到时的团队兜底路由——当manager.sendToTask返回not_found且配置了teamRouting时普通消息会作为团队邮件经runTeamSend投递send.ts工具描述同时声明Team messages always steer into the recipients running turn即团队消息的 steer 语义是既有行为本次未动不改跨会话寻址规则——指向其他会话拥有的子任务默认拒绝需显式all_scope: true不改一次性 agent如 plan-reviewer在所有状态下一律拒绝task_send。这一边界划分的意义在于always-steer 契约只作用于父 → 子的普通消息投递团队的 mailbox 投递、shutdown 握手、跨会话权限等邻接语义全部保持原状变更面因此可以被两条焦点测试文件send-always-steer.test.tsrenderers.test.ts与两个真实表面场景完整覆盖。7. 证据文档的其他约定文档末尾还有两个工程性约定值得留意Observations / Why this is enough / Cleanup receipts 三节标注 Pending该文件是随 PR 推进填充的活证据文件——观察记录、充分性论证与清理回执在验证执行后回填。阅读此类证据文档时Pending 意味着对应证据尚未落盘不应视为已完成。Omitted 一节明确声明脱敏原则Secret-bearing environment values, provider credentials, and raw private logs will not be copied into evidence.携带密钥的环境变量、provider 凭据与原始私有日志不进入证据文件。这与包级文档 packages/senpi-task/AGENTS.md 中记录的 QA 规范持久化记录带 redaction、安全测试一脉相承。8. 小结与延伸阅读回到证据文档的四条成功判据本文梳理的仓库证据与它们的对应关系是判据 1 由 send-schema.ts无deliver_as send.ts硬编码deliverAs: steer send-always-steer.test.ts三层断言共同支撑判据 2 由 running-child 与 finished-resident 两个真实表面场景支撑底层分别落在引擎的steerRunning与reviveTerminal分支判据 3 对应的包级 QA 入口为tsgo --noEmit -p packages/senpi-task/tsconfig.json与bun test packages/senpi-task见 packages/senpi-task/AGENTS.md 的 QA 一节判据 4 属于评审流程不在仓库静态证据范围内。对于维护 Agent 工具面的团队这篇证据文档示范了一个可复用的方法当一次变更同时触及模型可见 schema与运行时投递语义时用 RED/GREEN 焦点测试钉住契约的静态与动态侧面再用不带 mock 的真实工具链场景验证端到端行为并把脱敏与清理约束显式写进证据文件本身。延伸阅读均在当前仓库内packages/senpi-task/AGENTS.mdsenpi-task 包完整解剖含状态机、存储、runner、生命周期、steering 与团队运行时packages/senpi-task/src/steering/engine.ts转向引擎实现sendToTask/steerRunning/enqueuePending/notifyStarted全链路packages/senpi-task/src/steering/types.tsSendDelivery、SendInput与SendOutcome的完整类型面packages/senpi-task/src/tools/output/output.tstask_output的 status/tail/full 三种读取模式。【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考