Scheduled Event Watcher
Relevant source files
The following files were used as context for generating this wiki page:
The Scheduled Event Watcher is a core system component responsible for monitoring, scheduling, and triggering automated tasks based on filesystem events. It allows the agent to respond to immediate external signals, one-shot reminders, and periodic cron-based schedules by watching a specific directory for JSON event definitions.
Within the pi-digby architecture, the watcher acts as an autonomous loop that bridges the host filesystem and the agent's execution queue. When an event is due, the watcher translates the JSON definition into a system message that wakes the bot, allowing it to perform tasks like summarizing inboxes, posting reminders, or handling webhooks.
Sources: src/events/watcher.ts:241-246, src/main.ts:219-236
Architecture and Components
The system is built around the EventsWatcher interface and its implementation, EventsWatcherImpl. It utilizes a filesystem watcher to track changes in the {workspacePath}/events/ directory.
Event Types
The watcher supports three distinct categories of events:
| Event Type | Purpose | Key Parameters | Persistence |
|---|---|---|---|
| Immediate | Triggers as soon as the file is detected. | channelId, text | Deleted after trigger. |
| One-shot | Triggers once at a specific ISO 8601 timestamp. | at (timestamp with offset) | Deleted after trigger. |
| Periodic | Triggers repeatedly based on a cron expression. | schedule (cron), timezone | Persists until manual deletion. |
Sources: src/events/watcher.ts:11-34, src/agent/prompt.ts:108-124
Component Interaction
The following diagram illustrates how the EventsWatcher interacts with the filesystem and the main application logic.
The EventsWatcher monitors JSON files and dispatches them to specific scheduling mechanisms based on their type.
Sources: src/events/watcher.ts:60-140, src/main.ts:220-236
Event Lifecycle and Logic
Initialization and Scanning
Upon startup, the watcher performs an initial scan of the events directory to register existing files. It filters for .json files and processes each through handleFile.
- Stale Event Protection: Immediate events found during the initial scan that have a modification time (
mtimeMs) earlier than the watcher'sstartTimeare considered stale and deleted without triggering. - Debouncing: To prevent multiple triggers during rapid file writes, a 100ms debounce timer is applied to filesystem change events.
Sources: src/events/watcher.ts:68-84, src/events/watcher.ts:200-205
Execution Flow
When an event triggers, it is formatted into a system-prefixed message string:
[EVENT:filename:type:scheduleInfo] text
This string is passed to a TriggerFn, which attempts to enqueue the event into the specific ChannelQueue for the target channel. If the queue for that channel already contains 5 or more pending items, the event is discarded to prevent resource exhaustion.
The sequence of events from file detection to agent enqueuing, including safety limit checks.
Sources: src/events/watcher.ts:223-233, src/main.ts:228-236
Implementation Details
Data Structures
The core logic relies on internal maps to manage active schedules:
timers: StoresNodeJS.Timeoutreferences for one-shot events.crons: StoresCroninstances for periodic events.knownFiles: ASetused to track currently tracked files and detect deletions.
Sources: src/events/watcher.ts:50-55
Error Handling and Retries
Parsing event files is wrapped in a retry loop. Because external processes might still be writing to a file when the watcher detects it, the system attempts to read and parse the file up to 3 times with exponential backoff (starting at 100ms).
// src/events/watcher.ts:145-156
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
const raw = await readFile(filePath, "utf-8");
event = this.parseEvent(raw, filename);
break;
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
if (attempt < MAX_RETRIES - 1) {
await sleep(RETRY_BASE_MS * 2 ** attempt);
}
}
}
Sources: src/events/watcher.ts:145-156
Agent Integration
When the agent receives an event, it uses a specific event subscriber to translate the session events back to the user interface.
- Silent Completion: For periodic tasks, the agent can respond with
[SILENT]. TheAgentSurfaceandChannelRunnerlogic detects this marker and deletes the status message, preventing channel spam when a routine check finds nothing. - Formatting: Tool calls triggered by events are logged via
log.toolStartandlog.toolEnd, and if debug threading is enabled, detailed arguments are posted to a thread.
Sources: src/agent/setup.ts:316-320, src/agent/events.ts:100-115, src/agent/prompt.ts:153-155
Summary
The Scheduled Event Watcher provides a robust, filesystem-based API for time-based and external triggers. By leveraging Croner for periodic tasks and native fs.watch for reactivity, it allows the pi-digby agent to operate as a proactive assistant rather than a purely reactive chat bot. The system ensures reliability through parsing retries and protects against spam via queue limits and silent response support.