AI飞行客

掠过技术的云层,落地在工程的原野

OpenSpec + Codex 多 Agent 工程化:从规范定义到自动验收的闭环实践

“当 AI 能写代码,管理 AI 的工程方法就成了核心竞争力。规范驱动不是束缚,而是让十个 Agent 朝同一个方向开枪的指挥系统。”

过去一年,我见过的 AI 编码团队大概分成三类:

第一类,vibe coding 派。 打开 Cursor,把需求往 chat 里一扔,AI 就开始写。写完了跑一下,红了就丢回去让 AI 修,绿了合并。三个月后代码库变成了一座谁也不认识的迷宫。

第二类,AI 辅助派。 把 Copilot 当高级自动补全,AI 写代码,人做 review。比第一类好,但 review 越来越敷衍——谁有耐心逐行审查 AI 吐出来的 500 行代码?

第三类,规范驱动派。 先写 spec,再让 AI 按 spec 开发,最后用 spec 自动验收。开发过程中 spec 是唯一的真值来源,代码只是 spec 的实现物。

这篇文章,我想聊聊第三类。不是理论,是我们团队跑了大半年的实战经验。


问题:为什么单 Agent 编码不可持续?

先泼一盆冷水:让一个大模型同时扮演 PM、架构师、开发、QA,等于让一个人同时当厨师、服务员、收银员和清洁工。 它能做,但每一角色都在为其他角色擦屁股。

单 Agent 模式的死穴:

  1. 上下文膨胀:一个 Agent 既要理解业务需求,又要处理实现细节,还要考虑测试覆盖。上下文窗口再大,也装不下三层抽象。
  2. 标准漂移:第一轮对话里的设计原则,到第十轮早就被遗忘。AI 不会主动说”等等,这违反了我们在第三步约定的一致性原则”。
  3. 验收真空:代码写完了,谁来验收?再让同一个 Agent 验收自己的产出?那是自检,不是验收。
  4. 知识黑洞:对话历史散落在几十条消息里,项目交接时新成员面对的是一片废墟。

多 Agent 不是炫技,是责任分离。PM Agent 定方向,Dev Agent 实现,QA Agent 验收——每个 Agent 有明确的输入、输出和成功标准。


核心架构:OpenSpec 驱动的四阶段闭环

整个工作流分为四个阶段:

┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   规范定义   │ ──▶ │   多 Agent   │ ──▶ │   自动验收   │ ──▶ │   Spec 归档  │
│   (Spec)     │     │   协作开发   │     │   (Verify)   │     │   (Archive)  │
└──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
       ▲                                                              │
       │                                                              │
       └──────────────────── 可追溯、可复用 ──────────────────────────┘

关键原则:Spec 是贯穿始终的唯一真值(Single Source of Truth)。它定义了”做什么”(功能)和”怎么算对”(验收标准)。代码只是 spec 的物化实现。


Phase 1:规范定义 — OpenSpec 怎么写

我们内部用的不是一份自由文本的需求文档,而是一个结构化的 OpenSpec 文件。它本质上是一份机器可读、人类可审的契约。

OpenSpec 文件结构

# openspec.yaml
spec_version: "1.0"
feature_id: "user-auth-v2"
title: "用户认证系统重构"
owner: "platform-team"
priority: P0

# ── 业务上下文 ──
context:
  background: "现有认证系统存在 session 不一致问题..."
  stakeholders: ["移动端", "Web端", "管理后台"]
  constraints:
    - "必须兼容现有 JWT token 格式"
    - "不能引入新的外部依赖"
    - "P95 响应时间 < 100ms"

# ── 功能规格 ──
requirements:
  - id: R1
    title: "统一登录入口"
    description: |
      所有客户端通过 /api/v2/auth/login 进行认证,
      支持 email + password 和 OAuth 2.0 两种模式。
    acceptance_criteria:
      - "email/password 登录返回 access_token 和 refresh_token"
      - "OAuth 登录支持 Google 和 GitHub"
      - "错误的密码返回 401,格式错误的请求返回 400"
      - "access_token 有效期 15 分钟,refresh_token 有效期 7 天"
    test_cases:
      - id: T1
        scenario: "有效凭证登录"
        given: "用户存在且密码正确"
        when: "POST /api/v2/auth/login"
        then: "返回 200,包含 access_token"
      - id: T2
        scenario: "错误密码登录"
        given: "用户存在但密码错误"
        when: "POST /api/v2/auth/login"
        then: "返回 401,错误信息不包含密码细节"

  - id: R2
    title: "Token 刷新机制"
    description: "通过 refresh_token 获取新的 access_token"
    acceptance_criteria:
      - "有效的 refresh_token 返回新的 token pair"
      - "过期的 refresh_token 返回 403"
      - "刷新后旧的 refresh_token 立即失效(token rotation)"
    test_cases:
      - id: T3
        scenario: "正常刷新"
        given: "refresh_token 未过期且未被使用"
        when: "POST /api/v2/auth/refresh"
        then: "返回 200 和新 token pair"

# ── 架构约束 ──
architecture:
  patterns:
    - "Repository Pattern"
    - "Dependency Injection"
  forbidden:
    - "不允许在 handler 里直接操作数据库"
    - "不允许在业务代码里使用全局变量"
  dependencies:
    - name: "bcrypt"
      version: "^5.1"
      reason: "密码哈希"
    - name: "jsonwebtoken"
      version: "^9.0"
      reason: "JWT 实现"

# ── 接口契约 ──
api_contract:
  - path: "/api/v2/auth/login"
    method: POST
    request:
      content_type: "application/json"
      schema:
        email: { type: string, format: email, required: true }
        password: { type: string, min_length: 8, required: true }
    response:
      200:
        content_type: "application/json"
        schema:
          access_token: { type: string }
          refresh_token: { type: string }
          expires_in: { type: integer }
      400:
        schema:
          error: { type: string }
          code: { type: string }
      401:
        schema:
          error: { type: string }

# ── 验收定义 ──
verification:
  auto_tests:
    coverage_target: "90%"
    required_paths: ["/api/v2/auth/*"]
  manual_checklist:
    - "登录后 15 分钟 token 自动过期"
    - "并发刷新时不会出现 race condition"
  performance:
    - metric: "p95_latency"
      target: "< 100ms"
      test: "k6 load test with 1000 RPS"

为什么用 YAML 而不是自然语言?

三个原因:

  1. 结构化让 AI 理解更精准:自然语言有歧义,YAML 的键值对是强约束。你给 Dev Agent 喂 YAML,它不会”误解”你的验收标准。
  2. 可验证:测试框架可以直接解析 acceptance_criteria 和 test_cases,自动生成测试代码。
  3. 版本控制友好:diff 清晰,code review 时一眼看出改了哪些验收标准。

谁来写 Spec?

PM Agent + 人类 PM 共同完成。

流程是:
1. 人类 PM 写初稿(业务背景、核心需求)
2. PM Agent 补全技术细节(接口 schema、边界条件、异常场景)
3. 人类 PM 审阅、修正
4. 技术负责人签字(确认架构约束和依赖选择)

平均一个中等复杂度的 feature,spec 写 30-40 分钟。但省去了后续 3 小时的”AI 你到底要我做什么”的拉锯战。


Phase 2:多 Agent 协作开发

Spec 确定后,进入开发阶段。这里的关键是每个 Agent 只读自己的输入、只写自己的输出,通过 Spec 文件和代码仓库进行状态同步。

Agent 角色定义

Agent 职责 输入 输出 成功标准
PM Agent 需求澄清、规格细化、验收标准补充 用户故事、业务背景 OpenSpec YAML spec 被技术负责人 approve
Dev Agent 代码实现 OpenSpec + 现有代码库 代码 PR + 实现笔记 所有 acceptance criteria 被实现
QA Agent 测试生成、验收执行 OpenSpec + 代码 PR 测试报告 + 验收结果 所有 test cases 通过
Guard Agent 规范检查、架构守护 代码 PR + OpenSpec 审查报告 无 forbidden pattern 违反

协作流程

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  人类PM  │───▶│ PM Agent │───▶│ Dev Agent│───▶│ QA Agent │
│ 写初稿   │    │ 补全规格  │    │ 实现代码  │    │ 生成+执行 │
└──────────┘    └──────────┘    └──────────┘    │  测试     │
       ▲                                          └────┬─────┘
       │                                               │
       └────────────── 循环直到验收通过 ────────────────┘

Dev Agent 的工作模式

Dev Agent 不是”把 spec 翻译成代码”那么简单。它需要:

  1. 阅读现有代码库:通过 RAG(检索增强生成)索引现有代码,确保新代码和旧代码风格一致。
  2. 渐进式实现:不是一次性生成 1000 行代码,而是按 spec 的 requirement 逐个实现,每个 requirement 完成后提交一次 commit。
  3. 自我检查:每完成一个 requirement,Dev Agent 运行一次 lint + type check + 单元测试,红了就修,红了就修,直到绿了才继续下一个。
  4. 写实现笔记:在 PR description 里解释每个关键决策——为什么选择这个设计、哪里和 spec 有出入(如果有的话)、需要 review 时特别注意什么。
Dev Agent 的 prompt 核心逻辑:

你是一位资深后端工程师,擅长 Go 和微服务架构。
你的任务是根据以下 OpenSpec 实现代码。

规则:
1. 每次只实现一个 requirement(R1 → R2 → R3...)
2. 实现完成后必须运行 `make test` 和 `make lint`
3. 如果测试失败,修复后再继续
4. 禁止修改 spec 中定义的接口契约
5. 如果必须偏离 spec,在实现笔记中说明原因

输入:
- OpenSpec: [openspec.yaml 内容]
- 现有代码: [通过 RAG 检索的相关代码片段]
- Requirement 当前目标: R2

QA Agent 的工作模式

QA Agent 不是简单跑测试。它的核心能力是从 spec 生成测试

流程:
1. 解析 OpenSpec 中的 test_cases
2. 生成对应的自动化测试代码(单元测试、集成测试、e2e 测试)
3. 运行测试,生成报告
4. 如果测试失败,分析是代码 bug 还是测试本身的问题
5. 将结果写回 PR 评论

QA Agent 还会做边界扫描:基于 spec 中的 acceptance_criteria,自动补充一些边界测试——比如输入为空字符串、超长字符串、特殊字符、并发请求等——这些往往是人类写 spec 时遗漏的。

Guard Agent:架构守护者

这是最容易被忽略但最关键的 Agent。

Guard Agent 的职责:
– 检查代码是否违反了 spec 中的 forbidden 规则
– 检查新引入的依赖是否在 dependencies 白名单内
– 检查代码复杂度(圈复杂度、函数长度)
– 检查测试覆盖率是否达到 verification.auto_tests.coverage_target

Guard Agent 是一票否决权。它没过,PR 不能合并。


Phase 3:自动验收 — Spec 即测试

这是整个闭环的灵魂所在:验收标准不是写在文档里束之高阁的,而是被自动执行的。

三层验收金字塔

        ┌─────────────┐
        │   E2E 测试   │  ← 用户视角,验证完整流程
        │  (Playwright)│
        ├─────────────┤
        │  集成测试     │  ← API 视角,验证接口契约
        │   (API Test) │
        ├─────────────┤
        │  单元测试     │  ← 开发者视角,验证逻辑分支
        │  (Unit Test) │
        └─────────────┘

从 Spec 自动生成测试

我们用了一个内部工具(可以叫 SpecTest)来解析 OpenSpec:

# spec_to_test.py 核心逻辑

def generate_tests(spec: OpenSpec) -> TestSuite:
    suite = TestSuite()

    for req in spec.requirements:
        for tc in req.test_cases:
            if tc.is_api_test():
                suite.add(generate_api_test(tc, spec.api_contract))
            elif tc.is_unit_test():
                suite.add(generate_unit_test(tc, req.implementation_hint))

    # 边界扫描:基于 acceptance_criteria 自动补充
    for ac in spec.all_acceptance_criteria():
        suite.add(generate_boundary_tests(ac))

    return suite

接口契约的强制验证

OpenSpec 中定义的 api_contract 不是装饰。我们把它和 OpenAPI/Swagger 打通,做两件事:

  1. 静态验证:用 OpenAPI schema 校验代码中的 handler 定义是否和 spec 一致。如果 spec 说 /api/v2/auth/login 返回的 JSON 必须包含 access_token,但代码返回的是 token——编译时就报错
  2. 动态验证:集成测试运行时,所有 API 响应都经过 schema 校验。响应结构对不上?测试自动失败。

性能验收

spec 中的 verification.performance 不是建议,是硬指标。CI 流水线里跑 k6 压测,如果 P95 延迟超过 100ms,PR 被 block。

// k6 性能测试脚本(由 QA Agent 从 spec 生成)
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  thresholds: {
    http_req_duration: ['p(95)<100'], // 来自 spec
  },
  stages: [
    { duration: '1m', target: 1000 },
  ],
};

export default function () {
  const res = http.post('http://localhost:8080/api/v2/auth/login', {
    email: 'test@example.com',
    password: 'correct-password',
  });

  check(res, {
    'status is 200': (r) => r.status === 200,
    'has access_token': (r) => JSON.parse(r.body).access_token !== undefined,
  });
}

Phase 4:Spec 归档 — 从一次性文档到组织资产

很多团队的 spec 写完就扔。我们的做法是:spec 是活的,而且越用越值钱。

Spec 的版本化管理

每个 OpenSpec 文件和它对应的代码一起存在 Git 里:

features/
  user-auth-v2/
    openspec.yaml       # 规范
    openspec.lock       # 规范签名(防篡改)
    implementation/     # 代码
    tests/              # 测试
    reports/
      2025-01-15-e2e.html
      2025-01-15-perf.json

当 feature 验收通过、PR 合并后,整个目录被打上 archive/v1.0.0 标签。后续任何改动都必须更新 spec 并走同样的四阶段流程。

Spec 知识库

所有归档的 spec 被索引到一个向量数据库中。当新的需求来临时,PM Agent 先检索历史 spec:

“用户想做一个文件上传功能”

PM Agent 检索:找到之前 file-upload-v1 的 spec,里面有文件大小限制、MIME 类型白名单、病毒扫描接入点…

PM Agent:”建议复用 file-upload-v1 的规范,只需修改存储后端为 S3″

复用不是复制粘贴,是基于历史的智能推荐。 新 spec 的初稿 80% 由历史 spec 组成,PM 只需要调整差异部分。

Spec 健康度仪表盘

我们有一个内部看板,追踪每个 spec 的:

  • 验收通过率:spec 的 test cases 在 CI 中持续通过的比例
  • 偏差率:代码实现偏离 spec 的次数(由 Guard Agent 记录)
  • 复用次数:该 spec 被其他 feature 引用的次数
  • 技术债务指数:基于代码复杂度和 spec 修改频率计算

这些数据反过来指导 PM Agent 写更好的 spec——哪些验收标准经常导致测试失败?哪些架构约束经常被违反?spec 本身也在进化。


实战:一个 Feature 的完整生命周期

走一遍真实流程。

场景:给现有订单系统加”批量退款”功能

Day 1, 上午:人类 PM 写初稿

# 初稿
feature_id: "batch-refund-v1"
title: "订单批量退款"
context:
  background: "客服需要一次处理多个订单的退款..."
requirements:
  - id: R1
    title: "批量退款接口"
    description: "接收订单 ID 列表,执行批量退款"

Day 1, 下午:PM Agent 补全

PM Agent 检索到历史 single-refund-v1 的 spec,自动补全:
– 接口契约(请求/响应 schema)
– 幂等性要求(同一批退款不能重复执行)
– 部分退款失败的回滚策略
– 并发控制(防止同一订单被同时退款)
– 审计日志要求

人类 PM 审阅,删掉”并发控制”——因为订单系统已经有分布式锁。加了一条”最大批量 50 条”。

Day 2:Dev Agent 开发

Dev Agent 按 R1 → R2 → R3 逐个实现:
1. R1 接口骨架:写 handler + router
2. R2 退款逻辑:调用支付网关 + 更新订单状态
3. R3 幂等性:基于 refund_batch_id 实现幂等

每完成一个 requirement,运行测试。R2 第一次跑的时候红了——支付网关返回的 error format 变了。Dev Agent 自动调整解析逻辑,重跑,绿了,继续。

PR 提交,附实现笔记:

“支付网关的错误格式从 {'error': 'msg'} 变成了 {'errors': [{'message': 'msg'}]}
已兼容新旧两种格式,建议下季度 spec 里要求统一错误格式。”

Day 3:QA Agent 验收

QA Agent 解析 spec 生成测试:
– 正常批量退款(3 个订单)✅
– 部分失败(第 2 个订单支付网关拒绝)✅
– 幂等性测试(同一 batch_id 执行两次,第二次返回上次结果)✅
– 边界测试(51 个订单 → 返回 400)✅
– 并发测试(两个请求同时退同一订单)✅
– 性能测试(50 个订单 P95 < 500ms)✅

全部通过。Guard Agent 扫描:没有 forbidden pattern,测试覆盖率 94%(> 90% 目标)。

Day 3 下午:合并 + 归档

PR 合并,features/batch-refund-v1 目录被打上 archive/v1.0.0。spec 进入知识库,下次有类似需求时会优先被推荐。


工具链建议

我们内部的组合,你可以参考:

环节 工具 替代方案
Spec 编写/解析 YAML + 自定义 schema Markdown + frontmatter, TOML
Spec 版本管理 Git + 目录约定 Notion API, Confluence
Agent 编排 自建编排器(基于 LangChain) CrewAI, AutoGen, Dify
代码生成 Claude 3.5 Sonnet / GPT-4 Cursor Composer, GitHub Copilot Workspace
接口契约验证 OpenAPI + Swagger-codegen Protocol Buffers, JSON Schema
测试生成 自建 SpecTest + Pytest Playwright codegen, Jest snapshot
性能测试 k6 Artillery, Locust
知识库 自建向量库(pgvector) Pinecone, Weaviate
CI/CD GitHub Actions + ArgoCD GitLab CI, Jenkins

不要犯这些错

❌ 误区一:Spec 写得太细

有些团队把 spec 写成伪代码,每一行都规定死。Dev Agent 变成了打字机,没有发挥空间。好的 spec 描述”做什么”和”怎么算对”,不描述”怎么做”。

❌ 误区二:让 AI 自己写 Spec

PM Agent 的作用是补全和结构化,不是从零写 spec。业务背景、核心价值、优先级判断——这些需要人类。让 AI 独立写 spec,等于让 AI 替你做产品决策。

❌ 误区三:忽视 Guard Agent

团队最容易砍掉的环节。”测试都过了,架构检查一下意思意思就行。” 然后三个月后代码库里出现 12 个全局变量和 3 个 circular import。

❌ 误区四:Spec 归档了就不管了

归档不是终点。要持续追踪 spec 的健康度,定期 review 哪些 spec 的验收标准已经过时、哪些被频繁违反。spec 是活的文档。


总结:为什么这个模式能赢

AI 编码的竞争不是”谁的模型更强”,而是“谁的工程方法能让 AI 持续输出高质量代码”

OpenSpec + 多 Agent 闭环的核心价值:

  1. 可预测:spec 定义了”对”的标准,Agent 不会天马行空地发挥
  2. 可验收:spec 即测试,没有”看起来对了”的灰色地带
  3. 可追踪:每个代码行都能追溯到 spec 的哪个 requirement
  4. 可复用:spec 知识库越用越聪明,新 feature 的启动成本越来越低
  5. 可扩展:加人不用加沟通成本,spec 就是新成员的 onboarding 手册

最后说一句:这套方法不是为了”管理 AI”而存在的。即使没有 AI,规范驱动开发也是软件工程的最佳实践。AI 只是把这个模式的杠杆效应放大了 10 倍。

规范驱动不是束缚,是让创造力在正确轨道上行驶的护栏。


Kate / CTO / 相信规范比信仰更重要

2026年5月

发表回复

Your email address will not be published. Required fields are marked *.

*
*