Day 09 / 共 20 天 · 第 2 周 Agent 大脑

提示词构造与上下文组装

昨天(Day 08)走完了执行链路,其中 buildEmbeddedSystemPrompt 一笔带过;今天把它放大:模型到底"看到"什么?答案在 buildAgentSystemPromptsystem-prompt.ts:189)——它把技能、记忆、身份、工具、项目上下文一段段拼成系统提示。这是 Agent 行为的"总说明书",也为明天(Day 10)讲"记忆怎么注入"打基础。

📍 你在整门课的位置(第 2 周 · Agent 大脑)
W1 全景· Day6 Pi 内核 Day7 故障转移 Day8 执行循环 Day9 提示词 Day10 记忆· W3 技能/渠道
💡 用一个类比先兜住今天("岗前培训手册"世界观) 今天全程把系统提示当成公司给新员工的岗前培训手册:同一个人(大模型),发不同的手册,就变成不同岗位的员工。段式组装=手册按章节印(身份/技能菜单/工具清单/公司文化);PromptMode 三档=按岗位发不同厚度的手册(总管发全套、临时工发单页);Project Context=手册里夹的"公司文化+老板简介+你的客户档案"。记住"现拼的一本手册",今天就通了。
L01

系统提示是什么

🤔 痛点:同一个大模型,怎么让它一会儿是"你的私人管家"、一会儿是"只读文件的临时工"? 模型本身是"通用大脑",出厂时并不知道"它此刻叫什么、有哪些技能、谁是主人、能用哪些工具"。要是每次对话都在正文里重新交代一遍,既啰嗦又不一致。得有个固定的"开场说明书",每次自动塞在最前面。
💡 本质:系统提示 = 运行时"现拼"的岗前培训手册 同一个大模型,配不同系统提示就成了不同助理。OpenClaw 的手册不是写死的常量,而是根据"当前有哪些技能、有没有记忆能力、你是不是主人、用什么工具"动态组装出一份"本次专属"的。模型读完这份手册,才知道自己此刻的身份、能力和行为边界。

系统提示(system prompt)是每次对话都放在最前面、告诉模型"你是谁、能干什么、要遵守什么"的一大段文字。它由 buildAgentSystemPromptsystem-prompt.ts:189-689)动态生成,经 createSystemPromptOverride 覆盖进 Pi 会话。

系统提示 = 给助理的"岗前培训手册" 同一个大模型,给它不同的系统提示,就变成不同的助理。OpenClaw 的系统提示是运行时"现拼"的——根据当前有哪些技能、有没有记忆能力、你是不是主人、当前用什么工具,动态组装出一份"本次专属"的手册。模型读完这份手册,才知道自己此刻的身份、可用能力、行为规范。看懂这份手册怎么拼,就看懂了 OpenClaw 的"人格"和"行为边界"从哪来。
L02

段式组装

提示由一系列 build*Section 函数拼接,每个返回字符串数组,最后 lines.filter(Boolean).join("\n")system-prompt.ts:688):

## Skills (mandatory) buildSkillsSection :20 — 可用技能目录
## Memory Recall buildMemorySection :38 — 先搜记忆再答
## Authorized Senders buildUserIdentitySection :66 — 谁是主人
时间 / 回复标签 / 消息 / 语音 / 文档 :97-171
工具清单 coreToolSummaries :240 + toolOrder :274
# Project Context :616 — 注入记忆/人格/AGENTS 文件
## Silent Replies / ## Heartbeats :652 / :670 — 静默与心跳协议
## Runtime :682 — agent/host/os/model/channel/能力
读法:每段职责单一、可开可关,最后拼成一整篇。filter(Boolean) 把空段(比如没有记忆能力时的 Memory 段)自动剔除。这种"段式组装 + 空段自动省略"让提示词既模块化又不留空洞。
一段段 build*Section() filter(Boolean).join ## Skills(技能菜单) ## Memory Recall(无记忆能力→空) ## Authorized Senders(谁是主人) 工具清单 # Project Context(人设+记忆文件) ## Runtime(agent/os/model…) 拼接·剔空段 最终系统提示(一整篇) ✓ Skills ✗ Memory(被 filter 剔除) ✓ Authorized Senders ✓ 工具清单 ✓ Project Context ✓ Runtime
图注:每段独立生成,虚线那段(无记忆能力)返回空字符串,被 filter(Boolean) 剔掉——手册里不会留一页空白。
📝 举个例子:同一份代码,开/关记忆能力,拼出的手册不一样 有 memory 工具时:buildMemorySection() 返回 "## Memory Recall\n答历史问题前先 memory_search…" → 手册里出现这一段。
没有 memory 工具时:buildMemorySection() 返回 ""(空串)→ filter(Boolean) 把它删掉 → 手册里整段消失,也不会留个孤零零的空标题。这就是"段式组装 + 空段自动省略"的实际效果。
L03

PromptMode 三档

// system-prompt.ts:12-17
type PromptMode = "full" | "minimal" | "none";
// full    → 主 Agent,完整手册
// minimal → 子 Agent,仅 Tooling / Workspace / Runtime 三段(省 token)
// none    → 仅一行身份(system-prompt.ts:419)
为什么子 Agent 用精简版? 主 Agent 是"总管",需要完整培训手册。但它可能派出许多子 Agent 去干专门的小活儿(比如"读这个文件总结一下")——这些子 Agent 不需要知道所有技能、所有身份规则,给全套手册纯属浪费 token(花钱又占上下文)。所以 minimal 只给它干活必需的几段。none 则用于极轻量场景,只留一句"你是谁"。按需裁剪提示词是重要的成本优化——我们在 SuperAGI/crewAI 也见过类似的"上下文预算"思想。
档位发给谁手册里有哪些段为什么
full主 Agent(总管)全套:技能/记忆/身份/工具/上下文/协议/Runtime要独当一面,手册得全
minimal子 Agent(干专门小活)仅 Tooling / Workspace / Runtime 三段不需要知道所有技能和身份规则,省 token
none极轻量场景仅一行身份(:419能省则省

👶 小白:给子 Agent 也发全套手册,不是更保险吗?为什么要费劲裁剪?

👨‍🏫 老师:因为系统提示每次请求都要重发一遍,还占上下文窗口——它既花钱(按 token 计费)又挤占宝贵的窗口空间。一个只干"读这个文件总结一下"的子 Agent,你把 52 个技能目录、所有身份规则都塞给它,纯属浪费。就像派实习生去复印个文件,没必要先让他背完整本员工手册。full/minimal/none 就是"看活儿派手册"。

L04

技能目录段

buildSkillsSectionsystem-prompt.ts:20-36,标题"## Skills (mandatory)")只放技能的目录(名称+描述+文件路径),不放技能全文:

// 提示里长这样(XML 形式,由 Pi 的 formatSkillsForPrompt 生成):
// <available_skills>
//   <skill><name>weather</name><description>查天气…</description>
//          <location>skills/weather/SKILL.md</location></skill>
//   ...
// </available_skills>
// 指令:先扫描描述,命中后用 read 工具读对应 SKILL.md,再照做
读法:这是"渐进式披露"——提示里只给菜单(技能名+简介+路径),模型觉得需要哪个,才用 read 工具去读那个 SKILL.md 全文。避免把几十个技能全文塞进每次请求(省 token)。Day 11 详讲技能系统。
L05

工具清单段

工具清单由 coreToolSummaries:240)+ toolOrder:274)+ 规范化工具名(:301-311)生成 - name: summary 列表,告诉模型"你有这些工具、各是干嘛的"。

读法:模型要调工具,前提是知道有哪些工具、怎么用。这一段就是工具的"说明目录"。真正的工具定义(参数 schema、执行函数)在 Day 11 讲;这里是它们在提示词里的"自我介绍"。工具名会先规范化(统一命名风格)再列出。
L06

项目上下文注入

# Project Context 段(system-prompt.ts:616-649)注入工作区的 bootstrap 文件——这就是 README 说的"注入记忆文件":

// 注入的文件(src/agents/workspace.ts:25-33):
// AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md, HEARTBEAT.md,
// BOOTSTRAP.md, MEMORY.md
// 若含 SOUL.md → 追加 "embody its persona and tone"(:632-636)人格设定
这些文件 = 助理的"人设 + 长期记忆" SOUL.md 定义助理的性格语气(读到就"入戏")、USER.md 记你是谁、MEMORY.md 是长期记忆、AGENTS.md 是行为准则。把这些文件塞进每次系统提示,助理就"记得"你的偏好、保持一致的人格。这是"静态记忆注入"——每次都带上(Day 10 还有"动态语义检索"记忆)。字符预算会限制注入总量,防止把上下文撑爆。
L07

覆盖与钩子注入

系统提示还能被追加/覆盖:

  • params.extraSystemPrompt:587):调用方额外追加。
  • before_prompt_build 钩子:扩展注入内容(受 allowPromptInjection 策略门控)。
  • 运行中 prependSystemPromptAdditionattempt.ts:2109)。
  • 诊断:buildSystemPromptReportattempt.ts:1673)记录截断/注入/技能/工具,供排障。
读法:提示词不是死的——扩展可通过钩子注入自己的指令,但受安全策略门控(不是谁都能乱塞,Day 17 安全)。诊断报告让你能回看"这次模型到底收到了什么提示",是调试 Agent 行为的利器。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 系统提示是什么?为什么是运行时动态拼的?
  • 段式组装 + filter(Boolean) 的好处?
  • PromptMode 三档分别用在哪、为什么子 Agent 用 minimal?
  • 技能段为什么只放目录不放全文?
  • Project Context 注入哪些文件?SOUL.md 起什么作用?

✋ 动手

cd /Users/bitmart/work/codes/github/openclaw
grep -n 'function build.*Section\|PromptMode' src/agents/system-prompt.ts | head -20
sed -n '616,649p' src/agents/system-prompt.ts   # Project Context
sed -n '25,33p' src/agents/workspace.ts          # bootstrap 文件名
明天预告 · Day 10(第2周收官)记忆系统——OpenClaw 有两套记忆:静态 bootstrap 文件注入(今天见过)+ 动态语义检索(memory_search 工具 + 向量嵌入)。以及记忆"仅追加"回写的安全设计。
← Day 08 Agent 循环 Day 10 · 记忆系统 →