Harness Engineering实战:让 Agent 每次工作前先初始化—为什么初始化必须是独立阶段

课程来源:Learn Harness Engineering · 第六讲《为什么初始化需要独立阶段》


Harness Engineering 模块化指令实战:告别 600 行巨型 AGENTS:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/162702085

基于上述基础。

一、背景

使用 AI 编码 Agent 时,最常见的低效模式是:让 Agent 上来就写功能。它很快会发现测试框架没配好、环境有问题、目录不清晰,大量时间花在"搞清楚这个项目怎么运作",真正写功能的时间反而很少。

更好的做法是:在让 Agent 干活之前,先用一个独立的阶段把基础环境搭好、验证命令跑通、项目结构厘清。

一句话结论:初始化和实现是两种性质完全不同的任务,必须分阶段。初始化阶段只建"执行前提",不写业务代码。


注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、核心知识点讲解

2.1 两种不同的工作,优化目标天然冲突

阶段 优化目标 典型产出
实现阶段 最大化"已验证功能"的数量与质量 业务代码
初始化阶段 最大化"后续所有实现"的可靠性与效率 基础设施

当两者混在一起,Agent 面临多目标优化问题。在没有显式优先级时,它会自然倾向于"写代码"(直接可见产出),牺牲基础设施(价值在后续才体现)。结果:基础设施没搭牢,功能代码可靠性也打折。

2.2 初始化与实现混在一起的四类具体问题

  1. 基础设施搭不牢:Agent 花 80% 精力写功能,20% 随便搭基础设施。测试框架配了没验证、lint 太宽松、进度文件没建。缺陷在第二个会话才暴露——新 Agent 不知道怎么跑、怎么测、进度在哪。
  2. 未验证的累积(隐蔽代价):测试框架配好前就写的功能,回头补测试可能发现设计要推翻,前面写得越多,后面推翻重来越多。
  3. 上下文预算浪费:初始化消耗大量 token 预算,留给功能实现的预算不够。第一个会话只完成一半,第二个会话还得从头理解,两头没占着。
  4. 隐式假设埋雷:初始化的关键决策(测试框架、目录组织、依赖管理)不显式记录,后续会话可能做矛盾选择——例如第一会话选了 A 框架,第二会话又引入 B 框架,两套共存成本翻倍。

2.3 权威研究支撑

  • Anthropic《长运行应用开发研究》:明确建议分离初始化与实现。实验数据——独立初始化阶段的项目,多会话场景功能完成率比混合方式高 31%;初始化投入时间在后续 3–4 个会话中完全收回。
  • OpenAI Codex harness engineering 指南:强调"仓库即操作记录"原则——第一次运行就要建立清晰的操作结构,否则每次新会话都要重新推断约定。

2.4 核心概念术语表

  1. 初始化阶段:Agent 生命周期的第一个阶段,只建立后续实现所需的执行前提,不开发功能。产出是基础设施,不是业务代码。
  2. 启动就绪清单(初始化契约):项目能被"全新 Agent 会话"无歧义操作的条件——能启动、能测试、能看进度、能接手下一步(四条件缺一不可)。
  3. 从零开始 vs 从模板开始:从零开始时 Agent 自行推断结构效果差;从模板开始则基础设施就位效果好。能用模板就用模板(热启动)
  4. 随时可接手:项目任何时刻处于"可被全新 Agent 接手"状态,不需口头解释,只看仓库就能干。
  5. 从开始到第一次测试通过:衡量初始化效率的核心指标,越短越高效。
  6. 后续会话的成功率:后续会话不需依赖隐式知识就能成功执行任务的比例,是初始化质量的最佳衡量标准。

2.5 初始化的正确做法(五大产出)

核心原则:把初始化当作独立阶段执行。第一个会话只做初始化,不写任何业务功能代码。

初始化的五大产出:

  1. 可运行的环境:项目能启动、依赖装好、无环境问题。
  2. 可验证的测试框架:至少一个示例测试通过,证明框架确实配好(而不只是"配了")。
  3. 启动就绪清单文档:明确告诉后续会话如何操作(见 2.7 示例 1)。
  4. 任务分解:整个项目拆成有序任务列表,每个任务有明确验收标准(见 2.7 示例 2)。
  5. Git 提交作为检查点:初始化完成提交一个干净的 checkpoint,后续会话从此开始。

热启动策略:不要从空目录开始,用项目模板(如 Spring Initializr、create-react-app)预置标准目录、依赖、测试框架,只把"项目特有"的初始化留出来。

2.6 初始化的完成条件(验收)

用"启动就绪清单"四条件验收,缺一不可:

  • 能启动mvn spring-boot:run 无环境问题
  • 能测试mvn test 至少一个用例通过
  • 能看进度state/PROGRESS.md 与任务分解文件可读
  • 能接手:只看仓库即可回答"怎么跑 / 怎么测 / 下一步做什么"

2.7 课程给出的三个参考示例(原样保留,作为契约模板)

示例 1:启动就绪清单文档(初始化契约)

# 初始化契约

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

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

## 项目结构
- src/ — 源代码
- src/components/ — 组件
- src/api/ — API 客户端
- tests/ — 测试文件

示例 2:任务分解文件

# 任务分解

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

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

示例 3:初始化验收清单

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

2.8 实际案例对比(React 前端项目)

维度 混合方式 独立初始化
第一会话行为 脚手架 + 首个功能同时做 只建结构、配框架、写示例测试、建清单、提交 checkpoint
第二会话重建时间 ~20 分钟(推断结构/框架/构建) < 3 分钟(直接从任务列表干活)
跨所有会话总重建成本 基准 多约 60%
功能完成率 基准 31%

独立初始化多花的那 20 分钟,会在后续成倍收回。

2.9 核心要点总结

  1. 初始化与实现的优化目标不同,混在一起只会互相拖后腿。
  2. 初始化的产出是基础设施:可运行环境、可验证测试、启动就绪清单、任务分解。
  3. 用"启动就绪清单"四条件验收初始化:能启动、能测试、能看进度、能接手。
  4. 热启动优于冷启动,用项目模板预置标准化基础设施。
  5. 初始化投入的时间会在后续 3–4 个会话中完全收回,是前期投资,不是额外成本

三、示例代码搭建过程

目标:

把上面这些"软知识"固化成一个可在仓库里重放、可被新会话接手的工程结构。技术栈:Spring Boot 3.2.5 + Java 17 + Maven,包名 com.badao.ai,沿用 harness/{init,gates,skills,state} 的分层,以及 instructions/ 模块化指令、state/ 跨会话状态。

3.1 项目骨架与依赖

pom.xml 差异在于本工程没有真正调用大模型——初始化阶段的逻辑是纯 Java,因此即使不配置 DASHSCOPE_API_KEY 也能通过所有测试;只有在执行 mvn spring-boot:run 启动完整 Web 应用时才需要该环境变量。

<properties>
    <java.version>17</java.version>
    <spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-agent-framework</artifactId>
        <version>${spring-ai-alibaba.version}</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        <version>${spring-ai-alibaba.version}</version>
    </dependency>
</dependencies>

3.2 目录结构

spring-ai-alibaba-bailian-harness-initphase/
├── pom.xml
├── state/
│   ├── PROGRESS.md              ← 进度(启动就绪"能看进度")
│   └── DECISIONS.md             ← 初始化期决策留痕(杜绝隐式假设)
└── src/
    ├── main/
    │   ├── java/com/badao/ai/
    │   │   ├── SpringAiDemoApplication.java
    │   │   ├── config/InitPhaseConfig.java        ← 模块化指令(按阶段组装提示词)
    │   │   ├── controller/InitController.java     ← /api/init/run、/api/todos
    │   │   ├── model/TodoItem.java                ← 实现阶段产物(演示用)
    │   │   ├── service/TodoService.java           ← 实现阶段产物
    │   │   ├── service/InitPhaseService.java      ← 对外暴露初始化阶段执行与验收
    │   │   ├── runner/InitDemoRunner.java         ← 启动即跑"独立初始化阶段"
    │   │   └── harness/
    │   │       ├── init/InitializationPhase.java  ★ 核心:初始化阶段
    │   │       ├── gates/InitReadinessGate.java   ★ 核心:启动就绪闸门(四条件验收)
    │   │       ├── skills/TemplateBootstrapSkill.java ★ 热启动:基于模板预置结构
    │   │       └── state/InitContractManager.java ★ 固化契约/任务分解/决策
    │   └── resources/
    │       ├── application.yml
    │       └── instructions/
    │           ├── AGENTS.md                       ← 入口概览 + 硬约束 + 文档索引
    │           ├── docs/init-contract.md           ← 启动就绪清单契约
    │           ├── docs/task-breakdown.md          ← 任务分解
    │           ├── docs/anti-pattern.md            ← 反面案例(混合方式代价)
    │           └── legacy/GIANT_AGENTS.md          ← 反面教材:巨型初始化文件
    └── test/java/com/badao/ai/
        ├── TodoModelTest.java / TodoServiceTest.java   ← 示例测试(满足"能测试")
        ├── InitReadinessGateTest.java                 ← 闸门逻辑单测
        └── InitPhaseComparisonTest.java               ← 独立初始化 vs 混合 对比实验

3.3 核心类逐一讲解

(1)InitializationPhase —— 初始化阶段本身

它把"初始化"固化为一个可被后续任意新会话无歧义重放的步骤。关键约束:本阶段只搭基础设施,不写业务功能代码。

@Component
public class InitializationPhase {
    private final TemplateBootstrapSkill bootstrapSkill;
    private final InitReadinessGate readinessGate;
    private final InitContractManager contractManager;

    public InitResult run() {
        log.info("===== 初始化阶段开始(本阶段只搭基础设施,不写业务功能) =====");
        bootstrapSkill.bootstrapFromTemplate();   // 1) 热启动:预置标准结构
        contractManager.writeInitContract();       // 2) 写启动就绪清单
        contractManager.writeTaskBreakdown();      // 3) 写任务分解
        contractManager.recordInitDecision(...);   // 4) 记录框架/目录决策,杜绝隐式假设
        InitReadinessGate.ReadinessReport report = readinessGate.verify(); // 5) 四条件验收
        return new InitResult(report.allGreen(), report);
    }

    public record InitResult(boolean success, InitReadinessGate.ReadinessReport report) {}
}
(2)InitReadinessGate —— 把"初始化完成"变成可自动验证的硬指标

四项条件缺一不可,只有全绿才算交付:

@Component
public class InitReadinessGate {
    private final Path base;

    public ReadinessReport verify() {
        boolean canBuild   = Files.exists(base.resolve("pom.xml"));
        boolean canTest    = hasExampleTest();      // src/test/java 下存在 *Test.java
        boolean hasProgress= Files.exists(base.resolve("state/PROGRESS.md"))
                          && Files.exists(base.resolve(".../task-breakdown.md"));
        boolean canHandoff = Files.exists(base.resolve(".../init-contract.md"))
                          && canBuild && canTest && hasProgress;
        return new ReadinessReport(canBuild, canTest, hasProgress, canHandoff);
    }

    public record ReadinessReport(boolean canBuild, boolean canTest,
                                  boolean hasProgress, boolean canHandoff) {
        public boolean allGreen() {
            return canBuild && canTest && hasProgress && canHandoff;
        }
    }
}

设计要点:base 可注入(默认当前工作目录),便于在测试中指向临时目录做"独立初始化 vs 混合"对比。

(3)TemplateBootstrapSkill —— 热启动优于冷启动

不要从空目录让 Agent 自行推断结构,而是基于模板预置标准目录与依赖:

@Component
public class TemplateBootstrapSkill {
    public void bootstrapFromTemplate() {
        log.info("[热启动] 基于模板预置标准目录与依赖结构...");
        ensureDir("src/main/java/com/badao/ai");
        ensureDir("src/test/java/com/badao/ai");
        ensureDir("src/main/resources/instructions/docs");
        ensureDir("state");
    }
}

真实场景可对接 Spring Initializr / create-react-app 等脚手架;这里用本地目录约定演示"标准化结构就位,只留项目特有初始化"。

(4)InitContractManager —— 让仓库"随时可接手"

把初始化的产出固化成仓库里的可见文件,使任何新会话只看仓库即可接手

  • init-contract.md:怎么跑、怎么测、当前状态(启动就绪清单)
  • task-breakdown.md:下一步做什么、验收标准
  • state/DECISIONS.md:初始化期的框架/目录决策留痕,杜绝隐式假设
@Component
public class InitContractManager {
    public void writeInitContract()  { /* 写入启动就绪清单 */ }
    public void writeTaskBreakdown() { /* 写入任务分解 */ }
    public void recordInitDecision(String decision, String reason, String alternatives) {
        /* 追加决策日志,标注"初始化期" */
    }
}

3.4 配置、服务、控制器、启动器

  • InitPhaseConfig:模块化指令风格——AGENTS.md 常驻,init-contract.md / task-breakdown.md 按需加载,并提供 buildPromptForPhase("init"|"impl") 体现"初始化与实现在提示词层面也分离"。同时保留 legacy/GIANT_AGENTS.md 作为巨型文件反面教材。
  • InitPhaseService:编排 InitializationPhaseInitReadinessGate,返回四条件 JSON。
  • InitControllerPOST /api/init/run 触发独立初始化阶段;GET /api/init/readiness 仅验收(新会话判断能否接手);/api/todos 属于实现阶段功能。
  • InitDemoRunner:实现 CommandLineRunner应用一启动先跑初始化阶段(对应"第一个会话只做初始化"),通过四项验收后再进入正常服务功能。

3.5 指令文档与反面教材

初始化说明拆成:

  • AGENTS.md:概览 + 全局硬约束(其中第 1 条即"初始化阶段禁止写业务功能")+ 文档索引
  • docs/init-contract.md:启动就绪清单契约
  • docs/task-breakdown.md:任务分解
  • docs/anti-pattern.md:混合方式危害与量化对比
  • legacy/GIANT_AGENTS.md:把一切揉进一个巨型文件的反面教材(对比用)

3.6 测试即"可验证的测试框架"产出

TodoModelTest / TodoServiceTest 是"实现阶段"的示例测试,它们的存在同时满足了启动就绪清单中"能测试"这一项。

InitReadinessGateTest 验证闸门判定:四项齐全 => 全绿;缺 task-breakdown.md => 非全绿。

InitPhaseComparisonTest 即课程"课后练习 2"的落地——独立初始化 vs 混合方式的量化对比:

@Test
void separateInitPhaseProducesReadinessAllGreen() {
    InitializationPhase phase = new InitializationPhase(
            new TemplateBootstrapSkill(), new InitReadinessGate(), new InitContractManager());
    InitializationPhase.InitResult result = phase.run();
    assertTrue(result.success(), "独立初始化阶段应通过启动就绪四项验收");
    assertTrue(result.report().allGreen());
}

@Test
void mixedApproachLeavesReadinessGap() throws IOException {
    // 混合方式:脚手架和第一个功能一起写,基础设施不全(缺契约/任务分解/进度)
    Path base = Files.createTempDirectory("mixed");
    Files.write(base.resolve("pom.xml"), ".".getBytes());
    Files.createDirectories(base.resolve("src/test/java"));
    Files.write(base.resolve("src/test/java/DemoTest.java"), ".".getBytes());
    // 故意不写 init-contract.md / task-breakdown.md / state/PROGRESS.md
    InitReadinessGate.ReadinessReport report = new InitReadinessGate(base).verify();
    assertFalse(report.allGreen(), "混合方式不应通过启动就绪验收");
    assertFalse(report.canHandoff(), "混合方式无法让新会话直接接手");
}

四、如何运行

# 1) 编译并跑测试(无需大模型 Key,验证初始化阶段逻辑)
mvn test

# 2) 触发"独立初始化阶段",返回启动就绪四项验收 JSON
mvn spring-boot:run
curl -X POST "http://localhost:886/api/init/run"

# 3) 仅做验收(模拟新会话判断能否接手)
curl "http://localhost:886/api/init/readiness"

# 4) 进入实现阶段(基础设施就绪后的功能验证)
curl "http://localhost:886/api/todos"

运行完整 Web 应用需设置 DASHSCOPE_API_KEY 环境变量;纯测试与初始化阶段逻辑不依赖它。

预期:/api/init/run 返回 allGreen=true,并打印"初始化阶段通过四项验收,后续会话可直接接手实现"。


五、课程课后练习(建议动手)

  1. 启动就绪清单设计:为自己的项目写一份清单,开新 Agent 会话时只给仓库内容(不给口头上下文),记录它遇到的问题,每个问题对应清单缺失的条款。
  2. 对比实验:选一个中等复杂度新项目。方式 A 初始化与首次实现同时做;方式 B 先独立初始化再实现。4 个会话后对比首次验证时间、重建成本、功能完成率(本工程 InitPhaseComparisonTest 已给出自动化雏形)。
  3. 初始化验收清单:为项目设计验收清单,让新 Agent 逐项执行,记录通过/未通过项,未通过即 harness 需补强之处。

六、小结

第六讲的本质,是把"初始化"从一个被忽视的、顺手做的事,提升为工程的第一等公民:它有独立阶段、有固定产出、有可自动验证的验收(启动就绪四条件)、有决策留痕。本示例工程用 Spring Boot 把这套理念落到了代码里——InitializationPhase 负责"只搭基础设施",InitReadinessGate 负责"四项全绿才算交付",TemplateBootstrapSkill 落实"热启动",InitContractManager 保证"仓库随时可接手"。当下一个全新会话打开这个仓库时,它不需要任何口头交代,仅凭 state/instructions/ 就能立刻知道怎么跑、怎么测、下一步做什么。

更多推荐