Clawdbot 是一个开源的个人 AI 助手(MIT 许可证),由 Peter Steinberger 创建。和运行在云端的 ChatGPT 或 Claude 不同,Clawdbot 跑在你自己的电脑上,并且能接入你已经在用的聊天平台,比如 钉钉、飞书 等。
Clawdbot 真正出圈的点,是它能自主完成现实世界里的任务:管理邮件、创建日历日程、帮你做航班值机、按计划运行后台任务……但最吸引我的,是它的持久化记忆系统:它能 24/7 保留上下文,记住你们聊过的内容,并且能在之后的互动里无限叠加、持续利用。
Clawdbot 的路线:它不把记忆放在云端、交给公司控制,而是把一切都放在本地——用户完全拥有自己的上下文与技能。
下面我们拆开看看它是怎么做到的。
上下文是怎么拼出来的
在深入记忆之前,先搞清楚:每次请求时,模型到底看到了什么:
[0] System Prompt(系统提示词:静态 + 条件指令)
[1] Project Context(项目上下文:启动文件,如 AGENTS.md、SOUL.md 等)
[2] Conversation History(对话历史:消息、工具调用、压缩摘要)
[3] Current Message(当前消息)系统提示词决定了智能体的能力边界与可用工具。和记忆最相关的是 Project Context:它会把一组用户可编辑的 Markdown 文件注入到每一次请求里:
这些文件和记忆文件一起放在智能体的工作目录里,因此整个智能体的配置是透明、可见、可编辑的。
Context vs Memory
上下文是模型在单次请求里能看到的一切:
上下文 = 系统提示词 + 对话历史 + 工具结果 + 附件上下文的特点是:
• 短暂:只为这一次请求存在 • 有限:受模型上下文窗口限制(比如 200K tokens) • 昂贵:每个 token 都会影响 API 成本与速度
记忆则是写入磁盘的内容:
记忆 = MEMORY.md + memory/*.md + 会话转录(transcripts)记忆的特点是:
• 持久:重启也不会丢,能跨天、跨月 • 无限:可以一直增长 • 便宜:存到本地不增加 API 成本 • 可搜索:会被索引,支持语义检索
记忆工具
智能体通过两个专门的工具来访问记忆:
memory_search
用途:在所有记忆文件里找相关内容
{
"name": "memory_search",
"description": "强制回忆步骤:在回答有关过去工作/决策/日期/人物/偏好/待办的问题前,先对 MEMORY.md + memory/*.md 做语义搜索",
"parameters": {
"query": "我们关于 API 做了什么决定?",
"maxResults": 6,
"minScore": 0.35
}
}返回:
{
"results": [
{
"path": "memory/2026-01-20.md",
"startLine": 45,
"endLine": 52,
"score": 0.87,
"snippet": "## API 讨论\n为了简单起见,决定用 REST 而不是 GraphQL……",
"source": "memory"
}
],
"provider": "openai",
"model": "text-embedding-3-small"
}memory_get
用途:在 memory_search 找到线索后,读取具体内容
{
"name": "memory_get",
"description": "在 memory_search 之后,从某个记忆文件里读取指定行数的内容",
"parameters": {
"path": "memory/2026-01-20.md",
"from": 45,
"lines": 15
}
}返回:
{
"path": "memory/2026-01-20.md",
"text": "## API 讨论\n\n和团队开会讨论 API 架构。\n\n### 决策\n我们选择 REST 而不是 GraphQL,原因如下:\n1. 更容易实现\n2. 更利于缓存\n3. 团队更熟悉\n\n### 端点\n- GET /users\n- POST /auth/login\n- GET /projects/:id"
}写入记忆
Clawdbot 没有专门的 memory_write 工具。智能体写入记忆时,用的就是它平时写文件的标准 write 和 edit 工具。因为记忆本质就是 Markdown,你也可以手动改这些文件(系统会自动重新索引)。
至于该写到哪里,由 AGENTS.md 里的提示词规则来驱动:
此外,在压缩(compaction)前的 flush以及会话结束时,也会发生自动写入(后面会讲)。
记忆存在哪里
Clawdbot 的记忆系统建立在一个核心原则上:记忆就是智能体工作区里的一堆纯 Markdown。
Two-Layer Memory System
记忆文件放在智能体的工作区里(默认:~/clawd/):
~/clawd/
├── MEMORY.md - 第 2 层:整理过的长期记忆
└── memory/
├── 2026-01-26.md - 第 1 层:今天的日记
├── 2026-01-25.md - 昨天的日记
├── 2026-01-24.md - ...以此类推
└── ...Layer 1: Daily Logs (memory/YYYY-MM-DD.md)
这是只追加的日记式笔记。智能体会在一天中不断往里面写:当它觉得某件事值得记住,或者你明确让它记住时,它就写这里。
# 2026-01-26
## 10:30 AM - API 讨论
和用户讨论 REST vs GraphQL。决策:为了简单,选择 REST。
关键端点:/users、/auth、/projects。
## 2:15 PM - 部署
将 v2.3.0 部署到生产环境。没有问题。
## 4:00 PM - 用户偏好
用户提到自己更喜欢 TypeScript 而不是 JavaScriptLayer 2: Long-term Memory (MEMORY.md)
这是被整理过的长期知识库。只有当发生重要事件、关键决策、观点、经验教训时,智能体才会把内容写进这里。
# 长期记忆
## 用户偏好
- 更喜欢 TypeScript 而不是 JavaScript
- 喜欢简洁的解释
- 正在做一个名为 "Acme Dashboard" 的项目
## 重要决策
- 2026-01-15:数据库选用 PostgreSQL
- 2026-01-20:采用 REST 而不是 GraphQL
- 2026-01-26:使用 Tailwind CSS 做样式
## 关键联系人
- Alice (alice@acme.com) - 设计负责人
- Bob (bob@acme.com) - 后端工程师How the Agent Knows to Read Memory
AGENTS.md(会被自动加载)里包含了这样的指令:
## 每次会话(Every Session)
在做任何事之前:
1. 读 SOUL.md - 这决定你是谁
2. 读 USER.md - 这决定你在帮助谁
3. 读 memory/YYYY-MM-DD.md(今天和昨天)获取最近上下文
4. 如果是 MAIN SESSION(直接和人类对话的主会话),也要读 MEMORY.md
不要征求许可,直接做。翻成人话就是:每次会话开始,先把我是谁,我在帮谁,最近发生了啥(今天/昨天)读一遍;如果是主会话,再把长期记忆(MEMORY.md)也读上。
记忆如何被索引
当你保存了一条记忆文件,背后会发生这些事:
1. 文件保存(例如 ~/clawd/memory/2026-01-26.md)2. 文件监听器检测变化:用 Chokidar 监听 MEMORY.md + memory/**/*.md,并用 1.5 秒的 debounce 把频繁写入攒一攒再处理3. 切块(Chunking):把文本切成大约 400 token 的块,并做 80 token 的重叠 4. 向量化(Embedding):每个 chunk 送给 embedding 提供方(OpenAI / Gemini / 本地)生成向量(示例里是 1536 维) 5. 存储:写入 ~/.clawdbot/memory/<agentId>.sqlite,其中包括 chunks 表、向量表、全文检索表、以及 embedding 缓存
文章里还提到:
sqlite-vec 是一个 SQLite 扩展,让你可以在 SQLite 里直接做向量相似度检索,不需要额外的向量数据库。
FTS5 是 SQLite 内置的全文检索引擎,支持 BM25 关键词匹配。两者结合后,Clawdbot 可以用同一个轻量数据库文件做混合检索(语义 + 关键词)。
How Memory is Searched
当你搜记忆时,Clawdbot 会并行跑两套检索:
• 向量检索(语义):找意思相近的内容 • BM25(关键词):找字面命中的 token
然后按权重融合:
最终得分 = 0.7 * 向量得分 + 0.3 * 文本得分为什么是 70/30?因为语义相似度是主要信号,但 BM25 能兜住向量可能漏掉的精确项(人名、ID、日期等)。低于 minScore(默认 0.35)的结果会被过滤掉。这些值都可配置。
这让你既能用那个数据库的事儿这种模糊描述去搜,也能用 POSTGRES_URL这种精确关键词去搜。
多智能体记忆:彼此隔离
Clawdbot 支持多个智能体,并且它们的记忆是完全隔离的:
~/.clawdbot/memory/ # 状态目录(索引)
├── main.sqlite # main 智能体的向量索引
└── work.sqlite # work 智能体的向量索引
~/clawd/ # main 智能体工作区(事实源文件)
├── MEMORY.md
└── memory/
└── 2026-01-26.md
~/clawd-work/ # work 智能体工作区(事实源文件)
├── MEMORY.md
└── memory/
└── 2026-01-26.mdMarkdown 文件(事实源)在各自 workspace 里;SQLite 索引(衍生数据)在 state 目录里。记忆管理器用 agentId + workspaceDir 做键,所以默认不会跨智能体检索记忆。
智能体能不能读到彼此的记忆?**默认不能。**每个智能体只看到自己的 workspace。只是要注意:workspace 更像软沙盒(默认工作目录),不是硬边界。除非你开启严格沙盒,否则智能体理论上可以用绝对路径去访问别的 workspace。
这种隔离非常适合做场景拆分:比如一个私人智能体在 WhatsApp 上陪你,另一个工作智能体在 Slack 上干活——各自有独立记忆与性格。
压缩:对话太长怎么办
每个模型都有上下文窗口上限:Claude 是 200K tokens,GPT-5.1 是 1M。对话变长后,总会撞墙。
这时 Clawdbot 会做 compaction(压缩/精简):把更早的对话总结成一段浓缩摘要,同时保留最近的消息原样。
┌─────────────────────────────────────────────────────────────┐
│ 压缩前(Before Compaction) │
│ 上下文:180,000 / 200,000 tokens │
│ │
│ [Turn 1] 用户:"我们来做个 API" │
│ [Turn 2] 智能体:"好呀!你需要哪些端点?" │
│ [Turn 3] 用户:"用户和鉴权" │
│ [Turn 4] 智能体:*生成 500 行 schema* │
│ [Turn 5] 用户:"加上限流" │
│ [Turn 6] 智能体:*改代码* │
│ ...(又聊了 100 多轮)... │
│ [Turn 150] 用户:"现在进度咋样?" │
│ │
│ ⚠️ 快到上限了(APPROACHING LIMIT) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 触发压缩(Compaction Triggered) │
│ │
│ 1. 将第 1-140 轮总结成一段精简摘要 │
│ 2. 保留第 141-150 轮原样不动(最近上下文) │
│ 3. 把摘要写入 JSONL 会话转录(transcript) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 压缩后(After Compaction) │
│ 上下文:45,000 / 200,000 tokens │
│ │
│ [摘要] "用 /users、/auth 端点搭了 REST API。 │
│ 做了 JWT 鉴权、限流(100 次/分钟)、 │
│ 用 PostgreSQL。部署到 staging v2.4.0。 │
│ 当前重点:准备上线生产。" │
│ │
│ [Turn 141-150 保留原样] │
└─────────────────────────────────────────────────────────────┘自动 vs 手动压缩
自动:接近上下文上限时触发
• verbose 模式下你会看到: Auto-compaction complete• 原请求会在压缩后自动重试
手动:用 /compact 命令
/compact 只关注关键决策与未解决的问题而且,compaction 不只是为了省 token的一次性优化:压缩摘要会写入磁盘,存到会话的 JSONL transcript 文件里,所以未来会话可以直接从压缩后的历史开始。
压缩前记忆冲刷
基于大语言模型的压缩是有损的:重要信息可能被总结抹掉甚至丢失。为了解决这个风险,Clawdbot 在压缩前会做一次 pre-compaction memory flush(压缩前记忆冲刷)。
它的机制是:当上下文使用率跨过软阈值时,系统会插入一个静默的记忆冲刷轮次:
• System:提示智能体现在把耐久的记忆写进 memory/YYYY-MM-DD.md;如果没什么可写就回复NO_REPLY• Agent:回顾对话,把关键决策/事实写入记忆文件 • 用户:看不到这轮(因为 NO_REPLY)
这样一来,真正 compaction 的时候,即使摘要有损,关键知识也已经落盘了。
┌─────────────────────────────────────────────────────────────┐
│ 上下文接近上限(Context Approaching Limit) │
│ │
│ ████████████████████████████░░░░░░░░ 上下文已用 75% │
│ ↑ │
│ 触发软阈值(Soft threshold crossed) │
│ (contextWindow - reserve - softThreshold) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 静默记忆冲刷回合(Silent Memory Flush Turn) │
│ │
│ System: "压缩前记忆冲刷。现在把需要长期保留的内容 │
│ 写到 memory/YYYY-MM-DD.md。 │
│ 如果没什么可写,就回复 NO_REPLY。" │
│ │
│ Agent:回顾对话,提取重要信息 │
│ 把关键决策/事实写入记忆文件 │
│ -> NO_REPLY(用户什么都看不到) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 安全地继续压缩(Compaction Proceeds Safely) │
│ │
│ 关键信息已写入磁盘 │
│ 压缩不会把知识压没 │
└─────────────────────────────────────────────────────────────┘这套 flush 也可在 clawdbot.yaml 或 clawdbot.json 里配置:
{
agents: {
defaults: {
compaction: {
reserveTokensFloor: 20000,
memoryFlush: {
enabled: true,
softThresholdTokens: 4000,
systemPrompt: "会话即将压缩。现在保存需要长期保留的记忆。",
prompt: "把长期价值的内容写到 memory/YYYY-MM-DD.md;若无内容可写则回复 NO_REPLY。"
}
}
}
}
}裁剪:工具输出太大怎么办
工具调用结果可能非常巨大:一次 exec 可能输出 50,000 字符的日志。裁剪会在不改写历史的前提下,把旧的工具输出削薄,以减少发送给模型的上下文体积。
它同样是有损的:旧输出被裁剪后就无法恢复。
原文用示意图展示了裁剪前后:旧工具输出被截断(soft trim)或直接清空为占位符(hard clear),但磁盘上的 JSONL 文件仍保留完整输出(不变)。
按缓存 TTL 裁剪
Anthropic 会缓存 prompt 的前缀最多 5 分钟,用于降低重复调用的延迟与成本:在 TTL 内复用缓存 token,成本大约能便宜 ~90%。但问题是:如果会话空闲超过 TTL,下一次请求就失去缓存,需要把整段上下文重新写入缓存,成本回到全价。
Cache-TTL pruning 的思路是:检测到缓存过期后,在下一次请求前把旧的工具输出裁剪掉,让需要重新缓存的 prompt 变短,从而降低成本。
示例配置:
{
agent: {
contextPruning: {
mode: "cache-ttl", // 只在缓存过期后才裁剪
ttl: "600", // 对齐你的 cacheControlTtl
keepLastAssistants: 3, // 保护最近几条助手消息(别裁掉)
softTrim: {
maxChars: 4000,
headChars: 1500,
tailChars: 1500
},
hardClear: {
enabled: true,
placeholder: "[旧工具结果内容已清空]"
}
}
}
}会话生命周期
会话不会永远持续。它会按可配置的规则重置,给记忆制造天然边界。默认行为是每天重置,但也支持其他模式:
Session Memory Hook
当你运行 /new 开启新会话时,有个 session memory hook 能自动把当前上下文保存下来:
• 从即将结束的会话里提取最近 15 条消息 • 用大语言模型生成一个描述性的 slug • 保存到 ~/clawd/memory/2026-01-26-api-design.md之类的文件• 然后新会话开始:旧上下文就可以通过 memory_search被检索到
/new
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 触发 SESSION-MEMORY HOOK │
│ │
│ 1. 从即将结束的会话里提取最近 15 条消息 │
│ 2. 用大语言模型生成一个描述性的 slug │
│ 3. 保存到 ~/clawd/memory/2026-01-26-api-design.md │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 新会话开始 │
│ │
│ 之前的上下文现在可以通过 memory_search 被检索到 │
└─────────────────────────────────────────────────────────────┘总结
Clawdbot 的记忆系统之所以好用,是因为它坚持了几条关键原则:
1. Transparency Over Black Boxes(透明胜过黑盒)
记忆就是纯 Markdown:你能读、能改、能做版本控制。没有不透明的数据库格式或专有存储。
2. Search Over Injection(检索胜过硬塞)
不是把所有历史都塞进上下文,而是按需检索相关内容。上下文更聚焦,成本也更低。
3. Persistence Over Session(落盘胜过只活在会话里)
重要信息写进磁盘文件,而不是只留在对话历史里。只要已经保存,compaction 就毁不掉它。
4. Hybrid Over Pure(混合胜过单一)
纯向量检索会漏掉精确匹配;纯关键词检索会漏掉语义。混合检索两者都要。
参考
• Clawdbot Documentation - 官方文档:安装、配置与全部特性 • GitHub Repository - 源码、issue 与社区贡献 • How Clawdbot Remembers Everything