摘要 :很多 Agent 系统把"学习"简化成一句话:多聊几次就更懂你了。但工程上更关键的是:学到哪里、谁来写、如何不翻车。本文从 OpenClaw 的源码与运行时机制出发,把 Learning & Adaptation 拆成三条可验证的主线:文件化的学习载体(八类人格/工作区文件) 、系统提示词注入让学习默认生效 、以及 显式更新 / Heartbeat / Compaction 三类触发器如何把"经验"蒸馏成稳定规则。你将看到 OpenClaw 如何把学习变成可审计、可回滚、可控成本的工程系统。
关键词:OpenClaw;Learning & Adaptation;SOUL.md;AGENTS.md;System Prompt;Workspace Bootstrap;Heartbeat;Compaction;Distillation;Self-modification
系列文章:
- OpenClaw 深度解析与源代码导读 · 第1篇:系列导读------术语、版本与读源码方法
- OpenClaw 深度解析与源代码导读 · 第2篇:Skills------能力扩展平面与源码中的「目录即技能」
- OpenClaw 深度解析与源代码导读 · 第3篇:Gateway------常驻控制面、单端口多协议与进程骨架
- OpenClaw 深度解析与源代码导读 · 第4篇:Router------入站消息的分发中枢与决策逻辑
- OpenClaw 深度解析与源代码导读 · 第5篇:Brain------Prompt/Context/Harness Engineering 与执行框架
- OpenClaw 深度解析与源代码导读 · 第6篇:Hands------Shell、文件、浏览器与沙箱安全
- OpenClaw 深度解析与源代码导读 · 第7篇:Memory 子系统------持久化、内置记忆与「人格文件」分界
源码版本说明 :本文引用路径基于 openclaw/openclaw 仓库;本地阅读使用的 commit 为 0dd4958bc8a78d26b3b526b1f2e63b15110c64a2(2026-04-11)。GitHub 上可按该 SHA 查看对应版本的源码。
0. 这篇要解决什么问题?
很多 Agent 系统把"学习"讲成一句口号:多聊几次就更懂你了。但工程上你会立刻追问三件事:
- 学到哪里:是写进数据库?写进文件?还是只存在模型幻觉里?
- 谁来写:模型自己写?工具写?人写?有没有边界与审批?
- 怎么不翻车:一旦"学错了",会不会把系统 prompt 写崩、把安全规则改没?
OpenClaw 的回答非常工程化:它把"学习(Learning)"落在一套可读、可写、可审计的 Markdown 文件上,并通过运行时把这些文件"注入"到系统提示词(System Prompt)里,让学习结果对模型有稳定约束力。
💡 理解要点:OpenClaw 的 Learning 更像"把经验沉淀成可控配置",而不是"让模型在脑子里记住"。
1. Learning 的核心心智模型:把 Agent 变成"可迭代的软件"
想象一下你在带一个新人同事:
- 你不会指望他"凭感觉记住一切"
- 你会给他:工作手册(规则) 、沟通风格(语气) 、项目背景(上下文) 、值班清单(巡检)
- 每次踩坑后,你会更新手册:把"教训"写进去,下一次就不再犯
OpenClaw 把这套做成了八类文件(下文简称"人格/工作区文件"),并在每次 agent run 时自动注入。
1.1 架构图:Learning & Adaptation 的闭环总览(从输入到"写回文件")
学习载体(Artifacts / Files)
运行时(Runtime)
输入(Inputs)
写入/更新
写入/更新
写入/更新
写入/更新
写入/更新
写入/更新
flush/index
落盘
注入
注入
注入
注入
注入
按需/动态注入
用户消息 / Channel 消息 / 系统事件
System Prompt 构建
注入工作区文件
Brain / LLM 推理与工具调用
Hands / Tools
read/write/edit/exec...
AGENTS.md
顶层规则/工具策略
SOUL.md
语气/表达风格
USER.md
用户稳定偏好/约束
TOOLS.md
外部工具使用指南
MEMORY.md
记忆策略/检索策略
HEARTBEAT.md
周期清单/tasks
memory corpus/*.md
事实/偏好/项目上下文
sessions/*
messages.jsonl/summary.md
💡 理解要点:Learning 在 OpenClaw 里不是"记在模型里",而是写回文件 → 下次注入 prompt → 行为稳定改变。
对应源码与文档线索:
- 系统提示词如何拼装 :
openclaw/src/agents/system-prompt.ts+ 文档openclaw/docs/concepts/system-prompt.md - 工作区如何创建与加载 :
openclaw/src/agents/workspace.ts - 心跳如何驱动周期性维护 :
openclaw/src/infra/heartbeat-runner.ts+ 文档openclaw/docs/gateway/heartbeat.md
1.2 八类文件在 System Prompt 中的位置
很多读者会误以为:"既然这 8 个文件这么重要,那 system prompt 就是它们组成的吧?"
不是。 8 个文件只是 system prompt 的一部分(称为 "Project Context"),而非全部。
System Prompt 的完整组成
根据 openclaw/src/agents/system-prompt.ts 的构建逻辑,每次 agent run 的 system prompt 由以下部分拼接而成:
| 部分 | 内容 | 来源 |
|---|---|---|
| 基础身份 | "You are a personal assistant running inside OpenClaw." | 硬编码 |
| Tooling | 可用工具列表(read/write/edit/exec...)、工具调用风格 | 硬编码 |
| Safety | 安全规则(Anthropic constitution 风格) | 硬编码 |
| Skills | <available_skills> 列表及读取逻辑 |
硬编码 + 运行时技能扫描 |
| Memory | 记忆工具提示(memory_search/get/flush) | 硬编码 |
| OpenClaw 运维 | CLI 快速参考、自我更新规则、模型别名 | 硬编码 |
| 8 个文件(Project Context) | AGENTS.md / SOUL.md / ... 作为用户自定义层 | 工作区文件注入 |
| 动态运行时信息 | 当前时间、运行时状态、心跳提示 | 运行时注入 |
关键理解:固定部分 vs 可变部分
可以把 system prompt 想象成一份分层配置:
- 固定部分(OpenClaw 硬编码) :告诉 Agent"你能做什么、不能做什么、怎么做"------这是基础设施层,用户无法直接修改
- 可变部分(8 个文件注入) :告诉 Agent"你是谁、为谁服务、有什么偏好"------这是用户定制层,通过文件写入实现 Learning & Adaptation
为什么这样设计?
这种分层架构解决了两个工程问题:
- 安全隔离:底层规则(如"不要执行 rm -rf /")无法被用户文件意外覆盖
- 可控学习:用户/Agent 只能在"可变部分"写入,而不会破坏系统基础设施
💡 一句话:8 个文件是 system prompt 的"用户可写层",存放学习产物;而 system prompt 的"系统固定层"由 OpenClaw 源码定义,提供执行框架和安全边界。
2. "学习"写入到哪里:八类文件的分工(语义层)
OpenClaw 的一条重要设计原则是:把不同性质的信息放在不同文件里,让"学习"可控、可审计、可回滚。
2.1 八类文件的优先级:谁覆盖谁?
系统提示词会对注入的工作区文件排序,顺序在源码里是硬编码的(数字越小优先级越高):
| 优先级 | 文件 | 核心作用(一句话) |
|---|---|---|
| 10 | AGENTS.md |
顶层行为规则------定义 Agent 的工具策略、安全红线、失败处理与群体聊天规范 |
| 20 | SOUL.md |
人格与语气------塑造 Agent 的表达方式、立场态度、沟通风格(不是安全策略) |
| 30 | IDENTITY.md |
身份标识------Agent 的名称、版本、形象(emoji/avatar)等元信息 |
| 40 | USER.md |
用户画像------记录用户的称呼、偏好、项目背景、长期目标 |
| 50 | TOOLS.md |
工具使用指南------外部工具的本地配置、环境特定参数、快捷别名 |
| 60 | BOOTSTRAP.md |
首次启动清单------新工作区初始化时的自我认知流程(用后即删) |
| 70 | MEMORY.md |
长期记忆策略------记忆 flush/检索策略,以及用户/项目的稳定长期结论 |
| 动态 | HEARTBEAT.md |
周期巡检清单 ------定义心跳回合要执行的周期性检查任务(tasks:) |
🔍 实际例子:你在
USER.md里写"尽量简洁",但AGENTS.md里写"必须先给步骤并附带测试计划",最终会以AGENTS.md为准。
2.2 学习(Learning)与记忆(Memory)的边界
为了避免混乱,可以用一句话划分:
- Memory:偏"事实与素材库"(可检索、可引用、可丢弃噪音)
- Learning & Adaptation:偏"行为与策略"(影响 agent 的长期工作方式)
在 OpenClaw 的系统 prompt 文档里也能看到类似取舍:工作区 bootstrap 文件会自动注入,但 memory/YYYY-MM-DD.md 这类日记不会自动注入,而是通过 memory_search/memory_get 按需读取(减少上下文开销)。
💡 理解要点:Learning 的产物要短、稳、能长期注入;Memory 的产物可以大、杂,但要按需召回。
3. "学习"如何生效:系统提示词注入(Prompt-level Enforcement)
OpenClaw 的 system prompt 文档明确说了:每次 agent run 都会构建一个 OpenClaw-owned 的 system prompt,并把工作区文件作为 Project Context 注入进去(同时有 cache boundary 的稳定性设计)。
在 openclaw/src/agents/system-prompt.ts 里,你能看到三个特别关键的机制:
3.1 注入是"默认行为",不是"模型自觉"
工作区文件会在每次构建 prompt 时被加载,并作为 Project Context 拼进去(文档里叫 Workspace bootstrap injection)。这意味着:
- 你不需要每次对话都提醒"按这个风格写"
- 你只要把规则写进文件,运行时就会持续施加约束
🔍 实际例子:当你把"不要用套话开场"写进
SOUL.md,之后每一轮回复都受它影响(除非更高优先级覆盖)。
3.2 Cache Boundary:把"高频变更"放到动态区(以 HEARTBEAT.md 为例)
system-prompt.ts 把 heartbeat.md 视作动态上下文文件(DYNAMIC_CONTEXT_FILE_BASENAMES),并把它尽可能放在 cache boundary 之后。
直觉上:心跳清单可能经常改,如果放在稳定区,会导致缓存失效、token 成本上涨。
💡 理解要点:Learning 不是"写越多越好",而是"写在合适的层、控制变更频率"。
与其画图,你可以把它理解成:系统提示词被切成"尽量稳定的前缀(prefix)"与"容易变化的后缀(suffix)"两段 ,中间用 SYSTEM_PROMPT_CACHE_BOUNDARY 作为"缓存分界线(cache boundary)"。运行时会尽量让经常变化的内容落在分界线之后,减少缓存失效与 token 成本。
下面用一张表把"稳定区/动态区"说清楚(对应逻辑见 openclaw/src/agents/system-prompt.ts 的 DYNAMIC_CONTEXT_FILE_BASENAMES 与 boundary 拼装)。
| 区域 | 含义 | 典型内容(OpenClaw 注入) | 代表文件 | 建议更新频率(经验值) | 写作建议 |
|---|---|---|---|---|---|
| 稳定注入区(Cache-stable prefix) | 尽量保持不变的"规则/画像/策略"集合。变化会导致更大范围的缓存失效。 | 按优先级注入的工作区文件(除动态文件外) | AGENTS.md、SOUL.md、IDENTITY.md、USER.md、TOOLS.md、BOOTSTRAP.md、MEMORY.md |
低频:通常"按里程碑/按周/按发现明显长期规律时"更新;避免"每天多次改" | 内容要短、稳定、可长期复用;避免把流水账写进来 |
| 动态注入区(Cache-volatile suffix) | 允许频繁变化的"运行时状态/周期任务/短期提示"。尽量放在分界线后以降低成本。 | 动态上下文文件 + 运行时临时信息(时间、状态、心跳提示等) | HEARTBEAT.md(动态文件);以及运行时附加的临时块 |
可高频:按任务需要调整;同一日多次变更也可以,但应保持很短 | 只放"会变的、短期有效的";用 tasks: 控制频率与 no-tasks-due 跳过逻辑 |
再强调两点容易误解的地方:
- "稳定/动态"不是说文件本身永远不改:它描述的是"对缓存与成本的影响"。稳定区文件当然可以改,但要把它当成"配置/规则"来改,避免频繁小改动。
- 哪些文件会被当作动态文件是硬编码的 :OpenClaw 目前把
heartbeat.md标为动态(DYNAMIC_CONTEXT_FILE_BASENAMES = new Set(["heartbeat.md"])),因此会尽量放在 boundary 之后;其它文件按CONTEXT_FILE_ORDER排序注入。
🔍 实际例子:你频繁改
HEARTBEAT.md不应该导致"整段系统提示词都失效重算",因此它会被尽量放在 boundary 之后。
4. "学习"如何被触发:从对话回合到周期性自维护
OpenClaw 的 Learning/Adaptation 不是单一开关,更像三种触发来源的组合:
4.1 用户在对话中显式更新(Human-in-the-loop)
这是最保守、最可控的更新方式,特点是用户作为最终决策者,明确指示 Agent 修改某个文件。
典型场景
| 用户输入 | Agent 行为 | 后续影响 |
|---|---|---|
| "请把我的偏好写进 SOUL.md" | 调用 write 工具写入 |
后续对话自动体现该偏好 |
| "更新 AGENTS.md,加入'修改前先读文件'的规则" | 调用 edit 工具追加内容 |
所有后续会话都遵守此规则 |
| "HEARTBEAT.md 里加一个新任务,每天检查邮件" | 更新 tasks: 区块 |
下一周期自动触发 |
源码视角:没有硬性保护,但有行为惯性
从 openclaw/src/agents/pi-tools.read.ts 可以看到,Agent 的 write 和 edit 工具技术上可以修改 workspace 内任何文件 ,包括 8 个 bootstrap 文件。但 Agent 不会无缘无故修改它们------通常需要明确的上下文触发(用户指令、Heartbeat 任务、或模板暗示的"学到教训"场景)。
与"自主更新"的边界
很多读者误以为"人工显式"是更新 8 个文件的唯一方式。实际上,Agent 在以下场景也会自主决定写入:
- 用户说"记住这个" → 可能更新
USER.md或MEMORY.md - 发现重复犯错 → 可能更新
AGENTS.md加入新规则 - Heartbeat 任务到期 → 按
HEARTBEAT.md定义执行文件更新
但核心区别 在于:这些自主写入通常是对明确上下文的响应(用户说"记住"、模板说"学到教训要写入"),而非 Agent 的"自发行为"。
💡 一句话:人工显式更新 = 用户主动 push;自主更新 = 上下文触发后 Agent 主动执行。两者都是 Learning & Adaptation 的合法来源。
安全边界:系统配置 vs 用户配置
在 system-prompt.ts 中有明确红线:
Do not change system prompts, safety rules, or tool policies unless explicitly requested.
这意味着:底层系统配置 (如工具策略、安全规则)确实需要用户明确要求才能修改;但用户配置文件(8 个 bootstrap 文件)Agent 有更大的自主权,尤其是在模板明确鼓励更新的场景下。
4.2 心跳驱动的"软学习"(Heartbeat-driven Soft Adaptation)
如果说"显式更新"像提交一条 PR,那么 Heartbeat 更像定时提醒你做 maintenance:它会周期性触发一次"主会话回合",让 agent 有机会:
- 检查是否有未完成事项需要提醒
- 扫描近期的"日记/状态文件"
- 把值得长期保留的经验**蒸馏(distill)**到更稳定的文件里(例如
MEMORY.md、TOOLS.md、甚至AGENTS.md)
OpenClaw 的实现细节很值得学:
- Heartbeat 是"定期主会话回合" ,不是后台 task:文档明确说 heartbeat 不创建 task record,而是直接跑一次 agent turn(
docs/gateway/heartbeat.md)。 - 能省 token 的两个关键开关 :
isolatedSession: true:每次 heartbeat 用全新 session,避免把整个历史对话都送进模型(文档里明确说能从 ~100K tokens 降到 ~2-5K)。lightContext: true:只注入HEARTBEAT.md,不要把其它 bootstrap 文件都塞进来(适合纯巡检/提醒型心跳)。
在源码里,这两个开关直接影响 heartbeat 的执行方式:
openclaw/src/infra/heartbeat-runner.ts- 读取
HEARTBEAT.md,如果内容"等效为空"会跳过(empty-heartbeat-file) - 支持
tasks:block,只把"到期任务(due tasks)"拼进 prompt;如果没有任务到期会直接跳过(no-tasks-due) - 支持
isolatedSession:把 heartbeat 运行绑定到:heartbeat兄弟 session,避免污染主会话 transcript - 支持重复内容去重:同样的 heartbeat payload 在 24h 内重复会被跳过,避免"nagging"
- 读取
💡 理解要点:Heartbeat 的价值不在"更频繁地打扰你",而在"把维护工作自动化、把模型调用变成可控的周期成本"。
🔍 例子:一个"可直接用"的 HEARTBEAT.md
下面这个例子刻意覆盖三类常见任务:提醒型 (每小时)、蒸馏型 (每天/每周)、清理型(每周)。你可以把它当作"把 Learning 变成周期维护"的最小落地模板。
markdown
# HEARTBEAT.md
#
# 说明:
# - 这个文件支持 tasks: block。OpenClaw 会只执行"到期(due)"的任务;没到期会跳过,省 token(no-tasks-due)。
# - 如果你希望心跳"只维护不打扰",把 agents.defaults.heartbeat.target 设为 "none"(见下文 6.2 的配置示例)。
tasks:
# 1) 提醒型:每小时检查一次是否有"该提醒"的未完成事项
- name: remind-open-items
interval: 1h
prompt: |
Check the workspace for open items that should be reminded.
- Look for TODO markers in recent notes, or obvious "pending" sections in MEMORY.md / project docs.
- If there's something actionable, write a short alert in Chinese with the next step.
- If nothing needs attention, reply HEARTBEAT_OK.
# 2) 蒸馏型:每天把近期流水账提炼成"可长期注入"的稳定条目
- name: distill-daily-notes
interval: 24h
prompt: |
Distill the last 1-2 days of notes into stable learnings.
- Read memory/YYYY-MM-DD.md (today + yesterday) if present.
- Update MEMORY.md with concise, durable items (preferences, decisions, constraints).
- Do not copy raw logs into MEMORY.md; keep it short.
- If you made changes, summarize what changed in 3 bullets; otherwise HEARTBEAT_OK.
# 3) 清理型:每周清理过期/重复规则,避免"稳定区"膨胀导致 token burn
- name: prune-memory
interval: 7d
prompt: |
Review MEMORY.md and remove stale, duplicate, or one-off items.
- Keep only durable facts/policies.
- If anything is ambiguous, leave it and flag it as "needs confirmation" instead of deleting.
- If no changes are needed, reply HEARTBEAT_OK.
这个例子和前文的两个关键开关是配套的:
isolatedSession: true:让上述任务在:heartbeat隔离会话里跑,避免把主会话历史越跑越长。lightContext: true:适合这类"巡检/蒸馏/清理"任务------通常不需要把全部 bootstrap 文件都注入,只要HEARTBEAT.md的任务定义即可。
4.2.1 架构图:Heartbeat 的执行回路(Preflight → Due Tasks → Isolated Session → 去重)
Channel Delivery Brain/LLM Session Store Workspace(HEARTBEAT.md) heartbeat-runner.ts Scheduler/Timer Channel Delivery Brain/LLM Session Store Workspace(HEARTBEAT.md) heartbeat-runner.ts Scheduler/Timer alt [no tasks due] [due tasks 存在] alt [file 空 + 无 tasks] [有 tasks] [无 tasks] tick / wake(reason=interval|wake|cron|exec) Preflight(quiet-hours / requests-in-flight 等) 读取 HEARTBEAT.md skip(empty-heartbeat-file) parse tasks + 过滤 due tasks skip(no-tasks-due) 可选 isolatedSession(:heartbeat session) 发送 batched prompt(仅 due tasks + directives) reply(HEARTBEAT_OK 或 alert) 去重(24h 内重复 payload → skip duplicate) 可选投递(target=none/last/指定渠道) 更新 task last-run timestamps fallback heartbeat prompt HEARTBEAT_OK / alert 按可见性 showOk/showAlerts 投递或吞掉
下面按图中的顺序,把 Heartbeat(心跳回合)的真实执行流程讲清楚(对应实现主要在 openclaw/src/infra/heartbeat-runner.ts,并调用 openclaw/src/auto-reply/heartbeat.ts 的解析/过滤逻辑)。
A) 触发:是谁把 Heartbeat"叫醒"的?
图里第一步是:
Timer -> HB: tick / wake(reason=interval|wake|cron|exec)
这里的 reason 本质是在标注"这次心跳为什么发生",常见来源包括:
- interval :到点了(按
agents.defaults.heartbeat.every的周期) - wake/hook:系统事件/完成事件唤醒(例如后台任务完成、或需要主会话注意)
- cron:和 cron 事件相关的唤醒
- exec:和 exec completion 事件相关的唤醒
💡 理解要点:Heartbeat 并不等于"定时发消息",它首先是"定时跑一轮 agent turn";是否对外发消息是后面的 delivery 决策。
B) Preflight:在真正调用 LLM 之前,先过滤"是否值得跑"
图里第二步:
HB -> HB: Preflight(quiet-hours / requests-in-flight 等)
对应源码里会做一串"前置跳过(skip)"判断,典型包括:
- disabled :心跳未启用(
every=0m或全局禁用) - quiet-hours:不在 active hours(静默时段)
- requests-in-flight:主队列或 session lane 正在忙,避免打断流式对话;这类情况会跳过并由 wake 层稍后重试
🔍 实际例子:你正在和 agent 进行一次长回复/长工具执行时,Heartbeat 到点了也不会硬插队。
C) 读取 HEARTBEAT.md:有无任务清单决定了"batched tasks 模式"是否生效
图里第三步:
HB -> WS: 读取 HEARTBEAT.md
读取后分两种大分支:
- 文件内容等效为空 + 没有 tasks
直接跳过,图里对应:
skip(empty-heartbeat-file)
- 文件里有
tasks:block
进入 tasks 模式(batched tasks),图里对应:
parse tasks + 过滤 due tasks
其中 "due tasks(到期任务)" 的意思是:每个 task 都有自己的 interval,OpenClaw 会用 session 里保存的 heartbeatTaskState(上次执行时间戳)来判断是否到期。
D) no-tasks-due:如果没有任何任务到期,这一轮心跳会被直接跳过(省钱)
图里这个分支是:
alt no tasks due -> skip(no-tasks-due)
这一步非常关键:它让你可以把多个周期检查都写进同一个 HEARTBEAT.md,但每次 tick 只执行到期的那几个,而不是"每 30 分钟全做一遍"。
💡 理解要点:
tasks:的意义不是"让心跳更忙",而是"让心跳更可控、更便宜"。
E) isolatedSession:把心跳跑在 :heartbeat 会话里,避免把主会话越跑越长
图里这一步是:
HB -> Store: 可选 isolatedSession(:heartbeat session)
当你配置 isolatedSession: true 时,Heartbeat 会创建/复用一个 :heartbeat 后缀的隔离 session 来执行本次 LLM 回合。
这带来两个直接收益:
- 降低 token 成本:隔离 session 没有主会话的长历史(尤其适合"巡检型心跳")
- 避免污染主会话 transcript:心跳的"自言自语式维护"不会把主会话变成流水账
F) 组装 prompt 并调用 LLM:tasks 模式 vs fallback 模式
图里两条路径分别是:
- tasks 模式 :
发送 batched prompt(仅 due tasks + directives) - fallback 模式 (没有 tasks):
fallback heartbeat prompt
二者的共同点是:这一步才会真正调用 LLM,并得到两类结果:
HEARTBEAT_OK:表示"没事可汇报"(ack)- alert 文本:表示"有事需要你知道"
G) 去重(dedupe):24h 内相同 payload 会被抑制,避免 nagging
图里是:
去重(24h 内重复 payload → skip duplicate)
它的效果很直观:如果模型连续两次给出几乎相同的提醒(例如"你有一个未读邮件"但实际上状态没变),系统会把重复内容跳过,避免"每 30 分钟提醒一次"的骚扰体验。
H) 投递(Delivery):是否对外发消息取决于 target 与可见性策略
最后是:
可选投递(target=none/last/指定渠道)按可见性 showOk/showAlerts 投递或吞掉
你可以把 delivery 看成两个开关叠加:
- target 路由 :发到哪里(
none表示只跑不发) - 可见性策略:OK 是否展示(showOk)、Alert 是否展示(showAlerts)、是否发 indicator(useIndicator)
🔍 实际例子:你把
target: "none"打开,Heartbeat 仍然会"维护文件/更新状态",但不会在聊天里刷屏。
4.3 上下文压缩驱动的"蒸馏"(Compaction-driven Distillation)
当对话变长、token 接近上限时,系统触发 Compaction------把旧消息摘要化。这不仅是"省 token",更是一次"蒸馏":把长对话压成结构化摘要,保留关键任务、决策与标识符。
OpenClaw 的**压缩护栏(Safeguard)**是关键防翻车设计:
- 从
AGENTS.md抽取 "Session Startup" / "Red Lines" 塞进摘要尾部 - 即使摘要被截断,规则锚点优先保留,避免"越聊越跑偏"
💡 一句话:Compaction 不只是压缩,更是"被迫缩短上下文时,把行为约束固化下来"的自适应手段。
4.3.1 常见疑问:8 个文件会被"蒸馏"吗?
不会。
| 对象 | 是否被 Compaction 处理 | 说明 |
|---|---|---|
会话消息历史 (messagesToSummarize) |
✅ 被摘要 | 长对话分段 → 摘要 → 写入 summary / compaction entry |
8 个 workspace 文件 (AGENTS.md...) |
❌ 不被改写 | 按原有规则存在于 system prompt;唯一例外:AGENTS.md 的关键段落会作为 <workspace-critical-rules> 附加进摘要尾部当锚点 |
结论:Compaction 让"对话历史"变短;Safeguard 让"关键规则"不丢;Learning 文件更新仍靠显式写入或 Heartbeat。
4.3.2 蒸馏用的 Prompt 长什么样?
不是一段固定长文本,而是由三块指令拼成 (源码位置:compaction-safeguard-quality.ts + compaction-instructions.ts):
1) 结构化骨架(强制标题)
## Decisions
## Open TODOs
## Constraints/Rules
## Pending user asks
## Exact identifiers
要求严格保留这些段落,且标识符保真(strict/off/custom policy)。
2) 默认附加要求
- 保持语言一致、不翻译代码与路径、不改动标识符
3) 分块合并约束 (MERGE_SUMMARIES_INSTRUCTIONS)
- MUST PRESERVE: active tasks / batch progress / last user ask / decisions / constraints
4.3.3 架构:Compaction 五阶段链路
产物
护栏:保规则
摘要:两条路径
准备:整理素材
触发条件
fallback
上下文接近上限 / 需腾挪 token
选择消息段,保留最近 turns
修复 tool call/result 配对
收集 read/modified files
收集 Tool Failures
可选:外部 Provider 摘要
LLM summarizeInStages:分块+合并
可选:质量审计/重试
从 AGENTS.md 抽取关键规则
无内容时仍写 boundary 防循环
截断优先保留 suffix
写入 summary / compaction entry
继续对话:更小上下文+规则锚点
A) Trigger:何时触发?
上下文接近上限,下一轮会超窗时强制触发。
B) Preparation:整理什么?
- Pick:保留最近若干轮,防止关键信息被过度摘要
- Repair:修复 tool 配对,避免 provider 报错
- FileOps/ToolFail:记录文件操作与失败信息,方便后续排错
C) Summarize:怎么摘要?
- Provider 路径:配置外部摘要服务时优先走(一次处理全部)
- LLM 路径(summarizeInStages):分块 → 摘要 → 递归合并
- Quality Guard:检查是否丢了关键标识符、结构是否完整、是否覆盖最新需求;不合格可重试
💡 关键:标识符丢了 = "记得有文件但不知道叫什么"的灾难
D) Safeguard:护栏保什么?
- Rules :
AGENTS.md关键规则作为 suffix 附加,防止行为漂移 - Boundary:无内容时仍写最小 entry,避免 compaction 死循环
- Cap:截断时优先保留 suffix(规则、诊断),因为这些对"继续工作"更关键
E) Outputs:产物怎么用?
- Summary:结构化摘要 + 诊断/规则后缀,写入会话记录
- Continue:下一轮携带"摘要 + 最近消息",规则锚点仍在
🔍 例子:你在
AGENTS.md写了"修改前必须先读文件",即使对话被压缩,后续回合仍会在摘要尾部看到这条规则。
5. Learning & Adaptation 的"安全边界":允许自改,但不允许乱改
Learning 的本质是"写文件"。而"写文件"在 Agent 系统里既强大又危险。
OpenClaw 的思路是把风险拆成两类,并分别治理:
5.1 让"可改的地方"更可控:文件分层 + 注入顺序
把不同性质的信息放在不同文件里(并按优先级注入),天然降低了误改的破坏面:
- 想改语气 :改
SOUL.md(不会影响工具权限) - 想改工具策略 :改
AGENTS.md/TOOLS.md(可以审计) - 想改巡检节奏 :改
HEARTBEAT.md(动态区,成本可控) - 想改长期记忆 :改
MEMORY.md(但注意它会被注入,太长会涨 token)
这类"软边界"靠的是结构化与优先级,而不是强制执行。
5.2 让"关键阶段"更不容易崩:护栏与降级路径
以 compaction safeguard 为例,它准备了多种降级策略:
- provider 失败 → 回退到 LLM 分阶段摘要(
summarizeInStages) - 摘要质量不合格 → 允许重试(quality guard)
- 根本没有可摘要内容 → 仍写入一个最小 boundary,避免进入"反复触发 compaction 的死循环"
🔍 实际例子:在长时间运行的 gateway/cron/heartbeat 场景里,如果 compaction 进入 cancel loop,会直接拖垮自动化;safeguard 的目标就是"宁可保守,也别把系统卡死"。
6. 端到端:从"用户一句话"到"长期行为改变"
下面用一个具体场景把 Learning & Adaptation 走一遍。
6.1 场景:用户想要"更直接、更少套话"的回答风格
Round 1(用户反馈)
User:以后别用"很好的问题/我很乐意帮助",直接回答重点就行。
Round 2(显式学习写入)
Agent:收到。我会把这个偏好写进
SOUL.md,以后都按这个风格回复。
落地动作(你可以把它理解成"让 agent 自己给自己改配置"):
- 更新
SOUL.md的 Tone/Interaction 规则,例如加入:- "不要用套话开场"
- "结论先行"
Round 3(效果)
User:Builtin 的 indexing 到底有没有?
Agent:默认没有;只有启用 indexing 才会建立 SQLite FTS 索引。(然后给清晰解释)
💡 理解要点:Learning 的"完成标志"不是模型说"我记住了",而是文件被更新 + 后续行为稳定一致。
6.2 场景:把"日常维护"变成系统能力(Heartbeat)
你希望 agent 每天自动做三件事:
- 检查未完成事项(来自
agent-knowledge.md或任务清单) - 更新
MEMORY.md(蒸馏长期经验) - 如果
HEARTBEAT.md太长就自动收缩(避免 token burn)
你可以用一段配置把心跳变成"低成本巡检回合":
json5
{
"agents": {
"defaults": {
"heartbeat": {
"every": "30m",
"target": "none",
"isolatedSession": true,
"lightContext": true
}
}
}
}
再配合一个很小的 HEARTBEAT.md(上面示例的 tasks:),就形成了一个闭环:
定期触发(heartbeat-runner.ts)
↓
只跑到期 tasks(parseHeartbeatTasks + isTaskDue)
↓
必要时改文件(MEMORY.md / HEARTBEAT.md / TOOLS.md)
↓
无事则 HEARTBEAT_OK(并可被系统吞掉,不打扰用户)
🔍 实际例子:当你把
target: "none"打开,heartbeat 仍然运行,但不会对外发消息。你得到的是"后台维护能力",而不是"每 30 分钟来一句 OK"。
7. 与业界对照:OpenClaw 的 Learning 取舍在哪里?
这一套"文件化学习"看起来朴素,但它解决的是工程世界里最难的三件事:可控、可审计、可回滚。
7.1 优势:学习结果变成"可代码审查的差异"
当学习落在 Markdown 文件上时,你天然获得:
- diff:知道改了什么
- history:知道什么时候改的
- rollback:能回滚到上一版
这类特性在任何严肃工程里都非常关键,尤其当你开始做多 agent、做自动化、做长周期运行。
7.2 代价:需要你像维护配置一样维护人格文件
Learning 不再是"免费魔法",而是"需要 maintenance 的系统":
MEMORY.md写太长会增加注入 token,导致 compaction 更频繁AGENTS.md写得太泛会让模型"口号化",反而不稳定HEARTBEAT.md任务太多会让心跳成本不可控
💡 理解要点:把学习做成"可控系统",就意味着你要像维护服务一样维护它------但回报是稳定性与可预测性。
8. 最佳实践清单(拿来就能用)
8.1 文件怎么写才"有用"?
AGENTS.md:写"硬规则 + 工具策略 + 失败策略",避免写情绪化口号SOUL.md:写"语气/立场/表达习惯",避免写安全策略堆砌(官方也明确提醒不要把它变成 security policy dump)MEMORY.md:写"稳定的长期结论",不要把流水账堆进去(流水账放memory/YYYY-MM-DD.md或 corpus)HEARTBEAT.md:保持极短,用tasks:控制频率,能no-tasks-due就跳过
8.2 "学习"触发该怎么选?
- 要强一致 :用显式更新(用户确认后写
AGENTS.md/SOUL.md) - 要低成本维护 :用 heartbeat(
isolatedSession + lightContext) - 要自动收敛上下文:让 compaction 做蒸馏,但要依赖 safeguard(避免质量问题/死循环)
9. 小结:Learning & Adaptation 是"把经验写进系统"
这篇我们把 OpenClaw 的 Learning & Adaptation 拆成了三个层次:
- 语义层:八类文件分工(哪些属于规则、哪些属于语气、哪些属于巡检)
- 注入层:系统提示词自动注入,让学习"默认生效"
- 触发层:显式更新 / Heartbeat / Compaction,让学习"有节奏、可维护"
如果你只记住一句话:
OpenClaw 的 Learning 不是让模型记住你,而是让系统持续读到你写下的规则。
参考资料(外部)
- OpenAI Prompt Engineering Guide(关于高优先级指令层与迭代 prompt 的建议)[参考:
https://developers.openai.com/api/docs/guides/prompt-engineering] - OpenClaw Docs:System Prompt / SOUL / Heartbeat(本文以本地
openclaw/docs为准,线上镜像可能有漂移)[参考:https://docs.openclaw.ai]
附录 A:8 类文件在 OpenClaw 源码中的 Prompt 内容(模板与注入行为)
目的:把"八类文件到底在 prompt 里长什么样"说清楚,方便你对照源码快速定位。
版本基线:
0dd4958bc8a78d26b3b526b1f2e63b15110c64a2。
A.1 先看"如何进入 Prompt":源码装配规则
这 8 类文件并不是"模型自己会去找",而是运行时按固定规则注入。
规则 1:默认文件名定义(workspace 层)
源码 openclaw/src/agents/workspace.ts 中定义了默认文件名常量:
AGENTS.mdSOUL.mdTOOLS.mdIDENTITY.mdUSER.mdHEARTBEAT.mdBOOTSTRAP.mdMEMORY.md(并兼容小写memory.md作为 fallback)
规则 2:注入优先级(prompt 层)
源码 openclaw/src/agents/system-prompt.ts 中 CONTEXT_FILE_ORDER:
agents.md(10) >soul.md(20) >identity.md(30) >user.md(40) >tools.md(50) >bootstrap.md(60) >memory.md(70)
并且 heartbeat.md 被标记为动态上下文文件(DYNAMIC_CONTEXT_FILE_BASENAMES),尽量放在 cache boundary 之后。
规则 3:子会话/定时会话会被裁剪
workspace.ts 的 filterBootstrapFilesForSession() 显示:在 subagent/cron 场景会走最小 allowlist(AGENTS/SOUL/TOOLS/IDENTITY/USER),不是所有文件都无条件注入。
A.2 八类文件的"源码 Prompt 内容"速查表
| 文件 | 源码模板路径 | 在 Prompt 中的角色 | 模板现状 |
|---|---|---|---|
AGENTS.md |
docs/reference/templates/AGENTS.md |
顶层行为规则与会话启动约束 | 有默认模板 |
SOUL.md |
docs/reference/templates/SOUL.md |
语气、人格、风格边界 | 有默认模板 |
IDENTITY.md |
docs/reference/templates/IDENTITY.md |
身份元数据(name/vibe 等) | 有默认模板 |
USER.md |
docs/reference/templates/USER.md |
用户画像与长期偏好 | 有默认模板 |
TOOLS.md |
docs/reference/templates/TOOLS.md |
本地工具环境/偏好笔记 | 有默认模板 |
BOOTSTRAP.md |
docs/reference/templates/BOOTSTRAP.md |
首次启动引导(完成后可删除) | 有默认模板 |
MEMORY.md |
(无模板文件) | 长期记忆策略/沉淀内容 | 无默认模板,存在即注入 |
HEARTBEAT.md |
docs/reference/templates/HEARTBEAT.md |
周期任务入口(tasks/检查清单) | 有默认模板(默认近空) |
A.3 逐文件核心片段(来自源码模板)
下面是每个文件在源码模板中的"关键 prompt 语句/结构",用于理解它们如何影响 agent。
1) AGENTS.md(顶层规则)
来源:openclaw/docs/reference/templates/AGENTS.md
核心内容要点:
- Session Startup 明确要求:
- 先读
SOUL.md - 读
USER.md - 读最近
memory/YYYY-MM-DD.md - 主会话还要读
MEMORY.md
- 先读
- Red Lines 明确红线:
- 不外泄隐私
- 破坏性命令先确认
- Heartbeat 使用策略:
- 何时应
HEARTBEAT_OK - 心跳与 cron 的分工
- 何时应
这意味着:AGENTS.md 不只是"风格建议",而是 agent 的高优先级操作手册。
2) SOUL.md(人格与语气)
来源:openclaw/docs/reference/templates/SOUL.md
核心内容要点:
- Core Truths:强调"真帮助、少套话、有观点"
- Boundaries:隐私与外部动作边界
- Vibe:沟通语气基调
- Continuity:强调会话重启后依赖文件保持连续性
这类内容会直接影响"回复口吻与表达姿态"。
3) IDENTITY.md(身份)
来源:openclaw/docs/reference/templates/IDENTITY.md
核心内容要点:
- Name / Creature / Vibe / Emoji / Avatar 等身份字段
- 本质上是"自我标识层",帮助 agent 在长期会话中保持角色一致性
4) USER.md(用户画像)
来源:openclaw/docs/reference/templates/USER.md
核心内容要点:
- Name / Pronouns / Timezone / Notes
- Context(用户关心什么、正在做什么、偏好与雷区)
它是"对谁服务"的稳定背景层。
5) TOOLS.md(本地工具笔记)
来源:openclaw/docs/reference/templates/TOOLS.md
核心内容要点:
- 明确"技能定义工具通用能力,
TOOLS.md记录你本地环境特有信息" - 典型内容:摄像头别名、SSH 主机、TTS 偏好、设备昵称等
它解决的是"同一技能在不同环境如何落地"的问题。
6) BOOTSTRAP.md(首次引导)
来源:openclaw/docs/reference/templates/BOOTSTRAP.md
核心内容要点:
- 只在初次启动引导时高频使用
- 引导建立
IDENTITY.md/USER.md/SOUL.md - 模板中明确建议"初始化完成后删除 BOOTSTRAP.md"
它更像 onboarding 脚本,而非长期规则文件。
7) MEMORY.md(长期记忆)
来源:workspace.ts + system-prompt.ts 注入逻辑(无默认模板文件)
关键事实(很重要):
- 在当前源码中,
docs/reference/templates/下没有内置MEMORY.md模板 - 但运行时会尝试加载并注入:
- 优先
MEMORY.md - 不存在则 fallback
memory.md
- 优先
- 因此
MEMORY.md的内容由你/agent 在工作区中逐步沉淀,不是"固定官方文案"
这也是它最像"学习产物"的原因:存在即生效,内容随实践演化。
8) HEARTBEAT.md(周期任务)
来源:openclaw/docs/reference/templates/HEARTBEAT.md
模板核心非常简短,默认倾向"空文件以避免无效调用":
- 保持空(或仅注释)可跳过 heartbeat API 调用
- 需要周期检查时再添加 tasks
这和 heartbeat-runner.ts 的执行逻辑一致:空内容/无到期任务时会直接 skip,控制成本。
A.4 一句话总结附录
OpenClaw 的"八类文件 prompt 内容"并不是一段固定大 prompt 文本,而是:
- 模板层 (
docs/reference/templates/*.md)给出默认结构; - 工作区层 (
workspace.ts)把文件创建/加载出来; - 系统提示词层 (
system-prompt.ts)按优先级与动态规则注入到每轮 agent run。
所以你真正在"训练"agent 的地方,不是某个黑箱参数,而是这套可读、可改、可审计的文件体系。