Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第2章:Agent的记忆系统

“Memory is the mother of all wisdom.” — Aeschylus

引言:为什么Agent会“健忘“?

你有没有遇到过这样的情况:

  • 早上你告诉ChatGPT:“我在做一个关于气候变化的项目”,晚上再问它相关问题,它完全不记得了。
  • 你让AI助手帮你规划一周的任务,第二天它已经忘记你昨天说了什么。
  • 你在一个对话中详细描述了你的项目背景,但开始新对话时又要重复一遍。

这不是AI的bug,而是设计的必然。传统的对话式AI就像患有“短期失忆症“的聪明人——它很擅长当下的推理,但无法建立长期记忆。

而真正的Agent系统需要记忆。它需要记住:

  • 你的偏好和习惯
  • 过去做过的决策
  • 进行中的任务状态
  • 学到的经验和教训

本章将解决三个核心问题

  1. 为什么AI天生“健忘“,以及这如何限制了它的能力?
  2. Agent需要哪几种类型的记忆?
  3. 如何用文件系统构建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)。

人类大脑的策略:

  1. 选择性记忆:不是所有事都记住,只记重要的
  2. 压缩存储:把详细经历压缩成概念和模式
  3. 外部化:通过笔记本、日记、数据库扩展记忆
  4. 检索机制:需要时能快速调取相关记忆

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问题找他
- 王五:数据库性能优化高手

### 业务相关
- 陈六:产品经理,负责需求沟通
- 赵七:运营负责人,关注用户增长指标

长期记忆的管理策略

  1. 每日写入:每天记录重要事件
  2. 定期整理:每周/每月将日志精华提炼到MEMORY.md
  3. 主题分类:按领域组织知识(技术、业务、人际)
  4. 定期清理:移除过时信息,避免噪音

📚 跨章引用
长期记忆的检索机制在第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的记忆系统,可读性和可编辑性比查询速度更重要

原因:

  1. 人类需要理解和干预:记忆不只是Agent用,人类也要能读懂、修改
  2. Agent数量通常不大:不是成千上万个Agent并发访问,不需要数据库级别的性能
  3. Git是天然的时间机器:每一次改动都可追溯、可回滚
  4. 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的场景

  1. 需要被程序解析(而非人类阅读)
  2. 嵌套层级深(YAML缩进容易出错)
  3. 与外部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给我们什么

  1. 完整历史:每一次状态变化都有记录
  2. 可回滚:发现Agent做错决策,立即回到之前的状态
  3. 多人协作:多个Agent(或人类)可以并行工作,通过Git merge
  4. 远程备份: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/                   # 时间轴和备份

设计原则

  1. 扁平优于嵌套:最多3层目录,更深会导致难以查找
  2. 语义化命名:文件名应该自解释
  3. 分而治之:大文件拆分成多个小文件(每个<1000行)
  4. 索引文件:每个目录有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性能优势
   
   需要我详细回顾你的笔记吗?"

核心机制

  1. Ingestion(摄入):将你的文档(Markdown、PDF等)切分成小块(chunks)
  2. Embedding(嵌入):将每个chunk转化为向量(一串数字)
  3. Storage(存储):向量存入向量数据库(如Qdrant、Pinecone)
  4. Retrieval(检索):用户提问时,将问题也转化为向量,找到最相似的chunks
  5. 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:选择技术栈

我们需要:

  1. Embedding模型:将文本转化为向量
  2. 向量数据库:存储和检索向量
  3. 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系统的优化

单纯的语义搜索有时会漏掉精确匹配。结合关键词搜索更好:

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系统,我们实现了:

  1. 无限记忆:不受上下文窗口限制,可以存储GB级别的知识
  2. 精准检索:通过语义搜索,找到真正相关的内容
  3. 持续学习:每次添加新笔记,Agent的“大脑“就更丰富
  4. 可解释性:检索结果明确显示来源,可追溯

这就是Agent记忆系统的核心——从“健忘的助手“到“拥有长期记忆的智能体“。


本章总结

核心要点

  1. AI天生“健忘“:上下文窗口有限,无法记住所有信息
  2. 四种记忆类型
    • 短期记忆:对话窗口,易失
    • 工作记忆:STATE.yaml,任务级持久化
    • 长期记忆:memory/文件,永久存储
    • 程序记忆:Skills,可执行的流程
  3. 文件优于数据库:对于Agent记忆,可读性和Git支持比查询速度更重要
  4. Markdown/YAML/JSON:根据场景选择合适的格式
  5. Git = 时间机器:每次状态变化都可追溯、可回滚
  6. 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 社区仓库:


💬 思考题

  1. 你现在用什么工具管理个人知识?(Notion、Obsidian、笔记本?)
  2. 如果让Agent管理你的知识库,你希望它能做什么?
  3. 你有哪些重复性的“记忆检索“任务可以交给Agent?

下一章,我们将探讨Agent思维模式——如何从传统脚本思维转变为智能体思维,理解Agent决策的内在机制。


  1. 案例来源:Autonomous Project Management,awesome-openclaw-usecases 社区贡献

  2. 案例来源:Personal Knowledge Base (RAG),awesome-openclaw-usecases 社区贡献