Scheduling Module
Overview
The Scheduling module in @ever-works/agent manages automated, recurring updates for works. It provides cadence-based scheduling (hourly, daily, weekly, monthly), integrates with the subscription/billing system for plan enforcement, handles failure recovery with configurable retry limits, and implements drift-correction to maintain consistent execution timing.
The scheduler is designed to work with the Trigger.dev background job system -- it manages schedule state and entitlements while the actual execution dispatch happens through the WorkGenerationDispatcher interface.
Module Structure
packages/agent/src/
services/
work-schedule.service.ts # Core scheduling service (~571 lines)
work.module.ts # Registered as part of WorkModule
entities/
work.entity.ts # Schedule fields on Work entity
work-schedule.entity.ts # WorkSchedule entity (1:1 with Work)
types.ts # WorkScheduleCadence, WorkScheduleStatus
subscriptions/
subscription.service.ts # Plan enforcement for scheduling
tasks/
work-generation-dispatcher.ts # Dispatcher interface
work-generation.types.ts # Dispatch payload types
Key Classes and Services
WorkScheduleService
The core service (~571 lines) managing all scheduling operations:
Schedule management:
getSchedule(workId)-- retrieve the current schedule configuration and statusupdateSchedule(work, user, cadence, options)-- enable or update the schedule. Validates entitlements, calculates next run time, and persists the schedule.cancelSchedule(work, user)-- disable scheduling entirely. Clears the cadence, next run time, and resets status.pauseSchedule(work, user, reason?)-- temporarily pause without losing configuration. The cadence is preserved but no runs will be dispatched.
Run lifecycle:
markRunDispatched(workId)-- mark that a scheduled run has been sent to the background workermarkRunCompleted(workId)-- mark successful completion. Calculates the next run time using drift correction.markRunFailed(workId, error)-- record a failure. Increments the failure counter and potentially auto-pauses the schedule.
Recovery:
recoverStuckSchedules()-- find schedules that have been in "dispatched" status for too long and reset them for retry. This handles cases where the background worker crashed or timed out.validateRunEntitlement(work, user)-- check that the user's subscription plan allows the scheduled run to proceed.
Cadence System
Four cadence levels are supported:
| Cadence | Interval | Typical Use Case |
|---|---|---|
HOURLY | Every 60 minutes | High-frequency monitoring works |
DAILY | Every 24 hours | Standard content works |
WEEKLY | Every 7 days | Slower-moving curated lists |
MONTHLY | Every 30 days | Archival or low-update works |
Drift Correction
The scheduler calculates the next run time based on the intended execution time, not the actual completion time. This prevents gradual time drift:
Intended: 08:00 daily
Actual completion: 08:07
Next run: 08:00 tomorrow (not 08:07 tomorrow)
If the calculated next time is already in the past (e.g., the run took longer than the interval), the scheduler skips forward to the next valid slot.
Failure Handling
The service implements configurable failure limits:
maxFailureBeforePause-- configurable per schedule (1-10, default: 3)- On each failure, the failure counter increments
- When the counter reaches the limit, the schedule is automatically paused
- A 15-minute retry delay is added after each failure before the next attempt
- Failures are recorded with error messages for diagnostics
Billing Integration
Two billing modes are supported:
| Mode | Behavior |
|---|---|
SUBSCRIPTION | Schedule runs are included in the subscription plan. Cadence options are gated by plan tier. |
USAGE | Pay-per-use billing. Each run is metered and billed separately. |
Plan enforcement validates:
- Allowed cadence levels per subscription tier
- Maximum number of works with active schedules
- Per-run entitlement check before dispatch
API Reference
WorkScheduleService
// Schedule management
getSchedule(workId: string): Promise<ScheduleInfo>
updateSchedule(
work: Work,
user: User,
cadence: WorkScheduleCadence,
options?: {
maxFailureBeforePause?: number; // 1-10, default: 3
startAt?: Date; // When to begin (default: now + interval)
}
): Promise<ScheduleInfo>
cancelSchedule(work: Work, user: User): Promise<void>
pauseSchedule(work: Work, user: User, reason?: string): Promise<void>
// Run lifecycle
markRunDispatched(workId: string): Promise<void>
markRunCompleted(workId: string): Promise<void>
markRunFailed(workId: string, error: string): Promise<void>
// Recovery and validation
recoverStuckSchedules(): Promise<number> // Returns count of recovered schedules
validateRunEntitlement(work: Work, user: User): Promise<{
allowed: boolean;
reason?: string;
}>
ScheduleInfo
interface ScheduleInfo {
enabled: boolean;
cadence: WorkScheduleCadence | null;
status: WorkScheduleStatus | null;
nextRunAt: Date | null;
lastRunAt?: Date;
lastError?: string;
failureCount: number;
maxFailureBeforePause: number;
}
WorkScheduleCadence
type WorkScheduleCadence = 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY';