Day 20 / 共 20 天 · 收官

OpenSpec 规格驱动 & 收官串讲

最后一天:讲清平台"自己开发自己"背后的规格驱动流程,然后把 20 天串成一张完整地图,并给你继续深入的路线。昨天(Day 19)你亲手盖了一户;今天讲的是"这栋楼怎么让 AI 施工队自己扩建自己"——靠的是先出图纸(规格)再动工。最后把 20 天从地基到物业完整回望一遍,正式收官。

📍 你在 20 天里的位置(第 4 周:平台化与收官 · 终点)
D18 CI 闸门 D19 起新 Agent 番外 独立仓实战 D20 OpenSpec & 收官 🎉
💡 用一个类比先兜住今天(延续「盖楼/物业」世界观,最后一次) 今天讲的是「让这栋楼自己扩建自己」——但绝不是让 AI 施工队乱砌。规格驱动开发(SDD) = 动工前先出正式施工图纸 + 验收标准,甲乙双方(人和 AI)签字确认,再照图施工;openspec/ = 物业的档案室(现行房产证 specs / 报建中的申请 changes / 办结归档 archive);propose→apply→archive = 报建三段(出方案报审 → 照批复施工 → 竣工归档);自我开发闭环 = 让 AI 当施工队干重复活,但关键节点(评审、合并)人牢牢握着方向盘。这就是"连让 AI 开发 AI 这么危险的事,都用规格兜住了"。
L01

什么是规格驱动开发(SDD)

规格驱动开发(Spec-Driven Development):不是上来就写代码,而是先把"要做什么、怎么做、验收标准"写成正式规格文档,评审通过后再照着实现。本框架用 OpenSpec 工具做这件事,目录在 openspec/

为什么一个 Agent 框架要这么严格?因为它有一条"平台自己开发自己"的流水线(Day 15)——让 AI 去改平台代码,必须有清晰的规格作为契约,否则 AI 改飞了没人拦得住。规格就是人和 AI 之间的合同。

SDD vs 直接写代码:小改动直接写没问题;但涉及新能力、多人协作、或让 AI 代劳时,先有规格能让"做什么"和"怎么做"在动手前就对齐、可评审、可追溯。OpenSpec 把规格做成结构化文档 + 三段工作流。
L02

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
specs/<cap>/spec.md
已生效的正式能力规格(61 个)。这是契约的 ground truth——"平台现在到底有哪些能力"的唯一真相。
changes/<id>/
进行中的变更提案(13 个),每个含 4 份文件(下面)。相当于"报建审批中的申请袋"。
changes/archive/<date>-<id>/
已完工归档(173 个)。命名带日期,翻它就能按时间线看平台从 v0.1 到今天每一次扩建。

随便进一个进行中的 change 看它的 4 份文件(真实目录 openspec/changes/add-agent-effect-metrics/):

$ ls openspec/changes/add-agent-effect-metrics/
design.md  proposal.md  specs/  tasks.md
文件内容
proposal.mdWhy / What Changes / Impact——为什么做、改什么、影响哪些包
design.md设计决策(DD)
tasks.md勾选清单([x]完成 [~]进行中 [ ]待办)
specs/<cap>/spec.mdspec 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
💡 逐段读:一份合格的 proposal 长什么样 Why 先讲清"痛点是什么、为什么现在的做法不够"——这里是"业务效果无处可放,且业务方不会主动上报"。What Changes 列出具体改动,注意那句 老 agent 无字段走 None · BC——BC = Backward Compatible(向后兼容),规格里明确承诺"不破坏老 agent"。Impact 说影响哪些包。这三段就是人和 AI 之间的合同:动工前把"做什么、怎么不出错"写死。
⚖️ 设计取舍:为什么把 changes/specs/ 拆成两个目录,而不是直接改 specs? 你可能觉得"要改能力,直接改 specs/xxx/spec.md 不就行了"。但那样正在改的半成品已生效的真相就混在一起了——别人分不清哪条是"现在真有的能力"、哪条是"某人提议但还没做的"。拆开后:specs/ 永远只放"已生效",changes/ 放"审批中",等 change 完工再把它的 spec delta 合并进 specs/ 并归档(L03 的 archive)。这就是"现行房产证"和"报建申请"必须分柜存放。
数据结构:openspec/ 三块档案柜 openspec/ specs/ (61) 现行有效能力规格 = 契约 ground truth platform-api-foundation/ agentctl-cli/ … changes/ (13) 进行中提案 · 每个 4 文件: proposal.md · design.md tasks.md · specs/<cap>/ = 报建审批申请袋 changes/archive/ (173) 已完工归档 <date>-<id>/ = 办结历史卷宗 完整迭代史 change 完工 → spec delta 合并进 specs/ + 整个目录 git mv 到 archive/(L03 的 archive 段)
图注:specs 是"现在有什么",changes 是"正在改什么",archive 是"改过什么"——三柜分离,各有真相。
L03

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 出方案报审 → changes/<id>/ ② apply 照批复施工 写代码+跑测+勾 tasks ③ archive 竣工归档 specs 转正 + 移 archive/ 人 review 测试过 和 Git PR 类似,但粒度是「能力规格」而非「代码 diff」;自我开发闭环就是把这三段 Agent 化
图注:propose→apply→archive 三段流 = 报建三段(报审→施工→归档),人在前两段之间把关。
📝 单步走查:一个 change「add-echo-agent」走完三段
阶段此刻发生什么目录状态
① proposeAI 一次生成 proposal/design/tasks/spec delta 四份文件;人 review 不满意让 AI 原地改changes/add-echo-agent/ 出现
② applyAI 按 tasks.md 逐条写代码、跑测试、勾选完成项该目录 tasks 全 [x],代码进 apps/echo-agent
③ archive把 spec delta 搬进正式 specs/<cap>/,整个 change 目录移走移到 changes/archive/<date>-add-echo-agent/
change-id 用动词开头add-X-agent / modify-X / remove-X),一个 change = 一个 capability。这套流程把"改动"变成了有提案、有评审、有验收、有归档的正式过程——和 Git PR 类似,但粒度是"能力规格"而非"代码 diff"。
L04

自我开发闭环:4 个 agent 逐个走读真代码

Day 15 见过的自我开发流水线,本质就是把上面人工的三段流程"Agent 化"——用 4 个 agent 代替人跑 propose/apply/评审。它们全是真 app,就在 apps/ 下,今天挨个翻源码:

requirement-
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 --strictvalidate 节点)。_validate_router 看结果:过了去 reporter 收工;没过且修复次数 < 2repair 让 AI 看着报错自己改,改完回 write_files 重写、再 validate——形成一个最多转 2 圈的自修复环转满 2 圈还不过give_up(仍进 reporter,但标 failed_2x 让人知道"这个 AI 搞不定,你来")。

⚖️ 设计取舍:为什么 REPAIR_LIMIT = 2,不是无限重试? LLM 生成的规格不合法很常见(少个 Scenario、格式错)。让它看报错自己修,是省人力的好事。但如果无限重试,一个 AI 会不会陷进去反复改同一个错、烧一堆 token 还出不来?会。所以硬性封顶 2 次——"能自愈就自愈,2 次还不行就诚实交给人",而不是假装 AI 万能。这是"给自动化留逃生口"的典型:自动化不是把人排除,是把人从重复劳动里解放、只在真卡住时介入。
控制流:spec-author 的 validate → repair 自修复环 write_files validate--strict repair(≤2) reporter → END valid failed & <2 改完重写 2 次仍败 → failed_2x
图注:validate 不过就回炉重修,最多 2 次;转满还败就诚实标 failed_2x 交给人。自动化留了逃生口。

② 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")
⚠️ 一个真实的"文档漂移"坑(Day 01 反复强调) 平台首页 AGENT_METAmain.py:167)把 release-coordinator 描述成 "5 gate(ruff/mypy/pytest/arch/risk)",但你数一下上面 builder 里实际是 6 个 gate——多了 gate_doc这就是"文档/描述是快照,代码才是真相":描述写在先、后来加了 doc gate 没同步。读真实项目一定以 builder.py 为准。这个坑本身就是最好的一课。
四道保险合起来看:① 先有规格(propose 阶段人 review 4 份文件);② scope guardsafe_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 只干"照图施工"的重复活,"要不要盖、盖成什么样"始终是人说了算。

L05

20 天全景回顾

恭喜走到这里。把 20 天串成一张地图:

W1 心智:分层架构 → LangGraph → Supervisor → Agent 解剖
W2 可信底座:toolkit 总览 → Critic → 闸门 → 记忆 → 评测
W3 运行时:安全工具/LLM/Fake → server → CLI/MCP → 精读 sre-rca → Agent 全家福
W4 平台化:门户 → 部署 → CI → 起新 agent → OpenSpec 收官

一次请求的完整旅程,现在你能完整讲出来了:

用户提问
  → 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]
L06

贯穿全书的设计哲学

如果 20 天只记 6 句话,就记这些——它们比任何单个 API 都重要:

1. 分层与依赖单向:只能上依赖下,横切能力沉到 toolkit,业务间不互相 import。(Day 01/06)
2. 渐进抽象:第 2 次重复才抽象,不过早设计。CHANGELOG 是活教材。(Day 06)
3. 能用确定性手段就别用 LLM:规则分诊、白名单 fragments、代码层 Critic——省钱、更准、更快。(Day 07/14/15)
4. 防御纵深 + 优雅降级:多道闸门兜底,单点失败局部化不崩全局,宁可诚实说"不知道"也不误导。(Day 08)
5. 依赖注入 / 面向接口:get_llm、VectorStore Protocol、checkpointer 工厂——测试快、厂商无关、生产稳。(Day 09/11)
6. 可信靠机制不靠自觉:评测样本 + Critic 是准入门槛,CI 默认失败强制执行,scope guard 分离决策与执行。(Day 10/18)
👶 记忆口诀:6 条设计哲学一句话背下来「分层单向、二次才抽、能确定别用 LLM、纵深兜底、注入解耦、机制管人」——把这 6 个词组记住,就等于把 20 天最核心的东西装进了口袋,讲给别人听也不会漏。

AGENTS.md 的收尾金句浓缩了这一切:"保持渐进抽象、保持 fake LLM 测试、保持评测样本同库、保持 5 道闸门兜底——其余都可以谈。"

L07

那些"坑"再提醒一次

读这个真实项目,几个务必记住的现实(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。术语要按版本理解。
  • 诚实的降级注释:代码里"当前暂时降级/占位"的注释是现状而非终态,读到要留意。
这些"坑"本身就是最好的一课:真实工程从来不是完美的。学会"以代码为准、识别反模式、区分理想与现状",比记住任何 API 都更能让你读懂真实项目。
L08

继续深入的路线图

20 天建立了完整地图,想再深入可以:

1

亲手造一个真业务 agent

按 Day 19 走一遍,从 scaffold 到注册跑通。实践一次胜过读十遍。

2

精读 toolkit 的 api/ 子包

它占 toolkit 41%(~2190 行),是全平台覆盖率最高的抽象。读透 router.py + cost.py

3

跟读 CHANGELOG + ARCHITECTURE.md

从 v0.1 到 v0.6,理解每次抽象的"触发故事"——这是学"何时该抽象"的最佳材料。

4

读一个变体 agent 对照 sre-rca

如 alert-triage(仅 L1)、security-audit(5 专家)、supervisor-router(纯路由),体会同一套框架的裁剪弹性。

5

翻 openspec/changes/archive/

173 个归档 change 就是平台的完整演进史(按日期排序),看真实需求怎么变成规格再变成代码。

L09

结业 🎉

你已经完整走过了 gov-agents-platform 的 20 天。现在的你应该能——

  • ✅ 向别人讲清"这是个什么框架、分几层、每层管什么";
  • ✅ 打开任意一个 agent,从 builder.py 入手快速读懂它的流程;
  • ✅ 说清 Critic / 闸门 / 记忆 / 评测 / 成本治理 各自解决什么、怎么实现;
  • ✅ 跟一次请求走完从 server 到图到 envelope 的全程;
  • ✅ 自己 scaffold、开发、注册、部署一个新 agent;
  • ✅ 理解贯穿全书的 6 条设计哲学,并能迁移到你自己的项目。

这套"分层 + 可信工程化 + 渐进抽象"的思路,远不止适用于这一个框架——它是做任何严肃 AI Agent 系统的通用方法论。带走它。

"可信不是某一个功能,而是贯穿分层、闸门、评测、注入、scope guard 的一整套纪律。它让 AI Agent 从'能跑的 demo'变成'敢上生产的系统'。"
← Day 19 起新 Agent 🎉 返回 20 天总目录