犀牛鸟 2026 研究笔记 estelledc.github.io

精读: OpenClaw Pre-Compaction Memory Flush

Claude Code 记忆机制:精读: Claude Code 记忆系统 Codex 记忆机制:精读: Codex 记忆系统 Harness 视角:Agent 框架 · Claude Code / Codex 架构

调研元信息

项目
调研日期 2026-06-22
源码版本 github.com/openclaw/openclaw main HEAD(depth=1 clone)
本地路径 /tmp/openclaw/
涉及研究轴 B(Coding Agent)+ F(设计模式)
证据等级 [源码] 为主

一句话定位

OpenClaw 的 Pre-Compaction Memory Flush 是一种在上下文压缩之前、由嵌入式 agent 执行的静默持久化轮次——让模型在”记忆被删前”把重要信息写到磁盘,解决了”压缩 = 遗忘”的根本矛盾 [源码]

记忆类型标注

类型 涉及 说明
Working flush 的触发条件基于 context window token 使用量
Profile MEMORY.md 是用户/系统记忆,不是规则配置
User flush 产出写入 memory/YYYY-MM-DD.md(日记层)和 MEMORY.md(长期层)
External 间接 通过 Host-Adapter 可对接 DB-Agent-Memory 等外部记忆服务

日常类比

想象你在开一个长达 6 小时的研讨会。会议室的白板空间有限(context window),写满了就得擦掉旧内容(compaction)。OpenClaw 的做法是:在擦白板之前,安排一个速记员(嵌入式 agent)快速拍照,把重要内容誊写到笔记本(memory/YYYY-MM-DD.md)里。这样即使白板擦干净了,重要决策和结论都保留在笔记本中。

类比边界:速记员不是拍照留存所有内容,而是由 AI 判断”什么值得记”——这个判断通过 flush prompt 控制,质量取决于 prompt 设计。


架构总览

sequenceDiagram
    participant User as 用户
    participant AR as Agent Runner
    participant Gate as Flush Gate
    participant Plan as Flush Plan
    participant EA as 嵌入式 Agent
    participant FS as 文件系统
    participant Compact as Compaction

    User->>AR: 发送消息
    AR->>Gate: shouldRunMemoryFlush()
    Gate->>Gate: tokens >= threshold?<br/>已经 flush 过本轮?

    alt 需要 flush
        Gate->>Plan: buildMemoryFlushPlan()
        Plan-->>Gate: {prompt, path, model}
        Gate->>EA: runEmbeddedAgent(silent=true)
        EA->>FS: 写入 memory/YYYY-MM-DD.md
        EA-->>AR: NO_REPLY (静默)
        AR->>AR: 记录 memoryFlushAt
    end

    AR->>Compact: 正常 compaction(如需要)
    Compact->>AR: 压缩后继续

核心机制:三步 Flush 流程

第 1 步:Flush Gate — 何时触发

shouldRunMemoryFlush() 函数决定是否需要 flush [源码]

触发条件(满足任一即触发):

阻止条件(满足任一则跳过):

去重逻辑通过 hasAlreadyFlushedForCurrentCompaction() 实现——比较 memoryFlushCompactionCountcompactionCount,确保每个 compaction 周期只 flush 一次 [源码]

第 2 步:Flush Plan — 写什么、写到哪

buildMemoryFlushPlan() 构建 flush 计划 [源码]

目标文件memory/YYYY-MM-DD.md(基于用户时区),如果文件已存在则追加而非覆盖。

Flush Prompt 的核心指令

安全护栏:系统提示明确标注 MEMORY.mdDREAMS.mdSOUL.md 为只读,防止 flush agent 意外覆盖长期记忆或人格定义文件。

第 3 步:Flush Execution — 静默嵌入式 Agent

runMemoryFlushIfNeeded() 启动一个嵌入式 agent 来执行实际写入 [源码]

关键设计


三层记忆文件布局

OpenClaw 的记忆系统使用三层文件结构(默认在 ~/.openclaw/workspace[源码]

文件 生命周期 加载策略
长期记忆 MEMORY.md 持久 每个 DM session 启动时加载
日记记忆 memory/YYYY-MM-DD.md 按天 加载今天和昨天的日记
整理记录 DREAMS.md 持久 背景整合(dreaming)的产出

与 Claude Code / Codex 的对比

维度 OpenClaw Claude Code Codex
长期记忆 MEMORY.md(单文件) MEMORY.md(索引)+ topic.md(正文) MEMORY.md + memory_summary.md
短期记忆 memory/YYYY-MM-DD.md 无独立短期文件 raw_memories.md + rollout_summaries/
整合机制 Dreaming(日记→MEMORY.md 晋升) autoDream(去重+优化) Phase2(全局合并)
文件预算 10,000 字符上限 [源码] 200 行 / 25KB [源码] 无硬限制(Phase2 选 top-256)

Dreaming 系统:从日记到长期记忆

OpenClaw 的 Dreaming 系统负责将 flush 产出的日记内容晋升为长期记忆 [源码]

flowchart LR
    FLUSH["Flush Agent<br/>写入 memory/YYYY-MM-DD.md"] --> COLLECT["Dreaming<br/>收集短期信号"]
    COLLECT --> SCORE["候选评分<br/>(短期晋升得分)"]
    SCORE --> PROMOTE["晋升到 MEMORY.md"]
    PROMOTE --> BUDGET["预算检查<br/>≤10,000 字符"]
    BUDGET -->|超预算| DROP["删除最旧的<br/>自动晋升段落"]
    BUDGET -->|在预算内| DONE["完成"]

Memory Budget Compactionmemory-budget.ts):当 MEMORY.md 增长超过预算时,按日期顺序删除最旧的自动晋升段落,但保留用户手写的内容。这与 Claude Code 的硬截断(超 200 行直接截断并加 WARNING)形成鲜明对比——OpenClaw 的策略更温和,优先淘汰旧内容而非暴力截断 [源码]


Pre-Compaction Flush vs 同类机制

这是本文的核心对比——三种生产 Coding Agent 如何在 compaction 前保留关键信息:

维度 OpenClaw flush Claude Code extractMemories Codex Phase1
触发时机 compaction 前(主动) 每次无工具响应后(被动) root session 启动时(批量)
与 compaction 关系 前置——flush 在 compaction 之前运行 并行——extract 和 compaction 独立 独立——Phase1 不关心 compaction
执行方式 嵌入式 agent(静默轮次) forked agent(fire-and-forget) 并行 worker(最多 8 个)
写入目标 memory/YYYY-MM-DD.md(日记) ~/.claude/projects/*/memory/topic.md SQLite stage1_outputs
模型选择 可配独立模型(如本地小模型) 共享 prompt cache 的 Sonnet reasoning=Low 的模型
静默性 NO_REPLY token 抑制输出 fire-and-forget 不影响主流 异步后台
去重 per-compaction 只 flush 一次 hasMemoryWritesSince() 检查 冷却 6h per rollout
内容过滤 只存”持久性记忆” 四类型分类 + 排除可推导信息 过滤 developer 消息 + secret 脱敏

关键洞察:OpenClaw 的 flush 是唯一一个显式前置于 compaction 的方案。Claude Code 的 extractMemories 虽然也提取记忆,但它的触发与 compaction 无关——它在每次模型响应后触发,即使上下文远未满也会运行。Codex 的 Phase1 更是完全独立于 compaction,在 session 启动时回顾历史 rollout。

这意味着 OpenClaw 的设计动机最直接:”compaction 会删掉上下文,所以在删之前先保存”。而 Claude Code 和 Codex 的设计动机是”持续学习”,compaction 的信息损失是它们的副产品,不是主要驱动力。


与 DB-Agent-Memory 的集成

OpenClaw 是 DB-Agent-Memory 的三个 Adapter 之一(MCP、OpenClaw、Hermes)[已有]

flowchart TB
    subgraph openclaw["OpenClaw Agent"]
        FLUSH["Flush Agent"]
        DREAM["Dreaming"]
        MEM_MD["MEMORY.md"]
        DAILY["memory/YYYY-MM-DD.md"]
    end

    subgraph dbmem["DB-Agent-Memory"]
        HOST["Memory Host"]
        L0["L0 对话层"]
        L1["L1 原子事实"]
        L2["L2 场景块"]
        L3["L3 用户画像"]
        ADAPTER["OpenClaw Adapter"]
    end

    FLUSH --> DAILY
    DREAM --> MEM_MD
    openclaw -->|对话流| ADAPTER
    ADAPTER --> HOST
    HOST --> L0 --> L1 --> L2 --> L3

集成关键问题(犀牛鸟切入点)[源码][已有]

  1. Flush 与金字塔的协同:OpenClaw flush 写入 memory/YYYY-MM-DD.md 是平面文本,而 DB-Agent-Memory 有 L0→L3 的分层蒸馏。flush 内容应该从哪层注入?直接作为 L0?还是由 Host 判断层级?
  2. MMD 注入与 flush prompt 的关系:DB-Agent-Memory 的 MMD(稳定区+动态区)注入和 OpenClaw 的 flush prompt 注入是否互相干扰?两者都在向 context window 写入记忆相关内容。
  3. Dreaming vs 金字塔提炼:OpenClaw 的 Dreaming(日记→MEMORY.md 晋升)与 DB-Agent-Memory 的 L1→L2→L3 提炼是并行还是冗余?

配置接口

OpenClaw 通过 agent 配置文件暴露 flush 参数 [源码]

{
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000,
    "reserveTokensFloor": 20000,
    "memoryFlush": {
      "enabled": true,
      "softThresholdTokens": 4000,
      "forceFlushTranscriptBytes": "2mb",
      "model": "ollama/qwen3:8b",
      "prompt": "...",
      "systemPrompt": "..."
    }
  }
}

设计亮点:flush 模型可以独立配置。对于本地部署场景,可以用小模型(如 qwen3:8b)做 flush 以降低成本和延迟,同时主 agent 使用强模型。这在 Claude Code 和 Codex 中没有对应——它们的记忆提取使用固定的模型策略(Claude Code 用 Sonnet,Codex 用 reasoning=Low) [源码]


局限性

  1. Flush 质量依赖 prompt:flush prompt 决定了什么被保存,但”什么值得记”本身是主观判断——不同 prompt 设计会导致记忆质量差异显著 [源码]
  2. 日记文件膨胀memory/YYYY-MM-DD.md 只追加不删除,一天内多次 flush 可能导致同一天的日记文件过大。虽然有”不创建时间戳变体”的规则,但同一文件内仍可能有重复信息。
  3. Flush 与用户响应延迟:flush 是在用户消息处理流程中同步执行的(虽然对用户静默),可能增加响应延迟——特别是使用远程模型时 [源码]
  4. MEMORY.md 预算固定:10,000 字符的预算是硬编码的,对于长期运行的复杂项目可能不够用。
  5. Dreaming 调度不透明:dreaming 的触发条件(何时将日记内容晋升到 MEMORY.md)在配置中不可调,用户无法控制晋升策略。

证据等级汇总

论点 证据等级
Pre-compaction flush 架构和触发条件 [源码] flush-plan.ts + memory-flush.ts
嵌入式 agent 静默执行 [源码] agent-runner-memory.ts
三层文件布局(MEMORY.md / daily / DREAMS.md) [源码] root-memory-files.ts + docs/concepts/memory.md
Dreaming 晋升机制 [源码] dreaming-phases.ts + short-term-promotion.ts
Memory Budget 10,000 字符 [源码] memory-budget.ts
去重逻辑(per-compaction) [源码] memory-flush.ts
Flush 模型可独立配置 [源码] flush-plan.ts 配置 schema
与 DB-Agent-Memory 集成 [已有] deep-dive-tencentdb-agent-memory.md
与 Claude Code extractMemories 对比 [已有] deep-dive-claude-code-memory.md
与 Codex Phase1 对比 [已有] deep-dive-codex-memory.md

与开源记忆项目对照

维度 OpenClaw flush mem0 DB-Agent-Memory Letta
写入触发 上下文将满时 API 调用时 对话流 Agent 工具调用
写入策略 追加日记文件 扁平事实去重 四层蒸馏 自管理 Block
遗忘/淘汰 Budget compaction 无(L0 可清理) Agent 自行删除
与 compaction 关系 显式前置 不适用 不适用 不适用
多模型支持 可配独立 flush 模型 固定 固定 固定
文件 vs 数据库 文件(Markdown) 数据库(向量) 数据库(SQLite) 数据库(Postgres)