源码仓库: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.md
  • SOUL.md
  • AGENTS.md
  • TOOLS.md
  • IDENTITY.md
  • USER.md
  • HEARTBEAT.md
  • BOOTSTRAP.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" 模式会跳过:SkillsMemory RecallDocsUser IdentityReply TagsMessagingVoiceSilent RepliesHeartbeatsSelf-UpdateModel 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 Hooksrc/hooks/bundled/session-memory/HOOK.md

总结

组件生命周期注入方式
Session History单会话运行时从 .jsonl 加载,注入 messages
MEMORY.md 等 Bootstrap 文件跨会话构建时注入到 System Prompt
memory_search 结果按需运行时作为 tool_result 注入

OpenClaw 通过分层存储 + 按需注入的架构,既保证了对话的连续性,又实现了跨会话知识的持久化。


本文基于 OpenClaw 源码分析整理

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐