# How the Zakirullin Files Bot Handles Scheduled Tasks: Server-Side Worker Architecture Explained

> Discover how the Zakirullin Files bot handles scheduled tasks with its server-side worker. Learn about recurring job rescheduling and nightly cleanup for efficient operation.

- Repository: [Artem Zakirullin/files.md](https://github.com/zakirullin/files.md)
- Tags: architecture
- Published: 2026-05-21

---

**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`](https://github.com/zakirullin/files.md/blob/main/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`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go).

### Moving Files and Preventing Duplicates

When **MoveDueTasks** identifies a due task, it performs several atomic operations:

1. Copies the referenced markdown file into the user's inbox using `appendToChat`
2. Removes the task from [`Done.md`](https://github.com/zakirullin/files.md/blob/main/Done.md) or [`Later.md`](https://github.com/zakirullin/files.md/blob/main/Later.md) to prevent duplicates
3. Logs the operation for audit trails
4. 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`](https://github.com/zakirullin/files.md/blob/main/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`](https://github.com/zakirullin/files.md/blob/main/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`](https://github.com/zakirullin/files.md/blob/main/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`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go). The **AddToSchedule** method (lines 6-28) handles schedule creation and updates:

```go
// 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:

```go
// 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:

```go
// 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`](https://github.com/zakirullin/files.md/blob/main/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.go`](https://github.com/zakirullin/files.md/blob/main/server/worker.go) scans user configurations and moves due tasks from [`Done.md`](https://github.com/zakirullin/files.md/blob/main/Done.md) or [`Later.md`](https://github.com/zakirullin/files.md/blob/main/Later.md) into 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`](https://github.com/zakirullin/files.md/blob/main/Done.md). Additionally, when **MoveDueTasks** processes a scheduled item, it explicitly removes any matching entries from [`Done.md`](https://github.com/zakirullin/files.md/blob/main/Done.md) or [`Later.md`](https://github.com/zakirullin/files.md/blob/main/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`](https://github.com/zakirullin/files.md/blob/main/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.