在构建长期运行的 AI 助手时,最棘手的挑战之一是如何在有限的模型上下文窗口内,维持对话的连贯性与知识的持久性。传统的简单截断或全局摘要方法,往往导致关键上下文丢失或计算开销巨大。OpenClaw 作为一个旨在提供 “持久个人助理” 体验的开源平台,其核心架构选择了一条名为 “内存优先”(Memory-First)的路径,并通过精密的状态压缩与增量生成机制将其工程化。本文将深入剖析这一架构的底层实现,对比传统序列化开销,并提供可直接落地的参数配置与监控要点。
1. 状态压缩:非一次性摘要,而是增量式日志整理
OpenClaw 对会话状态的管理建立在一个关键洞察之上:对话本质是一个随时间增长的事件日志,而非一个需要反复全量序列化的臃肿对象。因此,其持久化层分为两层:
- 会话存储 (
sessions.json):一个轻量级的键值映射,存储会话元数据,如当前会话 ID、最后活动时间、思考级别、模型覆盖以及最重要的 ——令牌计数器(contextTokens,totalTokens)和压缩计数(compactionCount)。此文件小而可变,便于快速查询和更新。 - 转录本 (
<sessionId>.jsonl):这是真正的状态载体。它是一个追加写的 JSON Lines 文件,每个条目代表一个事件(用户消息、助手回复、工具调用结果、自定义状态)。条目间通过id和parentId构成树形结构,天然支持对话分支。其中,compaction类型的条目是整个压缩机制的核心。
压缩(Compaction) 在此模型中的定义非常精确:它不是删除旧消息,而是将一段连续的历史事件总结为一个新的 compaction 条目,并物理上保留 firstKeptEntryId 之后的所有原始消息。此后,模型看到的上下文是:“压缩摘要” + “压缩点之后的新消息”。这种方式保证了语义的连续性,同时将活跃上下文的大小控制在窗口之内。
2. 压缩算法实战:阈值触发与保留窗口
压缩并非随意发生,而是由一套明确的阈值策略驱动,主要嵌入在 Pi Agent 运行时中:
- 溢出恢复:当模型直接返回上下文溢出错误时,系统会立即触发压缩并重试请求。
- 阈值维护:在每次成功完成一轮对话后,系统检查
contextTokens > contextWindow - reserveTokens。如果成立,则触发压缩。
这里有两个关键工程参数:
reserveTokens:为系统提示词、工具定义及下一轮模型输出预留的头部空间。OpenClaw 强制设置了安全下限(默认 20k),以防止压缩后立即因执行 “家务管理”(如内存写入)而再次溢出。keepRecentTokens:指定在压缩后,至少保留多少令牌的原始消息(即firstKeptEntryId之后的条目)。这确保了最近、最相关的交互保持完整细节。
配置示例(位于 Pi 设置中):
{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
3. 增量生成与上下文组装:动态的提示词构建
“增量生成” 体现在每一轮对话的上下文组装阶段。模型永远不会接收到完整的、不断增长的原始转录本。相反,在每一轮开始时,Agent Runtime 会执行一次动态组装:
- 加载会话历史:从 JSONL 文件中读取,并实时重构逻辑视图。如果存在
compaction条目,则将其摘要作为上下文的一部分。 - 构建动态系统提示:合并工作空间中的
AGENTS.md(核心指令)、SOUL.md(个性语调)、TOOLS.md(工具惯例)以及当前激活的SKILL.md(技能指南)。这些文件独立于会话日志,实现了行为与状态的解耦。 - 语义记忆检索:从向量化的记忆存储(通常是 SQLite 数据库)中,根据当前查询检索最相关的过往片段(可能是来自
memory/YYYY-MM-DD.md的笔记或索引的过往对话),仅将这部分 “长期记忆” 注入当前上下文。
这个过程是增量且按需的。系统通过令牌预算,动态决定注入多少历史、多少记忆,从而在有限的上下文窗口内最大化信息密度。
4. 内存优先架构的核心:预压缩刷新与零拷贝设计
“内存优先” 的理念在于,将需要长期保留的知识主动从易失的会话日志中提升到持久的记忆文件中,然后再对日志进行压缩。OpenClaw 通过 “预阈值内存刷新” 机制实现了这一点。
在会话的 contextTokens 增长到接近压缩阈值(但尚未触发)时,系统会插入一个静默回合。此回合中,助手会收到一个以 NO_REPLY 开头的指令(如 “请将当前重要事实写入记忆”),促使其将当前对话中的关键决策、计划或事实整理并写入到工作空间的 memory/YYYY-MM-DD.md 等文件中。由于标记了 NO_REPLY,用户看不到任何输出。完成此操作后,sessions.json 中的 memoryFlushAt 时间戳和 memoryFlushCompactionCount 会被更新。
随后,当真正的压缩触发时,被总结掉的旧对话内容虽然从活跃上下文中消失,但其精华已安全存储在独立的记忆文件里,并可通过后续的语义检索被召回。这本质上是一种零拷贝设计思想的体现:重要的数据被 “移动” 到更合适的长期存储区,而非在原始日志中保留多个副本或进行昂贵的重序列化。
5. 可落地参数与监控清单
基于以上分析,在部署或借鉴类似架构时,应关注以下可操作的工程参数与监控点:
关键配置参数
| 参数 | 默认值 / 建议值 | 说明 |
|---|---|---|
compaction.reserveTokens |
≥ 20000 | 压缩触发预留空间。低于模型窗口 20k 以下易导致压缩循环。 |
compaction.keepRecentTokens |
20000 | 压缩后保留的原始消息令牌数,影响近期对话的细节保留。 |
compaction.memoryFlush.softThresholdTokens |
4000 | 内存刷新的软阈值,应低于 (reserveTokens - 单轮预期开销)。 |
agents.defaults.compaction.reserveTokensFloor |
20000 | OpenClaw 强制的最小安全值,设为 0 可禁用。 |
监控与健康检查清单
- 压缩频率:通过
openclaw sessions --json查看各会话的compactionCount。短期内快速增长可能意味着reserveTokens设置过小或单轮输出过长。 - 内存刷新状态:检查
memoryFlushAt是否及时更新,确保预刷新技术正常工作,防止知识在压缩中流失。 - 上下文令牌使用率:通过
/status命令或仪表板监控contextTokens相对于contextWindow的比例,维持在 70%-80% 以下为健康状态。 - 转录本文件大小:定期检查
*.jsonl文件大小。健康的压缩机制应使其增长趋于线性缓慢,而非指数级。 - 静默回合泄漏:确认包含
NO_REPLY的指令不会意外产生用户可见的输出流(需要特定版本支持流式抑制)。
与传统序列化方案的对比
与将整个会话状态序列化为单个大 JSON 对象相比,OpenClaw 的日志分层与压缩机制带来了明确优势:
- 写入开销:追加日志远低于重写整个大对象。
- 读取开销:上下文组装是选择性读取,而非加载全部历史。
- 容错与调试:JSONL 格式易于分割、查看和回溯,
compaction条目明确记录了摘要点,便于理解上下文演变。 - 分支支持:树形结构的日志天然支持对话分支探索,这是扁平化序列化难以实现的。
结语
OpenClaw 在状态管理上展示了一种成熟的工程范式:通过日志即状态的基础模型、阈值驱动的增量压缩算法,以及内存优先的预刷新策略,它有效地解决了长周期 AI 对话中的上下文管理难题。这套机制的核心在于将 “记忆” 与 “日志” 分离,并主动管理知识在不同存储层间的流动,而非被动地承受上下文窗口的限制。对于任何需要构建可持续交互 AI 系统的开发者而言,理解并借鉴这些模式,将有助于设计出更健壮、更高效的状态管理系统。
本文分析基于 OpenClaw 官方文档及开源代码,相关设计可能随项目迭代而演进。