使用完 OpenClaw 之后我最大的疑问是一个 Agent 同时活跃在 Telegram、Slack、企微甚至本地 CLI 里它是怎么“记住我是谁”的这些记忆又是如何做到统一的难道我一晚上花掉几十刀的 token 全都是因为他的上下文工程做的太烂更关键的是——它到底“记住”的是什么是对话是总结还是被结构化后的知识01 背景介绍OpenClaw Moltbot/clawdbot近日大火想必也无需过多介绍我们姑且将它简单理解成一个个人 AI 助手功能和 Cloude Code 类似同样可以处理文件和编码调用不同 Skills 和 MCP 帮用户处理日常任务。关于 OpenClaw 的整体系统架构已经有很多文章进行了解读和分析其核心思想是通过统一的 Gateway 进程管理所有外部通讯平台的链接通过 Agent Runtime 进行各个大模型的后台调用所有产生的数据、记忆全部存储在本地磁盘上其系统架构如下所示总结来看OpenClaw 主要有以下三个优势使用便捷性接入了聊天工具让用户可以随时随地通过自己熟悉的聊天应用与 OpenClaw 交互让他去完成任务主观能动性可以帮用户完成定时任务仅仅使用自然语言对话就可以让其创建任务甚至会基于大模型来自主判断任务的紧急程度和执行顺序长期记忆可以把用户的交互过程记录在本地并不断更新以便在以后的日常对话中进行搜索将相关记忆捞回上下文中。如何让类似的智能体真正拥有记忆是目前 AI 领域最棘手的问题之一。在行业侧我们也看到了很多有关 AI 陪伴类、AI 分身类、AI 个人助手类的应用出现其核心难点也是在解决如何让大模型记住与用户有关的尽可能多的信息。当前 AI 类应用面临的核心困境在于大多数系统只是简单的把上下文窗口当作作为的记忆。根据 Anthropic 最新的开发者调研有 68% 的 AI 应用团队都在为上下文丢失的问题苦恼。但是上下文的特点是临时的、有限的Claude 200K tokens、GPT-4 128K tokens、昂贵的——当对话超过限制模型要么会遗忘早期信息要么因为成本暴涨而不得不重置会话窗口。而 OpenClaw 的记忆管理系统与上述不同他将记忆从上下文中剥离出来构建了一个分层的、可搜索的、持久化的知识管理架构。使得 Agent 能够 7*24h 不间断地积累知识、记住用户偏好、延续历史上下文等真正实现了从“无状态工具”到“有记忆伙伴”的进化。接下来主要会针对 OpenClaw 的记忆系统进行分析从架构设计和工程实现层面展示这个由 AI 自己创造的“大脑”。02 OpenClaw 关于“记忆”的定义正如上文提到的很多开发者会错误地把页面上的”上下文窗口“当作记忆的全部但这并不准确。我们常说的上下文Context是指模型在单次请求中能看到的所有信息比如系统提示词、历史对话、工具调用结果等。它的本质特征是临时存在的仅在当前请求有效、容量受限的Claude 200K tokens、GPT-4 128K tokens、成本高昂的每次请求都要重新传输和计算。如果仅以此作为记忆的全部试图将所有记录的信息都塞进上下文这显然是不科学的会很快遇到性能、成本瓶颈。OpenClaw 所代表的应用其对“记忆Memory”的定义是持久存储在磁盘上的结构化信息。因此只要有足够的存储空间记忆就可以无限增长能够跨会话保留在使用时按需检索且存储成本几乎为零。更为关键的是这种记忆能够支持语音搜索也就意味着不需要把所有历史信息都塞进上下文只需要检索当前任务相关的片段。所以可以将上下文理解成 AI 的“工作台”决定当下能处理什么而记忆则是 AI 的“知识库”决定长期能积累什么。这种对于记忆的架构创新使得 OpenClaw 可以在多轮交互上始终保持上下文的轻量和聚焦把长期的记忆信息放到可搜索的记忆层。在其 Github Issue #847 中有一份实验数据有人测试了一个持续 72h 的自动化任务传统的上下文方案因为 token 限制触发了 23 次会话重置而基于记忆的方案仅仅触发了 3 次而且每一次重置之后都能通过搜索恢复关键上下文信息。讲到这就不能饶过一个重要的问题既然 OpenClaw 已经在上下文和记忆存储上做了如此大的改造为什么我们在实际使用时消耗依旧飞起这难道不是说明他的上下文工程做的很烂吗这是一个非常好的问题我们一开始的理解没错记忆层确实是为了轻量化上下文设计的但实际上记忆层只解决了一部分问题还有很多其他因素在消耗 token比如系统提示词、调用的工具信息、会话历史等这一部分在附录二中进行了详细说明。03 记忆系统的存储架构OpenClaw 的记忆系统采用了一个双源记忆架构的设计其将记忆整体分为了两类每日日志动态记忆和长期记忆静态记忆。记忆类型存储格式存储路径产生方式静态记忆Markdown 文件~/.openclaw/workspace/MEMORY.md 和 memory/*.md手动创建 自动生成动态记忆JSONL 文件~/.openclaw/agents/{agentId}/sessions/*.jsonl自动记录从直觉上看这是符合人类大脑的记忆机制的人的记忆也是由一个个重点片段组成的我们能记住的也只是某个特定的时刻、某件具体的事件把这些片段串联起来才形成了人类所谓的记忆至于每天发生的琐事终究会随着时间而被淡忘。因此此种记忆机制也可以说是实现了工程学和生物学上的高度统一。3.1 动态记忆的产生每次用户与 Agent 交互时系统会自动将对话内容追加到 JSONL 格式的会话日志文件中这是最原始的、未经处理过的记忆存储位置如下动态记忆的 JOSNL 格式示例如下{type:message,message:{role:user,content:帮我写一个 Python 爬虫}} {type:message,message:{role:assistant,content:好的我来帮你写...}} {type:tool_call,tool:bash,input:{command:python crawler.py}}代码实现方式如下export async function buildSessionEntry(absPath: string): PromiseSessionFileEntry | null { // 读取 JSONL 文件 const raw await fs.readFile(absPath, utf-8); const lines raw.split(\n); const collected: string[] []; for (const line of lines) { const record JSON.parse(line); // 只提取 user 和 assistant 的消息 if (message.role user || message.role assistant) { const label message.role user ? User : Assistant; collected.push(${label}: ${text}); } } return { path: sessionPathForFile(absPath), hash: hashText(content), content, // User: 帮我写... \n Assistant: 好的... }; }3.2 静态记忆的产生静态记忆是整个系统的长期记忆可以理解成是在短期的琐碎记忆中提炼出来的需要系统重点记住的内容比如用户的性格、身份信息、回答偏好等其文件存储结构如下静态记忆的产生途径分为以下三种途径一用户手动创建。用户可以直接编辑 MEMORY.md 文件写入需要 Agent 长期记住的信息比如“你要称呼我为老板”、“我喜欢简洁的回复”等。途径二session-memory Hook 自动转换。当用户执行 /new 命令重置会话时系统会触发 session-memory Hook自动将上一个会话的关键内容转换为 Markdown 文件。大致的流程为读取会话日志从 JSONL 文件中提取最近 N 条默认 15 条user/assistant 消息生成语义化文件名使用 LLM 根据对话内容生成描述性 slug如 api-design、bug-fix写入 Markdown 文件生成 memory/YYYY-MM-DD-{slug}.md 文件。途径三Memory Flush 自动写入类似于记忆刷新。这是一个非常关键的自动化机制当会话上下文接近 token 限制时系统会在压缩compaction前触发一个特殊的 Agent 回合在该回合中Agent 被明确指示将需要持久保存的重要信息写入 memory/YYYY-MM-DD.md 文件。特性MEMORY.mdmemory/*.md用途核心长期记忆按时间组织的会话记忆内容类型用户偏好、重要信息、工作流程等具体会话的摘要和细节更新方式用户手动维护为主系统自动生成为主命名规则固定为 MEMORY.mdYYYY-MM-DD(-{slug}).md检索优先级平等由向量相似度决定整个静态记忆系统的设计核心在于途径三往往“健忘”的问题就出在这一步即如何进行历史对话信息的压缩。其实此处OpenClaw 的处理方法比较粗暴是使用 LLM 直接对历史对话信息进行处理。首先通过 Memory Flush 进行一次记忆筛选让 Agent 自己判断什么是durable memories持久记忆代码如下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 ${SILENT_REPLY_TOKEN}., ].join( );然后最终压缩就是再使用 LLM 对历史消息进行有损摘要这里的默认指令只要求保留 “decisions, TODOs, open questions, constraints”不保留具体数值、时间点等细节代码如下const MERGE_SUMMARIES_INSTRUCTIONS Merge these partial summaries into a single cohesive summary. Preserve decisions, TODOs, open questions, and any constraints.;这正是 OpenClaw 在记忆上的平衡通过把历史会话记录JSONL 格式压缩成记忆Markdown 格式来避免上下文溢出的问题赋予系统记忆的能力但与此同时带来的问题也显而易见在进行压缩时难免会有信息的流失这在大多数场景下是合理的但对于具体几点这类精确信息确实是弱点。但并这不是 bug而是在长期记忆完整性和系统效率/成本之间的设计取舍。用户如果有重要的精确信息可以主动要求 Agent 记录到长期记忆中。好了现在解释了第一个问题OpenClaw 到底记住的是什么信息下面我们来看这些所谓的“记忆”是怎么在需要的时候被“记起来”的。04 记忆信息的检索与调用当产生并保存一个记忆文件.md时后台会自动触发如下的索引构建流程并在需要时进行检索4.1 索引构建默认情况下只有 Markdown 文件会被索引而 JSONL 会话日志不会被索引。Markdown 文件首先被分块默认每个块包含 400 tokens相邻块重叠 80 tokens而后每个块同时生成向量 Embedding支持三种OpenAI、Gemini、本地 和文本 Token分别存入 sqlite-vec 和 FTS5 索引这两个都是 SQLite 扩展意味着整个系统只依赖一个轻量级数据库文件不需要部署 ES 或者 Milvus。SQLite 数据库的核心 Schema 如下所示-- 文件元数据 CREATE TABLE files ( path TEXT PRIMARY KEY, -- memory/projects.md source TEXT NOT NULL, -- memory | sessions hash TEXT NOT NULL, -- SHA256 用于增量更新 mtime INTEGER NOT NULL, size INTEGER NOT NULL ); -- 文本块带 embedding CREATE TABLE chunks ( id TEXT PRIMARY KEY, -- UUID path TEXT NOT NULL, -- 来源文件 source TEXT NOT NULL, -- memory | sessions start_line INTEGER, end_line INTEGER, hash TEXT NOT NULL, model TEXT NOT NULL, -- text-embedding-3-small text TEXT NOT NULL, -- 原文 embedding TEXT NOT NULL, -- JSON 数组 [0.1, 0.2, ...] updated_at INTEGER ); -- 向量索引sqlite-vec 扩展 CREATE VIRTUAL TABLE chunks_vec USING vec0(...); -- 全文索引FTS5 CREATE VIRTUAL TABLE chunks_fts USING fts5( path, source, model, text, tokenizeporter unicode61 );对于具体的记忆索引实现流程在附录一中进行了详细阐述。4.2 记忆搜索OpenClaw 采用关键词 向量的混合加权搜索结合了两者的优势。当 Agent 需要搜索记忆时系统会启动两个搜索引擎向量搜索基于语义相似度。把之前向量化之后的内容通过计算余弦相似度找到意思相近的内容这个直接依赖 sqlite-vec 的扩展实现无需外部向量数据库BM25 关键词搜索基于词频统计。使用 SQLite 内置的 FTS5 全文检索引擎找到包含精确关键词的内容。混合检索的代码实现方式如下async search(query: string, opts?: { maxResults?: number; minScore?: number }) { // 1. 关键词搜索 (BM25) const keywordResults hybrid.enabled ? await this.searchKeyword(cleaned, candidates) : []; // 2. 向量搜索 const queryVec await this.embedQueryWithTimeout(cleaned); const vectorResults await this.searchVector(queryVec, candidates); // 3. 混合排序 if (!hybrid.enabled) { return vectorResults.filter(r r.score minScore).slice(0, maxResults); } const merged this.mergeHybridResults({ vector: vectorResults, keyword: keywordResults, vectorWeight: 0.7, // 向量权重 textWeight: 0.3, // 关键词权重 }); return merged.filter(r r.score minScore).slice(0, maxResults); }4.3 加权融合两个引擎的搜索结果按照 70:30 的权重合并最终得分 0.7 向量相似度 0.3 BM25 得分且只有得分超过 0.35 的结果才会被返回。混合排序实现方式如下所示const merged Array.from(byId.values()).map((entry) { // 最终得分 向量权重 × 向量相似度 关键词权重 × BM25 得分 const score vectorWeight * entry.vectorScore textWeight * entry.textScore; return { path, startLine, endLine, score, snippet, source }; }); return merged.toSorted((a, b) b.score - a.score);系统默认的 70% 和 30% 的比例来自与内部测试的结果在其模拟进行的 1000 次复杂查询测试中纯向量搜索的召回率为 76%纯 BM25 搜索的召回率为 68%而 70/30 的混合策略召回率达到 89%。4.4 Agent 交互基于上述介绍的记忆系统架构在实际使用时AI Agent 则是通过两个接口实现与整个记忆系统的交互。两个核心工具memory_search语义搜索此工具用来调用上述的记忆检索功能工具的定义如下return { label: Memory Search, name: memory_search, description: Mandatory recall step: semantically search MEMORY.md memory/*.md (and optional session transcripts) before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with path lines., parameters: MemorySearchSchema, execute: async (_toolCallId, params) { // 搜索查询文本必填 const query readStringParam(params, query, { required: true }); // 返回结果数量默认 6 const maxResults readNumberParam(params, maxResults); // 最低相关度阈值默认 0.35 const minScore readNumberParam(params, minScore); const { manager, error } await getMemorySearchManager({ cfg, agentId, }); // ... 执行搜索 const results await manager.search(query, { maxResults, minScore, sessionKey: options.agentSessionKey, }); return jsonResult({ results, provider: status.provider, model: status.model, fallback: status.fallback, }); }, };返回格式如下{ results: [ { path: memory/2026-01-10.md, startLine: 15, endLine: 20, score: 0.85, snippet: 用户提到喜欢蓝色特别是天空蓝..., source: memory }, { path: MEMORY.md, startLine: 5, endLine: 8, score: 0.72, snippet: 颜色偏好蓝色系..., source: memory } ], provider: openai, model: text-embedding-3-small }memory_get精确读取此工具用来精准读取记忆文件工具的定义如下return { label: Memory Get, name: memory_get, description: Safe snippet read from MEMORY.md, memory/*.md, or configured memorySearch.extraPaths with optional from/lines; use after memory_search to pull only the needed lines and keep context small., parameters: MemoryGetSchema, execute: async (_toolCallId, params) { // 文件相对路径如 memory/2026-01-10.md const relPath readStringParam(params, path, { required: true }); // 起始行号 const from readNumberParam(params, from, { integer: true }); // 读取行数 const lines readNumberParam(params, lines, { integer: true }); // ... 读取文件 const result await manager.readFile({ relPath, from: from ?? undefined, lines: lines ?? undefined, }); return jsonResult(result); }, };返回的格式如下{ text: 用户提到喜欢蓝色特别是天空蓝。\n在选择UI时偏好冷色调。\n..., path: memory/2026-01-10.md }Agent 记忆交互的指令约束Agent 被明确指示必须在特定场景下使用记忆工具return [ ## Memory Recall, Before answering anything about prior work, decisions, dates, people, preferences, or todos: run memory_search on MEMORY.md memory/*.md; then use memory_get to pull only the needed lines. If low confidence after search, say you checked., , ];Agent 主动写入记忆在 OpenClaw 的交互中Agent 也可以主动写入记忆文件使用标准的文件操作工具。比如当 Agent 判断需要记住某些信息时会使用 exec 或 write 工具写入对应的 memory/YYYY-MM-DD.md 文件例如exec(commandecho ## 用户偏好\n- 喜欢蓝色 memory/...) write(pathmemory/2026-02-05.md, content用户喜欢蓝色)而后 MemoryIndexManager 检测到文件变更 (fs.watch)会触发增量同步和更新 SQLite 索引这在上文提到的 Memory Flush 场景中特别重要。交互的安全边界为了保证整个系统的安全边界对 memory_get 制定了严格的路径限制规定其只能读取到特定位置的文件async readFile(params: { relPath: string; from?: number; lines?: number; }): Promise{ text: string; path: string } { // ... 路径验证 const inWorkspace relPath.length 0 !relPath.startsWith(..) !path.isAbsolute(relPath); const allowedWorkspace inWorkspace isMemoryPath(relPath); // 只允许读取 // 1. MEMORY.md / memory.md // 2. memory/*.md // 3. 配置的 extraPaths 中的 .md 文件 if (!allowedWorkspace !allowedAdditional) { throw new Error(path required); // 安全限制 } if (!absPath.endsWith(.md)) { throw new Error(path required); // 只允许 .md 文件 } // ... }05 一个典型的记忆系统交互流程场景用户询问 “我之前说过喜欢什么颜色”不难看出类似 OpenClaw 类型的个人助手使用体验大大依赖于所选用的基础大模型的性能如果选择某些开源的轻量化模型很容易陷入复杂工具调用的死循环而选择更优性能的闭源模型又会带来不可避免的成本开销那么问题来了C 端用户到底需不需要更高性能的模型06 附录一记忆索引的实现流程此部分将结合具体的例子详细整理记忆索引的完整流程。首先我们假设一个场景用户在 1 月 10 日与 Agent 进行了一次对话讨论了需要 Agent 设置提醒的事项在用户执行 /new 命令后session-memory 会自动将该对话进行提取并转化为 Markdown 文件如下文件路径~/.openclaw/memory/2026-01-10-reminders.md文件内容# Session: 2026-01-10 08:00:15 UTC ## Summary 用户请求设置每日提醒讨论了健身计划安排。 ## Key Points - 用户想要每天下午3点收到健身提醒 - 健身计划包括周一胸肌、周三背部、周五腿部 - 用户偏好使用 Telegram 接收提醒通知 ## Conversation Highlights User: 帮我设置一个每日提醒下午3点提醒我去健身 Assistant: 好的我已经为你设置了每日下午3点的健身提醒。 User: 对了我的健身计划是周一练胸周三练背周五练腿 Assistant: 明白了我已记录你的健身计划安排。 ## Action Items - [x] 设置每日 15:00 健身提醒 - [ ] 后续可添加具体训练动作细节6.1 Step 1: 文件发现与变更检测当存入了新的记忆文件会调用 sync() 方法系统首先会扫描记忆目录检测哪些文件需要索引如果发现记忆文件没有变化那么也就不需要重新构建一次索引。列出所有记忆文件找到所有和记忆有关的文件为下一步的判重做准备const files await listMemoryFiles(this.workspaceDir, this.settings.extraPaths); // 结果: [~/.openclaw/MEMORY.md, ~/.openclaw/memory/2026-01-10-reminders.md, ...]构建文件元信息对每个文件调用 buildfileEntry()只读取文件并计算整体 hash不做分块等处理。// buildFileEntry 实现 const stat await fs.stat(absPath); const content await fs.readFile(absPath, utf-8); // 读取整个文件 const hash hashText(content); // 计算整个文件的 hash return { path: memory/2026-01-10-reminders.md, absPath: /Users/xxx/.openclaw/memory/2026-01-10-reminders.md, mtimeMs: 1736496015000, size: 847, hash: e4d909c290d0fb1ca068ffaddf22cbd0 // 整个文件内容的 MD5 };判断是否需要重新索引结合上述文件的 hash 就可以判断出新产生的记忆文件是否与原有的文件相同如果相同就说明记忆没有变化省去了重新索引这一步。const record this.db .prepare(SELECT hash FROM files WHERE path ? AND source ?) .get(entry.path, memory); // 情况A: 数据库中无记录或 hash 不匹配 → 需要索引 // 情况B: hash 匹配 → 跳过无需重新索引 if (!params.needsFullReindex record?.hash entry.hash) { return; // 跳过 } await this.indexFile(entry, { source: memory }); // 需要索引这个情境中我们假设记忆是新的那么此时会输出结果[需要索引] memory/2026-01-10-reminders.md (hash: e4d909c290d0fb1ca068ffaddf22cbd0)6.2 Step 2: 文本分块经过上一步只有确定需要索引的文件才会使用 indexFile() 方法进行分块处理。调用的分块函数如下const content await fs.readFile(entry.absPath, utf-8); const chunks chunkMarkdown(content, this.settings.chunking); // 默认配置: { tokens: 400, overlap: 80 } export function chunkMarkdown( content: string, chunking: { tokens: number; overlap: number }, ): MemoryChunk[] { const lines content.split(\n); const maxChars Math.max(32, chunking.tokens * 4); // 1600 chars const overlapChars Math.max(0, chunking.overlap * 4); // 320 chars const chunks: MemoryChunk[] []; let current: Array{ line: string; lineNo: number } []; let currentChars 0; const flush () { // 将当前累积的行打包成一个 chunk const text current.map((entry) entry.line).join(\n); const startLine firstEntry.lineNo; const endLine lastEntry.lineNo; chunks.push({ startLine, endLine, text, hash: hashText(text), // SHA-256(chunk文本) }); }; const carryOverlap () { // 保留最后 overlapChars 个字符到下一个 chunk滑动窗口 // ... }; for (let i 0; i lines.length; i 1) { // 逐行累积超过 maxChars 就 flush if (currentChars lineSize maxChars current.length 0) { flush(); carryOverlap(); // 保留重叠部分 } current.push({ line: segment, lineNo }); currentChars lineSize; } flush(); // 最后一批 return chunks; }上述步骤中的分块算法按照以下规则切分文本按行遍历累积字符数默认约为 tokens * 4 个字符遇到 Markdown 标题#时优先断开保持语义完整块之间有重叠默认约 overlap * 4 个字符确保跨块检索的连续性。按照实例文件分块之后可能会被切分为 2 个块Chunk #startLineendLinetext摘要hashChunk 0112# Session: 2026-01-10…## Summary## Key Points- 用户想要每天下午3点…7a8b9c…Chunk 11022- 用户偏好使用 Telegram…## Conversation Highlights## Action Items3d4e5f…6.3 Step 3: 向量化Embedding上述每个 Chunk 的文本需要转化为向量用于进行语义搜索。首先系统会先查询已有的 embedding_cache 表看是否已经有相同文本的向量缓存const cached this.loadEmbeddingCache(chunks.map(chunk chunk.hash)); // 如果 chunk.hash 在缓存中命中直接复用向量跳过 API 调用对于缓存未命中的 Chunk调用配置好的 embedding provider如 OpenAI返回向量化后的结果。// 批量请求 embedding const batchEmbeddings await this.provider.embedBatch([ # Session: 2026-01-10 08:00:15 UTC\n\n## Summary\n用户请求设置每日提醒..., - 用户偏好使用 Telegram...\n\n## Conversation Highlights... ]); // 返回结果1536维向量text-embedding-3-small // Chunk 0: [0.0234, -0.0567, 0.0891, ..., -0.0123] (1536个浮点数) // Chunk 1: [0.0345, -0.0678, 0.0902, ..., -0.0234] (1536个浮点数)而后更新向量缓存this.upsertEmbeddingCache([ { hash: 7a8b9c..., embedding: [0.0234, -0.0567, ...] }, { hash: 3d4e5f..., embedding: [0.0345, -0.0678, ...] } ]);6.4 Step 4: 持久化存储向量化完成后系统会将上述数据写入 SQLite 数据库中的三张表。生成 Chunk ID首先需要先为每个 chunk 生成唯一的标识其是由多个因素组合 hash 生成。const id hashText( ${source}:${path}:${startLine}:${endLine}:${chunkHash}:${model} ); // Chunk 0 的 id 计算: // hashText(memory:memory/2026-01-10-reminders.md:1:12:7a8b9c...:text-embedding-3-small) // → a1b2c3d4e5f6... // Chunk 1 的 id 计算: // hashText(memory:memory/2026-01-10-reminders.md:10:22:3d4e5f...:text-embedding-3-small) // → g7h8i9j0k1l2...写入 chunks 主表主表中会存储每一个 chunk 所包含的/相关的完整元数据和向量JSONL 字符串格式比如在该例子中存入主表的一条信息如下this.db.prepare( INSERT INTO chunks (id, path, source, start_line, end_line, hash, model, text, embedding, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ).run( a1b2c3d4e5f6..., // id memory/2026-01-10-reminders.md, // path memory, // source 1, // start_line 12, // end_line 7a8b9c..., // chunk hash text-embedding-3-small, // model # Session: 2026-01-10..., // 原始文本 [0.0234, -0.0567, ...], // 向量 (JSON 字符串) 1736496100000 // updated_at );写入 chunks_vec 向量表此处的 sqlite_vec 为虚拟表专门用于向量相似度语义相似度的搜索表中存储的内容示例如下this.db.prepare(INSERT INTO chunks_vec (id, embedding) VALUES (?, ?)) .run( a1b2c3d4e5f6..., // 与 chunks 表相同的 id vectorToBlob([0.0234, ...]) // 转换为二进制 BLOB 格式 );写入 chunks_fts 全文搜索表此处的 FTS5 也是虚拟表而且 SQLite FTS5 会自动对 text 字段进行分词不需要手动进行分词处理。this.db.prepare( INSERT INTO chunks_fts (text, id, path, source, model, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?, ?) ).run( # Session: 2026-01-10..., // text (FTS5 自动分词) a1b2c3d4e5f6..., // id (UNINDEXED不分词) memory/2026-01-10-reminders.md, // path (UNINDEXED) memory, // source (UNINDEXED) text-embedding-3-small, // model (UNINDEXED) 1, // start_line (UNINDEXED) 12 // end_line (UNINDEXED) );上述有些字段标记为 UNINDEXED意味着不会参与全文搜索索引只是作为元数据进行存储方便后续 JOIN 查询和过滤只有 text 字段会被 FTS5 分词和建立倒排索引。更新 files 表上述存储完成后会再次记录文件级别的元信息用于下次进行变更检测。this.db.prepare( INSERT INTO files (path, source, hash, mtime, size) VALUES (?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET hashexcluded.hash, mtimeexcluded.mtime, sizeexcluded.size ).run( memory/2026-01-10-reminders.md, memory, e4d909c290d0fb1ca068ffaddf22cbd0, // 整个文件的 hash 1736496015000, 847 );6.5 Step 5: 最终数据库状态1、 files 表pathsourcehashmtimesizememory/2026-01-10-reminders.mdmemorye4d909c290d0fb1ca068ffaddf22cbd017364960150008472、 chunks 表主表idpathsourcestart_lineend_linehashmodeltextembeddinga1b2c3d4e5f6memory/2026-01-10-reminders.mdmemory1127a8b9c…text-embedding-3-small# Session: 2026-01-10 08:00:15 UTC## Summary用户请求设置每日提醒…[0.0234,-0.0567,…]g7h8i9j0k1l2memory/2026-01-10-reminders.mdmemory10223d4e5f…text-embedding-3-small- 用户偏好使用 Telegram…## Conversation Highlights## Action Items[0.0345,-0.0678,…]3、 chunks_vec 表sqlite-vec 向量表idembedding (BLOB)a1b2c3d4e5f6…binary: 1536 × float32 6144 bytesg7h8i9j0k1l2…binary: 1536 × float32 6144 bytes4、 chunks_fts 表FTS5 全文索引表textidpathsourcemodelstart_lineend_line# Session: 2026-01-10…a1b2c3d4e5f6memory/2026-01-10-reminders.mdmemorytext-embedding-3-small112- 用户偏好使用 Telegram…g7h8i9j0k1l2memory/2026-01-10-reminders.mdmemorytext-embedding-3-small1022注在FTS5 内部会自动为 text 列建立倒排索引如 - 健身 → [a1b2c3d4e5f6..., g7h8i9j0k1l2...] - 提醒 → [a1b2c3d4e5f6...] - Telegram → [g7h8i9j0k1l2...]07 附录二token 消耗的计算首先列出在 OpenClaw 中 token 消耗的完整构成接下来我们逐项进行分析1、 System Prompt系统提示词这是系统的固定开销在每次请求前会从多处构建系统提示词OpenClaw 规定构建系统提示词时参考的每个文件最大 20000 字符估算下来大概每次请求会有 5,000-25,000 字符是系统提示词。下表展示了 System Prompt 包含的内容组成部分大小估算说明核心指令~3,000 字符安全规则、回复格式、消息路由等工具说明列表~2,000 字符每个工具的简要说明Skills 提示可变技能描述和位置Bootstrap 文件最大 20,000 字符AGENTS.md, SOUL.md, IDENTITY.md 等Runtime 信息~500 字符主机、时区、模型等Sandbox 信息~300 字符沙箱配置2、 工具定义Tool SchemasOpenClaw 所调用的每个工具都有自己的 JSON Schema 定义这些定义会随着每次请求被发送估算下来每次请求中工具定义大约占 3000-5000 tokens 且无法压缩。OpenClaw 默认启用的部分工具如下所示工具Schema 复杂度估算 tokensbrowser非常复杂16 种 action~800exec中等~200read/write/edit简单~300message复杂多种 action~400cron中等~200memory_search/get简单~150sessions_*中等~400canvas中等~200nodes复杂~300………3、 会话历史即使有 Compaction 机制但在触发压缩之前会话历史依然在累积请求 1System Prompt 用户消息 1 回复 1 请求 2System Prompt 用户消息 1 回复 1 用户消息 2 回复 2 请求 3System Prompt 历史累积... 用户消息 3 ... 请求 N触发 Compaction → 摘要替换历史关键问题是为了尽可能优化用户体验这个压缩的阈值通常设定的非常高比如默认是 20000 字符这意味着系统会让历史累积到接近上下文窗口限制才压缩在此之前 token 消耗持续增长。4、 Memory Flush在会话压缩前调用的 Memory Flush 更是一个独立的 LLM 调用这就使得每次压缩前会额外消耗一次完整的 LLM 调用成本export async function runMemoryFlushIfNeeded(params: { cfg: OpenClawConfig; followupRun: FollowupRun; // ... }): PromiseSessionEntry | undefined { // 触发一个完整的 Agent 回合 // 包含 System Prompt 工具 指令 await runEmbeddedPiAgent({ // ... prompt: memoryFlushSettings.prompt, // ... }); }5、 记忆检索结果当 Agent 使用 memory_search 与记忆系统交互时返回的结果也会加入上下文用户: 我之前说过喜欢什么颜色 ↓ Agent 调用: memory_search(query喜欢的颜色) ↓ 返回结果: - memory/2026-01-10.md:15-20 (score: 0.85) 用户提到喜欢蓝色特别是天空蓝... - MEMORY.md:5-8 (score: 0.72) 颜色偏好蓝色系... ↓ 这些结果被加入上下文 → 消耗 token6、 工具调用链一个简单的任务也可能触发多次工具调用每次调用都是请求 响应token 双向计费。用户: 帮我查一下天气并发到 Telegram 调用链: 1. web_search(天气) → 返回结果 (500 tokens) 2. memory_search(用户位置) → 返回结果 (200 tokens) 3. message(channeltelegram, ...) → 返回确认 (100 tokens)因此可以看出记忆层确实实现了轻量化上下文的设计目标但还有很多问题无法解决比如固定开销无法消除System Prompt、工具定义每次都要发送、压缩是惰性的、记忆检索是增量成本、工具调用有额外成本。记忆层的真正价值不是降低单次成本而是使无限长的对话成为可能没有记忆层上下文会爆炸同时保持相关信息的可访问性压缩后仍可通过搜索找回如果想显著降低成本需要从减少固定开销精简 System Prompt、禁用工具和使用更便宜的模型入手。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】