返回博客2026年1月29日

Moltbot 记忆系统深度解析:文件驱动的 AI 长期记忆方案

AILLMMemoryMoltbotRAG

Moltbot 记忆系统深度解析:文件驱动的 AI 长期记忆方案

LLM 的记忆问题一直是业界难题。GPT 用黑盒式记忆,用户无法查看和编辑;Gemini 靠超长上下文硬扛,但终究有限。Moltbot 走了一条不同的路:文件为源、数据库为缓存、AI 自主管理

本文基于 Moltbot 源码,深入解析这套记忆系统的架构和实现细节。

核心设计理念

Moltbot 记忆系统的核心原则:

  1. Markdown 文件是唯一的事实来源 — 数据库只是派生缓存
  2. 透明可编辑 — 用户可以直接 catgrep、手动编辑记忆文件
  3. 可迁移 — 复制 ~/clawd/ 目录就能迁移全部记忆
  4. AI 自主管理 — 模型在上下文压缩前主动保存重要信息

整体架构

flowchart TB
    subgraph 文件层["文件层 (Source of Truth)"]
        MEMORY["MEMORY.md<br/>长期记忆"]
        DAILY["memory/YYYY-MM-DD.md<br/>每日日志"]
    end

    subgraph 索引层["索引层 (Derived Cache)"]
        SQLITE[("SQLite 数据库<br/>~/.clawdbot/memory/")]
        subgraph 表结构
            CHUNKS["chunks<br/>文本块 + 向量"]
            FTS["chunks_fts<br/>BM25 全文索引"]
            VEC["chunks_vec<br/>向量索引"]
            CACHE["embedding_cache<br/>嵌入缓存"]
        end
    end

    subgraph 检索层["检索层"]
        HYBRID["混合搜索引擎"]
        VECTOR["向量检索<br/>(语义相似)"]
        KEYWORD["关键词检索<br/>(精确匹配)"]
    end

    subgraph 工具层["工具层"]
        SEARCH["memory_search<br/>语义搜索"]
        GET["memory_get<br/>读取文件"]
    end

    MEMORY --> SQLITE
    DAILY --> SQLITE
    SQLITE --> CHUNKS
    SQLITE --> FTS
    SQLITE --> VEC
    SQLITE --> CACHE

    CHUNKS --> VECTOR
    FTS --> KEYWORD
    VECTOR --> HYBRID
    KEYWORD --> HYBRID

    HYBRID --> SEARCH
    MEMORY --> GET
    DAILY --> GET

文件存储层

两层记忆结构

Moltbot 使用两层 Markdown 文件来组织记忆:

~/clawd/                          # 默认工作区
├── MEMORY.md                     # 长期记忆 (可选)
└── memory/
    ├── 2026-01-27.md            # 昨天的笔记
    ├── 2026-01-28.md            # 今天的笔记
    └── ...
文件用途加载时机
MEMORY.md决策、偏好、持久事实仅在私人主会话中加载
memory/YYYY-MM-DD.md每日运行笔记和上下文加载今天 + 昨天

为什么选择 Markdown

  1. 人类可读 — 不需要特殊工具就能查看和编辑
  2. 版本控制友好 — 可以用 Git 追踪记忆变化
  3. 跨平台 — 任何文本编辑器都能处理
  4. AI 原生 — LLM 天生擅长理解和生成 Markdown

索引层:SQLite 作为派生缓存

虽然网上有说法称 Moltbot "没有数据库",但实际上它使用 SQLite 来加速检索。关键区别在于:数据库是派生的,不是主存储

数据库结构

-- 索引元数据 (用于检测配置变更)
CREATE TABLE meta (
    key TEXT PRIMARY KEY,
    value TEXT
);

-- 文件追踪
CREATE TABLE files (
    path TEXT PRIMARY KEY,
    hash TEXT,
    mtime INTEGER,
    size INTEGER,
    source TEXT
);

-- 文本块 + 嵌入向量
CREATE TABLE chunks (
    id TEXT PRIMARY KEY,
    path TEXT,
    start_line INTEGER,
    end_line INTEGER,
    text TEXT,
    embedding BLOB,
    model TEXT
);

-- BM25 全文搜索
CREATE VIRTUAL TABLE chunks_fts USING fts5(
    text,
    content='chunks',
    content_rowid='rowid'
);

-- 嵌入缓存 (避免重复计算)
CREATE TABLE embedding_cache (
    provider TEXT,
    model TEXT,
    hash TEXT,
    embedding BLOB,
    dims INTEGER,
    updated_at INTEGER,
    PRIMARY KEY (provider, model, hash)
);

为什么需要数据库

纯文件检索的问题:

  1. 向量搜索需要预计算 — 每次搜索都重新计算嵌入向量太慢
  2. 全文搜索需要索引 — 遍历所有文件效率太低
  3. 增量更新 — 只处理变更的文件,而不是每次全量重建

但数据库是 可丢弃的

# 删除数据库,系统会从 Markdown 文件重建
rm ~/.clawdbot/memory/*.sqlite

分块策略

Moltbot 将 Markdown 文件切分成小块,每块约 400 tokens,带 80 tokens 重叠:

// src/memory/internal.ts
const CHARS_PER_TOKEN = 4;
const DEFAULT_CHUNK_TOKENS = 400;
const DEFAULT_OVERLAP_TOKENS = 80;

// 每块约 1600 字符,重叠 320 字符
const chunkSize = DEFAULT_CHUNK_TOKENS * CHARS_PER_TOKEN;  // 1600
const overlap = DEFAULT_OVERLAP_TOKENS * CHARS_PER_TOKEN;  // 320

分块时保留行号信息,便于后续精确定位:

flowchart LR
    subgraph 原文件
        L1["Line 1-40"]
        L2["Line 35-75"]
        L3["Line 70-110"]
    end

    subgraph 分块结果
        C1["Chunk 1<br/>lines 1-40<br/>~400 tokens"]
        C2["Chunk 2<br/>lines 35-75<br/>~400 tokens"]
        C3["Chunk 3<br/>lines 70-110<br/>~400 tokens"]
    end

    L1 --> C1
    L2 --> C2
    L3 --> C3

    C1 -.->|"80 token<br/>重叠"| C2
    C2 -.->|"80 token<br/>重叠"| C3

混合搜索引擎

Moltbot 采用向量 + 关键词的混合搜索策略,兼顾语义理解和精确匹配。

为什么需要混合搜索

搜索类型擅长不擅长
向量搜索语义相似("Mac Studio 网关主机" ≈ "运行网关的机器")精确 ID、代码符号
关键词搜索精确匹配(a828e60memorySearch.query.hybrid同义词、换种说法

混合算法实现

// src/memory/hybrid.ts

// 1. BM25 排名转换为 0-1 分数
export function bm25RankToScore(rank: number): number {
  const normalized = Math.max(0, rank);
  return 1 / (1 + normalized);
}

// 2. 合并两路检索结果
export function mergeHybridResults(params: {
  vector: HybridVectorResult[];
  keyword: HybridKeywordResult[];
  vectorWeight: number;  // 默认 0.7
  textWeight: number;    // 默认 0.3
}) {
  const byId = new Map();

  // 收集向量检索结果
  for (const r of params.vector) {
    byId.set(r.id, {
      ...r,
      vectorScore: r.vectorScore,
      textScore: 0,
    });
  }

  // 合并关键词检索结果
  for (const r of params.keyword) {
    const existing = byId.get(r.id);
    if (existing) {
      existing.textScore = r.textScore;
    } else {
      byId.set(r.id, { ...r, vectorScore: 0, textScore: r.textScore });
    }
  }

  // 计算加权分数并排序
  return Array.from(byId.values())
    .map(entry => ({
      ...entry,
      score: params.vectorWeight * entry.vectorScore
           + params.textWeight * entry.textScore,
    }))
    .sort((a, b) => b.score - a.score);
}

检索流程

sequenceDiagram
    participant User as 用户查询
    participant Hybrid as 混合引擎
    participant Vector as 向量检索
    participant BM25 as BM25 检索
    participant Result as 最终结果

    User->>Hybrid: "Mac Studio 网关配置"

    par 并行检索
        Hybrid->>Vector: 语义搜索 (top 24)
        Hybrid->>BM25: 关键词搜索 (top 24)
    end

    Vector-->>Hybrid: 向量相似度结果
    BM25-->>Hybrid: BM25 排名结果

    Hybrid->>Hybrid: 合并去重
    Hybrid->>Hybrid: 加权打分<br/>0.7×vector + 0.3×text
    Hybrid->>Hybrid: 按分数排序
    Hybrid->>Result: 返回 top 6

默认配置

{
  "agents": {
    "defaults": {
      "memorySearch": {
        "query": {
          "maxResults": 6,
          "minScore": 0.35,
          "hybrid": {
            "enabled": true,
            "vectorWeight": 0.7,
            "textWeight": 0.3,
            "candidateMultiplier": 4  // 检索 4 倍候选,再重排序
          }
        }
      }
    }
  }
}

预压缩记忆刷新:AI 主动保存记忆

这是 Moltbot 记忆系统最巧妙的设计之一。当会话上下文即将被压缩时,系统会触发一个 静默的 agentic turn,提示模型主动保存重要信息。

触发条件

// src/auto-reply/reply/memory-flush.ts

export function shouldRunMemoryFlush(params: {
  entry?: { totalTokens, compactionCount, memoryFlushCompactionCount };
  contextWindowTokens: number;
  reserveTokensFloor: number;      // 默认 20000
  softThresholdTokens: number;     // 默认 4000
}): boolean {
  const { totalTokens } = params.entry;

  // 计算触发阈值
  const threshold = contextWindowTokens - reserveTokensFloor - softThresholdTokens;

  // token 数未达阈值,不触发
  if (totalTokens < threshold) return false;

  // 本轮压缩周期已经刷新过,不重复触发
  const { compactionCount, memoryFlushCompactionCount } = params.entry;
  if (memoryFlushCompactionCount === compactionCount) return false;

  return true;
}

刷新流程

flowchart TB
    START[会话进行中] --> CHECK{totalTokens >= threshold?}
    CHECK -->|否| CONTINUE[继续会话]
    CHECK -->|是| FLUSHED{本周期已刷新?}
    FLUSHED -->|是| CONTINUE
    FLUSHED -->|否| INJECT[注入静默 turn]

    INJECT --> PROMPT["系统提示:<br/>Pre-compaction memory flush.<br/>Store durable memories now."]
    PROMPT --> MODEL[模型执行]
    MODEL --> WRITE["写入 memory/YYYY-MM-DD.md"]
    WRITE --> REPLY["回复 NO_REPLY<br/>(用户不可见)"]
    REPLY --> MARK[标记已刷新]
    MARK --> CONTINUE

默认提示词

// src/auto-reply/reply/memory-flush.ts

export const DEFAULT_MEMORY_FLUSH_PROMPT = [
  "Pre-compaction memory flush.",
  "Store durable memories now (use memory/YYYY-MM-DD.md; create memory/ if needed).",
  "If nothing to store, reply with NO_REPLY.",
].join(" ");

export const DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT = [
  "Pre-compaction memory flush turn.",
  "The session is near auto-compaction; capture durable memories to disk.",
  "You may reply, but usually NO_REPLY is correct.",
].join(" ");

为什么这样设计

  1. 自动化 — 不需要用户手动提醒 "记住这个"
  2. 静默 — 用户看不到这轮对话,体验无感
  3. 防重复 — 每个压缩周期只触发一次
  4. 可配置 — 阈值、提示词都可以自定义

嵌入向量提供者

Moltbot 支持多种嵌入向量提供者,按优先级自动选择:

flowchart TD
    START[开始] --> LOCAL{本地模型<br/>已配置?}
    LOCAL -->|是| USE_LOCAL[使用 node-llama-cpp]
    LOCAL -->|否| OPENAI{OpenAI<br/>API Key?}
    OPENAI -->|是| USE_OPENAI[使用 text-embedding-3-small]
    OPENAI -->|否| GEMINI{Gemini<br/>API Key?}
    GEMINI -->|是| USE_GEMINI[使用 gemini-embedding-001]
    GEMINI -->|否| DISABLED[记忆搜索禁用]

批量嵌入优化

对于大量文本的索引,Moltbot 支持 OpenAI 和 Gemini 的批量 API:

{
  "agents": {
    "defaults": {
      "memorySearch": {
        "provider": "openai",
        "model": "text-embedding-3-small",
        "remote": {
          "batch": {
            "enabled": true,
            "concurrency": 2,        // 并行批次数
            "pollIntervalMs": 2000,  // 轮询间隔
            "timeoutMinutes": 60     // 超时时间
          }
        }
      }
    }
  }
}

批量 API 的优势:

  • 成本更低 — OpenAI 批量 API 约有 50% 折扣
  • 速度更快 — 异步处理,不阻塞主流程
  • 自动重试 — 429/5xx 错误自动重试最多 3 次

索引同步策略

Moltbot 采用多种策略保持索引新鲜:

触发时机行为
会话启动检查索引是否 dirty,异步触发同步
搜索时同步 dirty 文件后再搜索
文件变更监听 MEMORY.mdmemory/,1.5s 防抖
定时同步可配置的后台同步间隔
强制重建moltbot memory index 命令

重建检测

当以下配置变更时,自动触发全量重建:

  • 嵌入提供者 (provider)
  • 嵌入模型 (model)
  • 分块参数 (tokens/overlap)
  • 向量维度 (dims)

原子重建

为保证一致性,重建过程是原子的:

flowchart LR
    A[创建临时数据库] --> B[并行重建索引]
    B --> C[原子交换文件]
    C --> D{成功?}
    D -->|是| E[清理临时文件]
    D -->|否| F[从备份恢复]

工具接口

Moltbot 提供两个记忆工具给 AI 使用:

memory_search — 语义搜索

memory_search(
  query: string,       // 搜索查询
  maxResults?: number, // 最大结果数 (默认 6)
  minScore?: number    // 最低分数阈值 (默认 0.35)
)

// 返回
{
  results: [{
    path: "memory/2026-01-28.md",
    startLine: 15,
    endLine: 23,
    snippet: "...",
    score: 0.82
  }],
  provider: "openai",
  model: "text-embedding-3-small",
  fallback: false
}

memory_get — 读取文件

memory_get(
  path: string,   // 相对路径 (MEMORY.md 或 memory/*)
  from?: number,  // 起始行号
  lines?: number  // 读取行数
)

// 返回
{
  path: "memory/2026-01-28.md",
  text: "..."
}

路径验证:只允许访问 MEMORY.mdmemory/ 下的文件,防止目录遍历攻击。

与其他方案的对比

特性GPT MemoryGeminiMoltbot
存储位置服务端黑盒上下文窗口本地 Markdown
用户可见N/A
可编辑N/A
可迁移N/A✅ 复制目录即可
容量限制未知上下文长度磁盘空间
检索方式未知全量上下文混合搜索
主动保存自动(不透明)✅ 预压缩刷新

配置参考

完整的记忆搜索配置:

{
  "agents": {
    "defaults": {
      "memorySearch": {
        "enabled": true,
        "sources": ["memory"],  // 或 ["memory", "sessions"]
        "provider": "auto",     // "openai" | "gemini" | "local" | "auto"
        "model": "text-embedding-3-small",
        "fallback": "none",

        "local": {
          "modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/...",
          "modelCacheDir": null
        },

        "store": {
          "driver": "sqlite",
          "path": "~/.clawdbot/memory/{agentId}.sqlite",
          "vector": { "enabled": true }
        },

        "chunking": {
          "tokens": 400,
          "overlap": 80
        },

        "sync": {
          "onSessionStart": true,
          "onSearch": true,
          "watch": true,
          "watchDebounceMs": 1500
        },

        "query": {
          "maxResults": 6,
          "minScore": 0.35,
          "hybrid": {
            "enabled": true,
            "vectorWeight": 0.7,
            "textWeight": 0.3,
            "candidateMultiplier": 4
          }
        },

        "cache": {
          "enabled": true,
          "maxEntries": 50000
        }
      },

      "compaction": {
        "reserveTokensFloor": 20000,
        "memoryFlush": {
          "enabled": true,
          "softThresholdTokens": 4000
        }
      }
    }
  }
}

总结

Moltbot 的记忆系统体现了一种务实的设计哲学:

  1. 文件为源 — Markdown 是唯一的事实来源,数据库只是加速缓存
  2. 透明可控 — 用户可以直接查看、编辑、迁移记忆
  3. 混合检索 — 向量语义 + 关键词精确,互补而非替代
  4. 主动保存 — AI 在上下文压缩前自动保存重要信息
  5. 渐进降级 — 本地 → 远程 → 禁用,总有可用方案

这不是"IR 理论最优解",但正如文档所说:"简单、够快、实用"。

在大家都在堆复杂系统的时候,Moltbot 选择了一条更透明、更可控的路。这或许才是 AI 记忆系统应有的样子。


参考资料

相关文章

准备开始了吗?

先简单说明目标,我会给出最合适的沟通方式。