一览
状态 ⚠️ draft v3 —— 未实现。 本页描述的是一个提案,不是现有行为。仓库里没有任何触发层想做什么 给 copilot 一个触发层,让它对 GitHub 事件无人值守地运行(先做 PR 自动评审,再做 issue 回答/分流) 今天的实际状态 每一次运行都是手动发起的。headless 构件已具备,触发器、幂等/归属规则、安全加固与放量门禁都还没有 落地前必须先有 安全加固(本文档列出的那几项)+ 允许发布的分阶段门禁;发布本身仍受 ALLOW_POST约束讨论 #3 读这一页时请记住:它是设计讨论,不是使用说明。别照着它配置任何东西。
- 状态:draft v3,供团队讨论(已折入两轮 gpt-5.6 评审)
- 作者:tzhouam(+ copilot 分析)
- 日期:2026-07-23
- 讨论:#3
给 copilot 一个触发层,让它对我们的 GitHub 仓库无人值守地运行 —— 先做自动 PR 评审,之后再做 issue 回答/分流。今天每一次 run 都是手动发起的。 headless 的构件已经具备;本 RFC 覆盖触发器、幂等/触发归属规则、 必须先落地的安全加固,以及允许它发布的放量门。
- copilot 现在已被用在 vllm-omni 之外(AgentInfer、afd-plugin);issue #1/#2 就来自 这次采用,且都已修复。手动发起是仅剩的摩擦。
- 实测包络(20-PR 战役,同一批 20 个 PR 上的 3 次生成 replicate —— 不是 60 个独立条目):eco 路径下每次评审约 $0.19、6–13 分钟; precision 0.777 ± 0.026、recall 0.430 ± 0.018,且与已录制基线之间存在有记录的 上下文不对称。读法:评论通常是对的,但覆盖是部分的 —— 它是一个额外的评审者,便宜到可以对每个 PR 都跑;绝不是人工评审的替代品。
- 在 PR 打开后几分钟就到达的评审会改变作者行为;必须被请求才会发生的评审, 大多不会发生。
- headless 一次性:
-p "review <pr-url> and post the review" --yes; URL/裸引用的路由是确定性的(不花 LLM 意图调用),且显式的 post 意图能穿过它 (issue #1 的修复)。 - 双闸的对外写入:命令必须携带 post 意图并且
ALLOW_POST=1; 达不到就是 dry run。推送安全(只有 force-with-lease、保护main)不变, 且不在本 RFC 范围内。 pr.gate_check报告 draft/合并态/失败 CI —— 作为评审上下文, 刻意非阻塞。触发层必须做自己的资格过滤(draft/closed/merged); 它不能倚靠这个 step。- 无人值守的失败路径:
ESCALATION.md、退出码 3、升级邮件。
自动运行把"敌意的 PR/issue 内容"从理论输入变成日常输入,而当前姿态存在真实缺口。 这些必须先落地:
- H1 —— 读取受仓库边界约束。 今天 agent 的读取工具会把相对路径解析到该 run 的仓库根,
但绝对路径原样透传,而
PathScope只约束写。一个被提示注入的评审 agent 可以读取任意宿主文件(密钥、.env)并把它们引用进输出。 修法:对read_file/list_dir/grep/run_shell的 cwd, 拒绝该 run 根/worktree 之外的绝对路径,并拒绝.env*/密钥材料模式 (对话侧的repo_read已经有这套,把它应用到 step 运行时)。 - H2 —— 出站清洗器。 在
pr.post_review发布之前,扫描正文里凭据形状的字符串 (key 前缀、PEM 头、高熵 token),拒绝或脱敏并升级。这是"经评论外泄"的纵深防御; 注意<untrusted_data>围栏是 prompt 指导,不是执行边界 —— H1/H2 才是边界。 - H3 —— 最小权限身份与隔离。 轮询器不得使用环境里的
gh认证 (它可能持有很宽的权限)。要用专用机器账号 + 细粒度 PAT:仅限选定仓库、 PR 读写、别无其他 —— 且轮询器以一个专用 OS 用户身份运行, 它的环境里只有那个 PAT 和它自己的 LLM key,没有宿主的其他凭据。 这也让每一条被发布的评论都带上一个 bot 名字。 - H4 —— 自动运行中不允许 agent 控制的持久化写入。 评审 agent 平时可以提出 skill
candidate(
skill_update_candidate);在自动运行下那是一条从注入通向持久状态的 路径,所以自动触发的 run 禁用知识写入工具。自动 run 是纯读 + 报告; 知识累积仍然是人工发起的流程。
pull_request(opened / ready_for_review)→ 安装 copilot → 一次性运行 → 用仓库 token 发布。
- 优点:事件驱动,团队可见、可重跑,逐仓库选择加入。
- 缺点:fork PR 拿不到 secret,且
pull_request上是只读 token;pull_request_target会把 secret 交给由不可信 PR 触发的 run —— 在 H1/H2 充分浸泡之前不可接受。临时 runner 会重置知识平面 (debug memory、skill 先验);只有已入库的 profile briefing 会留存。 LLM key 会扩散进每个仓库的 Actions secret。
在那台已经跑着夜跑 rebase 的机器上,放一个由 systemd timer 驱动的 copilot-watch
脚本:逐个受管仓库,列出开放 PR → 资格过滤 → 去重(见下)→ 一次性运行 →
记录 + 发布(Phase 1+)。
- 优点:继承整套现成设置(checkout、worktree 缓存)和持久的知识平面; 没有 LLM secret 被导出到 GitHub;约 100–200 行;与夜跑 rebase 同一套运维模式。
- 缺点:触发对团队不可见(结果是可见的);单宿主;轮询延迟(见下)。
那是产品化的形态;真实的托管 + 认证面。不适合我们这个规模。
Phase 0 —— 只报告的影子运行(≥1 周)。 在团队提名的仓库上跑选项 B,H1–H4 已落地。
影子 run 完全不带 post 意图(一个真正只读的 TaskSpec ——
ALLOW_POST=0 是保险带,不是机制)。评审累积在 run 目录里;升级邮件发给运维者。
由人类裁决输出(见下方的门)。
Phase 1 —— 发布。 对通过门的仓库逐个翻开 ALLOW_POST=1。
以 bot 身份经 gh pr comment 发布,带签名页脚
(copilot-review: <repo> #<pr> @ <head-sha> / <config-digest>)。
已知且刻意的限制:这是一条评论,不是 GitHub review 提交 ——
没有 APPROVE/REQUEST_CHANGES 状态,没有 inline thread。
升级到 gh pr review(以及 bot 是否可以设置阻塞状态)是另一份后续提案。
Phase 2 —— 可选的 Actions 前端(只是草图)。 面向想要团队可见触发器的仓库:
仅同仓库 PR(不用 pull_request_target),/copilot review 评论命令用于重跑,
且在使用任何 secret 之前先做发起者权限校验。
每个仓库只有一个触发归属:一个仓库要么由轮询器服务、要么由 Actions 服务,
绝不同时;迁移即转移归属。Phase 2 在这里刻意写得不完整 ——
它会有自己的 RFC(触发安全、权限检查、workflow 溯源)再谈实现;
Phase 0/1 的任何东西都不依赖它。
- 资格:开放、非 draft、未合并/未关闭、在该仓库启用时间戳之后打开 (不做历史积压扫荡)。
- 去重键:
(repo, pr_number)—— 一个 PR 只被自动评审一次,在它首次符合资格时。 新的 push(synchronize、force-push)不会重新触发;重评审只按需发生 (/copilot review,绑定新的 head SHA,且仅限有写权限的评论者)。 这让成本和评论噪声随 PR 数线性增长,而不是随 push 数。 - 账本是缓存;GitHub 才是真相来源。 事务顺序:记
started→ 运行 → 就在发布之前重新解析 head SHA 与 PR 状态(已推进/已关闭 ⇒ 标stale,不发布)→ 发布 → 记posted+ 评论 URL。启动时以及任何崩溃不确定时, 通过查询 bot 的签名评论对账 —— 因此"发布与记录之间崩溃"不会重复发布, 而一条started却死掉的条目会重试而不是被抑制。 把被评审的 SHA 写进页脚,好让人类看得见 review-vs-head 的漂移。 - run 状态与上限:账本条目按
started → reviewed → posted | stale | failed | post_unknown流转。failed最多带退避重试两次,然后升级 —— 一个持续崩溃的 PR 不能每个周期都烧预算;post_unknown(已发布但未记录)只能由签名评论查询来解决。 每次 run 硬超时(30 分钟杀掉)、每日成本熔断(可配,例如跨仓库 $10/天)、 run 目录保留上限。 - 排队要诚实:一次评审要 6–13 分钟;一个 worker、每周期 ≤4 次新评审、
跨仓库 FIFO。延迟是"轻载下约 15 分钟",在突发时线性增长 ——
队列年龄告警(不只是邮件)是健康叙事的一部分,与既有的
/status和每周人工瞥一眼 账本并列。API/限流失败跳过该周期;连续 ≥3 次失败发邮件升级。 - 防回环:触发器忽略由 bot 账号或任何
[bot]发起者撰写的 PR 与评论 —— copilot 绝不因自己(或别的 bot)的输出而触发。
只报告的输出由仓库 owner + 另一位评审者裁决(分歧向更严格的标签收敛): 每条发现被标为 有用 / 中性 / 错误。在 ≥15 个 PR 且 ≥30 条发现上满足以下条件才晋升: 有用率 ≥ 0.75;≥50% 的 PR 至少收到一条有用发现(一个大多沉默的评审者不得通过); 零条严重错误发现(那种会误导合并的"错误阻塞项");且没有未解决的注入/外泄事故。 recall 被跟踪但不设门 —— copilot 是加法。签字:仓库 owner。 这是一道判断门,不是显著性检验 —— 那些下限的存在是为了让"有用率"有意义, 而 owner 可以在任何疑虑下让一个仓库继续留在影子期。
- 评审深度
auto;eco 模型路径。 - issue 自动回答:Phase 0/1 关闭;单独提案、单独的门 —— 错误的 PR 评论浪费评审者几分钟,错误的 issue 回答误导用户。
- 具备写能力的 kind(pr_debug 的修复、rebase 推送):永久排除在自动运行之外。
- 发布仍然是双闸的;自动运行改变的是评审何时跑,不是它被允许做什么。
- 注入的最坏情况(在 H1/H2 之后):一条错误或敌意的评论 —— 它本身可能触发别的仓库的评论 bot;bot 身份(H3)让其他自动化能过滤它, 而清洗器为外泄划定了边界。
- fork PR:仅轮询器(H3 身份只持有评论权限);
pull_request_target保持禁用,除非被显式重新讨论。 - 密钥:Phase 0/1 把所有 LLM key 留在 copilot 宿主上; Phase 2 才把它们加进参与仓库的 Actions secret。
systemctl stoptimer 和 service unit(终止任何活动 worker/run), 然后吊销 bot PAT ⇒ 没有新 run,也没有在途的发布。- 已发布的评论不会被自动撤回:当原因是质量/事故时,回滚流程包括批量最小化 bot 的
评论(GraphQL
minimizeComment),并在配置里关闭/copilot review处理。 - Phase 2:删掉 workflow 文件;触发归属逐仓库回退。
- Phase 0 覆盖哪些仓库?
- 晋升门(≥10 PR / ≥20 条发现上有用率 ≥0.75、零严重错误)是否合适, 除仓库 owner 之外还由谁裁决?
- "每 PR 一次 + 按命令重评审"是否可接受,还是有人希望每次 push 都重评审 (不顾成本/噪声)?
/copilot review是否应该落在 Phase 1(轮询器读评论)而不是等 Phase 2?- 谁运维轮询器宿主、谁负责升级收件箱?
- 评论 vs GitHub review 提交:Phase 1 里"没有状态"这个限制是否可接受, 还是在任何人依赖它之前,inline/review 状态支持就是必需项?
先由 copilot 的分析起草,然后两次经过我们的外部 plan-review 钩子(gpt-5.6)。 第 1 轮(裁决 REVISE)浮现、对照源码核实并折入了:绝对路径读取缺口(→ H1)、 gate_check 非阻塞的表述错误、评论 vs review 提交、synchronize/账本的矛盾 (→ 每 PR 一次的键控)、评测告诫的诚实性,以及事务性发布顺序。 第 2 轮(裁决 REVISE)补充了:skill candidate 写入作为一条注入路径(→ H4)、 不带 post 意图的影子 run、重试/超时/成本上限、防回环、门的下限, 以及"杀掉活动 worker"的回滚 —— 全部折入。 未采纳(记录以备讨论,判定在我们这个规模上不成比例): 带受限出网的完整容器隔离,以及把 Phase 1 阻塞在 GitHub-review 状态发布上。 评审者对这两点的立场可按需索取。