OpenClaw 上下文构建与存储机制源码解读
源码仓库:https://github.com/openclaw/openclaw
概述
本文深入分析 OpenClaw 中**上下文(Context)**的构建机制,涵盖:
- Session(会话)存储:消息历史的持久化与加载
- Memory(记忆)系统:跨会话知识的存储与检索
- 上下文注入:两部分数据如何拼接到 LLM 请求中
一、Session 存储机制
1.1 存储路径
~/.openclaw/
├── agents/
│ ├── main/
│ │ └── sessions/
│ │ ├── sessions.json # 会话元数据索引
│ │ └── <sessionId>.jsonl # 完整对话记录(JSONL格式)
│ └── {agentId}/
│ └── sessions/
│ ├── sessions.json
│ └── *.jsonl
源码路径:
src/config/sessions/paths.ts- 路径解析src/config/sessions/store.ts- 存储读写
1.2 数据格式
sessions.json - 元数据索引
{
"agent:main:telegram:direct:123456789": {
"sessionId": "abc-123-def-456",
"sessionFile": "abc-123-def-456.jsonl",
"model": "claude-3-5-sonnet",
"channel": "telegram",
"spawnDepth": 0,
"deliveryContext": {
"channel": "telegram",
"to": "123456789",
"accountId": "default"
},
"origin": {
"label": "John",
"provider": "telegram",
"from": "123456789"
}
}
}
*.jsonl - 完整对话记录
{"type":"session","version":1,"id":"abc-123","timestamp":"2024-03-26T10:00:00.000Z","cwd":"/path"}
{"type":"message","message":{"role":"user","content":"Hello!"}}
{"type":"message","message":{"role":"assistant","content":[{"type":"text","text":"Hi there!"}],"api":"openai-responses","provider":"openai","model":"claude-3-5-sonnet","usage":{"input":15,"output":20}}}
{"type":"message","message":{"role":"toolResult","toolName":"read","content":"file content"}}
{"type":"compaction","timestamp":"2024-03-26T12:00:00.000Z"}
源码路径:
src/config/sessions/types.ts- SessionEntry 类型定义src/config/sessions/transcript.ts- JSONL 读写
1.3 Session 消息加载流程
sessions/*.jsonl (磁盘)
↓ SessionManager.open()
createAgentSession()
↓ 传入 sessionManager
activeSession.messages (内存)
↓ sanitizeSessionHistory() + limitHistoryTurns()
activeSession.agent.replaceMessages(limited)
↓
LLM 请求 (systemPrompt + messages)
关键代码位置:
// 1. SessionManager 创建
// src/agents/pi-embedded-runner/run/attempt.ts:1743
sessionManager = guardSessionManager(SessionManager.open(params.sessionFile), {...});
// 2. Agent Session 创建(含历史消息加载)
// src/agents/pi-embedded-runner/run/attempt.ts:1836-1848
({ session } = await createAgentSession({
sessionManager, // ← 传入 SessionManager,消息从 .jsonl 自动加载
...
}));
// 3. 历史消息清理与限制
// src/agents/pi-embedded-runner/run/attempt.ts:2076-2107
const prior = await sanitizeSessionHistory({ messages: activeSession.messages, ... });
const truncated = limitHistoryTurns(validated, getHistoryLimitFromSessionKey(...));
activeSession.agent.replaceMessages(limited);
// 4. 历史限制逻辑:限制为最后 N 个用户轮次
// src/agents/pi-embedded-runner/history.ts:15-36
export function limitHistoryTurns(messages, limit) { ... }
源码路径:
src/agents/pi-embedded-runner/run/attempt.ts- 主执行流程src/agents/pi-embedded-runner/history.ts- 历史限制逻辑
二、Memory 存储机制
2.1 存储位置
Memory 文件位于**工作区(Workspace)**中,而非 Session 目录:
<workspace>/ # 默认 ~/.openclaw/workspace
├── MEMORY.md # 长期记忆
├── memory.md # 备用记忆(当 MEMORY.md 不存在时)
├── memory/
│ ├── YYYY-MM-DD.md # 每日日志
│ └── *.md # 其他记忆文件
└── SOUL.md # 人格定义
2.2 Memory 文件类型
| 文件 | 用途 | 生命周期 |
|---|---|---|
MEMORY.md | 长期知识、偏好、决策 | 持久化 |
memory/YYYY-MM-DD.md | 每日工作记录 | 追加写入 |
SOUL.md | 角色人格定义 | 持久化 |
2.3 Memory 工具
OpenClaw 提供两个 Agent 工具:
| 工具 | 功能 |
|---|---|
memory_search | 语义搜索记忆文件 |
memory_get | 读取指定记忆文件 |
源码路径:
src/agents/tools/memory-tool.ts- Memory 工具实现src/memory/- Memory 后端管理
2.4 自动 Memory Flush
在会话接近上下文上限(compaction)前,OpenClaw 会触发静默提醒写入 Memory:
// 触发条件:当 contextTokens > contextWindow - reserveTokensFloor - softThresholdTokens
// 配置示例
{
agents: {
defaults: {
compaction: {
memoryFlush: {
enabled: true,
softThresholdTokens: 4000,
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md"
}
}
}
}
}
三、上下文构建与注入
3.1 上下文的两部分
| 组件 | 传递方式 | 内容 |
|---|---|---|
| System Prompt | 字符串(静态) | 工具列表、规则、知识、使用指南 |
| Session Messages | 消息数组(动态) | 对话历史 + 当前消息 |
LLM Request = {
system: "你是个人助手...", // System Prompt
messages: [ // Session 历史 + 当前消息
{ role: "user", content: "..." },
{ role: "assistant", content: "..." },
{ role: "user", content: "新问题" }
]
}
3.2 System Prompt 构建
源码路径: src/agents/system-prompt.ts
System Prompt 由 buildAgentSystemPrompt() 函数构建,使用字符串数组累积的方式拼接:
// src/agents/system-prompt.ts:688
return lines.filter(Boolean).join("\n"); // 最终拼接
Section 拼接顺序:
const lines = [
"You are a personal assistant running inside OpenClaw.", // 身份
"",
"## Tooling", // 工具列表
"## Safety", // 安全规则
"## Skills", // 技能
"## Memory Recall", // Memory 使用指南(提示调用 memory_search)
"## Workspace", // 工作目录
"# Project Context", // Bootstrap 文件注入位置
...contextFiles, // MEMORY.md, SOUL.md 等内容
"## Silent Replies",
"## Heartbeats",
"## Runtime", // 运行时信息
];
3.3 Bootstrap 文件注入
源码路径: src/agents/system-prompt.ts:616-649
const contextFiles = params.contextFiles ?? [];
const bootstrapTruncationWarningLines = (params.bootstrapTruncationWarningLines ?? []).filter(
(line) => line.trim().length > 0,
);
const validContextFiles = contextFiles.filter(
(file) => typeof file.path === "string" && file.path.trim().length > 0,
);
if (validContextFiles.length > 0 || bootstrapTruncationWarningLines.length > 0) {
lines.push("# Project Context", "");
if (validContextFiles.length > 0) {
const hasSoulFile = validContextFiles.some((file) => {
const normalizedPath = file.path.trim().replace(/\\/g, "/");
const baseName = normalizedPath.split("/").pop() ?? normalizedPath;
return baseName.toLowerCase() === "soul.md";
});
// 以下项目上下文文件已加载
lines.push("The following project context files have been loaded:");
if (hasSoulFile) {
// 如果存在 SOUL.md,体现其人格和语调。避免生硬、泛泛的回复;遵循其指导,除非更高优先级的指令覆盖它
lines.push(
"If SOUL.md is present, embody its persona and tone. " +
"Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.",
);
}
lines.push("");
}
// 截断警告(如果有)
if (bootstrapTruncationWarningLines.length > 0) {
lines.push("⚠ Bootstrap truncation warning:");
for (const warningLine of bootstrapTruncationWarningLines) {
lines.push(`- ${warningLine}`);
}
lines.push("");
}
// 注入文件内容
for (const file of validContextFiles) {
lines.push(`## ${file.path}`, "", file.content, "");
}
}
生成内容示例:
# Project Context
The following project context files have been loaded:
以下项目上下文文件已加载
If SOUL.md is present, embody its persona and tone.
识别的 Bootstrap 文件:
MEMORY.md/memory.mdSOUL.mdAGENTS.mdTOOLS.mdIDENTITY.mdUSER.mdHEARTBEAT.mdBOOTSTRAP.md
源码路径: src/agents/workspace.ts:481-541
3.4 Memory Recall Section
源码路径: src/agents/system-prompt.ts:38-64
function buildMemorySection(params) {
if (params.isMinimal) {
return [];
}
if (!params.availableTools.has("memory_search") && !params.availableTools.has("memory_get")) {
return [];
}
const lines = [
"## Memory Recall",
// 中文:在回答关于之前工作、决策等问题前,先运行 memory_search
"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.",
// 中文:引用时包含 Source: <path#line> 帮助用户验证
"Citations: include Source: <path#line> when it helps the user verify memory snippets.",
];
if (params.citationsMode === "off") {
// 中文:引用已禁用
lines.push(
"Citations are disabled: do not mention file paths or line numbers in replies unless the user explicitly asks.",
);
}
lines.push("");
return lines;
}
生成的实际内容:
## 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.
Citations: include Source: <path#line> when it helps the user verify memory snippets.
3.5 其他常用 Section
Silent Replies(静默回复)
源码路径: src/agents/system-prompt.ts:651-667
if (!isMinimal) {
lines.push(
"## Silent Replies",
// 当无话可说时,只回复:SILENT_REPLY_TOKEN
`When you have nothing to say, respond with ONLY: ${SILENT_REPLY_TOKEN}`,
"",
"⚠️ Rules:",
// 它必须是完整的消息——不能有其他内容
"- It must be your ENTIRE message — nothing else",
// 永远不要附加在真实回复中
`- Never append it to an actual response (never include "${SILENT_REPLY_TOKEN}" in real replies)`,
// 永远不要用 markdown 或代码块包裹
"- Never wrap it in markdown or code blocks",
"",
`❌ Wrong: "Here's help... ${SILENT_REPLY_TOKEN}"`,
`❌ Wrong: "${SILENT_REPLY_TOKEN}"`,
`✅ Right: ${SILENT_REPLY_TOKEN}`,
"",
);
}
生成内容示例:
## Silent Replies
When you have nothing to say, respond with ONLY: NO_REPLY
⚠️ Rules:
- It must be your ENTIRE message — nothing else
- Never append it to an actual response
- Never wrap it in markdown or code blocks
❌ Wrong: "Here's help... NO_REPLY"
❌ Wrong: NO_REPLY
✅ Right: NO_REPLY
Heartbeats(心跳)
源码路径: src/agents/system-prompt.ts:669-680
if (!isMinimal) {
lines.push(
"## Heartbeats",
heartbeatPromptLine,
// 如果收到心跳轮询且无需处理,精确回复 HEARTBEAT_OK
"If you receive a heartbeat poll (a user message matching the heartbeat prompt above), " +
"and there is nothing that needs attention, reply exactly:",
"HEARTBEAT_OK",
// 开头的 HEARTBEAT_OK 视为心跳确认
'OpenClaw treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
// 如果有需要处理的事项,不要包含 HEARTBEAT_OK
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',
"",
);
}
3.6 Prompt Mode
System Prompt 支持三种模式:
| 模式 | 内容 | 用途 |
|---|---|---|
"full" | 所有 sections(默认) | 主 Agent |
"minimal" | 仅 Tooling, Workspace, Runtime | 子 Agent |
"none" | 仅基本身份行 | 极简模式 |
"minimal"模式会跳过:Skills、Memory Recall、Docs、User Identity、Reply Tags、Messaging、Voice、Silent Replies、Heartbeats、Self-Update、Model Aliases
四、Session 与 Memory 的关联
4.1 数据流向
sessions/*.jsonl (原始对话)
↓ compaction / /new / /reset
memory/YYYY-MM-DD.md (持久化笔记)
↓ 向量化索引
memory_search (语义检索)
↓
Agent 调用 memory_search 工具
↓
结果作为 tool_result 注入上下文
4.2 session-memory Hook
当执行 /new 或 /reset 时,session-memory hook 会自动保存会话上下文到 Memory:
源码路径: src/hooks/bundled/session-memory/HOOK.md
// Hook 工作流程
// 1. 找到上一个会话
// 2. 提取最后 N 条消息
// 3. 生成描述性文件名
// 4. 保存到 memory/YYYY-MM-DD-slug.md
4.3 Session 索引(实验性)
OpenClaw 支持将 Session 内容向量化以供 memory_search 检索:
// 配置
{
agents: {
defaults: {
memorySearch: {
experimental: { sessionMemory: true },
sources: ["memory", "sessions"] // 包含 sessions
}
}
}
}
五、完整上下文构建示例
┌─────────────────────────────────────────────────────────────────────┐
│ System Prompt (buildAgentSystemPrompt) │
├─────────────────────────────────────────────────────────────────────┤
│ You are a personal assistant... │
│ │
│ ## Tooling │
│ - read, write, edit, exec... │
│ │
│ ## Safety │
│ ... │
│ │
│ ## Memory Recall │
│ Before answering anything... run memory_search... │
│ │
│ # Project Context ← Bootstrap 文件注入 │
│ ## MEMORY.md │
│ - 用户偏好:喜欢简洁的回复 │
│ ## SOUL.md │
│ - 我是一个乐于助人的助手 │
├─────────────────────────────────────────────────────────────────────┤
│ Session Messages (from *.jsonl) │
├─────────────────────────────────────────────────────────────────────┤
│ { role: "user", content: "你好" } │
│ { role: "assistant", content: "你好!有什么可以帮你的?" } │
│ { role: "user", content: "帮我查一下昨天的会议记录" } │
│ { role: "assistant", content: "...", tool_calls: [...] } │
│ { role: "tool", content: "工具执行结果" } │
└─────────────────────────────────────────────────────────────────────┘
六、关键源码文件索引
| 功能 | 源码路径 |
|---|---|
| System Prompt 构建 | src/agents/system-prompt.ts |
| Session 存储 | src/config/sessions/store.ts |
| Session 路径 | src/config/sessions/paths.ts |
| Session 消息加载 | src/agents/pi-embedded-runner/run/attempt.ts |
| 历史限制 | src/agents/pi-embedded-runner/history.ts |
| JSONL 读写 | src/config/sessions/transcript.ts |
| Memory 工具 | src/agents/tools/memory-tool.ts |
| Bootstrap 文件加载 | src/agents/workspace.ts |
| Context Engine 接口 | src/context-engine/types.ts |
| Session-Memory Hook | src/hooks/bundled/session-memory/HOOK.md |
总结
| 组件 | 生命周期 | 注入方式 |
|---|---|---|
| Session History | 单会话 | 运行时从 .jsonl 加载,注入 messages |
| MEMORY.md 等 Bootstrap 文件 | 跨会话 | 构建时注入到 System Prompt |
| memory_search 结果 | 按需 | 运行时作为 tool_result 注入 |
OpenClaw 通过分层存储 + 按需注入的架构,既保证了对话的连续性,又实现了跨会话知识的持久化。
本文基于 OpenClaw 源码分析整理
更多推荐



所有评论(0)