事件与调度
本章讲 Ameba-Claw「自己动起来」的两条主线。「事件路由与自动化」介绍入站事件如何按声明式规则匹配并触发动作(交给 Agent 推理、直接调用能力、跑脚本、转发消息等);「定时调度」介绍到点或周期性地触发任务,它既能直接执行动作,也能只发一个事件、把后续交给事件路由处理。两者配合,设备就能在无人干预时按规则和时间自主响应。
事件路由与自动化
Ameba-Claw 是**事件驱动**的:无论是 IM 消息、定时触发、还是外设中断,都被包装成统一的**事件(Event)**,投递给事件路由器 claw_event_dispatcher。路由器按一组**规则**匹配事件、执行**动作**——可以直接调能力、跑 Lua、唤醒 Agent、发消息,或再抛新事件。
这套机制让"收到 X 就做 Y"这类自动化无需改固件即可配置,也是理解整个 harness 的钥匙。
事件模型
事件由 claw_event_t 描述,主要字段:
字段 |
说明 |
|---|---|
|
事件唯一编号(缺省自动分配) |
|
|
|
产生该事件的来源能力名 |
|
来源 / 目标通道(如 |
|
聊天、发送者、消息标识 |
|
事件键(供规则精确匹配) |
|
内容类型(如 |
|
消息正文 / JSON 负载(堆分配) |
|
会话策略: |
发布事件
能力实现(尤其是 IM 网关)在合适时机通过 claw_event_publisher.h 的接口发布事件:
claw_event_dispatcher_publish_message(source_cap, channel, chat_id, text, sender_id, message_id)—— 发布一条文本消息事件。claw_event_dispatcher_publish_trigger(source_cap, event_type, event_key, payload_json)—— 发布一条触发类事件。claw_event_dispatcher_publish_event(src)—— 发布一个已完整填好各字段的事件(深拷贝全部字段),可指定会话与回复目标。
路由器在独立任务里异步处理队列中的事件,避免在发布方的回调栈里承担重活。
路由器的工作流程
事件从队列取出后,handle_event 按以下逻辑处理:
斜杠命令拦截:若是来自已注册 IM 通道的
message事件,先尝试当作会话斜杠命令(/new、/list等)处理;命中则直接返回,不进入规则匹配。规则匹配:在锁内逐条与规则集匹配,命中的规则快照到堆上(保留各自的上下文),受**冷却时间**约束(
cooldown_ms内不重复触发)。命中一条consume_on_match=true的规则后停止继续匹配。动作执行:脱锁后,对每条命中的规则逐一执行其动作列表。
兜底路由:若没有任何规则命中,且事件是
message类型、且路由器配置了default_route_messages_to_agent,则把消息默认交给 Agent 推理(并先给来源通道发一句"处理中"的即时 ack)。调度器内建路由:来自
cap_scheduler的 Agent 唤醒事件即使没有任何用户规则匹配,也会直接送进 Agent——一个定时任务即一个意图,无需额外规则(用户规则匹配了则以用户规则为准)。
规则文件
规则持久化在 vfs:/router_rules/router_rules.json,是一个规则对象数组,顺序即匹配顺序。每条规则的字段:
字段 |
说明 |
|---|---|
|
规则唯一标识 |
|
是否启用(缺省 |
|
命中后是否阻止继续向下匹配(缺省 |
|
命中后可选的即时确认消息模板(空 = 默认) |
|
两次触发之间的最小间隔毫秒(0 = 不限) |
|
规则级变量对象,可在模板里用 |
|
匹配条件对象(见下) |
|
动作列表(见下) |
匹配条件 match
字段 |
说明 |
|---|---|
|
匹配事件类型(如 |
|
匹配来源能力名 |
|
匹配来源通道 / 聊天 ID |
|
与事件 |
|
正文子串匹配(空 = 不限) |
|
正文模式匹配。 |
留空的字段视为通配。所有非空字段都命中才算匹配。
动作列表 actions
每个动作对象用 kind 指定类型:
|
用途 |
|---|---|
|
唤醒 Agent 处理该事件(异步);不经过额外配置就能回复来源会话 |
|
不经过 LLM,直接调用某个能力。 |
|
运行一个 Lua 脚本。 |
|
向 IM 通道发送消息。目标通道依次取 |
|
生成一个新事件再交给路由器(可实现 A→B 规则链); |
|
丢弃事件 |
动作还支持这些可选字段:
on_error—— 该动作失败后:continue(默认,继续本规则后续动作)或stop(停止本规则剩余动作)。capture_output—— 把本动作的输出写入@{last.output},供后续动作引用。only_if—— 单条件守卫{ "left": "@{...}", "op": "...", "right": "..." };op支持eq/ne/gt/lt/ge/le/contains/exists。两侧都能解析为数字时按数值比较,否则按字符串比较。守卫不成立则跳过该动作。
模板占位符
动作的 input、ack 等模板里可以用 @{点分路径} 引用运行时数据;未命中的占位符渲染为空(不会漏出 @{...} 原文)。可用的根:
根 |
内容 |
|---|---|
|
事件字段全名,如 |
|
事件字段短别名,如 |
|
规则级变量(规则的 |
|
文本匹配信息; |
|
当前规则 id |
|
上一个 |
对于 rtk_cap / rtk_emit,input 是对象,会在节点级渲染(引号安全);对于 rtk_send,input 是纯字符串模板。
规则示例
前缀命令:收到 /run <脚本> 就跑对应 Lua 脚本,并把命令后的参数传进去:
{
"id": "run_command",
"match": { "event_type": "message", "text": "/run", "text_match_rule": "prefix" },
"actions": [
{
"kind": "rtk_script",
"script": "vfs:/scripts/runner.lua",
"input": { "command": "@{match.remainder}" }
}
]
}
把定时提醒事件转发到 Telegram:
{
"id": "morning_reminder_to_tg",
"match": { "event_type": "schedule", "event_key": "reminder" },
"actions": [
{
"kind": "rtk_send",
"cap": "telegram",
"input": "@{event.text}"
}
]
}
出站通道
rtk_send 动作最终通过出站通道注册表 claw_im_dispatch 把消息发出去:每个 IM 通道在装配时把自己的发送函数按通道名(telegram、feishu、qq、wechat、web、serial)登记进去,路由器按目标通道名找到对应发送函数调用。
用工具动态管理规则
cap_router_mgr 向 LLM(以及 AT 控制台)暴露一组工具,可在运行时增删查改路由规则,改动即时生效并持久化到 router_rules.json:
工具 |
说明 |
|---|---|
|
列出全部规则 |
|
按 id 查单条规则 |
|
新增一条规则 |
|
按 id 更新一条规则 |
|
按 id 删除一条规则 |
|
从磁盘重新加载全部规则 |
也可以在 Web 后台的文件管理里直接编辑 router_rules.json,编辑后调用 router_reload_rules 使其生效。
定时调度
cap_scheduler 是 Ameba-Claw 的**时间触发中心**:到点/周期性地"做一件事"。它可以自己直接执行动作,也可以只发一个事件、把后续处理交给事件路由(见 事件路由与自动化)。任务持久化在 vfs:/scheduler/ 下,重启后自动恢复进度。
一个调度任务 = 触发(何时) + 动作(做什么)
通过 scheduler_add_job 创建或更新(按 id upsert)一个任务,任务由**触发**和**动作**两部分组成。
触发类型(kind)
|
说明 |
|---|---|
|
重复的挂钟时间。 |
|
一次性。 |
|
每 N 秒。设 |
|
系统事件触发。 |
动作类型(action)
|
说明 |
|---|---|
|
设 |
|
设 |
|
设 |
可选字段:max_runs (0 = 不限)、end_at (本地时间,到点后停止)、id (复用以更新已有任务)。
备注
cron 与 once 依赖准确的系统时间**并且**需要已配置时区;若 get_local_time 报"时区未配置",需先让用户设时区(set_timezone)。interval 与 on_event 不依赖时钟,上电即可计时。
调度与执行分离
cap_scheduler 本身不含具体业务逻辑。当动作是 agent 时,调度器发一个 Agent 唤醒事件——事件路由对来自 cap_scheduler 的这类事件有**内建路由**,即使没有任何用户规则也会直接唤醒 Agent(一个定时任务即一个意图)。当动作是 emit 时,调度器发布原始事件,具体行为由你在 router_rules.json 里写的匹配规则决定。
这种"调度与执行分离"的设计,让定时任务能复用事件路由的全部动作能力。
暴露给 LLM 的工具
工具 |
说明 |
|---|---|
|
新增或更新(按 id upsert)一个调度任务 |
|
列出所有任务及运行状态、下次触发时间、运行/错过次数 |
|
按 id 获取单个任务的完整详情 |
|
按 id 删除任务 |
|
启用 / 禁用任务(禁用保留定义、停止触发) |
|
临时暂停 / 恢复(如"跳过今天的闹钟") |
|
立即执行一次任务的动作(用于测试),不改变其调度计划 |
示例
每天早上 8 点在 Telegram 提醒(动作走 Agent,自动回到发起会话):
scheduler_add_job {
"kind": "cron",
"cron_expr": "0 8 * * *",
"action": "agent",
"prompt": "告诉用户该起床了,并顺带播报今天天气"
}
开机即运行一个后台 Lua 脚本(动作走 cap,确定性):
scheduler_add_job {
"kind": "on_event",
"trigger_event": "wifi_connected",
"action": "cap",
"cap_id": "lua_run_async",
"cap_args": { "path": "vfs:/scripts/monitor.lua" }
}
时间同步
调度器依赖系统时间。设备上电后可能先基于不准确的时钟计时;首次 SNTP 同步成功后,系统会对调度器做一次时间校准(rebase),修正各任务的参考时间点,避免重复或遗漏触发。因此 cron / once 任务在时钟同步成功前不会触发。