mcpField
E
epic-runner

epic-runner

github.com/niekcandaeleAgent 工作流647 次安装2 天前更新
代码仓库

关于 epic-runner

epic-runner 是用于无人值守交付整个 epic 的 Agent 技能。它协调多个隔离的规划、实现、CI 和交付 Agent,并将 epic 分支作为最终审查点。

主要功能

  1. 01按依赖关系安排工单
  2. 02委派规划与实现
  3. 03每次只实现一个工单
  4. 04在 CI 等待期间规划后续工单
  5. 05在 epic 分支整合工作
  6. 06支持更新跟踪器和创建后续工单

适用场景

  1. 01无人值守交付多工单 epic
  2. 02按依赖顺序处理工单
  3. 03CI 运行时准备后续规划
  4. 04汇总已完成工作以供最终审查

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/niekcandaele/skills --skill epic-runner

安装后在 Claude Code 中输入/epic-runner确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
tracker-github.md
tracker-jira.md
tracker-markdown.md
trackers.md

详细介绍

Epic Runner — 无人值守地交付整个 Epic

你是前台负责人。每张工单都是一场比赛——由一个 Agent 规划、由一个 Agent 实施,并以绿色 PR 交付到 epic 分支。你负责安排进度并授权合并。你决定进行哪些比赛、按什么顺序进行,以及最后留下什么。你从不编写代码、审查代码,也不会亲自运行验证流水线。

启动你的人会离开。他们六小时后会回到终端,而他们在那里看到的,就是本次运行的全部成果。以下所有内容都服务于两个目标:尽可能完成 Epic 中真正能完成的工作,并且让未完成的工作不可能被忽视。

委派金字塔

你的上下文是这个系统中最稀缺的资源。无论 Epic 有 3 张工单还是 30 张工单,都必须让它大致保持不变,因为你是唯一一个一旦重启就会丢失工作脉络的部分。

epic-runner            you — the scheduler
├── plan-agent         one per ticket, plans in plan mode, writes a plan file
├── issue-agent        one per ticket, isolated context + worktree — implements, opens
│                      the PR, drives CI green, returns a mergeable PR
├── sync-agent         merges the target into the epic branch through its own PR
├── delivery-agent     every forge call and tracker mutation the schedule needs — CI
│                      proof, merge, PR state, lifecycle — returned as fixed fields
└── epic-verify-agent  one per finalization round, holds the full report so you don't
    └── verify → reviewer, codex-reviewer, comment-review, qa, ux-reviewer,
                 static-analysis, tester, exerciser, visual-verify

第一行以下的所有内容都已存在,并且已经隔离。你的职责是绝不把其中任何内容纳入自己的上下文。你会收到每张工单的一份简短结构化报告;完整过程会写入磁盘供用户查看,而不会返回给你。

因此:绝不读取验证报告。绝不读取差异。绝不读取非必要的计划。 如果你发现自己想检查实现细节,那就说明这项工作应该交给子 Agent,而不是由你处理。

delivery-agent 能确保锻造流程也遵循这一原则。 create-pr 和 check-ci 是涉及大量命令的复杂技能;调度器一旦调用它们,在本次运行的余下时间里就会一直持有它们的说明和输出——这是上下文随工单数量增长的唯一剩余途径。你绝不会调用这两者。你负责决策,它负责执行,返回给你的只有少数几个字段。此树中的每个模型层级都按角色设置;参见 Harness bindings。

工作项与唯一稀缺资源

你是对类型化工作项进行调度的调度器,而不是逐张处理工单的循环。

工作项需要开发栈吗?
规划下一张未阻塞的问题否
实施问题,直至产出可合并的 PR是
合并绿色 PR否
将目标分支同步到 Epic 分支是
起草后续工单否
验证或修复 Epic 分支是

同步在处理各个问题之间进行,此时没有问题 PR 处于打开状态,因此无需让任何正在进行的工作吸收同步操作。

开发栈才是限制因素——端口、内存、工作树。任何时候只能实施一个问题,该 Agent 会在自己的 CI 等待期间一直占用开发栈。你想让其他工作在任何合适的时候自由进行。

问题必须一次处理一个,原因与开发栈无关:每个问题都从 Epic 分支的某个版本分出,而该版本必须已经包含前一个问题。调度带来的好处是那些无需占用开发栈的工作——当前问题在 CI 中运行时规划下一个问题,以及起草探索性工单——这样,CI 的一小时也是运行持续推进的一小时。看着进度条等待的编排器,正是此设计旨在避免的失败模式。

当没有未阻塞的工作时停止。不是在工单失败时停止,而是在确实没有其他可做之事时停止。唯一会让本次运行立即终止的情况,是发现未经批准的提交已合并到 Epic 分支;参见 Merging。


阶段 0:设置

这是唯一会向用户询问问题的阶段。 在本阶段结束时获得确认后,你就只能独自工作,直到本次运行结束。运行所需的每项决策都必须在此处敲定,因为在第二小时提出的问题不会提醒任何人——它只会让运行悄然停滞,直到用户碰巧查看终端。

1. 解析参数
<epic-reference>                     Required. Free-form — see step 2.
--write-back=off|on                  Update the tracker as work completes.   [on]
--new-issues=never|propose|create    Follow-up ticket policy.            [propose]
--max-ci-fixes=N                     CI-fix attempts an issue-agent may
                                     spend on its own PR.                     [3]
--severity=N                         Epic verification blocking threshold:
                                     findings at or above this are remediated
                                     before the epic PR opens.                [5]
--target=<branch>                    Final merge target.           [repo default]

--target 指定 Epic 最终要合并到的分支。单独的问题绝不会以它为目标——它们的目标是第 4 步创建的 Epic 分支。

2. 解析史诗并构建跟踪器绑定

史诗引用刻意采用自由格式:GitHub 里程碑、标签或跟踪议题 URL,Jira 史诗键,或 Markdown 清单路径。不同项目管理工作的方式各异,这个技能没有理由强行规定其中一种。

阅读 references/trackers.md,并将引用解析为一个跟踪器绑定——用于执行六项操作的具体命令:

操作用途
list列出此史诗中的议题
read获取一个议题的标题、正文、验收标准和依赖链接
start将议题标记为正在处理(可选)
comment在议题上发表评论
close明确将议题标记为已完成
create新建议题(仅用于 `--new-issues=create

references/tracker-github.md、tracker-markdown.md 和 tracker-jira.md 是已完成的示例。如果跟踪器不属于这些类型,请根据可用的 CLI 或 MCP 工具找出绑定,并以相同格式记录下来。

--write-back=on 会在发布跟踪器状态中提到的四个时点调用 comment 和 close。请在此处解析两者,并确认 close 确实能将议题从当前状态推进到完成状态——如果到第三小时才发现 close 无法执行,这次运行的生命周期记录就已经丢失了。

如果无法解析写入操作,请降级为只读并说明这一点——能够读取的跟踪器仍然完全可用,只是用户之后需要手动协调状态。因为缺少 close 命令就拒绝开始,这显然不合理。

3. 阅读议题并构建依赖图

获取史诗中的每个议题。对每个议题,记录:ID、标题、正文、验收标准,以及任何已声明的依赖链接。

已声明的依赖优先。 跟踪器表达依赖的方式各不相同——Jira 链接类型、GitHub 任务列表嵌套或正文中的“blocked by #12”,以及 Markdown 文件中的缩进——references/trackers.md 介绍了如何提取它们。用户既然已经声明了顺序,就应严格遵循。

如果没有声明依赖,就自行判断。 API 尚不存在时无法构建前端,架构尚未确定时也无法迁移数据。跟踪器没有依赖链接,并不意味着这些工作确实可以并行;通常只是没人把依赖关系写下来。

宣布每一条推断出的边,而且只宣布推断出的边:

Inferred: #7 depends on #3 (consumes the /export endpoint #3 adds)

如果推断出的边有误,用户就无从察觉——他们只能疑惑为什么一个显然已准备就绪的任务始终没有被处理。已声明的边无需宣布;只需遵循即可。

没有结构的纯 Markdown 清单必须严格按从上到下的顺序处理。 人们会按照打算执行的顺序编写清单,把这个顺序视为毫无意义,就是丢弃用户已经提供的信息。

4. 创建史诗分支

史诗中的每个议题都会合并到同一个史诗分支,本次运行的所有改动都不会进入真正的目标分支。根据史诗引用推导出 <slug>——它也将作为状态目录的名称——并从获取到的目标分支创建 epic/<slug>:

git fetch "$BASE_REMOTE" "$TARGET_BRANCH"
git branch "epic/$SLUG" "$BASE_REMOTE/$TARGET_BRANCH"
git push "$BASE_REMOTE" "epic/$SLUG"

在 run.json 中记录创建时间;同步触发器从此时开始计算运行时长。如果该分支已存在,则表示这是一次恢复运行:采用现有分支,并按照状态一节所述与跟踪器进行协调。

由此带来两点,而这正是关键所在:

  • 各议题彼此集成,而不是集成到不断变化的目标分支上。 议题 4 从史诗分支创建,该分支已经包含议题 3,因此“基于前一个任务构建”自然成立,无需另外安排。
  • 整个史诗只需整体评审一次。 每个议题合并进来之前都会通过 CI 验证;所有内容汇集完毕前,不会有人查看代码。第二阶段验证的就是这个分支,而本次运行交付给人的也只有这一项:一个分支、一份差异、一个决策。
5. 检查基础情况

五项成本低廉的检查,每项都能防止运行在失败前浪费数小时:

  • 交付绑定。 PREFLIGHT,因为要解析它,就得阅读 create-pr 已披露的 Forge 操作参考,并对 check-ci 执行预检——这需要处理数万个 token 的提供方详情,只为回答一个是或否的问题。调度会根据已认证的提供方解析每项能力,并将其报告为 ok 或 missing:INSPECT、DRAFT、PUSH、UPDATE、READY、MERGE_EXACT_HEAD、CI_PROOF、REQUIRED_POLICY、STRICT_POLICY、REVIEW_REQUIREMENTS、MERGEABILITY,以及具名的 PROVIDER、跟踪器绑定的 comment 和 close 操作——只有当 --new-issues 不为 never 时才检查 create,因为不创建任何内容的策略无需具备创建能力——并针对每项缺失能力输出一行 BLOCKERS。它返回的是解析后的绑定本身,而不只是状态——即每项能力对应的具体操作,以及几行命令。预检一次的意义就在于此:提供方参考有数万个 token,而它解析出的绑定很短,所以你只需掌握这份简短内容,并在后续每次调度时交给它。状态不是 Agent 可以执行的东西。清单中包含草稿相关能力并非凑数:第 2 阶段以一份草稿 Epic PR 收尾,如果运行到第六小时才发现无法创建 PR,就无处安放已完成的工作。生命周期或 CI 证明能力中任何一项为 missing,都意味着 Epic 无法交付承诺的合并结果;应在确认中披露这一点,并在修改实现前停止,除非用户明确将运行范围缩小为仅创建草稿。
  • 分支保护。 同一调度会在两个位置读取并返回分支保护状态:EPIC_PROTECTION 和 TARGET_PROTECTION。在 Epic 分支上,如果保护规则要求人工审批,就会阻塞每个问题的合并——应直言不讳地说明,只有用户仍希望继续时才继续。在实际目标分支上,保护规则完全不影响本次运行,因为你不会合并到该分支;应报告这一点,让用户了解最终人工合并时需要满足哪些要求。绝不要绕过分支保护——无论是自行批准、推送到受保护分支,还是禁用规则,都不行。分支保护自有其存在理由,你无权擅自重新解释。
  • Codex 可用性。 verify 会将 codex-reviewer 用作独立的第二模型审查。Epic 在第 2 阶段只会接受一次验证,因此,如果 Codex CLI 缺失或未通过身份验证,运行中唯一的一次审查就会少一名审查者——而且直到最后才会发现,届时已没有时间补救。第一分钟就检查。
  • 相邻 Epic。 针对同一目标分支的其他长期 Epic 分支是最严重冲突的来源,而只有用户能安排它们的先后顺序。对于每个其他远程 epic/* 分支,报告其自与目标分支的合并基点以来修改过的文件数量和顶层目录——只报告数字和路径,绝不提供差异内容。恢复运行时,还要统计它与本 Epic 共享的文件数。标出规模特别大的相邻 Epic,以及工作领域与本 Epic 所涉问题重叠的相邻 Epic。这只需作为确认中的警告,不做其他处理。
  • 此 Epic 的先前运行状态(见下文的状态)。如果找到状态,请询问是恢复运行还是从头开始。
6. 编写共享上下文文件

状态目录中的一个文件,会作为路径传给每个 issue,而你永远不会读取它。 这种不对称是刻意的:它让 epic 能在各个 issue 之间共享知识,同时不让任何内容 进入必须在整个运行期间保持完整的那个上下文。

  • epic-context.md — 已完成 issue 的单行摘要、当前 issue,以及按标题列出的 剩余 issue。计划 Agent 和 issue Agent 会用它区分已安排的工作与尚未处理的工作; 只看单个 issue 的人无法做出这种区分。 在每次实现之前重写它,确保“剩余”保持准确。

它的价值体现在这样的时刻:实现者本来可能会做掉下一张工单已经负责的工作。这类 工作并非没有代价:它会未经规划地出现在别人的差异中,而原本负责这项工作的工单 到来时,却发现自己的任务已经以一种无人选择的方式完成了一半。

7. 确认一次,然后开始

打印解析后的绑定关系、epic 分支、依赖图、顺序、生效的参数,以及地面检查发现的 任何问题。获取一次确认。

然后不要再询问。从现在直到运行结束,与用户的唯一互动就是进度输出。


阶段 1:调度循环

每次迭代,选择当前最合适的工作项并完成它。有多个可选项时,大致按以下优先级处理: 合并通过检查的 PR(这会解除依赖项的阻塞)→ 同步(满足触发条件时) → 实现下一个无阻塞的 issue → 提前规划 → 起草后续工单。

规划 issue

启动一个规划 Agent。把 issue 交给它,让它自行研究——它可以访问代码仓库、Git 历史和任务跟踪器,因此应该充分利用这三者,而不是依赖他人提供的摘要。如果运行 环境提供规划模式——一种只能研究和起草、不能编辑的模式——规划 Agent 就必须在该 模式下运行。 重要的是这项保证:无法编写代码的规划者不会开始实现工单中简单的 那一半,然后把结果称为计划。

Plan the implementation of this ticket. Plan only — write no code.

Ticket: {id} — {title}
{body and acceptance criteria}
Tracker binding, if you need to read further than the ticket above: {tracker_binding}

Already completed in this epic: {ids and one-line summaries}

Write an implementation plan to: {state_dir}/plans/{id}.md

The plan is the requirements document for an implementation agent that will not see this
ticket — only your plan. Read the codebase. Check git history for how similar work was
done here. Look at what the completed tickets above actually changed.

If the ticket is too ambiguous to plan without inventing requirements, say so instead of
guessing: reply REFUSED with what specifically is underspecified.

只提前规划一个 issue,绝不再多。 提前六次合并写好的计划,是基于一个尚不存在 的代码库制定的。等你真正执行到它时,一半的假设都已经过时;过时的计划比没有计划 更糟,因为它看起来像是权威的。

只要有疑虑,就按存在歧义处理,不必等到确信。 细小的缺漏——例如未指定的错误 消息、显而易见的默认值——应通过在计划中写明明确的假设并在 PR 描述中标注来解决。 如果对工单要求什么确实拿不准,就应该拒绝。猜错会浪费一次实现机会和一小时 CI 时间, 最终做出错误的功能。

拒绝并不意味着运行失败。记录拒绝,将 issue 标记为 needs-attention,然后继续。

实现一个 issue

这需要开发栈。 在它返回之前,其他依赖它的任务都无法运行。

先发布 Publishing tracker state 中的开始时刻,然后在隔离的工作树中启动一个 issue Agent。它端到端负责该工单——代码、PR 和 CI——并交还一个可供合并的 PR:

Implement issue {id} from the plan at {state_dir}/plans/{id}.md.

Branch from {BASE_REMOTE}/epic/{slug}, with "{id}" in the branch name. Implement the
plan and commit. The plan is your requirements document — you will not see the ticket.
{state_dir}/epic-context.md lists what the remaining tickets own; work that belongs to
one of them is not yours to build.

Before opening the PR: run the project's tests and checks, exercise what you built, and
satisfy yourself it would survive review. CI is the gate, not the first reader.

/create-pr "{concise user-facing title}" --base=epic/{slug}
           --plan-file={state_dir}/plans/{id}.md --context={your journey context}

The context carries the tracker linkage line "{linkage}" for the PR body — it references
issue {id} without claiming the merge closes it, because this PR targets the epic branch.

Then take the PR to green yourself:

/check-ci --pr={PR_URL} {full head SHA}

Fix what fails, commit, push with /create-pr --push --pr={PR_URL}, and check again. At
most {max_ci_fixes} fix attempts, then stop and report. Fix CI; implement nothing the
plan does not call for. An hour of CI is normal — a long pipeline is a state, not a
problem, so wait it out rather than concluding it is stuck.

Write the full journey — what you built, friction, how you checked it, and every CI
failure you fixed — to {state_dir}/issues/{id}.md for a human.

Return ONLY the status block below, plus a Discoveries section if something outside this
ticket's scope turned up. No diffs, no logs, no narration — the orchestrator does not
read them and cannot afford the context.

STATUS: MERGEABLE | FAILED
PR_URL: {url or none}
BRANCH: {branch}
HEAD_SHA: {full 40-character SHA}
REMOTE_HEAD_SHA: {full SHA observed on the remote after the push}
CI_FIXES: {attempts spent}

以 epic 分支为目标不需要你做其他事:Agent 从 epic 分支的最新提交分支,你在开始下一个 issue 前合并当前 issue——这样每个 issue 分支都从已包含其前序 issue 的最新提交切出。

CI 是每个 issue 的关卡,issue Agent 会一并负责。 这样你需要处理的事情就很少:三十张工单的流程,无论底层发生了什么,你都只需查看三十个状态块。阅读代码是 Phase 2 的工作,到时只需在组装好的分支上进行一次。

解析状态块。MERGEABLE 要求 REMOTE_HEAD_SHA 与 HEAD_SHA 相同,且二者都是完整 SHA;满足时进入合并检查,其中的 HEAD_SHA 是后续所有比较的批准 SHA。FAILED、缺少字段或 head 不匹配都会导致该 issue 失败。记录所有发现。你只需保留这份精简状态。

包含 PR_URL 的状态块——无论可合并还是失败——就是发布 Publishing tracker state 中 PR 已创建时刻的时机:Agent 创建了 PR,因此你是第一个同时掌握其 URL 和对应 issue 的人。

失败的 issue 不会再次尝试实现。 CI 修复尝试就是重试;让一个新 Agent 在相同代码上重新开始,只会让调度器花四小时收敛到一个人五分钟就能解决的问题。

同步目标分支

epic 运行期间,目标分支会持续变化。同步会将目标分支合并到 epic 分支——频繁进行,让每次冲突仍然很小、意图也容易辨认,而不是最后一次性跨几十个文件处理。

时机。 同步是开发栈中的工作项,只有在没有 issue PR 正在打开或排队时才会开始;同步 PR 打开期间,不会启动 issue。在每次观察到合并后,都要自行测量分支差异——只看数量,不看差异内容:

git fetch "$BASE_REMOTE" "$TARGET_BRANCH" "epic/$SLUG"
TARGET_SHA=$(git rev-parse "$BASE_REMOTE/$TARGET_BRANCH")
MB=$(git merge-base "$TARGET_SHA" "$BASE_REMOTE/epic/$SLUG")
BEHIND=$(git rev-list --count "$BASE_REMOTE/epic/$SLUG..$TARGET_SHA")
OVERLAP=$(comm -12 <(git diff --name-only "$MB" "$BASE_REMOTE/epic/$SLUG" | sort) \
                   <(git diff --name-only "$MB" "$TARGET_SHA" | sort) | wc -l)

满足以下任一条件时进行同步:

  • OVERLAP 大于零——目标分支更改了 epic 也更改过的文件。
  • BEHIND 大于零,且距离上次同步——或 epic 分支创建时间(两者都记录在 run.json 中)已超过一天。
  • issue Agent 的发现报告指出,目标分支已修复某项 CI 失败。

每次同步都需要运行一次 CI;这就是触发条件要等待文件重叠或时间间隔的原因。

执行者。 由 sync Agent 在隔离的工作树中执行,级别与 issue Agent 相同。{n} 是 run.json 中的下一个同步编号,因此恢复运行时不会重用分支名:

Sync the target into the epic branch.

Branch epic-{slug}-sync-{n} from {BASE_REMOTE}/epic/{slug}, then git merge {TARGET_SHA}
(the tip of {target}). Resolve every conflict with /resolving-merge-conflicts: keep both
sides' intent, and invent nothing. {state_dir}/epic-context.md says what the epic is
building. Commit the result as a merge commit; never rebase or squash it.

Run the project's tests and checks; fix only what the merge broke.

/create-pr "Sync {target} into epic/{slug}" --base=epic/{slug} --context={your journey context}

Then take the PR to green yourself:

/check-ci --pr={PR_URL} {full head SHA}

Fix what fails, commit, push with /create-pr --push --pr={PR_URL}, and check again. At
most {max_ci_fixes} fix attempts, then stop and report. An hour of CI is normal — wait it
out rather than concluding it is stuck.

Write the journey — every conflicted file and how you resolved it, and every CI failure you
fixed — to {state_dir}/syncs/{n}.md.

Return ONLY this block. No diffs, no logs, no narration — the orchestrator does not read
them and cannot afford the context.

STATUS: MERGEABLE | FAILED
PR_URL: {url or none}
BRANCH: {branch}
HEAD_SHA: {full 40-character SHA}
REMOTE_HEAD_SHA: {full SHA observed on the remote after the push}
CI_FIXES: {attempts spent}
CONFLICTS_RESOLVED: {count of conflicted files}

落地同步遵循 Merging 中的流程——状态块检查、READ_GREEN、PUBLISH_STATE、MERGE、轮询排队中的合并、INSPECT、合并提交的祖先关系检查,以及污染时暂停——但有三点不同:

  • MERGE 调用需要带上 --merge-method=merge。压缩合并的同步会使目标分支的提交不在 epic 的提交祖先链中,后续每次同步都会再次遇到相同冲突。
  • 合并后还要亲自再确认一次: git merge-base --is-ancestor "$TARGET_SHA" "$BASE_REMOTE/epic/$SLUG"。如果同步通过了其他所有检查,但这一步失败,说明同步以压缩或变基方式落地;这次同步失败。
  • 不发布 tracker 时刻、不关闭工单、不重新评估依赖图——同步不属于任何 issue。

在 run.json 中记录每次同步:时间、TARGET_SHA、同步 PR、CONFLICTS_RESOLVED 和结果。如果同步解决了冲突,而下一个 issue 的计划已经写好,就丢弃该计划并重新规划——合并可能刚刚更改了计划中提到的代码。

同步失败时按 issue 失败处理——PR 和分支保留,不重试。本次运行剩余时间内都不再同步,包括 Phase 2 的同步:继续在未同步的最新提交上实现,不会因此留下无法处理的内容,并在完成报告中说明。

交付 Agent

调度器需要的每次 forge 调用,以及每次跟踪器 变更,都在这里进行。create-pr 和 check-ci 是封装嘈杂命令的大型技能:调用它们的调度器会在本次运行剩余时间内保留它们的说明文字和输出,而这项开销会随着每个工单不断增加。每项操作都由一个短生命周期 Agent 吸收这些噪声,并只返回字段。跟踪器读取不经过它:你会在阶段 0 直接读取,因为 list 和 read 是构建依赖图的基础,而且无论如何你都得保留它们输出的问题正文;任何拿到跟踪器绑定的 Agent 也会直接读取。

操作你提供它返回
PREFLIGHT跟踪器绑定、史诗分支、目标分支阶段 0 中的能力块,包括解析出的交付绑定
COMMENT问题 ID、所处时刻、该时刻包含的内容、任何已解析的工作流标签TRACKER,以及跟踪器最后的事实状态
CLOSE问题 IDTRACKER,以及重新读取时观察到的内容
FILE_TICKET已拟定工单的路径TRACKER,以及创建的问题 ID
PUBLISH_STATEPR URL、头部 SHA、预期状态、证据提供商报告的 PR_STATE、HEAD_SHA、MERGE_QUEUE
READ_GREENPR URL、已批准的 SHA、史诗分支每个绿色谓词字段,以及祖先关系结果
MERGEPR URL、已批准的 SHA、史诗分支、绿色谓词、你的授权;同步时还需 --merge-method=mergePR_STATE、HEAD_SHA、BASE、MERGE_COMMIT、MERGE_QUEUE、REASON
INSPECTPR URL与 MERGE 相同的六个字段
OPEN_EPIC_PR史诗分支、目标分支、上下文路径PR_URL、PR_STATE、BASE、HEAD_REF、HEAD_SHA、MERGEABLE

每次派发都携带所需的绑定——forge 操作使用 PREFLIGHT 解析出的交付绑定,跟踪器操作使用跟踪器绑定。全新的上下文无法把 #42 转化为提供商、仓库和具体命令;如果 Agent 必须对此进行猜测,它很可能会猜得头头是道,却完全错误。

每份简报的结构都相同:操作、参数、绑定、该操作所遵循的策略,然后是用问题 Agent 的措辞说明的返回要求——只返回这些字段,不要命令输出、CI 日志、参考文本或叙述;编排器不会读取这些内容,也没有足够的上下文空间容纳它们。

每次派发都附带四条规则,因为全新的上下文不会继承其中任何一条:

  • BASE 和 MERGE_COMMIT 来自提供商,而不是 create-pr。 create-pr 会报告 PR_STATE、HEAD_SHA、MERGE_QUEUE 和 TARGET——从这对值的目标一侧获取 BASE,并通过 PREFLIGHT 解析出的提供商检查操作读取合并提交。create-pr 从未输出过的合并提交应为 none;如果合并确实发生了,却仍为 none,就会阻止本应完成的交付。
  • 报告提供商实际执行的操作,而不是收到的请求。 解析 create-pr 的失败块,并返回其中最后记录的事实值 PR_URL、PR_STATE、HEAD_SHA 和 MERGE_QUEUE,而不是请求的状态转换。即使合并已完成,但记录状态失败,它仍然是一次合并;回滚它,或报告 PR 尚未合并,都会把记账失败变成对仓库状况的谎报。
  • 使用预检通过的提供商自身的操作。 绝不能用另一个 forge 的 GitHub 命令替代;应从已认证的提供商能力中解析出完全对应的操作。
  • 绝不能绕过分支保护、弱化规则,或自行批准以解除合并阻塞。
  • 绝不能将原始 CI 日志、令牌或个人数据复制到上下文或评论中。

将返回的失败块中的 HEAD_SHA 存为观察到的远程头部;它绝不会覆盖已批准的 SHA,后者由你掌握,且只能由你掌握。

发布调度器状态

问题 Agent 编写了 PR 正文;随着调度器推进 PR,确保正文仍然准确是你的职责, 需通过 PUBLISH_STATE 安排。对于每个已排队、已合并或调度器终止失败的状态, delivery-agent 会写入一个权限为 0600 的上下文,开头为 CONTEXT_KIND: delivery-state, 其中包含确切的 PR 状态、已批准的 head、完整的 check-ci 证明、排队或合并 证据,以及任何失败原因;然后通过 create-pr 使用 --no-push,针对该 PR 和 已批准或最近观测到的完整远程 head 发布。

create-pr 会保留现有的旅程/测试/阻碍正文,只替换其有界的生成式 Final State 区块。调度要求在更新后检查,以确保 head 和预期 PR 状态保持准确。调度器失败会记录 在同一 Final State 区块和跟踪器的终止失败评论中;PR 本身不会收到评论。

发布跟踪器状态

跟踪器记录问题的生命周期——待处理、进行中、已完成、失败—— 它是人、恢复运行或其他工具能看到的唯一记录。run.json 保存证据,而非生命周期。 因此,跟踪器显示问题已完成时,问题才算完成。

使用 --write-back=off 时,这些操作都不会运行:运行过程不会以任何方式修改跟踪器。 否则,在以下四个时点写入;每次都通过绑定的 comment 操作进行一次 COMMENT 调度, 并附上 Phase 3 要求的署名行:

时点评论内容
开始实施epic 运行、epic 集成分支、实施已开始
问题 PR 创建PR URL、源分支、目标 epic 分支、当前远程 head
观测到精确 head 合并PR URL、epic 分支、已批准的 head SHA、观测到的合并提交、CI 结果和修复尝试次数,以及明确说明工作已集成到 epic 分支,尚未合并到默认分支的一行
终止失败PR URL 和失败原因;问题保持打开

仅在绑定已解析出工作流标签或状态时,才在开始时应用它。不要隐式创建任何项目管理术语。

发送交付评论后,调度 CLOSE,以关闭并重新读取问题。 只有跟踪器报告问题已作为已完成关闭,依赖项才会解除阻塞,而不是因为你提出了关闭请求——因此,你应依据重新读取的结果采取行动,而非请求本身。评论或关闭操作失败时,返回跟踪器最后确认的事实状态;跟踪器仍报告问题处于打开状态时,绝不要将问题记录为跟踪器已完成。

如果绑定的 comment 或 close 是空操作,则其功能会降级:改为将该时点的内容写入完成报告,并在 Phase 0 的确认中说明一次。

请耐心等待。 问题 Agent 离开一小时很正常——这段时间大多花在 CI 上,而且多个分支同时落地时,共享运行器会排队。正因 CI 耗时长,这种程度的自主性才是安全的。长时间运行的 Agent 或流水线是一种状态,并非出现问题的证据。绝不要断定 CI 卡住了,绝不要建议跳过 CI,也绝不要在没有 CI 的情况下合并。利用等待时间做些不受阻的工作:规划下一个问题,起草发现工单。

合并

MERGEABLE 状态区块只是问题 Agent 的声明,并非证明。你需要确认它,因为你拥有合并权限——而且此处的权限指的是决策权,不是敲击按键的权力。 只有你持有已批准的 SHA,也只有你能授权针对该 SHA 进行合并。delivery-agent 可以执行授权,但不能自行生成授权。

使用 PR、已批准的 SHA 和 epic 分支调度 READ_GREEN。它会在该精确 head 上读取一次 check-ci --once,并返回下列字段,同时回答另一个问题——该 head 是否仍包含当前 epic 最新提交:

git fetch "$BASE_REMOTE" "epic/$SLUG"
git merge-base --is-ancestor "$BASE_REMOTE/epic/$SLUG" "$APPROVED_SHA"

绿色是肯定性测试,而不是没有红色。 只有满足以下所有条件时,PR 才可以合并:

  1. 每项必需检查都已报告最终结论。
  2. 所有这些结论都成功。(skipped 和 neutral 仅对非必需检查可接受。)
  3. PR 可以合并——没有冲突。

用 check-ci 的说法:CI: PASSED,HEAD_SHA 等于已批准的 SHA,REQUIRED_CHECKS: complete,每项可选检查都已结束且未失败,UP_TO_DATE: yes|not-required,并且 MERGEABLE: yes。

本套规则有意将必需检查的 skipped 和 neutral 视为非成功,即使某些提供方的原生合并规则可能接受它们也是如此。如果只测试“没有任何检查失败”,就会把四种不同的故障状态判为通过:仓库未配置任何检查、检查仍在排队、必需检查尚未开始报告,以及路径筛选工作流跳过了所有内容。

CI: NONE、CI: BLOCKED、HEAD 与预期不完全一致,或必需检查、严格策略或可合并状态不可观测,都会导致问题未通过。CI: FAILED、源分支冲突,或在问题 Agent 判定可合并的 HEAD 上出现 STRICT_POLICY: required 且 UP_TO_DATE: no,也会导致未通过——修复尝试已用尽。待人工批准会被记录,并将 PR 留给人工处理,而不是判为失败。

问题 PR 创建时已处于可合并状态,且不指定审阅者:不会要求任何人审阅它,因为人工实际进行的审阅是在 Phase 2 对 epic 分支进行,且只进行一次。授权合并前,先通过 PUBLISH_STATE 将通过 CI 的证明发布为交付状态。

如果 HEAD 不再包含 epic 分支的最新提交,也会以同样方式判定失败。 该分支是在一个之后发生了变化的 epic 分支基础上构建并测试的,因此其通过的 CI 不能证明集成结果没有问题。将其交回给 issue Agent,让其在现有分支上合并 epic 分支的最新提交,并再次让 PR 通过 CI——这次 CI 运行才是首次针对集成后的 HEAD 进行验证。在受保护的目标分支上,STRICT_POLICY: required 会强制执行这一要求;而 epic/* 分支通常不受保护,因此不会有其他机制执行它。

满足此条件后,通过分发 MERGE 授权合并。重新读取状态和执行合并必须在同一上下文中进行,正是因为两者之间不能插入任何操作:

Merge {PR_URL} at exactly {approved full SHA}, and only if it is green at that head.

Immediately before the merge invocation, read
/check-ci --pr={PR_URL} {approved full SHA} --once and require every one of:

{the green predicate above, verbatim}

plus REVIEW_REQUIREMENTS: satisfied|not-required. This closes the race where the target,
checks, or review requirements changed since the authorization was issued. Any non-green
reread, and pending human approval, mean you do not merge — report it and stop.

Then invoke /create-pr --merge --pr={PR_URL} --head-sha={approved full SHA}. create-pr
resolves the repository-preferred merge method through the preflighted delivery binding.
Use whatever the repository prefers and override nothing, unless this brief names a merge
method — then pass it as --merge-method.

Merge no head other than {approved full SHA}. If you cannot merge that exact head under
those conditions, reporting why is the correct outcome.

Return ONLY:
PR_STATE: merged | queued | unmerged
HEAD_SHA: {the full head the provider reports}
MERGE_COMMIT: {sha or none}
MERGE_QUEUE: {queue identifier or none}
REASON: {why, when not merged}

即使 CI 已通过,也要遵守相反的用户指示和分支保护规则。对问题 PR 而言,Squash 是一种常见且合理的仓库偏好:epic 分支会为每个问题保留一个干净的提交,同时 PR 上仍可查看逐个提交的历史记录。Sync PR 是例外;参见同步目标分支。

合并必须由执行合并的 Agent 之外的其他来源确认。 层级不是防护措施,重新读取自己生成的报告并与之核对也不是:如果 Agent 合并了错误的 HEAD,却抄录了要求的 HEAD,那么任何仅依据其自身返回结果进行的检查都会通过。因此,只有在合并 Agent 未写入的两项确认均通过后,PR_STATE: merged 才能完成交付:

  • 新的 INSPECT——由另一个 Agent 读取代码托管平台本身关于已合并 HEAD 的记录。要求 PR_STATE: merged、HEAD_SHA 等于已批准的 SHA、BASE 为 epic/{slug},并且 MERGE_COMMIT 是完整 SHA 且不为 none。

  • 亲自确认该提交确实位于分支上:

    git fetch "$BASE_REMOTE" "epic/$SLUG"
    git merge-base --is-ancestor "$MERGE_COMMIT" "$BASE_REMOTE/epic/$SLUG"
    

    检查针对的是合并提交,而不是已批准的 SHA。在偏好 Squash 的情况下,已批准的 SHA 有意不会成为任何提交的祖先——因为合并提交是新提交——因此检查它会导致每次 Squash 合并都失败,而且不会额外通过任何检查。

任一确认失败都意味着已发生污染,而第二项失败更令人担忧:如果 INSPECT 显示干净,但合并提交不在 epic 分支上,说明 PR 被合并到了其他位置——最可能的原因是分支的目标为默认分支,而不是 epic/{slug};在此之前没有任何步骤会重新检查这一点。BASE 不是 epic 分支时也应作同样处理,不要等到祖先关系检查才发现 BASE 字段已经说明的问题。 未经你批准的已合并提交头会污染史诗分支,这与无法合入的工单是另一类故障。未经批准的代码现在已进入每个后续问题都会基于它构建、Phase 2 会验证、史诗 PR 会交给人工审核的分支。将该问题标记为失败并不足以应对,因为出问题的并不是该问题。

因此:停止本次运行。 不要再将任何问题排入该分支,不要运行最终处理,也不要创建史诗 PR。Phase 3 仍要处理台账——已起草的工单是实际工作,创建工单不会给分支带来任何影响;Phase 4 仍要报告,但应采用以下格式,而不是通常的格式:

Epic {name} — HALTED, {n} of {m} issues merged into epic/{slug}, {duration}

CONTAMINATED  epic/{slug} carries a merge this run did not approve.
              issue        #{id}
              approved     {approved SHA}
              merged       {head the forge reports}
              commit       {merge commit}
              found by     {the INSPECT mismatch, or the ancestry test}

DO NOT MERGE  No epic PR was opened. Nothing here is reviewed work.
              The branch needs a person before this epic goes further.

MERGED        {the issues that landed on approved heads, with PR links}
NOT ATTEMPTED {everything the halt cancelled}

通常的报告会以史诗 PR 开头,因为这是本次运行无法为用户完成的事项。停止的运行没有史诗 PR,假装有 PR——或者悄悄沿用最显眼的一行缺失的格式——都会掩盖整份报告要传达的唯一事实。本次运行绝不能做的一件事,就是把一份 PR 交给他人,并将未经批准的内容呈现为已审核的工作——此设计的自主性完全建立在最终审核确实针对本次运行所声称的内容这一前提之上。

PR_STATE: queued 会创建一个已持久化的待合并工作项,其中包含 PR URL、已批准的 SHA 和队列标识符;使用 INSPECT 轮询。还要确保每次检查的 HEAD_SHA 都与存储的已批准 SHA 相同。如果检查结果尚未合并,但 SHA 不匹配,则将该问题标记为失败;绝不要认可或标记不同的提交头为已完成。如果检查结果报告 PR_STATE: merged,但 SHA 不匹配,这根本不是问题失败——队列合并了未经批准的提交头,造成了污染,因此必须如上所述停止本次运行。持续检查,直到观察到已批准的提交头合并或被最终拒绝。任何其他结果都属于合并失败:保持问题开放,不要解除依赖项的阻塞。

队列接受后,立即按照上述调度器状态流程发布 queued;观察到合并后,立即发布 merged。如果队列拒绝、CI/策略检查失败、CI 修复尝试次数耗尽,或调度器因其他终止路径结束,且 PR 已存在,则使用相同流程发布实际的失败状态和终止评论。

只有在观察到合并后,才能通过 发布跟踪器状态 发布交付评论并关闭问题。跟踪器报告问题已关闭且状态为已完成后,重新评估依赖图——解除依赖项阻塞的依据是这一观察结果,而不是合并本身。

问题失败时

失败各不相同,正确的应对方式也有很大差别,因此一概重试只会白白浪费资源。此表是指导原则,而非分类体系——如果遇到表中没有的情况,请直接判断:这件事已经耗费了多少成本?重试后出现不同结果的实际概率有多大?

失败情况重试?原因
Agent 退出——崩溃、工具错误、上下文耗尽是,仅一次没有学到任何东西;它只是倒下了
推送、PR、评论或状态更新失败否重试可能会重复创建内容;保留分支和 PR,交由人工处理
达到 --max-ci-fixes 次尝试后,CI 仍然未通过否已经重试过了;CI 表明这类问题不是另一个 Agent 能解决的
PR 状态为绿色,但你自己的合并前复查未通过否问题 Agent 的修复额度已用完,而且它的判断已经与代码托管平台不符
因 epic 之外的问题而受阻——架构变更、凭据或决策绝不重试无法补上缺失的条件
规划 Agent 拒绝处理——工单描述太含糊绝不需要人工处理

代价最高的失败最不可能在重试后成功,因此一概“重试一次”会把最多的精力花在最没希望的工作上。

失败的问题会保留其 PR 和分支。 不要关闭 PR,也不要删除分支。对应的跟踪工单也会保持开启状态,并附有来自发布跟踪状态的最终评论,其中会说明 PR 和失败原因。这些工作是真实存在的,之后会由人工接手。该问题在依赖图中仍未完成,因此依赖它的问题会变成搁置项。

绝不要基于失败的工作继续构建。 如果 #3 失败,不要在 #3 的分支上尝试 #4、#5、#6。该分支包含尚未通过 CI 的代码;如果人工之后以不同方式修复 #3,所有叠加在其上的工作都建立在虚构的基础上。跳过依赖项,处理仍未受阻的无关工作,并在最后报告搁置项。

受阻的问题是数据,不是中断信号。 记录下来并继续处理。无人值守运行的价值在于,用户睡觉时,十二张工单中有九张完成了——而不是十二张都在等待一个答复。

记录发现

在执行过程中,子 Agent 会发现一些没人预料到的问题。将它们收集到台账中。

发现是指超出当前处理工单范围的问题——例如当前工单调用的某个模块中的错误、缺少测试套件、没人预料到的阻碍,或计划中的某个假设最终被证明是错误的。也就是你会为之新建工单的问题。

当前工单涉及的代码中存在的小缺陷不算发现。 这些问题要么已经修复,要么被有意保留;无论哪种情况,都应记录在 PR 中,而不是台账里。没有这条界限,台账就会塞满实现者在自己改动的差异中发现的几十个问题,而一堵噪声墙在实际效果上等同于什么都没报告。

唯一的例外:严重程度优先于范围。 如果运行确实无法解决某个严重问题,即使它在当前范围内,也值得新建工单,因为“我们带着已知漏洞发布了”不能随着会话结束而消失。

去重。 在处理 #3、#5 和 #7 时发现的同一问题,应作为一条记录,注明三次发现,而不是创建三张工单。

运行期间就起草工单,不要等到结束时再做。起草不需要开发环境或 CI,因此可以在其他事情暂时无须处理时免费完成。为每项已确认的发现启动一个工单撰写 Agent:

Investigate and write a ticket for this finding.

Finding: {what was noticed, where, during which issue}

Investigate before you write. Read the code. Check logs and stack traces. Reproduce it if
you can. Research externally if the behaviour depends on a library or platform you're
unsure about. Confirm it is actually real and understand WHY.

If it turns out not to be real, say so and stop — that is a useful answer.

Otherwise write a ticket someone can pick up cold, months from now, with no memory of this
run: what the problem is, the evidence, the analysis, and a recommended approach.

Write it to {state_dir}/discoveries/{n}.md — do not file it.

调查才是重点。根据一句话的印象提交的工单,写出来时看似合理,等别人接手后却经不起推敲;这比没有工单更糟——它浪费读者的时间,也会削弱人们对系统提交的其他工单的信任。

单独来看不足以构成工单的小问题,也不应就此消失。 如果一次运行积累了一堆小缺陷,创建一张合并清理工单并列出所有问题是合适的——这样既能留存记录,也不会给跟踪器刷屏。是否值得提交,要根据具体情况判断;在干净的 Epic 中创建一张几乎空空如也的清理工单,只会制造噪声。


阶段 2:完成 Epic

所有可能合并的问题都已合并。本阶段就是评审——不是在最终检查之上再做一次检查,而是整个过程中唯一会有人阅读运行产出的代码的时机。每个问题的 CI 都证明了各自的分支可以构建并通过测试,但那只是另一种、更有限的结论。

这里也是唯一能看到集成后结果的地方。单独来看都正确的问题,组合起来时往往会冲突——例如两张工单以不同方向重写了同一个辅助函数,或第 4 张工单放宽了某项契约、第 9 张工单又再次收紧——而在此分支出现之前,没有任何环节能发现这些问题。

所以要留出足够时间。分三轮进行,并在过程中修复问题;这次运行的全部质量主张都取决于此。

整个系统中,只有这里会要求使用 --depth=deep。 其他所有地方的验证默认都很轻量——无论是人工输入 /verify,还是 player-coach 循环的每一轮——因为针对持续增长的差异进行判断的扇出操作无法收敛。这里的情况不同。本轮只会针对已完成的集成分支运行一次,而该分支不会在审查过程中继续增长;这也是 Epic 唯一一次评审。轻量验证要避免的成本,在本阶段并不会产生。

即使有些问题失败,也要运行本阶段。 部分完成的 Epic 仍应作为整体接受评审;但不能因此声称其已完整交付。唯一的例外是因 Epic 分支受到污染而中止的运行——这种情况下,绝不能评审该分支或为其创建 PR。

0. 验证前同步

除非同步因失败而被关闭,否则先测量分支差异;如果 BEHIND 大于零,就立即同步,不受触发条件所述内容的影响。评审应检查最终合入目标分支时的代码,包括冲突解决结果,而不是目标分支早已超前的旧分支。

1. 验证 Epic 分支

启动一个验证 Agent。由它保管报告,你就不用保管:

Verify the epic branch as one integrated change.

Check out epic/{slug} and invoke:
/verify --depth=deep --mode=report-only --scope=branch --base={BASE_REMOTE}/{target}
        --format=json --output={state_dir}/epic-verify/{round}.json

Return ONLY these three lines:
BLOCKING: <count of issues at severity {severity} or above>
TOTAL: <count of all issues>
REPORT: {the output path}

Return no findings, no descriptions, and no narration — the orchestrator does not read
verification reports and cannot afford the context.

如果没有阻塞性发现,本阶段结束;前往步骤 4。

2. 规划修复方案

启动一个修复规划 Agent,由它阅读你没有阅读的报告:

Write a remediation plan for the findings in {report path}.

The epic branch epic/{slug} is complete and its individual issues are merged. Verification
of the integrated result found blocking issues. Read that report and the code, and write a
plan to fix them.

Write the plan to {state_dir}/plans/remediation-{round}.md

Fix what was found. Add no functionality: anything that looks like new scope belongs in a
follow-up ticket, not in this plan — say so in the plan rather than planning it.

If a finding is not real, say so in the plan and explain why, instead of planning a change
that exists to satisfy a reviewer.
3. 执行修复

在普通分支上运行普通的 issue-agent——使用你处理 issue 时已有的相同任务说明,以 {state_dir}/plans/remediation-{round}.md 作为计划,分支名为 epic-{slug}-remediation-{round}。

然后按处理任何 issue 的相同合并流程推进。它会像其他所有运行一样,进入自己的 PR,这就是为什么不需要特殊处理。

然后返回第 1 步。 验证轮次最多共三轮:验证、修复、验证、修复、验证。第三轮后仍有阻塞项的,需在完成报告中交由人工处理——如果循环总能在自己的修复中发现新工作,就说明这个 epic 需要人来处理,而不是再来一轮。

4. 打开史诗 PR

与目标分支冲突的史诗 PR 不算交付完成。 验证需要数小时,而目标分支在这期间可能已发生变化。除非已关闭同步,否则请再次测量偏移量;如果 BEHIND 大于零,再同步一次。这次后期同步只进行一次;如果目标分支之后再次变动,仍要打开 PR,并让下面的可合并性检查来决定。

如果后期同步解决了冲突,这些解决方案就是尚未经过任何人审阅的代码。请在三轮上限之外额外进行一轮检查:使用步骤 1 的简报,将 --depth=deep 替换为 --depth=light,并写入 {state_dir}/epic-verify/late-sync.json。阻塞性发现只通过步骤 2 和 3 进行一次修正,不再返回步骤 1;仍未解决的问题都交由人工在完成报告中处理。

根据本次运行自身的状态编写上下文文件——包括每个问题的处理结果、每轮最终确认的验证结果,以及任何尚未解决的阻塞问题——然后使用 epic/{slug}、{target} 和该文件路径分派 OPEN_EPIC_PR。交付 Agent 会检出 epic/{slug},并针对 {target} 打开草稿 PR;create-pr 会为当前检出的分支打开 PR,因此检出的分支决定了 head,这就是为何史诗分支必须作为参数传入,而不能让 Agent 自行推断。

它会返回 PR_URL、PR_STATE、BASE、HEAD_REF、HEAD_SHA 和 MERGEABLE。要求前五项与你的请求一致:PR_STATE: draft、BASE 为 {target}、HEAD_REF 为 epic/{slug},且 HEAD_SHA 等于你自行获取的史诗分支最新提交 —

git fetch "$BASE_REMOTE" "epic/$SLUG"
git rev-parse "$BASE_REMOTE/epic/$SLUG"

获取操作不可省略。远程跟踪引用的时效取决于上次获取,而本阶段跟进的每次合并都是在第 1 阶段获取之后才推送到远程的——在运行的最后一步,过期引用既可能拒绝正确的 PR,也可能放过错误的 PR。

— 因为选择 head 的检出操作是在你看不到的情况下完成的。仅检查 BASE 和 PR_STATE,可能会放过从错误分支打开的 PR,或针对同一目标分支预先存在的草稿 PR;而这是整次运行要交给人的唯一交付物。分派内容包含以下规则——将此 PR 作为草稿打开,然后到此为止,不合并,也不标记为可审查。

MERGEABLE 只回答一个问题:head 是否与 {target} 冲突?分派内容会明确这一定义,因为不同提供方会把其他状态纳入可合并性字段——例如草稿状态、待处理的流水线——否则史诗草稿 PR 总会显示为受阻。分派内容还会要求重新读取,直到提供方完成计算:unknown 不是答案。MERGEABLE: no 不会阻止打开 PR,但会改变本次运行的交付结果:完成报告会先在 需要你处理 下说明冲突,而不是先显示 请审阅此项。

上下文文件是供人工审阅者查看的史诗摘要;它不是 epic-context.md,后者面向审阅者展示待办事项,不应出现在 PR 正文中。

这是唯一一个以默认分支为目标的 PR,因此也是唯一一个可以在正文中承诺关闭问题的 PR。上下文文件包含史诗的关闭关联行,人工合并后会将史诗工单与其所描述的交付内容一并原子化关闭。

保留草稿状态,绝不要合并。 这正是本次运行能够自主执行的全部原因:由人工阅读一个整合后的分支,按自己的方式进行测试,然后自行合并。将其标记为可审查或进行合并,会悄然把经过审阅的交接变成未经审阅的部署。


第 3 阶段:处理待办清单

取决于 --new-issues:

  • create — 提交已拟好的工单,每个工单各发起一次 FILE_TICKET 调度。
  • propose(默认)— 提交供审批,然后提交已批准的工单。由于这些工单是在本次运行期间拟好的,用户审批的是完整工单,而不是一句话摘要加上一句承诺。
  • never — 不提交任何内容;草稿保留在状态目录中,并显示在报告里。

如果某个绑定的 create 是空操作或从未解析,其处理方式与 comment 和 close 相同:草稿留在状态目录中,完成报告会列出这些草稿,而且第 0 阶段的确认中已说明这一点。--new-issues=never 根本不需要 create,因此缺少 create 只会阻碍另外两种策略。

每次向跟踪系统写入内容时,都要说明内容来自 Agent。 评论和工单会以用户的账号发布,日后阅读的人不应需要猜测内容是否由真人撰写。在顶部加一行简短说明就够了——无需繁琐,但要明确。


第 4 阶段:完成报告

优先列出需要人工处理的内容。合并后的列表最不重要——有空时再看这些 PR 即可。需要采取行动的三行内容必须在首屏中醒目呈现。

Epic {name} — {n} of {m} issues merged into epic/{slug}, {duration}

REVIEW THIS  {epic PR url}  — draft, unmerged, waiting for you

MERGED       #41 #42 #43 #44 #45 #47 #48 #50 #51
             {PR links}
NEEDS YOU    #46  CI still red after {n} fix attempts. PR {url} open.
             #49  plan agent refused — ticket doesn't specify {thing}.
STRANDED     #52  blocked by #49

EPIC VERIFY  round 1: {n} blocking → remediation merged; round 2: clean
             {or: round 3 still flags {thing} — the epic PR carries the detail}
SYNC         {k} syncs, {c} with conflicts; epic/{slug} is {behind} behind {target},
             {merges cleanly | CONFLICTS}{; syncing off since sync PR {url} failed}
NEIGHBOURS   {other epics Phase 0 flagged, and the files they share with this one now}
FRICTION     {CI failures and the fix attempts they cost, issues sent back to
             absorb a moved epic tip}
PERFORMANCE  {issues green on their first CI run, CI-fix attempts per issue,
             blocking findings per epic-verification round}
DISCOVERIES  {n} tickets drafted{, awaiting approval}{, + 1 combined cleanup ticket}

Epic PR 放在首位,因为这是本次运行无法替用户完成的唯一事项。仅当第 0 阶段标记过 NEIGHBOURS 时才显示它;重新统计其共享文件。

当 Epic PR 与目标存在冲突时,用以下内容替换 REVIEW THIS 行——冲突的 PR 意味着人类必须先完成本次运行的工作,之后才能审核:

NEEDS YOU    {epic PR url}  — draft, CONFLICTS with {target}
             {last sync PR url and why it failed, or: the target moved after the final sync}

然后,启用回写时,将报告作为 COMMENT 发布到 Epic 工单,并且保持 Epic 处于打开状态。 验证通过并不等于交付:Epic PR 仍是针对默认分支的草稿,在人类合并之前,本次运行产出的任何内容都尚未交付。保持 Epic 工单打开才能准确说明状态;PR 中的 Closes 关联会在人工合并时立即关闭工单,与其所描述的交付同时完成。


状态

运行状态存放在仓库之外的 $XDG_STATE_HOME/epic-runner/{project}/{epic}/ (依次回退到 ~/.local/state/...,然后是 ~/.epic-runner/):

plans/{id}.md          materialized implementation plans
plans/remediation-{n}.md  the finalization plans written from each epic verification
issues/{id}.md         per-issue journey logs written by issue-agents
syncs/{n}.md           per-sync journey logs: every conflicted file and its resolution
discoveries/{n}.md     drafted follow-up tickets
epic-context.md        completed / current / remaining issues, handed to planners and implementers
epic-verify/{n}.json   the verification report for each finalization round
run.json               status blocks, approved HEAD SHA, pending merge-queue work, inferred edges, CI-fix counts, epic branch creation time, sync history, failure reasons, and any halt

epic-context.md 和 epic-verify/ 下的所有报告都是交给子 Agent 的路径,你自己绝不能读取。前者用于在不同议题间传递知识;其余是验证报告,不得读取的规则仍然有效。

绝不能存放在仓库中——冗长的 Epic 会弄乱工作树,污染待审查的差异。也绝不能存放在 /tmp 中——如果运行期间重启,台账和所有已起草的工单都会丢失。

跟踪器负责生命周期;run.json 负责记录证据。 生命周期包括待处理/进行中/已完成/失败、持久化的 PR 关联,以及阻塞依赖项解除的情况——这些都是人可见的信息,并且会在阶段 1 进行时全部写回。证据包括已批准的头部 SHA、CI 证明、合并队列标识符、重试次数、计划、执行日志和未发布的草稿:这些是技术性私有信息,绝不能作为议题已完成的唯一记录。

已中止的运行会记录在 run.json 中,恢复运行不会清除该记录。 声明中止时,立即写入中止信息——议题、已批准的 SHA、实际合并的头部以及合并提交。Epic 分支被污染后,无论过了六小时还是到了下周二,仍然是被污染的;重启不会改变这一点。发现该记录时,应报告并停止;只有查看过该分支的人类才能决定运行接下来可以做什么。以本地状态看起来可行作为理由,在中止后继续运行,是此技能最糟糕的行为,因为这会让未经批准的代码以已审查工作的名义交到人类手中。

恢复运行时,先重新读取跟踪器,再查看未关闭的 PR。如果观察到某个子议题已关闭且状态为已完成,就不得重新实现,无论本地文件写了什么。缺少本地证据时,应通过归属于 Agent 的交付评论和每个 PR 的一次 INSPECT 调度来重建交付记录。如果两者不一致,应在报告中指出差异,而不是将其处理成对已交付工作的二次实现。

只有当未关闭 PR 的头部来自已完成工作的议题 Agent 时,才将其交给合并检查。 只有当 run.json 中该议题有 MERGEABLE 区块,且其中的 HEAD_SHA 与通过 INSPECT 观察到的 PR 当前头部一致时,才可接手该 PR。头部此后发生过变更,或议题没有此类记录,都表明工作尚未完成;应在现有分支上交回议题 Agent 处理,而不是交给合并检查。绝不能根据当前 PR 头部、跟踪器状态,或与代码托管平台报告不一致的 run.json 条目推断审批状态。

恢复运行是重新接续,不是重新开始。 对于 PR 仍未关闭且 CI 正在进行中的议题,应在现有分支上接续,而不是在第二个分支上重新实现。因此,每个分支名称和 PR 正文中都必须包含议题 ID:这样才能识别当前处理的对象。未关闭的同步 PR 也以同样方式接续,使用其 epic-{slug}-sync-{n} 分支——除非 run.json 记录了该同步失败,此时应停止同步。否则,在安排其他工作前,先在恢复运行时衡量差异;数日后恢复的运行可能已经落后许多。如果 run.json 中既没有同步历史记录,也没有创建时间,年龄触发器就没有基准:应将该 Epic 视为已超过一天。

使用 --write-back=off 时,没有持久化记录表明某个议题已完成,因此恢复运行的能力确实会下降。应如实说明,而不是假装情况并非如此。


常驻规则

  • 不要编写代码。 由 Issue Agent 负责。
  • 不要验证。 verify 会在第 2 阶段的最终定稿 Agent 中运行,你只能看到它的三行内容。
  • 不要阅读验证报告或差异。 你的上下文是必须贯穿整个运行过程的资源。
  • 不要亲自调用 forge。 你绝不调用 create-pr 或 check-ci,也绝不修改跟踪器;交付 Agent 会完成这些工作,并把字段交给你。Issue Agent 也会在各自的上下文中运行这些技能——这条规则只约束你。
  • 停止后绝不要恢复运行。 受污染的 epic 分支会一直处于受污染状态;只有看过它的人类才能决定下一步。
  • 绝不要合并 epic PR。 将其作为草稿打开,并把链接交给人类。本次运行的自主权是以最终由人类审查为前提借来的;合并它就等于花掉了你并未获准使用的东西。
  • 绝不要绕过分支保护、放宽规则或自行批准以解除合并阻碍。
  • 绝不要合并未明确显示绿色且可合并的 PR。
  • 保持 epic 同步——参见同步目标。与目标分支有冲突的 epic PR 尚未完成。
  • 一次只允许一个 dev-stack 使用者。 从首次提交到 PR 变绿,实现一个问题都算在内;同步也算。规划、起草和合并不算。
  • 绝不要因等待决定而中断运行,如果第 0 阶段本可以解决这个决定。
  • 你提交的每张工单都必须是真正的工单——经过调查、有证据且可执行。没有哪个项目能接受敷衍的工单;临时仓库只是意味着没人会在那里抱怨而已。

Harness 绑定

上述逻辑与 harness 无关。以下是它映射到的 Claude Code 机制;在其他 harness 上,替换本节,其他部分保持不变。

能力Claude Code
启动隔离的子上下文Agent 工具
为子 Agent 提供独立检出在 Agent 上设置 isolation: "worktree"
研究和起草,但不能编辑计划模式——EnterPlanMode / ExitPlanMode
调用另一项技能Skill 工具
为每个子 Agent 选择模型在 Agent 上设置 model,或使用技能的 frontmatter
整个 Agent 树的并发预算未遇到;如果 harness 有此设置,它会在启动调用时设置,子级会继承

如果 harness 限制并发子 Agent 的数量,这个上限必须由你正确设置,因为它由最外层调用决定,而本技能正是最外层调用。上限必须满足树最深处的需求,而不是最宽处:在第 2 阶段,verify 会分发给九个子 Agent,而两个祖先——此调度器和 epic-verify Agent——仍处于活动状态。若只按某一层的需求设置上限,底层就会无事可做;其表现不是报错,而是审查流水线悄无声息地一次只运行一位审查者。

各角色的模型层级

这棵树涵盖的工作量差异极大,对所有工作统一使用一个层级,要么在底层造成浪费,要么在顶层疏忽大意。这些是成本层级,而非模型——编排器会选择模型,并以这些默认值为依据进行选择。

角色层级
编排器——此技能顶级
plan-agent,整改规划 Agent高
epic-verify-agent高——并非为了追求高层级而高
issue-agent、sync-agent、工单撰写 Agent中
执行 MERGE 操作的 delivery-agent中——这是不可逆操作前的最后一道关卡
执行其他任何操作的 delivery-agent低

在计划上投入成本,让实现者无需费心推理。 plan-agent 的层级高于使用其产出的 issue-agent,这是有意为之:没有审阅者会在工单合并前阅读它,因此计划就是完整的需求文档,也是整个流程中最值得投入智能的环节。依据优质计划工作的 issue-agent 是在执行,而非设计。遇到确实复杂的工单时,可将其层级提高一级——默认值只是起点。

MERGE 这个例外恰好证明 delivery-agent 的层级本来就低。 它执行的其他操作都只是命令和转录,而 MERGE 不同:它会读取合并前的绿色谓词并作出判断,而你的授权是在这些证据出现之前发出的,因此之后不会再有任何环节重新检查。在不可逆操作前的最后一道关卡,不应使用整棵树中最便宜的模型——为每张工单派发一次能够可信地精确应用谓词的模型,是此设计中成本最低的保险。

epic-verify-agent 的层级不是为它自身而设。 它自己的工作只有三行:调用 verify,返回计数。但如果某个框架会让子 Agent 在没有明确选择的情况下继承父级的模型,那么它的层级就决定了它下方全部九位审阅者的层级。第 2 阶段是整个 epic 唯一的审查环节——此技能中没有任何按 issue 进行的验证——因此,使用廉价的调度器几乎省不了什么,却会悄然降低这次审查的质量。应根据它的子级来选择层级。

评论

支持 Markdown