跳到正文
主页
返回

Harness 工程(二):仓库与状态管理

Table of contents

Open Table of contents

1. 什么是仓库?

对于 Agent 来说,仓库是一个面向 AI Agent 的核心概念:它是 Agent 执行任务时唯一的、具备最高权威的「单一事实来源」(Single Source of Truth)和「记录系统」(System of Record)。

Agent 刚进入工作区时,它具备的启蒙知识只有仓库中的内容。OpenAI 在 Harness Engineering 一文中把这一点说得很直白:仓库里不存在的信息,对 Agent 来说等于不存在。 他们把这称为「仓库即规范」原则——仓库本身就是最高权威的规范文档。

1.1 仓库自检:五个问题

进入工作区的 Agent,若能够根据仓库中的内容回答以下五个问题,才说明仓库配置是合格的:

  1. 项目目的是什么?(要解决什么问题、交付什么)
  2. 如何启动/安装?(依赖、启动命令、环境要求)
  3. 如何验证?(测试、lint、make check 等)
  4. 当前进度与阻塞是什么?(做到哪、卡在哪)
  5. 关键架构决策及原因是什么?(为什么这样设计)

2. AGENTS.md 的正确用法

工作区出现 bug 时,我们习惯在 AGENTS.md 里加一条维护性规则。规则越来越多,意外情况也会增多,常见问题包括:

  1. 上下文有上限。 一条奇怪指令可能导致上下文膨胀,迫使 Agent 读取大量源文件。
  2. 中间迷失(Lost in the Middle)。 Liu et al., 2023 证明:在冗长的 AGENTS.md(例如 600 多行)中,首尾内容被更重视,中间提示反而被弱化。
  3. 优先级冲突。 Agent 无法判断模糊指令的优先级——语法风格、禁用方法等规则,读取后不知道谁更重要。
  4. 维护衰减。 大文件难维护;过时规则没人删(「也许别的地方依赖这条?」),加新规则却零成本。文件只增不减,信噪比持续下降——与软件技术债是同一问题。
  5. 矛盾累积。 不同时期的指令互相矛盾:一条说「TypeScript 严格模式」,另一条说「某些遗留文件允许 any」。Agent 每次随机选一条遵循。

正确用法


3. 跨对话上下文连接(长任务处理)

长任务中常见的问题:

  1. 上下文窗口有限。 不管模型宣称多大的窗口(128K、200K、1M),长任务总会用完。用完之后要么压缩(丢信息),要么重置(开新会话),两种方式都可能丢东西。
  2. State 文件缺失。 两个会话之间需要 state 文件记录进度、to-do 还差什么。会话交接时还会丢失「为什么这样设计架构」的中间层意义——下一段对话只看到完整代码,不知道设计动机。

3.1 核心概念

概念说明
状态持久化文件让新会话无歧义恢复到上次离开的地方(进度日志、验证记录、下一步行动)
重建成本新会话恢复到可执行状态所需的时间;好的 harness 能把重建成本从 15 分钟压到 3 分钟
漂移(Drift)Agent 的理解与仓库实际状态之间的偏差;每次会话边界都会引入漂移
上下文焦虑Anthropic 观察到的现象:Agent 接近上下文限制时表现异常,过早结束任务以避免信息丢失(见 Anthropic 长任务 harness 设计
压缩 vs 重置压缩:同一会话内摘要化,保留「是什么」但可能丢「为什么」;重置:开新会话从持久化状态重建,状态干净但依赖工件完备性

核心思路:把 Agent 当成每次会话都会清空短期记忆的工程师。 每次「下班」前必须把关键信息写下来,让下一个「接班」的 Agent 能快速上手。

3.2 工具 1:PROGRESS.md

让下一个上下文知道上一个会话做了什么、解决了什么问题——本质是 to-do 完成情况。

# 项目进度

## 当前状态
- 最新 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 文档

3.3 工具 2:DECISIONS.md

解决跨会话中「设计意义」的缺失——让下一个会话知道上一个会话为什么这样设计、有什么好处、解决了什么问题。没有 DECISIONS.md,下一个会话会花费多余的上下文去推理前一个会话的设计意图。

3.4 工具 3:Git

给工作区的 Agent 使用 git 的权利。在保证版本可回溯的前提下,git 提交作为检查点——每完成一个原子工作单元就提交,commit message 要说清楚做了什么和为什么。这是免费的、自动版本化的状态快照。

3.5 工具 4:Session Lifecycle 初始化

与员工交接类似:init.sh 或 harness 的初始化流程。在 AGENTS.md 里写明每次「上班」和「下班」的流程:

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

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

3.6 混合策略

3.7 压缩 vs 重置:如何选择

方法适用场景优点缺点
压缩探索性任务、需保持方向的长上下文继承上下文,保持开发方向占用上下文空间;可能继承上下文焦虑
重置前期 plan loop、头脑风暴后的执行阶段干净开始,无上下文焦虑缺失 DECISIONS.md 中的中间层设计信息

Anthropic 的实际数据(来源):对于 Sonnet 4.5,上下文焦虑足够严重,压缩单独不够用,上下文重置成为 harness 设计的关键组件;对于 Opus 4.5,该行为大幅减弱,可以不依赖重置而靠压缩管理上下文。这意味着:harness 设计需要对目标模型有具体理解,而不是套用通用模板。


4. 初始化的重要性

会话开启时若没有良好约束,Agent 会把 80% 的推理资源花在「如何写代码」,只剩 20% 用于基座打磨。长对话时,下一个会话的 Agent 不知道测试架构和上一会话设计的功能是什么,测试架构可能不完整,第二轮需要更多上下文去改正。最容易被忽略的是隐式假设埋下的雷——测试框架、目录组织、依赖管理等决策若不显式记录,后续会话可能做出矛盾选择(例如第一轮选 Vitest,第二轮又引入 Jest)。

好的初始化应做到四点:能启动、能测试、能说明状况、能让下一轮接手。

4.1 初始化的核心概念

  1. 产出应是开发框架,不是业务代码。 框架包含:能启动、能测试、能说明状况、能服务下一轮会话。
  2. 需要脚手架模板,让 Agent 不必每轮从零理解整个项目。
  3. 实践中的初始化包括:
    • 可运行环境(依赖、启动命令)
    • 可测试的测试框架(至少一例测试实例用于 verify)
    • 启动就绪清单(见下)
    • 任务分解(描述明确,非模糊功能列表)
    • Git 框架(提供检查点)
# 初始化契约

## 启动命令
- 安装依赖:`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/ — 测试文件

4.2 两种初始化方式对比(React 前端示例)

方式第一会话第二会话
混合方式脚手架 + 首个功能同时做;无显式启动/测试文档、无进度文件约 20 分钟推断项目结构、测试框架、构建流程
独立初始化只做初始化:目录结构、测试框架、示例测试、就绪清单、任务分解、初始 commit重建时间 < 3 分钟,直接从任务列表开始

5. 给任务划清边界

多个任务并行远不如逐个任务可靠。假设 Agent 上下文容量为 C,同时进行 k 个任务时,每个任务约分到 C/k 的推理资源;若某任务需要超过 C/k,该任务可能无法完成。任务执行最重要的是稳定性——有效安排边界,让 Agent 逐个完成。

5.1 核心概念

概念定义
过度延伸(Overreach)一次会话中激活的并行任务数量超过最优值。可同时做 5 个功能但 0 个跑通,就是 overreach。注意:任务「过大」属于 Scope 划分不当,不是 overreach。
不足完成(Under-finish)已启动的任务中,通过端到端验证的比例低于阈值。写了代码但没跑通测试,就是 under-finish
WIP 限制来自 Kanban,限制同时在进行的任务数。对 Agent,WIP=1 是最安全的默认值
完成证据(Completion Evidence)任务从「进行中」到「已完成」必须满足的可验证条件
范围表面(Scope Surface)DAG 结构,节点是工作单元,边是依赖关系;状态:未开始、进行中、阻塞、已通过
完成压力(Completion Pressure)WIP 限制 + 完成证据要求共同产生的约束力

为何 WIP 过大有问题? 一次会话激活过多并行任务时,容易进入 overreach(做很多事但都没做好),随后滑向 under-finish,Agent 疲于用上下文修修补补——这应落实到每轮会话的指令设计。

5.2 正确的任务边界划分

1. 强制 WIP=1

AGENTS.md 中规定:

## 工作规则
- 每次只做一个功能点
- 当前功能点端到端验证通过后,才能开始下一个
- 不要在实现功能 A 时"顺便"重构功能 B

2. 每个功能点需有验证三元组

一个功能完成以行为验证通过为准,需有测试审核。每个功能用三元组描述:

{
  "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"
}

状态可通过状态机实现:未开始、进行中、阻塞挂起、通过。

3. 把范围表面外部化

用机器可读文件(JSON 或 Markdown)记录所有任务状态。任何新会话都能直接读取,知道:哪个任务在做?什么行为算完成?已通过什么验证?


分享本文:

上一篇
Harness 工程(三):验证与测试设计
下一篇
MCP项目实践-DailySearch