前置核心边界声明

  1. RuntimeContext 不属于记忆存储组件,是记忆寻址与隔离凭证,本身不承载任何对话、事实消息;
  2. AgentState 是 HarnessAgent 专属短期会话记忆载体,替代原生 ReActAgent 的 MemoryBase;
  3. LongTermMemory 是跨会话长期事实记忆抽象,默认实现为文件存储,向量存储属于可插拔扩展,不是框架默认记忆;
  4. 压缩、持久化、定时合并、事件钩子(Hook)、中间件属于记忆体系的配套控制组件,负责记忆的治理、调度、落地;
  5. 两套记忆体系不要混用:原生 ReActAgent 使用 MemoryBase 作为短期记忆抽象;HarnessAgent 独立使用 AgentState + AgentStateStore 这套会话状态体系,二者 API、存储链路、生命周期完全隔离。

〇、记忆压缩总览(新增核心模块:两套记忆各自的默认压缩方式)

先把最重要的事说清:短期记忆压缩 ≠ 长期记忆压缩,两者是完全独立的两套机制,作用对象、触发时机、实现方式都不同。

表格

维度短期记忆压缩长期记忆压缩
作用对象AgentState 内的对话消息列表(当前会话上下文)MEMORY.md/ 每日 md 流水文件(跨会话事实)
默认方式LLM 摘要压缩 + 保留最近 N 轮原文(ConversationCompactor)后台定时合并 + 去重 + 精简刷新(MemoryMaintenanceScheduler)
是否调用 LLM✅ 是,每轮触发时调用 LLM 生成摘要❌ 否,纯文本层面合并去重,不重新调用 LLM 抽取
触发时机PreReasoningEvent(每轮推理前,达到阈值触发)后台定时调度(默认 6h 周期,30min 节流)
执行组件CompactionHook → ConversationCompactorMemoryMaintenanceScheduler
触发前置无特殊前置依赖 MemoryFlushHook 先抽取事实、写入每日流水
压缩是否丢信息有损,旧消息被摘要替换(但最近 N 轮原文保留)有损,重复 / 过时事实被清理
目的是降低注入 LLM 的上下文 token,防止超限防止记忆文件无限膨胀,控制加载 token 预算

一、寻址 / 身份组件(不存储记忆,仅用于定位、隔离记忆资源)

RuntimeContext

  • 核心字段:sessionId、userId、tenantId、traceId、spanId
  • 核心作用:记忆的全局索引 Key、多租户 / 多用户 / 多会话隔离凭证。框架传入 RuntimeContext,从 AgentStateStore 中加载对应会话的 AgentState;长期记忆检索、写入时,会把 userId、tenantId、sessionId 塞入 metadata,实现记忆数据隔离(A 用户检索不到 B 用户的长期记忆)。
  • 传递链路:全链路透传,Hook、工具、中间件、记忆组件、权限引擎、向量库都可以读取。
  • 生命周期:单次 Agent 调用的运行时临时对象,不持久化,每次请求重新构造。
  • 承载内容:只放身份、链路追踪标识,不存放对话文本、工具结果、用户偏好等记忆内容。
  • 一句话:钥匙,用来找到记忆盒子;盒子才装内容。

补充:同一 sessionId 多次请求复用同一份 AgentState;不同 tenantId 下相同 sessionId 互相隔离,是 SaaS 多租户基础。


二、短期记忆整套组件(当前会话、本轮对话上下文,ReAct 循环的工作内存)

短期记忆 = 本次会话完整交互上下文,是 LLM 推理、工具选择、工具结果反馈的直接素材。

1. AgentState(Harness 核心短期记忆对象)

内存态会话容器,存储内容清单:

  • 标准对话消息列表 List<Msg>:用户提问、模型回复、工具调用请求、工具返回结果、报错信息;
  • 会话摘要:由压缩器生成的摘要消息(见下方压缩机制);
  • 工具调用轨迹:历史工具名、入参、返回标记、调用时序;
  • 任务规划 Plan:Harness Plan 模式下子任务清单、完成标记;
  • 临时标记消息:带 mark 标记的特殊消息(summary 摘要标记、大工具结果引用标记等);
  • 会话元数据:创建时间、最后更新时间、token 统计、压缩标记、过期标记;
  • 会话状态标记:是否终止、是否等待人工确认。

生命周期:严格绑定唯一 sessionId,同会话多轮 ReAct 复用;会话结束 / 过期可销毁。 业务说明:常规不手动 new、不手动持久化,框架自动加载保存;可在 Hook/Middleware 中读取、修改消息列表。 与原生 ReAct 区别:ReAct 把消息封装在MemoryBase,Harness 收拢到 AgentState。

2. AgentStateStore(短期记忆存储后端)

  • 默认:本地文件存储(单机测试,序列化落盘);
  • 可替换:Redis、MySQL、MongoDB 分布式实现,适合集群多实例;
  • 核心方法:load(RuntimeContext)、save(AgentState, RuntimeContext)、remove(RuntimeContext);
  • 职责链路:RuntimeContext → AgentStateStore → 加载 / 落地 AgentState。

可自定义实现,增加过期淘汰、会话 TTL、读写加锁,防止并发修改同一 session 状态。

3. ConversationCompactor(对话压缩器,短期记忆默认压缩的核心引擎)

【本节重点:短期记忆默认压缩方式】 专门解决 AgentState 消息不断累积、Token 超限导致 LLM 调用失败的组件。

默认压缩策略:LLM 摘要压缩 + 保留最近 N 轮原文

  • 逻辑:把比较久远的历史消息交给 LLM 浓缩成一段结构化摘要,最近 N 轮完整保留原文,摘要替换原始长消息,降低整体 token 消耗;
  • 生成结构:摘要会提炼为固定块(task_overview 任务总目标、current_state 当前进度、important_discoveries 关键结论 / 约束、next_steps 下一步计划、context_to_preserve 需保留信息);
  • 放回 AgentState 后,摘要消息打上summary标记,tool_result等原始大消息被摘要引用替换。

默认参数(不手动配置时):

  • triggerMessages=30:消息条数达到 30 条触发压缩;
  • keepMessages=10:压缩后保留最近 10 条原文不压缩;
  • flushBeforeCompact=true:压缩前先把事实沉淀到长期记忆(见第三节);
  • 兜底:模型返回上下文超限报错时,forceCompactAndRetry强制再压缩一次并重试(框架自带)。

⚠️ 关键限制:

  • 框架没有内置滑动窗口截断策略(直接丢弃最早消息,不生成摘要);滑动窗口需要自己实现 Hook;
  • 压缩只作用在 AgentState 短期上下文,完全不影响 MEMORY.md 长期记忆文件。

4. CompactionHook(短期记忆压缩触发钩子)

监听PreReasoningEvent事件:

  • 每轮推理开始前,读取 AgentState 消息总 token 数 / 条数;
  • 超过阈值 → 调用 ConversationCompactor 执行压缩,更新 AgentState;
  • 超限报错兜底 → 触发 forceCompactAndRetry 强制压缩重试。

压缩发生在推理之前,不是工具返回之后。

5. ToolResultEvictionHook(工具结果驱逐钩子,短期记忆防膨胀配套)

应对工具返回超大文本(大 SQL、大文件导出、大数据集):

  • 超大原始结果不一直留在 AgentState 消息列表;
  • 原始内容落地到本地文件 / 对象存储,AgentState 只保留一条摘要引用消息,需要时按需加载;
  • 控制 AgentState 体积,避免单次工具返回直接打满上下文窗口。

属于压缩链路的配套能力,不需要 LLM 参与,纯工程手段。

原生 ReActAgent 短期记忆补充(了解即可,Harness 不使用)

  • MemoryBase:原生短期记忆抽象,方法add、get、clear;
  • 内置实现:InMemoryMemory、RedisMemory、AsyncSQLAlchemyMemory;
  • AutoContextMemory:原生 ReAct 自带渐进式自动压缩 Memory 实现;
  • 不要在 HarnessAgent 中混用这套。

三、长期记忆整套组件(跨会话、持久化事实记忆,和 AgentState 互相独立)

核心认知:长期记忆 ≠ 会话历史。存的是从对话中提炼出的稳定、跨会话有效的事实、偏好、约束;不会自动保存完整对话、临时查询结果、临时工具返回。

1. LongTermMemory(长期记忆顶层接口)

  • record(RuntimeContext ctx, List<Fact> factList):写入事实;
  • retrieve(RuntimeContext ctx, String query, int topK, double threshold):语义召回;
  • RuntimeContext 透传给实现层:文件版用于区分目录,向量版写入 metadata 用于隔离过滤。

2. 默认实现:FileSystemLongTermMemory(Markdown 文件式,开箱即用)

目录结构 workspace/memory:

  • memory/YYYY-MM-DD.md:每日事实流水,每次 flush 追加写入,不删除原始记录;
  • MEMORY.md:主记忆合并文件,Agent 加载、读取记忆的主体文件。

3. MemoryFlushHook + MemoryFlushManager(事实抽取写入链路)

监听PostCallEvent(一轮完整用户对话结束,不是每次工具调用后):

  • MemoryFlushHook 收集本轮 AgentState 全部对话素材,交给 MemoryFlushManager;
  • MemoryFlushManager 组装内置抽取 Prompt,调用 LLM 从对话中提取稳定事实(不是存全量对话);
  • 对 LLM 输出做清洗、空值过滤、简单重复过滤;
  • 输出 Fact 列表,调用 LongTermMemory.record () 写入每日 md 流水。

【记忆筛选判定规则就在这里】:LLM 根据抽取 Prompt 自主判断 —— 存稳定事实(用户偏好、个人信息、业务约束、重要结论、明确要求记住的),不存临时内容(一次性查询结果、临时计划、实时数据、客套话、临时报错)。开发者可替换抽取 Prompt 自定义筛选规则。

4. MemoryMaintenanceScheduler(长期记忆默认压缩的核心引擎)

【本节重点:长期记忆默认压缩方式】 后台异步调度任务,负责维护 md 文件,避免无限膨胀。默认周期 6h,节流窗口 30min。

默认压缩 / 合并逻辑:

  • 扫描所有每日流水文件(YYYY-MM-DD.md);
  • 事实合并:把多个流水里零散的同一主题事实归并;
  • 重复去重:内容重复的事实只保留一份;
  • 冲突覆盖:新旧事实冲突时保留新的、标记旧事实失效;
  • 过时清理:删除已失效、过期、无引用价值的事实;
  • 刷新主文件 MEMORY.md,作为最终加载记忆。

⚠️ 关键限制:

  • Scheduler不会重新调用 LLM 抽取事实,只对已经抽取好的事实做文本层面的合并去重;
  • LLM 抽取只在 PostCall 的 MemoryFlushHook 阶段做一次,后台只做物理合并。

节流作用:短时间高频对话时避免频繁合并 IO,降低文件锁冲突。

5. 内置记忆工具(可开关)

  • memory_search:按 query 检索长期记忆;
  • memory_get:读取全部记忆;
  • memory_save:主动写入一条事实,绕过 LLM 自动抽取,直接存入;
  • 开关:disableMemoryTools()。

6. 可替换扩展:向量版长期记忆(框架只给抽象,默认不开启)

组件链路:自定义 LongTermMemory + EmbeddingModel + VDBStoreBase

  • VDBStoreBase 内置实现:InMemoryStore、QdrantStore、PgVectorStore、MilvusStore、ElasticsearchStore;
  • 流程:抽取事实 → 向量化 → 向量 + 文本 + metadata 存入向量库;检索时语义检索 + metadata 过滤;
  • 区别:SimpleKnowledge(RAG 知识库)≠ LongTermMemory,前者是外部文档知识库、可作为普通 Tool 调用,后者是用户个人记忆,二者独立。

四、记忆相关配置与基础辅助类

MemoryConfig

统一配置入口,集中管控短期、长期记忆行为:

  • 短期:压缩 token 阈值、保留消息条数、压缩重试策略;
  • 长期:flush 触发条件、后台调度周期、记忆加载 token 预算、召回 topK、相似度阈值;
  • 预算控制:MEMORY.md 加载到上下文有 token 上限,超出预算不全部塞入 prompt,引导 Agent 调用 memory_search 按需检索。

Msg(消息原子模型)

全记忆体系统一消息单元,底层都是 Msg:

  • 字段:role、content、mark、metadata;
  • mark 标记(关键):summary压缩摘要、tool_result工具返回、hint框架提示;
  • 通过 mark 标记,压缩器、钩子、中间件可精准筛选不同类型消息。

五、HarnessAgent 一次完整调用的记忆数据流(含两套压缩机制)

用户输入消息 + RuntimeContext(寻址钥匙)
        ↓
AgentStateStore.load(context) → 加载/新建AgentState(短期会话记忆)
        ↓
【PreReasoningEvent】CompactionHook执行检查(短期记忆压缩检查点)
    └── 消息token/条数超限(默认30条) → ConversationCompactor执行LLM摘要压缩
        └── 旧消息→摘要(summary标记) + 保留最近10条原文,更新AgentState
        ↓
加载MEMORY.md(长期记忆),按MemoryConfig的token预算拼入prompt
        ↓
进入ReAct推理循环:LLM推理、选择工具、执行工具
        ↓
每轮工具返回、模型回复封装成Msg追加写入AgentState
        ↓
(若工具返回超大 → ToolResultEvictionHook把原始结果落盘,AgentState只留摘要引用)
        ↓
循环直到LLM不再调用工具,产出最终回答,本轮对话结束
        ↓
【PostCallEvent】批量触发后置钩子
    ├─ MemoryFlushHook启动:取出AgentState本轮对话素材
    │    └─ MemoryFlushManager:LLM抽取稳定事实→清洗去重→LongTermMemory.record写入每日md流水
    └─ AgentStateStore.save(AgentState, context):持久化本轮会话状态
        ↓
异步后台【长期记忆压缩】:MemoryMaintenanceScheduler到调度时间(默认6h/30min节流)
    └─ 扫描流水文件 → 合并、去重、冲突覆盖、过时清理 → 刷新MEMORY.md

补充:若开启向量版 LongTermMemory,record 阶段把事实向量化存入向量库,不再写 md 文件;合并维护逻辑由开发者自定义实现。


六、组件对比汇总表(含压缩机制列)

表格

组件存储内容是否持久化生命周期核心定位默认压缩方式
RuntimeContext身份链路标识❌单次 Agent 调用寻址钥匙,隔离凭证,不存消息无(不参与压缩)
AgentState会话完整对话、摘要、工具轨迹、plan✅(依托 AgentStateStore)绑定 sessionId,会话周期短期工作记忆,推理上下文载体LLM 摘要压缩 + 保留尾部原文
AgentStateStoreAgentState 序列化数据✅会话周期短期记忆存储后端不压缩,只持久化
ConversationCompactor无存储,纯计算❌触发时瞬时执行短期记忆压缩算法【主体】LLM 摘要 + 保留最近 N 轮
CompactionHook无存储,事件监听❌每轮推理前触发短期压缩触发器触发 Compactor 执行
ToolResultEvictionHook无存储,超大内容落外部存储✅(外部文件)工具返回后触发治理超大工具结果工具结果落盘 + 摘要引用
LongTermMemory用户偏好、稳定事实✅跨会话、长期长期记忆抽象实现层决定
FileSystemLongTermMemorymd 事实流水 + 合并主记忆✅长期默认长期记忆实现后台合并去重精简刷新
MemoryMaintenanceScheduler无存储,后台调度❌定时运行长期记忆压缩执行者【主体】定时合并去重刷新 MEMORY.md
VDBStoreBase事实 + 向量 + metadata✅长期向量存储扩展后端向量库自身索引,不做文本压缩
SimpleKnowledge外部文档分块向量✅知识库独立生命周期RAG 外部知识库不属于 Agent 记忆

七、两套默认压缩方式的核心差异再强调(面试高频)

  1. 性质不同:
    • 短期压缩(ConversationCompactor):生成式有损压缩,调用 LLM 把旧对话变摘要,保留尾部原文;
    • 长期压缩(MemoryMaintenanceScheduler):物理式合并压缩,不调用 LLM,纯文本合并去重刷新文件。
  2. 时机不同:
    • 短期:PreReasoning 每轮推理前,阈值触发,实时性强;
    • 长期:PostCall 先抽取写入流水,后台 Scheduler 异步定时(6h/30min 节流)才合并。
  3. 作用对象不同:
    • 短期压的是 AgentState(本轮会话上下文);
    • 长期压的是 MEMORY.md(跨会话事实档案)。
  4. 两者串联关系:
    • flushBeforeCompact=true时,短期压缩前先抽取事实→沉淀长期记忆(流水)→后台再压缩长期记忆文件。两套压缩形成一条完整的「先抽后合」链路。
  5. 都不含的:
    • 框架没有内置滑动窗口(直接删旧消息无摘要)—— 需要自研 Hook;
    • 长期压缩不做向量聚类去重 —— 默认纯文本逻辑,向量版由开发者实现。

八、高频坑点与边界补充

  1. AgentState 短期会话,LongTermMemory 事实沉淀,框架不自动把全量对话存长期记忆,必须 LLM 抽取筛选;
  2. 压缩只作用 AgentState 短期上下文,不压缩 MEMORY.md;长期记忆精简由后台 Scheduler 负责;
  3. 向量库只是 LongTermMemory 可选实现,不是默认记忆;SimpleKnowledge (RAG) 与 Agent 长期记忆两套独立;
  4. RuntimeContext 全程不存消息,只寻址隔离,无它则无法定位 AgentState 和用户长期记忆;
  5. Harness 记忆体系与原生 ReAct 的 MemoryBase 互相独立,禁止混用;
  6. 自动记忆写入时机是 PostCall 一轮对话结束;需实时边聊边记则自研 Hook 主动调用 record;
  7. memory_save 绕过 LLM 自动筛选直接写入,适合用户明确说 "记住这句话"。
Logo

更多推荐