docs: 添加 agentscope v2 迁移实施计划

按 spec 的 9 阶段拆为可执行任务(Phase 0 API spike → Phase 9 全量回归),
含文件结构图、逐任务 TDD 步骤、签名一致性自检。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
everywhere.z 2026-06-19 21:50:29 +08:00
parent 6143229255
commit 8954412e93
1 changed files with 937 additions and 0 deletions

View File

@ -0,0 +1,937 @@
# liteflow-react-agent AgentScope 2.0 迁移 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 把 `liteflow-react-agent` 模块底层从 agentscope 1.0.12 切到 2.0.x 的 `HarnessAgent`,会话/持久化/workspace/skill/工具全部改用 v2 原生,保住业务侧 `ReActAgentComponent` 核心用法与现有 `liteflow.agent.*` 配置键,并接通 compaction/plan-mode/subagent/sandbox/self-learning/interrupt 等增强能力。
**Architecture:** 每个 `ReActAgentComponent` 子类构建一个无状态 `HarnessAgent` 单例;`(conversationId, agentKey)` 映射为 `RuntimeContext(userId=conversationId, sessionId=agentKey)`;记忆/持久化走 `AgentStateStore`NONE/JVM/LOCAL_FILE/REDIS/MYSQL/+OSSworkspace/文件/shell 走 HarnessAgent 文件系统 + 内置工具 + `PermissionEngine`skill 走 `skillRepository`/workspace skillsHook→Middleware`stream()`→`streamEvents()`。删掉自建 `AgentSessionManager`/`AgentSessionFactory`/5 factory/`WorkspaceFileTools`/`ManagedShellCommandTool`/`SkillBoxFactory`。
**Tech Stack:** Java 21运行时Maven 多模块agentscope 2.0.xcore + extensions-redis/mysql/oss/skill-gitSpring BootJUnit 5 + Mockito 4.11Reactor。
**参考 spec** `docs/superpowers/specs/2026-06-19-agentscope-v2-migration-design.md`
## Global Constraints
- 目标 agentscope 版本:`2.0.x`(在根 `pom.xml``<agentscope.version>2.0.x</agentscope.version>`,具体 patch 取已发布最新)。
- 运行时 Java21+react-agent 已满足,不变)。
- 测试**只放** `liteflow-testcase-el/liteflow-testcase-el-react-agent` 模块react-agent-core **不放** `src/test`。**严禁**把任何 `skipTests=true` 改为 false。
- 业务侧源码兼容:`model()/systemPrompt()/userPrompt()/tools()/skills()/enableSkills()/resolveConversationId()/agentKey()/maxIterations()/handleReply(Msg)/buildModel()/ctx()/getSlot()` 签名保持不变。
- 老配置键保留并映射;被 v2 取代的键标 `@Deprecated` + 启动告警,不在本迁移删除。
- 不重写 `liteflow-core` 编排引擎agent 仍是 `NodeComponent``process()` 仍为 `final`
- 分支:`agentscope_v2`(已存在)。每个 Task 末尾 commit不 push除非用户要求。
- 凡依赖 spec §16 中 R1/R4/R5/R6 的 API**必须先完成 Task 0 的 API spike** 拿到确认签名后再写实现代码,不得臆造 API。
## File Structure
### 删除react-agent-core
- `agent/session/AgentSessionManager.java`
- `agent/session/AgentSession.java`
- `agent/session/factory/AgentSessionFactory.java`
- `agent/session/factory/AgentSessionFactoryRegistry.java`
- `agent/session/factory/{InMemory,LocalFile,Redis,Mysql,None}AgentSessionFactory.java`
- `agent/tool/WorkspaceFileTools.java`
- `agent/tool/ManagedShellCommandTool.java`
- `agent/skill/SkillBoxFactory.java`
- `agent/skill/SkillToolResolver.java`
- `agent/skill/SkillLoadResult.java`
- `agent/skill/SkillTrackingHook.java`
- `agent/hook/ReActLoggingHook.java`
- `agent/hook/ChatUsageTrackingHook.java`
### 新增react-agent-core`com.yomahub.liteflow.agent`
- `state/AgentStateStoreResolver.java` —— `MemoryStorageMode`→`AgentStateStore`。
- `state/HarnessAgentFactory.java` —— 按 `ReActAgentComponent` 子类构建并缓存无状态 `HarnessAgent` 单例。
- `state/NoOpAgentStateStore.java` —— R1 兜底(若 v2 无原生 NoOp
- `permission/PermissionConfigMapper.java` —— `ShellConfig`→`PermissionContextState`。
- `middleware/LoggingMiddleware.java` —— 替代 `ReActLoggingHook`
- `middleware/ChatUsageMiddleware.java` —— 替代 `ChatUsageTrackingHook`,支撑 `ctx.getChatUsage()`
- `middleware/SkillTrackingMiddleware.java` —— 替代 `SkillTrackingHook`
- `middleware/HookMiddlewareAdapter.java` —— 把业务侧遗留 `Hook` 桥接为 middleware。
- `event/AgentEventBridge.java` —— `AgentEvent`→`FlowEvent`。
- `skill/SkillRepositoryResolver.java` —— `skills.*` 配置→`skillRepository`/`SkillFilter`。
- `config/AgentStateStoreProperties.java`、`config/FilesystemProperties.java`、`config/CompactionProperties.java`、`config/PlanModeProperties.java`、`config/SubagentProperties.java`、`config/SandboxProperties.java` —— 新增能力配置(或并入既有 property 类,见 Task
### 改造
- `agent/component/ReActAgentComponent.java` —— `process()`/`buildAgent()` 重写,新增 `middlewares()/permissionContext()/runtimeContext()``hooks()` 桥接 + `@Deprecated`
- `agent/component/ReActAgentContext.java` —— 增加 `getRuntimeContext()`;保留 `getChatUsage()`
- `property/agent/*`liteflow-core—— 语义重映射 + `@Deprecated` + 新增子配置。
- `liteflow-react-agent-*/src/.../<vendor>/*Spec.java` —— 按 spike 结果微调 `XxxChatModel.builder()` 调用R4
- `docs/liteflow-react-agent-guide.md` —— 迁移说明。
### 测试(`liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/`
- `v2/AgentStateStoreResolverTest.java`、`v2/PermissionConfigMapperTest.java`、`v2/AgentEventBridgeTest.java`(纯单元,不用 Spring
- `v2/`集成Spring Boot`StateIsolationTest`、`MemoryModeMappingTest`、`StreamingBridgeTest`、`CompactionTest`、`PlanModeTest`、`SubagentTest`、`InterruptTest`、`BackCompatSmokeTest`。
---
## Phase 0 — API Spike解析 R1/R4/R5/R6/R7
### Task 0: v2 API 探针与发现
**目的:** 在写任何实现前,用最小可编译探针对真实 v2 jar 验证不确定 API产出一份发现笔记后续 Task 消费。
**Files:**
- Create(临时): `liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2spike/V2ApiProbe.java`
- Create(交付物): `docs/superpowers/specs/v2-api-findings.md`
**Interfaces:**
- Produces: `docs/superpowers/specs/v2-api-findings.md`,记录以下 5 项的**确认签名**(类全名 + 方法签名 + 行为),后续 Task 须引用此处结论。
- [ ] **Step 1: 依赖先行Task 1.1 提前到这里做最小版本)**
编辑 `/Users/bryan31/openSource/LiteFlow-Jdk17/pom.xml`,把 `<agentscope.version>1.0.12</agentscope.version>` 改为已发布最新 `2.0.x`(查 Maven Central 取最新 patch。仅改版本号先不删旧 session 包(编译会大面积失败,本 Task 只跑一个隔离探针)。
- [ ] **Step 2: 写探针测试,逐项验证**
```java
package com.yomahub.liteflow.test.agent.v2spike;
import io.agentscope.core.HarnessAgent; // 验证类全名
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.state.AgentStateStore;
import io.agentscope.core.state.InMemoryAgentStateStore;
import io.agentscope.core.state.JsonFileAgentStateStore;
import io.agentscope.core.middleware.MiddlewareBase; // 验证包路径
import io.agentscope.core.event.AgentEvent; // 验证事件包
import io.agentscope.core.message.UserMessage;
import org.junit.jupiter.api.Test;
class V2ApiProbe {
@Test void r1_noneStore_onHarness() throws Exception {
// R1: HarnessAgent 默认 stateStore 是什么?如何得到 NONE不持久化
// 探查 HarnessAgent.Builder 是否有 .disableStatePersistence() / 默认值;
// 是否存在 NoOpAgentStateStore / NullAgentStateStore
// 不设 stateStore 时 build() 是否报错。
}
@Test void r4_vendorChatModelBuilders() throws Exception {
// R4: OpenAIChatModel/DashScopeChatModel/AnthropicChatModel/GeminiChatModel
// 的 builder 在 v2 的方法名apiKey/modelName/baseUrl/stream/generateOptions/formatter是否与 1.0 一致。
}
@Test void r5_chatUsage_retrieval() throws Exception {
// R5: v2 message 上 ChatUsage 的 getterMiddlewareBase 在模型调用后的回调签名
// onModelCall 入参类型,如何拿到本次 message 与 usage
}
@Test void r6_permissionRule_shape() throws Exception {
// R6: PermissionContextState / PermissionRule / PermissionMode 的构造方式;
// 能否表达"命令名 ∈ 白名单才允许 execute"这种细粒度规则。
}
@Test void r7_java_baseline() {
// R7: 跑 mvn -pl liteflow-react-agent/liteflow-react-agent-core compile确认 JDK 21 可编译 v2。
}
}
```
注:每个方法体用反射或直接调用探查目标 API`HarnessAgent.builder().name("x").sysPrompt("y").model(stubModel).build()` 看默认 stateStore`assertions` 或打印记录确认结果。
- [ ] **Step 3: 跑探针**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=V2ApiProbe -DfailIfNoTests=false test 2>&1 | tail -60`
(编译会因旧 session 包失败——见 Step 4 临时处置。)
- [ ] **Step 4: 临时让 core 编译通过以运行探针**
为让探针能跑,临时把 `liteflow-react-agent-core` 里所有 import 了已删 `io.agentscope.core.session.*` 的类**整文件注释/临时移除**(仅本 Task 临时手段Task 2 会正式删除)。最省事做法:把 `agent/session/**``agent/skill/SkillBoxFactory.java`、`agent/tool/**`、`agent/hook/**` 临时移到 core 外的 `_tmp_stash/`,并临时简化 `ReActAgentComponent`(注释掉 buildAgent 里用到它们的段落),使 core 编译通过。
- [ ] **Step 5: 把确认结果写入发现笔记**
`docs/superpowers/specs/v2-api-findings.md` 记录 R1/R4/R5/R6/R7 的确认签名(类全名 + 方法签名 + 默认值 + 结论)。**后续每个 Task 凡用到这些 API须与笔记核对若笔记与计划代码冲突以笔记为准并回填修正计划。**
- [ ] **Step 6: 还原临时改动,仅保留 findings + 版本号 + 探针**
`_tmp_stash/` 移回(恢复 1.0 session 包,使仓库回到"仅版本号改了、探针+findings 新增"的可编译?不——版本号已升 v2session 包会编译失败)。处理:本 Task 结束时**保留版本号升级与探针/findings接受 core 暂不编译**,下一个 TaskTask 1/2正是修复编译。commit 信息注明"WIP: v2 探针core 暂不可编译"。
- [ ] **Step 7: Commit**
```bash
git add docs/superpowers/specs/v2-api-findings.md \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2spike/V2ApiProbe.java \
pom.xml
git commit -m "wip(agent): v2 API 探针与发现笔记 (R1/R4/R5/R6/R7)"
```
---
## Phase 1 — 依赖切换
### Task 1: 升级 agentscope 版本与扩展依赖
**Files:**
- Modify: `pom.xml`(根,`agentscope.version` + `dependencyManagement`
- Modify: `liteflow-react-agent/liteflow-react-agent-core/pom.xml`(按需加 extensions
**Interfaces:**
- Produces: 构建期可用 `io.agentscope.core.*`2.0.x与按需扩展。
- [ ] **Step 1: 根 pom 设定版本**
`pom.xml``<agentscope.version>` 改为 Task 0 确认的最新 `2.0.x`
- [ ] **Step 2: core pom 按需声明扩展provided/optional 优先,避免强耦合)**
`liteflow-react-agent-core/pom.xml` 新增(全部 `<optional>true</optional>`,反射/类路径探测式加载,与现有 Redis/MySQL factory 风格一致):
```xml
<dependency>
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-redis</artifactId>
<version>${agentscope.version}</version><optional>true</optional>
</dependency>
<dependency>
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-mysql</artifactId>
<version>${agentscope.version}</version><optional>true</optional>
</dependency>
<dependency>
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-oss</artifactId>
<version>${agentscope.version}</version><optional>true</optional>
</dependency>
<dependency>
<groupId>io.agentscope</groupId><artifactId>agentscope-extensions-skill-git-repository</artifactId>
<version>${agentscope.version}</version><optional>true</optional>
</dependency>
```
(具体 artifactId 以 Task 0 发现笔记为准OSS/扩展模块名若有出入,按笔记修正。)
- [ ] **Step 3: 校验依赖可解析**
Run: `mvn -pl liteflow-react-agent/liteflow-react-agent-core dependency:tree -Dincludes=io.agentscope 2>&1 | tail -40`
Expected: 列出 agentscope core + 所声明的扩展,版本均为 2.0.x。
- [ ] **Step 4: Commit**
```bash
git add pom.xml liteflow-react-agent/liteflow-react-agent-core/pom.xml
git commit -m "build(agent): 升级 agentscope 至 2.0.x 并声明扩展依赖"
```
---
## Phase 2 — 会话与状态层
### Task 2.1: AgentStateStoreResolvermode→store
**Files:**
- Create: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/AgentStateStoreResolver.java`
- Create(test): `liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/AgentStateStoreResolverTest.java`
**Interfaces:**
- Consumes: `MemoryStorageMode`、`AgentConfig``session.memory.*`、`workspace.root`、v2 `AgentStateStore` 及其实现(按 Task 0 findings、`ContextAwareHolder`(取 Redis/DataSource bean沿用现有反射风格
- Produces: `AgentStateStoreResolver.resolve(AgentConfig) -> AgentStateStore`NONE 返回 NoOp/不落盘 store见 R1 结论)。
- [ ] **Step 1: 写失败测试**
```java
package com.yomahub.liteflow.test.agent.v2;
import com.yomahub.liteflow.agent.state.AgentStateStoreResolver;
import com.yomahub.liteflow.property.agent.AgentConfig;
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
import io.agentscope.core.state.AgentStateStore;
import io.agentscope.core.state.InMemoryAgentStateStore;
import io.agentscope.core.state.JsonFileAgentStateStore;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
class AgentStateStoreResolverTest {
@TempDir Path tmp;
private AgentConfig cfg(MemoryStorageMode mode) {
AgentConfig c = new AgentConfig();
c.getWorkspace().setRoot(tmp.toString()); // 必填
c.getSession().getMemory().setMode(mode);
return c;
}
@Test void jvm_maps_to_inMemory() {
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.JVM));
assertTrue(s instanceof InMemoryAgentStateStore, s.getClass().getName());
}
@Test void localFile_maps_to_jsonFile() {
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.LOCAL_FILE));
assertTrue(s instanceof JsonFileAgentStateStore, s.getClass().getName());
}
@Test void none_maps_to_noOp() {
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.NONE));
// 不落盘save 是 no-opload 返回空 AgentState。具体断言依 R1 结论。
assertNotNull(s);
// e.g. assertTrue(s instanceof NoOpAgentStateStore);
}
}
```
REDIS/MYSQL 用例见 Task 2.1 Step 5需 mock bean放后。
- [ ] **Step 2: 跑测试确认失败**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=AgentStateStoreResolverTest test`
Expected: FAIL`AgentStateStoreResolver` 不存在)。
- [ ] **Step 3: 实现 resolverNONE/JVM/LOCAL_FILE 三档先通)**
```java
package com.yomahub.liteflow.agent.state;
import com.yomahub.liteflow.agent.exception.AgentConfigException;
import com.yomahub.liteflow.property.agent.AgentConfig;
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
import io.agentscope.core.state.AgentStateStore;
import io.agentscope.core.state.InMemoryAgentStateStore;
import io.agentscope.core.state.JsonFileAgentStateStore;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class AgentStateStoreResolver {
private AgentStateStoreResolver() {}
public static AgentStateStore resolve(AgentConfig cfg) {
MemoryStorageMode mode = cfg.getSession().getMemory().getMode();
switch (mode) {
case NONE: return NoOpAgentStateStore.INSTANCE; // R1 结论兜底
case JVM: return new InMemoryAgentStateStore();
case LOCAL_FILE: return new JsonFileAgentStateStore(localRoot(cfg));
case REDIS: return RedisStoreBuilder.build(cfg); // Task 2.1 Step 5
case MYSQL: return MysqlStoreBuilder.build(cfg); // Task 2.1 Step 5
case OSS: return OssStoreBuilder.build(cfg); // Task 2.1 Step 5
default: throw new AgentConfigException("Unsupported memory mode: " + mode);
}
}
private static Path localRoot(AgentConfig cfg) {
Path root = Paths.get(cfg.getWorkspace().getRoot()).toAbsolutePath().normalize()
.resolve(cfg.getSession().getMemory().getLocalFile().getSubDir());
return root;
}
}
```
`NoOpAgentStateStore`、`RedisStoreBuilder`、`MysqlStoreBuilder`、`OssStoreBuilder` 在后续 Step 创建NONE/JVM/LOCAL_FILE 本步即可让前三个测试通过。)
- [ ] **Step 4: 跑测试确认通过(前三例)**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=AgentStateStoreResolverTest test`
Expected: 前三例 PASS。
- [ ] **Step 5: NoOp + Redis + MySQL + OSS builder**
`NoOpAgentStateStore.java`:依 R1 结论——若 v2 提供原生 NoOp/Null store`NoOpAgentStateStore` 直接 `= 原生类`(删除自写);否则实现一个最小 `AgentStateStore``save(...)` 空实现,`load(...)` 返回新建空 `AgentState``delete(...)`/`list(...)` 空实现。
`RedisStoreBuilder.build(cfg)`:复刻旧 `RedisAgentSessionFactory` 的反射逻辑——按 `redis.beanName/clientType(REDISSON|JEDIS|LETTUCE)``ContextAwareHolder` 取 bean构造 v2 `RedisAgentStateStore`(具体构造方法/Builder 形态依 R1/Task 0 笔记)。
`MysqlStoreBuilder.build(cfg)`:按 `mysql.dataSourceBeanName/databaseName/tableName``DataSource`,构造 v2 `MysqlAgentStateStore`
`OssStoreBuilder.build(cfg)`:新增;按 `oss.*` 子配置Task 8 新增 property构造 `OssAgentStateStore`
为 Redis/MySQL 各补一个 mock 测试mock bean 返回 mock 客户端,断言返回的 store 类型),加入 `AgentStateStoreResolverTest`
- [ ] **Step 6: 跑全部 resolver 测试**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=AgentStateStoreResolverTest test`
Expected: 全部 PASS。
- [ ] **Step 7: Commit**
```bash
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/ \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/AgentStateStoreResolverTest.java
git commit -m "feat(agent): AgentStateStoreResolver 把 MemoryStorageMode 映射到 v2 AgentStateStore"
```
---
### Task 2.2: HarnessAgentFactory单例无状态 agent 构建)
**Files:**
- Create: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/HarnessAgentFactory.java`
**Interfaces:**
- Consumes: `ReActAgentComponent`(读 `model()/systemPrompt()/tools()/middlewares()/...`)、`AgentConfig`、`AgentStateStoreResolver`、`PermissionConfigMapper`Task 3.1)、`SkillRepositoryResolver`Task 4.1)、`MiddlewareBase` 列表。
- Produces: `HarnessAgentFactory.getOrCreate(ReActAgentComponent, AgentConfig) -> HarnessAgent`(按组件子类 Class 缓存单例)。
- [ ] **Step 1: 写失败测试**
```java
package com.yomahub.liteflow.test.agent.v2;
import com.yomahub.liteflow.agent.state.HarnessAgentFactory;
import com.yomahub.liteflow.agent.component.ReActAgentComponent;
import com.yomahub.liteflow.agent.model.ModelSpec;
import com.yomahub.liteflow.property.agent.AgentConfig;
import io.agentscope.core.HarnessAgent;
import io.agentscope.core.model.Model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class HarnessAgentFactoryTest {
static class StubCmp extends ReActAgentComponent {
@Override protected ModelSpec<?> model() { return cfg -> stubModel(); }
@Override protected String systemPrompt() { return "x"; }
@Override protected String userPrompt() { return "y"; }
}
static Model stubModel() { /* 返回一个 mock Model避免真实网络用 mockito */ return null; }
@Test void sameComponentClass_returnsSameSingleton() {
AgentConfig cfg = HarnessFixture.minimalConfig();
HarnessAgent a1 = HarnessAgentFactory.getOrCreate(new StubCmp(), cfg);
HarnessAgent a2 = HarnessAgentFactory.getOrCreate(new StubCmp(), cfg);
assertSame(a1, a2, "按组件子类缓存单例");
}
}
```
`HarnessFixture` 提供最小可构建 `AgentConfig`,放进 `v2/HarnessFixture.java`。)
- [ ] **Step 2: 跑测试确认失败**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=HarnessAgentFactoryTest test`
Expected: FAIL类不存在
- [ ] **Step 3: 实现 HarnessAgentFactory**
```java
package com.yomahub.liteflow.agent.state;
import com.yomahub.liteflow.agent.component.ReActAgentComponent;
import com.yomahub.liteflow.agent.middleware.HookMiddlewareAdapter;
import com.yomahub.liteflow.agent.model.ModelSpec;
import com.yomahub.liteflow.agent.permission.PermissionConfigMapper;
import com.yomahub.liteflow.agent.skill.SkillRepositoryResolver;
import com.yomahub.liteflow.property.agent.AgentConfig;
import io.agentscope.core.HarnessAgent;
import io.agentscope.core.middleware.MiddlewareBase;
import io.agentscope.core.model.Model;
import io.agentscope.core.state.AgentStateStore;
import io.agentscope.core.tool.Toolkit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public final class HarnessAgentFactory {
private static final ConcurrentHashMap<Class<?>, HarnessAgent> CACHE = new ConcurrentHashMap<>();
public static HarnessAgent getOrCreate(ReActAgentComponent cmp, AgentConfig cfg) {
return CACHE.computeIfAbsent(cmp.getClass(), k -> build(cmp, cfg));
}
private static HarnessAgent build(ReActAgentComponent cmp, AgentConfig cfg) {
Toolkit toolkit = new Toolkit();
cmp.tools().forEach(toolkit::registerTool);
List<MiddlewareBase> mws = new ArrayList<>(cmp.middlewares());
if (cmp.enableReActLogging()) mws.add(new com.yomahub.liteflow.agent.middleware.LoggingMiddleware());
mws.add(new com.yomahub.liteflow.agent.middleware.ChatUsageMiddleware());
// 业务侧遗留 Hook 桥接
cmp.hooks().forEach(h -> mws.add(HookMiddlewareAdapter.wrap(h)));
Model model = cmp.buildModel(); // ModelSpec.resolve(agentConfig())
HarnessAgent.Builder b = HarnessAgent.builder()
.name(cmp.getNodeId() == null ? "liteflow-agent" : cmp.getNodeId())
.sysPrompt(cmp.effectiveSystemPrompt())
.model(model)
.toolkit(toolkit)
.middlewares(mws)
.maxIters(cmp.maxIterations() > 0 ? cmp.maxIterations() : cfg.getDefaults().getMaxIterations())
.stateStore(AgentStateStoreResolver.resolve(cfg))
.permissionContext(PermissionConfigMapper.map(cfg));
// 文件系统 / skill / 增强能力Task 3/4/7 各自接入)
HarnessFilesystemConfigurer.configure(b, cmp, cfg); // Task 3.2
SkillRepositoryResolver.configure(b, cmp, cfg); // Task 4.1
HarnessCapabilityConfigurer.configure(b, cmp, cfg); // Task 7
return b.build();
}
/** 仅供测试重置缓存。 */
static void resetForTesting() { CACHE.clear(); }
}
```
`HarnessFilesystemConfigurer`/`SkillRepositoryResolver`/`HarnessCapabilityConfigurer` 在后续 Task 创建;本 Task 先各建一个空 `static configure(...)` 占位,使本 Task 编译通过,后续 Task 填充。)
- [ ] **Step 4: 跑测试确认通过**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=HarnessAgentFactoryTest test`
Expected: PASS。
- [ ] **Step 5: Commit**
```bash
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/state/HarnessAgentFactory.java \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/HarnessAgentFactoryTest.java \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/HarnessFixture.java
git commit -m "feat(agent): HarnessAgentFactory 构建并按组件子类缓存无状态 HarnessAgent 单例"
```
---
### Task 2.3: 重写 ReActAgentComponent.process()
**Files:**
- Modify: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentComponent.java`
- Modify: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/component/ReActAgentContext.java`
**Interfaces:**
- Consumes: `HarnessAgentFactory`、`AgentEventBridge`Task 6.1,本 Task 先用占位静态方法)、`RuntimeContext`、`UserMessage`。
- Produces: `process()` 走 v2 单例 agent + RuntimeContext新增 `middlewares()`、`runtimeContext()`、`permissionContext()``hooks()` 桥接 + `@Deprecated``ctx.getRuntimeContext()`。
- [ ] **Step 1: 写失败测试(隔离 + 共享语义)**
`v2/StateIsolationTest.java`Spring Boot 集成,用 mock 模型):
- 两个不同 `agentKey`(两个组件)同 `conversationId`:各自 `responseData` 独立;但两者 workspace 内能读写到同一文件(共享 workspace
- 同 `(conversationId, agentKey)` 两次调用:第二次能"记住"第一次AgentState 恢复)——用 JVM store + mock 模型回放验证。
(具体断言形式参考既有 `feature/multiturn/MultiTurnMemoryTest.java` 的 mock 模型回放范式。)
- [ ] **Step 2: 跑测试确认失败**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=StateIsolationTest test`
Expected: FAIL旧 session 包已删 / process 仍旧逻辑)。
- [ ] **Step 3: 重写 process()(替换 §spec 4.3 骨架为真实代码)**
`ReActAgentComponent.process()`、`callAgent()`、`streamAgent()`、`buildAgent()`、`AgentSessionManagerHolder` 整体替换:
```java
@Override
public final void process() throws Exception {
AgentConfig cfg = agentConfig();
HarnessAgent agent = HarnessAgentFactory.getOrCreate(this, cfg);
String cid = resolveConversationId();
Slot slot = getSlot();
slot.setConversationId(cid);
String akey = agentKey();
RuntimeContext rc = runtimeContext(cid, akey);
ReActAgentContext ctx = new ReActAgentContext(slot, cid, akey, workspaceRoot(cfg, cid));
ctx.setRuntimeContext(rc);
slot.setAttachment(ctxKey(), ctx);
try {
ChatUsageMiddleware.bind(ctx); // 让 ctx.getChatUsage() 生效
Msg reply = callAgent(agent, new UserMessage(userPrompt()), rc, slot);
handleReply(reply);
} finally {
ChatUsageMiddleware.unbind();
slot.removeAttachment(ctxKey());
}
}
protected RuntimeContext runtimeContext(String conversationId, String agentKey) {
return RuntimeContext.builder().userId(conversationId).sessionId(agentKey).build();
}
private Msg callAgent(HarnessAgent agent, Msg userMsg, RuntimeContext rc, Slot slot) {
if (!FlowEventPublisher.hasListener(slot)) {
return agent.call(List.of(userMsg), rc).block();
}
return AgentEventBridge.streamAndPublish(agent, userMsg, rc, slot).block();
}
/** 业务侧可用 middleware 取代 hooks()。默认空。 */
protected List<MiddlewareBase> middlewares() { return List.of(); }
/** 旧 hooks() 保留兼容,内部经 HookMiddlewareAdapter 桥接。 */
@Deprecated(forRemoval = true, since = "2.16.0")
protected List<Hook> hooks() { return List.of(); }
```
移除:`buildAgent()`、`streamAgent()`、`AgentSessionManagerHolder`、对 `AgentSessionManager`/`AgentSession` 的全部引用、`skillHookKey()` 相关 skill-tracking-by-hook 逻辑(改由 middlewareTask 5
`workspaceRoot(cfg, cid)`:返回 HarnessAgent workspace 根(不再每会话一目录),具体在 Task 3.2 文件系统配置里定义;本 Task 先返回 `Paths.get(cfg.getWorkspace().getRoot())`
`ReActAgentContext`:新增 `RuntimeContext runtimeContext` 字段 + getter/setter`getChatUsage()` 改为从 `ChatUsageMiddleware` 当前绑定 ctx 取累计值。
- [ ] **Step 4: 跑测试确认通过**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent -Dtest=StateIsolationTest test`
Expected: PASS。
- [ ] **Step 5: 删除旧 session 包**
```bash
git rm liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSessionManager.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSession.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/AgentSessionFactory.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/AgentSessionFactoryRegistry.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/InMemoryAgentSessionFactory.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/LocalFileAgentSessionFactory.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/RedisAgentSessionFactory.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/MysqlAgentSessionFactory.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/factory/NoneAgentSessionFactory.java
```
- [ ] **Step 6: 编译 react-agent 全模块**
Run: `mvn -pl liteflow-react-agent/liteflow-react-agent-core -am compile 2>&1 | tail -30`
Expected: 编译通过(无 `io.agentscope.core.session.*` 引用残留)。
- [ ] **Step 7: Commit**
```bash
git add -A liteflow-react-agent/liteflow-react-agent-core \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/StateIsolationTest.java
git commit -m "refactor(agent): process() 改走 HarnessAgent 单例+RuntimeContext删除旧 session 包"
```
---
## Phase 3 — 工具与权限
### Task 3.1: PermissionConfigMapperShellConfig→Permission
**Files:**
- Create: `liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/permission/PermissionConfigMapper.java`
- Create(test): `v2/PermissionConfigMapperTest.java`
**Interfaces:**
- Consumes: `ShellConfig`mode/whitelist/blacklist、R6 结论(`PermissionContextState`/`PermissionRule`/`PermissionMode` 构造)。
- Produces: `PermissionConfigMapper.map(AgentConfig) -> PermissionContextState`
- [ ] **Step 1: 写失败测试**
```java
@Test void disabled_mode_deniesExecute() { /* shell.mode=DISABLED → execute 被拒 */ }
@Test void whitelist_allowsListedCommand() { /* WHITELIST → 仅白名单命令允许 */ }
@Test void blacklist_deniesListedCommand() { /* BLACKLIST → 黑名单命令被拒 */ }
```
(具体 `PermissionContextState` 断言依 R6 笔记。)
- [ ] **Step 2: 跑确认失败**`mvn ... -Dtest=PermissionConfigMapperTest test` → FAIL。
- [ ] **Step 3: 实现**(依 R6 笔记构造 `PermissionContextState`DISABLED→对 `execute` 工具整体拒绝WHITELIST/BLACKLIST→命令级 `PermissionRule`,若 v2 原生不支持命令级,则在此 mapper 内做命令级裁决的轻量包装并记入 findings
- [ ] **Step 4: 跑通过** — 同上 → PASS。
- [ ] **Step 5: Commit**
```bash
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/permission/PermissionConfigMapper.java \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/PermissionConfigMapperTest.java
git commit -m "feat(agent): PermissionConfigMapper 把 ShellConfig 映射到 v2 PermissionContextState"
```
---
### Task 3.2: 删除自建工具,接 HarnessAgent 文件系统 + execute
**Files:**
- Delete: `agent/tool/WorkspaceFileTools.java`、`agent/tool/ManagedShellCommandTool.java`
- Create: `agent/state/HarnessFilesystemConfigurer.java`
- Modify: property `WorkspaceConfig` / `ShellConfig`(新增 `filesystemMode` 字段,默认 LOCAL保留老键
**Interfaces:**
- Consumes: `FilesystemSpec``LocalFilesystemSpec`/`DockerFilesystemSpec`/`RemoteFilesystemSpec`)、`filesystem.mode`。
- Produces: `HarnessFilesystemConfigurer.configure(HarnessAgent.Builder, cmp, cfg)`——按 mode 设 `.filesystem(...)``enableShellTool()`/`enableWorkspaceFileTools()` 转为对应工具组/`disable*` 开关。
- [ ] **Step 1: 写失败测试**`v2/FilesystemModeTest``filesystem.mode=LOCAL` 时 build 出的 agent 含 `execute`+文件工具;`DISABLED` shell 时不注册 `execute`
- [ ] **Step 2: 跑确认失败。**
- [ ] **Step 3: 实现 `HarnessFilesystemConfigurer`**
```java
static void configure(HarnessAgent.Builder b, ReActAgentComponent cmp, AgentConfig cfg) {
FilesystemMode mode = cfg.getWorkspace().getFilesystemMode(); // 新增字段,默认 LOCAL
switch (mode) {
case LOCAL -> { /* 默认HarnessAgent 自带 LocalFilesystemSpec + host shell按 enableShellTool() 决定是否禁 execute */ }
case SANDBOX_DOCKER -> b.filesystem(new DockerFilesystemSpec()); // 依 findings 调参
case SANDBOX_K8S, SANDBOX_E2B, SANDBOX_AGENTRUN -> { /* 依 findings */ }
case REMOTE -> b.filesystem(new RemoteFilesystemSpec(distributedStore(cfg)));
}
if (!cmp.enableShellTool()) b.disableShell(); // 方法名依 findings
if (!cmp.enableWorkspaceFileTools()) b.disableFileTools(); // 方法名依 findings
}
```
(具体 builder 方法名/`disable*` 形态依 Task 0 笔记修正。)
- [ ] **Step 4: property 新增** `WorkspaceConfig.filesystemMode`(枚举 `FilesystemMode`,默认 `LOCAL``@ConfigurationProperties` 自动绑定 `filesystem-mode`
- [ ] **Step 5: 删除自建工具类**
```bash
git rm liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/tool/WorkspaceFileTools.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/tool/ManagedShellCommandTool.java
```
- [ ] **Step 6: 跑测试 + 编译**`mvn ... -Dtest=FilesystemModeTest test` → PASS`mvn -pl liteflow-react-agent/liteflow-react-agent-core -am compile` → 通过。
- [ ] **Step 7: Commit**
```bash
git add -A
git commit -m "refactor(agent): 删除自建文件/shell 工具,采用 HarnessAgent 文件系统+execute+Permission"
```
---
## Phase 4 — Skill
### Task 4.1: SkillRepositoryResolver + 删除 SkillBoxFactory
**Files:**
- Delete: `agent/skill/SkillBoxFactory.java`、`SkillToolResolver.java`、`SkillLoadResult.java`、`SkillTrackingHook.java`
- Create: `agent/skill/SkillRepositoryResolver.java`
**Interfaces:**
- Consumes: `SkillsConfig`enabled/path/strict + 新增 repository 类型)、`cmp.skills()`allow-list、`cmp.enableSkills()`、v2 `skillRepository`/`SkillFilter`/`dynamicSkillsEnabled`。
- Produces: `SkillRepositoryResolver.configure(b, cmp, cfg)`——注册 `Classpath/Git/Nacos/MySQL` 仓库或走 workspace `skills/`,设 `skillFilter`
- [ ] **Step 1: 写失败测试**`v2/SkillRepositoryTest``skills.enabled=true` + `skills.path=<dir with SKILL.md>` 时 build 出的 agent 能解析到 skill`skills()` allow-list 生效过滤。
- [ ] **Step 2: 跑确认失败。**
- [ ] **Step 3: 实现 `SkillRepositoryResolver`**——默认把 `skills.path` 作为 workspace skills 目录HarnessAgent 原生扫描),可选 `.skillRepository(new ClasspathSkillRepository(...))` 等(依 findings。`enableSkills()==false` 时 `.dynamicSkillsEnabled(false)`。`skills()` 非空 → `.skillFilter(...)`
- [ ] **Step 4: 删除旧 skill 类**
```bash
git rm liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillBoxFactory.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillToolResolver.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillLoadResult.java \
liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillTrackingHook.java
```
- [ ] **Step 5: 跑测试 + 编译** → PASS / 通过。
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "refactor(agent): SkillBoxFactory→skillRepository/workspace skills删除旧 skill 类"
```
---
## Phase 5 — Hook → Middleware
### Task 5.1: LoggingMiddleware + ChatUsageMiddleware + SkillTrackingMiddleware + HookMiddlewareAdapter
**Files:**
- Create: `agent/middleware/LoggingMiddleware.java`、`ChatUsageMiddleware.java`、`SkillTrackingMiddleware.java`、`HookMiddlewareAdapter.java`
**Interfaces:**
- Consumes: R5 笔记(`MiddlewareBase` 各 stage 回调签名、`ChatUsage` 取值点)、`MiddlewareBase`、`Hook`(桥接源)。
- Produces: `ctx.getChatUsage()``ChatUsageMiddleware``onModelCall` 累加;`bind(ctx)/unbind()` 线程本地绑定。
- [ ] **Step 1: 写失败测试**
```java
// v2/ChatUsageMiddlewareTest
@Test void accumulates_across_steps() {
// 回放两次 model call各自带 ChatUsage断言 ctx.getChatUsage() 为两者之和
}
```
- [ ] **Step 2: 跑确认失败。**
- [ ] **Step 3: 实现**——`ChatUsageMiddleware``onModelCall` 后从 message 取 `ChatUsage`R5累加到与当前 `ReActAgentContext` 绑定的累加器(`bind/unbind` 用 ThreadLocal 或 slot attachment。`LoggingMiddleware``onReasoning`/`onActing` 打 reason/act/error替代 `ReActLoggingHook`)。`SkillTrackingMiddleware`:记录已用 skill替代 `SkillTrackingHook`)。`HookMiddlewareAdapter.wrap(Hook)`:把旧 `Hook.onEvent` 转成对应 stage 调用(依 findings 的 `LegacyHookDispatcher` 是否可直接复用)。
- [ ] **Step 4: 跑测试** → PASS。
- [ ] **Step 5: Commit**
```bash
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/middleware/ \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/ChatUsageMiddlewareTest.java
git commit -m "feat(agent): Hook→MiddlewareLogging/ChatUsage/SkillTracking+ Hook 桥接适配器"
```
---
## Phase 6 — 流式事件
### Task 6.1: AgentEventBridgeAgentEvent→FlowEvent
**Files:**
- Create: `agent/event/AgentEventBridge.java`
- Create(test): `v2/AgentEventBridgeTest.java`(纯单元)
**Interfaces:**
- Consumes: v2 `AgentEvent`/`AgentEventType`(依 findings 的具体事件类型常量)、`FlowEvent`/`FlowEventPublisher`。
- Produces: `AgentEventBridge.streamAndPublish(HarnessAgent, Msg, RuntimeContext, Slot) -> Mono<Msg>`;映射 4 类对外 type + 可选 `agent.hitl.*`
- [ ] **Step 1: 写失败测试**
```java
@Test void reasoningDelta_mapsTo_agentReasoning() { /* TextBlockDeltaEvent → "agent.reasoning", last=false */ }
@Test void agentResult_mapsTo_agentResult_lastTrue() { /* AgentResultEvent → "agent.result", last=true, 返回该 Msg */ }
@Test void hitlEvent_mapsTo_agentHitl() { /* RequireUserConfirmEvent → "agent.hitl.confirm" */ }
```
- [ ] **Step 2: 跑确认失败。**
- [ ] **Step 3: 实现**
```java
public static Mono<Msg> streamAndPublish(HarnessAgent agent, Msg userMsg, RuntimeContext rc, Slot slot) {
AtomicReference<Msg> result = new AtomicReference<>();
return agent.streamEvents(List.of(userMsg), rc)
.doOnNext(ev -> {
FlowEventMapping m = map(ev);
if (m == null) return;
if (m.isResult()) result.set(m.msg());
FlowEventPublisher.publish(slot, FlowEvent.builder()
.type(m.type()).chainId(slot.getChainId()).nodeId(/* cmp nodeId */)
.requestId(slot.getRequestId()).conversationId(slot.getConversationId())
.text(m.text()).last(m.last()).data(ev).build());
})
.then(Mono.defer(() -> result.get() == null ? Mono.empty() : Mono.just(result.get())));
}
```
`map(AgentEvent)` 依 findings 把 `AgentEventType` 常量映射到 4 类 type+HITL
- [ ] **Step 4: 跑测试** → PASS。
- [ ] **Step 5: 集成测试**`v2/StreamingBridgeTest`Spring Boot + mock 模型流式回放):订阅 FlowEvent断言收到 `agent.reasoning` 增量与末尾 `agent.result(last=true)`
- [ ] **Step 6: Commit**
```bash
git add liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/event/AgentEventBridge.java \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/AgentEventBridgeTest.java \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/StreamingBridgeTest.java
git commit -m "feat(agent): AgentEventBridge 把 v2 AgentEvent 映射到 FlowEvent (streamEvents)"
```
---
## Phase 7 — 增强能力接入
### Task 7.1: HarnessCapabilityConfigurercompaction / memory / toolResultEviction / planMode / taskList / subagent / sandbox / interrupt
**Files:**
- Create: `agent/state/HarnessCapabilityConfigurer.java`
- Create: property `CompactionProperties`/`PlanModeProperties`/`TaskListProperties`/`SubagentProperties`(或并入 `AgentConfig`
- Create: `ReActAgentComponent.interrupt(String conversationId, String agentKey)` 方法
- Create(test): `v2/CompactionTest`、`v2/PlanModeTest`、`v2/SubagentTest`、`v2/InterruptTest`
**Interfaces:**
- Consumes: 新增配置开关、`HarnessAgent.Builder` 的 `.compaction()/.memory()/.toolResultEviction()/.enablePlanMode()/.enableTaskList()/.subagent()`、`agent.interrupt(userId,sessionId)`。
- Produces: 按配置在 `HarnessAgentFactory.build()` 阶段挂载能力;`interrupt()` 透传到 agent。
- [ ] **Step 1: 写失败测试**(每个能力一个最小集成用例)
- `CompactionTest`:长对话超过阈值 → 触发压缩(断言 AgentState.summary 非空)。
- `PlanModeTest``planMode.enabled=true` → agent 先进只读计划阶段,`plan_exit` 后才获写权限。
- `SubagentTest`:声明一个子 agent → 主 agent 可委派(断言子 agent reply 出现)。
- `InterruptTest`in-flight 调用 `interrupt(conv,key)` → 该 session call 以 `INTERRUPTED` 结束、AgentState 已存。
- [ ] **Step 2: 跑确认失败。**
- [ ] **Step 3: 实现 `HarnessCapabilityConfigurer.configure(b, cmp, cfg)`**
```java
if (cfg.getCompaction().isEnabled())
b.compaction(CompactionConfig.builder().model(cfg.getCompaction().getModel()).build());
// .memory(MemoryConfig) 默认开,可按 cfg 覆盖 prompts/model
if (cfg.getPlanMode().isEnabled()) b.enablePlanMode();
if (cfg.getTaskList().isEnabled()) b.enableTaskList(true);
cfg.getSubagents().forEach(sd -> b.subagent(sd));
```
(具体 `CompactionConfig`/`MemoryConfig`/subagent 声明构造依 findings。
- [ ] **Step 4: 实现 interrupt**
```java
// ReActAgentComponent
public final GenerateResult interrupt(String conversationId, String agentKey) {
HarnessAgent agent = HarnessAgentFactory.getOrCreate(this, agentConfig());
return agent.interrupt(conversationId, agentKey); // 返回类型依 findings
}
```
- [ ] **Step 5: property 类**`AgentConfig` 新增 `compaction/planMode/taskList/subagents` 子配置(`@ConfigurationProperties` 自动绑定 `compaction.*` 等)。
- [ ] **Step 6: 跑测试** → 全 PASS。
- [ ] **Step 7: Commit**
```bash
git add -A
git commit -m "feat(agent): 接入 HarnessAgent compaction/memory/planMode/taskList/subagent/interrupt"
```
---
## Phase 8 — 配置映射收口 + 废弃告警 + 文档
### Task 8.1: @Deprecated 标注 + 启动告警
**Files:**
- Modify: `property/agent/SessionConfig.java``maxSessions/idleTimeout/cleanupInterval` `@Deprecated`
- Modify: `property/agent/MemoryStorageConfig.java``loadOnFirstUse/saveAfterCall/saveOnError` `@Deprecated`
- Create: `agent/config/AgentConfigDeprecationLogger.java`(启动时对使用了已废弃键的配置打 WARN
- [ ] **Step 1: 写失败测试**`v2/DeprecationWarningTest`:配置含 `session.maxSessions` 时启动日志含 WARN 指引。
- [ ] **Step 2: 跑确认失败。**
- [ ] **Step 3: 实现** — 给字段加 `@Deprecated(since="2.16.0", forRemoval=true)` + JavaDoc 指引替代;`AgentConfigDeprecationLogger` 在 agent 模块初始化时扫描配置并打 WARN。
- [ ] **Step 4: 跑测试** → PASS。
- [ ] **Step 5: Commit**
```bash
git add -A
git commit -m "chore(agent): 标注被 v2 取代的配置键 @Deprecated 并启动告警"
```
---
### Task 8.2: 更新使用指南 + 源码兼容冒烟
**Files:**
- Modify: `docs/liteflow-react-agent-guide.md`
- Create(test): `v2/BackCompatSmokeTest.java`
- [ ] **Step 1: 写源码兼容冒烟测试** — 取 guide §2.3 的 `DeepSeekAgentCmp`(继承 `ReActAgentComponent`,覆写 `model()/systemPrompt()/userPrompt()`**原样不动**放进 `v2/BackCompatSmokeTest`,用 mock 模型跑通一次 `execute2Resp`,断言成功。这把"业务子类零改动"做成回归测试。
- [ ] **Step 2: 跑确认通过**(若失败,说明对外签名被破坏,回查 Task 2.3/5
- [ ] **Step 3: 更新 guide** — 增加"从 1.0 升级"章节配置键映射表spec §7、workspace 布局/清理语义变化、hooks()→middlewares() 迁移、被删自建工具类的影响、R1R7 的行为说明。
- [ ] **Step 4: Commit**
```bash
git add docs/liteflow-react-agent-guide.md \
liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/v2/BackCompatSmokeTest.java
git commit -m "docs(agent): 更新 react-agent 指南并新增 1.0→2.0 升级说明 + 兼容冒烟测试"
```
---
## Phase 9 — 全量回归
### Task 9: 全量构建 + 既有测试回归
- [ ] **Step 1: 单模块测试**
Run: `mvn -pl liteflow-testcase-el/liteflow-testcase-el-react-agent test 2>&1 | tail -40`
Expected: 全 PASS含既有平台/特性测试,可能需按 v2 调整 mock 模型回放——若既有测试因 v2 API 变化失败,按 findings 修正测试夹具,**不改 skipTests**)。
- [ ] **Step 2: react-agent 全模块编译 + 打包**
Run: `mvn -pl liteflow-react-agent/liteflow-react-agent-core,liteflow-react-agent/liteflow-react-agent-openai,liteflow-react-agent/liteflow-react-agent-anthropic,liteflow-react-agent/liteflow-react-agent-gemini,liteflow-react-agent/liteflow-react-agent-dashscope -am clean package -DskipTests 2>&1 | tail -30`
Expected: BUILD SUCCESS。
- [ ] **Step 3: vendor Spec 微调核对R4** — 逐个 vendor `XxxSpec.resolve()` 与 findings 笔记核对 `XxxChatModel.builder()` 调用,必要时修正。
- [ ] **Step 4: 全量回归JDK 21**
Run: `mvn -P compile-17+ clean test 2>&1 | tail -40`
Expected: BUILD SUCCESSreact-agent 相关全绿;其余模块不受影响)。
- [ ] **Step 5: Commit若有测试夹具修正**
```bash
git add -A
git commit -m "test(agent): v2 迁移全量回归(含既有测试夹具按 v2 调整)"
```
---
## Self-Review计划自检
**1. Spec 覆盖**spec §3 架构地图→Task 2.x/3.x/4.x/5/6§4 会话/状态→Task 2.12.3§5 workspace/工具→Task 3.x§6 skill→Task 4.1§7 配置映射→Task 2.1/3.2/7.1/8.1§8 Hook→Middleware→Task 5.1§9 模型层→Task 9 Step 3R4§10 流式→Task 6.1§11 新增能力→Task 7.1§12 API 兼容→Task 2.3/8.2§13 破坏性变更→各删除步骤§14 九阶段→Phase 09§15 测试→各 Task 测试 + Phase 9§16 风险 R1R7→Task 0 spike 逐项消化。无遗漏。
**2. 占位符扫描**:凡涉及 R1/R4/R5/R6 不确定 API 处,均以"依 Task 0 findings 修正/核对"显式标注,并配 spike 任务先行——非空洞占位。`HarnessFilesystemConfigurer`/`SkillRepositoryResolver`/`HarnessCapabilityConfigurer` 在 Task 2.2 先建空 `configure(...)` 占位以保编译,所属 Task 内填充,已在各自步骤说明。
**3. 类型一致性**`AgentStateStoreResolver.resolve(AgentConfig)->AgentStateStore`、`HarnessAgentFactory.getOrCreate(ReActAgentComponent,AgentConfig)->HarnessAgent`、`PermissionConfigMapper.map(AgentConfig)->PermissionContextState`、`AgentEventBridge.streamAndPublish(HarnessAgent,Msg,RuntimeContext,Slot)->Mono<Msg>`、`runtimeContext(String,String)->RuntimeContext`、`middlewares()->List<MiddlewareBase>`、`hooks()->List<Hook>` 在各 Task 间签名一致。`ctx.getRuntimeContext()`/`getChatUsage()` 跨 Task 2.3 与 5.1 一致。
**已知前置依赖**Task 0spike/findings必须最先Task 1依赖紧随Task 2.1→2.2→2.3 顺序依赖Task 3.1/4.1/5.1/6.1 可在 2.3 后并行Task 7.1 依赖 3.1/4.1Task 8/9 收尾。