Solon v4.0.4

harness - 会话与心智记忆

</> markdown
2026年7月30日 下午5:09:06

会话(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_extractmemory_recallmemory_searchmemory_consolidatememory_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 的默认路径

通过 HarnessOptionsgetHarnessMemory() 可以获取记忆数据的默认存储路径({harnessHome}memory/),供自定义 Provider 实现时使用:

String getHarnessMemory() {
    return harnessHome + "memory/";
}

3.4 进阶记忆 Provider 适配

除 MD 外,框架还提供以下内置存储与搜索实现,可按需组合自己的 MemorySolution

类别实现类说明
StorerMemoryStorerMdImpl基于 MD 文件的持久化
StorerMemoryStorerRedisImpl基于 Redis 的持久化
StorerMemoryStorerRogueImpl基于 Rogue KV 存储的持久化
SearcherMemorySearcherMdImpl基于内存的全文检索
SearcherMemorySearcherLuceneImpl基于 Lucene 的全文检索
SearcherMemorySearcherRepositoryImpl基于向量库的语义检索

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();

各配置项的含义:

配置默认值说明
memoryEnabledtrue是否启用心智记忆。设为 false 时 MemoryTalent 被禁用
memoryRelevanceCount6按当前用户输入做语义检索的记忆条数。总注入预算 = relevanceCount + priorityCount。小窗口模型建议 3-4,大窗口模型建议 8-10
memoryPriorityCount5按重要度(importance>=5)兜底的记忆条数。语义匹配不足时保证核心认知不丢;search 未用完的预算自动流转给 priorityCount
memorySummaryLength80memory_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() 注入时采用相关性 + 热度混合策略

  1. 步骤 A:按当前用户输入做语义检索(relevanceCount 条)
  2. 步骤 B:热记忆兜底(取剩余预算,检索重要度 >= 5 的高质量条目)
  3. 两者按 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 实例