Codex + Spec-Kit 开发 LeRobot Agent 的实践与反思
“现在问题解决了,但请你重新 Review 一下今天的几轮修改,看看是不是补丁叠补丁式的修改,如果是的话,重构成最优解”,这是我在这个项目里对 AI 说得最多的一句话。 你也可以说 “review the code, think from greenfield” 或者 “/samplify”,但不管怎么说,这句话的核心意思是:当 AI 在一个已经快速迭代过的代码库里继续修改时,它很容易陷入“补丁叠补丁”的陷阱,而不是停下来重新思考最优解。
过去一周,我做了个基于 LeRobot 的 SO-101 机械臂 PoC。最初只是一个能跑起来的遥操作脚本,后来长成具备配置驱动、基础测试、服务接口和调试界面的 MVP。这个项目并不是我一个人独立完成的。我主要搭起了大约 60% 的骨架和核心功能,包括命令行入口、配置体系、API 分层、Web 服务、调试面板(dashboard),以及大部分基础测试。到了后期,团队里的同事也逐步参与进来,补上了模型推理、训练以及一些更贴近业务场景的能力。
现在的结构:
- 命令行入口:
teleoperate、record、replay、serve - Python API 分层:
src/api - FastAPI 服务:
src/webapi - React 调试面板:
dashboard/ - 单元测试:
tests/unit - 配置文件:
config/agent.toml

一句话概括:用 Spec-Kit 管"要做什么",用 Codex 管"怎么更快做出来"
但这不是一个"AI 加 Spec-Kit 就一路顺风"的故事。中间有很多反复、修正和取舍。Spec-Kit 帮了我不少,也带来过混乱;Codex 让我省了很多重复劳动,也把我前端、协作和问题表达上的短板照得很清楚。
这篇文章讲的就是这些真实经验。
一、为什么一开始没有直接写代码
表面上看这是个"硬件 + Python"工具,但做起来才发现是一整条链路的问题:
- 读取 SO-101 的 follower、leader、camera 配置
- 跑通
teleoperate(遥操作) - 支持
record(数据采集)和replay(回放) - 把能力暴露成 HTTP / WebSocket 服务,方便外部调用
- 做调试面板,把串口、相机、控制状态、任务状态可视化
- 后续接入推理和 LLM,而不是推翻重来
直接写代码很容易在第 3 步以后失控。配置解析、硬件连接、任务生命周期、Web 接口、前端调试、测试替身——这些边界一旦不清,后面每加一个功能都牵一发而动全身。
所以我一开始没急着写功能(实际情况是拿到机械臂那一刻我不知道如何开始),而是先走 Spec-Kit 的标准流程,把问题拆成几个可以连续交付的阶段。回头看这个决定是对的,它很早就把项目骨架定下来了。
二、怎么用 Spec-Kit 定义这个项目
这不是我第一次用 Spec-Kit。我的理解是把它当成一个"收敛模糊需求"的约束器,而不是一套文档模板。
最开始不是先写实现方案,而是先写清楚系统必须具备什么能力:
- 用统一配置描述 follower、leader、cameras
- 支持
teleoperate、record、replay三个核心动作 - 代码要有 lint/format 校验,单元测试,git commit hook
- 可以在不碰硬件的前提下做单元测试
- 需要有 Web 层,方便外部系统和前端接入
然后再写约束:
- 底层依赖 LeRobot
- Python 版本固定在 3.12
- 用
uv管理环境和依赖 - 配置用 TOML
- 测试用 pytest
- 尽量走真实的 LeRobot 路径,而不是再造一层假的 runtime
具体执行上,把不同功能依次推进到 specify -> plan -> tasks 这条链路。先形成规格,再形成计划,最后拆成任务。最终阶段大致是:
- 第一阶段:命令行入口跑通
teleoperate、record、replay - 第二阶段:把配置、校验、控制会话抽成稳定的 API 层
- 第三阶段:暴露 FastAPI 接口,补齐状态查询和流式能力
- 第四阶段:做调试面板,验证整条链路是否真的可操作
- 第五阶段:接入 LLM / 推理,让这个 PoC 具备更明确的 agent 雏形
这套做法最大的价值不是"文档更完整",而是每轮功能落地之后,项目都处在一个能跑、能验的最小闭环状态。
三、怎么用 Spec-Kit 的标准流程驱动 Codex
前面那套拆分思路落地时,我不是手工写一堆文档再扔给 Codex,而是沿着 github/spec-kit 的官方工作流推进。
Spec-Kit 的核心流程:
specify init初始化项目结构/speckit.specify把功能想法变成结构化规格/speckit.plan从规格生成实现计划,以及 research、data model、contracts、quickstart 等设计产物/speckit.tasks把计划拆成可执行的任务列表/speckit.implement或让 Codex 基于这些产物执行实现
GitHub 在 spec-driven.md 里写得很明确:/speckit.specify 负责"做什么",/speckit.plan 负责"怎么做",/speckit.tasks 负责"具体任务",/speckit.implement 负责"执行"。
我在项目里基本遵循了这个顺序,但中间也做了不少调整。
关于 /speckit.specify
这个命令用来把模糊想法变成结构化规格。我在项目早期用得最多,特别是定义配置模型、CLI 接口、API 契约的时候。
实际用下来发现,specify 适合处理"边界清晰、输入输出明确"的功能。比如定义配置结构、CLI 参数、API 路由——这些场景的 specify 产物可以直接进入 plan 阶段。
但对于"需要探索"的问题,specify 容易过度承诺。我早期在前端调试面板的 specify 阶段花了不少时间定义组件结构和状态流转,但后来实际实现时发现,很多假设在真实数据流面前并不成立。specify 阶段产出的规格成了需要修正的约束,而不是可以直接执行的蓝图。
关于 /speckit.plan
plan 阶段从 specify 产物生成实现计划。我主要用它做几件事:
- 把规格拆成可执行的步骤
- 识别依赖关系和阻塞点
- 生成 data model、contracts、quickstart 等设计产物
plan 的价值在于它强迫你在动手之前想清楚"先做哪块,后做哪块"。我在项目里用 plan 阶段明确了几个关键顺序:必须先稳定配置层,才能做 API 层;必须先有 API 层的 mock,才能写单元测试;必须先有 webapi 的基础路由,才能做调试面板。
这些顺序看起来理所当然,但在实际开发中很容易因为"这块看起来简单,先做了吧"而打乱。plan 阶段的作用就是提前暴露这些依赖。
但 plan 也有局限。它假设 specify 阶段的规格是稳定的,而实际上规格经常需要调整。我在项目中期遇到过几次 plan 产物和实际实现脱节的情况,原因是指定阶段对 LeRobot 的某些行为假设不准确,导致 plan 阶段拆出来的任务无法直接执行。
关于 /speckit.tasks
tasks 阶段把 plan 产物拆成具体任务。这是我最常用的阶段,因为它直接产生可以丢给 Codex 执行的任务列表。
我在项目里形成的习惯是:每个 task 只聚焦一个具体目标;任务描述里包含前置条件和验收标准;对于复杂任务,先让 Codex 生成伪代码或接口草案,确认方向后再完整实现。
tasks 阶段的一个教训是:不要把"探索性工作"和"实现性工作"混在一起。我早期有几个任务描述是"调研 X 并实现 Y",结果 Codex 在调研部分花了大量时间,实现部分却很粗糙。后来我把这类任务拆成两个:先调研,再实现。
关于 /speckit.implement
implement 阶段是实际编码。我通常不会直接用 /speckit.implement 命令,而是把 tasks 产物丢给 Codex,让它基于上下文执行。
这里的关键是:Codex 的执行质量严重依赖于前面的 specify 和 plan 质量。如果 specify 阶段的规格有模糊之处,Codex 会按自己的理解补全,而这些补全不一定符合预期。如果 plan 阶段的步骤有依赖错误,Codex 会在执行时卡住或产生循环依赖。
我在项目里养成的一个习惯是:在让 Codex 执行之前,先人工过一遍 tasks 列表,确认每个任务的输入输出是清晰的。这个步骤花不了多少时间,但能避免很多返工。
四、Spec-Kit 带来过哪些混乱
Spec-Kit 不是银弹。我在项目里遇到过几次明显的混乱:
1. 过时的计划、任务、设计过程没有及时清理
Spec-Kit 的 workflow 会产生大量中间产物:specifications、plans、tasks、design docs。这些产物在早期很有用,但一旦实现偏离了原始规格,它们就变成了噪音。
我在项目中期发现,Codex 有时会引用过时的设计文档来做决策。比如某个 API 接口在 specify 阶段定义了字段 X,但实际实现时去掉了 X,只保留了 Y。但 Codex 在后续修改时,偶尔会基于旧的 specify 文档建议"补充 X 字段的校验逻辑"。
问题根源是:我没有及时清理已经过时的 spec 产物。Spec-Kit 的 workflow 假设规格是稳定的,但 PoC 项目经常需要快速调整。当调整发生时,旧的 spec 产物如果没有被清理,就会成为上下文的污染源。
2. 不是所有问题都适合走 Spec-Kit 流水线
Spec-Kit 适合处理"边界清晰、可以结构化描述"的问题。但对于"需要试错"或"依赖直觉"的问题,Spec-Kit 的 overhead 不值得。
我在前端调试面板的开发上体会很深。调试面板的 UI 布局、交互流程、状态展示方式——这些很难在 specify 阶段就定义清楚,因为它们依赖"用起来顺不顺手"这种主观感受。我早期花了太多时间在前端的 specify 和 plan 上,结果很多规格在真实实现后都被推翻了。
后来我的调整是:前端和交互问题,先快速搭一个能跑的版本,用起来了再迭代。Spec-Kit 的 workflow 留给后端和 API 层,前端用更轻量的方式推进。
3. 多人协作时的 spec 冲突
项目后期变成团队协作后,Spec-Kit 的产物管理变得更复杂。不同人可能对同一个模块有不同的 specify 理解,而这些理解如果没有及时对齐,就会产生冲突。
我们遇到的一个典型情况是:A 同事基于某个 specify 文档实现了功能 X,B 同事基于另一个版本的 specify 文档实现了功能 Y,两个功能在集成时发现接口不兼容。
解决方式不是禁止并行开发,而是明确"哪个 specify 是当前主分支的权威版本"。我们在项目后期约定:spec 产物也走 git 管理,任何 specify 的更新都要提 PR,确保所有人看到的是同一个版本。
五、Codex 真正替我做了什么
Codex 真正提高效率的地方主要有四个:
1. 把 Spec-Kit 流程产出的规格和任务快速翻译成第一版工程骨架
从配置模型、请求对象、命令行参数、API 路由到测试样例,Codex 能基于已经成型的 Spec-Kit 产物、计划和任务,很快给出一版结构正确的初稿。这样我不用把时间花在重复劳动上,能把精力放在真正的设计决策上。
2. 适合做结构化重构
把逻辑从命令行入口挪到 api 层,把通用状态收敛到会话对象里,把 Web 层改成统一的 routes/schemas/state 组织方式——这种工作如果纯手工做,非常琐碎;但只要 Spec-Kit 流程已经把边界和任务拆清楚,Codex 执行起来就很快。
3. 适合补测试和文档
这个项目里的 README、docs/API.md 以及一系列单测,本质上都属于"高价值但重复度高"的工作。这类任务很适合让 Codex 跟着已有实现补全。
4. 能持续保持上下文
项目从 teleoperate 扩展到 record / replay,再扩展到 webapi 和调试面板,如果每次都换一个完全没有上下文的助手,沟通成本会很高。Codex 的优势在于它能沿着既有代码结构继续往下做,而不是每次都从头理解一遍项目。
六、Codex 照出了我哪些短板
Codex 不是万能的。它很强,但会把我的短板放大。
1. 前端和交互是我的盲区
我在后端和系统架构上经验多一些,但前端和交互设计是明显的短板。Codex 能帮我写出"功能正确"的前端代码,但写不出"体验好"的交互。
调试面板的早期版本就是个例子。Codex 按我的描述实现了状态展示、按钮控制、日志输出——功能都对,但用起来很别扭。布局拥挤、信息层级不清、操作反馈不明确。这些问题不是 Codex 的错,是我自己没有想清楚"用户真正需要看到什么"。
我后来调整的方式是:前端任务不再直接丢给 Codex,而是先自己画个草图,明确每一屏的核心信息和操作路径,再把草图描述给 Codex 实现。这个步骤省不了,因为 Codex 只能执行"描述清楚"的需求,不能补全"描述模糊"的意图。
2. 问题定义比解决方案更重要
Codex 会把你的问题定义当成真理。如果你问的是"怎么实现 X",它会认真给你实现 X 的方案;但如果 X 本身不是最优解,Codex 不会提醒你。
我在项目早期遇到过典型的 XY problem。我想要一个"能实时显示机械臂状态的调试面板",于是让 Codex 实现了一个 WebSocket 实时推送状态的前端。实现完后发现,真正的需求不是"实时",而是"在关键操作节点能看到状态快照"。实时推送带来了不必要的复杂度和性能开销。
问题出在我一开始的问题定义上。我描述的是"我想要的方案",而不是"我真正想解决的问题"。Codex 忠实地执行了方案,但方案本身错了。
后来我给自己加了一条规则,特别是在前端和交互问题上:先描述真正目标,再描述我以为可行的实现方式,明确告诉 Codex——如果实现方式不对,优先纠正我的问题定义。
这其实是在主动对抗 XY problem。对 AI 来说,最有价值的不是我先给出一个貌似具体的方案,而是先把真正目标说清楚。
3. 多人都用 AI 之后,协作冲突比以前更容易放大
我也在尝试通过 Harness Engineering 解决这个问题,以后有机会分享这部分内容
项目后期变成团队协作。以前主要是"我和同事协作",现在很多时候变成了"我和我的 AI、同事和他的 AI,再加上我们彼此协作"。
表面上看大家都更快了,但实际冲突反而更容易变大。
以前两个人写代码,冲突通常发生在实现细节层;现在两个人各自带着 AI 写代码,冲突经常在更早的阶段就出现了,因为 AI 会把每个人当前上下文里的默认假设继续往前推。
典型情况:你让 AI 按会话模型继续扩展,同事让 AI 按推理任务模型继续扩展,两边都各自"看起来合理",合到一起时才发现状态流、接口语义、模块职责已经互相侵入了。
我们后来摸索出的一个更有效的协作方式是:先按模块严格确认边界,再各自实现,最后统一重构。
“确认边界"不是口头分工,而是要明确到足够可执行:谁负责 api 的哪一层,谁负责 webapi 的哪些路由和状态,谁负责模型训练和推理链路,哪些对象是稳定接口不能随意变更,哪些地方允许先局部实现最后再统一整理。
这样做有两个直接的好处:每个人都可以在自己的边界内放心使用 AI 提速,系统级冲突被推迟到可控的整合和重构阶段。
在 AI 时代里,模块边界比以前更重要。以前边界不清主要伤的是开发效率,现在边界不清很容易让多个人各自带着 AI 把系统一起推偏。
七、如果让我再从头做一次
回头看,这个 PoC 能比较顺地从一个 CLI 工具长成现在这个带服务层、前端层、测试和文档的 MVP,靠的不是我一开始就把所有设计都想对了,而是采用了一种可以持续修正的工作方式:
- 先用 Spec-Kit 的标准流程把功能收敛成 Spec-Kit 产物、计划和任务
- 用 Codex 快速生成初版实现
- 用测试和真实联调不断收紧接口
- 再把稳定下来的能力继续往下一层扩展
- 在多人协作阶段先锁模块边界,再各自借助 AI 实现,最后做统一重构
但如果让我重新来一次,我会比当时更明确地做三件事:
- 更早清理已经过时的计划、任务和设计过程,避免上下文污染
- 更早区分"架构问题"和"微调问题”,不要让所有任务都走 Spec-Kit 流水线
- 在前端和交互问题上,先讲清楚真正目标,再讲我以为的方案,降低 XY problem 的概率
八、总结
- Spec-Kit 适合搭骨架,不适合覆盖所有微调
- Codex 很强,但会被错误上下文和错误问题定义带偏
- 多人都用 AI 之后,模块边界比以前更重要
对这个项目来说,AI 不是用来替代工程方法的,它只会把原有的方法放大。方法对了,推进会很快;方法错了,跑偏也会很快。
这也是我现在更相信的一种开发范式:不是让模型代替工程方法,而是让模型在工程方法之内做执行;同时在必要的时候,主动清理上下文、修正问题定义、重新划清协作边界。
参考链接
- LeRobot: https://huggingface.co/docs/lerobot
- SO-101: https://huggingface.co/docs/lerobot/so101
- Codex: https://openai.com/codex/
- Spec-kit-repo: https://github.com/github/spec-kit
- Spec-kit-readme: https://github.com/github/spec-kit/blob/main/README.md
- Spec-kit-spec-driven: https://github.com/github/spec-kit/blob/main/spec-driven.md
- XY-Problem: https://en.wikipedia.org/wiki/XY_problem
