AgentScope(Java Harness 版本)Memory 全套组件系统梳理
前置核心边界声明
- RuntimeContext 不属于记忆存储组件,是记忆寻址与隔离凭证,本身不承载任何对话、事实消息;
- AgentState 是 HarnessAgent 专属短期会话记忆载体,替代原生 ReActAgent 的
MemoryBase;- LongTermMemory 是跨会话长期事实记忆抽象,默认实现为文件存储,向量存储属于可插拔扩展,不是框架默认记忆;
- 压缩、持久化、定时合并、事件钩子(Hook)、中间件属于记忆体系的配套控制组件,负责记忆的治理、调度、落地;
- 两套记忆体系不要混用:原生 ReActAgent 使用
MemoryBase作为短期记忆抽象;HarnessAgent 独立使用 AgentState + AgentStateStore 这套会话状态体系,二者 API、存储链路、生命周期完全隔离。
〇、记忆压缩总览(新增核心模块:两套记忆各自的默认压缩方式)
先把最重要的事说清:短期记忆压缩 ≠ 长期记忆压缩,两者是完全独立的两套机制,作用对象、触发时机、实现方式都不同。
表格
| 维度 | 短期记忆压缩 | 长期记忆压缩 |
|---|---|---|
| 作用对象 | AgentState 内的对话消息列表(当前会话上下文) | MEMORY.md/ 每日 md 流水文件(跨会话事实) |
| 默认方式 | LLM 摘要压缩 + 保留最近 N 轮原文(ConversationCompactor) | 后台定时合并 + 去重 + 精简刷新(MemoryMaintenanceScheduler) |
| 是否调用 LLM | ✅ 是,每轮触发时调用 LLM 生成摘要 | ❌ 否,纯文本层面合并去重,不重新调用 LLM 抽取 |
| 触发时机 | PreReasoningEvent(每轮推理前,达到阈值触发) | 后台定时调度(默认 6h 周期,30min 节流) |
| 执行组件 | CompactionHook → ConversationCompactor | MemoryMaintenanceScheduler |
| 触发前置 | 无特殊前置 | 依赖 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 摘要压缩 + 保留尾部原文 |
| AgentStateStore | AgentState 序列化数据 | ✅ | 会话周期 | 短期记忆存储后端 | 不压缩,只持久化 |
| ConversationCompactor | 无存储,纯计算 | ❌ | 触发时瞬时执行 | 短期记忆压缩算法 | 【主体】LLM 摘要 + 保留最近 N 轮 |
| CompactionHook | 无存储,事件监听 | ❌ | 每轮推理前触发 | 短期压缩触发器 | 触发 Compactor 执行 |
| ToolResultEvictionHook | 无存储,超大内容落外部存储 | ✅(外部文件) | 工具返回后触发 | 治理超大工具结果 | 工具结果落盘 + 摘要引用 |
| LongTermMemory | 用户偏好、稳定事实 | ✅ | 跨会话、长期 | 长期记忆抽象 | 实现层决定 |
| FileSystemLongTermMemory | md 事实流水 + 合并主记忆 | ✅ | 长期 | 默认长期记忆实现 | 后台合并去重精简刷新 |
| MemoryMaintenanceScheduler | 无存储,后台调度 | ❌ | 定时运行 | 长期记忆压缩执行者 | 【主体】定时合并去重刷新 MEMORY.md |
| VDBStoreBase | 事实 + 向量 + metadata | ✅ | 长期 | 向量存储扩展后端 | 向量库自身索引,不做文本压缩 |
| SimpleKnowledge | 外部文档分块向量 | ✅ | 知识库独立生命周期 | RAG 外部知识库 | 不属于 Agent 记忆 |
七、两套默认压缩方式的核心差异再强调(面试高频)
- 性质不同:
- 短期压缩(ConversationCompactor):生成式有损压缩,调用 LLM 把旧对话变摘要,保留尾部原文;
- 长期压缩(MemoryMaintenanceScheduler):物理式合并压缩,不调用 LLM,纯文本合并去重刷新文件。
- 时机不同:
- 短期:PreReasoning 每轮推理前,阈值触发,实时性强;
- 长期:PostCall 先抽取写入流水,后台 Scheduler 异步定时(6h/30min 节流)才合并。
- 作用对象不同:
- 短期压的是 AgentState(本轮会话上下文);
- 长期压的是 MEMORY.md(跨会话事实档案)。
- 两者串联关系:
flushBeforeCompact=true时,短期压缩前先抽取事实→沉淀长期记忆(流水)→后台再压缩长期记忆文件。两套压缩形成一条完整的「先抽后合」链路。
- 都不含的:
- 框架没有内置滑动窗口(直接删旧消息无摘要)—— 需要自研 Hook;
- 长期压缩不做向量聚类去重 —— 默认纯文本逻辑,向量版由开发者实现。
八、高频坑点与边界补充
- AgentState 短期会话,LongTermMemory 事实沉淀,框架不自动把全量对话存长期记忆,必须 LLM 抽取筛选;
- 压缩只作用 AgentState 短期上下文,不压缩 MEMORY.md;长期记忆精简由后台 Scheduler 负责;
- 向量库只是 LongTermMemory 可选实现,不是默认记忆;SimpleKnowledge (RAG) 与 Agent 长期记忆两套独立;
- RuntimeContext 全程不存消息,只寻址隔离,无它则无法定位 AgentState 和用户长期记忆;
- Harness 记忆体系与原生 ReAct 的 MemoryBase 互相独立,禁止混用;
- 自动记忆写入时机是 PostCall 一轮对话结束;需实时边聊边记则自研 Hook 主动调用 record;
- memory_save 绕过 LLM 自动筛选直接写入,适合用户明确说 "记住这句话"。
更多推荐

所有评论(0)