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 编码会话实际上同时承担编码、评审、验证三个职责。项目记忆与防御脚本的审计记录留下了四类典型失败:
check-push-approval.py | ||
check-tracker-refs.py | ||
session-status.py | ||
references/e2e-verification-pitfalls.md |
这些不是模型能力问题,而是组织结构问题:当判断“代码是否合格”的权力和“希望尽快完成”的动机在同一个会话里,验证必然被动机侵蚀。项目在 AGENTS.md 中把教训固化为四条会话纪律:不自批准、一个检出只有一个写者、--no-verify 必须附引用证据、推送必须有记录在案的批准。
1.2 从本地监督到无人值守:迁移动因
2026-08-15 之前,三角色工作流跑在开发者的 Mac 上:编码面是本地检出,人类是事实上的调度器——触发评审、盯着推送、手动重启循环。这带来两个天花板:
- 人类成为循环的同步依赖。 循环每走一步都可能停下来等人确认,夜间与离席时间全部浪费。
- 验证面与编码面同机。 本地测试库与生产工单库历史分叉(同一个
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 公司负责把复杂工单做成可验证的代码,编排器负责让循环不停。

外层三角色的权力划分(AGENTS.md 角色表,迁移后版本):
/mnt/projects/ppt-bot-v2-worktrees/auto-company | |||
conductor-review.py 派遣) | claude-reviewer 账号发布记录 | ||
ppt-v2.izhixue.cc |
两条硬边界保证分立不是纸面的:
- 凭证隔离: 实现者子代理永远接触不到
claude-reviewer凭证(auto mode 规则明文规定); - 机械校验: 即使有人绕过纪律手动发布批准记录,
check-push-approval.py的 approver 白名单(默认仅claude-reviewer,PUSH_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)管理:
技能文件里有一段关键澄清,防止两层角色混淆:
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 三个设计决策值得注意:
- 终止条件外置于工单系统——循环不靠计时器停,靠“未关闭工单数 = 0”停;队列不清空就不下班,清完就自动退场。
MAX_RUNS=12是预算闸——防止异常工单(反复重开、门禁死锁)让循环无限烧钱;触顶即停并留下日志,等人类介入后手动重启。- 每轮之间重新读队列——优先级排序由跟踪器负责(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-companyworktree 可写;部署检出只允许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.jsonl,check-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-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 投入产出分析
关键的结构性收益不在速度而在可离场性:循环的每一步失败都有明确的落点(工单留言 + Feishu + 审计日志),人类回来时面对的是排队好的例外,而不是一堆需要考古的现场。
4 可迁移的设计思路
- 权力分立必须落到凭证与校验层。 “不许自批准”写在文档里是纪律,写进 approver 白名单 + 摘要校验才是机制。迁移任何多代理工作流时先问:批准者身份如何被系统识别?批准记录能否被伪造者发布?
- 用摘要把批准绑定到精确对象。 PUSH-APPROVAL 的 sha256 digest 让“批准后偷改”在数学上失效。任何“评审通过”类记录都应绑定被评审内容的哈希,而不是依赖时间戳或口头承诺。
- 终止条件外置 + 预算闸双保险。 循环靠“队列清空”自然终止(目标导向),靠 MAX_RUNS/单轮预算强制终止(成本导向),二者缺一不可:前者让它勤奋,后者让它可控。
- 内层质量门与外层权力门分离。 子代理之间的互评(Task Reviewer/Reviewer 波次)提升交付质量,但不能替代独立批准权。内层再严格,外层批准者必须是另一个凭证、另一个进程、另一套提示词。
- 检测者不修复,修复者不检测。 守望者只立单、治疗者只续跑、循环只按工单干活——每个组件的职责单一到可以用一句话说清,故障归因才不会变成考古。
总结
ppt-bot-v2 的 AI Company 工作流把“角色分离”从组织口号做成了机械事实:外层三角色以凭证隔离与摘要门禁保证批准权独立,内层七角色以六波次与证据纪律保证交付质量,编排器以 cron 心跳与工单驱动的终止条件保证循环自治。迁移到生产服务器后的 40 小时里,它以 45 次提交处理了 41 个工单,包括一次 25 分钟的问题闭环,而人类只做了立项与例外处理。它给出的最重要经验是:无人值守开发的可信度不来自更聪明的代理,而来自让代理无法越权的架构——信任永远在门禁里,不在会话里。
联系方式: linmk@tup.tsinghua.edu.cn
夜雨聆风