Compaction、Memory 与 Dream
对应源码
| 机制 | 源码 |
|---|---|
| Memory 文件与归档 | nanobot/agent/memory.py::MemoryStore |
| Session consolidation | nanobot/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 字段。
前置知识
- 理解 Session 和上下文窗口;
- 推荐补充 裁剪、摘要、压缩与缓存。
1. 先区分四种机制
| 机制 | 触发 | 作用对象 | 结果 |
|---|---|---|---|
| Session replay 裁剪 | 每次 BUILD | 本轮 history | 少回放一些消息 |
| Runner context governance | 每次 iteration | messages_for_model | 临时压缩/裁剪工具结果和历史 |
| Consolidator | token 压力或 idle compact | Session 较早安全片段 | 摘要写入 history.jsonl |
| Dream | Gateway 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.md | Agent 长期语气、身份和沟通方式 |
USER.md | 稳定用户信息和偏好 |
MEMORY.md | 项目事实、决定和长期上下文 |
history.jsonl | Consolidator 产生的结构化历史材料 |
.cursor | history 写入游标 |
.dream_cursor | Dream 已消费到的位置 |
.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():
- 获取该 Session 的 consolidation lock;
- 刷新 Session 引用;
- 计算完整 prompt token 估计;
- 先处理 replay message overflow;
- 与输入预算比较;
- 选择合法 consolidation boundary;
- 调用 LLM 总结较早片段;
- 将摘要追加到
history.jsonl; - 推进
last_consolidated; - 保存
_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 才重新读取。
观察任务
- 在学习 workspace 找到 memory 目录;
- 完成多轮对话;
- 查看 Session 与
history.jsonl是否变化; - 使用
/dream前后比较 SOUL/USER/MEMORY; - 使用
/dream-log查看 GitStore 变更; - 不把真实个人信息写入实验请求。
源码与测试
nanobot/agent/memory.pynanobot/agent/autocompact.pynanobot/agent/context.pynanobot/templates/agent/dream.mdnanobot/utils/gitstore.pytests/agent/test_memory_store.pytests/agent/test_consolidator.pytests/agent/test_consolidation_ratio.pytests/agent/test_auto_compact.pytests/agent/test_dream.pytests/agent/test_dream_session.pytests/agent/test_git_store.py
自检
- 为什么 Session replay 裁剪不等于 Consolidation?
history.jsonl为什么不是最终 Memory?- Consolidator 失败时为什么仍推进边界?
.cursor和.dream_cursor分别追踪什么?modelOverride为什么必须标记为待实现?