Skip to content
页面导航
精简

一.让仓库成为唯一的事实来源

不在仓库里的知识对 agent 来说等于不存在。把关键决策信息放进仓库是最基本的 harness 投资——画好地图,才不会迷路。

1.知识靠近代码:在代码仓库各个文件夹里添加描述文档
2.标准化入口文件:

  • (1)根目录创建 AGENTS.md
  • (2)每个微服务目录下添加 ARCHITECTURE.md
  • (3)创建集中的 CONSTRAINTS.md,用"禁止/必须"的明确语言记录硬约束
  • (4)每个服务目录添加 PROGRESS.md,记录当前工作状态

3.知识要靠近代码、最小但完备、跟代码一起更新
4.知识衰减是最大敌人。过时的文档比没有文档更危险——它会让 agent 走错方向还以为自己是对的。

二、把指令拆分到不同文件里

防止巨型指令文件陷阱,随着规则或文档的补充,agent会发现冲突的规则命令,或者吃掉巨大的上下文预算,而其中相当一部分对于本次任务来说是不重要的。

会导致以下的问题
1.指令膨胀。
2.中间迷失效应:埋在 600 行文件中间第 300 行的关键约束,被有效忽略的概率非常高。
3.指令信噪比(SNR):文件中与当前任务相关的指令占总指令的比例。做 bug 修复时被要求读 50 行部署指令——SNR 很低。
4.路由文件:短小的入口文件,核心功能是引导 agent 去找更详细的文档,而不是自己包含所有内容。50-200 行就够了。
5.渐进式披露:先给概要信息,需要的时候再给详细信息。
6.优先级模糊度:当所有指令以相同格式和位置呈现时,agent 分不清哪些是不可违反的硬约束,哪些是建议性的软约束。

核心原则:常用信息放手边,偶尔用的收起来,用不上的别带。

入口文件 AGENTS.md 控制在 50-200 行,只放最常用的东西:
(1)项目概览(一两句话说清楚这是什么)入口文件是路由器,不是百科全书。50-200 行,只放概览、硬约束、和链接。
(2)首次运行命令(make setup && make test)
(3)全局硬约束(不超过 15 条不可违反的规则)
(4)指向专题文档的链接(一行描述 + 适用条件)。

示例:

# AGENTS.md

## 项目概览
Python 3.11 FastAPI 后端,PostgreSQL 15 数据库。

## 快速开始
- 安装:`make setup`
- 测试:`make test`
- 完整验证:`make check`

## 硬约束
- 所有 API 必须走 OAuth 2.0 认证
- 所有数据库查询必须用 SQLAlchemy 2.0 语法
- 所有 PR 必须通过 pytest + mypy --strict + ruff check

## 专题文档
- [API 设计规范](docs/api-patterns.md) — 添加新端点时必读
- [数据库操作约束](docs/database-rules.md) — 涉及数据库修改时必读
- [测试标准](docs/testing-standards.md) — 编写测试时参考

规范:

1.每个专题文档 50-150 行,按主题放在 docs/ 目录下或对应模块目录旁。
2.部分信息直接放在代码里更合适,类型定义、接口注释、配置文件里的说明。
3.每条指令都应该标明来源("为什么加这条规则?")、适用条件("这条规则在什么时候需要?")、过期条件("什么情况下可以删掉这条规则?")。定期审计,删掉过时的、冗余的、矛盾的条目。用不上的依赖就该删掉,不然它们只会拖慢系统。
4.如果某条指令必须在入口文件里,放顶部或底部——不要放中间。LLM 对长文本中间部分的信息利用效率显著低于两端。但更好的做法是把指令放到专题文档里,让 agent 按需加载。
5.提升指令信噪比,agent 把更多上下文预算花在实际任务上,而不是处理无关指令。

三、跨会话的任务也要求保持上下文连续

一个AI工匠,每天早上醒来都不记得昨天干了什么。你得重新认识整个工地——哪面墙砌了一半、为什么用的是红砖而不是青砖、水电管道走到哪了。更糟的是,你可能会把昨天已经装好的窗户拆了重来,因为你不记得它已经装过了。所以要解决AI coding agent 在跨会话任务中面对的困境--断片儿。要求通过结构化的状态持久化,让它像一个每天坚持写日记的工匠,通过日记去还原进度。

原因:
1.上下文窗口是有限的。理解代码库、跟踪自己的决策历史、处理工具输出、维护对话上下文。这些信息都会导致超长上下文。
2.agent 产生的信息不是均匀重要的。没有必要回顾所有的信息,而是提炼出来。
3.AI的上下文焦虑:当 agent 感觉上下文快满了,它们会表现出一种"过早收敛"的行为。

核心概念

1.上下文窗口是有限:不管模型吹多大的窗口,长任务总会用完。用完之后要么压缩(丢信息),要么重置(开新会话)。两种方式都会丢东西。
2.连续性工件:持久化的状态文件,让新会话能无歧义地恢复到上次离开的地方。最基本的形式:进度日志 + 验证记录 + 下一步行动。就是那个工匠的日记本。
3.重建成本:降低新会话恢复到可执行状态所需的时间。
4.漂移(Drift):agent 的理解跟代码仓库实际状态之间的偏差。每次会话边界都会引入漂移,不加控制会越漂越远。
5.上下文焦虑。
6.压缩 vs 重置:压缩是在同一个会话里把上下文摘要化(保留"是什么",可能丢了"为什么");重置是开新会话从持久化状态重建(干净但依赖工件完备性)。

记录日志的格式:

工具 1:进度文件(PROGRESS.md)

# 项目进度

## 当前状态
- 最新 commit: abc1234 (feat: add user preferences endpoint)
- 测试状态: 42/43 通过 (test_pagination_edge_case 失败)
- Lint: 通过

## 已完成
- [x] 用户模型和数据库迁移
- [x] 基础 CRUD 端点
- [x] 认证中间件集成

## 进行中
- [ ] 分页功能 (90% - 边界条件测试失败)

## 已知问题
- test_pagination_edge_case 在空结果集时返回 500
- 需要确认是否要在列表中包含已删除用户

## 下一步
1. 修复分页边界条件 bug
2. 添加"是否包含已删除用户"的查询参数
3. 更新 API 文档

工具 2:决策日志(DECISIONS.md)。记录重要的设计决策和原因。不需要详细的设计文档,只需要"什么决策、为什么、什么时候做的"——这是日记本里的备忘:

# 设计决策

## 2024-01-15: 使用 Redis 缓存用户偏好
- 原因: 读取频率高(每次 API 调用都需要),数据量小
- 否决方案: 用 PostgreSQL 物化视图(变更频率高,物化视图维护成本不划算)
- 约束: 缓存 TTL 设为 5 分钟,写入时主动失效

工具 3:git 提交作为检查点。
工具 4:在 AGENTS.md 里写明每天"上班"和"下班"的流程

## 每次会话开始时(上班打卡)
1. 读 PROGRESS.md 了解当前状态
2. 读 DECISIONS.md 了解重要决策
3. 跑 make check 确认仓库处于一致状态
4. 从 PROGRESS.md 的"下一步"部分继续工作

## 每次会话结束前(下班打卡)
1. 更新 PROGRESS.md
2. 跑 make check 确认一致状态
3. 提交所有已完成的工作

上下文焦虑的深层分析:

当上下文接近窗口限制时,agent 会表现出强烈的"过早收敛"行为。这就像考试时发现时间快到了,赶紧随便填选择题。

有两种策略:
(1)压缩(Compaction) 在同一个会话里把早期对话摘要化。
(2)重置(Context Reset) :完全清空上下文,开一个新会话,从持久化工件重建。

注意:不同模型对于上述两种策略有不同的表现差异,所以在设计时需要对目标模型有具体的理解,而不是套用通用模板。

关键要点

  • 上下文窗口是有限的资源。长任务一定会跨会话,跨会话一定会丢信息——这就像工匠每天都会失忆一样,是客观现实。
  • 解决方案不是更大的窗口,而是更好的状态持久化。进度文件 + 决策日志 + git 检查点——给失忆的工匠一个靠谱的日记本。
  • 把 agent 当成会失忆的工程师来管理:每次"下班"前写清楚做了什么、为什么、下一步做什么。
  • 重建成本是关键指标。好的 harness 应该让新会话在 3 分钟内恢复到可执行状态。
  • 混合策略:短任务在会话内完成,长任务用结构化工件维持连续性。

四、让 agent 每次工作前先初始化

在让 agent 开始干活之前,先用一个独立的阶段把基础环境搭好、验证命令跑通、项目结构搞清楚。

初始化实现的优化目标完全不同。

  • 实现阶段 的目标是:最大化已验证功能的数量和质量。
  • 初始化阶段 的目标是:最大化后续所有实现的可靠性和效率。

如何做好初始化

把初始化当作一个独立的阶段来执行。 第一个会话只做初始化,不写任何业务功能代码。初始化的产出是:

  1. 可运行的环境。 项目能启动、依赖都装好、没有环境问题。地基浇好了,没裂缝。

  2. 可验证的测试框架。 至少有一个示例测试能通过。这证明测试框架本身是配对的——就像地基上立了一根柱子,证明地基能承重。

  3. 自举契约文档。 一个明确的文档告诉后续会话:

# 初始化契约

## 启动命令
- 安装依赖:`make setup`
- 启动开发服务器:`make dev`
- 运行测试:`make test`
- 完整验证:`make check`

## 当前状态
- 所有依赖已安装并锁定
- 测试框架已配置(Vitest + React Testing Library)
- 示例测试通过(1/1)
- Lint 规则已配置(ESLint + Prettier)

## 项目结构
- src/ — 源代码
- src/components/ — React 组件
- src/api/ — API 客户端
- tests/ — 测试文件
  1. 任务分解。 把整个项目拆成有序的任务列表,每个任务有明确的验收标准:
# 任务分解

## Task 1: 用户认证基础
- 实现 JWT 认证中间件
- 添加登录/注册端点
- 验收标准:pytest tests/test_auth.py 全部通过

## Task 2: 用户资料页面
- 实现用户资料 CRUD
- 添加资料编辑表单
- 验收标准:pytest tests/test_profile.py 全部通过

## Task 3: 搜索功能
- ...
  1. Git 提交作为检查点。 初始化完成后提交一个干净的 checkpoint。后续所有工作都从这个 checkpoint 开始。

热启动策略:不要从空目录开始。用一个项目模板(create-react-app、fastapi-template 等)预置好标准的目录结构、依赖配置和测试框架。把通用的初始化步骤预置到模板里,只留下项目特有的初始化工作。就像在有通水通电的工地上开工,比从荒郊野岭开始强一万倍。

初始化的完成条件:不是"写了多少代码",而是自举契约的四个条件都满足了——能启动、能测试、能看进度、能接手下一步。用这个检查清单验收初始化:

## 初始化验收清单
- [ ] `make setup` 从零开始能成功
- [ ] `make test` 至少有一个测试通过
- [ ] 新的 agent 会话能只看仓库回答"怎么跑"和"怎么测"
- [ ] 任务分解文件存在且有至少 3 个任务
- [ ] 所有内容已提交到 git

如果我是一个新项目:

基于这个核心思想,在执行一个新任务时,最佳的操作步骤应严格划分为初始化阶段(打地基)实现阶段(砌墙)。以下是具体的步骤和案例说明:

阶段一:独立初始化阶段(只打地基,不写业务代码)

在这个阶段,你的唯一目标是最大化后续实现的可靠性和效率,产出物是基础设施,而不是功能代码。

步骤 1:热启动与搭建可运行环境

  • 操作: 不要从零开始(冷启动),而是使用成熟的脚手架或项目模板(热启动)创建项目结构,并确保所有依赖安装成功,项目能成功跑起来。
  • 检查点: 没有任何环境报错。

步骤 2:配置可验证的测试框架

  • 操作: 引入测试框架,并至少编写一个能跑通的示例测试。这不仅是为了测试未来的代码,更是为了验证“测试框架本身是否配置正确”。
  • 检查点: 测试命令能够成功执行并报告 1/1 测试通过。

步骤 3:编写“自举契约”(Bootstrap Contract)

  • 操作: 编写一份明确的文档(如 README.mdSETUP.md),确保任何全新的 Agent 或开发者只要看这个文档,就能无歧义地接手项目。契约必须包含:如何安装依赖、如何启动服务、如何运行测试、以及当前的项目结构。
  • 检查点: 别人不问你任何问题,仅凭文档就能启动项目。

步骤 4:进行任务分解与设定验收标准

  • 操作: 把要做的宏大任务拆分成有序的小任务列表(如 TASKS.md),并为每个任务制定明确的客观验收标准(最好是对应某个测试文件跑通)。
  • 检查点: 拥有至少 3 个清晰的子任务。

步骤 5:提交干净的检查点(Checkpoint)

  • 操作: 将上述所有的基建、配置、契约文档全部提交到 Git。后续所有的功能开发,都必须基于这个干净、可运行的基准点开始。

阶段二:实现阶段(地基干了,开始砌墙)

步骤 6:按图索骥进行功能开发

  • 操作: 在环境完美、测试就绪、任务清晰的前提下,开启新的工作流,严格按照“自举契约”和“任务列表”一步步编写业务代码,写完一个验证一个。

🌰 举例说明:开发一个“待办事项(Todo)全栈应用”

假设我们要让 AI Agent 开发一个 Todo 应用,按照最佳实践,操作过程如下:

❌ 错误做法(混合方式):

你直接对 Agent 说:“帮我写一个 Todo 应用,要有前后端。” Agent 立刻开始疯狂输出代码:写了 App.vue,写了 server.js,写了一半发现数据库连不上,又去改数据库配置;改完发现前端跨域,又去配跨域... 最后用完了上下文 token 额度,留下一个跑不起来的半成品代码堆。下一个 Agent 接手时,完全不知道该从哪修起。

✅ 正确做法(分离方式):

第一步:只让 Agent 做初始化(独立会话) 你对 Agent 说:“我们要开发一个 Todo 应用。现在请进行项目初始化:1. 使用 Vite+Vue 和 Express 搭建基础目录;2. 配置好 Vitest 和 Jest 测试框架并各写一个示例测试确保通过;3. 梳理好启动命令;4. 将开发拆分为 3 个子任务并写在 TASKS.md 里。注意:当前阶段不要实现任何 Todo 相关的业务代码。

此时 Agent 的输出产物:

  1. 完整的目录结构(前后端分离)。
  2. npm run devnpm run test 可以完美运行。
  3. 一份 README.md(自举契约),里面写着:
    • 启动:前端 cd web && npm run dev,后端 cd api && npm start
    • 测试:前端 npm run test
  4. 一份 TASKS.md
    • Task 1: 实现后端 Todo 的 CRUD 接口(验收标准:api.test.js 全部通过)
    • Task 2: 实现前端 Todo 列表渲染与新增功能
    • Task 3: 实现前端 Todo 删除与状态切换
  5. 自动执行 git initgit commit -m "chore: 项目初始化与契约建立"

第二步:开始功能实现(新会话) 关闭刚才的会话,开启一个全新的 Agent 会话。 你对 Agent 说:“阅读仓库里的 README.mdTASKS.md,请开始执行 Task 1。”

此时,Agent 会清楚地知道项目如何启动和测试,它只需专注编写后端的 CRUD 接口,写完后运行 npm run test 验证。完成后提交代码,再稳步推进 Task 2。

总结: 花 20% 的时间建立具有可运行、可测试、可交接三个特性的基础设施(打地基),能够为后续 80% 的业务代码实现省去无数个因为“上下文丢失”和“环境报错”带来的大坑。

如果是一个新的需求

“打地基与砌墙分离”的思想,本质上是一种控制软件复杂度和降低试错成本的方法论。它不仅适用于从零开始的“宏观”项目,同样适用于日常迭代的“微观”新需求。

我们可以把它们区分为“宏观初始化”“微观初始化”

1. 开启新项目时(宏观初始化:建城)

这是我们之前讨论的重点,它的“地基”是最重的。

  • 动作: 搭建底层脚手架、配置全局的 CI/CD、引入全局测试框架(Vitest/Jest)、规划整体目录结构、编写基础的 README.md 自举契约。
  • 目标: 确保这个项目能跑起来,且任何人(或 AI)接手时环境是一致的。

2. 拿到新需求时(微观初始化:建新房间)

即便项目已经跑得很稳定了,当你要加一个新功能(比如在前端表格加一个“导出为 Excel”的功能)时,依然严禁直接开始写业务逻辑代码。你需要为这个特定需求做“微观初始化”。

  • 动作:
    1. 梳理与定位(探勘): 不写代码,先去查找并阅读与该需求相关的现有代码(比如那个表格组件在哪、API 请求类在哪),搞清楚上下文。
    2. 契约与接口设计: 先把前端组件需要的 Props、或者后端要提供的新 API 路径和入参/出参结构定义好(哪怕只是写个空壳函数或 Mock 数据)。
    3. 编写需求级测试(打桩): 为这个新功能写好空的或会报错的测试用例(TDD 思想)。
    4. 任务拆解更新: 在项目管理工具或 TASKS.md 中,把这个“导出 Excel”的需求拆成几个步骤。
  • 目标: 确保新需求的边界清晰,且不会破坏现有的老代码。

🌰 举例说明:在现有的 Todo 应用中新增“导出功能”

❌ 错误做法(混合方式): 接到需求后,直接 npm install xlsx,然后打开 TodoList.vue 开始写导入库的代码、写数据转换逻辑、写按钮点击事件。写到一半发现数据格式不对,又去改后端的接口,最后页面报错连带以前的列表也渲染不出来了。

✅ 正确做法(微观初始化先行):

第一阶段:微观初始化(不动核心业务代码)

  1. 拆解任务:TASKS.md 中写下:1. 后端新增 /api/export 空接口并跑通测试;2. 前端引入 Excel 库并写一个只导出静态假数据的 Demo;3. 前后端联调,接入真实数据。
  2. 定义契约: 在后端写一个 /api/export 路由,里面只有 return { msg: "ok" },然后写一个测试文件测一下这个路由通不通。
  3. 环境准备: 安装好 xlsx 等依赖包,并确保项目依然能成功 npm run dev(验证引入新包没有引发冲突)。

第二阶段:功能实现

在“打地基与砌墙理论”的指导下,第二阶段(砌墙)的核心原则是:单线程执行、小步快跑、频繁验证。

步骤 1:以后端为起点,完成核心逻辑(针对 Task 1)

  • 动作: 找到在初始化阶段写好的那个只返回 { msg: "ok" }/api/export 路由。现在,开始向里面填写真实的业务代码:连接数据库,查询待办事项列表,将数据格式化,并作为流或 JSON 结构返回。
  • 验证(至关重要): 运行你在初始化阶段写好的后端测试用例(或者直接用 Postman/cURL 调用该接口)。
  • 检查点: 确保接口能成功返回真实的、结构正确的数据。此时完全不要去管前端

步骤 2:以前端为起点,完成独立组件(针对 Task 2)

  • 动作: 回到前端代码。在页面上加一个“导出”按钮。写一段独立的函数,先不要调用后端接口,而是用一段写死的假数据(比如 [{id:1, title:"test"}]),送给 xlsx 库去生成并下载 Excel 文件。
  • 验证: 点击页面的“导出”按钮,浏览器应该能成功下载一个 Excel 文件,并且打开后格式是你期望的。
  • 检查点: 此时你已经证明了“浏览器端生成和下载 Excel”这条链路是通的。如果报错了,你马上就知道是前端 xlsx 库的用法问题,绝对不用怀疑是后端接口挂了(因为还没连后端)

步骤 3:前后端联调与缝合(针对 Task 3)

  • 动作: 把前端按钮的点击事件,改为调用后端真实的 /api/export 接口。拿到真实数据后,再传给步骤 2 中写好的导出函数。
  • 添加边界处理: 既然主链路通了,现在开始处理异常情况:
    • 加一个 Loading 动画(防止用户狂点)。
    • 加一个错误提示(如果后端报错或者断网了,弹窗提示用户)。
    • 处理空数据情况(如果没有待办事项,按钮置灰或提示“无数据可导出”)。
  • 检查点: 一个完整的、健壮的功能跑通了。

步骤 4:全局回归测试与代码清理

  • 动作: 把初始化阶段为了调试而写的 console.log 全部删掉。检查代码规范(ESLint)。
  • 验证: 运行项目的全局测试命令npm run test),不仅要保证你的导出功能测试通过,还要保证你没有不小心把以前的“新增/删除”功能的代码弄坏。
  • 检查点: 所有测试 100% 通过。

步骤 5:提交代码,划掉需求

  • 动作:TASKS.md 中把所有的任务打上勾。将代码 Commit(例如:feat: 实现待办事项导出 Excel 功能)。

五、给 agent 划清每次任务的边界

贪多嚼不烂,这句话放到 AI agent 身上格外贴切。Agent 天生就有"多做一点"的冲动。当提示太宽泛时,agent 倾向于"同时启动多件事"而非"先做完一件事"。

注意力是有限的资源 在AI agent开发中同样需要注意。

核心概念

  • 过度延伸(Overreach):agent 在一次会话中激活的任务数量超过最优值。它不是主观判断,而是可量化的——同时做 5 个功能但 0 个跑通,就是 overreach。
  • 不足完成(Under-finish):已启动的任务中,通过端到端验证的比例低于阈值。写了代码但没跑通测试,就是 under-finish。
  • WIP 限制(Work-in-Progress Limit):来自 Kanban 方法论。核心思想:限制同时在进行的任务数量。对于 agent,WIP=1 是最安全的默认值——做完一个再做下一个。。
  • 完成证据(Completion Evidence):一个任务从"进行中"变成"已完成"必须满足的可验证条件。没有这个,agent 会用"代码看起来没问题"代替"行为通过测试"。
  • 范围表面(Scope Surface):一个 DAG 结构,每个节点是一个工作单元,边是依赖关系。状态只有四种:未开始、进行中、阻塞、已通过。
  • 完成压力(Completion Pressure):harness 通过 WIP 限制和完成证据要求共同产生的约束力,迫使 agent 先完成当前任务再开始新任务。

正确思路

  1. 强制 WIP=1,明确告诉 agent:任何时刻只允许一个任务处于"进行中"状态。 在 Claude Code 的 CLAUDE.md 或 Codex 的 AGENTS.md 里写:
## 工作规则
- 每次只做一个功能点
- 当前功能点端到端验证通过后,才能开始下一个
- 不要在实现功能 A 时"顺便"重构功能 B
  1. 给每个任务定义显式的完成证据,完成不是"代码写完了",而是"行为验证通过了"。完成证据必须是可执行的在你的功能列表里,每个条目都要有验证命令:
F01: 用户注册
  验证: curl -X POST /api/register -d '{"email":"test@example.com","password":"123456"}' | jq .status == 201
  状态: passing
  1. 把范围表面外部化 用一个机器可读的文件(JSON 或 Markdown)记录所有任务的状态。任何新会话都能直接读这个文件,知道:哪个任务在做?什么行为算完成?已经通过了什么验证? 4.监控验证完成率,持续跟踪 VCR(Verified Completion Rate)= 已通过验证的任务数 / 已启动的任务数。VCR < 1.0 时,阻止新任务启动。

"少做但做完"永远优于"多做但做半"

六、一定要功能清单约束agent

功能清单是 harness 原语:它不是"可选的规划工具",而是其他所有 harness 组件依赖的基础数据结构。就像数据库里的表结构——你不能说"要不我们省掉主键吧",省掉了整个系统就散架了。

文档是给人看的,原语是给系统用的。文档可以被忽略,原语不能被绕过。

怎么做

  1. 定义一个最小化的功能清单格式。不需要复杂的系统,一个结构化的 Markdown 或 JSON 文件就够了。关键是每个条目必须有三元组:
{
  "id": "F03",
  "behavior": "POST /cart/items with {product_id, quantity} returns 201",
  "verification": "curl -X POST http://localhost:3000/api/cart/items -H 'Content-Type: application/json' -d '{\"product_id\":1,\"quantity\":2}' | jq .status == 201",
  "state": "passing",
  "evidence": "commit abc123, test output log"
}

2.在 CLAUDE.md 里写清楚规则

## 功能清单规则
- 功能清单文件: /docs/features.md
- 每次只激活一个功能项
- 功能项验证命令必须通过才能标为 passing
- 不要修改功能清单的状态——由验证脚本自动更新

3.粒度校准。每个功能项应该是"一次会话能完成"的范围。太粗了做不完,太细了管理开销大。"用户可以添加商品到购物车"是一个好粒度,"实现购物车"太粗了,"创建 Cart 模型的 name 字段"太细了。

关键要点

  • 功能清单是 harness 的脊梁骨,不是给人看的备忘录。调度器、验证器、交接器都依赖它。
  • 每个功能项必须有三元组:行为描述 + 验证命令 + 当前状态。缺一项就不完整——就像三条腿的凳子少一条腿。
  • 状态转移由 harness 控制,agent 不能自己改状态。通过验证 = 唯一的升级路径。
  • 功能清单是项目的单一权威来源——任何关于"该做什么"的信息都从这里派生。
  • 粒度控制在"一次会话能完成"的范围。太粗做不完,太细管不过来。

七、防止 agent 提前宣告完成

现代神经网络系统性地过度自信——模型自报的置信度显著高于实际准确率。

单元测试通过 ≠ 任务完成

这是最常见的陷阱,也是最危险的一个。agent 写了代码,跑了单元测试,全部绿色,然后说"做完了"。但单元测试的设计哲学——隔离被测单元、模拟依赖——恰好使其无法检测跨组件问题:

  • 接口不匹配:渲染进程传给预加载脚本的文件路径是相对路径,但预加载脚本期望绝对路径。各自的单元测试都用了 mock,都通过了。只有端到端跑通时才发现问题。就像乐队里每个乐手各自练习都完美,但合在一起才发现定调不一样。

  • 状态传播错误:数据库迁移改了表结构,但 ORM 的缓存层还持有旧结构的缓存条目。单元测试每次都是全新的 mock 环境,不会暴露这种跨层状态不一致。

  • 环境依赖性:代码在测试环境(一切 mock)行为正确,在真实环境因配置差异、网络延迟、服务不可用而失败。就像在排练室唱得很好,上台演出时音响设备出了问题。

怎么防止提前交卷

  1. 外部化终止判定
  2. 构建三层终止校验
  • 第一层:语法与静态分析。成本最低,信息量最小,但必须通过。这是最低限度的检查——字都没写错才能往下看。
  • 第二层:运行时行为验证。测试执行、应用启动检查、关键路径验证。这是核心完成证据。不仅要写了,还要能跑。
  • 第三层:系统级确认。端到端测试、集成验证、用户场景模拟。防止过早声明的最后一道防线。不仅要能跑,还要跑对。
  1. 为 agent 设计好的"红笔批注"。给 agent 写的错误消息要包含修复指导,也就是详细描述,而不是简单的抛出错误状态。
  2. 捕获运行时信号。
    5.核心功能验证通过之前不许重构——完成优先级约束是防止过早优化的关键。

八、跑通完整流程才算真正验证

  • 单元测试对组件边界缺陷系统性盲视——它们的隔离设计恰好使其无法检测交互问题。每个人唱得都对,不代表合唱不跑调。
  • 端到端测试不仅检测缺陷,还改变 agent 的编码行为——让它更关注集成和边界。
  • 架构规则必须可执行——不是写在文档里等人来看,而是每次提交自动检查。
  • 错误消息要面向 agent 设计——包含"怎么修"的具体步骤,形成自我修正闭环。
  • 审查反馈提升让 harness 自动变强——每个被捕获的缺陷类别都变成永久防线。

九、每次会话结束前都做好交接

长期可靠性取决于操作纪律,不仅是单次运行的成功。 每个会话结束时的状态质量,直接决定下一个会话的效率。

  1. 清洁状态是完成的必要条件 在 harness 里明确定义:会话完成 = 任务通过验证 AND 清洁状态检查通过。 缺任何一个,会话不算完成。在 CLAUDE.md 里写:
## 会话退出检查清单
- [ ] 构建通过 (npm run build)
- [ ] 所有测试通过 (npm test)
- [ ] 功能清单已更新
- [ ] 无调试代码残留 (console.log, debugger, TODO)
- [ ] 标准启动路径可用 (npm run dev)
  1. 双模式清理策略 结合两种清理模式:
  • 即时清理(每个会话结束时):清理本次会话创建的临时工件、更新功能清单状态、确保构建和测试通过。这是"引用计数式"清理——用完就清。就像吃完饭马上洗碗,不留到第二天。

  • 定期清理(每周一次):全系统扫描——处理累积的结构性问题、更新质量文档、运行基准测试检测漂移。这是"追踪式"清理——定期做一次大扫除。

  1. 维护质量文档 质量文档是对每个模块持续评分的活跃工件——就像宿舍的卫生检查评分表:

  2. 定期简化 harness harness 里的每个组件之所以存在,是因为模型无法独立做好某件事。但随着模型改进,这些假设会过时。就像你大一的时候需要学长带着选课,到了大三你自己就知道怎么选了——学长的作用变小了,但有些事情你还是需要问。 一个更具体的原则:随着模型改进,harness 的有趣组合不是变少了,而是移动了。 以前必须解决的问题被模型能力覆盖了,但新的能力边界打开了以前不可能的 harness 设计。AI 工程师的工作是持续找到下一个有价值的组合。

  3. 清理操作必须幂等 清理脚本要能安全地重复执行——就像打扫卫生,扫两遍比扫一遍更干净,但不会更脏:

# 幂等的清理操作
rm -f /tmp/debug-*.log  # -f 确保文件不存在时不报错
git checkout -- .env.local  # 恢复到已知状态
npm run test  # 验证清理未破坏功能
  1. 高吞吐量改变了 merge 哲学 当 agent 的产出远超人类审查能力时,传统的 merge 哲学需要调整。OpenAI 团队的经验:在一个 agent 每天开 3.5 个 PR(且后来增加到更多)的环境里,最小化阻塞式 merge gate 是正确的。PR 应该短命。测试 flake 通常用后续运行解决,而不是无限期阻塞进度。在一个修正成本很低、等待成本很高的系统里,快速前进 + 快速修正是比缓慢确认更好的策略。

注意:这在低产出环境里是不负责任的。但在 agent 产出远超人类注意力的环境里,这往往是正确的权衡。关键判断标准:修正一个 bug 的平均成本 vs 等待人类审查一个 PR 的平均成本。 前者低于后者时,快速合并是对的。