乐于分享
好东西不私藏

把软件公司装进生产服务器:三角色 * AI Company 无人值守自动开发工作流

把软件公司装进生产服务器:三角色 * AI Company 无人值守自动开发工作流

An AI Company in Production: The Three-Role Unattended Auto-Development Workflow

封面

系列说明 本文是 ppt-bot-v2 工程实践系列的“生产运行卷”。角色分离与机械门禁的理论基础见《不信代理,信门禁:基于角色分离与机械验证的多 AI 协作开发流程》(trust-the-gate-not-the-agent.md),服务器与 7×24 运行地基见《先把灯点亮:7×24 自主 AI 开发团队的物理地基》(keeping-the-lights-on-7x24.md),迭代循环的驯服方法见《Loop Engineering 实践:六个问题,六次驯服 Agent 迭代循环》(loop-engineering-in-practice.md)。本文回答的问题是:当这套体系整体搬上生产服务器、人类离场之后,它如何自己运转、自己修自己。


摘要

单人 + 多 AI 会话的开发模式有一个结构性缺陷:同一个会话既写代码、又判断代码是否合格、又决定何时上线。角色冲突导致自批准、幽灵工单、半途而废与未验证部署反复出现。ppt-bot-v2 项目用一次架构迁移解决了这个问题:把“编码—评审—测试”拆成三个物理隔离的角色面(Codex 编码面、Claude 评审面、生产服务器验证面),再在生产服务器上嵌套一个由七个子代理组成的“AI 公司”(Brainstormer/Planner/Implementer/Task Reviewer/Verifier/Reviewer/Fixer,六个波次),外层用 cron 心跳 + 编排脚本驱动整个循环直到工单队列清空。所有信任点都被机械门禁取代:推送必须携带评审者以独立账号发布的 PUSH-APPROVAL 摘要记录,提交必须引用真实存在的工单号,钩子绕过被逐条审计。迁移后约 40 小时的无人值守窗口内,该工作流自主完成 45 次提交、处理 41 个工单,其中包括一次“发现卡死 → 立单 → 修复 → 部署”全程约 25 分钟的闭环。本文完整描述该架构的角色划分、嵌套编排、门禁实现与运行数据,并提炼可迁移到其他项目的五条设计原则。

关键词: 无人值守开发;角色分离;多代理编排;机械门禁;工单驱动;AI 公司工作流


1 问题:为什么“一个人 + 一堆 AI 会话”不够

1.1 会话身兼三职的四种失败形态

在 2026-08-10 确立角色纪律之前,ppt-bot-v2 的每个 AI 编码会话实际上同时承担编码、评审、验证三个职责。项目记忆与防御脚本的审计记录留下了四类典型失败:

失败形态
具体表现
留下的证据
自批准
编码会话给自己的推送写 approval 记录
check-push-approval.py
 引入 approver 白名单的直接动因
幽灵工单
提交信息引用根本不存在的 TRA-N
check-tracker-refs.py
 历史审计发现 73 个幽灵引用
半途而废
提交后不推送、不关单,会话终止
session-status.py
 开局即检查“未推送提交”
未验证部署
“已部署”被当成“已验证”
references/e2e-verification-pitfalls.md
 的过早宣布修复清单

这些不是模型能力问题,而是组织结构问题:当判断“代码是否合格”的权力和“希望尽快完成”的动机在同一个会话里,验证必然被动机侵蚀。项目在 AGENTS.md 中把教训固化为四条会话纪律:不自批准、一个检出只有一个写者、--no-verify 必须附引用证据、推送必须有记录在案的批准。

1.2 从本地监督到无人值守:迁移动因

2026-08-15 之前,三角色工作流跑在开发者的 Mac 上:编码面是本地检出,人类是事实上的调度器——触发评审、盯着推送、手动重启循环。这带来两个天花板:

  1. 人类成为循环的同步依赖。 循环每走一步都可能停下来等人确认,夜间与离席时间全部浪费。
  2. 验证面与编码面同机。 本地测试库与生产工单库历史分叉(同一个 TRA-N
     在两个库里指向不同工单),验证结论的可信度打折。

迁移提交 9e08c69(TRA-424,“Migrate the 3-role workflow fully to prod: AI Company auto mode”)一次性完成了三件事:角色表改为纯生产拓扑(Hermes 作为 CEO 常驻生产服务器,编码面改为 auto-company worktree,Claude CLI 评审器安装在生产服务器并通过冒烟测试 PROD-REVIEWER-OK);ai-company-workflow 技能从生产文件系统收编进仓库(798 行 SKILL.md + 6 份 references);AGENTS.md 的角色面同步更新。自此人类的角色从“调度器”退化为“工单提交者与例外处理者”。


2 架构:外层三角色 × 内层七角色 × 一个编排器

2.1 方法概述

整个体系是三层嵌套结构:外层三角色负责权力分立,内层 AI 公司负责把复杂工单做成可验证的代码,编排器负责让循环不停。

图 1 无人值守三角色工作流闭环(mermaid 源文件:images/fig-ai-company-loop.mmd)

外层三角色的权力划分(AGENTS.md 角色表,迁移后版本):

角色
执行者
运行面
职责边界
编码
Codex CLI / AI Company 波次
/mnt/projects/ppt-bot-v2-worktrees/auto-company
实现、测试、提交;无权批准自己
评审
Claude CLI(经 conductor-review.py 派遣)
生产服务器独立进程
唯一批准权;以 claude-reviewer 账号发布记录
测试与工单
生产服务器运行时 + 生产跟踪器
ppt-v2.izhixue.cc
运行时验证、E2E 证明、工单是唯一的真相源

两条硬边界保证分立不是纸面的:

  • 凭证隔离: 实现者子代理永远接触不到 claude-reviewer 凭证(auto mode 规则明文规定);
  • 机械校验: 即使有人绕过纪律手动发布批准记录,check-push-approval.py 的 approver 白名单(默认仅 claude-reviewerPUSH_APPROVAL_APPROVERS 可覆盖)会把编码账号发布的记录判为 self_approval 并拒绝推送。

2.2 内层 AI 公司:七角色、六波次

外层循环处理的是“一个工单从立项到关闭”,而工单内部的开发工作由 AI 公司完成。skills/ai-company-workflow/SKILL.md(v2.0.0)定义了七个角色在六个波次中的分工,由 Hermes 的 ai-company 插件(9 个工具:company_start/company_dispatch/company_dispatch_task/company_status 等,SQLite 持久化会话历史,开源仓库 back1992/hermes-plugin-ai-company)管理:

波次
角色
职责
关键纪律
1
Brainstormer
苏格拉底式追问 → 设计文档
YAGNI 追问阶梯,一次一问
2
Planner
拆解为零上下文小任务
每任务 2–5 分钟、自带 TDD 步骤
3
Implementer + Task Reviewer
逐任务红绿重构 + 逐任务评审
Iron Law TDD:先有失败测试
4
Verifier
全量验证
“应该能过”不是证据,必须粘贴命令输出
5
Reviewer
整支分支复审
规格符合 + 过度工程狩猎
6
Fixer
修复评审发现
四阶段系统化调试,三连击升级规则

技能文件里有一段关键澄清,防止两层角色混淆:

AI Company 的七角色是内层子代理,不取代外层三角色——外层评审者仍然是 Claude CLI(经 conductor-review.py),外层部署验证仍然是生产服务器上的 Hermes。Wave 3 的 Task Reviewer 与 Wave 5 的 Reviewer 是内部质量门,不是最终推送批准人。

这个“内层质量门 + 外层权力门”的双层设计,是内层评审再严格也不能省略外层 Claude 评审的原因。

每次 AI Company 会话的产出链是完整的:设计文档(docs/design/<feature>.md)→ 计划(plans/<feature>.md)→ 逐任务实现 → 逐波次收据。提交 b6dcc4f(TRA-438)的信息体就是活标本:


1
2
3


AI Company session fe3efc73: Brainstormer->Planner->Implementer->Reviewer
(all APPROVED, no fix wave). Design: docs/design/extraction-always-on-
progress-eta.md. Plan: plans/extraction-always-on-progress-eta.md.

2.3 编排器:让循环自己转起来

无人值守的心脏是生产服务器上的 ~/.hermes/scripts/auto-fix-loop.sh(约 35 行 bash),逻辑朴素到可以全文引用核心段:


1
2
3
4
5
6
7
8
9
10
11
12


for idx in $(seq 1 $MAX_RUNS); do          # MAX_RUNS=12
  # 等待当前 hermes cron 运行结束(60s 轮询, 上限 180 次)
  while true; do
    st=st" != "running" ] && break
    ...
  done
  oc=$(open_count)                          # 查询生产跟踪器未关闭工单数
  log "run oc"
  if [ "$oc" = "0" ]; then log "ALL ISSUES CLOSED"; exit 0; fi
  if [ $idx -eq $MAX_RUNS ]; then log "MAX_RUNS reached..."; exit 0; fi
  trigger                                   # setsid 触发下一轮 cron 运行
done

三个设计决策值得注意:

  1. 终止条件外置于工单系统——循环不靠计时器停,靠“未关闭工单数 = 0”停;队列不清空就不下班,清完就自动退场。
  2. MAX_RUNS=12 是预算闸——防止异常工单(反复重开、门禁死锁)让循环无限烧钱;触顶即停并留下日志,等人类介入后手动重启。
  3. 每轮之间重新读队列——优先级排序由跟踪器负责(P1 先于 P2),编排器不缓存任何工单状态,天然容忍中途插入的新工单。

2.4 无人值守的硬规则(auto mode)

skills/coding-cycle/SKILL.md 的 “Auto mode (unattended, prod cron)” 一节是给循环立的宪法:

  • 预算: 每轮最多处理 3 个 TRA;每个发现最多 3 次修复尝试(Fixer 三连击),超限即停、Feishu 通知、工单保持打开并留言。
  • 绝不 --no-verify: 门禁失败就停跑,门禁输出贴到工单和 Feishu。
  • 单写者: 只有 auto-company worktree 可写;部署检出只允许 git pull
  • 评审分离: Claude CLI 是唯一批准权;实现者子代理接触不到评审凭证。
  • 停止条件: worktree 被其他会话占用、真实评审后的推送门禁拒绝、部署健康检查失败(自动回滚到上一提交)——任一触发即停、通知、不推送。

3 工程实践:门禁、通知与 40 小时运行数据

3.1 四个关键门禁的实现

推送批准门(pre-push)。.githooks/pre-push 调用 scripts/check-push-approval.py,在生产跟踪器的批准日志(TRA-252)里查找与待推送提交集匹配的记录:


1
2
3


PUSH_APPROVAL_RE = re.compile(
    r"PUSH-APPROVAL\s+digest=([0-9a-f]{64})\s+verdict=(APPROVED|REJECTED)")
DEFAULT_APPROVERS = "claude-reviewer"

摘要 digest 是待推送提交集的 sha256——批准与那一组精确的 SHA 绑定,批准之后任何 amend/rebase 都会改变摘要、强制重新评审。find_approval() 把记录三分类:白名单账号发布的 APPROVED 记为 approved;非白名单账号发布的匹配记录记为 self_approval(拒绝);无匹配记为 missing。2026-08-11 起,编码账号伪造批准在机制层面已不可能。

评审派遣器(conductor-review.py)。 评审不是“问一句 Claude 觉得怎么样”,而是结构化协议:组装 diff(上限 30000 字符)+ 提交信息 + 钩子输出为评审包,派遣 Claude CLI,要求只输出一个 JSON 块


1


{"verdict": "APPROVED", "reasons": ["..."], "evidence_checked": ["..."]}

评审提示词开宗明义:“Your job: verify, not trust”,五项检查包括测试证据必须有可复跑命令支撑、提交信息中的 TRA 引用必须真实、--no-verify 必须附引用的手动输出、“packet 里不得出现 coder 批准自己”。--post 模式下脚本以 claude-reviewer 身份把 PUSH-APPROVAL 机械地发布到 TRA-252——批准记录的产生者是脚本而非任何会话的自觉。

工单引用门(commit-msg,Layer 20)。check-tracker-refs.py 对生产跟踪器走 HTTP 校验提交信息里的每个 TRA-N 真实存在;--require-ref 进一步要求非文档提交必须引用至少一个工单。历史审计发现的 73 个幽灵引用是这个门的立项依据,如今由 .githooks/commit-msg 在提交时硬拦截(跟踪器故障时 fail-open,宁可放行不可阻塞开发,故障本身另有监控)。

钩子审计门(post-commit)。 每次提交把 pre-commit/commit-msg 的通过状态写入 .git/ppt-hook-audit.jsonlcheck-hook-audit.py 定期扫描,--no-verify 的绕过提交与无钩子提交都会被点名。纪律靠审计兜底,审计靠定期 sweep 兑现——这是“两击规则”(同一纪律被违反两次就升级为机械门禁)的闭环。

此外还有 30 余个防御脚本构成纵深(docs/DEFENDER_LAYERS.md):API 契约同步、类型安全、序列化器-模型一致性、测试质量、重复代码拦截、安全模式扫描、Celery 事务安全……它们是内层 Verifier 波次之下的常备地基。

3.2 观测:Feishu 事件流与运行时哨兵

无人值守的代价是失败无人目击,因此通知与哨兵是架构的一部分而非附件:

  • 事件通知: scripts/notify-workflow-event.sh 在五个状态点推送 Feishu——立项(issue)、评审结论(review,conductor-review.py 自动触发)、推送(push,含被门禁拦截的 blocked 事件)、部署(deploy,含失败原因)、关单(close)。TRA-422 引入,TRA-447 补上监控文档。
  • 运行时哨兵: Layer 23 Celery 错误守望者(scripts/celery-error-watchdog.py)扫描 worker 日志、按异常类型 + 最内层项目帧去重、限速地自动立单——只检测、只立单、从不修复,修复永远走三角色循环;Layer 25 凭证完整性门(TRA-443)防止环境变量与数据库密码漂移再次锁死流水线。

3.3 运行数据:迁移后 40 小时窗口

统计区间:2026-08-15 00:00 至 2026-08-16 15:50(迁移提交 9e08c69 之后),生产检出 git log 实测:

指标
数值
提交数
45
涉及工单数
41 个不同 TRA-N
(TRA-391 至 TRA-460 区间)
覆盖层次
解析内核、翻译管线、前端 UX、基础设施、工作流自身
人类介入
立项(含 2 个现场 P1/P2)、DNS 操作、例外恢复

提交分布说明这不是“刷量”:内容缺陷修复(TRA-429/430/431/432/433 一簇)、前端体验(TRA-438 常驻进度条 + 诚实 ETA,附 172/172 测试通过证据)、后端基础设施(TRA-456 长任务独立队列与 worker、TRA-445 任务级单飞守卫、TRA-454 治疗者心跳 + 逐章检查点续跑)、以及工作流对自身的修复(TRA-458 修复 celery worker 节点名冲突——那是它自己的基础设施 bug;TRA-447 把 Feishu 监控写回自己的技能文档)。

案例:TRA-460 的 25 分钟闭环。 2026-08-16 15:10,一本 567 页书籍的翻译管线卡死,py-spy 双采样锁定 pymupdf insert_htmlbox 的链接处理在目录章上空转 45 分钟以上;15:21 编排层以 P1 立案(附完整调用栈证据与修复方向);循环第 3 轮立即拣选该 P1;约 15:35 修复提交 f07bedd(“unwrap <a> tags before insert_htmlbox”)落地并部署;15:47 书籍任务经检查点自动续跑,目录章渲染通过。立案到生产修复约 15 分钟,发现到解困约 40 分钟,全程无人写一行代码。

3.4 投入产出分析

投入项
形态
备注
一次性架构成本
角色纪律 + 门禁脚本 + 迁移(TRA-424)
约两周迭代沉淀,之后随仓库版本化
每轮运行成本
LLM 调用(编码 + 评审 + 子代理)+ 一台生产服务器
预算闸:12 轮/批、3 单/轮、3 次修复/发现
人类时间
立项与例外处理
本窗口内人类操作均为分钟级
产出
45 提交 / 41 工单 / 40 小时
含测试、契约再生、部署、E2E 验证

关键的结构性收益不在速度而在可离场性:循环的每一步失败都有明确的落点(工单留言 + Feishu + 审计日志),人类回来时面对的是排队好的例外,而不是一堆需要考古的现场。


4 可迁移的设计思路

  1. 权力分立必须落到凭证与校验层。 “不许自批准”写在文档里是纪律,写进 approver 白名单 + 摘要校验才是机制。迁移任何多代理工作流时先问:批准者身份如何被系统识别?批准记录能否被伪造者发布?
  2. 用摘要把批准绑定到精确对象。 PUSH-APPROVAL 的 sha256 digest 让“批准后偷改”在数学上失效。任何“评审通过”类记录都应绑定被评审内容的哈希,而不是依赖时间戳或口头承诺。
  3. 终止条件外置 + 预算闸双保险。 循环靠“队列清空”自然终止(目标导向),靠 MAX_RUNS/单轮预算强制终止(成本导向),二者缺一不可:前者让它勤奋,后者让它可控。
  4. 内层质量门与外层权力门分离。 子代理之间的互评(Task Reviewer/Reviewer 波次)提升交付质量,但不能替代独立批准权。内层再严格,外层批准者必须是另一个凭证、另一个进程、另一套提示词。
  5. 检测者不修复,修复者不检测。 守望者只立单、治疗者只续跑、循环只按工单干活——每个组件的职责单一到可以用一句话说清,故障归因才不会变成考古。

总结

ppt-bot-v2 的 AI Company 工作流把“角色分离”从组织口号做成了机械事实:外层三角色以凭证隔离与摘要门禁保证批准权独立,内层七角色以六波次与证据纪律保证交付质量,编排器以 cron 心跳与工单驱动的终止条件保证循环自治。迁移到生产服务器后的 40 小时里,它以 45 次提交处理了 41 个工单,包括一次 25 分钟的问题闭环,而人类只做了立项与例外处理。它给出的最重要经验是:无人值守开发的可信度不来自更聪明的代理,而来自让代理无法越权的架构——信任永远在门禁里,不在会话里。


联系方式: linmk@tup.tsinghua.edu.cn