OpenSpec 规格驱动 & 收官串讲
最后一天:讲清平台"自己开发自己"背后的规格驱动流程,然后把 20 天串成一张完整地图,并给你继续深入的路线。昨天(Day 19)你亲手盖了一户;今天讲的是"这栋楼怎么让 AI 施工队自己扩建自己"——靠的是先出图纸(规格)再动工。最后把 20 天从地基到物业完整回望一遍,正式收官。
openspec/ = 物业的档案室(现行房产证 specs / 报建中的申请 changes / 办结归档 archive);propose→apply→archive = 报建三段(出方案报审 → 照批复施工 → 竣工归档);自我开发闭环 = 让 AI 当施工队干重复活,但关键节点(评审、合并)人牢牢握着方向盘。这就是"连让 AI 开发 AI 这么危险的事,都用规格兜住了"。什么是规格驱动开发(SDD)
规格驱动开发(Spec-Driven Development):不是上来就写代码,而是先把"要做什么、怎么做、验收标准"写成正式规格文档,评审通过后再照着实现。本框架用 OpenSpec 工具做这件事,目录在 openspec/。
为什么一个 Agent 框架要这么严格?因为它有一条"平台自己开发自己"的流水线(Day 15)——让 AI 去改平台代码,必须有清晰的规格作为契约,否则 AI 改飞了没人拦得住。规格就是人和 AI 之间的合同。
openspec 目录结构(翻开真目录)
别停在描述,直接 ls 平台的 openspec/——这是真实的三块 + 真实的数量:
$ ls openspec/
changes schemas specs
$ ls openspec/specs | wc -l # 现行有效的正式能力规格
61
$ ls openspec/changes | grep -v archive | wc -l # 进行中的提案
13
$ ls openspec/changes/archive | wc -l # 已完工归档(完整迭代史)
173
$ ls openspec/changes/archive | tail -3
2026-05-30-add-supervisor-router
2026-05-30-extend-portal-invocation-with-verify-fields
2026-05-31-restructure-bmc-maven-langgraph-extended-phases
随便进一个进行中的 change 看它的 4 份文件(真实目录 openspec/changes/add-agent-effect-metrics/):
$ ls openspec/changes/add-agent-effect-metrics/
design.md proposal.md specs/ tasks.md
| 文件 | 内容 |
|---|---|
proposal.md | Why / What Changes / Impact——为什么做、改什么、影响哪些包 |
design.md | 设计决策(DD) |
tasks.md | 勾选清单([x]完成 [~]进行中 [ ]待办) |
specs/<cap>/spec.md | spec delta:这次要新增/改的 Requirement + Scenario |
真读一段 proposal.md 的开头(就是 Day 21 会讲的那条 telemetry 特性的提案,裁剪):
# openspec/changes/add-agent-effect-metrics/proposal.md
## Why
平台已有 invocation telemetry 管道,但只采**通用**指标(calls/cost/success)。
各 agent **各自的业务效果**——bmc-agent 升了多少工程、arch-compliance 扫了多少违规
——无处可放...而业务方开发 agent 时**不会主动关注上报**。所以效果采集必须
**下沉到框架依赖库**(ai-trust-toolkit),把业务方负担压到"发一行语义信号"。
## What Changes
- 新 record_effect(key, n) · per-invoke ContextVar 累加器
- push_invoke_metrics 加 effect_metrics 自动透传 ... 老 agent 无字段走 None · BC
## Impact
- toolkit: 新 record_effect / effect ContextVar · 纯 additive · BC
老 agent 无字段走 None · BC——BC = Backward Compatible(向后兼容),规格里明确承诺"不破坏老 agent"。Impact 说影响哪些包。这三段就是人和 AI 之间的合同:动工前把"做什么、怎么不出错"写死。changes/ 和 specs/ 拆成两个目录,而不是直接改 specs?
你可能觉得"要改能力,直接改 specs/xxx/spec.md 不就行了"。但那样正在改的半成品和已生效的真相就混在一起了——别人分不清哪条是"现在真有的能力"、哪条是"某人提议但还没做的"。拆开后:specs/ 永远只放"已生效",changes/ 放"审批中",等 change 完工再把它的 spec delta 合并进 specs/ 并归档(L03 的 archive)。这就是"现行房产证"和"报建申请"必须分柜存放。propose / apply / archive 三段工作流
通过 slash command 驱动(docs/new-agent-openspec-cookbook.md):
① propose
/openspec-propose <id>- AI 一次性生成 design + specs + tasks + proposal
- 落到
changes/<id>/ - 人 review 4 份文件(不满意让 AI 原地改,别手改)
② apply
/openspec-apply-change <id>- AI 按 tasks.md 逐条把代码写出来
- 跑测试、勾选完成项
③ archive
/openspec-archive-change <id>- 把 change 的 specs 搬进正式
specs/ - 整个 change 目录移到
archive/
| 阶段 | 此刻发生什么 | 目录状态 |
|---|---|---|
| ① propose | AI 一次生成 proposal/design/tasks/spec delta 四份文件;人 review 不满意让 AI 原地改 | changes/add-echo-agent/ 出现 |
| ② apply | AI 按 tasks.md 逐条写代码、跑测试、勾选完成项 | 该目录 tasks 全 [x],代码进 apps/echo-agent |
| ③ archive | 把 spec delta 搬进正式 specs/<cap>/,整个 change 目录移走 | 移到 changes/archive/<date>-add-echo-agent/ |
add-X-agent / modify-X / remove-X),一个 change = 一个 capability。这套流程把"改动"变成了有提案、有评审、有验收、有归档的正式过程——和 Git PR 类似,但粒度是"能力规格"而非"代码 diff"。自我开发闭环:4 个 agent 逐个走读真代码
Day 15 见过的自我开发流水线,本质就是把上面人工的三段流程"Agent 化"——用 4 个 agent 代替人跑 propose/apply/评审。它们全是真 app,就在 apps/ 下,今天挨个翻源码:
discoverer发现需求→IssueDraft→ spec-author= /openspec-propose
自动生成 change→ 人 review把关→ spec-executor= /openspec-apply
写代码提 agent/*→ release-
coordinator并行 gate 评审
① spec-author:把 /openspec-propose 自动化 + 自修复循环
它是一张 8 节点的 LangGraph 图,末尾有个巧妙的 repair loop。看 apps/spec-author/spec_author/builder.py:40:
REPAIR_LIMIT = 2 # builder.py:40
def _validate_router(state) -> Literal["reporter", "repair", "give_up"]:
vr = state.get("validate_result", {}) or {}
status = vr.get("status", "pending")
if status == "valid":
return "reporter" # 校验过 → 去汇报
cur = int(state.get("self_repair_count", 0))
if cur < REPAIR_LIMIT:
return "repair" # 没过且还有修复额度 → 去修
return "give_up" # 修了 2 次还没过 → 认怂(标 failed_2x)
# ... 图里:
g.add_conditional_edges("validate", _validate_router,
{"reporter": "reporter", "repair": "repair", "give_up": "reporter"})
g.add_edge("repair", "write_files") # repair 后重新 write → validate(成环)
逐行读:AI 生成完 4 份文件后跑一遍 openspec validate --strict(validate 节点)。_validate_router 看结果:过了去 reporter 收工;没过且修复次数 < 2 去 repair 让 AI 看着报错自己改,改完回 write_files 重写、再 validate——形成一个最多转 2 圈的自修复环;转满 2 圈还不过就 give_up(仍进 reporter,但标 failed_2x 让人知道"这个 AI 搞不定,你来")。
REPAIR_LIMIT = 2,不是无限重试?
LLM 生成的规格不合法很常见(少个 Scenario、格式错)。让它看报错自己修,是省人力的好事。但如果无限重试,一个 AI 会不会陷进去反复改同一个错、烧一堆 token 还出不来?会。所以硬性封顶 2 次——"能自愈就自愈,2 次还不行就诚实交给人",而不是假装 AI 万能。这是"给自动化留逃生口"的典型:自动化不是把人排除,是把人从重复劳动里解放、只在真卡住时介入。② scope guard:AI 只能写"本次 change 目录"
让 AI 写文件最吓人的是"它乱写把主仓刨了"。spec-author 的 safe_change_write 就是那道闸——看 apps/spec-author/spec_author/tools/safe_change_write.py:14:
_DENY_PREFIXES = ( # safe_change_write.py:14 · 黑名单
"openspec/specs/", # 正式规格是 ground truth · 只能走 archive 流程改
"apps/spec-author/", # 自己不许改自己(防递归)
".env", ".git/", "secrets/", "deploy/")
def is_change_path_allowed(rel_path: str, change_id: str) -> tuple[bool, str]:
rp = _normalize(rel_path)
if ".." in rp.split("/"):
return False, "path 含 `..` parent traversal 拒" # ← 防目录穿越
for deny in _DENY_PREFIXES:
if rp.startswith(deny):
return False, f"黑名单前缀 {deny!r}"
expected_prefix = f"openspec/changes/{change_id}/"
if not rp.startswith(expected_prefix): # ← 只准写本次 change
return False, f"必须在 {expected_prefix!r} 子树 · 实际 {rp!r}"
if not re.match(r"^\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*$", change_id):
return False, f"change_id 格式不规范:{change_id!r}" # ← 连 id 格式都校
return True, ""
openspec/specs/、.git、.env、密钥、部署配置——AI 一律不许碰。防穿越:路径里出现 ..(想跳出去到父目录)直接拒。白名单:写的路径必须以 openspec/changes/<本次id>/ 开头——AI 只能在自己这一单的申请袋里写字,串到别的 change 或主仓代码都报错。甚至 change_id 的格式都用正则校(必须 YYYY-MM-DD-slug)。层层收窄,把"AI 能造成的破坏"锁死在一个目录里。safe_change_write_file(..., overwrite=False)(safe_change_write.py:99):if abs_path.exists() and not overwrite: raise FileExistsError。默认不许覆盖,只有 repair loop 重写时才显式传 overwrite=True。为什么? 防两个并发任务/两次误调把彼此的产物盖掉(race)。默认最保守,需要覆盖的场景才主动解锁——又是"危险操作要显式声明"。③ spec-executor:写代码只能提到 agent/* 分支
spec-author 只写 spec,真改代码的是 spec-executor。它的 git 操作同样上了锁——apps/spec-executor/spec_executor/tools/git_tools.py:82:
async def git_commit(*, worktree_path, message, branch) -> str: # git_tools.py:71
"""在 worktree 内 commit 到 branch · 强约束 agent/* · 返 commit hash。"""
if not branch.startswith("agent/"): # git_tools.py:82
raise GitError(f"commit only allowed on agent/* branches · got {branch!r}")
...
# 连建 worktree 都强制 agent/* 前缀:
async def git_worktree_add(*, branch, base_branch="HEAD", ...): # git_tools.py:34
if not branch.startswith("agent/"): # git_tools.py:45
raise GitError(f"worktree branch must start with 'agent/' · got {branch!r}")
大白话:spec-executor 把改动全放在临时 worktree(ephemeral)里干,且只能 commit 到 agent/* 开头的分支——它永远碰不到 main。commit 到别的分支?raise GitError。这就保证了"AI 改的东西必须经过一个 agent/* 分支 → 人 review → 人来合并到 main",AI 没有任何直接动主干的能力。
④ release-coordinator:并行 6 道 gate 出 PR 决策
AI 提了 agent/* 分支,谁来评审?release-coordinator——一张扇出并行的图(就是 Day 05 的并行套路),看 apps/release-coordinator/release_coordinator/builder.py:71:
builder.add_node("gate_ruff", gate_ruff) # 代码风格
builder.add_node("gate_mypy", gate_mypy) # 类型
builder.add_node("gate_pytest", gate_pytest) # 测试
builder.add_node("gate_arch", gate_arch_compliance) # 架构合规
builder.add_node("gate_risk", gate_risk_reviewer) # 风险(调 risk-reviewer agent!)
builder.add_node("gate_doc", gate_doc_check) # 文档
# scan_diff → 6 gate 并行(同 source 多 add_edge 即并行) → merge_decisions
for g in ("gate_ruff","gate_mypy","gate_pytest","gate_arch","gate_risk","gate_doc"):
builder.add_edge("scan_diff", g)
builder.add_edge(g, "merge_decisions")
AGENT_META(main.py:167)把 release-coordinator 描述成 "5 gate(ruff/mypy/pytest/arch/risk)",但你数一下上面 builder 里实际是 6 个 gate——多了 gate_doc。这就是"文档/描述是快照,代码才是真相":描述写在先、后来加了 doc gate 没同步。读真实项目一定以 builder.py 为准。这个坑本身就是最好的一课。safe_change_write 只准写本次 change 目录、git_commit 只准提 agent/*);③ 自修复有上限(repair ≤2,搞不定诚实交人);④ 并行 gate + 合并方向盘永远在人手里。这让"AI 自己改平台"从危险变成可控——AI 只在护栏内自动化重复劳动,人守住方向盘。连"让 AI 开发 AI"这么危险的事,都用规格 + scope guard + gate + Critic 层层兜住了——这就是全框架"可信"二字的最高体现。👶 小白:让 AI 去改平台自己的代码,这不吓人吗?万一它改飞了、把地基刨了怎么办?
👨🏫 老师:所以它不是"放手让 AI 乱盖",而是"AI 当施工队、人当监理"。你已经看到四道锁的真代码了:写规格只能写申请袋(safe_change_write 黑白名单)、写代码只能提 agent/* 分支(git_commit 报错拦)、生成不合格自己修但最多 2 次(REPAIR_LIMIT)、最后 6 道 gate 拦一遍 + 合并永远人拍板。AI 只干"照图施工"的重复活,"要不要盖、盖成什么样"始终是人说了算。
20 天全景回顾
恭喜走到这里。把 20 天串成一张地图:
一次请求的完整旅程,现在你能完整讲出来了:
用户提问
→ server 治理链(限频/配额/预算/生成case_id/绑记账器) [Day 12]
→ agent 的 LangGraph 图: [Day 03/05/14]
triage 分诊(规则) → recall 召回历史(情景记忆) [Day 09]
→ 4 专家并行取证(Sonnet, 失败自动兜底) [Day 08/11]
→ rag 查 SOP(语义记忆) → synthesizer 综合(证据不足则跳过) [Day 08/09]
→ critic 双层防幻觉(L1代码+L2 Haiku, 三态重试) [Day 07]
→ writeback 守门后沉淀经验 [Day 08/09]
→ 包成统一 envelope → 脱敏/PII scrub → 落库 → 推 Prometheus [Day 08/11/12]
→ 全程成本自动归因(ContextVar)、可被评测(golden set)、CI 拦退步 [Day 10/11/18]
贯穿全书的设计哲学
如果 20 天只记 6 句话,就记这些——它们比任何单个 API 都重要:
AGENTS.md 的收尾金句浓缩了这一切:"保持渐进抽象、保持 fake LLM 测试、保持评测样本同库、保持 5 道闸门兜底——其余都可以谈。"
那些"坑"再提醒一次
读这个真实项目,几个务必记住的现实(Day 01 起反复强调):
- 文档数字漂移:文档说 9/12 个 agent,但
AGENT_REGISTRY真数一下是 18 个;release-coordinator 文档写"5 gate"、builder 里实际 6 个——永远以代码/配置为准,文档是快照。 - 配置里的明文密码/API key:反面教材,绝不照抄,真实部署走环境变量/Secret。
- 名字相近但不同:两个 MCP、两个 supervisor、三层命名(注册名/路由名/CLI名)。
- "5 道闸门"其实是 6 道:v0.6 加了 budget_gate。术语要按版本理解。
- 诚实的降级注释:代码里"当前暂时降级/占位"的注释是现状而非终态,读到要留意。
继续深入的路线图
20 天建立了完整地图,想再深入可以:
亲手造一个真业务 agent
按 Day 19 走一遍,从 scaffold 到注册跑通。实践一次胜过读十遍。
精读 toolkit 的 api/ 子包
它占 toolkit 41%(~2190 行),是全平台覆盖率最高的抽象。读透 router.py + cost.py。
跟读 CHANGELOG + ARCHITECTURE.md
从 v0.1 到 v0.6,理解每次抽象的"触发故事"——这是学"何时该抽象"的最佳材料。
读一个变体 agent 对照 sre-rca
如 alert-triage(仅 L1)、security-audit(5 专家)、supervisor-router(纯路由),体会同一套框架的裁剪弹性。
翻 openspec/changes/archive/
173 个归档 change 就是平台的完整演进史(按日期排序),看真实需求怎么变成规格再变成代码。
结业 🎉
你已经完整走过了 gov-agents-platform 的 20 天。现在的你应该能——
- ✅ 向别人讲清"这是个什么框架、分几层、每层管什么";
- ✅ 打开任意一个 agent,从 builder.py 入手快速读懂它的流程;
- ✅ 说清 Critic / 闸门 / 记忆 / 评测 / 成本治理 各自解决什么、怎么实现;
- ✅ 跟一次请求走完从 server 到图到 envelope 的全程;
- ✅ 自己 scaffold、开发、注册、部署一个新 agent;
- ✅ 理解贯穿全书的 6 条设计哲学,并能迁移到你自己的项目。
这套"分层 + 可信工程化 + 渐进抽象"的思路,远不止适用于这一个框架——它是做任何严肃 AI Agent 系统的通用方法论。带走它。