为什么需要 stream 触发器
前面写过「条件触发器」(at / every / cron),适合按时间点跑任务。但有一类需求它覆盖不了:某个长驻进程什么时候吐出某行日志,你并不知道,也不想每 30 秒去轮询一次。
典型场景:
- 爬虫 / 训练任务跑在后台,一旦打印
ERROR或OutOfMemory,立刻通知你;
- 自建服务启动完成后打印
listening on :8080,自动帮你做健康检查;
- 监控脚本输出
ALERT disk>90%,马上发微信。
这类「进程说了算,时间说了不算」的事,就该用 stream 触发器。
stream 触发器长什么样
在 automations 的 add 动作里,schedule 改成 kind:"stream":
{
"name": "log-error-watcher",
"schedule": {
"kind": "stream",
"command": ["python3", "/opt/myapp/run.py"],
"mode": "match",
"match": "(?i)error|out of memory|traceback"
},
"payload": { "kind": "agentTurn", "message": "检测到进程报错,见触发输出。" }
}
关键字段:
| 字段 | 含义 |
| --- | --- |
| command | 被监督的进程启动命令(argv 数组) |
| mode | line 逐行触发 / match 正则命中才触发 |
| match | mode:"match" 时的正则表达式 |
| cwd | 可选,进程工作目录 |
| maxBatchBytes | 单次最多缓冲的日志字节,防刷屏 |
mode:"line" 是每行都触发一次,mode:"match" 只有命中正则才触发——绝大多数场景用 match,否则日志一多会把自动化跑爆。
实操一:进程报错即推微信
把下面这段存成 add_job.json(或直接用 automations 的 add 调用)。目标:监督一个会偶发报错的服务,命中 error 就把整段上下文交给 agent 总结并推送。
{
"name": "myapi-error-watch",
"schedule": {
"kind": "stream",
"command": ["node", "/srv/myapi/server.js"],
"mode": "match",
"match": "(?i)\\b(error|exception|unhandled)\\b"
},
"payload": {
"kind": "agentTurn",
"message": "后台 myapi 进程日志命中 error 关键字,请读取上下文并简要说明可能原因,必要时给出排查命令。",
"timeoutSeconds": 120
}
}
要点:
- 进程由 OpenClaw 拉起并接管输出,你不用自己
nohup &;
- 正则加
\b边界,避免terror(如单词里含 error)误命中;
- 触发时整个运行上下文都会作为该次运行的输入,agent 能看到前后日志。
实操二:服务启动完成自动探活
很多服务启动慢,你不想傻等。用 match 盯 listening 关键字,命中就跑一次健康检查:
{
"name": "svc-ready-check",
"schedule": {
"kind": "stream",
"command": ["/srv/myapi/start.sh"],
"mode": "match",
"match": "listening on"
},
"trigger": {
"once": true,
"script": "fetch('http://127.0.0.1:8080/health').then(r=>r.ok?{fire:true}:{fire:false}).catch(()=>({fire:false}))"
}
}
这里用了 trigger.once: true,表示命中一次后就自动失效,避免重复触发。配合 trigger.script 还能做二次判断(上面的例子里只有健康检查真通过才 fire)。
几个踩坑点
stream触发器依赖cron.triggers.enabled。如果该开关被关,系统会明确告诉你「trigger watcher disabled」,此时应改用定时轮询或手动方案,而不是硬等。
- 触发器脚本必须只读。
trigger.script里只能读状态、返回{fire,message,state},不能执行写操作;真正干活的动作放在payload里。
- 去重靠 state,不靠记忆。脚本每次返回一个
state对象,下次运行会带着它。想避免同一条日志反复报警,就把已处理的位置 / 哈希存进state,下次比对。
- 进程退出不等于触发。stream 看的是「输出内容」,进程正常退出没打印关键行,触发器不会 fire。要捕获「进程挂了」本身,得在
payload之外另加存活检查。
- 日志量很大时设
maxBatchBytes,否则一次缓冲几 MB 会把单次运行撑爆。
和 cron / every 怎么选
| 需求 | 用谁 |
| --- | --- |
| 每天 03:00 发文章 | cron |
| 每 5 分钟检查一次 | every |
| 进程一报错就通知 | stream |
| 服务启动完成做探活 | stream + once |
| 某时间点一次性提醒 | at |
一句话:时间你能预判就用 cron/every;只有「进程自己知道什么时候」才用 stream。
小结
stream 触发器本质是「把进程的标准输出变成事件源」。它补上了定时任务覆盖不到的盲区——事件驱动而非时间驱动。配置记住三件事:command 交给 OpenClaw 拉起、mode:"match" 配好正则、trigger.once 控制是否只跑一次。剩下的,让日志替你按铃。
扫码关注公众号,获取更多 OpenClaw 实操技巧:
