第2章:Agent的记忆系统
“Memory is the mother of all wisdom.” — Aeschylus
引言:为什么Agent会“健忘“?
你有没有遇到过这样的情况:
- 早上你告诉ChatGPT:“我在做一个关于气候变化的项目”,晚上再问它相关问题,它完全不记得了。
- 你让AI助手帮你规划一周的任务,第二天它已经忘记你昨天说了什么。
- 你在一个对话中详细描述了你的项目背景,但开始新对话时又要重复一遍。
这不是AI的bug,而是设计的必然。传统的对话式AI就像患有“短期失忆症“的聪明人——它很擅长当下的推理,但无法建立长期记忆。
而真正的Agent系统需要记忆。它需要记住:
- 你的偏好和习惯
- 过去做过的决策
- 进行中的任务状态
- 学到的经验和教训
本章将解决三个核心问题:
- 为什么AI天生“健忘“,以及这如何限制了它的能力?
- Agent需要哪几种类型的记忆?
- 如何用文件系统构建Agent的“第二大脑“?
读完本章,你将理解记忆系统的设计原理,并能搭建一个具有长期记忆能力的个人知识库。
2.1 为什么Agent需要记忆?
短期记忆的局限
让我们先理解AI的“记忆“是如何工作的。
当你和ChatGPT对话时,它看到的是:
[系统提示词] + [对话历史] + [你的最新输入]
这些内容被编码成tokens(可以简单理解为单词片段),然后AI基于这些tokens生成回复。这就是AI的全部“记忆“——当前对话窗口里的所有内容。
上下文窗口:AI的工作记忆
AI模型有一个上下文窗口(Context Window)限制。以Claude 3.5 Sonnet为例:
- 上下文窗口:200K tokens(约15万个英文单词)
- 听起来很大?但考虑到:
- 一本中等长度的技术书:约10万字=13万tokens
- 一个月的工作日志:约5-10万tokens
- 你的个人知识库:可能上百万tokens
结果:即使是最大的上下文窗口,也无法容纳一个人的所有知识和经验。
真实案例:上下文窗口耗尽
来看一个真实的场景:
# 某个Agent的一天
08:00 - 生成早报(消耗5K tokens)
09:00 - 处理邮件分类(消耗10K tokens)
10:00 - 代码审查(消耗20K tokens)
14:00 - 文档整理(消耗15K tokens)
18:00 - 生成日报(消耗8K tokens)
...
当晚23:00,累计tokens: 180K
问题来了:当上下文窗口接近200K时,Agent开始“遗忘“早上做的事情。就像人的工作记忆(Working Memory)只能同时处理7±2个信息块,AI也有其极限。
💡 AI辅助提示
不确定tokens是什么?问AI:
“什么是tokens?为什么它限制了AI的记忆?能用简单例子说明吗?”
长期记忆的必要性
人类如何克服工作记忆的限制?答案是长期记忆(Long-term Memory)。
人类大脑的策略:
- 选择性记忆:不是所有事都记住,只记重要的
- 压缩存储:把详细经历压缩成概念和模式
- 外部化:通过笔记本、日记、数据库扩展记忆
- 检索机制:需要时能快速调取相关记忆
Agent也需要类似的系统。
为什么长期记忆至关重要?
考虑这些场景:
场景1:个人助理Agent
# 如果只有短期记忆:
你:"提醒我明天早上9点打电话给张三"
Agent:"好的,已设置提醒"
[第二天,Agent重启]
Agent:(完全忘记了昨天的对话)
# 如果有长期记忆:
你:"提醒我明天早上9点打电话给张三"
Agent:写入 tasks.md → "2024-01-15 09:00 - 打电话给张三"
[第二天,Agent启动时读取tasks.md]
Agent:"早上好!今天9点你需要打电话给张三。"
场景2:代码审查Agent
# 如果只有短期记忆:
每次审查代码都是"第一次见"
无法学习团队的代码风格
无法记住之前提过的问题
# 如果有长期记忆:
Agent维护 style-guide.md 和 common-issues.md
记录每次审查的重点问题
下次遇到类似问题时主动提醒
逐步建立团队知识库
场景3:自我修复服务器Agent
# 如果只有短期记忆:
服务器:内存使用率95%
Agent:"检测到内存高,重启服务"
[一小时后]
服务器:内存使用率95%
Agent:"检测到内存高,重启服务"
(陷入无限循环,没有学习)
# 如果有长期记忆:
服务器:内存使用率95%
Agent:
1. 写入 incidents.log:"2024-01-15 10:00 - 内存泄漏,服务A"
2. 重启服务
3. 更新 known-issues.md:"服务A存在内存泄漏,已提issue #123"
[一小时后]
服务器:内存使用率95%
Agent:
1. 读取 incidents.log,发现重复问题
2. "检测到服务A重复内存泄漏(今天第3次)"
3. 发送告警:"紧急:服务A需要人工介入"
4. 自动回滚到上一个稳定版本
核心洞察:长期记忆让Agent从“每次从零开始“变成“持续学习和进化“。
📚 扩展阅读
想深入了解人类记忆系统?推荐《思考,快与慢》(丹尼尔·卡尼曼)中关于系统1和系统2的讨论,以及《记忆的七宗罪》(丹尼尔·夏克特)。
2.2 四种记忆类型
人类记忆研究给我们提供了很好的框架。在AI Agent设计中,我们可以借鉴类似的分类:
1. 短期记忆(Short-term Memory)
定义:当前对话窗口中的所有内容。
特点:
- 容量有限(受上下文窗口限制)
- 易失性(对话结束即消失)
- 访问速度快(直接在prompt中)
典型内容:
- 当前对话的完整历史
- 正在处理的任务细节
- 临时计算结果
OpenClaw实现:
每次调用API时,传入的messages数组:
[
{role: "system", content: "你是一个助理..."},
{role: "user", content: "帮我查天气"},
{role: "assistant", content: "你在哪个城市?"},
{role: "user", content: "东京"},
...
]
局限:
- 无法跨会话保持
- 容易被新信息“挤出“
- 成本高(每次都传输完整历史)
2. 工作记忆(Working Memory)
定义:Agent正在进行的任务所需的上下文。
类比:就像你在做项目时,桌上摊开的几份文档。
特点:
- 中等容量(几个文件、几千行代码)
- 会话级持久化(任务完成前保持)
- 结构化(有明确的schema)
典型内容:
- 当前任务的状态(STATE.yaml)
- 临时草稿(DRAFT.md)
- 进行中的计划(PLAN.md)
OpenClaw实现案例:Autonomous Project Manager1
# STATE.yaml - 项目管理Agent的工作记忆
current_sprint:
sprint_number: 5
start_date: "2024-01-15"
end_date: "2024-01-29"
goal: "完成用户认证模块"
active_tasks:
- id: "task-23"
title: "实现JWT token刷新机制"
assigned_to: "dev-agent"
status: "in_progress"
blockers: []
- id: "task-24"
title: "编写API文档"
assigned_to: "doc-agent"
status: "blocked"
blockers: ["task-23"]
recent_decisions:
- date: "2024-01-14"
decision: "选择PostgreSQL而非MongoDB"
rationale: "需要强一致性和事务支持"
- date: "2024-01-13"
decision: "采用微服务架构"
rationale: "团队规模增长,需要独立部署"
为什么需要工作记忆:
- 避免重复推理:Agent不需要每次都“重新理解“项目状态
- 多Agent协作:多个Agent共享STATE.yaml,避免信息不同步
- 可回溯:通过Git历史,可以看到项目状态的演变
🔧 实践技巧
工作记忆应该多大?经验法则:
- 单文件不超过1000行
- 总大小不超过50KB
- 能在1-2秒内被Agent读取和理解
3. 长期记忆(Long-term Memory)
定义:Agent需要永久保存的知识和经验。
类比:你的日记本、笔记本、过去的项目文档。
特点:
- 容量巨大(理论上无限)
- 持久化(跨会话、跨重启)
- 需要检索机制(无法全部加载)
典型内容:
- 历史对话日志
- 学到的经验教训
- 用户偏好和习惯
- 积累的知识库
OpenClaw实现:Memory系统
# 文件结构
workspace/
├── memory/
│ ├── 2024-01-01.md # 每日日志
│ ├── 2024-01-02.md
│ ├── ...
│ └── 2024-01-15.md
│
├── MEMORY.md # 长期记忆(精华)
└── knowledge/
├── tech-stack.md # 技术栈知识
├── contacts.md # 人脉关系
└── preferences.md # 用户偏好
每日日志(Daily Memory)
# 2024-01-15.md
## 完成的任务
- ✅ 部署了新版本到生产环境
- ✅ 修复了 #234 bug(内存泄漏)
- ✅ 代码审查 PR #456
## 重要对话
- 用户说:"以后代码审查关注性能问题"
→ 更新 code-review-guide.md,增加性能检查清单
## 遇到的问题
- Docker镜像构建失败
- 原因:base image版本不兼容
- 解决:pin到特定版本
- 记录到 troubleshooting.md
## 今日学习
- 了解了Kubernetes的Readiness Probe
- 阅读了关于Rate Limiting的最佳实践
长期记忆(Curated Memory)
# MEMORY.md
## 关于用户
### 工作习惯
- 早上8-9点最适合深度工作
- 不喜欢被打断,重要通知用邮件
- 每周五下午做周总结
### 技术偏好
- 编程语言:TypeScript > Python > Go
- 数据库:PostgreSQL(强一致性场景),Redis(缓存)
- 部署:Kubernetes on AWS
### 沟通偏好
- 简报要简洁,bullet points优于长段落
- 技术细节可以深入,但先说结论
- 紧急事件直接发Telegram通知
## 项目上下文
### 当前主要项目:PersonalOS
- 目标:构建个人效率基础设施
- 技术栈:Next.js + tRPC + Prisma
- 部署:Vercel + Supabase
- 进度:MVP阶段,预计2月完成
### 历史项目教训
- 项目A:过早优化导致进度延迟
- 教训:先实现核心功能,性能优化放在v2
- 项目B:没有做好API版本管理
- 教训:从一开始就用 /v1/ 路径
## 重要联系人
### 技术同行
- 张三:Kubernetes专家,遇到K8s问题可咨询
- 李四:前端架构师,UI/UX问题找他
- 王五:数据库性能优化高手
### 业务相关
- 陈六:产品经理,负责需求沟通
- 赵七:运营负责人,关注用户增长指标
长期记忆的管理策略:
- 每日写入:每天记录重要事件
- 定期整理:每周/每月将日志精华提炼到MEMORY.md
- 主题分类:按领域组织知识(技术、业务、人际)
- 定期清理:移除过时信息,避免噪音
📚 跨章引用
长期记忆的检索机制在第13章《知识管理场景》中深入讨论,特别是RAG(检索增强生成)技术。
4. 程序记忆(Procedural Memory)
定义:如何做事的知识——流程、模式、技能。
类比:就像你学会骑自行车后,不需要每次都“思考“如何保持平衡。
特点:
- 编码为可执行的规则和脚本
- 调用成本低(不占用上下文窗口)
- 可复用和分享
典型内容:
- 标准操作流程(SOP)
- 代码模板和脚手架
- 决策树和规则引擎
- 自动化脚本
OpenClaw实现:技能系统(Skills)
# Skills目录结构
skills/
├── github/
│ ├── SKILL.md # 如何使用GitHub API
│ ├── create-pr.sh # 创建PR的脚本
│ └── code-review.py # 代码审查流程
│
├── email/
│ ├── SKILL.md
│ ├── triage.yaml # 邮件分类规则
│ └── templates/ # 邮件模板
│ ├── meeting-invite.md
│ └── status-update.md
│
└── server-health/
├── SKILL.md
├── check-health.sh
└── auto-heal.yaml # 自动修复规则
案例:邮件分类规则(程序记忆)
# skills/email/triage.yaml
rules:
- name: "紧急客户问题"
condition:
from: "@customer-domain.com"
subject_contains: ["urgent", "down", "not working"]
action:
label: "客户支持-紧急"
notify: telegram
priority: high
- name: "项目更新"
condition:
from: "github-notifications"
subject_contains: ["[Project-X]"]
action:
label: "项目X"
archive: false
priority: medium
- name: "营销邮件"
condition:
subject_contains: ["unsubscribe", "newsletter"]
action:
label: "营销"
archive: true
priority: low
程序记忆的价值:
- 一致性:每次都按相同标准执行,不会因疲劳或情绪而变化
- 可审计:规则明确,可以review和改进
- 可迁移:可以分享给其他Agent或用户
💡 AI辅助提示
不确定如何将你的流程编码为规则?问AI:
“我有一个邮件分类的流程(描述流程),如何将它转化为YAML规则?”
四种记忆的对比
| 记忆类型 | 容量 | 持久性 | 访问速度 | 典型用途 | OpenClaw实现 |
|---|---|---|---|---|---|
| 短期记忆 | 小(200K tokens) | 会话级 | 极快 | 当前对话 | Messages数组 |
| 工作记忆 | 中(几个文件) | 任务级 | 快 | 当前任务状态 | STATE.yaml |
| 长期记忆 | 大(GB级) | 永久 | 需检索 | 历史经验、知识库 | memory/, MEMORY.md |
| 程序记忆 | 中(代码+配置) | 永久 | 极快 | 自动化流程 | Skills, 脚本 |
记忆系统的整体设计
一个完整的Agent记忆系统应该:
┌─────────────────────────────────────────────┐
│ 短期记忆(对话窗口) │
│ "你刚才说要部署到生产环境..." │
└──────────────┬──────────────────────────────┘
│
↓ (重要信息写入)
┌─────────────────────────────────────────────┐
│ 工作记忆(STATE.yaml) │
│ current_task: "部署v2.3.0" │
│ status: "等待用户确认" │
└──────────────┬──────────────────────────────┘
│
↓ (任务完成后归档)
┌─────────────────────────────────────────────┐
│ 长期记忆(memory/YYYY-MM-DD.md) │
│ - 2024-01-15: 部署v2.3.0成功 │
│ - 遇到了XX问题,通过YY解决 │
└──────────────┬──────────────────────────────┘
│
↓ (定期提炼)
┌─────────────────────────────────────────────┐
│ 长期记忆(MEMORY.md - 精华) │
│ 部署流程:先跑测试,再灰度发布,最后全量 │
│ 常见问题:数据库迁移容易超时,需要分批 │
└──────────────┬──────────────────────────────┘
│
↓ (沉淀为流程)
┌─────────────────────────────────────────────┐
│ 程序记忆(Skills/deploy.sh) │
│ #!/bin/bash │
│ npm run test && deploy-staging && ... │
└─────────────────────────────────────────────┘
关键洞察:
- 自下而上:短期→工作→长期→程序,记忆逐步沉淀
- 按需加载:不是所有记忆都加载到上下文,按需检索
- 持续演化:通过反馈循环,记忆系统不断完善
2.3 文件作为记忆载体
现在我们理解了Agent需要哪些记忆,下一个问题是:用什么来存储这些记忆?
答案是:文件系统。
为什么选择文件系统?
你可能会问:“为什么不用数据库?数据库不是更适合存储数据吗?”
让我们对比一下:
数据库 vs 文件系统
| 特性 | 数据库(如PostgreSQL) | 文件系统(Markdown/YAML) |
|---|---|---|
| 可读性 | SQL查询,对人类不友好 | 纯文本,任何编辑器都能打开 |
| 版本控制 | 需要额外工具 | Git原生支持 |
| 协作 | 需要网络连接 | 本地文件,随处可编辑 |
| 备份 | 需要专门的备份策略 | Git push即备份 |
| 迁移 | 需要导出/导入 | 直接复制文件夹 |
| Agent友好 | 需要SQL技能 | 直接read/write文件 |
| 检索速度 | 快(有索引) | 慢(需要全文搜索) |
| 结构化 | 强制schema | 灵活(可以混用) |
核心理念:对于Agent的记忆系统,可读性和可编辑性比查询速度更重要。
原因:
- 人类需要理解和干预:记忆不只是Agent用,人类也要能读懂、修改
- Agent数量通常不大:不是成千上万个Agent并发访问,不需要数据库级别的性能
- Git是天然的时间机器:每一次改动都可追溯、可回滚
- LLM天然理解自然语言:Markdown比SQL对LLM更友好
Markdown:人类和AI的共同语言
Markdown的优势:
1. 语义丰富
# 项目A - 用户认证模块
## 当前状态
项目进入第二轮测试,发现3个P1 bug。
## 待办事项
- [ ] 修复JWT token过期问题(预计2h)
- [ ] 添加单元测试覆盖率到90%
- [x] 完成API文档
## 决策记录
**2024-01-15**: 选择bcrypt而非SHA256进行密码哈希
- 理由:bcrypt有自适应成本因子,更安全
- 参考:[OWASP密码存储指南](...)
AI读到这段Markdown时,它能理解:
- 标题结构(project → status → todos → decisions)
- 列表项(待办事项,已完成vs未完成)
- 强调(加粗的日期)
- 链接(外部参考)
这比纯JSON或数据库表格富有表达力得多。
2. 可混合格式
# 服务器健康检查报告
## 总览
- 总服务数:5
- 健康:4
- 异常:1
## 详细信息
| 服务名 | 状态 | CPU | 内存 | 最后检查时间 |
|-------|------|-----|------|------------|
| API | ✅ | 45% | 2.1GB | 10:05:23 |
| DB | ✅ | 60% | 4.5GB | 10:05:23 |
| Redis | ❌ | 98% | 7.8GB | 10:05:23 |
## 异常详情
### Redis服务(异常)
**症状**:CPU使用率98%,持续10分钟
**可能原因**:
1. 慢查询堵塞
2. 内存碎片导致频繁GC
**建议措施**:
\`\`\`bash
# 检查慢查询
redis-cli slowlog get 10
# 查看内存碎片率
redis-cli info memory | grep fragmentation
\`\`\`
**自动修复记录**:
10:06:15 - 执行 `redis-cli config set maxmemory-policy allkeys-lru`
10:06:30 - 重启Redis服务
10:07:00 - CPU降至12%,问题解决✅
注意:同一个文件混合了表格、代码块、列表、emoji——这种灵活性是数据库难以实现的。
🔧 实践技巧
Markdown文件的命名规范:
- 日期开头:
2024-01-15-deploy-log.md(易排序)- 小写+连字符:
project-alpha-status.md(避免空格)- 语义化:
user-feedback-jan-2024.md(一目了然)
YAML:结构化配置的首选
当记忆需要严格的结构时(如工作记忆STATE.yaml),YAML是更好的选择。
YAML vs JSON
// JSON - 机器友好,人类不友好
{
"project": {
"name": "OpenClaw",
"status": "active",
"tasks": [
{"id": 1, "title": "Fix bug #123", "done": false},
{"id": 2, "title": "Write docs", "done": true}
]
}
}
# YAML - 人类和机器都友好
project:
name: OpenClaw
status: active
tasks:
- id: 1
title: Fix bug #123
done: false
- id: 2
title: Write docs
done: true
YAML的优势:
- 没有大括号和逗号的噪音
- 支持注释(JSON不支持)
- 更简洁(空格缩进)
实际案例:Autonomous Project Manager的STATE.yaml
# STATE.yaml - 自主项目管理Agent的工作记忆
meta:
last_updated: "2024-01-15T10:30:00Z"
agent_version: "2.1.0"
project:
name: "PersonalOS"
phase: "MVP Development"
deadline: "2024-02-28"
current_sprint:
number: 3
start: "2024-01-08"
end: "2024-01-22"
goal: "Complete user authentication and basic dashboard"
# 当前冲刺的任务
tasks:
- id: "TASK-301"
title: "Implement JWT refresh token mechanism"
assigned_to: "dev-agent"
status: "in_progress"
priority: "high"
blockers: []
progress: 60
- id: "TASK-302"
title: "Design dashboard UI mockups"
assigned_to: "design-agent"
status: "review"
priority: "medium"
blockers: []
progress: 90
- id: "TASK-303"
title: "Write API documentation"
assigned_to: "doc-agent"
status: "blocked"
priority: "medium"
blockers: ["TASK-301"] # 等待301完成
progress: 30
# 技术决策日志
decisions:
- date: "2024-01-14"
decision: "Use PostgreSQL for user data"
rationale: "Need ACID guarantees for auth data"
alternatives_considered:
- MongoDB: "Too flexible, hard to enforce schema"
- SQLite: "Not suitable for production scale"
impact: "Low - standard choice for this use case"
- date: "2024-01-12"
decision: "Adopt tRPC for API layer"
rationale: "Type-safe API calls, better DX"
alternatives_considered:
- REST: "Verbose, need to maintain OpenAPI spec"
- GraphQL: "Overkill for our simple queries"
impact: "Medium - affects frontend-backend contract"
# 风险跟踪
risks:
- id: "RISK-01"
title: "Deadline可能延期"
probability: "medium"
impact: "high"
mitigation: "每天下午3点进度同步,及时调整任务优先级"
status: "monitoring"
- id: "RISK-02"
title: "Third-party auth service不稳定"
probability: "low"
impact: "high"
mitigation: "实现本地auth作为fallback"
status: "mitigated"
# 下一步行动(由Agent自动更新)
next_actions:
- "TASK-301完成后,立即通知doc-agent可以开始TASK-303"
- "TASK-302进入review后,安排与产品经理的评审会议"
- "每日晨会前更新本文件"
为什么这样设计:
- 结构清晰:meta、project、sprint、decisions、risks分区明确
- 易于解析:Agent可以直接读取特定section
- 易于更新:修改单个任务不影响其他部分
- 易于协作:多个Agent可以读取同一STATE.yaml,避免信息不同步
📚 跨章引用
STATE.yaml的详细设计模式在第4章《架构模式》中深入讨论,特别是多Agent协作场景。
JSON:何时使用?
虽然YAML更易读,但JSON在某些场景下更合适:
适合用JSON的场景
- 需要被程序解析(而非人类阅读)
- 嵌套层级深(YAML缩进容易出错)
- 与外部API交互(大多数API返回JSON)
案例:API响应缓存
// cache/weather-api-response.json
{
"timestamp": "2024-01-15T10:30:00Z",
"location": {
"city": "Tokyo",
"coordinates": {"lat": 35.6762, "lon": 139.6503}
},
"current": {
"temp": 18,
"condition": "sunny",
"humidity": 45
},
"forecast": [
{"date": "2024-01-16", "temp_high": 20, "temp_low": 12},
{"date": "2024-01-17", "temp_high": 19, "temp_low": 11}
]
}
为什么用JSON:
- 直接从API获取,原样保存
- Agent可能需要传给其他API(JSON是通用格式)
- 嵌套结构复杂,YAML缩进容易出错
Git:时间轴和协作基础设施
文件系统的最大优势之一:Git原生支持。
Git = Agent记忆的时间机器
# 查看STATE.yaml的历史变化
git log -p STATE.yaml
# 某次改动的详情
commit 3a7f2e9
Date: 2024-01-15 10:30:00
Update STATE.yaml: Mark TASK-301 as in_progress
- Started implementing JWT refresh mechanism
- Estimated completion: tomorrow afternoon
diff --git a/STATE.yaml b/STATE.yaml
--- a/STATE.yaml
+++ b/STATE.yaml
@@ -15,7 +15,7 @@ current_sprint:
- id: "TASK-301"
title: "Implement JWT refresh token mechanism"
assigned_to: "dev-agent"
- status: "todo"
+ status: "in_progress"
priority: "high"
Git给我们什么:
- 完整历史:每一次状态变化都有记录
- 可回滚:发现Agent做错决策,立即回到之前的状态
- 多人协作:多个Agent(或人类)可以并行工作,通过Git merge
- 远程备份:push到GitHub/GitLab,永不丢失
案例:Self-Healing Server的审计日志
# 服务器自动修复的Git历史
git log --oneline infra/
a3b5c7d (2024-01-15 10:30) Auto-heal: Restart Redis (CPU 98%)
f2e4d6c (2024-01-15 08:15) Auto-heal: Scale up API pods (latency > 500ms)
9b1c3e5 (2024-01-14 22:45) Auto-heal: Clean up old Docker images (disk 95%)
每一次自动修复都是一次commit,包含:
- What:做了什么操作
- Why:检测到什么问题
- When:精确到秒的时间戳
- How:修改了哪些配置文件(diff)
这比数据库日志表强大得多。
🔧 实践技巧
Git commit message规范:
- 第一行:简洁描述(50字符内)
- 空一行
- 详细说明:为什么这样改,背景是什么
好的commit:
Fix: Memory leak in Redis connection pool Detected: Memory usage growing 100MB/hour Root cause: Connections not properly closed after timeout Solution: Added explicit conn.close() in error handler Result: Memory stable at 2.1GB
文件组织最佳实践
一个典型的OpenClaw Agent工作区:
workspace/
├── AGENTS.md # Agent身份和角色定义
├── SOUL.md # Agent的"人格"
├── MEMORY.md # 长期记忆(精华)
├── TOOLS.md # 常用工具和命令
│
├── memory/ # 每日日志
│ ├── 2024-01-01.md
│ ├── 2024-01-02.md
│ └── ...
│
├── projects/ # 进行中的项目
│ ├── project-alpha/
│ │ ├── STATE.yaml # 项目状态(工作记忆)
│ │ ├── PLAN.md # 项目计划
│ │ ├── DECISIONS.md # 决策日志
│ │ └── src/ # 项目代码
│ │
│ └── project-beta/
│ └── ...
│
├── knowledge/ # 知识库(长期记忆)
│ ├── tech/
│ │ ├── kubernetes.md
│ │ ├── postgresql.md
│ │ └── troubleshooting.md
│ │
│ ├── business/
│ │ ├── competitors.md
│ │ └── market-research.md
│ │
│ └── people/
│ └── contacts.md # 人脉关系
│
├── skills/ # 程序记忆
│ ├── github/
│ ├── email/
│ └── server-health/
│
└── .git/ # 时间轴和备份
设计原则:
- 扁平优于嵌套:最多3层目录,更深会导致难以查找
- 语义化命名:文件名应该自解释
- 分而治之:大文件拆分成多个小文件(每个<1000行)
- 索引文件:每个目录有README.md说明结构
2.4 实战:搭建个人知识库
现在让我们动手搭建一个真实的Agent记忆系统——个人知识库(Personal Knowledge Base)2,使用RAG(Retrieval-Augmented Generation)技术。
什么是RAG?
**RAG(检索增强生成)**是一种让AI“记住“大量知识的技术。
传统对话 vs RAG
传统对话:
你:"我之前研究过Kubernetes的网络模型吗?"
AI:"我不知道,我没有你过去的记忆。"
使用RAG:
你:"我之前研究过Kubernetes的网络模型吗?"
AI:
[在后台检索你的knowledge/tech/kubernetes.md]
"是的,你在2024年1月研究过。你的笔记提到:
- Kubernetes使用CNI(Container Network Interface)
- 你比较了Calico、Flannel、Cilium
- 你倾向于Cilium,因为它有eBPF性能优势
需要我详细回顾你的笔记吗?"
核心机制:
- Ingestion(摄入):将你的文档(Markdown、PDF等)切分成小块(chunks)
- Embedding(嵌入):将每个chunk转化为向量(一串数字)
- Storage(存储):向量存入向量数据库(如Qdrant、Pinecone)
- Retrieval(检索):用户提问时,将问题也转化为向量,找到最相似的chunks
- Generation(生成):将检索到的chunks和问题一起发给LLM,生成答案
💡 AI辅助提示
不理解“向量“和“相似度“?问AI:
“什么是向量嵌入(vector embedding)?为什么可以用来衡量文本相似度?能用简单例子说明吗?”
搭建步骤
步骤1:准备知识库内容
首先,创建你的知识库文件夹:
mkdir -p ~/openclaw-workspace/knowledge/tech
mkdir -p ~/openclaw-workspace/knowledge/business
mkdir -p ~/openclaw-workspace/knowledge/personal
添加一些文档:
# knowledge/tech/kubernetes.md
# Kubernetes学习笔记
## 2024-01-10 - 网络模型研究
今天研究了Kubernetes的网络模型。
### CNI(Container Network Interface)
- Kubernetes使用CNI作为网络插件标准
- 常见实现:Calico、Flannel、Cilium、Weave
### CNI对比
| CNI | 优势 | 劣势 | 适用场景 |
|-----|------|------|----------|
| Calico | 性能好,功能全 | 配置复杂 | 生产环境 |
| Flannel | 简单易用 | 功能有限 | 测试环境 |
| Cilium | eBPF高性能,可观测性强 | 需要新内核 | 追求性能 |
### 决策
我选择了**Cilium**,原因:
- eBPF带来的性能提升(官方数据:网络延迟降低30%)
- 内置网络策略可视化(Hubble)
- 社区活跃,CNCF孵化项目
### 参考资料
- [Cilium官方文档](https://cilium.io/)
- [eBPF介绍](https://ebpf.io/)
# knowledge/personal/contacts.md
# 重要联系人
## 技术领域
### 张三(Kubernetes专家)
- GitHub: @zhangsan
- 专长:K8s生产环境排障
- 合作项目:2023年帮助优化我们的集群性能
- 最后联系:2024-01-05(讨论Cilium迁移)
### 李四(前端架构师)
- GitHub: @lisi
- 专长:React、性能优化
- 合作项目:PersonalOS前端设计
- 最后联系:2024-01-12
步骤2:选择技术栈
我们需要:
- Embedding模型:将文本转化为向量
- 向量数据库:存储和检索向量
- Agent框架:OpenClaw + RAG skill
推荐技术栈:
- Embedding模型:OpenAI
text-embedding-3-small(便宜且效果好) - 向量数据库:Qdrant(开源,可本地部署)
- Agent:OpenClaw + 自定义RAG skill
步骤3:安装Qdrant
# 使用Docker运行Qdrant
docker run -d \
--name qdrant \
-p 6333:6333 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
验证安装:
curl http://localhost:6333/
# 应该返回:{"title":"qdrant - vector search engine",...}
步骤4:编写Ingestion脚本
创建 tools/ingest-knowledge.py:
#!/usr/bin/env python3
"""
将Markdown文件ingestion到Qdrant向量数据库
"""
import os
from pathlib import Path
from openai import OpenAI
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import hashlib
# 配置
KNOWLEDGE_BASE_PATH = Path("~/openclaw-workspace/knowledge").expanduser()
QDRANT_URL = "http://localhost:6333"
COLLECTION_NAME = "my_knowledge"
EMBEDDING_MODEL = "text-embedding-3-small"
CHUNK_SIZE = 500 # 每个chunk约500字符
# 初始化客户端
openai_client = OpenAI()
qdrant_client = QdrantClient(url=QDRANT_URL)
def create_collection():
"""创建Qdrant collection(如果不存在)"""
collections = qdrant_client.get_collections().collections
if not any(c.name == COLLECTION_NAME for c in collections):
qdrant_client.create_collection(
collection_name=COLLECTION_NAME,
vectors_config=VectorParams(
size=1536, # text-embedding-3-small的维度
distance=Distance.COSINE
)
)
print(f"✅ Created collection: {COLLECTION_NAME}")
else:
print(f"ℹ️ Collection {COLLECTION_NAME} already exists")
def chunk_text(text: str, chunk_size: int = CHUNK_SIZE) -> list[str]:
"""将长文本切分成chunks"""
# 简单策略:按段落切分,然后合并小段落
paragraphs = text.split('\n\n')
chunks = []
current_chunk = ""
for para in paragraphs:
if len(current_chunk) + len(para) < chunk_size:
current_chunk += para + "\n\n"
else:
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = para + "\n\n"
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
def get_embedding(text: str) -> list[float]:
"""获取文本的embedding向量"""
response = openai_client.embeddings.create(
model=EMBEDDING_MODEL,
input=text
)
return response.data[0].embedding
def ingest_file(file_path: Path):
"""处理单个Markdown文件"""
print(f"Processing: {file_path}")
# 读取文件
content = file_path.read_text(encoding='utf-8')
# 切分成chunks
chunks = chunk_text(content)
print(f" Split into {len(chunks)} chunks")
# 为每个chunk生成embedding并存储
points = []
for i, chunk in enumerate(chunks):
# 生成唯一ID(基于文件路径+chunk索引)
chunk_id = hashlib.md5(
f"{file_path}:{i}".encode()
).hexdigest()
# 获取embedding
embedding = get_embedding(chunk)
# 创建point
point = PointStruct(
id=chunk_id,
vector=embedding,
payload={
"text": chunk,
"source_file": str(file_path),
"chunk_index": i,
"total_chunks": len(chunks)
}
)
points.append(point)
# 批量上传到Qdrant
qdrant_client.upsert(
collection_name=COLLECTION_NAME,
points=points
)
print(f" ✅ Uploaded {len(points)} chunks")
def ingest_all():
"""处理所有Markdown文件"""
create_collection()
# 递归查找所有.md文件
md_files = list(KNOWLEDGE_BASE_PATH.rglob("*.md"))
print(f"\nFound {len(md_files)} Markdown files")
for file_path in md_files:
try:
ingest_file(file_path)
except Exception as e:
print(f" ❌ Error processing {file_path}: {e}")
print("\n🎉 Ingestion complete!")
if __name__ == "__main__":
ingest_all()
运行ingestion:
python tools/ingest-knowledge.py
输出:
Found 15 Markdown files
Processing: knowledge/tech/kubernetes.md
Split into 8 chunks
✅ Uploaded 8 chunks
Processing: knowledge/personal/contacts.md
Split into 3 chunks
✅ Uploaded 3 chunks
...
🎉 Ingestion complete!
步骤5:编写检索函数
创建 tools/search-knowledge.py:
#!/usr/bin/env python3
"""
搜索知识库
"""
import sys
from openai import OpenAI
from qdrant_client import QdrantClient
QDRANT_URL = "http://localhost:6333"
COLLECTION_NAME = "my_knowledge"
EMBEDDING_MODEL = "text-embedding-3-small"
TOP_K = 5 # 返回最相似的5个chunks
openai_client = OpenAI()
qdrant_client = QdrantClient(url=QDRANT_URL)
def search(query: str, top_k: int = TOP_K):
"""搜索知识库"""
# 1. 将查询转化为向量
query_embedding = openai_client.embeddings.create(
model=EMBEDDING_MODEL,
input=query
).data[0].embedding
# 2. 在Qdrant中搜索
search_results = qdrant_client.search(
collection_name=COLLECTION_NAME,
query_vector=query_embedding,
limit=top_k
)
# 3. 格式化结果
print(f"\n🔍 Search results for: \"{query}\"\n")
print("=" * 60)
for i, result in enumerate(search_results, 1):
print(f"\n[Result {i}] (score: {result.score:.3f})")
print(f"Source: {result.payload['source_file']}")
print(f"Chunk {result.payload['chunk_index'] + 1}/{result.payload['total_chunks']}")
print("-" * 60)
print(result.payload['text'][:300] + "...")
print()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python search-knowledge.py <query>")
sys.exit(1)
query = " ".join(sys.argv[1:])
search(query)
测试搜索:
python tools/search-knowledge.py "我之前研究过哪些Kubernetes网络方案?"
输出:
🔍 Search results for: "我之前研究过哪些Kubernetes网络方案?"
============================================================
[Result 1] (score: 0.876)
Source: knowledge/tech/kubernetes.md
Chunk 3/8
------------------------------------------------------------
### CNI对比
| CNI | 优势 | 劣势 | 适用场景 |
|-----|------|------|----------|
| Calico | 性能好,功能全 | 配置复杂 | 生产环境 |
| Flannel | 简单易用 | 功能有限 | 测试环境 |
| Cilium | eBPF高性能,可观测性强 | 需要新内核 | 追求性能 |
### 决策
我选择了**Cilium**...
[Result 2] (score: 0.834)
Source: knowledge/tech/kubernetes.md
Chunk 2/8
------------------------------------------------------------
### CNI(Container Network Interface)
- Kubernetes使用CNI作为网络插件标准
- 常见实现:Calico、Flannel、Cilium、Weave...
完美! 我们成功检索到了相关知识。
步骤6:集成到OpenClaw Agent
最后,创建一个OpenClaw skill来使用这个RAG系统。
创建 skills/knowledge-base/SKILL.md:
# Knowledge Base RAG Skill
## 描述
搜索个人知识库,检索相关笔记和文档。
## 使用方式
### 搜索知识库
\`\`\`bash
python ~/openclaw-workspace/tools/search-knowledge.py "<query>"
\`\`\`
### 更新知识库(重新ingestion)
\`\`\`bash
python ~/openclaw-workspace/tools/ingest-knowledge.py
\`\`\`
## 何时使用
- 用户问"我之前研究过XX吗?"
- 用户问"我对XX的看法是什么?"
- 需要回顾过去的笔记和决策
- 查找某个人的联系方式或背景
## 示例
**用户**:"我之前研究过Kubernetes网络方案吗?"
**Agent行动**:
1. 运行:`python tools/search-knowledge.py "Kubernetes网络方案"`
2. 获取top 3相关chunks
3. 总结给用户:"是的,你研究过Calico、Flannel和Cilium三种方案,最终选择了Cilium..."
更新 AGENTS.md,让Agent知道如何使用这个技能:
## 记忆系统
你有一个长期记忆系统(Personal Knowledge Base)。
### 何时使用
- 用户问起过去的事情、研究、决策
- 需要查找某个技术细节
- 用户提到"我之前说过..."
### 如何使用
\`\`\`bash
# 搜索知识库
python ~/openclaw-workspace/tools/search-knowledge.py "<query>"
# 将结果总结后回复用户
\`\`\`
### 自动更新
- 每次用户分享重要信息,你应该:
1. 将信息添加到相应的knowledge/文件
2. 运行 ingest-knowledge.py 更新向量数据库
步骤7:测试完整流程
现在和Agent对话:
你:"我之前研究过Kubernetes的网络模型吗?"
Agent:[内部思考]
1. 这是关于过去记忆的问题
2. 应该搜索knowledge base
3. 运行: python tools/search-knowledge.py "Kubernetes网络模型"
4. 获取结果...
Agent回复:"是的,你在2024年1月10日研究过Kubernetes的网络模型。
你比较了三种CNI方案:
1. **Calico** - 性能好,功能全,但配置复杂
2. **Flannel** - 简单易用,但功能有限
3. **Cilium** - eBPF高性能,可观测性强
你最终选择了**Cilium**,主要原因是:
- eBPF带来的性能提升(网络延迟降低30%)
- 内置网络策略可视化(Hubble)
- 社区活跃
需要我详细回顾你的笔记吗?"
成功! Agent现在有了长期记忆。
RAG系统的优化
优化1:混合检索(Hybrid Search)
单纯的语义搜索有时会漏掉精确匹配。结合关键词搜索更好:
def hybrid_search(query: str, top_k: int = TOP_K):
"""混合检索:语义 + 关键词"""
# 1. 语义搜索
semantic_results = qdrant_client.search(
collection_name=COLLECTION_NAME,
query_vector=get_embedding(query),
limit=top_k * 2 # 多取一些候选
)
# 2. 关键词搜索(简单版:检查query中的关键词是否出现在text中)
keywords = set(query.lower().split())
# 3. 重新排序:语义相似度 + 关键词匹配数
scored_results = []
for result in semantic_results:
text_lower = result.payload['text'].lower()
keyword_matches = sum(1 for kw in keywords if kw in text_lower)
# 综合分数 = 语义分数 * 0.7 + 关键词匹配 * 0.3
combined_score = result.score * 0.7 + (keyword_matches / len(keywords)) * 0.3
scored_results.append((combined_score, result))
# 按综合分数排序
scored_results.sort(reverse=True, key=lambda x: x[0])
return [r for _, r in scored_results[:top_k]]
优化2:自动更新机制
让Agent自动检测knowledge/目录的变化并重新ingestion:
# tools/watch-and-ingest.py
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class KnowledgeBaseHandler(FileSystemEventHandler):
def on_modified(self, event):
if event.src_path.endswith('.md'):
print(f"Detected change: {event.src_path}")
print("Re-ingesting...")
ingest_file(Path(event.src_path))
if __name__ == "__main__":
observer = Observer()
observer.schedule(KnowledgeBaseHandler(), KNOWLEDGE_BASE_PATH, recursive=True)
observer.start()
print("👀 Watching for changes in knowledge base...")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
在后台运行:
python tools/watch-and-ingest.py &
现在,每次你更新knowledge/目录下的文件,都会自动重新ingestion!
🔧 实践技巧
RAG系统的效果高度依赖chunk大小:
- 太小(<200字符):缺乏上下文,检索结果不完整
- 太大(>1000字符):噪音多,相似度计算不准确
- 推荐:500-800字符,以段落为单位切分
优化3:元数据过滤
有时你想限定搜索范围:
def search_with_filter(query: str, source_filter: str = None):
"""支持过滤的搜索"""
query_filter = None
if source_filter:
from qdrant_client.models import Filter, FieldCondition, MatchValue
query_filter = Filter(
must=[
FieldCondition(
key="source_file",
match=MatchValue(value=source_filter)
)
]
)
results = qdrant_client.search(
collection_name=COLLECTION_NAME,
query_vector=get_embedding(query),
query_filter=query_filter,
limit=TOP_K
)
return results
使用:
# 只搜索技术类笔记
python search-knowledge.py "Kubernetes" --filter "knowledge/tech/"
总结:RAG系统的价值
通过搭建RAG系统,我们实现了:
- 无限记忆:不受上下文窗口限制,可以存储GB级别的知识
- 精准检索:通过语义搜索,找到真正相关的内容
- 持续学习:每次添加新笔记,Agent的“大脑“就更丰富
- 可解释性:检索结果明确显示来源,可追溯
这就是Agent记忆系统的核心——从“健忘的助手“到“拥有长期记忆的智能体“。
本章总结
核心要点
- AI天生“健忘“:上下文窗口有限,无法记住所有信息
- 四种记忆类型:
- 短期记忆:对话窗口,易失
- 工作记忆:STATE.yaml,任务级持久化
- 长期记忆:memory/文件,永久存储
- 程序记忆:Skills,可执行的流程
- 文件优于数据库:对于Agent记忆,可读性和Git支持比查询速度更重要
- Markdown/YAML/JSON:根据场景选择合适的格式
- Git = 时间机器:每次状态变化都可追溯、可回滚
- RAG系统:通过向量检索实现“无限记忆“
下一步行动
- 创建你的
knowledge/目录,开始记录笔记 - 搭建Qdrant向量数据库
- 运行ingestion脚本,构建你的第一个RAG系统
- 更新
AGENTS.md,让Agent知道如何使用记忆系统 - 尝试问Agent:“我之前研究过XX吗?”
延伸阅读
- 📚 记忆心理学:《记忆的七宗罪》(丹尼尔·夏克特)
- 🔧 RAG技术:LangChain官方文档的RAG教程
- 🎯 向量数据库对比:Qdrant vs Pinecone vs Weaviate
- 🏗️ Obsidian + RAG:将笔记应用与Agent结合
跨章引用预告
- 第4章《架构模式》:多Agent如何共享记忆?STATE.yaml的协作模式
- 第11章《基础设施场景》:如何用记忆系统管理Home Lab知识库
- 第13章《知识管理场景》:更高级的RAG技术,包括混合检索和re-ranking
参考资料
本章引用的案例均来自 awesome-openclaw-usecases 社区仓库:
💬 思考题
- 你现在用什么工具管理个人知识?(Notion、Obsidian、笔记本?)
- 如果让Agent管理你的知识库,你希望它能做什么?
- 你有哪些重复性的“记忆检索“任务可以交给Agent?
下一章,我们将探讨Agent思维模式——如何从传统脚本思维转变为智能体思维,理解Agent决策的内在机制。
-
案例来源:Autonomous Project Management,awesome-openclaw-usecases 社区贡献 ↩
-
案例来源:Personal Knowledge Base (RAG),awesome-openclaw-usecases 社区贡献 ↩