为什么需要条件触发器

前面几篇讲过 automationscron(定点跑)和 every(间隔跑)。但这两类都是盲跑:不管有没有事,到点就执行一遍,AI 读上下文、调工具,token 照烧。

真实场景里,很多任务其实是"等某个条件成立才该动":

  • 某个后台脚本挂了才需要报警,正常时不该刷屏;
  • 某个文件/端口出现变化才需要同步,没变化不用跑;
  • 监控一个进程日志,只有出现 ERROR 才触发处理。

automations 里的 trigger 字段就是干这个的——它是一个轻量只读的条件看门狗,每轮只跑一个小脚本判断是否"起火",起火才真正调用你的大模型任务。正常时几乎零成本。

触发器的工作机制

automations 里给一个 cron / every 任务加上 trigger

{
  "schedule": { "kind": "every", "everyMs": 30000 },
  "trigger": { "script": "if down: fire; else save state only", "once": false }
}

机制要点(基于 OpenClaw 真实定义):

  • trigger 只跑脚本,不调模型:每轮执行 trigger.script,这是只读检查,成本极低。
  • 返回值决定要不要跑 payload:脚本输出 json({fire:true, message, state}) 时,fire:true 才会执行真正的 payload(你的 agentTurn / 通知)。
  • state 由脚本自己管理:用 trigger.state 读写(一次只读快照),靠它做去重——比如"已经告警过就不要重复告警"。
  • once:true 触发一次后自动禁用:适合"只处理一次"的一次性动作。
  • 配额限制:trigger 每轮上限约 30s / 5 次工具调用 / 16KB 状态,足够做条件判断,不够做重活。

关键区别:cron.triggers.enabled=false 时触发器整体失效,别在自己关了之后困惑"为什么没触发"。

实战一:监控后台脚本是否还活着

最经典用法——前面《自动化会悄悄死掉》那篇讲过体检,这里用 trigger 做成自动报警版

建一个每 30 秒检查一次、只在脚本挂掉时通知的任务:

{
  "name": "bg-script-watchdog",
  "schedule": { "kind": "every", "everyMs": 30000 },
  "trigger": {
    "script": "import os,json\npid=os.path.exists('/tmp/myscript.pid')\nrun=os.system('pgrep -f myscript.py >/dev/null')==0\nstate={'alerted':False}\nfire=False;msg=''\nif not run and not state.get('alerted'):\n    fire=True;msg='myscript.py 已停止';state['alerted']=True\nif run and state.get('alerted'):\n    state['alerted']=False\nprint(json.dumps({'fire':fire,'message':msg,'state':state}))"
  },
  "payload": {
    "kind": "agentTurn",
    "message": "后台脚本 myscript.py 停止了,请发微信通知 Z 并给出重启命令。"
  }
}

逻辑拆解:

  • 脚本用 pgrep -f myscript.py 判断进程在不在;
  • 第一次发现不在 → fire:true 并标记 alerted,触发 AI 去发通知;
  • 之后一直不在,因为 alerted 已是 true不会重复刷告警
  • 进程复活 → 清掉 alerted,下次再挂能重新报。

这就是 trigger 比纯 every 强的地方:自带去重状态,省 token 又不漏报。

实战二:端口/服务探活,挂了才处理

同理,监控本地服务端口,比如 OpenClaw 网关或你的业务端口:

{
  "name": "port-check",
  "schedule": { "kind": "every", "everyMs": 60000 },
  "trigger": {
    "script": "import socket,json\nstate={'down':False}\nhost,port='127.0.0.1',8090\ns=socket.socket();s.settimeout(2)\ntry:\n    s.connect((host,port));ok=True\nexcept: ok=False\nfinally: s.close()\nfire=False;msg=''\nif not ok and not state.get('down'):\n    fire=True;msg=f'{port} 端口无响应';state['down']=True\nif ok and state.get('down'): state['down']=False\nprint(json.dumps({'fire':fire,'message':msg,'state':state}))"
  },
  "payload": { "kind": "agentTurn", "message": "8090 端口无响应,检查网关状态并重启。" }
}

trigger 与 cron、every 怎么选

场景 用 cron 用 every 用 trigger
每天 03:00 发一篇文章 ✅ 定点
每 10 分钟拉一次热搜 ✅ 间隔
进程挂了才报警 ✅ 条件
日志出现 ERROR 才处理 ✅ 条件
文件出现才同步 ✅ 条件

经验法则:"没变化也要跑"用 cron/every;"只有变化才该动"用 trigger。后者能同时压住 token 成本和告警噪音。

几个踩坑提醒

  • trigger.script 必须是纯只读判断:动作(发通知、重启)放到 payload 里,靠 fire:true 触发,不要在 trigger 脚本里直接执行副作用。
  • state 去重别漏:没做 alerted/down 标记,脚本每次都会 fire,等于退化成盲跑还更烧。
  • once:true 用一次就关:适合"首次检测到就做一次"的一次性处理,别用在需要反复监控的场景。
  • 别忘了配额:trigger 每轮 30s / 5 工具调用 / 16KB,复杂检查要拆简单点。
  • 确认 triggers 没被关cron.triggers.enabled 若为 false,触发器根本不跑。

小结

automationstrigger 把"定时轮询"升级成"条件驱动":平时只跑一个轻量脚本看门,真出事了才唤醒大模型干活。既省 token,又避免"一切正常也天天刷你一脸通知"。配合前面讲的 cron 发稿、every 拉热搜,你的 OpenClaw 自动化工具有了完整的"定点 / 间隔 / 条件"三件套。


扫码关注公众号,获取更多 OpenClaw 实操技巧:

公众号二维码