记录一次并发数的自检(2026-09-08)
记录一次并发数的自检(2026-09-08)
免费的东西,真正贵的是它的上限
这天的活是一整串:先把 WorkBuddy 的模型档位摸一遍,给 wb.py 定一个免费的默认模型,再回头给这个免费模型补上并发自检。表面上看都是配置,实际上是在回答同一个问题——免费的东西,边界在哪里,边界被撞到的时候,谁该说话。
前面那半段是"选哪个",后面那半段是"选完之后,用超了怎么办"。我先做的其实是后面那半段,因为它是真正会让我半夜爬起来的那种问题。
3 流是那根线
先把线找出来。之前核实 WorkBuddy 免费模型的时候已经确认过,hy4-preview 系列是有一个并发上限的:
hy3—— 项目默认的免费模型,0 credit 成本,没有节流(no throttling)hy4-preview系列 —— 当前促销免费,并发上限约 3 流:3 流以内正常运行,4 流及以上开始排队,5 流及以上触发超时
这组数字就是自检的阈值来源。核实过程里还有两条写得很死的边界:hy4-preview 家族的 stream idle timeout 固定是 5 分钟;而产品定价文档里**只把 hy3 列为 0 credit 成本,并不列 hy4-preview**。也就是说,"免费"这件事本身是有时效的,它是个促销状态,不能当成常量写进脑子里。
真正的问题是:这根线只在我的记忆里。 哪天我一口气起五个后台任务,看到的是它们一个个卡住,而 wb.py 什么都不说。所以这天的核心动作很具体——把"已经跑了几个 hy4"这件事,从记忆里搬进工具里。
把自检写进 wb.py
实现不复杂,但有几个地方必须克制。我在 wb.py 里加了三个辅助函数:
_is_running—— 判断一条任务记录是不是"正在跑"_hy4_concurrency_stats—— 统计当前并发,算出指标_warn_hy4_concurrency—— 到阈值时打警告
然后把 _warn_hy4_concurrency 插进 cmd_run,紧跟在已有的 _warn_paid_model 之后。这里定了一条原则:不动原有警告流的任何一行,新警告只负责"加在后面"。付费模型警告是提醒"你选了要花钱的模型",并发警告是提醒"你可能要排队了",两件事互不干扰,谁也别去改谁。
再留一个退出口:--allow-hy4-concurrency。当明确知道自己在干什么、就是要多起几个任务的时候,可以显式把这条警告关掉。默认开启、可显式关闭,比反过来安全。
cmd_list 也一起改了。--json 输出里新增了 summary 字段:
1 | running_total // 正在跑的任务总数 |
另外每条记录都带上它自己的 model。人类可读的列表输出里,如果 concurrency_warn 为真,就补一行 stderr 提示——不用看 JSON 也能一眼知道。
一条差点写错的边界
验证的时候,我特意盯着一个地方:**ok / failed / killed 这些"已结束"的记录,绝对不能算进并发。**
一开始很容易顺手写成"有几条 hy4 记录就算几个",但那样会把历史记录也数进去,警告会疯掉——明明没有任务在跑,屏幕上却一直喊"超了"。正确的口径是只看"正在跑"的。这条后来被直接写进了经验:计数只数在跑的,别数跑完的。
端到端验证用测试用的 meta.json 覆盖了四种情形:
| 正在跑的 hy4 数 | 预期行为 |
|---|---|
| 0 / 1 / 2 | 不警告 |
| 3 | 警告触发 |
3 + --allow-hy4-concurrency |
警告被抑制 |
| 含 ok / failed / killed 记录 | 不计入计数 |
四种都对上了。SKILL.md 里也补了一节 **"Auto-detection of hy4-preview fanout"**。
顺带把 MODEL_COST 里的 hy4-* 条目也改成"免费 + 并发限制",并带上合适的并发限制说明。警告要么准,要么别响——一个免费模型要是被软警告当成付费的,那是另一种误报。
顺带把模型档位摸清了
自检只是这天的一半。因为要给"并发满了"准备一个退路,我把整套模型档位又过了一遍。
摸的方式是先克制地取证,不靠印象:从官方产品配置文件里查模型的定价与可用性;再针对文档里没有列出的模型,去核实它真实的计费状态,确认是不是正处在促销免费期——不能只信"官方列了哪些免费模型"。证据够了,再跑两组测试:一组是把同一份端到端冒烟负载打在所有候选模型上,量性能和成本;另一组是并行突发测试,专门去撞候选免费模型的并发上限和超时阈值。
结论落到一个免费默认上,并且用三层来兜:
- 代码里硬编码
WB_DEFAULT_MODEL,指向免费的hy3 - 用
setx把WB_DEFAULT_MODEL写进HKCU\Environment,让新开的 shell 也认 - 加一个"软警告"——只有明确选了已知付费模型时,才往 stderr 打一行字,不阻塞
解析顺序固定成:**--model CLI 参数 > WB_DEFAULT_MODEL 环境变量 > 硬编码默认值**。做法是先让环境变量作为 argparse 的默认值,再让显式 CLI 参数永远压过它。
警告这块验证得很细:选付费模型,出警告;选免费的 hy3,不出;选一个未知模型,也不出——因为对未来还没出现的模型不能瞎报警,宁可漏,不可误伤。
验证用例覆盖了所有选型情形:CLI 覆盖、环境变量生效、默认使用、未知模型;同时确认了与 codebuddy CLI 的 --model 参数兼容。测试产物都落进了 wb_runs/ 目录留档。
最后落成一张选型表,写进 SKILL.md 的模型选型推荐矩阵。核心两行是:
| 模型 | 定位 | 说明 |
|---|---|---|
hy3 |
项目默认 | 完全免费,0 credit 成本,无节流 |
hy4-preview 系列 |
促销免费 | 并发上限约 3 流,4 流起排队,5 流起超时 |
并且定下项目约定:委托编码任务默认用 hy3,除非明确要求更强的模型。 同时,超过并发上限的并行后台任务不要用 hy4 系列,避免排队和超时——因为并发上限是硬约束,不是靠运气绕过去的。
没清干净的尾巴
清点的时候顺手发现一个没处理的问题:wb_runs/ 里还躺着一批残留的闲置任务记录(stale runs),是更早几天生成的。它们不影响功能,但会让并发的统计口径变脏。
当天的处理是"先记下来":考虑给 wb.py 加一个 gc 子命令,自动清理这些残留。这天没做,但挂进了待办。
同一天还有一条做了的事:SESSION_LOG_11 追加进了 craw_app1_wz 目录,把 wb.py 这一串配置决策的正式日志按 append-only 的方式存下来,供 Codex 侧加载参考。改代码的同时留一条决策记录,比事后翻 diff 便宜。
最后
这天的收获分三层。
最表面的是那三个函数和一条警告——一个能用的并发自检,从"我心里有数"变成"工具会说话"。
中间一层是那组阈值:3 流、5 分钟。这些数字不写下来,下次还是会眼睁睁看着任务排队却不知道为什么。免费模型的成本不在钱上,在它的上限上——钱花光了你会心疼,并发超了它只是静静地卡住,更难受。
最里面一层,是"把默认值钉死在免费档"这个决定本身。
一个工具默认用什么模型,看起来是配置细节,但它决定了在你不注意的时候,钱和并发是被谁消耗的。默认值就是"无意识状态下的行为",所以它必须是保守的:先用免费的,要更强的能力必须显式说出来,而且说出来的那一刻要有警告。把兜底放进默认值里,比指望自己每次都记得加参数,可靠得多。
这天做的其实不是新增功能,而是给一个已经免费的东西,补上它缺的那句提醒。
那天之后,wb.py 的默认模型、警告、并发自检,三样东西是绑在一起变的——因为它们的答案本来就是同一个:你要用掉多少资源,工具得先告诉你。