告别手工作坊:给 AI Agent 集群做一次配置工程化
我在本地跑着一个 5 个 Agent 的集群——主协调器、博客写手、开发助手、智能家居中枢、通讯助手,由 OpenClaw 平台统一调度。一开始,所有配置都直接在运行时目录 ~/.openclaw/ 里手动编辑,仓库里的"源配置"还是 Docker 时代的残留。直到今天我想批量换个模型,才发现这套手工作坊已经完全不可维护了。
问题到底有多糟
先看一下"换模型"这件小事需要改多少地方:
agents.defaults.model.primary— 全局默认- 5 个 agent 各自的
model.primary - hephaestus agent 的
heartbeat.model - cron job
daily-articles里硬编码的payload.model
共 8 处,分布在 2 个 JSON 文件里。漏改一处就会出现不一致——实际上我就是漏了 cron job 那个,导致每日写作任务还在用旧模型。
更大的问题是配置漂移:仓库里的 config/openclaw.json 还写着 3 个已不存在的 agent(Analyzer、Scanner、Writer)和 google/gemini-3-pro 这种旧模型名。仓库完全失去了"源头"的作用。
改造思路:声明式模板 + 自动化部署
核心目标很简单:Git 仓库是唯一的事实来源,一个命令就能把配置安全地同步到运行时。
改造后的工程结构:
Hephaestus/
├── config/
│ ├── openclaw.json # 配置模板(secrets 用占位符)
│ ├── cron-jobs.json # 定时任务声明(无运行时状态)
│ ├── SOUL.md # Agent 人格定义
│ └── HEARTBEAT.md # 每日工作流
├── secrets/
│ └── .env # 所有密钥(gitignored)
├── scripts/
│ ├── deploy.sh # 一键部署
│ └── ctl.sh # 服务管理
配置模板化
把运行时 openclaw.json 变成模板,关键改动是把敏感信息换成环境变量占位符:
{
"channels": {
"discord": {
"enabled": true,
"token": "${DISCORD_BOT_TOKEN}"
}
},
"gateway": {
"auth": {
"mode": "token",
"token": "${OPENCLAW_GATEWAY_TOKEN}"
}
}
}
同时去掉了两类字段:
- 运行时自动生成的:
meta(版本时间戳)、wizard(向导状态)—— 这些由 OpenClaw 自己维护 - 定时任务里的运行时状态:
state.nextRunAtMs、state.lastRunAtMs等
模板只描述"我想要的配置是什么",干净且与运行环境无关。
Cron 任务:去掉 model 硬编码
这是我踩过的坑。之前 daily-articles 这个定时任务在 payload 里硬编码了模型:
{
"payload": {
"kind": "agentTurn",
"message": "读取 SOUL.md...",
"model": "google-gemini-cli/gemini-3-pro-preview"
}
}
这意味着即使我把 agent 的默认模型换了,这个定时任务还是会用旧模型。解决方案很简单——在模板里删掉 model 字段,让它继承 agent 自身的配置。
密钥独立管理
所有被占位符替换掉的值都放在 secrets/.env 里,这个文件通过 .gitignore 排除:
OPENCLAW_GATEWAY_TOKEN=7fd8cd94...
DISCORD_BOT_TOKEN=MTQ3Njc3...
ANTHROPIC_API_KEY=sk-ant-api03-...
部署脚本:合并而非覆盖
deploy.sh 是整个流程的核心。它不是简单地把模板复制过去——那样会丢失运行时状态。它做的是智能合并。
关键流程:
# 1. 加载密钥
source "$REPO_DIR/secrets/.env"
# 2. 占位符替换
GENERATED=$(cat "$TEMPLATE" | sed \
-e "s|\${DISCORD_BOT_TOKEN}|${DISCORD_BOT_TOKEN}|g" \
-e "s|\${OPENCLAW_GATEWAY_TOKEN}|${OPENCLAW_GATEWAY_TOKEN}|g"
)
# 3. 保留运行时的 meta/wizard 字段
FINAL_CONFIG=$(python3 -c "
import sys, json
with open('$RUNTIME_CONFIG') as f:
runtime = json.load(f)
generated = json.loads(sys.stdin.read())
for key in ('meta', 'wizard'):
if key in runtime:
generated[key] = runtime[key]
json.dump(generated, sys.stdout, indent=2, ensure_ascii=False)
" <<< "$GENERATED")
定时任务的合并更精细——从模板读声明性定义,从运行时读 state 和时间戳,按 id 匹配后合并:
FINAL_CRON=$(python3 -c "
import sys, json
with open('$CRON_TEMPLATE') as f:
template = json.load(f)
with open('$RUNTIME_CRON') as f:
runtime = json.load(f)
runtime_state = {}
for job in runtime.get('jobs', []):
if 'state' in job:
runtime_state[job['id']] = job['state']
for job in template.get('jobs', []):
if job['id'] in runtime_state:
job['state'] = runtime_state[job['id']]
json.dump(template, sys.stdout, indent=2, ensure_ascii=False)
")
这样做的好处是:部署不会重置定时任务的计时器。如果一个任务还有 3 小时后运行,部署后它仍然是 3 小时后运行,而不是从零开始计时。
部署的最后两步是重启和健康检查:
# 重启 gateway
launchctl kickstart -k "gui/$(id -u)/ai.openclaw.gateway"
# 等待服务起来
for i in $(seq 1 15); do
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 \
"http://127.0.0.1:18789/__openclaw__/canvas/" 2>/dev/null || echo "000")
if [[ "$HTTP_CODE" != "000" ]]; then
echo "Gateway is up (HTTP $HTTP_CODE)"
break
fi
sleep 2
done
还有两个实用参数:
--dry-run:只输出 diff,不实际写入,适合部署前预检--set-model <model>:批量替换所有 agent 的模型,一个命令搞定
服务管理:统一入口
以前每次操作都要敲冗长的 launchctl 命令。现在 ctl.sh 封装了所有常用操作:
./scripts/ctl.sh status # 一览全局
./scripts/ctl.sh restart # 重启
./scripts/ctl.sh tail # 实时日志
./scripts/ctl.sh cron # 定时任务概览
status 命令会输出一个完整的仪表盘:
=== OpenClaw Status ===
Gateway:
Service: LOADED (pid=36397)
HTTP health: OK (HTTP 200)
Port: 18789
Agents:
main model=minimax-portal/MiniMax-M2.5-highspeed [DEFAULT]
hephaestus model=minimax-portal/MiniMax-M2.5-highspeed
dev model=minimax-portal/MiniMax-M2.5-highspeed
home model=minimax-portal/MiniMax-M2.5-highspeed
comms model=minimax-portal/MiniMax-M2.5-highspeed
Cron Jobs:
✓ daily-articles agent=hephaestus last=ok next=in 5h38min
✓ hacker-news-daily agent=main last=- next=in 9h58min
✓ quant-auto-evolve agent=main last=- next=in 14min
所有 agent 的模型、所有定时任务的下次运行时间,一目了然。
实际效果
改造完成后,今天这个"把所有模型从 Gemini 换成 MiniMax M2.5 Highspeed"的需求,操作变成了:
- 编辑
config/openclaw.json,改模型名 ./scripts/deploy.sh
10 秒搞定,而且所有变更都在 Git 里有记录。
或者更暴力一点:
./scripts/deploy.sh --set-model minimax-portal/MiniMax-M2.5-highspeed
一行命令,所有 agent、所有 heartbeat、所有地方全部替换。
回头看,这次改造的核心就是把软件工程里成熟的 IaC 理念应用到了 AI Agent 管理上。配置模板化、密钥分离、声明式部署、智能合并——这些都不是什么新概念,但当它们组合在一起时,带来的运维体验提升是实实在在的。