AI 每次都从头来?给它一条跨会话的路
长任务失败,很多时候不是代码不会写,而是上次做过什么没人留下来。本期搭建进度日志、初始化脚本和交接说明,让下一次会话接得上。


Harness 从 0 到 1|第 4 期
适配个人开发者和国内小团队的低成本做法:先让状态落盘,再谈自动化。
这套系列面向正在用 Claude Code 、 Codex 、 Cursor 、 OpenCode 或其他编程代理的中国开发者、产品经理和小团队。你不需要先学会复杂的 Agent 框架,先把自己的项目整理成代理能读、能执行、能复查的工作区。
这一期解决什么
长任务失败,很多时候不是代码不会写,而是上次做过什么没人留下来。本期搭建进度日志、初始化脚本和交接说明,让下一次会话接得上。
先记住系列路线
- 第 1 期:看见模型能力和执行可靠性之间的差距。
- 第 2 期:建立 Harness 的五子系统地图。
- 第 3~6 期:把规则、状态、范围和验证逐层放回仓库。
- 第 7 期:把一次次手动提示升级成自动循环。
- 第 8 期:当循环长成图,学习路由、并行、回退和人工审批。
下面进入本期正文。建议你打开一个自己正在维护的项目,对照着做,不要只收藏。
你让 Claude Code 帮你实现一个完整的功能,它跑了 30 分钟,做了大部分工作,但上下文快满了。你开个新会话继续,然后发现:它不记得上次做了什么决策、为什么选了方案 A 而不是方案 B 、哪些文件已经改过、测试跑到什么状态了。它得花 15 分钟重新探索一遍项目,而且可能跟上次的做法不一致。
这是 AI coding agent 在跨会话任务中面对的真实困境。本节课讲为什么 agent 会”断片”,以及如何通过结构化的状态持久化,让新会话能快速接上上次的工作。
上下文窗口:不是无限的
上下文窗口是有限的。这不是一个可以通过模型升级解决的问题,即使窗口大小增长到 1M tokens,复杂任务依然会用完。因为 agent 不只是在生成代码,它还要理解代码库、跟踪自己的决策历史、处理工具输出、维护对话上下文。这些信息加起来增长得比窗口扩容快得多。
更深层的问题在于,agent 产生的信息不是均匀重要的。中间推理步骤包含决策的”为什么”,比如为什么选了方案 A 而不是方案 B,为什么用了这个库而不是那个库,为什么跳过了某个优化。最终输出只包含”是什么”,即代码本身。压缩策略通常保留后者但丢了前者。下一个会话看到代码却不知道为什么这么写,可能会”优化”掉一个有意为之的设计决策。
Anthropic 在长运行 agent 研究中观察到一个有意思的现象:当 agent 感觉上下文快满了,它们会表现出”赶工收尾”的行为,匆忙结束当前工作,跳过验证步骤,或者选一个简单的方案而不是最优方案。 Anthropic 把这叫”上下文焦虑”。
会话连续性流程
没有状态持久化文件的时候,每个新会话都要重新摸索:
flowchart LR
S1["会话 1<br/>功能做到一半"] --> End1["上下文快满了<br/>会话结束"]
End1 --> S2["会话 2 重新开始"]
S2 --> Guess["重新读目录、重跑测试、<br/>猜上次为什么这么写"]
Guess --> Drift["代码会重复改<br/>恢复速度也很慢"]
有状态持久化文件的时候,新会话能快速接上:
flowchart LR
Work["会话 1 的工作"] --> Progress["PROGRESS.md<br/>已完成 / 进行中 / 下一步"]
Work --> Decisions["DECISIONS.md<br/>为什么这样做"]
Work --> Verify["验证记录<br/>哪些测试通过或失败"]
Work --> Commit["Git 检查点<br/>当前仓库状态"]
Progress --> Rebuild["会话 2 重建"]
Decisions --> Rebuild
Verify --> Rebuild
Commit --> Rebuild
Rebuild --> Resume["新会话能很快接上"]
核心概念
- 上下文窗口是有限的:不管模型宣称多大的窗口(128K 、 200K 、 1M),长任务总会用完。用完之后要么压缩(丢信息),要么重置(开新会话),两种方式都会丢东西。
- 状态持久化文件:持久化的状态文件,让新会话能无歧义地恢复到上次离开的地方。最基本的形式包括进度日志、验证记录和下一步行动。
- 重建成本:新会话恢复到可执行状态所需的时间。好的 harness 能把重建成本从 15 分钟压到 3 分钟。
- 漂移(Drift):agent 的理解跟代码仓库实际状态之间的偏差。每次会话边界都会引入漂移,不加控制会越漂越远。
- 上下文焦虑:Anthropic 观察到的现象,agent 在接近上下文限制时表现异常,过早结束任务以避免信息丢失,本质上是一种非理性的资源焦虑。
- 压缩 vs 重置:压缩是在同一个会话里把上下文摘要化,保留”是什么”但可能丢了”为什么”;重置是开新会话从持久化状态重建,状态干净但依赖工件的完备性。
连续性断了以后会发生什么
上个会话花了很多上下文预算分析了三种方案的优劣,最终选了方案 B 。这个会话的 agent 不知道这个分析过程,可能基于不完整的信息重新做了决策,而且可能选了方案 A 。同样的信息,不同的结论,因为做决策的上下文丢了。
更要命的是重复劳动。 Agent 不确定某项工作是否已完成,重新做了一遍。或者更糟,做了一半发现跟已有的实现冲突,需要返工。在没有进度记录的情况下,新会话完全不知道哪些工作已经有人做过了。
几个会话累积下来,实现方向可能已经悄悄偏离了原始需求。每个新会话对项目目标的理解都略有偏差,偏差一层层叠加,最终的结果可能跟最初的意图相去甚远。
还有验证缺口。上个会话的验证结果(哪些测试通过、哪些失败、为什么失败)没有记录,新会话得重新跑一遍验证才能了解当前状态。每次都重新诊断,每次都浪费宝贵的上下文。
OpenAI 和 Anthropic 都在他们的文档里强调了结构化状态持久化的重要性。 OpenAI 的 harness engineering 文章把仓库当作”操作记录”,每次操作的结果都应该在仓库里留下可追溯的痕迹。 Anthropic 的 long-running agents 文档则更具体地建议使用”交接文件”,包含当前状态、已知问题和下一步行动的结构化文档。
状态持久化的实践方法
核心思路**:把 agent 当成一个每次会话都会清空短期记忆的工程师来管理。** 每次它要”下班”之前,必须把关键信息写下来,让下一个”接班”的 agent 能快速上手。
工具 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 提交作为检查点。 每完成一个原子工作单元就提交,commit message 要说清楚做了什么和为什么。这是免费的、自动版本化的状态快照。
工具 4:init.sh 或 harness 的初始化流程。 在 AGENTS.md 里写明每次”上班”和”下班”的流程:
## 每次会话开始时(上班)
1. 读 PROGRESS.md 了解当前状态
2. 读 DECISIONS.md 了解重要决策
3. 跑 make check 确认仓库处于一致状态
4. 从 PROGRESS.md 的"下一步"部分继续工作
## 每次会话结束前(下班)
1. 更新 PROGRESS.md
2. 跑 make check 确认一致状态
3. 提交所有已完成的工作
混合策略:不需要每次都重置上下文。短任务(30 分钟以内)可以在同一个会话里完成,长任务(跨会话)必须用进度文件和决策日志来维持连续性。判断标准:如果任务需要的上下文超过窗口的 60%,就开始准备交接。
上下文焦虑的深层分析
Anthropic 在 2026 年 3 月发布的研究进一步揭示了上下文焦虑的具体表现:在 Sonnet 4.5 上,当上下文接近窗口限制时,agent 会表现出强烈的”赶工收尾”行为。
针对这个现象,有两种策略:
压缩(Compaction):在同一个会话里把早期对话摘要化。优点是保留连续性,agent 能看到”是什么”。缺点是”为什么”经常在摘要中丢失,比如为什么选了方案 B 而非 A,为什么跳过了某个优化。更关键的是,压缩并不能消除上下文焦虑,agent 知道上下文曾经很大,心理上仍然倾向于加速收尾。
重置(Context Reset):完全清空上下文,开一个新会话,从持久化工件重建。优点是干净的心理状态,新会话没有”我快没时间了”的焦虑。缺点是依赖交接工件的完备性,如果进度文件里漏了关键信息,新会话可能在错误方向上浪费时间。
Anthropic 的实际数据:对于 Sonnet 4.5,上下文焦虑足够严重,以至于压缩单独不够用,上下文重置成为 harness 设计的关键组件。但对于 Opus 4.5,这种行为大幅减弱,可以不依赖重置而靠压缩管理上下文。这意味着:harness 设计需要对目标模型有具体的理解,而不是套用通用模板。
实际案例
一个 agent 被要求实现一个带用户认证的博客系统,12 个功能点,预计需要 5 个会话。
没有状态持久化文件的基线:会话 1 实现了用户模型和基础路由。会话 2 开始时,agent 不记得认证中间件的接口约定,花了约 15 分钟推断上次的设计意图。到会话 3,累积漂移导致 agent 开始重复已实现的功能。到会话 5,仓库有大量冗余代码,但核心认证功能仍未通过端到端测试。 12 个功能点只完成了 7 个,其中 3 个有隐含的正确性问题。
有状态持久化文件的对照:使用进度文件、决策日志、验证记录和 git 检查点。每个会话结束时自动更新状态报告。会话 2 的重建成本降到约 3 分钟。到会话 5,所有 12 个功能点完成且通过验证。
定量对比:重建时间减少约 78%,功能完成率从 58% 提升到 100%,隐含缺陷率从 43% 降到 8% 。
核心要点
- 上下文窗口是有限的资源。长任务一定会跨会话,跨会话一定会丢信息,这是客观现实。
- 解决方案不是更大的窗口,而是更好的状态持久化。进度文件、决策日志、 git 检查点,三者配合让新会话能接上之前的工作。
- 把 agent 当成每次会话都会清空短期记忆的工程师来管理:每次”下班”前写清楚做了什么、为什么、下一步做什么。
- 重建成本是关键指标。好的 harness 应该让新会话在 3 分钟内恢复到可执行状态。
- 混合策略:短任务在会话内完成,长任务用结构化工件维持连续性。
延伸阅读
- Anthropic: Effective Harnesses for Long-Running Agents
- OpenAI: Harness Engineering
- Lost in the Middle: How Language Models Use Long Contexts
- Claude Code Documentation
- HumanLayer: Harness Engineering for Coding Agents
练习
-
连续性损耗度量:选一个需要至少 3 个会话的开发任务。不提供任何状态持久化文件,在每个会话开始时记录 agent 花了多少上下文来”搞清楚上次做了什么”。会话结束后,创建进度文件,让下一个会话从进度文件开始。对比有进度文件和没有时的重建成本。
-
交接模板设计:设计一个最小化的交接模板,包含四个字段:仓库状态(commit hash)、运行时状态(测试通过率)、阻塞项、下一步行动。让一个全新的 agent 会话只凭这个模板恢复项目状态,记录恢复过程中出现的歧义点,迭代改进模板。
-
混合策略实验:在一个包含 5 个会话的开发任务中,对比三种策略:(a) 每次都开全新会话 + 进度文件,(b) 在同一个会话里尽可能多做(上下文压缩),(c) 混合策略(短任务在会话内,长任务跨会话 + 进度文件)。对比重建时间、功能完成率和决策一致性。
在使用 AI 编码 agent 时,一个常见的低效模式是:让 agent 直接开始做功能,它上来就写代码,但很快会发现测试框架没配好、环境有问题、项目结构不清晰,大量时间花在了”搞清楚这个项目怎么运作”上面,真正用于写功能的时间反而很少。
更好的做法是,在让 agent 开始干活之前,先用一个独立的阶段把基础环境搭好、验证命令跑通、项目结构搞清楚。初始化工作应该和功能实现分开,它们是两种性质完全不同的任务。
这节课要讨论的,就是为什么初始化必须是独立的阶段,不能跟实现混在一起。
两种不同的工作
初始化和实现的优化目标完全不同。实现阶段的目标是最大化已验证功能的数量和质量,初始化阶段的目标则是最大化后续所有实现的可靠性和效率。
当你把初始化和实现混在一起的时候,agent 面临一个多目标优化问题:它要同时搭基础设施和写功能代码。在没有显式优先级设定的情况下,agent 自然倾向于写代码(因为那是直接可见的产出),而牺牲基础设施(因为它的价值只能在后续会话中体现)。结果就是基础设施没搭牢,功能代码的可靠性也打了折扣。
初始化生命周期
flowchart TB
subgraph Wrong["混在一起的一次会话(错误)"]
W1["一上来就开始做功能"] --> W2["做到一半才发现环境和测试缺口"]
W2 --> W3["累积未经验证的代码"]
W3 --> W4["下个会话还得重新摸项目状态"]
end
subgraph Right["独立初始化阶段(正确)"]
R1["会话 1:环境可运行"] --> R2["示例测试通过"]
R2 --> R3["写出启动契约 + 任务清单"]
R3 --> R4["提交干净检查点"]
R4 --> R5["后续会话直接开始做已准备好的任务"]
end
初始化与实现同时进行的问题
最直接的问题是基础设施搭不牢。 Agent 花了 80% 的精力写功能代码,剩下 20% 随便搭了点基础设施。测试框架配了但没验证过,lint 规则设了但太宽松,进度文件没创建。这些缺陷在第一个会话里不明显(因为 agent 还记得它做了什么),但到第二个会话就暴露了:新 agent 不知道项目怎么跑、怎么测、做到哪了。
更隐蔽的代价是”未验证的累积”。在测试框架配好之前写的功能代码,等回头补测试的时候可能发现设计上就有问题,早知道的话应该用不同的方式实现。前面写的代码越多,后面需要推翻重来的就越多。
上下文预算也在被浪费。初始化工作(配环境、配测试、理解项目结构)消耗了大量预算,留给实际功能实现的反而不够了。结果第一个会话只完成了一半的功能,第二个会话还得从头理解项目。预算花在了初始化上,但初始化也没做好,两头都没占着。
最容易被忽略的是隐式假设埋下的雷。 Agent 在初始化过程中做的决策(用什么测试框架、目录怎么组织、依赖怎么管理)如果不显式记录下来,后续会话就可能做出矛盾的选择。第一个会话选了 Vitest 做测试框架,第二个会话的 agent 不知道,又引入了 Jest,两套测试框架共存,维护成本翻倍。
Anthropic 在他们的长运行应用开发研究中明确建议把初始化和实现分离。他们的实验数据:使用独立初始化阶段的项目,多会话场景中的功能完成率比混合方式高 31% 。而且初始化阶段投入的时间在后续 3-4 个会话中就能完全收回。
OpenAI 的 Codex harness engineering 指南也强调”仓库作为操作记录”的原则:第一次运行就要建立清晰的操作结构,否则每次新会话都得重新推断项目约定。
核心概念
- 初始化阶段:agent 生命周期中的第一个阶段,只建立后续实现所需的执行前提,不做功能开发。它的产出是基础设施,而不是业务代码。
- 启动就绪清单:一个项目能被全新 agent 会话无歧义操作的条件:能启动、能测试、能看进度、能接手下一步。四个条件缺一不可。
- 从零开始 vs 从模板开始:从零开始意味着 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/ — 测试文件
4. 任务分解。 把整个项目拆成有序的任务列表,每个任务有明确的验收标准:
# 任务分解
## Task 1: 用户认证基础
- 实现 JWT 认证中间件
- 添加登录/注册端点
- 验收标准:pytest tests/test_auth.py 全部通过
## Task 2: 用户资料页面
- 实现用户资料 CRUD
- 添加资料编辑表单
- 验收标准:pytest tests/test_profile.py 全部通过
## Task 3: 搜索功能
- ...
5. Git 提交作为检查点。 初始化完成后提交一个干净的 checkpoint 。后续所有工作都从这个 checkpoint 开始。
热启动策略:不要从空目录开始。用一个项目模板(create-react-app 、 fastapi-template 等)预置好标准的目录结构、依赖配置和测试框架。把通用的初始化步骤预置到模板里,只留下项目特有的初始化工作。
初始化的完成条件:启动就绪清单的四个条件全部满足——能启动、能测试、能看进度、能接手下一步。用这个检查清单验收初始化:
## 初始化验收清单
- [ ] `make setup` 从零开始能成功
- [ ] `make test` 至少有一个测试通过
- [ ] 新的 agent 会话能只看仓库回答"怎么跑"和"怎么测"
- [ ] 任务分解文件存在且有至少 3 个任务
- [ ] 所有内容已提交到 git
实际案例
一个 React 前端项目的两种初始化方式对比:
混合方式:agent 在第一个会话中同时做了项目脚手架创建和首个功能实现。会话结束时,仓库有可运行的代码,但没有显式的启动和测试命令文档、没有进度跟踪文件、没有任务分解。第二个会话花了约 20 分钟推断项目结构、测试框架和构建流程。
独立初始化:第一个会话只做初始化,用项目模板创建目录结构、配置测试框架(Vitest + React Testing Library)、写一个示例测试并验证通过、创建启动就绪清单文档和任务分解文件、提交初始检查点。第二个会话的重建时间不到 3 分钟,直接从任务列表开始工作。
整个项目周期对比:混合方式的总重建时间(跨所有会话)比独立初始化多约 60% 。独立初始化多花的那 20 分钟在后续会话中被成倍收回。前期多投入一点时间把初始化做扎实,后续的效率反而更高。
核心要点
- 初始化和实现的优化目标不同,混在一起只会互相拖后腿。
- 初始化的产出是基础设施:可运行的环境、可验证的测试、启动就绪清单、任务分解。
- 用”启动就绪清单”的四个条件验收初始化:能启动、能测试、能看进度、能接手下一步。
- 热启动优于冷启动,用项目模板预置标准化的基础设施。
- 初始化投入的时间会在后续 3-4 个会话中完全收回,这是前期投资,不是额外成本。
延伸阅读
- Anthropic: Effective Harnesses for Long-Running Agents
- OpenAI: Harness Engineering
- HumanLayer: Harness Engineering for Coding Agents
- Infrastructure as Code — Martin Fowler
- SWE-agent: Agent-Computer Interfaces
练习
-
启动就绪清单设计:为你正在开发的项目写一个完整的启动就绪清单。然后开一个全新的 agent 会话,只给它看仓库内容(不给任何口头上下文),让它尝试启动项目、跑测试、了解当前进度。记录它遇到的问题,每个问题都对应启动就绪清单中缺失的一个条款。
-
对比实验:选一个中等复杂度的新项目。方式 A 让 agent 初始化和首次实现同时做,方式 B 先花一个会话做独立初始化,第二个会话再开始实现。在 4 个会话后对比首次验证时间、重建成本和功能完成率。
-
初始化验收清单:为你的项目设计一个初始化验收清单,让一个全新的 agent 会话逐项执行,记录哪些项通过了、哪些没通过。没通过的项就是 harness 需要补强的地方。
放到中国团队里,怎么用
国内个人开发者和小团队常见的问题很具体:项目规则散在飞书、微信群和口头约定里;测试命令没人维护;换一个人或换一次会话就要重新解释;模型调用还有额度、网络、数据合规和代码保密边界。 Harness 解决不了这些外部问题,但能把项目内部的规则、状态和验收方式固定下来。
建议先做一个低风险版本:只允许代理读取和修改一个测试仓库;所有写入动作走 Git 分支;涉及生产数据、客户代码、密钥和外部系统时,先人工确认;每次任务结束保留验证结果和变更说明。国内团队最容易忽略的不是模型选择,而是权限和数据流向。
本期动手清单
- 在自己的项目里找出一个反复返工的任务。
- 记录代理从开始到结束实际读了哪些文件、执行了哪些命令。
- 把缺失的规则、状态或验证补成项目文件。
- 用同一个任务再跑一次,只比较结果和返工,不凭感觉下结论。
- 把失败归因到 Harness 的某一层,下一期继续补。
下一期预告
下一期会把本期的概念进一步落成一套文件结构和模板。到时候我们不再讨论“应该怎样”,而是直接从项目根目录开始搭。