📑 本文目录
1. 核心问题:记忆膨胀如何解决
2. 三层记忆架构
3. 分层记忆加载详解
4. Tier 3 记忆的唤醒机制
5. 什么样的记忆会进入长期记忆
6. 📝 总结
随着使用时间越长,nanobot 积累的记忆会越来越多。如果不加控制,每次对话的上下文会越来越长,最终导致:
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: System Prompt (每次请求都包含) │
│ ├── 核心身份 (identity) │
│ ├── Bootstrap 文件 (AGENTS.md, SOUL.md, USER.md 等) │
│ ├── 长期记忆 MEMORY.md (完整内容) │
│ └── 今日笔记 (当天记忆,完整内容) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: Conversation History (限制数量) │
│ └── 最近 max_messages=50 条对话历史 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: Persistent Storage (不直接送入 LLM) │
│ ├── 历史日期记忆文件 (memory/2024-01-15.md...) │
│ └── 历史对话记录 (sessions/*.jsonl) │
└─────────────────────────────────────────────────────────────┘
历史截断机制 (session/manager.py):
def get_history(self, max_messages: int = 50) -> list[dict[str, Any]]:
# 只取最近 50 条消息
recent = self.messages[-max_messages:] if len(self.messages) > max_messages else self.messages
return [{"role": m["role"], "content": m["content"]} for m in recent]
完整加载特点:直接嵌入 System Prompt,LLM 立即可见
内容 | 加载方式 |
|---|---|
对话历史 | 最多 50 条 |
特点:作为 messages 传入,旧消息自动丢弃
内容 | 加载方式 |
|---|---|
历史日期记忆文件 | 不自动加载 |
历史对话记录 | 不自动加载 |
特点:需要时通过 read_file 工具按需读取
分层记忆加载是指 nanobot 根据记忆的重要性和时效性,采用不同的加载策略,而不是把所有记忆都塞进 LLM 的上下文。
用户提问: "上周我让你记了什么?"┌────────────────────────────────────────────────────────────┐
│ Step 1: 构建上下文 │
│ System Prompt: 包含 MEMORY.md + 今日笔记 │
│ History: 最近 50 条对话 │
│ │
│ → 此时**不包含**上周的记忆文件 │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ Step 2: LLM 决定需要读取历史记忆 │
│ LLM 调用: read_file(path="memory/2024-01-15.md") │
│ │
│ → 通过工具**按需加载**特定日期的记忆 │
└────────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────────┐
│ Step 3: 记忆内容加入上下文 │
│ Tool Result: 上周记忆的内容 │
│ LLM 基于读取的内容回答用户 │
└────────────────────────────────────────────────────────────┘
层级 | 类比人类大脑 | 设计原因 |
|---|---|---|
Tier 1 | 工作记忆 | 关键信息需要 LLM 随时知道 |
Tier 2 | 短期记忆 | 对话连续性需要,但旧对话相关性递减 |
Tier 3 | 长期存储 | 历史信息可能有用也可能没用,让 LLM 自己判断 |
Tier 3 记忆是"被动唤醒"的 —— 它静静地躺在文件系统里,只有当 LLM 通过推理认为"我需要这个信息"时,才会通过
read_file工具被唤醒。
用户: "帮我查一下上周三我记了什么"
↓
LLM 推理: 用户询问历史记忆 → 需要读取特定日期文件
↓
调用: read_file(path="memory/2024-01-10.md")
用户: "根据之前的项目计划,接下来该做什么?"
↓
LLM 推理: 当前上下文没有项目计划 → 需要查找 MEMORY.md 或历史笔记
↓
调用: read_file(path="memory/MEMORY.md")
或: list_dir(path="memory") → 查看有哪些历史文件
场景: MEMORY.md 中有一条记录:
"重要: 所有 API 密钥都保存在 ~/secrets.json"
↓
用户: "我需要更新 API 密钥"
↓
LLM 看到 MEMORY.md 中的指引 → 知道要去读取 ~/secrets.json
↓
调用: read_file(path="~/secrets.json")
方案 | 优点 | 缺点 |
|---|---|---|
❌ 自动检索相关记忆 | 无需 LLM 推理 | 增加复杂性,可能召回不相关内容 |
✅ LLM 自主决定 (nanobot) | 简单可靠,精准召回 | 需要 LLM 有推理能力 |
nanobot 的长期记忆 (MEMORY.md) 采用开放式设计,通过 System Prompt 引导 + LLM 自主判断 来决定什么该记住。
# context.py
"When remembering something, write to {workspace_path}/memory/MEMORY.md"
类别 | 示例 | 为什么进长期记忆 |
|---|---|---|
用户身份信息 | "用户叫张三,是软件工程师" | 跨会话都需要知道 |
个人偏好 | "用户喜欢用 Python,不喜欢 Java" | 影响后续回答风格 |
重要事实 | "用户的 OpenAI API key 在 ~/.key" | 需要持久保存 |
项目背景 | "正在开发一个电商网站" | 多轮对话的上下文基础 |
关键决策 | "决定使用 PostgreSQL 而不是 MySQL" | 避免重复讨论 |
约束条件 | "代码必须兼容 Python 3.9" | 影响所有后续代码生成 |
┌─────────────────────────────────────────────────────────────────┐
│ 长期记忆 (MEMORY.md) 每日笔记 (YYYY-MM-DD.md) │
├─────────────────────────────────────────────────────────────────┤
│ • 跨会话持久 • 当天有效 │
│ • 需要显式决定写入 • 自动追加 │
│ • 结构化组织 (User/Preferences) • 时间线式记录 │
│ • 每次对话都加载 • 只加载当天 │
│ • 适合: 事实、偏好、配置 • 适合: 临时想法、当日待办 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 方式 1: LLM 自主决定写入 │
├─────────────────────────────────────────────────────────────────┤
│ 用户: "我叫李四,以后就这么叫我吧" │
│ ↓ │
│ LLM 推理: 这是重要身份信息 → 应该写入长期记忆 │
│ ↓ │
│ 调用: write_file(path="memory/MEMORY.md", content="...") │
└─────────────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────────────┐
│ 方式 2: 用户显式要求 │
├─────────────────────────────────────────────────────────────────┤
│ 用户: "记住我的邮箱是 li@example.com" │
│ ↓ │
│ LLM 推理: 用户要求记住 → 写入 MEMORY.md │
│ ↓ │
│ 调用: edit_file 或 write_file 更新 MEMORY.md │
└─────────────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────────────┐
│ 方式 3: 从每日笔记提炼 │
├─────────────────────────────────────────────────────────────────┤
│ 场景: 今天讨论了一整天的技术方案 │
│ ↓ │
│ LLM 推理: 今日笔记中有重要结论 → 提炼到长期记忆 │
│ ↓ │
│ 读取今天的笔记 → 总结关键点 → 追加到 MEMORY.md │
└─────────────────────────────────────────────────────────────────┘
"长期记忆是用户的第二大脑,不是日志"
nanobot 的记忆系统通过分层设计解决了记忆膨胀问题:
这种设计让 nanobot 可以积累无限多的历史记忆,而每次请求的 token 消耗始终保持在可控范围内。