Clawdbot是如何管理记忆的

Published

Tags: AI

大模型是没有记忆的。对于大模型而已,你发给它的每条信息本身的内容就是它下一个回复的所有输入。你和它的历史聊天信息大模型本身是不存储的。当然,这样的大模型可以完成的任务是很局限的。因此我们必须让大模型具有记忆,能够和我们实现对话,理解上下文。记忆管理就是每个agent需要精心设计的。

这篇文章大致说说OpenClawd(Moltbot或Clawdbot)是如何实现记忆功能的。总的来说,OpenClawd的记忆实现并不复杂,也没有太多创新,但因为它的实现方式和ChatGPT或Claude很不一样,算是开宗立派的打法,因此学习一下还是很有必要的。我认为OpenClawd最重要的创新就是让记忆回到每个用户手上,完全透明,完全可配置。用户如果需要,可以直接修改记忆本身,或者通过参数调整记忆的工作方式。这一点和ChatGPT或Claude这种把记忆限制在服务端,对用户不透明的做法更民主。说到底,智慧可以属于大厂,但记忆和上下文应该属于用户。我希望OpenClawd开创的记忆回归用户的做法未来会变得更加普遍。

从头说起。我们说了用户的每次对话内容就是模型回复的所有输入信息,那么我们需要了解我们给大模型发的每个信息具体有什么内容。你可能会反问我,我写的输入信息不就是内容本身了吗?其实远远不止。你可能只写了短短一句话:告诉我今天天气如何,但大模型收到的信息可能远远多于你这句话的内容。agent在接收到你的信息之后,会自动添加上很多额外信息,再将所有这些信息一起发给大模型。具体而言,每个对话请求包含一下4个层次的信息:

[0] 系统提示(System Prompt)
[1] 项目上下文(Project Cotnext, bootstrap files: AGENTS.md, SOUL.md, etc)
[2] 历史对话(messages, tool calls, compaction summaries)
[3] 用户本次发送的信息

其中系统提示词定义了agent的基本背景和角色。项目上下文从一些markdown文件中抽取并添加到每个对话中。用户可以很容易地修改这些markdown文件,从而控制项目上下文的内容。这些markdown文件包括:

上下文和记忆的区别 #

上下文和记忆是有区别的。

上下文是指你向大模型提交的一次对话信息中,除了你直接提供的信息之外的全部内容。在Clawdbot中,上下文 = 系统提示 + 对话历史 + 工具调用结果 + 附件

上下文具有以下特点:

记忆则不同于上下文。记忆是保存在硬盘中的:记忆 = MEMORY.md memory/*.md + Session Transcripts

记忆的特点:

Clawdbot如何访问记忆 #

在Clawdbot中,agent通过两个特殊工具访问记忆。第一个工具是memory_search

这个工具的目的是在记忆文件中找到相关的记忆。中Clawdbot中,这个工具的调用方式如下:

{
  "name": "memory_search",
  "description": "Mandatory recall step: semantically search MEMORY.md + memory/*.md before answering questions about prior work, decisions, dates, people, preferences, or todos",
  "parameters": {
    "query": "What did we decide about the API?",
    "maxResults": 6,
    "minScore": 0.35
  }
}

其返回可以是这样的:

{
  "results": [
    {
      "path": "memory/2026-01-20.md",
      "startLine": 45,
      "endLine": 52,
      "score": 0.87,
      "snippet": "## API Discussion\nDecided to use REST over GraphQL for simplicity...",
      "source": "memory"
    }
  ],
  "provider": "openai",
  "model": "text-embedding-3-small"
}

第二个工具是:memory_get

其功能是读取特定记忆的内容。这个工具一般是基于memory_search的结果进一步操作的:

{
  "name": "memory_get",
  "description": "Read specific lines from a memory file after memory_search",
  "parameters": {
    "path": "memory/2026-01-20.md",
    "from": 45,
    "lines": 15
  }
}

其返回结果可能是:

{
  "path": "memory/2026-01-20.md",
  "text": "## API Discussion\n\nMet with the team to discuss API architecture.\n\n### Decision\nWe chose REST over GraphQL for the following reasons:\n1. Simpler to implement\n2. Better caching\n3. Team familiarity\n\n### Endpoints\n- GET /users\n- POST /auth/login\n- GET /projects/:id"
}

Clawdbot如何生成记忆 #

并不存在类似memory_write这样的特定工具。Clawdbot直接使用writeedit这样的通用文件操作工具写入记忆。记忆本身就是简单的markdown格式,因此你可以很方便地编辑记忆文件。每次编辑完毕后,记忆索引会自动更新。

这里最重要的决策是记忆保存到哪里。这个决策是在AGENTS.md文件中定义好的:

生成记忆的时间点也很关键。记忆一般是自动生成的。触发记忆生成的条件可以是上下文压缩时,或者对话结束时。

记忆的存储 #

Clawdbot的记忆系统的设计理念是“记忆就是markdown文本,保存在agent的workspace文件夹中”。

两层记忆文件结构 #

记忆文件保存在agent的workspace目录下(默认路径是~/clawd/):

~/clawd/
|- Memory.md # 第二层:长期记忆
└─ memory/
    |- 2026-01-26.md  # 第一层:日志
    |- 2026-01-25.md  
    |- 2026-01-24.md
    └─ ...

第一层:日志(memory/YYYY-MM-DD.md#

这些日志由agent每天写入。一般只添加不删除。每当agent认为需要记下某些信息,或者用户明确要求agent记忆某些信息时,agent会自动将这些信息写入日志。日志可以是如下内容。

# 2026-01-26

## 10:30 AM - API Discussion
Discussed REST vs GraphQL with user. Decision: use REST for simplicity.
Key endpoints: /users, /auth, /projects.

## 2:15 PM - Deployment
Deployed v2.3.0 to production. No issues.

## 4:00 PM - User Preference
User mentioned they prefer TypeScript over JavaScript.

第二层:长期记忆(MEMORY.md#

长期记忆是精心编撰的、持久的记忆和知识。每当有重大事件、想法、决策、意见或教训时,agent就会把它们编撰成长期记忆。长期记忆类似如下:

# Long-term Memory

## User Preferences
- Prefers TypeScript over JavaScript
- Likes concise explanations
- Working on project "Acme Dashboard"

## Important Decisions
- 2026-01-15: Chose PostgreSQL for database
- 2026-01-20: Adopted REST over GraphQL
- 2026-01-26: Using Tailwind CSS for styling

## Key Contacts
- Alice (alice@acme.com) - Design lead
- Bob (bob@acme.com) - Backend engineer

Agent如何知道要读取记忆 #

记忆的使用说明被定义在AGENTS.md文件里。每次对话这个文件都会被自动加载。这个文件内容如下:

## Every Session

Before doing anything else:
1. Read SOUL.md - this is who you are
2. Read USER.md - this is who you are helping
3. Read memory/YYYY-MM-DD.md (today and yesterday) for recent context
4. If in MAIN SESSION (direct chat with your human), also read MEMORY.md

Don't ask permission, just do it.

记忆索引 #

每当记忆文件更新时,都会触发以下操作:

  1. 记忆文件更新
  2. 记忆文件监控程序检测到变动,延迟1.5秒让写入完成
  3. 将记忆内容分块:每400个tokens一个记忆块,相邻两个记忆块保留80个tokens的重叠区。每个记忆块大小为400个tokens是平衡语义连续性和颗粒度。重叠区确保跨记忆块的信息点不被切断。这两个数值均可重新设定
  4. 每个记忆块转换成向量。每个向量1536维
  5. 记忆块存储在~/.clawdbot/memory/<agentId>.sqlite中。数据库结构如下:
    • chunks (id, path, start_line, end_line, text, hash)
    • chunks_vec (id, embedding) -> sqlite-vec
    • chunks_fts (text) -> FTS5 full-text
    • embedding_cache (hash, vector) -> avoid re-embedding

sqlite-vec是一个sqlite的扩展,用于直接在sqlite中进行基于向量的模糊查询。用了sqlite-vec的支持就不需要专门的向量数据库了。

FTS5是sqlite内置的全文搜索引擎,支持BM25关键字匹配。两者配合允许Clawdbot进行基于语义和关键词的混合检索。

记忆检索 #

当查询记忆是,Clawdbot会同时执行两套检索策略。基于向量的语义检索查询意思相同的信息。基于BM25的关键字检索则尝试匹配tokens。两套策略的查询结果会进行加权平均,计算最终匹配分 = (0.7 * 语义检索匹配分) + (0.3 * 关键词检索匹配分)

加权平均的权重设置为70/30的原因是:语义检索相似度是记忆召回的主要指标。BM25的关键字匹配则可以补全一些语义检索可能漏掉的信息,比如人名、ID值、日期等。

最终匹配分低于一个阈值的搜索结果会被自动过滤掉。这个阈值默认为0.35且可以配置。

这套检索策略保证既可以找到语义相关的记忆,也不会漏掉需要精确检索的信息点。

多agents的记忆管理 #

Clawdbot支持多agents模式。在这个模式下,每个agent拥有完全隔离的记忆文件系统:

~/.clawdbot/memory/   # State directory
|- main.sqlite       # Vector index for "main" agent
└─ work.sqlite       # Vector index for "work" agent

~/clawd/              # "main" agent workspace
|- MEMORY.md
└─ memory/
    └─ 2026-01-26.md

~/clawd-work/         # "work" agent workspace
|— MEMORY.md
└─ memory/
    └─ 2026-01-26.md

每个agent有完全独立的记忆文件结构。每个agent也有完全独立的记忆块数据库,保存在Clawdbot根目录下。记忆管理操作会明确指出是哪个agent ID,在哪个workspace文件夹下进行。因此记忆检索和更新只会在每个agent自己的记忆系统中进行,默认情况下不会出现跨agents的记忆操作。

你可能会问:一个agent能否读取其他agent的记忆呢?默认情况下不行。每个agent只能访问自己的workspace。当然,workspace只是一个弱sandbox概念,并不是一个硬性边界。理论上一个agent时可以通过绝对路径访问其他agents的记忆文件系统的。除非你明确设置agent间强隔离。

这种agents间的记忆隔离设计对于不同上下文管理是很有价值的。你可以想象一个应用场景:whatsapp上有一个“私人”agent,Slack上有一个“工作”agent,且这两个agents之间的记忆是完全不同de。这样可以有效避免记忆混淆。

长对话场景下的压缩(Compaction) #

每个大模型的上下文窗口都是有限的。Claude的上下文窗口为200K个tokens,ChatGPT-5.1的上下文窗口有1M。不论上下文窗口有多大,都会被持续的长对话耗尽。

当对话太长导致历史对话信息长度临近窗口上限时,Clawdbot会使用压缩:将较老的对话压缩,同时尽量保持新近对话的完整性。以下是一个例子:

┌──────────────────────────────────────────────────────────────┐
| Before compaction |
| Context: 180,000/200,000 tokens |
| |
| [Turn 1] User: "Let's build an API" |
| [Turn 2] Agent: "Sure! What endpoints do you need?" |
| [Turn 3] User: "Users and auth" |
| [Turn 4] Agent: Creates 500-line schema |
| [Turn 5] User: "Add rate limiting" |
| [Turn 6] Agent: Modify code |
| ... |
| [Turn 150] User: "What's the status?" |
| |
| APPRAOCHING LIMIT |
└──────────────────────────────────────────────────────────────┘
|

┌──────────────────────────────────────────────────────────────┐
| Compaction Triggered |
| |
| 1. Summarize turns 1-140 into a compact summary |
| 2. Keep turns 141-150 intact (recent context) |
| 3. Persist summary to JSONL transcript |
└──────────────────────────────────────────────────────────────┘
|

┌──────────────────────────────────────────────────────────────┐
| After Compaction |
| Context: 45,000/200,000 tokens |
| |
| [SUMMARY] "Build REST API with /users, /auth endpoints. |
| Implemented JWT auth, rate limiting (100 req/min), |
| PostgreSQL database. Deployed to staging v2.4.0. |
| Current focus: production deployment prep." |
| |
| [Turn 141-150 preserved as-is] |
└──────────────────────────────────────────────────────────────┘

自动压缩 vs 手动压缩 #

当长对话内容逼近窗口大小时,自动压缩会被自动触发:

手动压缩需要用户使用/compact命令触发:

/compact Focus on decisions and open questions

与其他一些优化不同,压缩操作会将数据持久化到磁盘。摘要信息会写入会话的JSONL转录文件,因此后续会话将从压缩后的历史记录开始。

内存刷新(Memory Flush) #

基于大模型的压缩是一个有损压缩过程。重要的信息可能会被压缩丢失。为了避免这种情况,Clawdbot采用了压缩钱内存刷新机制。

当上下文逼近窗口大小时,Clawdbot会自动分析历史上下文,将重要的内容写入memory/YYYY-MM-DD.md日志,之后再进行记忆压缩。这样就避免丢失重要的记忆点。

内存刷新可以在clawdbot.yamlclawdbot.json文件中配置。

{
  agents: {
    defaults: {
      compaction: {
        reserveTokensFloor: 20000,
        memoryFlush: {
          enabled: true,
          softThresholdTokens: 4000,
          systemPrompt: "Session nearing compaction. Store durable memories now.",
          prompt: "Write lasting notes to memory/YYYY-MM-DD.md; reply NO_REPLY if nothing to store."
        }
      }
    }
  }
}

剪枝(Pruning) #

剪枝是针对某些工具返回结果太大而设计的。有时候单一exec命令可能返回50K字符的log。剪枝技术可以裁剪掉老的工具返回结果。这是一个信息损失的过程:老的工具返回结果一旦被残剪掉就无法恢复了。

┌──────────────────────────────────────────────────────────────┐
| BEFORE PRUNING (in-memory)                                   |
|                                                              |
| Tool result (exec): [50K chars of npm install output]        |
| Tool result (read): [Large config file, 10K chars]           |
| Tool result (exec): [Build logs, 30K chars]                  |
| User: "Did the build succeed?"                               |
└──────────────────────────────────────────────────────────────┘
                               |
                               ▼
┌──────────────────────────────────────────────────────────────┐
| AFTER PRUNING (sent to model)                                |
|                                                              |
| Tool result (exec): "npm WARN ...[truncated]... Succes"      |
| Tool result (read): "[Old tool result content cleared]"      |
| Toll result (exec): "[Kept - too recent to prune]"           |
| User: "Did the build succeed?"                               |
└──────────────────────────────────────────────────────────────┘

JSONL file on disk: UNCHANGED (full outputs still there)

Cache-TTL Pruning #

Anthropic 会将提示符前缀缓存最多 5 分钟,以降低重复调用时的延迟和成本。如果在 TTL 窗口内发送相同的提示符前缀,缓存的令牌成本可降低约 90%。TTL 过期后,下一个请求必须重新缓存整个提示符。

问题在于:如果会话超过 TTL 后仍处于空闲状态,则下一个请求将丢失缓存,并且必须以完整的“缓存写入”价格重新缓存完整的会话历史记录。

缓存 TTL 修剪通过检测缓存何时过期并在下次请求之前修剪旧的工具结果来解决这个问题。更小的重新缓存提示意味着更低的成本:

{
  agent: {
    contextPruning: {
      mode: "cache-ttl",      // Only prune after cache expires
      ttl: "600",              // Match your cacheControlTtl
      keepLastAssistants: 3,  // Protect recent tool results
      softTrim: {
        maxChars: 4000,
        headChars: 1500,
        tailChars: 1500
      },
      hardClear: {
        enabled: true,
        placeholder: "[Old tool result content cleared]"
      }
    }
  }
}

对话的生命周期 #

对话并不是永远持续的。对话可以根据配置规则重置。对话的重置给记忆创造了天然的边界。默认的对话重置规则是每天重置。但是也有其他可选项:

对话记忆钩子(Session Memory Hook) #

当你使用/new开始一个新的对话是,对话记忆钩子回自动保存上下文,算法如下:

┌──────────────────────────────────────────────────────────────┐
| SESSION-MEMORY HOOK TRIGGERED                                |
| 1. 获取最近15分钟内的对话信息                                    |
| 2. 利用大模型生成描述性的片段                                    |
| 3. 保存至 ~/clawd/memory/2026-02-26-api-design.md             |
└──────────────────────────────────────────────────────────────┘
                                |
                                ▼
┌──────────────────────────────────────────────────────────────┐
| NEW SESSION                                                  |
| Previous context is now searchable via memory_search         |
└──────────────────────────────────────────────────────────────┘

总结 #

Clawdbot的记忆系统之所以成功,是因为它的设计理念拥抱了一些关键原则:

  1. 选择透明而非黑盒。记忆本质上就是markdown。你可以读懂它,修改它,对它版本控制。没有晦涩的数据库或难懂的文件格式。
  2. 可检索而不是一味添加。与其把所有信息都塞进上下文,agent可以搜素它认为相关的数据,并只将相关的数据添加进上下文。这保持了上下文的关注性,也降低了成本。
  3. 可持续而不是仅在对话中存在。重要的信息被保存在磁盘文件中,而不仅仅在对话的历史记录里。压缩并不会损失已经保存到文件中的内容。且压缩的过程就会将重要的对话上下文写入磁盘文件永久保留。
  4. 多样而不是单一的记忆检索策略。仅使用向量检索可能会漏掉一些精准匹配才能获得的重要相关信息。仅使用精准匹配则可能漏掉语义相关信息。两者相结合就能完整检索到相关信息。