{"slug": "giving-your-neovim-ai-assistant-a-sense-of-time-scheduled-tasks-in-chat-nvim", "title": "Giving Your Neovim AI Assistant a Sense of Time: Scheduled Tasks in chat.nvim", "summary": "A developer has added a scheduled task feature to the Neovim plugin chat.nvim, enabling AI assistants to proactively initiate conversations at future times. The design uses libuv timers with event-driven scheduling, converts all time descriptions to Unix timestamps, and decouples task execution from message delivery to ensure reliability. This addresses a common limitation where AI assistants lack a sense of time and cannot perform delayed actions.", "body_md": "You tell AI \"fix this code,\" it responds instantly. You say \"remind me about the meeting in an hour,\" it says \"sure\" — and then nothing happens.\n\nThis isn't a bug in one product. It's a common limitation across most mainstream AI assistants: **they are passive.** They answer when you ask. They stay silent when you don't. They have no concept of \"time,\" no concept of \"the future,\" and no ability to initiate anything on their own. While some open-source or closed-source Agent frameworks already support scheduled tasks, mainstream assistants like Doubao and DeepSeek still lack this capability.\n\n[chat.nvim](https://github.com/wsdjeg/chat.nvim) now includes a scheduled task feature, designed to give AI a sense of time — letting it proactively reach out to you at a future moment.\n\nTo break AI's passivity, the key question is: **how do we make AI start working at a future moment, as if it just received a user message?**\n\nThe solution is straightforward, three steps:\n\nSimple in concept, but the devil is in the details. Let's walk through the design decisions for each part.\n\nUsers can describe time in three ways:\n\nPick one, they're mutually exclusive.\n\nRegardless of how the user describes it, internally everything converts to a Unix timestamp. \"In 1 hour\" becomes `current_time + 3600`\n\n. \"Every day\" becomes every 86400 seconds starting from creation time.\n\nWhy? Because the scheduling engine only needs to handle one time model. No need to distinguish between \"delay\" and \"schedule,\" no need for different logic branches. Unified abstraction eliminates branching.\n\nThe most intuitive approach is polling: scan the task list every few seconds to check if anything is due.\n\nThat's wasteful. 99 out of 100 tasks are still waiting, yet you scan them every second.\n\nA better approach: one independent timer per task. It wakes up only when due, with zero CPU overhead while waiting. This is event-driven, not poll-driven.\n\nNeovim has libuv built in. `uv.new_timer()`\n\ndoes exactly this — millisecond precision, non-blocking on the main thread.\n\nRecurring tasks have a pitfall: if Neovim restarts midway, how do you calculate the next trigger time after recovery?\n\nThe instinctive approach is `current_time + interval`\n\n. But this causes rhythm drift — you set \"every day at 10 AM,\" after a restart it becomes 10:03, after another restart 10:07...\n\nThe correct approach: calculate the next trigger based on creation time and execution count.\n\n```\nnext_trigger = creation_time + (executed_count + 1) × interval\n```\n\nThis way, no matter how many restarts happen, the 7th execution always lands at `creation_time + 7 × interval`\n\n. The rhythm is locked to the moment of creation.\n\nlibuv timers have a hard limit: maximum timeout of `2³¹-1`\n\nmilliseconds, approximately 24.8 days.\n\nWhat if a user sets a task 30 days out?\n\nThe solution is sharding. First set a 24.8-day timer. When it fires, check — the real trigger time hasn't arrived yet, so recalculate the remaining delay and set a new timer. Two relays cover any duration.\n\nThe 30-day cap covers reasonable use cases while preventing a buggy task from hanging around forever.\n\nThis is the most critical decision in the entire design.\n\nThe scheduling engine does exactly one thing: when the time comes, push a message into the queue. It doesn't call the LLM API, doesn't care if AI is online, doesn't care about network status.\n\nWhy not just call the API directly when the timer fires? Because once responsibilities are mixed, complexity explodes:\n\nAfter decoupling, the scheduling engine's logic is dead simple: fire → push → done. Everything else is handled by the message queue.\n\nAfter a task fires, the message enters a queue. The queue is responsible for delivering the message to AI at \"the right moment.\"\n\nWhat's the right moment? **When the session is idle.**\n\nIt's like a colleague messaging you while you're in a meeting — the message doesn't disappear, you just read it after the meeting.\n\nOnly after 3 consecutive send failures does the message get discarded, preventing transient glitches from swallowing messages.\n\nNeovim isn't a persistent process — users can close it at any time. Scheduled tasks must survive restarts.\n\nThe approach is straightforward: every time a task is created, cancelled, or fired, write all tasks to a JSON file on disk. On next launch, load it back.\n\nOne detail: libuv timers are in-memory objects that can't be serialized. When persisting, the timer handle must be excluded and recreated on recovery.\n\nRecovery has two different strategies:\n\n```\nYou say: \"Remind me about the meeting in 1 hour\"\n    │\n    ▼\nAI calls the tool, calculates trigger_at = current_time + 3600\n    │\n    ▼\nScheduling engine creates the task, writes to disk, arms a 3600s timer\n    │\n    ··· (1 hour later) ···\n    │\n    ▼\nTimer fires → pushes \"Remind me about the meeting\" into the session's message queue\n    │\n    ▼\nQueue detects session is idle → sends to AI\n    │\n    ▼\nAI receives the message, responds with a reminder — just as if you said it yourself\n```\n\nJust tell AI in natural language:\n\nRemind me to follow up on the Subei People's Hospital project in 1 hour\n\nAI will automatically calculate the time and create a scheduled task.\n\nThree modes:\n\n`delay_seconds=3600`\n\nor `trigger_at=1717200000`\n\n`interval=86400`\n\n(daily), optionally `repeat_count=7`\n\nto limit repeats`action=\"list\"`\n\nto view, `action=\"cancel\"`\n\nto cancel| Feature | Details |\n|---|---|\n| Persistence | Survives Neovim restarts |\n| Max delay | 30 days |\n| Min interval | 10 seconds |\n| Precision | Second-level (Unix timestamp based) |\n| Dependencies | Zero external deps — pure Neovim + libuv |\n\nThe scheduled task system in chat.nvim proves that giving AI a sense of time doesn't require a complex architecture. The key design principles:\n\nThe result: AI that can proactively reach out when you need it, not just respond when you ask.", "url": "https://wpnews.pro/news/giving-your-neovim-ai-assistant-a-sense-of-time-scheduled-tasks-in-chat-nvim", "canonical_source": "https://dev.to/wsdjeg/giving-your-neovim-ai-assistant-a-sense-of-time-scheduled-tasks-in-chatnvim-3da9", "published_at": "2026-07-08 13:27:59+00:00", "updated_at": "2026-07-08 13:58:38.780024+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "large-language-models", "ai-agents"], "entities": ["chat.nvim", "Neovim", "libuv", "Doubao", "DeepSeek"], "alternates": {"html": "https://wpnews.pro/news/giving-your-neovim-ai-assistant-a-sense-of-time-scheduled-tasks-in-chat-nvim", "markdown": "https://wpnews.pro/news/giving-your-neovim-ai-assistant-a-sense-of-time-scheduled-tasks-in-chat-nvim.md", "text": "https://wpnews.pro/news/giving-your-neovim-ai-assistant-a-sense-of-time-scheduled-tasks-in-chat-nvim.txt", "jsonld": "https://wpnews.pro/news/giving-your-neovim-ai-assistant-a-sense-of-time-scheduled-tasks-in-chat-nvim.jsonld"}}