harness - 会话与心智记忆
会话(Session)保存一次对话的上下文,心智记忆(Memory)则在多次对话之间长期留存关键事实。两者配合,让 Agent 既有"短期记忆"也有"长期记忆"。
1、会话提供者(sessionProvider)
sessionProvider 是构建引擎的必填项,决定会话如何创建与存储。
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(InMemoryAgentSession::of) // 内存会话(进程级,重启即失)
.build();
AgentSessionProvider 是一个函数式接口(@FunctionalInterface),唯一的抽象方法是 AgentSession getSession(String instanceId),因此可以直接使用方法引用或 Lambda:
// 方法引用
.sessionProvider(MySessionProvider::new)
// Lambda(每次返回新会话)
.sessionProvider(sessionId -> new InMemoryAgentSession(sessionId))
获取/使用会话:
AgentSession session = engine.getSession("default"); // 按实例 id 获取
engine.prompt("hello")
.session(session) // 不传则为临时会话(不留历史)
.call();
内置会话实现:
| 实现 | 说明 |
|---|---|
InMemoryAgentSession | 基于内存的会话(进程级别,重启即失),提供多个静态工厂:of()、of(sessionId)、of(sessionId, maxMessages)、of(FlowContext) |
FileAgentSession | 基于文件的会话(带内存缓存层),消息以 NDJSON 格式持久化到磁盘,快照以 JSON 持久化,适用于需要重启恢复的场景 |
RedisAgentSession | 基于 Redis 的会话,适用于分布式多实例共享会话的场景 |
需要自定义持久化方案时,实现 AgentSessionProvider 接口(函数式),把会话落到文件或数据库即可。
会话的运行态数据包括消息历史和执行流快照。使用 FileAgentSession 时消息默认落在 {harnessHome}/sessions/ 下(由 getHarnessSessions() 路径决定),每条会话独立存储为 {sessionId}.messages.ndjson 和 {sessionId}.snapshot.json。
会话快照(Snapshot):
AgentSession 接口扩展了会话快照能力:
// 同步/更新执行快照(Flow 引擎状态)
void updateSnapshot();
// 获取会话上下文(FlowContext)
FlowContext getContext();
// 获取当前状态快照
default FlowContext getSnapshot() {
return getContext();
}
快照机制使 Agent 在执行流(Flow)被中断后可恢复现场,配合 FileAgentSession 等持久化实现可在进程重启后恢复会话。
内置会话提供者快捷工厂:
可直接用 AgentSessionProvider 接口的函数式特性快速构造:
// 文件会话提供者(持久化到 {harnessHome}/sessions/)
AgentSessionProvider fileSessionProvider = sessionId ->
new FileAgentSession(sessionId, engine.getHarnessSessions());
2、会话窗口与上下文压缩
为控制上下文长度,引擎提供两层机制(详见 《harness - 配置参考》):
sessionWindowSize:新指令携带几条历史消息(默认 8)。compressionMaxMessages:消息条数超阈值时触发压缩(默认 40 条)。compressionMaxContextRatio:上下文总长度占比超阈值时触发压缩(默认 0.75,即上下文占用模型窗口 75% 时触发)。compressionModel:可选,指定专用的压缩模型(不配置则使用当前对话模型)。compressionInterceptor:可选,自定义压缩拦截器,覆盖默认的上下文摘要策略。
配置方式:
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(InMemoryAgentSession::of)
.sessionWindowSize(8)
.compressionThreshold(40, 0.75)
.compressionModel("deepseek-v3")
.build();
也可在运行时动态调整:
engine.setSessionWindowSize(12);
3、心智记忆(Memory)
心智记忆让 Agent 把用户偏好、项目规约等关键事实长期保存,并在需要时检索召回。需满足两个条件:memoryEnabled=true(默认开启)且已通过 memoryProvider(...) 配置记忆方案。
心智记忆能力由内置 MemoryTalent 提供,对应工具权限名为 memory(但实际暴露多个工具:memory_extract、memory_recall、memory_search、memory_consolidate、memory_prune),需在工具权限配置中确保已授权。
3.1 架构分层
MemorySolutionProvider (Provider 接口: 按工作区返回方案)
└─ MemorySolution (组合接口)
├─ MemoryStorer (物理持久化 + TTL 管理)
└─ MemorySearcher (语义检索 + 热记忆提取)
-
MemorySolutionProvider:工厂/提供者接口,根据运行上下文(如工作目录__cwd)返回对应的MemorySolution实例。同时提供getScopesDefault()(默认 "workspace")和getScopesDescription()方法,供 MemoryTalent 动态展示作用域说明。 -
MemorySolution:组合了存储与搜索能力,构成完整的长期记忆解决闭环。还提供了统一的 TTL 策略:default int computeTtl(int importance) { if (importance >= 10) return -1; // 永久 if (importance >= 5) return 2592000; // 30 天 return 604800; // 7 天 }
3.2 配置记忆方案
通过 HarnessEngine.Builder.memoryProvider(MemorySolutionProvider) 注入:
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(InMemoryAgentSession::of)
.memoryProvider(new MyMemoryProvider())
.build();
自定义 MemorySolutionProvider 示例:
MemorySolutionProvider 是一个接口,只有一个方法需要实现:
public interface MemorySolutionProvider {
String SHARED_USER_ID = "shared";
MemorySolution get(String __cwd); // 按工作区返回方案
default String getScopesDefault() { // 默认作用域
return "workspace";
}
default String getScopesDescription() { // 作用域说明
return "存储作用域: workspace(工作区,默认) 或 user(用户全局)。跨项目的通用认知用 user 域。";
}
}
MD 方案实现(开箱即用,零外部依赖):
框架内置的 MemorySolutionMdImpl 构造器接受 Map<String, Path>(作用域→路径映射),使用 LinkedHashMap 保证迭代顺序(低→高优先级,后者覆盖前者):
public class MyMemoryProvider implements MemorySolutionProvider {
private final Map<String, MemorySolution> cached = new ConcurrentHashMap<>();
@Override
public MemorySolution get(String __cwd) {
return cached.computeIfAbsent(__cwd, k -> {
// 按作用域划分目录:workspace 级存工作区,user 级存用户主目录
Map<String, Path> scopeMap = new LinkedHashMap<>();
scopeMap.put("user", Paths.get(System.getProperty("user.home"), ".demo/memory/"));
scopeMap.put("workspace", Paths.get(k, ".demo/memory/"));
return new MemorySolutionMdImpl(scopeMap);
});
}
}
注意:MemorySolutionMdImpl 只接受 Map<String, Path> 构造器,作用是域→目录映射;user 域优先级低于 workspace 域(前者在后者的 LinkedHashMap 中排在前面),同级 Key 写入时 workspace 覆盖 user;读取时按作用域合并。
实际上,MD 方案内部 Store 和 Search 共享同一份内存数据,启动时全量加载已有 MD 文件,写入同时更新搜索索引,后台定期清理过期条目(默认每小时清理一次)。
3.3 记忆 Provider 的默认路径
通过 HarnessOptions 的 getHarnessMemory() 可以获取记忆数据的默认存储路径({harnessHome}memory/),供自定义 Provider 实现时使用:
String getHarnessMemory() {
return harnessHome + "memory/";
}
3.4 进阶记忆 Provider 适配
除 MD 外,框架还提供以下内置存储与搜索实现,可按需组合自己的 MemorySolution:
| 类别 | 实现类 | 说明 |
|---|---|---|
| Storer | MemoryStorerMdImpl | 基于 MD 文件的持久化 |
| Storer | MemoryStorerRedisImpl | 基于 Redis 的持久化 |
| Storer | MemoryStorerRogueImpl | 基于 Rogue KV 存储的持久化 |
| Searcher | MemorySearcherMdImpl | 基于内存的全文检索 |
| Searcher | MemorySearcherLuceneImpl | 基于 Lucene 的全文检索 |
| Searcher | MemorySearcherRepositoryImpl | 基于向量库的语义检索 |
4、MemoryTalent 配置
通过 HarnessEngine.Builder 提供了一组针对 MemoryTalent 的配置方法,可在构建阶段微调记忆行为:
4.1 Builder 级配置
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(InMemoryAgentSession::of)
.memoryProvider(new MyMemoryProvider())
.memoryEnabled(true) // 启用心智记忆(默认 true)
.memoryRelevanceCount(6) // 按语义匹配的记忆条数(默认 6)
.memoryPriorityCount(5) // 按重要度兜底的记忆条数(默认 5)
.memorySummaryLength(80) // listAll 视图的摘要截断长度(默认 80)
.build();
各配置项的含义:
| 配置 | 默认值 | 说明 |
|---|---|---|
memoryEnabled | true | 是否启用心智记忆。设为 false 时 MemoryTalent 被禁用 |
memoryRelevanceCount | 6 | 按当前用户输入做语义检索的记忆条数。总注入预算 = relevanceCount + priorityCount。小窗口模型建议 3-4,大窗口模型建议 8-10 |
memoryPriorityCount | 5 | 按重要度(importance>=5)兜底的记忆条数。语义匹配不足时保证核心认知不丢;search 未用完的预算自动流转给 priorityCount |
memorySummaryLength | 80 | memory_search('*') 列表视图中每个条目的摘要截断长度。仅影响列表展示,注入路径使用完整内容 |
4.2 运行时动态调整
可在运行时动态调整记忆行为:
// 构建引擎后调整
engine.setMemoryRelevanceCount(8);
engine.setMemoryPriorityCount(3);
engine.setMemorySummaryLength(120);
4.3 运行时动态开关
可在运行时动态开启或关闭心智记忆功能(同时更新 Options 配置和 MemoryTalent 的 enabled 状态):
engine.setMemoryEnabled(true); // 运行时开启心智记忆
engine.setMemoryEnabled(false); // 运行时关闭心智记忆
关闭后,MemoryTalent 会被动态禁用,不再参与后续对话的认知注入。
5、记忆能力
配置记忆方案后,Agent 可自主进行 5 种操作:
| 工具名 | 能力 | 说明 |
|---|---|---|
memory_extract | 提取与覆盖 | 存入事实/偏好/进度。同名 Key 会返回旧记录供对比,信息有变则覆盖写入。包含近似 Key 探测(防止碎片化)和碎片密度检测(低分碎片超 5 条提示整合) |
memory_recall | 精确召回 | 通过 Key 获取该条目的完整细节(内容、时间、重要度) |
memory_search | 语义检索 | 用自然语言找回相关记忆;传入 '*' 列出全部条目索引(Key + 摘要),用于回答"记住了哪些" |
memory_consolidate | 认知升维 | 将多个碎片整合为高层洞察并清理冗余。新洞察自动赋最高重要度(10,永久保留)。支持原地升维(newKey 复用 keys_to_merge 中的某个 Key) |
memory_prune | 记忆修剪 | 删除错误、重复或过时的认知,同时清理存储主体和检索索引 |
记忆条目带重要度评分(1-10),Agent 在 getInstruction() 注入时采用相关性 + 热度混合策略:
- 步骤 A:按当前用户输入做语义检索(
relevanceCount条) - 步骤 B:热记忆兜底(取剩余预算,检索重要度 >= 5 的高质量条目)
- 两者按 Key 去重合并后注入到 LLM 的 system prompt 中
通过 MemoryTalent.relevanceInjection(boolean) 可控制画像注入策略: - true(默认):语义检索 + 热记忆混合注入 - false:仅注入热记忆(记忆量极大或检索延迟较高时可用)
6、作用域隔离
心智记忆支持作用域隔离,通过 MemorySolutionProvider 的描述方法向 LLM 呈现:
workspace(默认):工作区隔离,不同项目的记忆互不干扰user:用户全局,跨项目的通用认知共享
通过 MemoryTalent.sessionIsolation(boolean) 可控制是否按会话 ID 隔离用户标识(存储时 userId 为 sessionId 而非 shared),适用于多租户场景:
// 在 HarnessEngine 构建时不可直接配置,需自定义 MemoryTalent
// 默认 sessionIsolation=false(所有会话共享用户身份)
7、完整配置示例
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(InMemoryAgentSession::of)
.memoryProvider(new MyMemoryProvider())
.memoryEnabled(true)
.memoryRelevanceCount(6)
.memoryPriorityCount(5)
.memorySummaryLength(80)
.sessionWindowSize(8)
.compressionThreshold(40, 0.75)
.build();
// 运行时关闭心智记忆
engine.setMemoryEnabled(false);
// 运行时调整记忆检索数量
engine.setMemoryRelevanceCount(10);
// 获取 MemoryTalent 实例直接操作(如注册拦截器)
engine.getMemoryTalent(); // 返回 MemoryTalent 实例