How the Zakirullin Files Bot Handles Scheduled Tasks: Server-Side Worker Architecture Explained
The Zakirullin Files bot processes scheduled tasks through a server-side worker package that detects due items, reschedules recurring jobs using cron expressions, and cleans up completed checklist items nightly.
The zakirullin/files.md repository implements a sophisticated task scheduling system entirely within its Go codebase. Unlike bots that rely on external cron daemons, this architecture uses an in-process worker to orchestrate task movement and maintenance. This article examines the exact mechanisms used to move due tasks, handle recurring schedules, and maintain clean markdown files.
How the Worker Detects and Moves Due Tasks
The core scheduling logic resides in server/worker.go, where the worker package coordinates all timed operations. The system operates through three distinct phases that ensure tasks surface at the correct moment without creating duplicates.
Scanning User Configurations for Expired Timestamps
The MoveDueTasks function serves as the entry point for scheduled task processing. It iterates through every user's configuration file, checking the ScheduledAt timestamp against the current time. When it finds tasks where the scheduled time has passed, it triggers the movement workflow.
Each user maintains a JSON configuration file at userconfig/config that stores an array of Schedule structs containing Filename, ScheduledAt, and Cron fields. The configuration is accessed via userconfig.Config defined in server/userconfig/userconfig.go.
Moving Files and Preventing Duplicates
When MoveDueTasks identifies a due task, it performs several atomic operations:
- Copies the referenced markdown file into the user's inbox using
appendToChat - Removes the task from
Done.mdorLater.mdto prevent duplicates - Logs the operation for audit trails
- Either deletes the schedule (for one-off tasks) or reschedules it using a cron expression
The function creates a temporary Bot instance via NewBot with the user's filesystem, database, and configuration to handle the insertion. This ensures the task appears in the correct context within the user's workspace.
Rescheduling Recurring Tasks with Cron Expressions
The bot handles recurring tasks through the robfig/cron library (github.com/robfig/cron/v3), parsing standard cron expressions to calculate future execution times.
Calculating Next Run Time with NextExcludeToday
The NextExcludeToday function (located at lines 37-48 in server/worker.go) prevents tasks from re-triggering immediately after execution. When a schedule contains a non-empty Cron field, this function:
- Parses the cron expression using the robfig/cron parser
- Calculates the next occurrence after the end of the current day
- Returns a timestamp ensuring the task won't fire again today
This design choice ensures that a task completed on Monday with a weekly cron expression won't immediately trigger again if the worker runs multiple times per day.
Nightly Cleanup of Completed Checklist Items
At approximately 23:50 user-local time, the RemoveCompletedChecklistItems function (lines 85-124 in server/worker.go) executes maintenance operations. This process:
- Walks through inbox, chat, and later files
- Strips checked-off checklist items from markdown files
- Archives completed items to
Done.md - Records operations in the journal
This nightly cleanup prevents completed tasks from cluttering active files while maintaining a historical record in the archive.
Data Flow and Configuration Storage
The scheduling system relies on persistent JSON configuration managed through server/userconfig/userconfig.go. The AddToSchedule method (lines 6-28) handles schedule creation and updates:
// Example: adding a one-off task to a user's schedule
cfg := userconfig.NewConfig(userFS, userID, "config.json")
err := cfg.AddToSchedule("meeting.md", time.Now().Add(2*time.Hour).Unix(), "")
if err != nil {
log.Fatalf("cannot schedule task: %v", err)
}
For recurring tasks, provide a cron expression and use NextExcludeToday to set the initial timestamp:
// Example: creating a recurring task with a cron expression
cronExpr := "0 9 * * 1" // every Monday at 09:00
err := cfg.AddToSchedule("weekly-report.md", server.NextExcludeToday(cronExpr), cronExpr)
The worker execution is typically invoked from the main scheduler loop:
// Example: moving all due tasks for every user
if err := server.MoveDueTasks(
storagePath, // e.g. "/var/filesmd"
"config.json", // user config filename
afero.NewOsFs(), // filesystem backend
telegramAPI, // implements server.Chat interface
); err != nil {
log.Printf("schedule worker error: %v", err)
}
Generating Human-Readable Schedule Reports
The ScheduleReport function (lines 50-73 in server/worker.go) formats upcoming tasks into intuitive groupings: Today, Tomorrow, specific weekdays, or full dates. This utility enables the bot to present users with clear summaries of pending scheduled items without exposing raw timestamps or cron syntax.
Summary
- MoveDueTasks in
server/worker.goscans user configurations and moves due tasks fromDone.mdorLater.mdinto the inbox while preventing duplicates. - NextExcludeToday leverages the robfig/cron library to calculate the next execution time after the current day, preventing immediate re-triggering of recurring tasks.
- RemoveCompletedChecklistItems runs nightly at 23:50 to archive completed checklist items and maintain clean markdown files.
- Configuration persists in JSON format via
userconfig.Config, with AddToSchedule handling atomic updates to the schedule array. - The architecture operates entirely server-side without external cron dependencies, using Go's in-process scheduling capabilities.
Frequently Asked Questions
How does the bot determine when to move a scheduled task?
The bot evaluates the ScheduledAt Unix timestamp stored in each user's JSON configuration. When MoveDueTasks finds a timestamp that has passed relative to the current time, it immediately copies the referenced file to the inbox and updates the schedule status.
What happens to recurring tasks after they execute?
After moving a recurring task to the inbox, the worker checks for a non-empty Cron field. If present, it calls NextExcludeToday to calculate the next execution time after today, updates the ScheduledAt field, and preserves the entry. If no cron expression exists, the worker removes the schedule entirely.
How does the bot prevent completed tasks from reappearing?
The RemoveCompletedChecklistItems function runs nightly and strips checked-off items from all active files (inbox, chat, later), moving them to Done.md. Additionally, when MoveDueTasks processes a scheduled item, it explicitly removes any matching entries from Done.md or Later.md before inserting the fresh copy.
Where does the bot store scheduling configuration?
Each user's schedule array persists in userconfig/config as JSON, managed by the userconfig package in server/userconfig/userconfig.go. The AddToSchedule method handles atomic updates, either modifying existing entries or appending new ones based on the filename and user ID.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →