惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
MyScale Blog
MyScale Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
C
Check Point Blog
宝玉的分享
宝玉的分享
B
Blog RSS Feed
博客园 - 三生石上(FineUI控件)
量子位
Martin Fowler
Martin Fowler
酷 壳 – CoolShell
酷 壳 – CoolShell
Jina AI
Jina AI
IT之家
IT之家
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗
J
Java Code Geeks
The Cloudflare Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
腾讯CDC
P
Proofpoint News Feed
美团技术团队
H
Help Net Security
B
Blog
博客园_首页

BlogFinder

日常漫步 Vol.24 之漫步前山河 - 雅余 周报 #1-聊聊本周的收获 - Edwin's Blog 我的OpenCode必装插件与Skill Write Something 掌中之物未必在掌握之中 · CRIVU PiliNara,一个更顺手的 PiliPlus 分支 「NekoEcho」:做一个必有回响的猫娘主题博客 2026-05 书影音总结 简化博客主题 - 安迪 我第一次发布 npm 包 拾花小记#45:中考前的二三事 – 小改学习志 黛西花园5月游 #18 枇杷又熟了的五月月报 一些奇奇怪怪的需求?word仿方正书版的几个小操作 - Xiobb's Blog 0419 御温泉之旅 修复了一些bug,网站基本上趋于稳定了 - 新锐博客 又回到四十年前 如何定义成功 迷鹿屋2026已重新上线 科技冰火两重天+一周回顾 ${title} 热度退了,我反而用得更深了-咕咚同学 我到底该不该换个域名? 随身WIFI折腾记 - 安迪 博客撰写体验提升——hexo pro插件 为什么不用相机把屏幕上的接关密码拍下来? 国清寺与天台山 – Ouroboros ★★★★☆《挽救计划》——久违的经济上行感 - Davidの3号基地 删除右键“打开方式”里多余选项 第三周刊_No.53|一切都会被支付两次
chat.nvim 定时任务的设计与实现
Eric Wong · 2026-06-27 · via BlogFinder

AI 是被动的

你跟 AI 说”帮我改这段代码”,它秒回。你说”一小时后提醒我开会”,它说”好的”——然后就没有然后了。

这不是某个产品的 bug,而是大多数主流 AI 助手的通病:它们是被动的。 你问它才答,你不问它就沉默。它没有”时间”的概念,没有”未来”的概念,更没有”主动发起”的能力。虽然一些开源或闭源的 Agent 框架已经支持定时任务,但像豆包、DeepSeek 等主流 AI 助手,仍然不具备这一能力。

chat.nvim 新增了定时任务功能,目的就是给 AI 装上”时间感”——让它能在未来的某个时刻,主动找你。

核心思路

要打破 AI 的被动性,核心问题是:如何在未来的某个时刻,让 AI 像收到用户消息一样开始工作?

解法不复杂,就三步:

  1. 记住一个任务(什么时间、做什么)
  2. 到点触发
  3. 把消息注入会话,AI 像收到用户消息一样处理

思路简单,但魔鬼在细节。下面讲每个环节的设计取舍。

时间模型:统一为绝对时间戳

用户可以三种方式描述时间:

  • “1 小时后” —— 相对延迟
  • “明天早上 9 点” —— 绝对时刻
  • “每天晚上 10 点” —— 周期循环

三选其一,互斥。

无论用户怎么描述,内部统一转换为 Unix 时间戳。”1 小时后”变成 当前时间 + 3600,”每天”变成从创建时间起每 86400 秒一次。

为什么?因为调度引擎只需要处理一种时间模型,不需要区分”延迟”和”定时”,不需要为不同类型写不同逻辑。统一抽象,消灭分支。

触发机制:不轮询,用定时器

最直觉的方案是轮询:每隔几秒扫描一遍任务列表,看有没有到点的。

这很浪费。100 个任务里有 99 个还在等待,你却每秒扫一遍。

更好的方案是每个任务一个独立定时器,到点才唤醒,不到点 CPU 零开销。这是事件驱动而非轮询驱动。

Neovim 内置 libuv,uv.new_timer() 就是干这个的,精确到毫秒级,而且不阻塞主线程。

周期任务的防漂移

周期性任务有个坑:如果 Neovim 中途重启了,恢复后下次触发时间怎么算?

直觉做法是”当前时间 + interval”。但这会导致节奏漂移——你设的每天 10 点,重启后变成 10:03,再重启变成 10:07……

正确做法是:基于创建时间和已执行次数算下次触发。

下次触发 = 创建时间 + (已执行次数 + 1) × 间隔

这样无论重启多少次,第 7 次执行永远在创建时间 + 7 × 间隔那个点。节奏锁定在创建那一刻。

24.8 天上限

libuv 的 timer 有个硬限制:最大超时 2³¹-1 毫秒,约 24.8 天。

如果用户设了一个 30 天后的任务,怎么办?

解法是分片。先设一个 24.8 天的 timer,到点后醒来一看——还没到真正的触发时间,重新算剩余延迟,再设一个新 timer。两次接力,覆盖任意时长。

上限设为 30 天,既覆盖合理需求,又不至于让一个 bug 任务永远挂在系统里。

调度与执行解耦

这是整个设计中最关键的决策。

调度引擎只负责一件事:到点,往消息队列里 push 一条消息。它不调用 LLM API,不关心 AI 是否在线,不关心网络状态。

为什么不直接到点调 API?因为职责一旦混合,复杂度就上来了:

  • AI 正在处理另一个请求怎么办?
  • 网络断了怎么办?
  • 会话被删了怎么办?
  • 用户正在打字怎么办?

解耦之后,调度引擎的逻辑极其简单:到点 → push → 完事。剩下的交给消息队列处理。

消息队列:等一个窗口

任务触发后,消息进入队列。队列负责在”合适的时机”把消息送给 AI。

什么是合适的时机?会话空闲的时候。

  • 如果 AI 没在忙,立即发送
  • 如果 AI 正在处理别的请求,排队等,每隔几秒检查一次,等它空闲了再注入

这就像同事给你发了消息,但你在开会——消息不会丢,等你开完会再看。

连续 3 次发送失败才丢弃,防止偶发故障吞消息。

持久化与恢复

Neovim 不是常驻进程,用户随时可能关掉。定时任务必须跨重启存活。

方案很直接:每次创建、取消、触发时,把全部任务写一份 JSON 到磁盘。下次启动时加载。

但有个细节:libuv timer 是内存对象,无法序列化。持久化时必须排除 timer 句柄,恢复时重新创建。

恢复时还有两条不同策略:

  • 过期的一次性任务:直接跳过。已经过了触发时间,没有意义了
  • 过期的周期性任务:必须恢复。周期任务还要继续执行,不能因为重启就断

一次完整流程

你说:"1 小时后提醒我开会"
    │
    ▼
AI 调用工具,计算 trigger_at = 当前时间 + 3600
    │
    ▼
调度引擎创建任务,写入磁盘,arm 一个 3600 秒的定时器
    │
    ···(1 小时后)···
    │
    ▼
定时器触发 → 往会话的消息队列 push "提醒我开会"
    │
    ▼
队列检测会话空闲 → 发送给 AI
    │
    ▼
AI 收到消息,像你亲自说的一样,回复提醒

怎么用

直接用自然语言跟 AI 说就行:

1 小时后提醒我跟进苏北人民医院的项目

AI 会自动计算时间并创建定时任务。

三种模式:

  • 一次性delay_seconds=3600trigger_at=1717200000
  • 周期性interval=86400(每天),可选 repeat_count=7 限制次数
  • 管理action="list" 查看,action="cancel" 取消
特性 说明
持久化 Neovim 重启后依然有效
最大延时 30 天
最小间隔 10 秒
精度 秒级(基于 Unix 时间戳)
依赖 零外部依赖,纯 Neovim + libuv