schedule-core

August 26, 2026 · View on GitHub

Part of the DSH plugin suite — six Apache-2.0 plugins for DeepSeek Harness.

A persistent, cross-restart scheduler core: SQLite as the archive, lease-claim as the coordination, wall-clock discipline as the law. Zero framework dependencies — execution side effects are host-injected. Every claim carries an experiment number and a control group.

中文版见 README.zh-CN.md

license

Why this exists

In-process reminders (setTimeout) and session-local scheduling have two fatal problems: the process dies, the schedule dies (no persistence), and the session dies, the reminder dies (no cross-session claiming). This core stores schedule state in a SQLite archive and uses a single conditional UPDATE for multi-process lease preemption — no Redis, no message queue; SQLite is the arbiter.

CapabilityMechanismExperiment
Survives restartsSQLite three tables + append-only event logUnit / real-machine closed loop
Multi-consumer mutual exclusionLease claiming (claimed_by/lease_until single conditional UPDATE, changes=1 wins)EXP-3 I5
Due dispatchC-hybrid driver (single timer for the nearest due + fallback polling)EXP-2
Clock disciplineStrict RFC3339 validation / IANA time zones / DST rollover detection / rollback never fires early / jump-forward goes straight overdueEXP-5
Cold startLease-release fast path + due-flip unified into sweepEXP-1
VerificationWall-clock disorder fuzz + implementation×model differentialEXP-3 / EXP-4

State model

scheduled ──(now >= scheduledAt)──▶ overdue ──(claimed + dispatched ok)──▶ dispatched
    │                                  │
    └──(cancel)──▶ cancelled ◀──(cancel)┘
  • dispatched / cancelled are terminal states.
  • Crash window: dispatched but the event never landed on disk → the lease expires (default 60s) and is re-claimed → at-least-once (no exactly-once promise — the same honest statement as the official one).
  • An every record only tracks the latest occurrence (missed intervals are not enumerated); the anchor advances in alignment.

Quick start

import { ScheduleRegistry } from 'schedule-core'

const reg = new ScheduleRegistry({
  dbPath: './data/schedules.db',
  now: Date.now,                          // injectable clock (key design for testing/differential verification)
  executor: {
    executeJob: (rec) => { /* host executes the task */ return 'done' },
    deliverReminder: (rec) => { /* host delivers the reminder */ return 'done' },
  },
})

const rec = reg.create({ prompt: 'backup', rule: 'after', afterSeconds: 60, action: 'job',
  jobSpec: { kind: 'pwsh', command: 'Backup.ps1', label: 'nightly' } })
reg.list()          // active records
reg.delete(rec.id)  // { deleted: true }
reg.dispose()

When the executor returns 'retry' the record stays overdue and retries next cycle — natural backoff while the host is not ready.

Honest boundaries

  • at-least-once: a crash window may double-dispatch (by design, not a defect).
  • every tracks only the latest: missed intervals are not replayed.
  • No Cron/calendar rules: every_seconds ≥ 300 (aligned with the official lower bound).
  • Single-machine scheduling: cross-machine scheduling needs shared storage + distributed locks — this core does neither, and does not claim them.
  • Offline applicability: architecturally no network dependency (local SQLite + local timer); multi-day offline runs are not measured, not claimed.
  • Execution side effects (job/remind) are the host's business; this core only guarantees when, by whom, exactly one claim.

Development

npm run build   # tsc → lib/
npm test        # 37 unit + 200-seed fuzz + 644 differential assertions

Requires: Node ≥ 22.6 (node:sqlite, measured on 25.8).

License

Apache-2.0