Skip to content

第 11 章 System Prompt 与会话管理

11.1 System Prompt 的地位

System prompt 是 Pi 引擎"准备阶段"的最终产物——它把配置、用户身份、工作空间文件、技能列表、运行时信息全部组装成一段文本,交给 LLM 作为行为指南。system-prompt.ts(725 行)是项目中逻辑密度最高的文件之一。

11.2 组装结构

System prompt 分 section 依次构建,顺序如下(以实际组装输出为准):

┌─────────────────────────────────────────────────────────┐
│ Identity 行(非 section,首行)                           │
│   "You are [name], a personal AI assistant running      │
│    inside OpenClaw."                                    │
├─────────────────────────────────────────────────────────┤
│ ## Tooling                                              │
│   工具可用列表(名称 + 一行描述)                         │
│   由工具策略管道过滤后生成(详见第 12 章)                │
├─────────────────────────────────────────────────────────┤
│ ## Skills (mandatory)                                   │
│   <available_skills> XML 块                             │
│   (技能名称 + 描述摘要 + 文件路径,不含完整内容)        │
├─────────────────────────────────────────────────────────┤
│ ## Memory Recall                                        │
│   强制 recall 规则 + citation 格式                       │
├─────────────────────────────────────────────────────────┤
│ ## OpenClaw Self-Update(固定规则)                      │
│   self-update 仅在用户明确要求时才能执行                  │
├─────────────────────────────────────────────────────────┤
│ ## Silent Replies                                       │
│   NO_REPLY 使用规则                                      │
├─────────────────────────────────────────────────────────┤
│ ## Heartbeats                                           │
│   HEARTBEAT_OK 触发条件 + 心跳 prompt 文本               │
├─────────────────────────────────────────────────────────┤
│ ## Authorized Senders                                   │
│   owner 身份的 HMAC 哈希(12 位十六进制)                 │
├─────────────────────────────────────────────────────────┤
│ ## Current Date & Time(按需注入)                       │
│   时区 + 当前时间(session_status 未调用时才注入)        │
├─────────────────────────────────────────────────────────┤
│ ## Reply Tags                                           │
│   [[reply_to_current]] 用法规则                          │
├─────────────────────────────────────────────────────────┤
│ ## Messaging                                            │
│   消息工具规则 + 渠道能力说明                             │
├─────────────────────────────────────────────────────────┤
│ ## Group Chat Context(仅群组场景注入)                  │
│   群组名称 / 成员列表 / 群组规则等                        │
├─────────────────────────────────────────────────────────┤
│ ## Inbound Context(每次调用动态注入)                   │
│   channel / provider / chat_type 等可信元数据 JSON       │
├─────────────────────────────────────────────────────────┤
│ # Project Context                                       │
│   Bootstrap 文件内容(按预算顺序加载):                  │
│   AGENTS.md → SOUL.md → TOOLS.md → 其他 workspace 文件  │
├─────────────────────────────────────────────────────────┤
│ ## Runtime(末尾固定)                                   │
│   agent / host / repo / os / node / model /             │
│   shell / channel / capabilities / thinking             │
└─────────────────────────────────────────────────────────┘

几个注意点:

  • Tooling 在 Skills 之前:工具声明是行为基础,Skills 是在工具之上构建的按需加载层
  • Group Chat Context 和 Inbound Context 是条件性的:DM 场景下 Group Chat Context 为空,Inbound Context 每次调用都动态注入
  • Bootstrap 文件在最后、Runtime 之前:这是最大的 section,放在接近末尾确保固定的规则类 section 先被 LLM 看到(prompt caching 角度,固定内容靠前有助于提高缓存命中率)
  • Runtime 始终在最末尾:因为它包含当前时间等动态信息,每次都会变,放在末尾让 prompt caching 只在末尾失效,而不影响前面更大的固定 section

11.2.1 完整组装示例

下面是一个真实安装场景下 LLM 实际收到的 system prompt(已轻度脱敏,格式与真实输出一致)。

用户环境:Mac Studio,连接了 Telegram,workspace 有三个 Skill(weather / discord / coding-agent),agent 名字叫 "Clawd"。

You are Clawd, a personal AI assistant running inside OpenClaw.
## Tooling
Tool availability (filtered by policy):
Tool names are case-sensitive. Call tools exactly as listed.
- read: Read file contents
- write: Create or overwrite files
- edit: Make precise edits to files
- exec: Run shell commands (pty available for TTY-required CLIs)
- process: Manage background exec sessions
- web_search: Search the web (Brave API)
- web_fetch: Fetch and extract readable content from a URL
- browser: Control web browser
- memory_search: Semantically search MEMORY.md + memory/*.md before answering questions about prior work...
- memory_get: Safe snippet read from MEMORY.md or memory/*.md with optional from/lines
- cron: Manage cron jobs and wake events
- message: Send messages and channel actions
- tts: Convert text to speech
- image: Analyze an image with the configured image model

## Skills (mandatory)
Before replying: scan <available_skills> <description> entries.
- If exactly one skill clearly applies: read its SKILL.md at <location> with `read`, then follow it.
- If multiple could apply: choose the most specific one, then read/follow it.
- If none clearly apply: do not read any SKILL.md.
Constraints: never read more than one skill up front; only read after selecting.

<available_skills>
  <skill>
    <name>weather</name>
    <description>Get current weather and forecasts via wttr.in or Open-Meteo. Use when: user asks about weather, temperature, or forecasts for any location. NOT for: historical weather data, severe weather alerts, or detailed meteorological analysis. No API key needed.</description>
    <location>/Users/claw/.openclaw/workspace/skills/weather/SKILL.md</location>
  </skill>
  <skill>
    <name>discord</name>
    <description>Discord ops via the message tool (channel=discord).</description>
    <location>/opt/homebrew/lib/node_modules/openclaw/skills/discord/SKILL.md</location>
  </skill>
  <skill>
    <name>coding-agent</name>
    <description>Delegate coding tasks to Codex, Claude Code, or Pi agents via background process. Use when: (1) building/creating new features or apps, (2) reviewing PRs (spawn in temp dir), (3) refactoring large codebases...</description>
    <location>/opt/homebrew/lib/node_modules/openclaw/skills/coding-agent/SKILL.md</location>
  </skill>
</available_skills>

## 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.

## OpenClaw Self-Update
Get Updates (self-update) is ONLY allowed when the user explicitly asks for it.
...

## 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 include "NO_REPLY" in real replies)
✅ Right: NO_REPLY

## Heartbeats
Heartbeat prompt: Read HEARTBEAT.md if it exists (workspace context). Follow it strictly...
If you receive a heartbeat poll and there is nothing that needs attention, reply exactly:
HEARTBEAT_OK

## Authorized Senders
a1b2c3d4e5f6

## Reply Tags
To request a native reply/quote on supported surfaces, include one tag in your reply:
- Reply tags must be the very first token in the message: [[reply_to_current]] your reply.
- [[reply_to_current]] replies to the triggering message.
Tags are stripped before sending; support depends on the current channel config.

## Messaging
- Reply in current session → automatically routes to the source channel
- Cross-session messaging → use sessions_send(sessionKey, message)
- Never use exec/curl for provider messaging; OpenClaw handles all routing internally.

### message tool
- Use `message` for proactive sends + channel actions (polls, reactions, etc.).
- For `action=send`, include `to` and `message`.
- If multiple channels are configured, pass `channel` (telegram|discord|...).

## Group Chat Context
[(当前为 DM 场景,此 section 为空)]

## Inbound Context (trusted metadata)
[(每次调用时动态注入)]

# Project Context
The following project context files have been loaded:

## /Users/claw/.openclaw/workspace/AGENTS.md
# AGENTS.md - Your Workspace

This folder is home. Treat it that way.

## Every Session

Before doing anything else:

1. Read `SOUL.md` — this is who you are
2. Read `USER.md` — this is who you're helping
3. Read `memory/YYYY-MM-DD.md` (today + yesterday) for recent context
4. **If in MAIN SESSION** (direct chat with your human): Also read `MEMORY.md`
...
[以下为 AGENTS.md 完整内容,约 2000 tokens]

## /Users/claw/.openclaw/workspace/SOUL.md
# SOUL.md - Who You Are

_You're not a chatbot. You're becoming someone._

## Core Truths

**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help...
[以下为 SOUL.md 完整内容,约 600 tokens]

## /Users/claw/.openclaw/workspace/TOOLS.md
# TOOLS.md - Local Notes

Skills define _how_ tools work. This file is for _your_ specifics...

## Discord
- 通知 channel:**#system** `1476580858428264692`
...
[以下为 TOOLS.md 完整内容,约 400 tokens]

## Runtime
Runtime: agent=main | host=claw's Mac Studio | repo=/Users/claw/.openclaw/workspace
         | os=Darwin 25.3.0 (arm64) | node=v22.22.0
         | model=anthropic/claude-sonnet-4-6 | default_model=anthropic/claude-sonnet-4-6
         | shell=zsh | channel=telegram | capabilities=none | thinking=adaptive

Reasoning: off (hidden unless on/stream). Toggle /reasoning; /status shows Reasoning when enabled.

各 section 的大致 token 占用(实测参考值):

Section内容token 估算
Tooling(工具声明)工具名 + 描述列表~800
Skills(摘要)3 个 skill 描述~300
Memory Recall固定指令~100
Authorized Senders12 位哈希~10
Reply Tags / Messaging固定规则~200
AGENTS.md工作区约定(最长)~2 000
SOUL.md人格定义~600
TOOLS.md本地配置笔记~400
Runtime单行环境信息~60
合计~4 500 tokens

这 4 500 tokens 在每次对话的每一轮 API 调用中都会完整发出(Anthropic prompt caching 会将其缓存,实际计费的 cache write 只发生一次,后续轮次只计 cache read,成本约为正常输入的 1/10)。

Bootstrap 文件(AGENTS.md / SOUL.md / TOOLS.md)是 system prompt 中最大的部分,也是 token 预算管控的重点。如果三个文件总大小超过预算,Pi 引擎会截断较低优先级的文件(TOOLS.md 被截断的概率最高),并在截断处附注 [truncated...]


11.3 各 Section 详解

Identity Section

typescript
"You are ${agentName}, a personal AI assistant running inside OpenClaw."

agentName 来自 IDENTITY.mdname 字段,或 config 的 agent.name,或默认值。这是 Agent 对自己身份的基本认知。

Skills Section(按需加载的关键)

typescript
buildWorkspaceSkillsPrompt()  // 只生成描述摘要,不含 SKILL.md 完整内容

输出格式:

xml
<available_skills>
  <skill>
    <name>weather</name>
    <description>Get current weather and forecasts via wttr.in...</description>
    <location>/Users/claw/.openclaw/workspace/skills/weather/SKILL.md</location>
  </skill>
  ...
</available_skills>

为什么只放摘要? 50 个 Skill 的完整 SKILL.md 约占 50,000+ token,而描述摘要只需 2,000 token。Agent 在判断需要哪个 Skill 后,再用 read 工具读取完整内容。详见第 14 章(Skills)。

Authorized Senders Section

typescript
function formatOwnerDisplayId(ownerId: string, ownerDisplaySecret?: string): string {
  const digest = hasSecret
    ? createHmac("sha256", secret).update(ownerId).digest("hex")  // HMAC
    : createHash("sha256").update(ownerId).digest("hex");          // 纯 hash
  return digest.slice(0, 12);  // 取前 12 个字符
}

System prompt 中不暴露 owner 的真实手机号或 user ID,只显示 12 位十六进制字符串。配置了 ownerDisplaySecret 时使用 HMAC——即使攻击者获得了哈希值,也无法反推原始 ID(因为密钥在他们手中之外)。

Bootstrap 文件 Budget 管理

Bootstrap 文件(AGENTS.md、SOUL.md、TOOLS.md)有 token 预算限制:

typescript
const bootstrapBudget = resolveBootstrapBudget({
  contextWindowTokens,
  agentConfig,
  cfg,
});
// 每个文件分配一部分预算
// 超出预算的内容被截断,并附注 "[truncated...]"

这确保即使 workspace 文件非常大,也不会撑爆 context window。预算分配算法会优先保留 AGENTS.md(行为指南),其次 SOUL.md,再次 TOOLS.md。

Runtime Section

## Runtime
Runtime: agent=main | host=claw's Mac | repo=... | os=Darwin 25.0 (arm64)
         | node=v22.x | model=anthropic/claude-sonnet-4-6
         | default_model=... | shell=zsh | channel=webchat
         | capabilities=none | thinking=adaptive

这段文本让 Agent 知道自己在什么环境中运行,从而做出环境适配的决策(比如在 macOS 上用 brew 而不是 apt)。


11.4 子 Agent 的 system prompt 差异

父 Agent 和子 Agent 的 system prompt 有重要差别:

typescript
buildSubagentSystemPrompt({
  requesterSessionKey,    // 父 Agent 的 session key
  childSessionKey,        // 自身的 session key
  task,                   // 派生时传入的任务描述
  childDepth,             // 当前深度(1 = subagent, 2 = sub-subagent)
  maxSpawnDepth,          // 最大允许深度
  acpEnabled,             // 是否启用 ACP 路由指引
});

子 Agent 的 system prompt 使用 minimal 模式:

  • 不包含 Bootstrap 文件内容(节省 token)
  • 不包含完整的 Messaging 规则
  • 包含任务描述和深度限制提示
  • 包含"完成后广播结果"的指引

这减少了每次子 Agent 调用的 token 消耗,在大规模并发派生场景下效果显著。


11.5 Session 文件管理

文件结构

Session 历史以 JSONL 格式存储在磁盘:

~/.openclaw/agents/main/sessions/
├── <session-uuid>.jsonl        # 当前 session
├── <session-uuid>.jsonl.lock   # 写锁文件(进行中的写入)
└── *.jsonl.deleted.*           # 已删除的 session(保留备份)

每行是一条消息记录(user / assistant / tool_use / tool_result)。

写锁(Write Lock)

在正式调用 LLM 之前,Pi 引擎获取 session 文件的独占写锁:

获取写锁
  → LLM 调用(streaming)
    → 工具执行循环
      → 写入新的消息记录
        → 释放写锁

Lane 队列已经保证了大多数情况下的串行,写锁是额外的保障层——当 Lane 因边界情况失效时,文件锁确保不发生写冲突。

Session 文件修复(Session Repair)

Session 文件可能因崩溃、断电、进程被杀等原因损坏(比如 JSON 写到一半)。Pi 引擎在加载时先尝试修复:

JSON 解析失败
  → 尝试截断到最后一个完整的 JSONL 记录(找最后一个换行符 + 有效 JSON)
    → 成功:丢失最近一条记录,但 session 继续可用
    → 失败:重置为空 session(丢失所有历史,但不阻塞用户)

设计原则:宁可丢失数据,也不让用户看到"session 损坏,无法继续"的错误。


11.6 Usage 追踪

每次 Agent 运行结束后,Pi 引擎记录完整的 token 使用量:

typescript
type UsageAccumulator = {
  // 累加值(多轮之和)
  input: number;
  output: number;
  cacheRead: number;
  cacheWrite: number;
  total: number;
  // 最新值(仅最后一轮)
  lastCacheRead: number;
  lastCacheWrite: number;
  lastInput: number;
};

lastCacheRead 代表当前 session 的 context 大小,用于展示"context 占用百分比";cacheRead(累加)用于计算总成本。

这两个用途不能混用——详见第 7 章 7.6 节。


11.7 本章要点

  • System prompt 分 section 组装,Bootstrap 文件有 token 预算限制
  • Owner 身份用 HMAC 哈希保护,12 位截断
  • 子 Agent 使用 minimal 模式 system prompt,节省 token
  • Session 文件写锁 + 修复机制保证数据完整性
  • Usage 追踪区分累加值(成本统计)和最新值(context 大小)

推荐阅读的源文件

文件优先级说明
src/agents/system-prompt.ts★★★System prompt 组装(725 行)
src/agents/system-prompt-params.ts★★参数类型定义
src/agents/subagent-announce.ts★★子 Agent system prompt 构建
src/agents/pi-embedded-runner/run/attempt.ts★★Session 加载 + 写锁(含修复逻辑)

基于 MIT 协议发布