给一个导出接口增加 CSV 格式,听起来是一件事。实际动手时,可能要先找到现有导出链路,确认字段与编码约定,再改接口、补测试。如果这些工作交给几个代理,真正需要设计的就不只是它们各自的提示词:谁决定拆分,谁拿到什么上下文,结果回到哪里,什么时候继续,什么时候应该停下来?
这也是读oh-my-openagent源码时值得追的一条线。代理的名字很容易记住,任务在它们之间怎样流动,才更能说明编排层做了什么。
2026-11-01 修订:本文更新了项目版本边界,删除无法对应固定版本的文件数、Hook 数和角色总数,重新核对了任务派发与续跑逻辑。下文是源码分析,没有在本次修订中运行项目,也不提供速度或成本排名。
先分清现在读的是哪一版
仓库仍是code-yeongyu/oh-my-openagent。本次固定到提交13a25928c58ac7b481ac711e376d225899c74013,避免把持续变化的开发分支当成不变的说明书。
一个重要变化是:当前 README 主推独立的omo命令,运行在项目的 senpi 引擎上;文档把这条路径称为 OmO Native。OpenCode 版与它有迁移和共存关系,不能再把整个项目概括成“只运行在 OpenCode 上的插件”。README 与迁移说明
下面选择OpenCode 1.x 适配路径追踪一次任务,因为它能把旧文讨论的机制连接到具体代码。这个范围也有必要说清:该提交为 OpenCode 2 提供的setup入口只完成插件注册并记录提示,并没有运行同一套 V1 完整逻辑。看到相同项目名,不代表不同宿主走的是相同执行链。实际入口
一条消息不会自动走遍所有代理
OpenCode 适配层的入口会组装管理器、工具和 Hook,再把它们接到宿主接口上。plugin-interface.ts里可以直接看到消息处理、消息变换、系统提示变换、工具执行前后以及事件处理等入口。接口组装
Hook 提供的是介入时机。某个 Hook 是否启用、这次事件是否满足条件,还要看配置与具体处理函数。不能因为仓库里存在这些模块,就画成每次请求必定经过全部环节的流水线。
以 Sisyphus 主会话为例,它可能直接处理小修改,也可能调用task委派工作。task的参数把两个维度分开:
subagent_type:点名一个专门角色。category:按任务类别选择执行配置。
工具声明要求二选一。代码接着分别进入角色解析或类别解析,而不是把两者当作同一个名字的不同写法。task 工具
图 1:按源码归纳的委派关系。它描述任务已经被决定委派后的路径,不表示每个请求都必须创建子代理。
还有一个容易混淆的地方:**工具中的类别分支,是在主代理已经给出类别参数之后执行的。**它不能证明系统对任何自然语言需求都能选出最合适的类别。任务怎样拆、何时需要顾问、一次该给多少上下文,仍受到模型、提示和已有信息的影响。
这一区分很重要。程序可以检查参数、限制工具、启动会话;任务判断是否合理,还需要看真实执行结果。
Category 选配置,执行角色不一定随之改变
旧文把角色列成了一个固定团队,却没有给出对应版本与注册口径。工作领域、任务类别和实际代理很容易因此混在一起,让人以为每增加一种工作,就得再加一个代理。
实际的类别解析更值得看。category-resolver.ts会合并类别配置,查询可用模型,处理用户覆盖、提示追加与回退链;这条路径最终返回的执行角色是Sisyphus-Junior。因此,不同类别可以让同一个执行角色采用不同的模型和任务约束。类别解析
拿前面的 CSV 导出来说,可以先把“查清现有约定”和“按已确认约定实现”分开。前者的交付是证据与位置,后者的交付才是代码和测试。是否值得拆成两个代理,要看搜索范围与依赖;如果只是一个文件里的局部修改,拆分本身也有传递上下文的成本。
Category 的价值,在于把任务执行配置集中管理。它并不自动保证选到的模型更便宜、更准确;回退之后有没有改变输出质量,也需要单独观察。
后台任务有独立会话,但未必有独立工作区
选择后台执行后,executeBackgroundTask会把任务描述、提示、父会话、模型、技能和权限等信息交给BackgroundManager。管理器先建立pending任务并进入并发队列,再在实际启动时创建子会话。后台派发
这里有一段值得停下来看的逻辑:子任务目录取input.cwd,没有提供时,使用父会话目录。也就是说,新会话不等于自动获得隔离的文件副本。几个代理如果落在同一目录,又同时改同一文件,仍然需要处理写入冲突和集成问题。目录与会话创建
这给实际使用带来的建议很具体:并行检索可以按证据分工;并行修改要先划清模块或工作区,并明确谁负责整合。多开几个会话,解决的是执行并行,不能替代任务边界设计。
工具限制也应按实际代码理解。共享限制表里,Sisyphus-Junior的task被禁用;后台启动还会合并角色限制和用户权限。它是一项具体的工具限制,旧文直接写成“不能继续委托”过于笼统——同一启动逻辑里还存在call_omo_agent等能力,是否能使用要继续看角色与宿主约束。角色限制、启动时的工具配置
续跑不是“只要没做完,就无限重试”
旧文把 ULW、Boulder 与 TODO 续跑合并成了一种机制,表达得太满。阅读时可以先把三个问题分开:工作方式如何被提示,计划状态怎样记录,空闲之后是否应该再启动一轮。
在这个提交中,Boulder 包把状态路径定义为.omo/boulder.json,承担计划跟踪所需的状态记录。todo-continuation-enforcer则有自己的空闲事件处理器,检查当前会话是否需要继续。前者保存工作状态,后者判断一次续跑机会,职责有关联但并不相同。Boulder 路径、续跑判断
图 2:续跑条件的简化关系,不是逐行控制流。源码还包含恢复、上下文限制、冷却和失败次数等检查。
例如,代码会在用户问题尚未回答、后台任务仍在运行、会话被取消或出现不可重试错误时跳过续跑,并调用停滞检测决定是否还应继续。这个设计比“自动再发一句继续”复杂得多,也说明自动化需要停止条件。
假如 CSV 字段顺序还等人确认,代理应该把这个决定交回来;假如只是测试没跑完,就应沿原任务继续验证。两种情形都叫“还没做完”,处理方式却不能一样。
评价编排,最后还得回到交付
这次重读后,我更愿意用几项可观察的问题评价它:任务是否被合理拆开,角色和模型配置是否匹配,子任务结果是否带证据,修改是否正确整合,失败时能否停在可恢复的位置。
源码提供了这些问题的实现入口,但没有替我们给出性能结论。我在另一篇 DSH Super-agent 分析里讨论过人工注意力成本;这里也一样,代理产出增加之后,还要看审查、返工与协调是否一起增加。
读多代理项目,可以先挑一个具体任务,沿着“谁决定、如何派发、结果去哪、何时停止”走一遍。比起记住角色总数,这条链更容易迁移到自己的开发流程里。