Compaction、Memory 与 Dream

对应源码

机制源码
Memory 文件与归档nanobot/agent/memory.py::MemoryStore
Session consolidationnanobot/agent/memory.py::Consolidator
自动压缩调度nanobot/agent/autocompact.py::AutoCompact
Memory 注入模型上下文nanobot/agent/context.py::ContextBuilder.build_system_prompt
Dream prompt 与 Git 快照nanobot/templates/agent/dream.md、nanobot/utils/gitstore.py

学习目标

读完后,你应当能够:

  • 区分四种“压缩”;
  • 解释 Session 如何进入 history.jsonl;
  • 解释 Dream 如何更新长期文件;
  • 说明 cursor 的作用;
  • 找到配置中真正生效和已废弃的 Dream 字段。

前置知识

1. 先区分四种机制

机制触发作用对象结果
Session replay 裁剪每次 BUILD本轮 history少回放一些消息
Runner context governance每次 iterationmessages_for_model临时压缩/裁剪工具结果和历史
Consolidatortoken 压力或 idle compactSession 较早安全片段摘要写入 history.jsonl
DreamGateway cron 或 /dream新 history + 长期文件更新 SOUL/USER/MEMORY

它们不能统一叫“Memory 压缩”。

2. MemoryStore 管理哪些文件

默认 workspace:

workspace/
├── SOUL.md
├── USER.md
└── memory/
    ├── MEMORY.md
    ├── history.jsonl
    ├── .cursor
    ├── .dream_cursor
    └── .git/

职责:

文件角色
SOUL.mdAgent 长期语气、身份和沟通方式
USER.md稳定用户信息和偏好
MEMORY.md项目事实、决定和长期上下文
history.jsonlConsolidator 产生的结构化历史材料
.cursorhistory 写入游标
.dream_cursorDream 已消费到的位置
.git长期记忆文件的变更历史

3. history.jsonl 是材料,不是最终长期记忆

每条记录类似:

{
  "cursor": 42,
  "timestamp": "2026-07-24 10:00",
  "content": "- 用户选择了某方案",
  "session_key": "cli:direct"
}

实际可选字段以当前源码为准。

append_history():

  • 使用线程锁串行分配 cursor 和追加;
  • 清理思考内容泄漏;
  • 限制单条内容;
  • 更新 .cursor。

compact_history() 或重写路径使用原子替换。普通 append 与完整重写的持久化方式不同,不应笼统写成“所有 Memory 写入都 fsync + rename”。

4. Consolidator

Consolidator.maybe_consolidate_by_tokens():

  1. 获取该 Session 的 consolidation lock;
  2. 刷新 Session 引用;
  3. 计算完整 prompt token 估计;
  4. 先处理 replay message overflow;
  5. 与输入预算比较;
  6. 选择合法 consolidation boundary;
  7. 调用 LLM 总结较早片段;
  8. 将摘要追加到 history.jsonl;
  9. 推进 last_consolidated;
  10. 保存 _last_summary。
flowchart TD
    S["Session.messages"] --> EST["估算 prompt tokens"]
    EST -->|低于预算| KEEP["不归档"]
    EST -->|达到预算| B["选择安全边界"]
    B --> LLM["LLM 总结"]
    LLM -->|成功| H["history.jsonl 摘要"]
    LLM -->|失败| RAW["raw archive breadcrumb"]
    H --> CUR["推进 last_consolidated"]
    RAW --> CUR
    CUR --> META["保存 _last_summary"]

如果 LLM 总结失败:

  • 使用 raw archive 留下有限 breadcrumb;
  • 推进边界,避免下一次反复归档同一片段;
  • 本轮停止继续轰击已退化的 Provider。

5. Token 预算

Consolidator 的输入预算会预留:

  • 最大 completion tokens;
  • safety buffer。

consolidation_ratio 决定压缩后的目标占用比例,而不是“每次删除多少条消息”。

默认值以 AgentDefaults.consolidation_ratio 为准,当前为 0.5。

6. AutoCompact

AutoCompact 处理 idle Session:

  • 配置字段序列化名称为 idleCompactAfterMinutes;
  • 当前默认 15 分钟;
  • 设为 0 禁用;
  • AgentLoop 消费队列超时时检查过期 Session;
  • 活跃 session 不应被同时 compact;
  • 使用 Consolidator 的锁保护硬裁剪;
  • summary 同时保存在内存和 Session metadata,支持进程重启后的 cold path。

下一次 prepare_session() 将 _last_summary 注入本轮 system prompt 的 Archived Context Summary。

7. Dream

Dream 是 Gateway 中的系统 Cron job,也可以通过 /dream 手动触发。

流程:

flowchart TD
    H["history.jsonl 新条目"] --> BP["MemoryStore.build_dream_prompt"]
    BP --> DS["独立 dream Session / Agent 执行"]
    DS --> TOOLS["Dream 专用文件编辑工具"]
    TOOLS --> FILES["SOUL.md / USER.md / MEMORY.md"]
    FILES --> GIT["GitStore 记录变更"]
    DS --> CUR["推进 .dream_cursor"]

Dream 读取:

  • 未消费 history;
  • 当前 SOUL;
  • 当前 USER;
  • 当前 MEMORY;
  • Skill creator 路径等运行信息。

它不是把 history.jsonl 全文复制进 MEMORY.md,而是通过工具对长期文件做小范围整理。

8. Dream 配置边界

AgentDefaults.dream 当前字段:

字段当前行为
enabled是否注册周期 Dream job
intervalH默认周期
cron兼容的 cron override
modelOverride待实现
maxBatchSize已废弃,不再使用
maxIterations已废弃,不再使用
annotateLineAges已废弃,不再使用

不能因为字段仍在 schema 中,就写成当前功能。

9. Memory 如何进入下一次模型请求

ContextBuilder 可能加入:

  • 已定制 MEMORY.md;
  • .dream_cursor 之后的 recent history;
  • Session 的 _last_summary;
  • SOUL/USER 作为 bootstrap 文件。

这解释了为什么 Memory 更新不是“立即覆盖当前 Session”:

  • 当前会话仍主要依赖 Session history;
  • Consolidator 先产生 history;
  • Dream 周期性整理长期文件;
  • 下一次 ContextBuilder 才重新读取。

观察任务

  1. 在学习 workspace 找到 memory 目录;
  2. 完成多轮对话;
  3. 查看 Session 与 history.jsonl 是否变化;
  4. 使用 /dream 前后比较 SOUL/USER/MEMORY;
  5. 使用 /dream-log 查看 GitStore 变更;
  6. 不把真实个人信息写入实验请求。

源码与测试

  • nanobot/agent/memory.py
  • nanobot/agent/autocompact.py
  • nanobot/agent/context.py
  • nanobot/templates/agent/dream.md
  • nanobot/utils/gitstore.py
  • tests/agent/test_memory_store.py
  • tests/agent/test_consolidator.py
  • tests/agent/test_consolidation_ratio.py
  • tests/agent/test_auto_compact.py
  • tests/agent/test_dream.py
  • tests/agent/test_dream_session.py
  • tests/agent/test_git_store.py

自检

  1. 为什么 Session replay 裁剪不等于 Consolidation?
  2. history.jsonl 为什么不是最终 Memory?
  3. Consolidator 失败时为什么仍推进边界?
  4. .cursor 和 .dream_cursor 分别追踪什么?
  5. modelOverride 为什么必须标记为待实现?

下一步

阅读 Goal 与 Continuation。