第三幕 闹钟响了,谁来决定做什么
第三幕 闹钟响了,谁来决定做什么
传统做法:写死的因果
传统智能家居的做法:温度超过 28 度,风扇自动开;低于 26 度,自动关。规则引擎,因果写死在配置里。
这个系统没有这种因果。温度越界,它只做一件事:把"温度越界了"这件事告诉模型,然后问,你想怎么办。
这就是 alarm 系统,整个毕设里我最满意的一部分。LLM 第一次获得了主动根据实际情况调节的能力,而不是只能按规则机械执行。规则只能预见写规则时想到的情况,模型能看现场。
分层:谁负责什么
alarm 分三层,各干各的:
- 判定在内核态。阈值状态机、定时器、触发管线,是 Rust 内核服务,属信任边界内;
- 配置入口在用户态。规则怎么设、闹钟怎么建,是 Rune 插件声明的工具和命令;
- 响应决策是模型。判定层只产出事件,做什么永远由模型决定。
分层的依据是一条红线:无规则引擎。"阈值→事件"是数据面,可以确定性判定;"事件→动作"是决策面,交给 LLM。阈值判定的确定性保住了,模型决策的全局视角也保住了,两边都不越界。
触发管线:一条 notify,一个回合
规则长这样:
{ "device_id": "env_sensor", "metric": "temp", "operator": "gt",
"threshold": 30, "duration_secs": 300 }持续越界 5 分钟才触发,防瞬时抖动。状态机是 正常 → 触发中 → 已报警 → 回落重置:回落之后再越界才会再报,防风暴。
触发后,报警以一条工具消息插进消息流:
{ "entry": "alarm::notify", "result": [{ "kind": "rule", "rule_id": "r1",
"metric": "temp", "current": 31.2, "threshold": 30, "triggered_at": 1723000000000 }] }多规则同时越界,合并成这一条。然后内核发一个中断,外部驱动任务在回合空闲时调 run_turn,主动开一个没有用户消息的回合。模型在这个回合里自己决定:调 iot::control 调节设备,还是只提醒一句。没有强制动作,没有建议动作字段。建议动作等于双真相源,答案得模型自己给。
"主动开回合"这个能力上游没有。我把 Interrupt::Custom、共享中断总线、run_turn 无用户消息回合作为通用能力提交回了 so-lite-agent。这是"一边榨内核一边喂内核"的第一次兑现。
三表定时器
除了阈值报警,还有三类用户定时提醒:
- 倒计时:延迟一段时间,比如"二十分钟后提醒我收衣服";
- 闹钟:指定时刻,可周重复;
- 番茄钟:工作/休息多轮自动循环,支持"跳过"。
几个细节值得说。定时器到期后关闭保留,不删除:卡片显示"已结束",重新启用等于重新调度。暂停是时钟停摆,恢复接着走。"今日停止"只停当天,跨日自动恢复。番茄钟的阶段(工作/休息/第几轮)跨重启持久化,重启不丢进度。
实测:33 度
联调时给 mock 设备注入 ALARM_TEMP=33,配好规则等着。流程是:持续越界 → notify 进时间线 → 主动回合 → 模型自己调 iot::control,把风扇档位调到 high,ack 闭环。然后防风暴生效,不重复报。
倒计时那条链路也一样:到点 → notify → 模型转述提醒。
整个过程里没有任何一行代码写着"温度高就开风扇"。这个因果是模型在回合里自己建立的。规则引擎能做的事它也能做;规则引擎做不了的事,比如"温度高但我刚运动完,只开低档",它还能接着聊下去。
收个尾
alarm 让 Agent 从"被问才答"变成了"有事就说"。这个转变有个前提:上一幕那条只增的时间线。报警消息落在时间线上,和其他事件一样真实、一样可审计。它是世界发生的事,不是系统打的补丁。
下一幕讲怎么证明这套东西真的会干活:评测。
