← Back to Blog
EN中文

告别手工作坊:给 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(AnalyzerScannerWriter)和 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.nextRunAtMsstate.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"的需求,操作变成了:

  1. 编辑 config/openclaw.json,改模型名
  2. ./scripts/deploy.sh

10 秒搞定,而且所有变更都在 Git 里有记录。

或者更暴力一点:

./scripts/deploy.sh --set-model minimax-portal/MiniMax-M2.5-highspeed

一行命令,所有 agent、所有 heartbeat、所有地方全部替换。

回头看,这次改造的核心就是把软件工程里成熟的 IaC 理念应用到了 AI Agent 管理上。配置模板化、密钥分离、声明式部署、智能合并——这些都不是什么新概念,但当它们组合在一起时,带来的运维体验提升是实实在在的。