oh-my-openagent:后台任务全局并发限制配置 maxBackgroundAgents 的全链路验证策略
本文以仓库中 .agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md 这份验证策略文档为主体,完整还原“为 oh-my-opencode(omo)插件的 background_task 配置新增一个全局后台 Agent 并发上限 maxBackgroundAgents”这一变更应当如何被验证:从静态分析、单元测试矩阵,到配置加载链路核对、构建验证、边界情况推演与 CI 回归。结合当前仓库中 BackgroundTaskConfigSchema 与 ConcurrencyManager 的真实实现,读者可以掌握一套“可选配置字段 + 并发限流器”类变更的可复制验证方法论。
1. 验证对象:一次“可选字段”变更的完整上下文
该验证策略文档出自 work-with-pr 技能的评估工作区,对应的任务(见同目录 eval_metadata.json)是:在 oh-my-opencode 插件配置中新增一个限制“同时可运行的后台 Agent 数量”的选项,放入插件配置 schema,提供校验,并让后台管理器(background manager)真正尊重该限制。
这类变更看似只是“加一个字段”,实际上横跨了 oh-my-opencode 插件的四层结构:
| 层 | 涉及代码 | 验证要点 |
|---|---|---|
| 配置 schema 层 | background-task.ts | 新字段的取值校验(最小值、整数、可选性) |
| 类型层 | BackgroundTaskConfig = z.infer<...> |
类型自动推导,无需手写 |
| 并发管理层 | concurrency.ts | 全局上限与既有“按模型限流”的交互 |
| 装配层 | create-managers.ts 等 | 配置对象能一路透传到限流器 |
下面按验证策略文档的六个章节顺序展开,并在每一层补充当前仓库源码中的实际证据。
2. 现状基线:BackgroundTaskConfig 与 ConcurrencyManager 长什么样
在理解验证方案前,先确认验证的落点。当前仓库中的 BackgroundTaskConfigSchema 是一个全部字段可选的 Zod 对象:
// packages/omo-opencode/src/config/schema/background-task.ts
export const BackgroundTaskConfigSchema = z.object({
defaultConcurrency: z.number().min(0).optional(),
providerConcurrency: z.record(z.string(), z.number().min(0)).optional(),
modelConcurrency: z.record(z.string(), z.number().min(0)).optional(),
maxDepth: z.number().int().min(1).optional(),
maxLiveDescendantsPerRoot: z.number().int().min(0).optional(),
staleTimeoutMs: z.number().min(60000).optional(), // 默认 3 分钟,最小 1 分钟
messageStalenessTimeoutMs: z.number().min(60000).optional(),
taskTtlMs: z.number().min(300000).optional(),
sessionGoneTimeoutMs: z.number().min(10000).optional(),
taskCleanupDelayMs: z.number().min(60000).optional(),
syncPollTimeoutMs: z.number().min(60000).optional(),
maxToolCalls: z.number().int().min(10).optional(), // 熔断器默认 200
circuitBreaker: CircuitBreakerConfigSchema.optional(),
})
export type BackgroundTaskConfig = z.infer<typeof BackgroundTaskConfigSchema>
关键事实有两点,它们直接支撑验证策略文档的两条论断:
- 类型自动推导:
BackgroundTaskConfig由z.infer从 schema 推导而来。验证策略文档据此断言“新增字段会自动更新类型”,无需手工同步接口定义——这一点在 background-task.ts 中可直接印证。 - 全部字段可选:新加一个 optional 字段不会破坏任何现有消费者的解析,这正是文档中“backward compatible”结论的 schema 依据。
顶层配置将该 schema 挂载在 background_task 键下(见 oh-my-opencode-config.ts):
background_task: BackgroundTaskConfigSchema.optional(),
与并发直接相关的核心类是 ConcurrencyManager。当前实现只做了按模型/Provider 维度的限流:
getConcurrencyLimit(model)的查找顺序为:modelConcurrency[model]→providerConcurrency[provider]→defaultConcurrency→ 兜底常量5,且配置值0一律解释为Infinity(不限制);acquire(model, taskId?)超限时把调用方挂入按 key 分桶的等待队列,队列条目带settled标志防止release()与cancelWaiters()双重结算;release(model)优先把槽位“交接”给队首等待者(count 不变),无等待者才递减计数;clear()用于管理器停机时拒绝全部等待者并重置计数。
从当前源码结构看,ConcurrencyManager 只有 per-model 计数(counts: Map<string, number>),尚无跨模型的“全局槽位”概念。验证策略文档中出现的 getGlobalLimit() / getGlobalCount() 正是本次变更计划引入的扩展点——这一点决定了第 4 节测试矩阵中哪些是新增用例、哪些是回归用例。
3. 第一道关:静态分析
验证策略文档的第一部分要求两条静态检查,对应文档原节的 “TypeScript Typecheck” 与 “LSP Diagnostics”。
3.1 TypeScript 类型检查
bun run typecheck
文档给出的三条预期:
- 没有引入任何新的类型错误;
- 由于
BackgroundTaskConfig由 Zod schema 经z.infer推导,新增maxBackgroundAgents字段后类型自动更新; - 所有既有的
BackgroundTaskConfig消费者保持兼容(新字段是 optional)。
结合源码可以进一步解释这条策略为什么“够”:schema 文件的导出面只有 BackgroundTaskConfigSchema 与推导出的类型(见 background-task.ts),下游消费方拿到的都是同一个推导类型,因此“字段可选 ⇒ 消费者无需改动”是一个可以从类型系统直接保证的性质,typecheck 通过即验证了这一点。
3.2 LSP 诊断核对变更文件
文档要求对以下四个变更文件逐一确认无诊断错误(原路径为插件包内相对路径,此处已转换为仓库根目录相对路径):
这四个文件恰好构成“schema ↔ 实现”与“测试 ↔ 测试”两两配对的最小变更面,是判断变更是否“越界”的清单基线。
4. 第二道关:单元测试矩阵
4.1 Schema 校验用例(background-task.test.ts)
文档给出的执行方式:
bun test packages/omo-opencode/src/config/schema/background-task.test.ts
计划新增的校验矩阵完整继承如下(maxBackgroundAgents 语义为“全局最多同时运行的后台 Agent 数”):
| 用例 | 输入 | 期望 |
|---|---|---|
| 合法值 (10) | { maxBackgroundAgents: 10 } |
解析为 10 |
| 最小边界 (1) | { maxBackgroundAgents: 1 } |
解析为 1 |
| 低于最小值 (0) | { maxBackgroundAgents: 0 } |
抛出 ZodError |
| 负数 (-1) | { maxBackgroundAgents: -1 } |
抛出 ZodError |
| 非整数 (2.5) | { maxBackgroundAgents: 2.5 } |
抛出 ZodError |
| 未提供 | {} |
字段为 undefined |
对照现状有助于理解每个用例的位置:当前的 background-task.test.ts 已按同一模式覆盖过 defaultConcurrency: 0(合法,语义为不限)、maxDepth: 0(低于 min(1) 抛错)、syncPollTimeoutMs 的合法值/59999ms 下限/字符串类型等分支。新增字段用例复用同一测试骨架即可,这也是“现有回归零改动”能成立的原因之一。
4.2 ConcurrencyManager 用例(concurrency.test.ts)
bun test packages/omo-opencode/src/features/background-agent/concurrency.test.ts
文档计划的全局限流行为矩阵:
| 用例 | 前置条件 | 期望 |
|---|---|---|
| 无配置 = 无全局限制 | 未设置 maxBackgroundAgents |
getGlobalLimit() 返回 Infinity |
| 配置被尊重 | maxBackgroundAgents: 3 |
getGlobalLimit() 返回 3 |
| 跨模型阻塞 | 全局上限 2,已占用 model-a + model-b,再申请 model-c | model-c 被阻塞 |
| 上限内放行 | 全局上限 3,占用 3 个不同模型 | 全部成功 |
| per-model 与全局交互 | per-model 1、全局 3,对 model-a 申请两次 | 被 per-model 阻塞,而非全局 |
| 释放解除阻塞 | 全局上限 1,占用 model-a,排队 model-b,释放 model-a | model-b 继续 |
| 无全局限制 = 不执行全局检查 | 未配置,占用 6 个不同模型 | 全部成功 |
| clear 重置全局计数 | 占用 2 个后 clear | getGlobalCount() 为 0 |
矩阵里最有信息量的两条是“跨模型阻塞”与“per-model 与全局交互”,因为它们精确刻画了两级限流的短路顺序:先过 per-model 限流、再过全局限流,谁先触顶谁先阻塞。这与当前 ConcurrencyManager 中 acquire() 对单一 key 的判定结构一致(见 concurrency.ts),新增全局判定相当于在原有 key 计数之外再叠加一个跨 key 的全局计数器。
“释放解除阻塞”一栏则可以直接映射到现有代码语义:release() 的槽位交接逻辑(跳过 settled 条目、命中队首则直接 resolve)已经保证了“释放即唤醒”,全局计数只需复用同一交接路径(见 concurrency.ts)。
4.3 既有测试回归
文档要求以下三个测试套件在不改动的前提下继续全部通过:
bun test packages/omo-opencode/src/features/background-agent/concurrency.test.ts
bun test packages/omo-opencode/src/config/schema/background-task.test.ts
bun test packages/omo-opencode/src/config/schema.test.ts
注意第三项 schema.test.ts(顶层聚合 schema 的测试):它守护的是“顶层配置里加一个字段不破坏整体 safeParse 行为”这一集成性质,与 oh-my-opencode-config.ts 中 background_task 的挂载点相呼应。
5. 第三道关:集成验证(配置加载链路)
验证策略文档的第三部分给出了一条五步链路,用于确认新字段能从磁盘上的 JSON 一路流到限流判定:
- Schema → Type:
BackgroundTaskConfig类型经z.infer自动包含maxBackgroundAgents; - 配置文件 → Schema:配置加载入口使用
OhMyOpenCodeConfigSchema.safeParse(),其中包含BackgroundTaskConfigSchema; - 配置 → Manager:管理器装配模块将
pluginConfig.background_task整体传入BackgroundManager构造函数; - Manager → ConcurrencyManager:
BackgroundManager构造函数把配置传给new ConcurrencyManager(config); - ConcurrencyManager → 执行:
acquire()经由getGlobalLimit()读取config.maxBackgroundAgents。
文档的关键论断是:第 2~4 步无需任何改动,因为新字段是 optional,且既有装配代码传递的是整个 BackgroundTaskConfig 对象而非逐字段搬运。用当前仓库印证这条链路:构造函数 constructor(config?: BackgroundTaskConfig) 确实是“整对象注入”(见 concurrency.ts),管理器侧通过 create-managers.ts 组装(见 create-managers.ts),配置侧的顶层挂载见 oh-my-opencode-config.ts。因此验证工作量集中在第 1 步(schema)和第 5 步(执行),中间三步只需“链路走查”而非新增测试。
5.1 手动配置解析验证
文档还给出了一个不依赖完整插件启动过程、直接对 schema 做管道验证的最小实验(原命令中路径相对插件包,此处转换为仓库根相对路径,并同步修正为 Bun 的 ESM 环境写法):
echo '{ "background_task": { "maxBackgroundAgents": 3 } }' | bun -e "
const { BackgroundTaskConfigSchema } = await import('./packages/omo-opencode/src/config/schema/background-task.ts');
const result = BackgroundTaskConfigSchema.safeParse(JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf-8')).background_task);
console.log(result.success, result.data);
"
预期输出 true 且解析结果包含 maxBackgroundAgents: 3。这条命令的价值在于把“配置文件 → Schema”一步从代码走查变成了可执行断言。
6. 第四道关:构建验证
bun run build
文档对构建的验证点:
- 构建成功;
- 如涉及 schema 的 JSON 输出产物,其中应包含新字段。
对应到本仓库,omo-opencode 的发布形态包含独立的平台包(见 packages/ 下的各 oh-my-opencode-<platform> 目录),构建产物的完整性本身就是发布前门禁的一部分;验证策略文档将 build 放在单元/集成之后,作为“可发布”的最后确认。
7. 第五道关:边界情况推演
文档第六节前给出的边界矩阵是整篇策略的精华,完整继承如下:
| 边界情况 | 期望行为 |
|---|---|
未设置 maxBackgroundAgents |
不执行全局限制(向后兼容) |
maxBackgroundAgents: 1 |
所有模型合计一次只允许 1 个后台 Agent |
| 全局值 > 所有 per-model 上限之和 | 全局限制永不触发(per-model 更紧) |
| per-model 比全局更紧 | 先被 per-model 阻塞 |
| 全局比 per-model 更紧 | 先被全局阻塞 |
| 某模型释放使另一模型解除阻塞 | 全局槽位释放,另一模型的等待者继续 |
| 管理器停机且存在全局等待者 | clear() 拒绝所有等待者并重置全局计数 |
| 并发 acquire/release | 无竞态条件(JS 单线程事件循环) |
对照当前实现,可以解释最后三行为什么有把握:
- “
clear()拒绝所有等待者”与 ConcurrencyManager.clear() 的现有行为一致——它遍历所有队列调用cancelWaiters()再清空计数,全局计数只需并入同一清理路径; - “释放使另一模型解除阻塞”依赖全局队列同样参与
release()的交接逻辑; - “无竞态”的前提是 JS 单线程事件循环:
acquire的“检查 + 递增”之间没有await让出点(见 acquire 实现),因此计数不会在判定中途被篡改。
8. 第六道关:CI 回归
文档第六部分说明:现有 CI 工作流(ci.yml)会依次执行:
bun run typecheck—— 类型检查;bun test—— 全量测试,包含新增用例;bun run build—— 构建验证。
结论是无需为本次变更修改任何 CI 配置——新增的 schema 字段、全局限流逻辑与测试都会自然落入既有门禁的覆盖面内。这与前几节形成闭环:本地执行清单与 CI 门禁完全同构,本地绿即 CI 预期绿。
9. 小结:一份可复用的验证清单
把六个章节压缩成执行顺序,得到面向“新增可选配置字段 + 限流器行为变更”的通用清单:
- 静态:
bun run typecheck+ 四个变更文件的 LSP 诊断(第 3 节清单); - 单元:schema 六态校验矩阵(合法/边界/非法/缺省)+ ConcurrencyManager 八条全局行为用例 + 三个既有套件零改动回归(第 4 节两张表);
- 集成:走查五步配置链路,仅第 1、5 步需要新逻辑;用
bun -e管道命令做一次可执行的 safeParse 验证(第 5 节); - 构建:
bun run build,确认产物与 schema JSON 输出含新字段(第 6 节); - 边界:逐条核对第 7 节矩阵,重点验证两级限流的短路顺序与
clear()清理语义; - CI:确认 typecheck/test/build 三道门禁无需变更即可覆盖。
这套策略文档的示范价值在于:它没有停留在“加测试”的层面,而是把“哪些链路无需改动”也写成了论断(第 2~4 步装配零改动、CI 零改动),从而把验证预算集中投放到 schema 判定与全局限流语义这两个真正变化的点上。对于 oh-my-opencode 这类以 Zod schema 驱动配置、以 ConcurrencyManager 驱动并发语义的插件系统,这就是新增限流类配置项时的标准验证路径。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00