OpenClaw 场景切换体系:282 Agents 动态管理,告别 100GB 内存爆炸
从 282 Agents 100GB 内存爆炸到场景化动态加载的实战记录

OpenClaw 场景切换体系:282 Agents 动态管理,告别 100GB 内存爆炸
一、噩梦的开始:100GB 内存的「死机」之夜
深夜两点,手机震动——服务器告警。Gateway 进程已吃掉 100GB+ 内存,Swap 爆满,OOM Killer 正在游荡。
htop 画面上,OpenClaw Gateway 进程像一个黑洞,吞噬着服务器的每一滴内存。Restart 之后不出 30 分钟,同样的场景再次上演。
为什么?
因为我的 openclaw.json 里定义了整整 282 个 Agents。
不是手写的——手写 282 个 Agent 配置会写到天荒地老。它们来自 blog-writer skill 编排器自动生成的工作流 Agent、各种第三方 skill 的入口 Agent、定制化的工具 Agent……每一个都乖乖躺在配置里,等着被 Gateway 加载到内存。
Gateway 的行为很简单:启动时一次性加载所有 Agent 配置到内存。282 个 Agent 的完整配置树、system prompt、tool 描述、路由信息……加在一起,内存占用轻松突破 100GB。
关键是,这 282 个 Agent 我根本不会同时使用。我在写作时只需要 20 个左右,做自媒体运营时需要 50 个左右。其他 200 多个 Agent 完全是「占着茅坑不拉屎」——它们躺在内存里,除了给 OOM Killer 送业绩之外毫无贡献。
这就是场景切换体系要解决的核心问题:按需加载,动态切换,而不是一次全部拉满。
二、设计原则:一份无处逃逸的配置源
在动手之前,先确立几条铁律:
2.1 openclaw.json.full 是唯一真相
所有 Agent 配置的权威来源只有一个文件:openclaw.json.full。
这个文件包含全部 282 个 Agent 的完整配置,是手写维护的唯一目标。任何时候有人问"这个 Agent 的配置是哪来的",答案都是「从 openclaw.json.full 里来的」。
这意味着:
- 永不直接编辑
openclaw.json——它只是一个「运行时产物」,随时可以被覆盖 - 任何场景切换工具都只读
openclaw.json.full,从它出发 openclaw.json的本质是一个「缓存」,不是「源码」
2.2 场景 = 预定义的 Agent 子集
每个工作场景是一个 JSON 文件,定义了这个场景需要加载哪些 Agents,以及对应的 Gateway 配置参数。
三个初始场景:
| 场景 | Agents 数量 | 适用场景 |
|---|---|---|
writing | ~21 | 博客写作、知识库归档 |
social | ~50 | 自媒体运营(小红书、抖音、B站) |
full | ~282 | 完整开发测试 |
切换场景的实质:根据场景定义,从 openclaw.json.full 里抽取出对应 Agent 子集,生成新的 openclaw.json,然后重启 Gateway。
2.3 保持事变链可追溯
每次切换时记录时间戳、源 commit、场景名,写入日志。这样哪天出问题了,可以回溯「是谁在什么时候切换到了什么场景」。
三、switch-scene.py:统一入口的设计
所有场景切换操作通过一个 Python 脚本完成:
python3 switch-scene.py writing # 切换到写作模式
python3 switch-scene.py social # 切换到自媒体模式
python3 switch-scene.py full # 切换到完整模式
python3 switch-scene.py list # 查看当前场景
python3 switch-scene.py status # 查看详细状态switch-scene.py 本身是一个有状态的 CLI——它维护了一个简化的状态文件 .scene_state.json,记录当前场景和时间戳。
核心逻辑大致如下:
1. 读取场景定义文件(scenes/writing.json 或 scenes/social.json 等)
2. 从 openclaw.json.full 中按 agent_id 列表过滤出该场景的 Agent 子集
3. 合并场景级别的 Gateway 配置覆盖(如 --memory-budget 等)
4. 写入 openclaw.json
5. 记录切换日志
6. 提示重启 Gateway关键设计细节之——Agent 完整性检查:
过滤出来的 Agent 子集,每个 Agent 会检查是否能在 openclaw.json.full 中找到完整的定义。如果场景配置里声明了一个不存在的 Agent,脚本会退出并报错,而不是生成一个残缺的 openclaw.json。
# 示例:检查 writing 场景需要的 Agent 是否都在 full 中
python3 switch-scene.py check writing场景定义文件示例(scenes/writing.json):
{
"scene": "writing",
"description": "写作场景:博客创作 + 知识库管理",
"agents": [
"blog-writer",
"kb-writer",
"kb-adapter",
"note-taker",
"outline-editor",
"content-reviewer",
...
],
"gateway_overrides": {
"max_memory_mb": 4096
}
}四、update-full:纯文本级合并
这是整个体系中最大的踩坑点。
4.1 最初的想法(失败)
最初的想法是「每次往 openclaw.json.full 里加 Agent 时,写一个 API 调用来更新 JSON」。但很快发现这条路走不通——JSON 的层次结构太复杂了:
- 有些 Agent 需要嵌套在 parent 下面
- 有些需要更新某个 Agent 的 tool list
- 有些需要修改 model 配置
写一个通用的 JSON 合并 API,光处理各种 edge case 就足够写 500 行代码,而且还容易出 bug。
4.2 最终的方案:纯文本级合并
最后选择了最粗暴也最可靠的方案:纯文本字符串替换。
python3 update-full.py --agent-table agent_table.jsonupdate-full.py 做了什么?
- 读取
agent_table.json(一个扁平化的 Agent 定义表) - 从
openclaw.json.full中找到对应的插入位置(通过正则匹配特定的锚点注释) - 直接做字符串替换
这个方案的好处:
- 零 JSON 解析风险:不反序列化再序列化,不会出现格式重排、顺序打乱等问题
- 保留注释和格式:
openclaw.json.full里我维持了大量注释,纯文本处理不会丢失它们 - 幂等:多次运行相同输入,结果一致
锚点注释的设计:
"agents": {
// ##AGENT-BLOCK-START## blog-writer
"agent:blog-writer": {
"name": "blog-writer",
...
},
// ##AGENT-BLOCK-END## blog-writer
}update-full 通过 ##AGENT-BLOCK-START## 和 ##AGENT-BLOCK-END## 这种锚点来定位替换区域。
4.3 update-full 使用流程
# 1. 创建或修改 agent_table.json(新增一个 Agent 定义)
# 2. 执行合并
python3 update-full.py --agent-table agent_table.json
# 3. 验证没有坏掉整个 JSON
python3 -c "import json; json.load(open('openclaw.json.full')); print('Valid!')"
# 4. 提交到版本管理
git add openclaw.json.full agent_table.json
git commit -m "feat: 新增 xxx Agent"五、升级流程:安全的变更管理
有了场景切换和配置合并,接下来需要一套安全的升级流程。
5.1 日常增删 Agent
5.2 场景配置变更
# 修改 scenes/writing.json 里的 agent list
# 切换验证
python3 switch-scene.py writing --dry-run
# 实际切换
python3 switch-scene.py writing5.3 紧急回滚
# 回退到上一个版本
git checkout HEAD~1 -- openclaw.json.full
python3 switch-scene.py full5.4 GC/backup 场景的独立配置
Gateway 的 openclaw.json 用 switch-scene 来管理,但有些运维任务是独立运行的——比如 GC cron job 和 backup 脚本。这些任务有自己的独立配置,不用 switch-scene 管理,直接在 cron 里写死。
# GC清理(独立运行)
0 3 * * * /opt/openclaw/scripts/gc-cleanup.sh
# 全量备份(独立运行)
0 4 * * * /opt/openclaw/scripts/backup-full.sh六、Blog-Writer Skill 编排器设计
场景切换体系搭好之后,所有工作流都跑在它上面。其中最重要的就是 blog-writer skill 的编排器(orchestrator)。
6.1 编排器 vs. 单一 Agent
为什么不把所有逻辑塞到一个 Agent 里?
答案很简单:单一 Agent 的 token 窗口和 context 长度有限。写一篇文章可能需要 5 个步骤(调研、列大纲、写初稿、改写、审校),如果放在一个 Agent 里,前几步的上下文会污染后几步。
编排器的思路是:把一个大任务拆成多个小 Agent,每个 Agent 负责一个子任务,编排器负责调度和上下文传递。
6.2 三个阶段
blog-writer skill 的编排分为三个阶段:
阶段一:从 openclaw.json.full 生成写作场景 Agent
python3 switch-scene.py writing这个命令从 full 配置中提取约 21 个写作相关 Agent,包括:
research-agent:研究方向调研outline-agent:文章大纲生成content-agent:正文写作(一级)rewrite-agent:改写润色(二级)review-agent:审校(三级)kb-adapter:知识库适配器
阶段二:编排器分发写作任务
编排器接收用户的写作指令(主题、字数、风格),自动编排写作流程——按顺序调用各 Agent,传递中间结果。这个编排器本身也是一个 Agent,定义在 openclaw.json.full 中。
阶段三:归档和销毁
写作完成后,编排器触发 kb-writer Agent 将整篇文章归档到知识库,然后释放资源。
6.3 为什么是三级审核
文章的质量控制设计了三关:
- 一级(content-agent):写初稿,重点在框架完整、论证充分
- 二级(rewrite-agent):改写优化,重点在表达流畅、结构合理
- 三级(review-agent):最终审校,重点在逻辑一致性、事实准确性
三级审核不是过度设计——在写较长的技术文章时,每个 Agent 的注意力集中在自己的职责上,比一个 Agent 写到底的质量要高得多。
七、踩坑记录:那些让我通宵的 Bug
7.1 Bug#1:纯文本合并的锚点偏移
现象:运行 update-full.py 后,openclaw.json.full 的某些区域出现重复或缺失。
原因:锚点注释的定位是「字符串精确匹配」。当修改 agent_table.json 后重新运行合并时,如果老锚点已经被修改过,正则匹配到的位置不对,导致内容插入到错误的地方。
修复:给锚点加了唯一性哈希后缀,确保即使内容变化,锚点也能被精确识别。
// ##AGENT-BLOCK-START## blog-writer##7f3a1c##教训:纯文本合并虽然简单,但锚点的健壮性是命门。不要相信任何「自然语言」级别的锚点——必须用可验证的标识符。
7.2 Bug#2:Gateway 增量加载导致乱序
现象:从 writing 场景切换到 social 场景后,Gateway 正常。但如果回头切回 writing,部分 Agent 的路由出现混乱。
原因:Gateway 的 reload 机制是增量加载——它不会清空已加载的 Agent 列表,而是把新的配置整合到现有状态上。切换场景时如果某些 Agent 在旧场景存在但在新场景不存在,它们的路由信息仍然留在 Gateway 内存中。
修复:switch-scene.py 在切换前发送 --reload --no-incremental 参数给 Gateway,强制全量重载。
# switch-scene.py 关键逻辑
subprocess.run([
"./openclaw", "gateway", "reload",
"--config", "openclaw.json",
"--no-incremental"
])7.3 Bug#3:场景切换导致正在运行的 job 中断
现象:blog-writer 正在写文章,运维人员切换到 social 场景,Gateway 重启,正在生成的文章直接丢失。
原因:switch-scene 没有检查是否有活跃的 Agent 任务。
修复:切换前检查是否有正在运行的任务:
python3 switch-scene.py check-active
# 如果有活跃任务,拒绝切换并提示添加了 --force 参数允许强制切换:
python3 switch-scene.py social --force # 强制切换,中断正在运行的任务7.4 Bug#4:openclaw.json.full 格式意外被 JSON 格式化器破坏
现象:发现 openclaw.json.full 里所有的注释和格式化缩进被清除了。
原因:某个脚本里调用了 json.dumps 来写 openclaw.json.full,而在 Python 中 json.dumps 默认会丢弃所有注释(JSON 标准不支持注释)。一次 git merge 失误触发了一个格式化步骤。
修复:在 .git/hooks/pre-commit 中增加了检查:
#!/bin/bash
# 防止 openclaw.json.full 被 JSON 格式化器破坏
if git diff --cached --name-only | grep -q 'openclaw.json.full'; then
echo "Warning: openclaw.json.full is in staging. Running sanity check..."
python3 -c "
import json
with open('openclaw.json.full') as f:
content = f.read()
# 检查是否有注释被移除
if '//' not in content:
print('ERROR: Comments have been stripped from openclaw.json.full!')
print('Commit blocked. Do NOT use json.dumps on this file.')
exit(1)
"
if [ $? -ne 0 ]; then
exit 1
fi
fi八、当前状态与成果
经过两轮迭代(初始版 + 踩坑修复),场景切换体系已经平稳运行:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 内存占用 | 100GB+(死机) | 4GB(writing)/ 8GB(social)/ 30GB(full) |
| 切换耗时 | N/A(从未成功) | ~8秒(包含 Gateway reload) |
| Agent 管理 | 手动编辑 JSON | agent_table + update-full |
| 可追溯性 | 无 | 每次切换有日志 |
更重要的是,这个体系让整个 OpenClaw 实例变得可维护了。新加一个 Agent 不再需要「祈祷」——知道会有多少人受到影响。日常工作时只加载需要的 Agent,开发测试时才上 full 模式。
九、如果你也要做类似的事
有几个建议送给面临同样问题的朋友:
不要一次性加载不需要的 Agent——这是最直接的性能优化。Gateway 的内存占用 ≈ 所有 Agent 配置树大小的总和。Agent 数量翻倍,内存几乎翻倍。
用纯文本处理 JSON 配置——看似不优雅,但实际上是最可靠的方式。JSON 的序列化/反序列化每次都会改变格式,而字符串替换不会。只要锚点设计得好,纯文本合并比 JSON API 稳定 10 倍。
场景定义文件要版本管理——把
scenes/目录纳入 git 管理,每次场景配置变更都有历史记录。某天切错了场景,能快速回溯「之前的 writing 场景是啥样的」。预留 –dry-run 模式——切换场景前先 dry-run,很多人不敢用
switch-scene.py就是因为怕把生产环境搞坏。dry-run 只生成openclaw.json但不重启 Gateway,方便人工 review。Gateway 配置文件和场景切换体系要解耦——GC、backup、监控这些运维任务不依赖场景管理,它们有自己的独立配置。场景切换只影响 Agent 负载,不影响基础设施。
十、未来的方向
场景切换体系目前还算够用,但有几个可以改进的方向:
热加载——目前切换后需要 reload Gateway(~8秒),更理想的是 Agent 级别的热加载,不需要中断其他 Agent 的工作流
场景自动切换——根据当前用户的任务类型自动检测并切换场景,不需要运维手动执行
内存预算管理——每个场景都有内存预算上限,当某个 Agent 的 task 超出预算时自动杀死,并回退到最小配置
更智能的 update-full——目前基于锚点替换的机制虽然稳定,但 agent_table 和 full 之间的 diff 展示不够友好,可以考虑集成
json-diff工具
如果你也在管理大量 OpenClaw Agents,欢迎交流踩坑经验。这个场景切换体系虽然不完美,但至少让我从「半夜被 OOM 告警叫醒」的状态中解脱出来了。
–全文完–

梦行志
