传递的设计与实现)
oh-my-openagent senpi-task always-steer 机制解析task_send 无条件转向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导读本文围绕 oh-my-openagent 中 senpi-task 子系统的 always-steer 机制展开它重构了task_send工具的消息投递语义从「默认以 followUp跟进提示投递、可选 steer中途转向」改为「普通文本消息无条件以 steer 方式注入运行中的子任务会话」。读者将理解这一语义变更的动机、公开 schema 的收敛方式、底层 steering engine 的运行中转向与驻留会话复活revival两条核心路径以及仓库中对应的 RED/GREEN 证据链与回归门禁可直接用于理解或复现该功能的演进过程。一、背景为什么 task_send 需要 always-steer在packages/senpi-task/src/steering/types.ts中投递方式delivery被定义为两种取值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向正在运行的子任务发送消息等价于追加一条跟进提示而非打断其当前回合steer是「礼貌的中途注入」需要显式选择。问题在于followUp语义下父任务发给子任务的「转向指令」只能等子任务当前回合结束才生效导致编排orchestration中父任务无法及时纠正子任务方向执行路径常出现「跑偏一整轮才被纠正」的现象。always-steer 机制证据目录 .omo/evidence/20260727-senpi-task-always-steer/的目标正是消除这种语义分裂普通文本消息永远以steer注入公开工具面不再暴露投递方式选项。二、公开契约收敛删除 deliver_as2.1 变更核心该改动属于 HEAVY 级别证据 README 的 Scope 标注原因是公开的task_sendschema 与任务会话消息投递语义发生变化。分支为fix/senpi-task-always-steer基线为origin/dev的f2ae25041。改动的第一步是从公开输入 schema 中删除deliver_as。当前仓库中 send-schema.ts 定义的TaskSendParams已无此字段export 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. }), ), })成员级member-scoped变体同样收敛为三字段export const MemberScopedTaskSendParams Type.Object({ to: Recipient, message: PlainMessage, summary: Summary, })2.2 为何是不可表示而非校验拒绝自审文档06-self-review.md第 2 条点出了设计取向TypeBox 仍是边界解析器删除deliver_as后「非法投递方式」在TaskSendInput类型层面变得不可表示unrepresentable而不是运行期再校验拒绝。这符合「使非法状态不可表示」的类型安全实践——从类型系统根上消灭了错误选项。2.3 公开描述同步收敛工具描述send.ts 的DESCRIPTION中也不再出现deliver_as、followUp、interrupt等词改为明确陈述新语义Plain-text messages always steer a running child immediately.同时保留了对驻留会话resident复活语义的说明对已停驻parked的进程内或分离 RPC 子任务发送普通文本消息会在合格终态后恢复其会话已被 kill、cancel 或丢失的子任务永不复活非终态挂起suspended的子任务随父会话恢复。团队消息team messages同样总是转向注入接收方的当前回合。三、运行时路由普通消息无条件转发为 steer3.1 runTaskSend 的实现runTaskSend 的核心分支逻辑是if (typeof params.message string) { const outcome await manager.sendToTask({ idOrName: params.to, message: params.message, deliverAs: steer, // ← 无条件 steer ...(callerSessionId ! undefined ? { callerSessionId } : {}), ...(params.all_scope true ? { allScope: true } : {}), }) // not_found 时回退到团队路由runTeamSend ... } if (params.message ! undefined) return routeStructuredMessage(params.to, params.message, params, teamRouting) return invalidArguments(message is required)可以看到字符串消息被硬编码为deliverAs: steer不再有任何可选分支结构化消息shutdown_request/shutdown_response仍走routeStructuredMessage团队关闭流程message缺失则返回invalidArguments(message is required)。validateParams也从三个参数简化到一个自审第 8 条仅保留「关闭请求拒绝时必须携带 reason」的校验。3.2 底层 engine 的两种交付语义SendDelivery常量仍保留followUp因为它服务于底层引擎的队列与继续执行路径如 manager.ts 的continueTask仍接受deliverAs参数。也就是说删除的是公开工具面的选项而非底层引擎的能力。steer与followUp在SendOutcome中通过delivered: SendDelivery区分export type SendOutcome | { readonly kind: steered; readonly task_id: string; readonly status: TaskStatus; readonly delivered: SendDelivery } | { readonly kind: revived; readonly task_id: string; readonly run_epoch: number } | { readonly kind: queued; readonly task_id: string; readonly queue_position: number } // ...3.3 渲染层不再显示投递方式用户可见的调用渲染renderers.ts 的renderTaskSendCall不再打印deliver:...后缀。测试 send-always-steer.test.ts 断言const line component.render(120)[0] ?? expect(line).toContain(task_send to:st_1) expect(line).not.toContain(deliver:) expect(line).toContain(new direction)四、RED/GREEN 证据链三条行为敏感断言4.1 RED 阶段改动前01-red-focused-tests.txt 记录了三个典型失败恰好对应契约的三个方面schema 泄漏公开参数仍含deliver_as——[to, message, deliver_as, team_run_id, summary, all_scope]路由错误普通task_send仍以deliverAs: followUp转发而期望是steer渲染残留用户可见调用仍打印task_send to:st_1 deliver:followUp message:new direction。RED 测试命令bun test packages/senpi-task/src/tools/control/send-always-steer.test.ts结果0 pass / 3 fail退出码 1。证据文件还特别标注了一个细节早期「环境相关失败」Cannot find module code-yeongyu/senpi被判定为无效 RED 并丢弃——只有在隔离 worktree 中安装依赖后捕获的行为敏感失败才是有效 RED。这是仓库测试纪律见 .omo/rules/test-discipline.md的体现。4.2 GREEN 阶段改动后02-green-focused-tests.txt 汇总了 6 个文件的 49 个测试bun test \ packages/senpi-task/src/tools/control/send-always-steer.test.ts \ packages/senpi-task/src/tools/control/send.test.ts \ packages/senpi-task/src/tools/control/send-team.test.ts \ packages/senpi-task/src/tools/control/send-unify-integration.test.ts \ packages/senpi-task/src/tools/control/renderers.test.ts \ packages/senpi-task/src/tools/control/member-scoped-renderer.test.ts结果49 pass / 0 fail / 137 expect()覆盖公开 schema 无deliver_as、普通消息无条件转发deliverAs: steer、用户可见调用无deliver:、lead/member 团队路由仍转向运行中接收方、已完成驻留子任务仍能复活同一会话以及渲染器宽度、韩文词边界、结构化关闭、scope 与错误分支。4.3 契约级测试的断言细节测试文件 的三个测试组成了 always-steer 的公开契约schema 断言parameterNames不含deliver_as且工具描述不含deliver_as、followUp、interrupt路由断言runTaskSend(manager, { to: st_1, message: new direction }, parent-1)后捕获的SendInput必须精确等于{ idOrName: st_1, message: new direction, deliverAs: steer, callerSessionId: parent-1 }渲染断言渲染行含task_send to:st_1与消息正文但不含deliver:。五、真实运行面验证两条核心执行路径5.1 运行中子任务steer 注入03a-live-running-steer.txt 通过 Senpi RPC 端到端驱动packages/omo-senpi/scripts/qa/task-rpc-e2e.mjs验证。场景中字面上的task_send调用完全省略投递选项{name:task_send,arguments:{to:p1,message:steer: keep going}}二进制可观测结果steer_ack_mid_runPASS即运行中的子任务在回合中途收到了转向注入并确认。同时通过的还有real_credentials_untouched_and_caller_env_ignored真实凭据未被触碰、调用方环境被忽略、process_mode_routes_to_rpc_runner、spawn_process_pid_and_session_jsonl、completion_push_arrives、no_leaked_rpc_child_pidsleakedPids: 0。证据文件还如实披露了两个与本次改动无关的失败kill_marks_error_killed_true、reconcile_lost_terminates_orphan并说明它们属于独立的 kill/reconcile fixture、不经过被改动的无选项task_send路径——体现证据链的诚实性。5.2 已完成驻留子任务resident 复活03b-live-resident-revival.txt 使用直接 Bun 驱动导入被改动的 senpi-task manager fixture 并调用runTaskSend验证驻留会话复活启动manual-resident其第一回合以最终响应RESIDENT-FIRST完成status: completed,residency: resident,epoch: 0调用runTaskSend(manager, { to: taskId, message: RESIDENT-REVIVED }, manual-parent)——同样不含任何投递选项复活后的回合完成且同一 task id、同一驻留会话从epoch: 0推进到epoch: 1最终响应为RESIDENT-REVIVED。{ send: { kind: revived, task_id: st_019fa2ea, run_epoch: 1 }, after: { status: completed, residency: resident, epoch: 1, final: RESIDENT-REVIVED } }证据还披露了另一条被放弃的 E2E 路径task-e2e.mjs其 mock-provider 子进程在任何task_send调用前就以Connection error失败因此该运行不作为功能证据日志保留在task-e2e-debug/供评审者检视——同样是只把有效证据当证据的实践。六、回归门禁与评审结论6.1 五道回归门禁05-regression-gates.txt 记录了改动合入前必须通过的关卡门禁命令结果senpi-task 全量测试bun run --cwd packages/senpi-task test554 pass / 0 fail / 1786 expect()77 文件senpi 兼容性bun run --cwd worktree test:senpi含插件重建、omo-senpitsgo --noEmit与测试套件rebase 到origin/dev后403 pass / 0 fail / 1174 expect()70 文件包级类型检查bun run --cwd packages/senpi-task typechecktsgo --noEmit退出码 0适配层类型检查bun run --cwd packages/omo-senpi typecheck退出码 0插件构建 diff 检查bun run --cwd worktree build:senpi-plugingit diff --check退出码 0生成产物与源码输入一致LSP 门禁04-lsp.txt因 daemon 超时不可用证据明确以编译门禁作为替代而不是虚构一个 LSP PASS。6.2 自审与评审06-self-review.md 按 11 项编程后评审标准逐项核对关键结论单一职责send-schema.ts拥有公开输入 schemasend.ts拥有路由renderers.ts拥有渲染新测试拥有 always-steer 公开契约边界纯净TypeBox 仍是边界解析器删除deliver_as使非法投递方式不可表示变体判别结构化消息与结果变体仍以assertNever穷举 switch无逃生舱未引入任何any、抑制、非空断言或忽略的诊断防御层删除过时的投递校验分支而非叠加冗余检查无参数膨胀validateParams从三个参数简化为一个测试可复现回退 schema/运行时/渲染层改动即可复现三个 RED 失败。规模检查命令bun run packages/omo-senpi/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts 14 changed TypeScript files结果No violations in 14 file(s).评审驱动的清理还包括删除控制层SendManager、SendResultDetails与渲染器中因公开选项移除而不可达的 interrupt/no-op 变体同时保留底层 manager/steering interrupt API 供内部生命周期与对抗性测试使用。七、使用视角always-steer 对编排者的实际意义从使用角度看always-steer 语义让多智能体编排orchestration中的「中途纠偏」成为默认行为运行中纠偏父任务发送普通文本即注入子任务当前回合无需记忆deliver_as选项驻留会话复活已完成resident的子任务收到消息后以同一会话续跑如run_epoch从 0 推进到 1团队消息lead→member 消息总是转向注入接收方的当前回合边界行为kill/cancel/丢失的子任务永不复活one-shot agent如 plan-reviewer在任何状态下拒绝task_send需要新起一个实例跨会话子任务需all_scope: true。这些边界在 send.ts 的DESCRIPTION中均有明确陈述且由 send-team.test.ts、send-shutdown.ts 等测试与实现锁定。八、相关证据与延伸阅读证据主文档.omo/evidence/20260727-senpi-task-always-steer/README.mdRED 捕获01-red-focused-tests.txtGREEN 汇总02-green-focused-tests.txt真实运行验证03a-live-running-steer.txt、03b-live-resident-revival.txt回归门禁05-regression-gates.txt自审报告06-self-review.md核心实现send.ts、send-schema.ts、steering/types.ts契约测试send-always-steer.test.ts关联模块senpi-task 包说明、omo-senpi 适配层需要说明的是指定路径.omo/plans/senpi-task-always-steer.md在当前仓库中不存在本文以仓库中同主题的完整证据文档.omo/evidence/20260727-senpi-task-always-steer/含 README、RED/GREEN 捕获、真实运行日志、回归门禁与自审为绝对主体结合packages/senpi-task源码进行印证与展开内容完整覆盖并超越原证据文档的信息密度。【免费下载链接】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),仅供参考