乐于分享
好东西不私藏

OpenClaw 上下文处理深度解析:从消息接收到大模型输出

OpenClaw 上下文处理深度解析:从消息接收到大模型输出

本文基于 OpenClaw 源码分析,探讨一个消息从进入系统到生成回复的完整上下文生命周期。


一、整体架构:上下文处理的五个阶段

OpenClaw 的上下文处理并非一个单一的函数,而是贯穿整个消息生命周期的协作流水线。整体可分为五个阶段:

消息入口 → 预检过滤 → 上下文组装 → 模型调用 → 上下文维护

每个阶段职责清晰、边界明确,通过 ctxPayload 这个核心数据结构串联。


二、阶段一:消息接收与预检过滤

2.1 入口:preflightDiscordMessage

以 Discord 频道为例(message-handler-DbRk4Lwu.js),消息通过 WebSocket 事件进入后,首先经过 preflightDiscordMessage 函数进行预检。这不是一个简单的"有没有消息"的判断,而是一套完整的安全与路由决策引擎。

// 源码位置: message-handler-DbRk4Lwu.js
async function preflightDiscordMessage(params) {
  // 1. 认证与去重
  if (isProcessAborted(params.abortSignal)) return null;
  if (dedupeKey && recentInboundMessages.check(dedupeKey)) return null;
  // 2. 机器人过滤
  if (author.bot && allowBotsMode = "off" && !sender.isPluralKit) return null;
  // 3. 消息类型过滤(系统事件、斜杠命令等)
  if (isGuildMessage && (
    message.type = MessageType.ChatInputCommand ||
    message.type = MessageType.ContextMenuCommand
  )) return null;
  // 4. DM/频道权限策略
  if (dmPolicy = "disabled") return null;
  const dmAccess = await resolveDiscordDmCommandAccess({ ... });
  // 5. 提及检测(@机器人)
  const wasMentioned = matchesMentionWithExplicit({ text, mentionRegexes, ... });
  // 6. 成员白名单
  const { hasAccessRestrictions, memberAllowed } = resolveDiscordMemberAccessState({ ... });
  if (isGuildMessage && hasAccessRestrictions && !memberAllowed) return null;
  // 7. 其他频道消息忽略
  if (ignoreOtherMentions && hasUserOrRoleMention && !wasMentioned) return null;
  // 8. 系统事件注入
  const systemText = resolveDiscordSystemEvent(message, systemLocation);
  if (systemText) {
    enqueueSystemEvent(systemText, { sessionKey: effectiveRoute.sessionKey, ... });
    return null;
  }
}

2.2 频道历史管理

预检通过后,消息会进入频道历史记录(reply-history-CYr7j6cE.js):

// 构建历史上下文标记
const HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
function buildHistoryContext(params) {
  const { historyText, currentMessage } = params;
  if (!historyText.trim()) return currentMessage;
  return [
    HISTORY_CONTEXT_MARKER,
    historyText,
    "",
    CURRENT_MESSAGE_MARKER,
    currentMessage
  ].join(lineBreak);
}
// 历史记录追加(带 LRU 驱逐)
const MAX_HISTORY_KEYS = 1e3;
function appendHistoryEntry(params) {
  const history = historyMap.get(historyKey) ?? [];
  history.push(entry);
  while (history.length > params.limit) history.shift(); // 超过限制时移除最旧记录
  historyMap.set(historyKey, history);
  evictOldHistoryKeys(historyMap);
}

关键设计点:频道历史以 Map 结构存储在内存中,而非每次都从磁盘读取。通过 MAX_HISTORY_KEYS = 1000 防止内存无限增长,通过 historyLimit(默认50条)控制每个频道的历史深度。


三、阶段二:上下文组装

3.1 核心结构 ctxPayload

通过 finalizeInboundContexttemplating-BpbUbFSs.js)构建最终传递给模型的上下文结构:

const ctxPayload = finalizeInboundContext({
  Body: combinedBody,              // 格式化后的消息内容
  BodyForAgent: baseText ?? text,   // 供 Agent 使用的原始文本
  InboundHistory: inboundHistory,    // 频道历史(已格式化)
  RawBody: baseText,                // 未处理的原始文本
  CommandBody: baseText,            // 命令体(用于 /command 解析)
  // 路由信息
  From: effectiveFrom,             // 消息来源标识
  To: effectiveTo,                 // 目标会话
  SessionKey: boundSessionKey ?? threadKeys.sessionKey,
  AccountId: route.accountId,
  // 发送者信息
  SenderName: senderName,
  SenderId: sender.id,
  SenderUsername: senderUsername,
  SenderTag: senderTag,
  // 群组/频道信息
  GroupSubject: groupSubject,       // 群组主题
  GroupChannel: groupChannel,       // 频道标签
  GroupSystemPrompt: isGuildMessage ? groupSystemPrompt : void 0,
  GroupSpace: isGuildMessage ? (guildInfo?.id ?? guildSlug) : void 0,
  // 安全上下文
  UntrustedContext: untrustedContext,
  OwnerAllowFrom: ownerAllowFrom,
  // 回复上下文
  ReplyToId: replyContext?.id,
  ReplyToBody: replyContext?.body,
  ReplyToSender: replyContext?.sender,
  // 线程信息
  MessageThreadId: threadChannel?.id,
  ThreadStarterBody: threadStarterBody,
  ThreadLabel: threadLabel,
  // 媒体信息
  ...mediaPayload,
  // 元数据
  Provider: "discord",
  Surface: "discord",
  WasMentioned: effectiveWasMentioned,
  Timestamp: resolveTimestampMs(message.timestamp),
  MessageSid: message.id,
});

3.2 安全防护:输入净化

这是 OpenClaw 上下文处理中最体现安全意识的部分(templating-BpbUbFSs.js):

const BRACKETED_SYSTEM_TAG_RE = /\[\s*(System\s*Message|System|Assistant|Internal)\s*\]/gi;
const LINE_SYSTEM_PREFIX_RE = /^(\s*)System:(?=\s|$)/gim;
function sanitizeInboundSystemTags(input) {
  return input
    .replace(BRACKETED_SYSTEM_TAG_RE, (_match, tag) => `(${tag})`)
    .replace(LINE_SYSTEM_PREFIX_RE, "$1System (untrusted):");
}

防护逻辑:用户可能尝试在消息中注入 [System Message] 或 System: 前缀来冒充系统指令。sanitizeInboundSystemTags 会将这些标记替换为不可执行的格式,防止 Prompt Injection 攻击。

3.3 会话键(SessionKey)的路由逻辑

会话键是 OpenClaw 多租户/多会话隔离的核心机制(sessions-uRDRs4f-.js):

function deriveSessionKey(scope, ctx) {
  if (scope === "global") return "global";
  const resolvedGroup = resolveGroupSessionKey(ctx);
  if (resolvedGroup) return resolvedGroup.key; // 群组会话: "discord:group:123456"
  return (ctx.From ? normalizeE164(ctx.From) : "") || "unknown"; // 私聊 → main
}
// 群组会话键示例:
// discord:group:123456
// telegram:channel:-1002381931352
// whatsapp:group:123456789@g.us

关键设计:私聊消息全部归一化到 main 会话,群组/频道消息则保持隔离。这意味着 AI 在私聊中拥有完整的多轮上下文,在群组中则按频道隔离。


四、阶段三:上下文向 Prompt 的转化

4.1 上下文引擎注册机制

OpenClaw 实现了可插拔的上下文引擎架构(pi-embedded-BaSvmUpW.js):

// 注册表设计
function registerContextEngineForOwner(id, factory, owner, opts) {
  // id: "legacy" | "custom-plugin-slot"
  // owner: "core" | "public-sdk"
  // 支持第三方通过 api.registerContextEngine() 插入自定义引擎
}
// 内置 Legacy 引擎(默认)
var LegacyContextEngine = class {
  async ingest(_params) { return { ingested: false }; }
  async assemble(params) { return { messages: params.messages, estimatedTokens: 0 }; }
  async compact(params) { return await delegateCompactionToRuntime(params); }
};

4.2 引导文件与工作区上下文

OpenClaw 在启动时会加载工作区中的引导文件,组成初始系统上下文(pi-embedded-BaSvmUpW.js):

// 加载 bootstrap 文件
const bootstrapFiles = filterBootstrapFilesForSession({
  sessionKey, workspaceDir, bootstrapContextMode
});
// 典型文件: SOUL.md, AGENTS.md, USER.md, HEARTBEAT.md
// 构建初始上下文
async function loadWorkspaceBootstrapFiles(sessionKey, workspaceDir) {
  // 1. 读取 workspaceDir 下的 .md 文件
  // 2. 按文件名排序拼接
  // 3. 作为 system prompt 前缀注入
}

这正是 AGENTS.md 中描述的行为:每次会话启动时,读取 SOUL.md → USER.md → memory/ 当日文件 → MEMORY.md,这些文件共同构成了 AI 的"人格"和"记忆"。

4.3 Skills 系统:动态工具上下文注入

Skills 是 OpenClaw 的可扩展工具集,通过 SKILL.md 文件描述(skills-Xrdxpo0d.js):

// Skill 解析流程
function loadSkillsFromDir(skillDir) {
  const entries = [];
  const files = listMarkdownFilesRecursive(skillDir); // 递归查找 .md 文件
  for (const filePath of files) {
    const frontmatter = parseFrontmatterBlock(raw);
    const promptTemplate = stripFrontmatter(raw);
    // frontmatter.name → 技能名称
    // frontmatter.description → 技能描述(用于 agent 理解何时调用)
    // promptTemplate → 技能的完整指令文本
  }
}
// Skill 过滤与选择
function filterSkillEntries(entries, config, skillFilter, eligibility) {
  let filtered = entries.filter(entry => shouldIncludeSkill({ entry, config, eligibility }));
  if (skillFilter !== void 0) {
    const normalized = normalizeSkillFilter(skillFilter) ?? [];
    filtered = normalized.length > 0
      ? filtered.filter(entry => normalized.includes(entry.skill.name))
      : [];
  }
  return filtered;
}

关键设计:Skill 的路径在注入 Prompt 前会被压缩为 ~/... 格式compactSkillPaths),节省大量 token:

// 原始: /Users/alice/.bun/node/lib/skills/github/SKILL.md
// 压缩后: ~/.bun/node/lib/skills/github/SKILL.md
function compactSkillPaths(skills) {
  const home = os.homedir();
  return skills.map(=> ({
    ...s,
    filePath: s.filePath.startsWith(prefix) ? "~/" + s.filePath.slice(prefix.length) : s.filePath
  }));
}

五、阶段四:会话存储与历史持久化

5.1 双层存储架构

OpenClaw 实现了内存缓存 + 磁盘持久化的双层会话存储(sessions-uRDRs4f-.js):

// 内存层: SessionManager (来自 pi-coding-agent 包)
// 磁盘层: session store (JSON 文件)
const CURRENT_SESSION_VERSION = "...";
async function loadSessionStore(storePath) {
  const raw = await readFile(storePath, "utf-8");
  return JSON.parse(raw);
}
async function saveSessionStore(storePath, store) {
  await writeJsonAtomic(storePath, store);
}

5.2 会话元数据管理

function mergeSessionEntry(existing, patch) {
  return mergeSessionEntryWithPolicy(existing, patch);
}
function mergeSessionEntryWithPolicy(existing, patch, options) {
  const sessionId = patch.sessionId ?? existing?.sessionId ?? crypto.randomUUID();
  const updatedAt = resolveMergedUpdatedAt(existing, patch, options);
  if (!existing) {
    return normalizeSessionRuntimeModelFields({ ...patch, sessionId, updatedAt });
  }
  return normalizeSessionRuntimeModelFields({
    ...existing,
    ...patch,
    sessionId,
    updatedAt
  });
}

5.3 磁盘预算控制

会话文件会不断增长,OpenClaw 实现了磁盘预算控制:

// 预算配置
const maintenance = {
  maxDiskBytes: 100 * 1024 * 1024,  // 100MB 上限
  highWaterBytes: 80 * 1024 * 1024,  // 80MB 时开始清理
  keepArchivedTranscripts: true,
  removeEmptySessionFiles: true,
};
// 按修改时间排序,删除最旧的会话文件直到低于预算
async function enforceSessionDiskBudget(params) {
  const files = await readSessionsDirFiles(sessionsDir);
  files.sort((a, b) => a.mtimeMs - b.mtimeMs); // 最旧的先删
  while (total > highWaterBytes && files.length > 0) {
    const removed = await removeFileForBudget({ filePath: files.shift().path });
    total -= removed;
  }
}

六、阶段五:上下文压缩与溢出处理

6.1 溢出检测

当模型上下文窗口接近上限时,OpenClaw 会触发压缩(agent-runner.runtime-i_gf132J.js):

async function runAgentTurnWithFallback(params) {
  while (true) {
    try {
      const result = await runWithModelFallback({
        ...resolveModelFallbackOptions(params.followupRun.run),
        runId,
        run: (provider, model, runOptions) => {
          return runEmbeddedPiAgent({
            ...embeddedContext,
            // 模型调用
            onCompactionStart: async () => {
              // 通知用户正在压缩
              await params.opts.onCompactionStart?.();
            },
            onCompactionEnd: async () => {
              // 压缩完成,重新计数
              attemptCompactionCount += 1;
              await params.opts.onCompactionEnd?.();
            }
          });
        }
      });
    } catch (error) {
      if (isContextOverflowError(error)) {
        // 触发上下文压缩后重试
        await compactEmbeddedPiSessionDirect({ sessionFile, tokenBudget, force: true });
        continue; // 重试
      }
      throw error;
    }
  }
}

6.2 委托式压缩

自定义上下文引擎可以将压缩委托给内置运行时:

async function delegateCompactionToRuntime(params) {
  const { compactEmbeddedPiSessionDirect } = await import("./compact.runtime-C_quO5Zm.js");
  const result = await compactEmbeddedPiSessionDirect({
    sessionId: params.sessionId,
    sessionFile: params.sessionFile,
    tokenBudget: params.tokenBudget,
    force: params.force,
    customInstructions: params.customInstructions,
    workspaceDir: params.workspaceDir
  });
  return { compacted: result.compacted, ... };
}

七、完整的上下文流图

Discord/Telegram/Signal 等消息源         │         ▼  ┌──────────────────┐  │ preflightMessage │  ← 预检过滤(权限/白名单/去重/系统事件)  └────────┬─────────┘           │ ctxPayload           ▼  ┌──────────────────────────┐  │ finalizeInboundContext   │  ← 净化用户输入、构建标准上下文结构  └────────┬─────────────────┘           │           ▼  ┌──────────────────────────┐  │ recordInboundSession     │  ← 写入会话存储、更新元数据  └────────┬─────────────────┘           │           ▼  ┌──────────────────────────┐  │ buildPendingHistoryContext│  ← 追加频道历史  └────────┬─────────────────┘           │           ▼  ┌──────────────────────────┐  │ dispatchInboundMessage   │  ← 触发 Agent 执行  └────────┬─────────────────┘           │           ▼  ┌──────────────────────────────────────────────┐  │ runEmbeddedPiAgent                            │  │  ├─ 加载引导文件 (SOUL.md / AGENTS.md / USER.md) │  │  ├─ 注入 Skills (SKILL.md 系统)                  │  │  ├─ 注入会话历史 (SessionManager)                │  │  └─ 构建最终 messages[] 数组                    │  └────────┬─────────────────────────────────────┘           │           ▼        模型 API 调用           │     ┌─────┴─────┐     │ 成功      │ 溢出错误     │           │     ▼           ▼  回复投递   触发压缩 (compact)           │           ▼        重试调用模型

八、关键设计哲学总结

设计点
实现方式
价值
安全净化sanitizeInboundSystemTags
 过滤 [System] 等注入
防止 Prompt Injection
会话隔离
群组按频道隔离,私聊归一到 main
多租户安全
历史热存储
频道历史存内存 + 磁盘持久化
低延迟访问
上下文压缩
可插拔引擎 + 委托运行时
扩展性强
Skills 动态加载
SKILL.md 按需解析 + 路径压缩
token 优化
磁盘预算
LRU 删除 + 高水位触发
自动健康管理
多渠道统一抽象ctxPayload
 标准化所有渠道
架构一致

源码版本: OpenClaw (Node.js), 2026-03-26 基于 dist/ 目录分析