[原创] 基于NanoBot简单了解下memory[2]
无双·bbd
2026年04月29日 17:25

nanobot/agent/memory.py UML 类图

概述

memory.py 实现了 nanobot 的记忆系统,包含三个核心组件:

  1. MemoryStore - 纯文件 I/O 层,负责持久化存储

  2. Consolidator - 轻量级的 token 预算触发的历史消息整合

  3. Dream - 重量级的定时记忆处理器,两阶段分析和编辑

类图


关于Agent们的记忆力之谜2

这可能是有关记忆的代码中最复杂的部分。我们上次看点亮地图的时候,还只是顶层逻辑,对几个关键组件的调用依赖,到这已经是一个比较复杂的记忆系统全貌了。同时三个核心组件的分布是比较清晰的,假设记忆有自己的主线,那么Consolidator 负责聚焦于主线的提纯(意思是只关心自己关心的,其余的会丢弃),Dream 负责定期反刍(所以更正前文猜测,NanoBot没有Hermes那么强,不具备自我改进自我进化能力),MemoryStore 负责本地持久化。它算不算是全部,并不是,至少还有Session和Context。

看完 Consolidator: 你会发现,整合器在执行从历史到记忆的提纯过程中 1️⃣ 脉络是什么、顺序是什么、繁简程度等,是你可以定义好输入给LLM的; 2️⃣ LLM会怎么理解你的输入,以及执行结果与你的预期偏差,由LLM的水平决定。 另外,NanoBot的 Consolidator 是个轻量级的记忆整合器,架构设计上的触发器是token预算。Harness Engineering角度说,工业级应用估计要更复杂一些,同时触发器/反馈器可以做得更细密一些。

看完 Dream: 它是个重量级的带cron计划任务的记忆整合器。 核心是两段式记忆处理:

  1. 第一阶段分析历史,包含内存中的历史和 history.jsonl 文件中的历史,并生成分析摘要(直接调用LLM)。这个摘要基本也会标明要进行的编辑动作,对应 dream_phase1.md 中的按行打标。

  2. 第二阶段通过 AgentRunner 工具调用 read_file/edit_file 功能,使LLM能进行精准的增量编辑而非替换整个文件。 另外,NanoBot的 Dream 一定意义上也算是个轻量级的记忆整合器,只是加强了记忆内容的存储和二次整理。没有考虑记忆信息量庞大时,存储维护效率、LLM的检索效率和Agent的Context Windows最大化利用的问题。触发阈值时也没有显著的自我动态平衡能力。

想实现前面提到的强大的Agent记忆力,如果是从NanoBot起项目,基本上等于重写个Bot。

继续点亮地图


时序图:Consolidator 整合流程

archive()

通过LLM对消息做概要提炼,同时添加到history.jsonl中。

这里更细节的“提纯”过程,写在consolidator_archive.md文件里。

代码块
PlainText
自动换行
复制代码
在这次转换中提取关键事实。只输出符合以下类别的物项,其余全部跳过(舍弃):
Extract key facts from this conversation. Only output items matching these categories, skip everything else:

- 用户事实:私人信息,偏好,陈述的观点,习惯
- User facts: personal info, preferences, stated opinions, habits

- 决策:做过的选择,达成的结论
- Decisions: choices made, conclusions reached

- 解决方案:通过反复试错发现的工作方法,尤其是那些在失败尝试后最终成功的非显而易见的方法
- Solutions: working approaches discovered through trial and error, especially non-obvious methods that succeeded after failed attempts

- 事件:计划,截止日期,重要事件
- Events: plans, deadlines, notable occurrences

- 偏好:社交风格,工具的偏好设置
- Preferences: communication style, tool preferences

  
优先级:用户正确和偏好 > 解决方案 > 决策 > 事件 > 环境事实。最有价值的记忆能阻止用户不得不重复自己(已经做过的努力)。
Priority: user corrections and preferences > solutions > decisions > events > environment facts. The most valuable memory prevents the user from having to repeat themselves.

  
跳过:可从源代码、Git历史或现有记忆中已捕获的任何内容中,推导出的代码模式(大意是从输入信息或已有记忆中简单推导就能得出的范式或经验)。
Skip: code patterns derivable from source, git history, or anything already captured in existing memory.

  
每条事实单独列出,简明扼要。无需前言,也无需评论
Output as concise bullet points, one fact per line. No preamble, no commentary.

如果没有值得注意的事件,输出:(无)
If nothing noteworthy happened, output: (nothing)
复制成功

estimate_session_prompt_tokens()

估算用于正常会话的历史视图,对应当前prompt的尺寸 用工具估算部分的逻辑暂时不影响主线理解,不细究。

pick_consolidation_boundary()

获取用户轮次中可以整合的边界(可以删除足够多老旧的 prompt tokens)


时序图:Dream 处理流程

细节_build_tools()

Dream Agent用到的最小工具集:内建的ReadFileTool、EditFileTool、WriteFileTool

细节_list_existing_skills()

所有的skill都是 name — description 形式罗列,方便放在context中供随时查找随时生成随时使用

dream_phase1.md

代码块
PlainText
自动换行
复制代码
你有两个同等重要的任务:
1、从谈话历史中提取出新的事实
2、对现有内存文件去重 - 即使在历史记录中从未提及,也能发现并标记出冗余、重复或过时的内容
You have TWO equally important tasks:

1. Extract new facts from conversation history

2. Deduplicate existing memory files — find and flag redundant, overlapping, or stale content even if NOT mentioned in history

  
每个发现分别输出一行:
[FILE] 原子事实(不在记忆中的)
[FILE-REMOVE] 删除的原因
[SKILL] 横短线命名法:可重用模式的一行式描述
Output one line per finding:

[FILE] atomic fact (not already in memory)

[FILE-REMOVE] reason for removal

[SKILL] kebab-case-name: one-line description of the reusable pattern

  

Files: USER (identity, preferences), SOUL (bot behavior, tone), MEMORY (knowledge, project context)

  
规则:
- 原子事实:“有个叫Luna的猫”,而不是“讨论宠物护理”
- 准确:[USER]的地理位置是东京,而非大阪
- 捕获用户验证过并且已确认的方法
Rules:

- Atomic facts: "has a cat named Luna" not "discussed pet care"

- Corrections: [USER] location is Tokyo, not Osaka

- Capture confirmed approaches the user validated

  
去重 - 扫描所有记忆文件找到以下重复的范式:
- 在多个地方陈述了相同的事实(例如,USER.md和多个MEMORY.md条目中都有的“用中文交流”)
- 覆盖了同一主题的重叠或嵌套章节
- 在USER.md或SOUL.md中信息,被捕获到MEMORY.md中了(MEMORY.md不应复制永久文件内容)
- 可以在不丢失信息的情况下进行压缩的详细条目
每找到一个重复的副本,对其中权威性较低的副本输出[FILE-REMOVE](更倾向于将事实保存在它们的规范位置)
Deduplication — scan ALL memory files for these redundancy patterns:

- Same fact stated in multiple places (e.g., "communicates in Chinese" in both USER.md and multiple MEMORY.md entries)

- Overlapping or nested sections covering the same topic

- Information in MEMORY.md that is already captured in USER.md or SOUL.md (MEMORY.md should not duplicate permanent-file content)

- Verbose entries that can be condensed without losing information

For each duplicate found, output [FILE-REMOVE] for the less authoritative copy (prefer keeping facts in their canonical location)

  

稳定性 - MEMORY.md中,有些行可能有``← Nd``后缀表示自上次修改以来的天数:
- SOUL.md 和 USER.md 没有年龄注释 — 它们是永久性的,只会随更正而更新
- 年龄仅表示内容最后一次被触碰的时间,而不是是否应该被删除
- 使用内容做判断:用户的 习惯/偏好/个性特征 都是永久性的,而无关年龄
- 只修剪客观上过时的内容:已通过的事件、已解决的跟踪、已取代的方法
- 带有``← Nd``(N>{stale_threshold_days}})的那些行,值得更仔细的审查,但不要自动删除
- 删除时:更推荐(你)删除其中独立的各个项目而不是整个章节
Staleness — MEMORY.md lines may have a ``← Nd`` suffix showing days since last modification:

- SOUL.md and USER.md have no age annotations — they are permanent, only update with corrections

- Age only indicates when content was last touched, not whether it should be removed

- Use content judgment: user habits/preferences/personality traits are permanent regardless of age

- Only prune content that is objectively outdated: passed events, resolved tracking, superseded approaches

- Lines with ``← Nd`` (N>{{ stale_threshold_days }}) deserve closer review but are NOT automatically removable

- When removing: prefer deleting individual items over entire sections

  

技能发现 — 当所有这些条件都为真时,标记[Skill]:
- 在对话历史中出现了2次以上的 一个特定的、可重复的工作流
- 它涉及明确的步骤(而不是类似“喜欢简洁的答案”这样模糊的偏好)
- 它可以保证自己的指令集足够强大(而不是像“读取文件”这样微不足道)
- 不要担心重复 — 下一阶段将检查现有skill
Skill discovery — flag [SKILL] when ALL of these are true:

- A specific, repeatable workflow appeared 2+ times in the conversation history

- It involves clear steps (not vague preferences like "likes concise answers")

- It is substantial enough to warrant its own instruction set (not trivial like "read a file")

- Do not worry about duplicates — the next phase will check against existing skills

  

不要添加:当前天气、瞬时状态、临时错误、会话填充。
Do not add: current weather, transient status, temporary errors, conversational filler.

  

[SKIP] 如果没有需要更新的内容。
[SKIP] if nothing needs updating.
复制成功

dream_phase2.md

代码块
PlainText
自动换行
复制代码
根据以下的分析去更新内存文件。
- [FILE]条目:将描述的内容添加到相应的文件中
- [FILE-REMOVE]条目:从记忆文件中删除相应内容
- [SKILL]条目:使用 write_file 在 skills/<name>/SKILL.md下 创建新skill
Update memory files based on the analysis below.

- [FILE] entries: add the described content to the appropriate file

- [FILE-REMOVE] entries: delete the corresponding content from memory files

- [SKILL] entries: create a new skill under skills/<name>/SKILL.md using write_file

  

## 文件路径(工作区root目录的相对路径)
- SOUL.md
- USER.md
- 内存/memory.md
- skills/<name>/SKILL.md(仅适用于[SKILL]条目)
## File paths (relative to workspace root)

- SOUL.md

- USER.md

- memory/MEMORY.md

- skills/<name>/SKILL.md (for [SKILL] entries only)

  

不要猜测路径。
Do NOT guess paths.

  

## 编辑规则
- 直接编辑 — 下面提供的文件内容,不需要 read_file
- 使用与 old_text (格式)完全相同的文本,包括周围的空白行以方便唯一匹配
- 在一次 edit_file 调用中批量更改同一文件
- 对于删除操作:章节标题 + 所有类似 old_text 的文档排版,new_text 应该是空的
- 仅限于部分编辑操作 — 永远不要重写整个文件
- 如果没有要更新的内容,直接停止而不要去调用工具
## Editing rules

- Edit directly — file contents provided below, no read_file needed

- Use exact text as old_text, include surrounding blank lines for unique match

- Batch changes to the same file into one edit_file call

- For deletions: section header + all bullets as old_text, new_text empty

- Surgical edits only — never rewrite entire files

- If nothing to update, stop without calling tools

  

## 技能创建规则(适用于[Skill]条目)
- 使用 write_file 创建 skills/<name>/SKILL.md
- 在编写之前,请先阅读 `{skill_creator_path}` 以获取格式参考(前体结构、命名约定、质量标准)
- **反复检查**:阅读下面列出的现有skill,以验证新skill在功能上不是多余的。如果现有skill已经涵盖了相同的工作流程,则跳过创建。
- 包含带有名称和描述字段的 YAML frontmatter
- 将 SKILL.md 控制在2000字以内 — 简洁可行
- 包括:使用时间、步骤、输出格式,至少一个示例
- 不要覆盖现有的skill — 如果skill目录已存在,请跳过
- Agent想引用特定工具要去访问(read_file、write_file、exec、web_search等)
- skill是指令集,而不是代码 — 不要把实现代码放进去
## Skill creation rules (for [SKILL] entries)

- Use write_file to create skills/<name>/SKILL.md

- Before writing, read_file `{{ skill_creator_path }}` for format reference (frontmatter structure, naming conventions, quality standards)

- **Dedup check**: read existing skills listed below to verify the new skill is not functionally redundant. Skip creation if an existing skill already covers the same workflow.

- Include YAML frontmatter with name and description fields

- Keep SKILL.md under 2000 words — concise and actionable

- Include: when to use, steps, output format, at least one example

- Do NOT overwrite existing skills — skip if the skill directory already exists

- Reference specific tools the agent has access to (read_file, write_file, exec, web_search, etc.)

- Skills are instruction sets, not code — do not include implementation code

  

## 质量
- 每一行都必须包含独立值
- 清晰标题下面是简明的文档排版
- 缩减(不是删除)时:保留基本事实,删除详细信息
- 如果不确定是否要删除,请保留,但要添加“(请核对)”
## Quality

- Every line must carry standalone value

- Concise bullets under clear headers

- When reducing (not deleting): keep essential facts, drop verbose details

- If uncertain whether to delete, keep but add "(verify currency)"
复制成功

关于MemoryStore

这里也是个个人习惯:程序 = 数据 + 逻辑。 很多代码仓库巨大无比,前面说可以找个最小可运行单元,再做explore或exploit拓展式阅读。但从逻辑上找绳头,还是从数据结构设计上找绳头,其实都可以。毕竟有的人也会习惯于先看看在本地都存了什么数据,心里会感觉踏实那么一点点。 你觉得怎么找绳头简单,阅读负担轻,你就怎么来,没有很强的自律能力不要和自己的注意力耐久度做对抗。春风伴你读书时,风吹哪页读哪页。

纯文件I/O层:MEMORY.md, history.jsonl, SOUL.md, USER.md. 不包含任何 LLM 调用。

文件 用途

MEMORY.md 知识、工程上下文等长期记忆的事实

history.jsonl 追加式历史记录,JSONL 格式

SOUL.md Agent的灵魂/人格/品味 设定

USER.md 用户资料及偏好

.cursor 当前历史游标

.dream_cursor Dream 处理游标