graphlemon 构建报告
六个 Phase,45 个 Unit,一套真正跑起来的"熄灯"软件工厂。
graphlemon 是一个 Level 5 自主研发系统:固定的 Planner → Coder → Validator Org Graph,驱动每个任务
动态生成的 Work Graph,完全跑在 Claude Code 自己的 claude -p 后端之上——能写代码、能抓自己的回归,
到本次会话结束时,还能在无人读 diff 的情况下重新部署自己正在运行的进程。
- 状态:PH0~PH5 全部完成,45 / 45 个 Unit
- 测试:552 个测试全绿
- Git:
fc94ae8(初始基线)→59e96d3(U-043,当前 tip) - 数据模型:17 张 sqlite 表
- Unit 分类:Risk Gate 18 / Value Path 17 / Enabler 10
- 过程中"停下来问用户"的关键决策:3 次
Phase 链路
PH0(9 units,Enabler)→ PH1(6 units,MVP 闭环)→ PH2(7 units,质量闭环)
→ PH3(10 units,控制台)→ PH4(3 units,全量回归收口)→ PH5(10 units,自举能力)
PH0:脚手架与第一个高风险假设
Unit:U-001 ~ U-008、U-004.5
15 张核心表的数据模型、Hermes 人设迁移到 .claude/agents/*.md、一套受 RUN_REAL_AGENT_E2E 环境变量
门控的真实 API 测试 harness,以及本地后端/前端的骨架——后面一切工作的地基。
假设验证——不是"干净通过":A-2 假设在这里被真正验证:Planner/Validator 的人设几乎可以原样迁移, 但 Coder 的
SOUL.md不行——里面硬编码了一个真实的飞书 Base API token、整段"认领"机制强绑定lark-cli、还指示自己再跑一次claude --dangerously-skip-permissions,三处都必须删除或重写才能安全 运行。
PH1:第一条真正跑通的闭环
Unit:U-009 ~ U-014
用"实现贪吃蛇游戏"这个目标真实跑通全链路:Planner 产出 BRD→PRD→SDD→Benchmark,Coder 实现并自测, Validator 复核并给出根因归属——架构文档 §11 验收标准第 1-3 条,是真跑出来的,不是模拟的。
假设确认:A-1 假设在这里被确认——一次真实花费 $0.46 的 spike 证明了 Claude Code 的动态工作流原语 (
agent()/parallel()/pipeline())可以通过claude -p无交互调用,并返回可解析的结构化结果。平台层怪癖(实测发现):
--json-schema一旦和--agent同时传,会被静默忽略——模型退化成把结构化 内容塞进自由文本里的一个```json围栏代码块,而不是填充structured_output。官方文档没有 说明,是靠真实调用发现的。
PH2:教会它从自己的错误里恢复
Unit:U-015 ~ U-021
三视角根因判定、Org-level Loop 的 fast_lane/full_replan 路由、双层预算护栏、Claude plan 额度耗尽的
等待重试机制、Graph Engine 断点恢复。人为植入一个 bug,从一端进去,从另一端出来时已经是一次真实的修复、
重新验证、任务关闭。
PH3:一个不需要构建步骤的控制台
Unit:U-022 ~ U-030、U-024.5
项目创建、目标输入、实时仪表盘、任务看板与详情页、DAG 图可视化、实时日志查看器、Artifact/Skill 浏览器、 配置页——纯原生 HTML/CSS/JS,配一个手写的 WebSocket 服务端,因为"纯 stdlib、无第三方依赖、无构建步骤" 这条约束对 graphlemon 自己的控制台同样适用。
真实 bug:HTTP 服务端各请求线程和 WebSocket 轮询线程共享同一个 sqlite 连接时,如果关闭连接没有 持有和读写操作同一把锁,会直接把解释器段错误崩溃——"关闭"看起来像一次性操作,但对同一个连接对象 而言并不天然安全。修复后连续压测多次全部稳定。
后期补漏:一次收口审计中发现,WebSocket 重连后任务看板和任务详情页从未真正重新拉取过状态快照—— 只有仪表盘做出反应,而且也只是切换了一个提示条。现在三处重连后都会真正刷新,符合 REQ-12 自己的边界 用例要求。
PH4:两轮审计,十个真实缺口
Unit:U-031 ~ U-033
把 06 节 REQ-01~REQ-14 的全部验收用例,逐条对照实际测试覆盖重新核对了两遍——而不是相信"单测全绿"就 等于"验收要求真的被覆盖了"。
结构性缺口:Work Graph 的三个核心控制器(规划、执行、单节点运行)各自都做过完整单测,但从来没有 在生产代码里被真正串联调用过——唯一验证过的闭环(PH1 的贪吃蛇场景)悄悄绕开了它们,走了一条更简单 的并行路径。补上这层编排后,真实 API 调用又暴露出两个新 bug(一个 manifest 字段名不匹配、一个缺失的 工作目录),都是只有真实调用才会暴露的问题。
第二轮审计:PH5 开工前对验收用例的又一次逐条复核,发现了 5 个新的真实缺口:一处遗漏的中间状态写入、 一个从未真正测过的调度器场景、三条"节点被标记 blocked 但从未告知父任务"的失败路径、一个原本完全不存在 的 PRD 一致性检查、外加一处偏弱的测试断言。其中一次修复(新增的一致性检查调用)当场抓到模型把
consistent字段返回成 JSON 字符串"false",而 Python 的真值判断把它当成了True。假设正式收口:A-2 假设在这里正式收口,结论仍是"部分成立"——PH0 之后又有四个不同 Unit 里真实调用 过迁移后的人设,没有再暴露新的偏差。但结论不会因此升级为"confirmed":那次重写已经发生了,后面跑得再 干净也不能把它抹掉。
PH5:教会它重新部署自己
Unit:U-034 ~ U-043
一整套蓝绿自部署机制:在独立的 git worktree 里拉起候选实例、跑三项 cutover 前健康检查、切换生产流量、 在监控窗口内采样四类信号、必要时回滚——包括"回滚本身也失败"这条兜底路径。
SDD 内部冲突:设计文档和正式规范对"到底什么条件会触发一次自部署"有分歧——设计文档说"self-target 改动通过验收即可",规范原文却把它挂在一条已存在的
core_enginebug 记录上——而一次干净的自我改进任务 根本不会产生这条记录。没有挑一种理解悄悄往下走,而是直接问了用户。真实 bug:用
kill(pid, 0)做存活检测分不清"真的在跑"和"已经退出但从未被父进程回收"——一个僵尸 进程能让这个检测正常通过。一个真实(未 mock)的、真的等待进程消失的测试因此一直挂起,直到补上正确的 进程回收逻辑才修好。其他发现:缺一个跨重启追踪候选实例 checkout 目录的字段、后端服务完全没有命令行入口、参数表里唯独 缺一个固定的生产端口、也没有
/api/health端点给健康检查调用。
⚠️ 尚未真实执行的一项
U-042 的验收测试——对本仓库执行一次真实的自举 cutover,真实调用 claude、真实替换正在运行的进程——已经
建好并且在 mock 下全部通过,但被一个刻意设置得更严格、和项目里其他真实测试用的环境变量不同的开关
(RUN_REAL_SELF_HOSTING_E2E=1)挡住,因为这是整个代码库里爆炸半径最大的一个动作:它能替换掉正在运行
的 graphlemon 进程本身,还会在这个真实仓库上执行 git revert。
这项测试从未被真正执行过。 是否要真的跑一次,是需要用户决定的事,不是构建过程自己能替你做的决定。
假设台账
| ID | 假设 | 结论 |
|---|---|---|
| A-1 | Claude Code 的动态工作流原语可以通过 claude -p 无交互调用,并返回可解析的结构化结果 |
confirmed |
| A-2 | 本地 Hermes 人设文件(SOUL.md/AGENTS.md)可以原样迁移到 .claude/agents/*.md,无需大改 |
部分成立(已收口) |
graphlemon · 纯 stdlib Python 实现,无任何第三方依赖 · 552 个测试 · 17 张表 · fc94ae8 → 59e96d3