Ai读码,OpenClaw 源码深度解析(三):记忆篇 — 会话持久化、压缩与记忆系统
三层记忆,各司其职— 短期(jsonl) + 日记(daily md) + 长期(MEMORY.md),类似人类的记忆系统树结构 transcript— 支持 fork/分支,不是简单的追加日志— 压缩前强制刷盘,防止重要信息丢失。这是整个系统最有创意的设计— 压缩时保留失败记录,防止 Agent 重蹈覆辙— 主动裁剪过期工具结果,配合 heartbeat 保热,优化 token 成本NO_REP
OpenClaw 源码深度解析(三):记忆篇 — 会话持久化、压缩与记忆系统
OpenClaw v2026.2.25 | Node.js 22+ | TypeScript ESM
引言
一个 AI Agent 如果只能记住当前对话,那它就是个玩具。真正有用的 Agent 需要长期记忆——记住你是谁、上次聊了什么、你的偏好和习惯。
OpenClaw 的记忆系统是整个架构中最精巧的部分。它不是一个简单的数据库,而是一个三层文件系统 + 智能压缩 + 记忆刷盘的复合体系。本文深入解析这个系统——从 jsonl transcript 的树结构到 auto-compaction 的触发机制,从 MEMORY.md 的蒸馏逻辑到 cache-TTL 上下文裁剪。
一、三层记忆架构
OpenClaw 的记忆不是单一存储,而是三层各有分工的体系:
┌─────────────────────────────────────────────────────┐
│ Layer 1: MEMORY.md — 精选长期记忆 │
│ ├── 核心事实、偏好、决策 │
│ ├── 只在主会话加载 (安全考虑) │
│ └── 由 daily notes 蒸馏而来 │
├─────────────────────────────────────────────────────┤
│ Layer 2: memory/YYYY-MM-DD.md — 每日记忆日志 │
│ ├── 当天事件的原始记录 (append-only) │
│ ├── 不自动注入 prompt │
│ └── Agent 用 read 工具按需读取 │
├─────────────────────────────────────────────────────┤
│ Layer 3: <sessionId>.jsonl — 对话记录 │
│ ├── 完整的对话树 (user/assistant/tool messages) │
│ ├── Agent Loop 的"工作记忆" │
│ └── 超过上限时自动压缩 │
└─────────────────────────────────────────────────────┘
类比:
- Layer 3 (jsonl) = 你的短期工作记忆(正在处理的事)
- Layer 2 (daily md) = 你的日记本(今天发生了什么)
- Layer 1 (MEMORY.md) = 你的长期知识(你知道的重要事实)
二、Layer 3:Transcript (对话记录)
2.1 文件格式
Transcript 是 JSONL 格式——每行一个 JSON 对象,支持增量写入,不需要解析整个文件就能追加新内容。
// 第一行: session header
{"type":"session","id":"sess_20260310_abc","cwd":"/home/user/.openclaw/workspace","timestamp":"2026-03-10T15:00:00Z","parentSession":null}
// 第二行+: 消息 (树结构)
{"id":"msg_001","parentId":"sess_20260310_abc","role":"user","content":"帮我查一下天气","timestamp":"2026-03-10T15:00:01Z"}
{"id":"msg_002","parentId":"msg_001","role":"assistant","content":"好的,我来帮你查。","toolCalls":[{"id":"call_1","name":"web_fetch","input":{"url":"https://wttr.in/Beijing?format=j1"}}],"timestamp":"2026-03-10T15:00:02Z"}
{"id":"msg_003","parentId":"msg_002","role":"tool","name":"web_fetch","toolCallId":"call_1","content":"北京 晴 15-25°C 风力3级","timestamp":"2026-03-10T15:00:03Z"}
{"id":"msg_004","parentId":"msg_003","role":"assistant","content":"今天北京晴天,气温15-25°C,风力3级,适合外出活动。","timestamp":"2026-03-10T15:00:04Z"}
2.2 树结构设计
每条消息有 id 和 parentId,形成一棵对话树:
sess_20260310_abc (root)
└── msg_001 (user: 帮我查一下天气)
└── msg_002 (assistant: 好的... [tool_call: web_fetch])
└── msg_003 (tool: 天气数据)
└── msg_004 (assistant: 今天北京晴天...)
为什么是树而不是列表? 因为 OpenClaw 支持会话分支。你可以从某个历史节点 fork 出新分支,探索不同的对话路径。这在 coding agent 场景中特别有用——"试试另一个方案"时不需要从零开始。
2.3 SessionManager
Pi SDK 的 SessionManager 负责读写这些文件:
// Pi SDK 内部 (简化)
class SessionManager {
private file: string;
private entries: Map<string, SessionEntry>; // id → entry
static open(file: string): SessionManager {
// 1. 读取 jsonl 文件
const lines = readFileSync(file, 'utf-8').split('\n');
// 2. 解析每一行为 SessionEntry
// 3. 构建 id → entry 映射和 parentId 索引
return new SessionManager(file, entries);
}
append(entry: SessionEntry): void {
// 追加写入 (原子操作)
appendFileSync(this.file, JSON.stringify(entry) + '\n');
this.entries.set(entry.id, entry);
}
getHistory(): SessionEntry[] {
// 从 root 开始,按 parentId 链遍历,返回有序的消息列表
return this.traverseTree(this.rootId);
}
}
2.4 SessionManager 缓存
频繁打开和解析 jsonl 文件很浪费。OpenClaw 缓存了 SessionManager 实例:
// src/agents/pi-embedded-runner/session-manager-cache.ts
const cache = new Map<string, CacheEntry>();
interface CacheEntry {
manager: SessionManager;
lastAccess: number;
refCount: number;
}
function getSessionManager(file: string): SessionManager {
const entry = cache.get(file);
if (entry) {
entry.lastAccess = Date.now();
entry.refCount++;
return entry.manager;
}
const manager = SessionManager.open(file);
cache.set(file, { manager, lastAccess: Date.now(), refCount: 1 });
return manager;
}
function releaseSessionManager(file: string): void {
const entry = cache.get(file);
if (entry) {
entry.refCount--;
if (entry.refCount <= 0) {
cache.delete(file);
}
}
}
缓存有 TTL(5 分钟),超时自动释放。这样同一 session 短时间内收到多条消息时,不需要重复解析。
2.5 损坏修复
jsonl 文件可能损坏(进程崩溃时写到一半)。OpenClaw 有自动修复机制:
// src/agents/session-file-repair.ts
function repairSessionFileIfNeeded(file: string): void {
const lines = readFileSync(file, 'utf-8').split('\n');
const valid: string[] = [];
let repaired = false;
for (const line of lines) {
if (!line.trim()) continue;
try {
JSON.parse(line);
valid.push(line);
} catch {
// 跳过无效行 (损坏的 JSON)
repaired = true;
}
}
if (repaired) {
// 备份原文件
copyFileSync(file, `${file}.bak.${Date.now()}`);
// 写入修复后的内容
writeFileSync(file, valid.join('\n') + '\n');
}
}
三、Session Store (会话元数据)
3.1 sessions.json
除了 transcript 文件,OpenClaw 还维护一个 sessions.json,存储每个 session 的元数据:
{
// sessionKey → SessionEntry
"agent:main:telegram:user:123456": {
"sessionId": "sess_20260310_abc",
"updatedAt": 1709984400000,
"chatType": "direct",
"provider": "telegram",
"displayName": "张三",
"inputTokens": 15230,
"outputTokens": 3840,
"totalTokens": 19070,
"contextTokens": 45000,
"thinkingLevel": 2,
"modelOverride": null,
"compactionCount": 3,
"memoryFlushAt": 1709980800000,
"memoryFlushCompactionCount": 3
},
"agent:main:telegram:group:-1009876543210": {
"sessionId": "sess_20260310_def",
"updatedAt": 1709983500000,
"chatType": "group",
"compactionCount": 0
}
}
为什么需要两层?
| sessions.json | .jsonl transcript | |
|---|---|---|
| 用途 | 快速查找 session 状态 | 存储完整对话内容 |
| 大小 | 小 (几百 KB) | 大 (可达数 MB) |
| 读写频率 | 每次消息更新 | 每次消息追加 |
| 可编辑 | ✅ 安全可编辑 | ⚠️ 不建议手动编辑 |
3.2 自动维护
sessions.json 有自动清理机制,防止无限膨胀:
// 配置
interface MaintenanceConfig {
mode: 'warn' | 'enforce'; // warn=只报告, enforce=自动清理
pruneAfter: string; // 默认 '30d' — 30天不活跃的 session
maxEntries: number; // 默认 500 — 最多保留多少 session
rotateBytes: number; // 默认 10MB — 文件超过多大时轮转
maxDiskBytes?: number; // 总磁盘预算 (可选)
highWaterBytes?: number; // 清理目标 (默认 maxDiskBytes * 80%)
}
// 清理顺序
// 1. 删除最老的归档/orphan transcript 文件
// 2. 如果还不够,驱逐最老的 session entry + 对应 transcript
// 3. 直到磁盘使用低于 highWaterBytes
3.3 Session 生命周期
新消息到达
│
▼
sessionKey 是否存在于 sessions.json?
├── 否 → 创建新 entry,分配新 sessionId
└── 是 → 检查是否需要重置:
│
├── Daily Reset (默认每天凌晨 4:00)
│ └── 到时间后第一条消息触发新 session
│
├── Idle Reset (配置 session.reset.idleMinutes)
│ └── 超过空闲时间后第一条消息触发新 session
│
└── 都不需要 → 复用现有 session
Daily Reset 和 Idle Reset 是惰性触发的——不是定时器主动重置,而是下一条消息到达时检查条件。
四、Auto-Compaction (自动压缩)
这是记忆系统最核心的机制。随着对话越来越长,context tokens 会逼近模型的上限。如果不处理,LLM 会报 context overflow 错误。
4.1 触发条件
Auto-compaction 由 Pi SDK 决定触发,有两种场景:
// 场景 1: 溢出恢复
// LLM 返回 context overflow 错误 → 自动压缩 → 用压缩后的历史重试
// 场景 2: 阈值维护
// 每次成功 turn 后检查:
if (contextTokens > contextWindow - reserveTokens) {
// 触发压缩
// reserveTokens 默认 16384 (为下一轮留余量)
}
4.2 压缩过程
压缩前:
┌───────────────────────────────────────────┐
│ [系统消息] │
│ [msg_001] 用户: 帮我查天气 │
│ [msg_002] 助手: 好的 [tool_call] │
│ [msg_003] 工具: 天气数据 │
│ [msg_004] 助手: 今天晴天... │
│ [msg_005] 用户: 那明天呢 │
│ [msg_006] 助手: 好的 [tool_call] │
│ [msg_007] 工具: 明天数据 │
│ [msg_008] 助手: 明天多云... │
│ [msg_009] 用户: 谢谢 │
│ [msg_010] 助手: 不客气 │
└───────────────────────────────────────────┘
↓ 压缩 (LLM 生成摘要)
压缩后:
┌───────────────────────────────────────────┐
│ [系统消息] │
│ [compaction] 对话摘要: 用户询问了今明两天 │
│ 的北京天气。今天晴15-25°C,明天多云12-20°C│
│ [msg_009] 用户: 谢谢 │
│ [msg_010] 助手: 不客气 │
└───────────────────────────────────────────┘
压缩的本质是:让 LLM 把旧对话总结成一段摘要,然后用摘要替换旧消息。新消息保持原样,因为它们最可能是接下来对话的上下文。
4.3 Compaction Safeguard Extension
OpenClaw 没有完全信任 Pi SDK 的默认压缩行为。它注入了一个压缩保护扩展:
// src/agents/pi-extensions/compaction-safeguard.ts
// 为压缩提供额外的安全策略
// 1. 自适应 token 预算
// 根据历史大小动态调整压缩摘要的 token 预算
function adaptiveTokenBudget(totalTokens: number, contextWindow: number): number {
// 历史越长,摘要可以越长 (因为有更多内容要保留)
const ratio = totalTokens / contextWindow;
return Math.min(
contextWindow * 0.1, // 最多用 10% 的上下文窗口
2000 + ratio * 3000 // 基础 2000 + 按比例增长
);
}
// 2. 工具失败摘要
// 压缩时额外保留工具失败的信息 (避免重蹈覆辙)
function includeToolFailureSummaries(entries: SessionEntry[]): string {
const failures = entries.filter(e =>
e.role === 'tool' && e.content.startsWith('Error:')
);
if (!failures.length) return '';
return '\n[重要: 以下工具调用曾失败]\n' +
failures.map(f => `- ${f.name}: ${f.content}`).join('\n');
}
// 3. 文件操作摘要
// 保留关键文件变更记录
function includeFileOperationSummaries(entries: SessionEntry[]): string {
const fileOps = entries.filter(e =>
e.toolName === 'write' || e.toolName === 'edit'
);
if (!fileOps.length) return '';
return '\n[文件变更记录]\n' +
fileOps.map(f => `- ${f.toolName}: ${f.params.path}`).join('\n');
}
这个设计很有洞察力:普通的压缩会丢失"失败经验"。如果 Agent 之前尝试某个方法失败了,压缩后它可能会再次尝试同样的方法。Safeguard 扩展确保失败信息被保留。
4.4 reserveTokens 安全下限
// src/agents/pi-settings.ts
function ensurePiCompactionReserveTokens(config: Config): number {
const configured = config.agents?.defaults?.compaction?.reserveTokens ?? 16384;
const floor = config.agents?.defaults?.compaction?.reserveTokensFloor ?? 20000;
// 如果配置值低于下限,强制提升
return Math.max(configured, floor);
}
为什么需要安全下限?因为压缩不是瞬间完成的。如果 reserveTokens 太小,Agent 在压缩之前的最后一次运行就可能触发 overflow。留足 20000 tokens 的余量,确保有足够空间完成"记忆刷盘→压缩"的流程。
五、Pre-Compaction Memory Flush (压缩前记忆刷盘)
这是 OpenClaw 记忆系统最精妙的设计之一。
问题
当 auto-compaction 发生时,旧对话会被摘要替换。摘要中会丢失很多细节。如果 Agent 之前了解到的用户偏好、重要决策、待办事项没有被持久化到 MEMORY.md,它们就会永久丢失。
解决方案
在压缩之前,OpenClaw 注入一次静默的 Agent 运行,让 Agent 把重要信息写到文件里:
// 触发条件
// 当 contextTokens 超过 "软阈值" (softThresholdTokens)
// 但还没到 Pi SDK 的硬压缩阈值
if (sessionEntry.contextTokens > contextWindow - reserveTokens - softThresholdTokens) {
// 检查本压缩周期是否已经 flush 过
if (sessionEntry.memoryFlushCompactionCount !== sessionEntry.compactionCount) {
await runMemoryFlush(params);
sessionEntry.memoryFlushAt = Date.now();
sessionEntry.memoryFlushCompactionCount = sessionEntry.compactionCount;
}
}
Flush 的实现
async function runMemoryFlush(params: FlushParams): Promise<void> {
// 发送一条特殊的用户消息,要求 Agent 保存记忆
const flushPrompt = params.config.agents?.defaults?.compaction?.memoryFlush?.prompt
?? '[System] Context is nearing compaction threshold. Review recent conversation and write any important facts, decisions, preferences, or open loops to memory/YYYY-MM-DD.md and update MEMORY.md if needed. Reply with NO_REPLY when done.';
// 额外的系统提示
const flushSystemPrompt = params.config.agents?.defaults?.compaction?.memoryFlush?.systemPrompt
?? 'This is a silent housekeeping turn. Write important context to disk. Start your reply with NO_REPLY.';
// 运行一次完整的 Agent 运行 (但不投递给用户)
await runEmbeddedPiAgent({
...params,
prompt: flushPrompt,
appendSystemPrompt: flushSystemPrompt,
onBlockReply: (payload) => {
// 以 NO_REPLY 开头 → 不发送给用户
if (payload.text.startsWith('NO_REPLY')) return;
},
});
}
时序图
contextTokens 增长
│
▼
contextWindow - reserveTokens - softThreshold (默认: 上限前 4000 tokens)
│
├── 触发 Memory Flush!
│ ├── Agent 静默运行
│ ├── Agent 用 write 工具更新 memory/2026-03-10.md
│ ├── Agent 用 edit 工具更新 MEMORY.md
│ ├── Agent 回复 NO_REPLY
│ └── 用户完全不知道发生了这件事
│
▼
contextWindow - reserveTokens (Pi SDK 硬压缩阈值)
│
├── 触发 Auto-Compaction!
│ ├── 旧对话被 LLM 摘要替换
│ ├── 但重要信息已经在文件里了
│ └── 下次 Agent 启动时 read MEMORY.md 恢复记忆
│
▼
contextWindow (溢出)
└── 不会到这里,因为已经压缩过了
这个设计解决了 AI Agent 长期运行的核心难题:如何在无限对话中保持重要信息不丢失。
六、Cache-TTL Context Pruning (缓存感知上下文裁剪)
这是 OpenClaw 在 token 成本优化上的另一个巧妙设计。
问题
LLM provider 的 prompt caching 有 TTL(通常是 1 小时)。如果 session 超过 1 小时没有活动,缓存过期了。下一次请求时,整个 prompt 需要重新缓存(付 cacheWrite 费用)。
但更糟的是:旧的 tool_result 已经没用了(比如 1 小时前查的天气),但它们仍然占据 context,导致:
- 缓存命中失败,全部重新缓存
- 旧的 tool_result 占据空间,可用 token 变少
解决方案
当 session 空闲超过 cache TTL 后,主动裁剪旧的 tool_result,然后重新缓存精简后的上下文:
// src/agents/pi-extensions/context-pruning.ts
function pruneExpiredToolResults(params: PruneParams): PrunedResult {
const now = Date.now();
const ttlMs = parseTTL(params.config.contextPruning.ttl); // e.g., '1h'
const history = params.sessionManager.getHistory();
const pruned: SessionEntry[] = [];
let savedTokens = 0;
for (const entry of history) {
// 裁剪超过 TTL 的 tool_result
if (entry.role === 'tool' && isToolPrunable(entry)) {
const age = now - entry.timestamp;
if (age > ttlMs) {
savedTokens += estimateTokens(entry.content);
// 替换为简短摘要而不是完全删除
pruned.push({
...entry,
content: `[tool result pruned: ${entry.name}, ${Math.round(age / 60000)}min ago]`,
});
continue;
}
}
pruned.push(entry);
}
return { pruned, savedTokens };
}
Heartbeat 保热
配合 heartbeat,可以让缓存永远不过期:
# 心跳间隔略小于 cache TTL
agents:
defaults:
heartbeat:
every: "55m" # cache TTL 是 1h,55m 发一次心跳保持缓存活跃
这样做的效果:
- 正常活跃 session:缓存一直 warm,cacheRead 费用
- 空闲超过 TTL 的 session:裁剪旧数据后重新缓存,减少后续 cacheWrite
- 长期空闲 session:持续裁剪,保持 context 精简
七、Session Reset 策略
7.1 Daily Reset
// 默认每天凌晨 4:00 (Gateway 主机本地时间)
function checkDailyReset(entry: SessionEntry, config: Config): boolean {
const resetHour = config.session?.reset?.dailyHour ?? 4;
const lastDate = new Date(entry.updatedAt);
const now = new Date();
// 最后活跃日期 < 今天 AND 当前小时 >= resetHour
return (
lastDate.toDateString() !== now.toDateString() &&
now.getHours() >= resetHour
);
}
Daily Reset 的设计意图是每天一个全新的对话上下文。这避免了 context 无限膨胀,也让 Agent 有一个"清新的开始"——同时仍然可以通过 MEMORY.md 恢复重要信息。
7.2 Idle Reset
function checkIdleReset(entry: SessionEntry, config: Config): boolean {
const idleMinutes = config.session?.reset?.idleMinutes;
if (!idleMinutes) return false;
const elapsed = Date.now() - entry.updatedAt;
return elapsed > idleMinutes * 60 * 1000;
}
7.3 重置后的连续性
重置只创建新 session,不会删除旧数据:
sess_20260310_abc (昨天的 transcript, 保留)
sess_20260311_def (今天的 transcript, 新建)
旧 transcript 仍然在磁盘上,只是不再作为默认上下文加载。Agent 可以通过 /context 命令查看,或者通过 memory 工具回顾。
八、NO_REPLY 静默机制
OpenClaw 有一个特殊的约定:如果 Agent 的回复以 NO_REPLY 开头,这条消息不会投递给用户。
这在多个场景中使用:
- Memory Flush — 压缩前刷盘,用户不应看到
- Heartbeat — 心跳检查的输出,不需要通知用户
- Cron 任务 — 后台定时任务的内部处理
- Session Spawn — 子 agent 的内部运行
// 投递层检查
function shouldDeliverToUser(text: string): boolean {
return !text.trim().startsWith('NO_REPLY');
}
// 流式阶段也做了优化: 如果第一个 chunk 是 NO_REPLY,
// 不发送 typing indicator,避免用户看到"正在输入"又没消息
九、记忆系统的数据流全景
┌─────────────────────────────────────────────────────────────┐
│ 用户发消息 │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Session 初始化 │
│ ├── 读取 sessions.json → 找到 sessionKey │
│ ├── 检查 daily/idle reset → 可能创建新 session │
│ ├── 打开 .jsonl transcript → 恢复对话树 │
│ ├── 读取 MEMORY.md (仅主会话) │
│ └── 注入 workspace 文件到 system prompt │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Agent Loop 执行 │
│ ├── LLM 根据历史 + 新消息做出决策 │
│ ├── 可能调用 read/edit/write 工具 │
│ │ └── write memory/2026-03-10.md → 记录事件 │
│ │ └── edit MEMORY.md → 更新长期记忆 │
│ └── 回复用户 │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 持久化 │
│ ├── SessionManager.append() → 追加到 .jsonl │
│ ├── 更新 sessions.json (tokens, timestamp) │
│ └── 检查 compaction 条件 │
│ │ │
│ ├── contextTokens > 软阈值? │
│ │ └── 触发 Memory Flush! │
│ │ ├── Agent 静默运行 │
│ │ ├── 重要信息 → memory/*.md + MEMORY.md │
│ │ └── 回复 NO_REPLY │
│ │ │
│ └── contextTokens > 硬阈值? │
│ └── 触发 Auto-Compaction! │
│ ├── LLM 生成对话摘要 │
│ ├── 旧消息替换为摘要 │
│ ├── Safeguard: 保留失败记录 │
│ └── 新消息保持原样 │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 空闲期 │
│ ├── Cache TTL 过期? → Context Pruning (裁剪旧 tool_result) │
│ ├── Heartbeat? → 保持缓存 warm (可选) │
│ └── Daily Reset? → 下次消息创建新 session │
└─────────────────────────────────────────────────────────────┘
十、设计亮点总结
- 三层记忆,各司其职 — 短期(jsonl) + 日记(daily md) + 长期(MEMORY.md),类似人类的记忆系统
- 树结构 transcript — 支持 fork/分支,不是简单的追加日志
- Pre-Compaction Memory Flush — 压缩前强制刷盘,防止重要信息丢失。这是整个系统最有创意的设计
- Compaction Safeguard — 压缩时保留失败记录,防止 Agent 重蹈覆辙
- Cache-TTL Pruning — 主动裁剪过期工具结果,配合 heartbeat 保热,优化 token 成本
- NO_REPLY 静默机制 — 后台任务不干扰用户,一个约定解决了多种场景
- 惰性 Session Reset — 不主动重置,下条消息时检查,简化状态管理
- 自动维护 — sessions.json 自动清理过期条目和磁盘预算控制
系列完结
三篇文章覆盖了 OpenClaw 最核心的三条代码路径:
| 篇章 | 核心问题 | 关键模块 |
|---|---|---|
| 规划篇 | 消息如何变成一次 Agent 运行 | 路由、Session 初始化、System Prompt 构建、工具准备 |
| 执行篇 | Agent Loop 如何调用 LLM 和工具 | 流式调用、工具执行、分块响应、错误恢复 |
| 记忆篇 | 如何持久化和管理长期记忆 | Transcript、Compaction、Memory Flush、Context Pruning |
OpenClaw 的架构有几个贯穿始终的设计哲学:
- 文件即状态 — 用文件系统做持久化,简单、透明、可 debug
- 渐进式复杂度 — 简单场景简单处理(斜杠命令快速通道),复杂场景才走完整链路
- 防护层叠 — 多层安全机制(Tool Policy、AbortSignal、Compaction Safeguard)
- 成本意识 — Cache-TTL Pruning、Heartbeat 保热、History Limiting 都是为了控制 token 成本
希望这个系列对你理解 OpenClaw 的架构有帮助。如果你想进一步深入某个模块,或者想用 Spring Boot 实现其中某个子系统,随时聊。
更多推荐

所有评论(0)