Skip to main content
Orbi 会领取符合条件的 Issue,在 worktree 中实现,开 PR,独立审查,合并并记录结果。 一个 Orbi 任务是一个 runtime outcome,二选一形状:
  • 修复 — 当 X,应该 Y,实际 Z。示例: #1074 — 当 gh auth status 退出码为 1 且当前账号有效时,Runner 应该解析出该 账号;实际却因退出码让交付失败。
  • 变更 — 当 X,用户应该能够 Y;今天不能,因为 Z。示例: #1048 — 用户从 发布镜像启动 runner 时,应该得到一个配好模型 provider 的 runner;当时还没有任何代码读取 ORBI_PI_* 变量。
任务从一个 GitHub Issue 开始,以一个已合并的 PR 结束(或一个需要人工 的 ai-blocked 现场)。这一页覆盖完整的自动链路和项目的 GitHub 工作流 词汇。

完整链路

状态机(七个交付状态;review/fix 循环留在同一个 PR 上——只有它的 head 可以前进):
已有 run/PR 的失败被分类(Issue #50):可恢复失败(Pi 执行失败、 验证失败、未推送本地 commit、会话内未能修复的 finding) 留在自动修复循环——Issue 标记 ai-fix-needed,失败 comment 写入 Issue 和 PR(带完整现场),下一个 tick 在同一 run、branch、worktree、PR 上 继续(不重新创建 PR、不重复领取)。worktree 缺失根本不是失败:resume 会从远端交付分支重建它并继续推进(Issue #807——worktree 只是远端状态 的本地缓存,branch 和 PR 都在 GitHub 上)。只有 AI 无法安全判断或修复的外部 前置条件(无可恢复现场、base 分支变更、审查超轮、PR 未合并被关闭、 交付分支已从远端删除——没有任何东西可以用来重建 worktree) 才 fail fast:Issue 被标记 ai-blocked 并写明为什么不能自动恢复,自动 循环从此不再碰它——人工决定下一步。

运行中的交付修正

无需取消正在进行的交付,也可以修正方向。当 Issue 处于 ai-in-progress 时,可以评论新的决定,或直接编辑 Issue body。这两个渠道 是等价的:Runner 会在下一次 steering 轮询时拾取它们(间隔由 steering_poll_seconds 控制,默认 60 秒),并带着修正重启当前 session。 重启次数由 steering_max_rounds 限制(默认 3 次);达到上限后,后续修正 不会再重启这次交付,journal 会记录 steering_limit_reached。让当前交付完成, 再把修订后的范围放入后续交付。 只有受信任的仓库角色——OWNER、MAINTAINER、MEMBER 或 COLLABORATOR——的 评论可以 steering。公开评论者的评论会按设计被忽略,因为允许任意评论者 控制交付会暴露 prompt injection 入口。初始受信任评论时间线受 issue_comments_limit 限制(默认 200 条):更早的评论会被丢弃,注入的上下文 会明确说明省略了多少条。 死循环守卫与评论去重(Issue #825):恢复接续可能绑定新的 run id (一次可恢复失败就够了),而 PR body 只在创建时写入一次,所以 resume 校验接受本交付线曾用过的任一 run marker——marker 集合读自 Issue 的 受信任评论(与恢复现场同一信任过滤,公开评论里复制的 marker 不计入; branch、base、head、精确 URL 校验全部保留,#45/#89 的防串 PR 语义不变)。 同一 (run_id, 失败指纹) 的失败评论全程只发一次(隐藏的 <!-- orbi:fail=... --> 标记为键),后续相同失败原位递增该评论的隐藏 :次数 后缀(journal 记录重复);同一 (run_id, 失败指纹) 连续失败 达到 3 次(现场块——新开的 PR、完成的评审轮——代表交付线前进,会重置 连击)即判定死循环而非抖动,升级为 ai-blocked 并在评论里写明连续 次数与「前置条件未变化」。 链路规则:
  • 一个任务 = 一个 run = 一个 feature branch = 一个 worktree = 一个 PR。 review/fix 循环期间 PR 编号永远不变;只有它的 head 可以前进。
  • 只有两个 opened-PR 状态会被自动拾取:ai-pr-opened(等待 review) 和 ai-fix-needed(等待下一个审查会话)。ai-ready 作为新工作被 领取;ai-blocked 从不自动恢复。携带回填的 ai-in-progress 标签的 交付仍会被拾取(Issue #178:review 中被杀的 Runner 会留下该标签—— 正向的 opened-PR 限定已保证 implement 阶段的 Issue 不会匹配该扫描)。
  • review/fix 循环有界(5 轮)。已完成轮数存在受信任现场里 (review_round 字段,Issue #788):每条轮次评论携带更新后的现场 块,下一 tick 从中读出推进后的计数(人工在 block 之后把 Issue 改回 ai-fix-needed 的恢复仍会重置预算,Issue #483)。超轮仍有 findings 时 Issue 标记 ai-blocked(超轮是人工决策);可恢复失败(Issue #50) 保持 ai-fix-needed。无论哪种,PR、branch 和 worktree 原样保留。 评审会话自己也守卫预算(Issue #878):开工前其 prompt 会读本交付 (同一 run_id)的轮次评论,当同一失败原因已连续出现在两轮及以上 时,直接发点名重复的 findings verdict,不再重复同一修复路径—— 同一堵墙最多撞两轮。
  • tick 内零等待(Issue #788):旧进程内等待循环(wait_for_delivery、 对 CI/mergeability 的轮询、release_ci_wait_seconds / mergeable_wait_seconds)已移除。所有等待都走 waiting 原语——写状态、 返回、释放槽位,下一 tick 重读(delivery_ci_pending、 merge_gate_ci_pending、merge_gate_mergeable_unknown)。可恢复 扫描直接跳过 CI 仍 pending 的候选(Issue #1473),pending 的 PR 因此不再占掉一整个 tick。崩溃恢复 路径就是主路径。
  • base 前进由 task branch 上对最新 origin/<base> 的普通 git merge 吸收(冲突手工解决),然后重跑完整测试。不 force push、不自动解决 冲突、不 push 保护分支。
  • 吸收契约(Issue #877,Runner 机器校验):一轮评审开始时 head 已落后 origin/<base> 或 PR 有冲突时,该轮结束必须满足二者之一——分支包含 origin/<base>(git merge-base --is-ancestor 为真),或 verdict 为 findings 且写明「本轮尝试吸收但放弃」及具体原因。head 仍不含 base 的 pass 会在轮次评论里被 Runner 点名为违反吸收契约( review_absorb_abandoned);既有 5 轮有界语义不变。
  • Git transport(Issue #114、#580):git 数据操作走配置的传输方式 (git_transport:默认 ssh——git over SSH——,或 https 走 gh credential helper),GitHub API 操作留在 gh token 上——完整的双通道边界与启动前检查见运维; 凭据见安全。

Reviewing with a different model(用不同的模型评审)

独立评审不必运行在写代码的同一个模型上:review_pi_provider、 review_pi_model、review_pi_thinking 为评审会话单独选择 provider/模型/思考级别,评审者因此不会继承实现者的盲区。每个键独立 回落到实现侧的对应值(review_pi_provider → pi_provider、 review_pi_model → pi_model、review_pi_thinking → pi_thinking),只覆盖其中一个也有效。两个选择都必须存在于配置的 pi_providers 目录中,并在启动时分别校验——评审选择非法会以 review provider selection invalid fail fast。本文不推荐也未实测 任何具体配对;能力与回落机制才是契约。

合并账本:external_commits 与 commits

Runner 合并交付 PR 时,Orbi merged PR: Issue 评论只展示用户真正 要行动的三件事——PR 链接、review 结果与目标分支——并把 merged-as-is 事实(Issue #833)收进一个折叠的 Run details 块:
Run details 行保持 key=value 形式(等号,不是冒号),因为它们是 机器读的锚点:orbi-cloud 的 mergeCommentFields 读取器与本仓库的 comment-reader 契约精确匹配 external_commits=K、review_rounds=N 与 run_id=…。Orbi: merged milestone 行以同样的 key=value 形式 带上这些事实。
  • commits —— 合并时 PR 分支相对 base 的全部 commit 数(比例的分母)。
  • external_commits —— 不在引擎自己推送历史里的 commit 数(开 PR 时的 head 加上 Runner 自己推的每一轮 review/fix head;绝不检查 git 作者 身份)。0 = merged as-is:合并前没有人改过交付分支的一行。计数在 合并后通过 merge commit 的父链读取(M^1 为 base 端、M^2 为 PR head),减去引擎记录的推送线:最后一个引擎推送 head;外部接管 (Issue #608)时再排除接管的贡献者 head(推送线的起点),贡献者的 commit 保持为外部。
  • external_commits=unknown —— 推送历史无法证实(重建的 worktree 丢了 run-state 记录;某轮 fix 推送在记录之前回合已结束且历史不完整;或 记录的 head/base 不是合并 head 的祖先)。squash 或 rebase 合并 (Issue #1480)没有 M^2,两项指标同样为 unknown。数不出来的历史 绝不写成 0。
引擎推送 head 在三个点记入 task worktree 的 run state(pushed_head, Issue #219 文件)——交付推送、verdict 覆盖的 review/fix 推送、以及 回合开始时对上一 session 已推送但回合在 re-freeze 记录之前结束的 head 的采纳——同一 worktree 的接续都能恢复这份历史;全新的 worktree 从无记录开始,退化为 unknown。外部接管在 claim 时一次性记录接管的 起点 head(pushed_base),且其回合开始的采纳永不运行:worktree 本来 就始于贡献者的 head。记录本身是 bypass:state 文件缺失或损坏只记 pushed_head_unrecorded 日志,交付继续。

交付标签

GitHub 标签是外部状态机。它们不是 commit 创建的——每个仓库要初始化 (见快速开始): 仓库策略可能在 Orbi 的评审和检查都通过后仍阻塞合并。Orbi 交付 PR 后停止 在 ai-awaiting-merge;Issue 评论写出精确 head 和维护者要做的一件事(适用时 包含设置 URL)。维护者完成策略变更或给出所需批准后,下一 tick 恢复,仍由 Orbi 自己合并 PR。若 preflight 没有已知的 FAILED 动作,或是其他合并失败, 仍进入 ai-blocked。

人工验收门禁(ai-human-review)

产品主线是无人值守:默认流水线人在环外运行,且本门禁默认关闭——开关 关闭时交付、评审、merge、release 的行为与 Issue #763 之前逐字节一致。 host 配置打开门禁(orbi.toml 里 human_review_gate = true——这是部署 方的信任决策,不是仓库可写策略)。打开后每个交付在 PR 打开时向 Issue 发一条验收清单评论,分两栏:
  • 栏一 · 本次 run 已验证的(证据,不是待办):PR head 的 CI 检查 状态(Issue #1613:引擎不再解析本地测试日志)、覆盖层(仓库测试套件)、 改动文件数。
  • 栏二 · 机器验不了的最小集合,按每个交付的改动路径与可用证据生成 (不是硬编码的”人类专属”模板):业务意图(测试只证明实现与测试一致, 不证明测试与意图一致)、UI 视觉与文案、外部环境验证,以及 head 的检查 未通过或 diff 不可读时的证据缺失条目。
栏二非空时它就是门禁:评审轮次在任何 Pi session 之前短路——票回 ai-ready(opened-PR 标签保留作 resume 锚),槽位释放,此后每个 tick 只花一次 label 读。等待不消耗评审轮次预算,也不碰 ai-fix-needed。 人确认清单后打 ai-human-review,下一 tick 照常评审与合并(verdict 绑定 与 --match-head-commit 零改动)。 栏二为空是目标状态:清单明确写「无需人工介入」,交付不要求该标签直接 通过。模型能力提升时,栏二条目迁进栏一——清单评论带机器可解析的 <!-- orbi:human-review {...} --> 块(schema 1),迁移可以做工具化, 工件形态不变。清单不声称任何正确性保证:栏一是已验证内容的证据,栏二是 人仍需确认的最小集合。

薄 ticket 闸门

对一个含糊的 ticket 自信交付,比不交付更糟:烧掉一次 run、开出一个没人 能接受的 PR,还把你真正要问的问题埋掉。闸门默认关闭,仓库用 .github/orbi.toml 里的 clarify_thin_tickets = true 打开;未启用的仓库 行为与之前完全一致。 闸门打开时,一次 fresh claim 会先问一次 Pi session(--no-tools,无 worktree、无分支、不交付)判定 ticket 正文的三项检查:可观察结果 (用户会看到或做得了什么不同的)、验收条件(怎么区分完成与未完成)、 单一结果(一个 fix 或一个 change,而不是几件互不相关的事)。判定通过 则一切不变,交付照常开始。 判定不通过则在任何工作存在之前中止本次尝试:只发一条评论列出全部缺失项 与修复动作,把该仓库的认领标签(默认 ai-ready,可经 dispatch_label 改名) 换成 ai-needs-detail,本 tick 结束——无 worktree、无分支、无 Pi session、无 PR,槽位留给下一个 Issue,该票与 ai-blocked 一样等人。重新编辑 Issue 并加上该认领标签才获得下一次机会, 届时判定从头再跑一遍。 所有错误路径都是旁路——闸门绝不会弄丢一次交付。模型报错或超时、答案不可 用、评论或标签写入失败,都只记 clarify_check_skipped journal 行,交付照常继续。 判定与中止分别对应 Operations 里的 clarify_check_skipped 和 clarify_needs_detail 行。

票派发:content、dev、ops

ai-ready 是所有任务类型的执行开关;第二个标签选择类型 (Issue #537)。共三类——派发时每个 Issue 选定其一: ops 语义(Issue #537):
  • 信任边界是(凭据,票面)——不是命令白名单。 会话原样获得 runner 的全部环境(gh 凭据、CLOUDFLARE_API_TOKEN、AWS keys 等,外加 deploy home 的 .orbi/env 文件);凭据给多宽是用户的决定,runner 不做二次裁剪。
  • 票面即授权。 会话只执行票面写明的 ops 动作(合并哪个 PR、部署 哪个环境);票面之外的动作回帖说明并停。人工审批门(environment approval、Release 批准)不由会话代行——这是提示词约定,不是执法代码。
  • 交付 = 证据(Issue #526 的事实文化):每一步把真实命令与真实 输出 / API 返回原文贴到票上;未执行的步骤如实标注未执行。纯 ops 运行 (无 commit)以 Runner 关闭 Issue 并摘除 ai-in-progress 收尾—— 无 PR。附带代码 commit 则照常走 PR 路径(审查 + 合并)。
调研型票(Issue #739):票面要求只调研/设计——「先调研,不要直接写 实现」——的票,派发时就是 ops 票:打 ai-ready + ai-ops-only。 dev 票带不动这个指令:dev 的交付判据是 task branch 上有新提交, 照办不提交的 agent 会以 the agent delivered no commit 失败,票被 烧成 ai-blocked。ops 剧本的证据纪律(真实命令、原文输出、未验证项 如实标注)就是调研交付物本身;无 commit 的 ops 运行正常收尾(关票、 摘除 ai-in-progress),调研后继续落地的代码仍走正常 PR 流程。 Issue #537 之前,ai-ops-only 标签派发的是内容代理(#530 只改了名, 模式未变——名字承诺 ops,模式仍是纯内容);现在 content 路径派发到 ai-content-only,ai-ops-only 是完整执行的 ops 路径。

Run marker 与 run_id

每个任务 attempt 生成一个 run_id(8 位 hex,例如 e07383c2),该 attempt 的每一步复用同一个值;同一 Issue 的 retry 生成新的。同一个 id 出现在:
  • 该 attempt 的每条 journal 行(前缀 [e07383c2]);
  • Issue/PR 评论:可见字段 run_id=e07383c2 + 隐藏机器可读 marker <!-- orbi:run=e07383c2 -->;
  • 里程碑与场景评论还携带隐藏的 runner marker <!-- runner=1a2b3c4d -->: 写出评论的部署指纹——执行 checkout 的 HEAD;非 editable 安装为包版本; 探测失败为 unknown(live progress 评论与 PR 评论不带它);
  • Orbi opened PR 场景评论携带隐藏的带版本 run 现场 <!-- orbi:scene:v1 {json} -->(src/orbi/scene.py):这是 resume 扫描解析的机器可读恢复记录——现场损坏与现场缺失是两个不同的失败 分支(Issue #786);
  • 稳定的按 Issue 命名的 feature branch(orbi/<owner>-<repo>-issue-<N>);
  • worktree 名(.worktrees/orbi-<owner>-<repo>-issue-<n>-e07383c2),以及 Pi session 目录和 run 产物;
  • PR body——稳定 marker <!-- orbi:run=e07383c2 --> 是 PR 契约 的一部分;Runner 拒绝没有它的 PR。
一条 grep 还原完整时间线:

失败记录:orbi:failure:v1

引擎通过失败通道上报的每一次失败都是机器可读的:详细的 Orbi: blocked / Orbi: fix needed 评论与其旁的短 milestone 都在 run marker 旁携带恰好一个隐藏的带版本 block:
outcome 为 blocked(终态,由人决策)或 fix_needed(下一 tick 恢复 同一个 run)。reason_code 说明交付为何停止;action_code 说明读者该 做什么;retry_safe 表示仅重新入队能否在没有人工修复的情况下成功。两个 集合都是封闭的、由引擎拥有(src/orbi/failure.py);分类器无法识别的 异常映射为 unclassified,block 永不省略;在本功能之前写出的评论完全 没有 block——读取方会忽略它(parse 返回 None)。 动作:requeue 给 Issue 重新加 ai-ready(或点击重试)——无需修复; fix_ticket 修复失败并重跑该 Issue;decide 需要人工决策; check_credentials 检查并修复凭据后重试;wait_quota 等配额窗口重置后 重试。人类可读的标题、**Reason:** 与 **Action:** 行与 block 并列; milestone 的可见摘要就是同一条有长度上限、不含本机路径的理由——完整的 命令 stderr 绝不会被截成第一个词。

PR body 契约:Fixes #N

PR 描述必须包含 Fixes #<issue-number>(可以放在首行)。GitHub 读的是 body(不是 PR title),PR merge 到默认分支时原生关闭 Issue。Runner 在 PR 验收时校验该关键词,缺失即 fail fast。这个校验失败会记录在对应 Issue 上,不会让 runner tick 进程退出。 分阶段计划不能用一张 Issue 和一个 PR 承载多个独立 runtime outcome。应使用 Epic 或父 Issue 做协调,把每个阶段拆成子 Issue;每张子票各自有一个交付 PR, 并写入对应的 Fixes #<子票号>。这样既保留 GitHub 原生关票,也不需要新增第二 种 PR 正文语法。如果现有 PR 写的是 Part of #N,维护者应在该 PR 已经完整 交付一个子票结果时改成精确的 Fixes #N,或者先把剩余阶段拆成子票再重试。

Epic、Release task 与 P0 优先级

项目用普通 GitHub 原语组织多任务工作——没有 DAG、没有优先级数字、没有 独立队列:
  • Epic:协调 Issue,把一组相关任务归在一起(例如 v0.1 release checklist)。它带 ai-epic 标签。Epic 本身不是可直接执行的任务: 实际工作拆成独立的 ai-ready Issue,每个一个 runtime outcome、一个 PR、一次 review、一次 merge。Runner 从不领取 Epic(Issue #93): 普通领取扫描跳过它并记录结构化日志 epic_not_claimed issue=N repo=...(不领取、不改标签、不建 worktree、 不启动 run、不占 slot),重启恢复扫描也排除它。Epic 的子票声明唯一 来源是 GitHub 原生 sub-issues 关系:所有子 Issue 必须 CLOSED(或其 PR 已 merged),且没有 open 的原生 blockedBy。正文中的引用(包括 Children/Child Issues 段落)不构成声明。没有 sub-issues 或子票、依赖 状态畸形时 fail-closed;Runner 每个 tick 重试并留下幂等审计评论。随后 同一个 tick reconcile open Milestone:title 精确匹配的已发布 Release (draft 不算)且当前没有 open Issue 才关闭,其余情况保持 open。最后 同一个 tick 还会扫一遍 open PR(Issue #746):head 为稳定交付命名 (orbi/<repo>-issue-<N>)且源票已被人工关闭的 PR,会在 PR 上收到 一条幂等的报告评论——所有 resumable 扫描都只查 state=open 的票,没有 这个扫描该 PR 会永久悬空。合并/关闭由人决定:Runner 绝不自动合, 也绝不越过人的关闭决定去自动关。
  • Release task:带 ai-release 标签的 ai-ready Issue(Issue #98)。它不是开发任务。三个普通扫描会记录 release_not_claimed 并跳过它;只有 release fallback 扫描使用 allow_release=True。在领取前, _pick_from_scan 会执行 Milestone 完整性门禁:只要所属 Milestone 还有 其他 open Issue(open_issues > 1),Runner 就记录 release_milestone_incomplete,让 release 保持 ai-ready,并在之后的 tick 重试。这是可恢复的等待,永远不会变成 ai-blocked。Milestone 查询 无法评估时也会跳过领取,并记录 release_milestone_check_failed。该领取层 发生在 process_issue 和所有 release 状态机等待之前,因此排在 open ticket 后面的 release 没有 wait_started,累积的 release_waited_seconds 为零。 领取后,process_issue 才会把它路由到确定性的 release 状态机而不是 run_pi——没有 Pi 会话、没有 PR。状态机幂等且可恢复(重启后恢复同一 run id/worktree),按顺序执行这些步骤:
    1. 严格解析 Issue 正文的 ## Release 声明:version、 base_branch、scope(#N 列表)或 scope_from_milestone, 以及可选的 version_file(默认 pyproject.toml,也可为 package.json 或 none)。声明不携带任何测试契约 (Issue #569):测试验收就是 release commit 的 GitHub Actions CI 结果(#268 CI-wait 门)。字段缺失、重复或未知 都 fail fast。
    2. 冻结 base——release commit 就是 origin/<base_branch>(在 base-sync 锁下 fetch)。在任何门禁等待之前,校验声明(或默认) 的 version_file 确实存在于该冻结树的根目录(Issue #740): 不匹配在认领瞬间 fail fast,错误信息列出根目录实际存在的受支持 文件和 none 选项,不会在烧完门禁和范围核验后才以 FileNotFoundError 失败。
    3. 执行发布前门禁。只有上述领取层允许领取后,门禁层的 release_waiting_deliveries 才可能等待带有 ai-in-progress、 ai-pr-opened 或 ai-fix-needed 的 open Issue(release Issue 本身 除外)。release 有 Milestone 时,只有同一 Milestone 内的 Issue 会阻塞 门禁(Issue #671);没有 Milestone 时仍按全仓扫描。只有 ai-ready 的 Issue 不会阻塞该门禁。这个有界等待使用 RELEASE_DELIVERIES_WAIT_SECONDS;超时就是 delivery wait timeout, release 进入 ai-blocked。随后要求 release commit 的 CI 全绿 (no check runs passes 带明确证据; pending check 是中间态——门禁等待最终结论,每次轮询一条 release_waiting_ci journal 行和一次进度评论更新,上限 release_ci_wait_seconds(默认 1800 秒);等待超时以独立原因 失败,绝不报成 CI 失败)。open PR 刻意不是门禁(Issue #608, 维护者裁决):open PR 是队列状态,不是发布前提——是否存在 open PR、多少个、谁提的,与发版无关;滞留 PR 由交付循环自身的 接管/对账机制处理。因真实 gh 失败而查不了的门禁算失败门禁。
    4. 逐项验证 scope:gh pr view 必须报告 MERGED,否则 gh issue view 必须报告 CLOSED 且 stateReason 为 COMPLETED——NOT_PLANNED 关闭(重复/不做)的票在 Scope 证据里 标注为排除、绝不进入 Changelog,Changelog 的 PR 链接只在 PR 位于 带 tag 的 commit 区间内时才写入(Issue #1492)。Changelog 本身源自带 tag 的 commit 区间(prev_tag..release_commit),绝不源自 Milestone Issue 列表:Milestone scope 只决定能否发版,不决定 notes 内容。 对于 Milestone-derived scope, open_milestone_evidence 不等待:仍 open 的 Milestone Issue 会被 排除,并记录为 NOT released (still open in milestone <name>): <item>。 禁止解析复选框——每个 scope 项都对照 live API 检查。
    5. 测试验收就是 release commit 的 CI-wait 门:版本 bump 之后,门禁会对 该 commit 重跑(步骤 3 的 release_ci_wait_seconds 等待),CI 红或 等待超时走既有可恢复失败路径(ai-blocked)。没有本地测试执行 (Issue #569)——release 流程即 bump → push → CI-wait → tag。 如果 CI 红或等待超时,既有失败路径会阻止发布;版本 bump 可以保留在 main,这是可接受的终态。bump 是幂等的,修复 CI 后重试 release 会安全地再次准备所请求的版本。
    6. 打 tag:远端 tag 不存在时,在 release commit 上创建 annotated tag 并普通 push(绝不 --force)。已存在时必须精确指向 release commit——不匹配 fail fast。已存在的 tag 绝不移动、覆盖或 force-push。
    7. 发布 GitHub Release(幂等),notes 带完整验证证据:version、 tag、release commit、逐项 scope 证据、门禁证据、测试证据和 run marker。文档站 Release 页不由状态机写入(Issue #1483):仓库 自己的 .github/workflows/release-docs.yml(Issue #1482)在 Release 发布时用其 body 生成 docs/release-<version>.mdx 与 docs/zh/release-<version>.mdx,把新版本插到 docs/docs.json 的 Releases/发布 导航分组首位(中英文两处,最新在前),并 转移 (latest) 标题标记。自 Issue #1096 起两个分组各嵌套一个 折叠的 Earlier releases(历史版本)子分组——自动同步把新版本 插到可见页首位,并将超过最新三个版本的可见条目移入该子分组, 让可见分组始终保持最新三个版本。幂等:已有页面内容一致时不重复 生成、不覆盖,无改动时不提交;已有页面内容不一致则 fail fast—— workflow 运行失败,绝不静默跳过。
    8. 给 release Issue 打上 ai-merged 并关闭它(交付终态转换);merged 事件同时清除 ai-ready 与 ai-in-progress,发布后的 Issue 只剩 ai-merged(Issue #1526)。
    9. 关闭 title 与发布 version 精确一致的 Milestone(Issue #214), 然后写成功评论(release URL、tag、commit、证据、Milestone 证据)。只按 title 精确匹配(不猜测、不模糊匹配、绝不关别的 Milestone):0 个 open Issue 时关闭;已关闭 是幂等成功(不重开);Milestone 缺失或仍有 open Issue 则 fail fast,带上 version、Milestone 编号/链接和 open issue 列表(绝不 静默跳过)。release 票自己永不算未完成工作(Issue #808):它正被 本次发版关闭,issue 索引落后关票几秒(Issue #754)造成的 「只剩 release 票自己」的陈旧读既不阻止也不拖延关闭;其余任何 open Issue 仍然拒绝关闭。 任何失败:单独进入 ai-blocked(release 是人工决策 点——不自动重试),失败评论带 run marker 和具体原因,异常向上传 播让 tick fail fast。
    ai-release 标签是类型标记,不是交付状态:它不属于交付状态机、 不是 ready 扫描的排除条件,Runner 从不增删它——只有人在派发时 (与 ai-ready 一起)添加。
  • P0 优先级:普通 p0 GitHub 标签标记紧急 Issue(生产故障)。它 不是交付状态——只决定 ready 领取顺序,从不改变 Issue 粒度、任何 交付状态或终态语义,Runner 也从不增删它。ready 领取顺序固定: ai-ready+p0 → ai-ready+bug → 普通 ai-ready(三次 gh issue list 扫描,共享完全相同的排除条件和 blockedBy 语义)。 P0 遵守所有现有排除规则和单 slot 约束:被阻塞的 P0 被跳过(回退到 bug/普通扫描),在途的 P0 由重启扫描接回。P0 运行失败单独进入 ai-blocked(领取标签被移除;ai-ready 残留被所有 ready 扫描排除), 所以没有任何 tick 重新领取——没有无限重试。没有优先级数字或加权队列。
Issue 之间的任务依赖用 GitHub 原生 blockedBy 关系(gh issue edit N --add-blocked-by M);Runner 读该字段并跳过有未关闭 blocker 的 Issue。 Issue body 里的 Depends on #N 行不被解析。

仓库级交付策略(.github/orbi.toml)

仓库可以把交付策略作为代码版本化。claim 时 Runner 通过 GitHub contents API 读取仓库默认分支 tip 上的 .github/orbi.toml—— 绝不读任务分支、绝不读本地 worktree——并按按键分域把白名单键应用 到宿主 orbi.toml 之上。没有该文件的仓库行为与之前完全一致(零变化)。 位置固定,只有文件名可用 [[repositories]] 条目的可选字段 config_path(默认 .github/orbi.toml)覆盖。 只允许交付策略键:
覆盖是按键分域的:仓库文件声明 active_milestone 时,它胜过宿主 配置,也就是 claim 扫描使用的值。orbi milestone set <title> 把新值 写到同一个地方——仓库策略声明该键时写策略(对默认分支做 contents PUT),否则写宿主 orbi.toml 的那一行——并打印 wrote: 指明目标 (Issue #1306)。 host-only 身份/安全键(见 安全)、坏 TOML 或类型错误都会让 claim fail-fast 并 在错误里列出违规键,Issue 进入 ai-blocked——一个仓库的坏文件绝不 影响其他池。其余不认识的键则被忽略:journal 记一行 repo_config_keys_ignored 指明该键与引擎版本,run 评论带 repo_config_ignored,claim 用已知键继续(Issue #1329)。该文件由交付 宿主实际运行的引擎版本读取,而它往往比写出该文件的代码更旧,所以新策略 键必须能被旧引擎安全忽略:它默认保持功能加入前的行为,不认识它的引擎照 常交付。host-only 集合同样包括宿主调度:max_concurrency 永远不能下放到仓库文件——并发 slot 池属于部署宿主。 dispatch_label 不得是交付生命周期标签 (ai-in-progress、ai-merged 等):那些恒为宿主常量。阻塞落在 claim 选中的那个 Issue 上;dispatch_label 不是 ai-ready 的仓库在 文件本身坏掉时没有 ai-ready 候选 Issue 可阻塞,此时 fail-fast 原因 只在 journal 的 repo_config_invalid 行(不改标签、不发评论)。run 评论记录 repo_config: <sha>;与上一 run 的 sha 不同时附 repo_config_changed 标注,且当前一版内容可读时附有效键值 diff repo_config_diff,让变更 始终在 run 上可见。传输原因(限流、网络)导致的读取失败只记一行 repo_config_read_failed 并回退宿主配置——该降级在 run 评论上表现为 缺少 repo_config 字段。

等待 Milestone 决策时的唯一回复

整条链路安静下来时,Runner 会说明为什么,而不是静默等待。两种空闲状态 通过同一个决策通知呈现——一张带有指纹 orbi-milestone-advance old=<active> candidates=<titles> 的 Issue——两者 都由票内同一条命令回答:在该通知上以受信任评论形式发出的 /milestone <版本>: 两种通知都是幂等的(每种等待只有一张 open 通知,绝不是每 tick 一张), 等待结束后自动关闭并留下回执,而且都不会自行行动:Runner 绝不自己创建 release 票,也绝不在没有回复的情况下推进 Milestone。回复执行的是确定性 三步命令 /milestone(milestone → release ticket → active_milestone),详见 Operations;不涉及任何模型调用。