auto-dev — 一个持续运作的队列,自动实现并合并 Issue
功能概述
implement-issue 将一个计划的 Issue 转化为一个可用的 PR;merge-pr 将一个 PR 合并。auto-dev 大规模地监督它们:N 个后台工人各自拥有一个完整的 Issue(implement-issue <N> → merge-pr <PR>);当某个工人的 PR 被合并后,该工人即被退役,并立即调度一个新工人处理队列中的下一个 Issue — 因此始终保持 N 个 Issue 在处理中,无需您手动干预。
其价值在于简单的“在一个循环中运行它们”所缺乏的编排能力:
- 冲突避免 — 每个并发工人处理的是不同代码区域的 Issue,因此分支很少发生冲突。
- 排序 — 先处理小的、独立的 Issue(快速获胜,冲突更少),然后是中等规模的。默认排除大/超大以及需要手动 QA 的 Issue。
- 强制合并 — 工人容易在“PR 就绪”阶段停止(只完成了一半工作)。auto-dev 会在每次信号时从 GitHub 验证真实的合并状态,并重新驱动任何停滞的工人。
- 范围外捕获 — 工人如果遇到不相关的错误,仅在
implement-issue的例外条款下就地修复(仅限于该 PR 已修改文件中的且小的错误,并在自己的提交中修复),否则通过create-issue提交,而不是丢弃它,并按照共享的提交标准(../_shared/filing-bar.md)提交,以便 N 个工人都不会以不同的标准提交。待办事项列表保持真实且可清空 (#410)。 - 生命周期卫生 — 完成的代理被关闭;状态文件在重启后仍然存在,因此队列是可恢复的。
自主性契约
用户启动此流程后即可离开 — 他们看着待办事项列表清空,而不需要批准每一步。无需干预:选择合理的默认值,明确说明,然后继续。只有在遇到真正的障碍时才停止:
gh未认证,或者您没有推送/合并权限。- 没有符合条件的 Issue(过滤后队列为空)— 报告待办事项已清空。
- 工人的诚实硬性障碍(无法通过的测试、双方重写相同逻辑的冲突、工人无法自行授予的必需审批)。明确指出;不要强行合并。
绝不要伪造进度:复选框、“就绪”标记或合并都声明工作已完成 — 每个都需要证据支持,正如子技能所要求的那样。Squash 合并是唯一不可逆的操作 — 它只通过 merge-pr 在通过检查、可合并的 PR 上执行,永远不会在此处通过 --admin 覆盖。
输入
- 并发数
N— 同时工作的工人数。默认为 3(3-4 是最佳点;更多并发 = 更多争用和更多需要保持不相交的区域)。 - 排序 — 默认按工作量小→大排序。尊重用户的任何指定。
- 符合条件的集合 — 默认为:已打开且 (a) 包含实现计划、(b) 是代码任务(非手动“QA”/“手动验证”)、(c) 在工作量上限范围内(默认 ≤ 中等)的 Issue。尊重缩小范围(例如“仅
studioIssue”、“标记为priority: high”)。 - 心跳 — 通常通过
loop以动态模式启动,因此循环的自定步调唤醒就是心跳。如果直接调用,请自行设置唤醒。 - 模型层级 — 默认为 分层,并非全部 Opus(参见 Token 经济):机械性/文档类任务使用最便宜但能力足够的模型,典型错误使用中型模型,顶级模型保留给跨领域/困难工作 + 故障升级时使用。这是单个最大的成本杠杆。尊重覆盖(“全部使用 Opus”、“所有任务使用 Sonnet”)。
Token 经济 — 廉价运行队列
一次长时间运行的成本大约是 ~83% 的每次调用上下文缓存读取,~16% 的输出 — 您为上下文体积 × 调用次数付费,而不是为推理付费。应用以下规则;完整原理和测量结果请参见 references/token-economics.md。
在一次真实的 19 次合并运行中测量(一个 .NET/Blazor 仓库,N=3,21 个工人会话,8.63 亿 Tokens): 224 次调用/会话 · 上下文大小 30K → 350K · 每次调用的平均上下文为 181K · 每次调用 0.55 个工具调用。缓存读取占 Tokens 的 98.3%。成本 = 所有调用上下文的 Σ,因此后期的调用成本约是早期的 10 倍,而调用次数是超线性的 — 它同时驱动了两个因素。请根据此标准(而不是凭直觉)对杠杆进行排名。
该标题统计的是 工人 会话 — 而协调器占了账单的三分之一。 将监督器自身的内容也折叠到同一个汇总中:一个会话,551 条助手消息,每次消息的平均上下文约 210K,占运行总列表等效成本的 33% — 比所有顶级工人会话的总和还多 — 并且在整个 19 次合并过程中从未压缩过一次。工人的花费同样偏斜:37 个工人会话中,前 3 个占所有工人成本的 32%,而最差的是一个在中间层级上的
effort: mediumIssue,因此标签和层级都无法预测。这两个测量结果,以及约束它们的两个计数预算,都在 references/token-economics.md 的 § 会话长度 中。
1. 将每个工人拆分为两个子代理:实现,然后合并 — 唯一一个经测量有效的杠杆。A/B 验证,约占所有工人 Tokens 的 11%。 阶段 1(/auto-dev-worker <N>)停在 PR 就绪时;阶段 2(/auto-dev-merge <PR>)在一个全新的上下文中将其合并。
| 合并阶段 | 调用次数 | 每次调用的平均上下文 | 缓存读取 |
|---|---|---|---|
| 之前(18 个工人,在实现会话内部) | 27 次平均 | 247K | 每个工人 6.6M |
| 之后(v2 阶段 2,全新会话) | 24 | 71K | 1.7M |
调用次数几乎相同,因此每次调用的上下文是全部差异所在:合并阶段减少了 74%,约占工人总花费的 11%。通过一个文件在阶段之间传递 PR 编号,该文件由阶段 1 的提示命名(回退方案:解析报告行,然后 gh pr list --head)。额外好处:在基线运行中观察到的两次完成后楔入都发生在长会话的后期调用中 — 较短的会话楔入更少。
2. 工具调用批处理 — 尝试过但效果不佳。不要指望能从中获益。 一个带有示例的硬性批处理规则被添加到工人提示中并进行了 A/B 测量:密度从 0.556 → 0.537 次调用/调用(阶段 1)— 即没有变化。保留该规则(它不耗费任何成本),但不要期望它能节省预算。诊断结果:密度低于 1.0 并不意味着可能被批处理的调用没有被批处理。约 45% 的调用没有进行任何工具调用 — 它们是思考/叙述性调用 — 其余大部分是真正的顺序操作(编辑 → 构建 → 读取错误 → 修复)。可恢复的浪费是没有产生工具调用的调用次数,这是一个输出冗长的问题,而不是批处理的问题。如果您要解决这个问题,应该解决冗长问题(思考长度、步骤之间的叙述),并测量调用/次来检查您是否改进了它。
3. 将大的命令输出保留在上下文之外 — 经测量,一个完整的测试运行从 4,262 Tokens 减少到约 2 Tokens。完整的 dotnet test 输出约 17K 个字符,并且在后续的每个调用中都会被重新读取。强制要求:将输出记录到文件,仅在上下文中放置 EXIT + 计数,仅在失败时 grep 该文件。明确禁止通过 tail/head 管道传输命令 — 这会在仍然消耗调用次数的同时截断证据。
诚实范围: 每次运行减少的量是精确且已验证的,但总体收益取决于工人运行测试套件的频率。在 A/B 测试中,最大的单个 Bash 结果从 25.2K 字符下降到 10.5K 字符,而每次调用的工具结果量几乎没有变化(722 → 710 字符)— 因为 A/B 测试的 Issue 是一个很少运行测试的文档任务。预计在测试密集型的 Issue 上会有真正的回报。
4. 根据任务对模型进行分层 — 真实有效但依赖于层级,详见 token-economics.md:中等模型和小模型之间的差距不大($0.419/Mtok 对比 $0.168/Mtok ≈ 2.5 倍);顶级模型和中等模型之间的差距很大($2.376/Mtok 对比 $0.419/Mtok ≈ 5.7 倍)。优先考虑用于跨领域工作;对于常规错误影响较小。根据 Issue 标签路由(根据运行时的中小/顶级三元组调整名称,例如 Haiku/Sonnet/Opus):
| Issue 形态(按标签) | 层级 |
|---|---|
文档/模板/清单、格式和快照重新生成、priority:low+effort:S 单行守卫 | 小型(例如 Haiku) |
大多数错误:发射器/验证器/解析器守卫、Studio TS、CLI、LSP — effort:S/M;默认情况下的大 Issue | 中型(例如 Sonnet) |
| 较低层级未能使此 Issue 通过测试 | 顶级(例如 Opus),被动升级(一次) |
| 跨领域(多个区域/发射器)、歧义/设计 — 维护者知道很困难的工作 | 顶级(例如 Opus),预测性最后手段 |
协调器(你)保持在顶级模型上,但保持其每次调用的上下文较小(杠杆 2)。
5. 减少每次调用重新读取的内容。
- 修剪固定的前导内容 — 它在每个工人的每次调用中都要支付。
CLAUDE.md大小为 44KB(约 11K Tokens),一次运行中一个文件就花费了 5200 万 Tokens;将部署/密钥/kubectl 参考材料移至链接文档中,并留下一个指针。 - 没有每个 Issue 的任务列表 — 它会无限制增长并在每次调用时重新注入。状态文件是您唯一的工作记忆。
- 有意识地进行压缩,以计数的节奏进行,而不是凭感觉。 节奏整数只有一个家,references/token-economics.md § 两个预算;步骤 4 从状态文件的合并计数器中触发它,这个计数字段也是重新调查节奏已经使用的。不要在此处重复该数字 —
tests/auto-dev-cost-budgets/test.sh如果重复则会构建失败。为什么计数: 成本是 Σ(上下文 × 调用次数),因此运行的尾部是超线性的,而这个规则所取代的规则 —/context百分比或一个宽松到在 19 次合并运行中从未触发过的节奏 — 没有约束任何东西,并且在单个会话中花费了 213 美元。始终使用聚焦指令进行压缩,例如/compact 保留 slot→Issue/PR 映射、合并计数器、队列顺序、提交的后续任务。在任何紧凑 //clear/loop重新触发后的第一个操作:重新读取状态文件。 - 保持工人的最终报告简洁 — 它们在之后的每次协调调用中都会被重新读取。
- 将繁重的读取委托给可丢弃的
Explore子代理 — 文件转储随子代理一起消亡,而不是加载到您的上下文中。 - 精简启动监督器会话 — 子代理继承监督器的 MCP 集合,因此连接到您的会话的每个服务器都会在每个工人的每次调用中运行。每个工人的 MCP 剥离不再可用(Agent 工具没有每个生成的 MCP 配置);在启动的地方一次性修剪它。
6. 减少调用次数(每次往返都会重新读取整个上下文)。
- 将独立的工具调用批处理到一次调用中(并行读取/grep/
gh)。 - 让脚本折叠查询+分类 —
scripts/survey.sh,scripts/reconcile.sh。 - 除非即将合并,否则不要以短节奏轮询 — 不必要的唤醒会重新读取所有内容,并且如果超过约 5 分钟的缓存 TTL,则需要支付一次完整的缓存写入(1.25 倍),而不是读取(0.1 倍)。
- 限制会话长度,而不仅仅是调用次数。 批处理已经过 A/B 测试并且没有效果(杠杆 2),因此每次调用的优化已接近枯竭,而会话长度是剩下的变量 — 37 个工人会话中,前 3 个占所有工人成本的 32%,而且工作量标签和层级都无法预测哪三个。因此,阶段 1 的工人带有调用预算(整数位于 references/token-economics.md § 两个预算,不在此处),并在达到预算时以
STATUS: PARTIAL移交;步骤 4 在全新的子代理中恢复它们,这才是节省的地方 —SendMessage恢复会保留预算旨在丢弃的上下文。这是杠杆 1 的机制,该部分唯一验证过的胜利,应用于长度接缝而不是阶段接缝。
测量它 — 如果没有 A/B 测试,永远不要声称某个杠杆有效。三个脚本,所有脚本都使用一个包含会话 .jsonl 记录的目录:
scripts/usage_report.py— 按模型划分的 Tokens + 美元等值。跟踪 Tokens/合并 和 $/合并。 ⚠️ 它会聚合整个项目目录,包括过去的运行 — 首先将一个运行符号链接到临时目录中,保持布局:监督器会话的<sid>.jsonl加上其<sid>/subagents/目录(<proj>/<sid>/subagents/agent-*.jsonl是每个工人记录所在的位置,因此一次运行的整个队列位于一个 id 下 — 从状态文件或gh时间戳中选择 id;mtime 是不可靠的)。一摞扁平的agent-*.jsonl符号链接会被读取为0 个子代理,并且不匹配任何东西。scripts/analyze_cache.py— 调用次数/会话、开始时的上下文与峰值、每次调用的平均上下文以及按工具划分的工具结果量。这就是您看到为什么一个会话开销昂贵的地方。scripts/measure_phase2.py— 通过其报告行的ISSUE:将每个 Issue 的阶段 1 和阶段 2 子代理记录配对,并将成本拆分为实现阶段与合并阶段;对于 2.0 之前的记录,它会找到会话内的合并移交调用。这就是证明杠杆 1 有效的地方。
两个指标承载了大部分信号:每次调用的平均上下文(后期的调用膨胀了吗?)和每次调用的工具调用次数(调用在做任何工作吗?基线约 0.55 — 参见杠杆 2)。
工作原理
┌─────────────────────── auto-dev (您,监督器) ───────────────────────┐
│ 状态文件:队列(小→大),处理中的 slot,已完成,已提交 │
│ 每次信号 → 与 GitHub 协调 → 结束合并后 + 补充 → 保持 N 个在运行 │
└──────────────────────────────────────────────────────────────────────┘
│ 调度(区域隔离) ▲ 结构化报告 / 空闲通知
▼ │
工人 A ─ implement-issue → [就绪] ⇢ 全新的子代理 ─ merge-pr ─ 报告 ┐
工人 B ─ implement-issue → merge-pr ─ 报告 ─┤ N 个后台代理,每个一个 Issue,
工人 C ─ implement-issue → merge-pr ─ 报告 ─┘ 合并后退役,从队列中补充
工人是使用 Agent 工具生成的后台子代理。通过它们的结构化报告进行通信 — 子代理的最后一条消息就是它的报告,在返回时交付给您 — 通过空闲通知,以及通过 SendMessage 与仍在运行的代理通信;永远不要读取它们的原始记录文件(<proj>/<your-session>/subagents/agent-*.jsonl)— 工人的输出是一个巨大的日志,会撑爆您的上下文。依赖报告。