目录

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

update-full.py 做了什么?

  1. 读取 agent_table.json(一个扁平化的 Agent 定义表)
  2. openclaw.json.full 中找到对应的插入位置(通过正则匹配特定的锚点注释)
  3. 直接做字符串替换

这个方案的好处:

  • 零 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

graph TD A[修改 agent_table.json] --> B[运行 update-full.py] B --> C[验证 JSON 合法性] C --> D[Git commit] D --> E[switch-scene.py 切换到目标场景] E --> F[重启 Gateway]

5.2 场景配置变更

# 修改 scenes/writing.json 里的 agent list
# 切换验证
python3 switch-scene.py writing --dry-run

# 实际切换
python3 switch-scene.py writing

5.3 紧急回滚

# 回退到上一个版本
git checkout HEAD~1 -- openclaw.json.full
python3 switch-scene.py full

5.4 GC/backup 场景的独立配置

Gateway 的 openclaw.jsonswitch-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 为什么是三级审核

文章的质量控制设计了三关:

  1. 一级(content-agent):写初稿,重点在框架完整、论证充分
  2. 二级(rewrite-agent):改写优化,重点在表达流畅、结构合理
  3. 三级(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 管理手动编辑 JSONagent_table + update-full
可追溯性每次切换有日志

更重要的是,这个体系让整个 OpenClaw 实例变得可维护了。新加一个 Agent 不再需要「祈祷」——知道会有多少人受到影响。日常工作时只加载需要的 Agent,开发测试时才上 full 模式。

九、如果你也要做类似的事

有几个建议送给面临同样问题的朋友:

  1. 不要一次性加载不需要的 Agent——这是最直接的性能优化。Gateway 的内存占用 ≈ 所有 Agent 配置树大小的总和。Agent 数量翻倍,内存几乎翻倍。

  2. 用纯文本处理 JSON 配置——看似不优雅,但实际上是最可靠的方式。JSON 的序列化/反序列化每次都会改变格式,而字符串替换不会。只要锚点设计得好,纯文本合并比 JSON API 稳定 10 倍。

  3. 场景定义文件要版本管理——把 scenes/ 目录纳入 git 管理,每次场景配置变更都有历史记录。某天切错了场景,能快速回溯「之前的 writing 场景是啥样的」。

  4. 预留 –dry-run 模式——切换场景前先 dry-run,很多人不敢用 switch-scene.py 就是因为怕把生产环境搞坏。dry-run 只生成 openclaw.json 但不重启 Gateway,方便人工 review。

  5. Gateway 配置文件和场景切换体系要解耦——GC、backup、监控这些运维任务不依赖场景管理,它们有自己的独立配置。场景切换只影响 Agent 负载,不影响基础设施。

十、未来的方向

场景切换体系目前还算够用,但有几个可以改进的方向:

  1. 热加载——目前切换后需要 reload Gateway(~8秒),更理想的是 Agent 级别的热加载,不需要中断其他 Agent 的工作流

  2. 场景自动切换——根据当前用户的任务类型自动检测并切换场景,不需要运维手动执行

  3. 内存预算管理——每个场景都有内存预算上限,当某个 Agent 的 task 超出预算时自动杀死,并回退到最小配置

  4. 更智能的 update-full——目前基于锚点替换的机制虽然稳定,但 agent_table 和 full 之间的 diff 展示不够友好,可以考虑集成 json-diff 工具


如果你也在管理大量 OpenClaw Agents,欢迎交流踩坑经验。这个场景切换体系虽然不完美,但至少让我从「半夜被 OOM 告警叫醒」的状态中解脱出来了。


–全文完–

感谢阅读
若你有故事想讲、有困惑想聊、或是想找个人说说心里话,甚至只是吐槽发泄一下情绪,都欢迎来找我聊聊:   《内容已折叠,点击展开》

希望我写的每一个字,成为我自己和某个人活下去、拼下去的力量。                     《内容已折叠,点击展开》

“技术终归是工具,而我们一次次认真把问题理顺,守住的其实不只是页面样式和代码输出,还有那一点不愿被混乱打败的心气,是每一个深夜仍愿点灯前行的人。”

转载请注明来自https://oklife.me。

文尾配图水墨画图片