4.4 KiB
ADR-0002: SF Schedule System is Pull-Based, Not Daemon-Based
Date: 2026-05-05
Status: Accepted
Deciders: SF core team (M010)
Related: M010 S01 (schedule store), M010 S02 (schedule CLI), M010 S03 (milestone YAML integration), M010 S05 (this slice)
Context
The SF schedule system requires time-bound reminders that surface at a future date. Several design options were considered:
- Daemon-based (cron/launchd) — A background process fires items at their due time using the OS scheduler.
- Daemon-based (in-process timer) — SF itself runs as a long-lived process with in-process timers.
- Pull-based (on-demand query) — Items are stored durably and queried at integration points (launch, auto-mode boundaries, explicit CLI query).
Option 1 was explicitly ruled out early: platform-specific (cron on Unix, launchd on macOS, Task Scheduler on Windows), requires daemon installation, and cannot fire items when SF is not running.
Option 2 was ruled out because SF is designed to be a session-based tool — agents run in fresh contexts per unit, state does not accumulate across sessions, and there is no persistent long-lived process in the happy path.
Option 3 (pull-based) is what we adopted.
Decision
The SF schedule system is pull-based:
- Schedule entries are stored as append-only JSONL in
.sf/schedule.jsonl(project) or~/.sf/schedule.jsonl(global). - There is no background daemon or timer process.
- Entries are queried ("pulled") at defined integration points:
- Launch —
loader.tscallsfindDue()and prints a banner if items are overdue - Auto-mode boundaries —
sf headless querypopulates aschedulefield withdueandupcomingentries - CLI —
sf schedule list --duefor explicit human query - TUI status overlay — displays due/upcoming schedule entries in the dashboard
- Launch —
Consequences
Positive
- Portable — works identically on Linux, macOS, and Windows without platform-specific code
- Simple — no process management, no signal handlers, no daemon lifecycle
- Auditable — the JSONL file is a complete, append-only audit trail of all schedule operations
- Resilient — no fire-and-forget timer that might miss if the process is restarted
- Stateless — fits SF's session model: fresh context per unit, no in-memory state
Negative / Explicitly Deferred
- No fire-at-exact-time — items are not delivered at their exact
due_at; they surface at the next pull query. If an item is due at 3 AM and the user opens SF at 9 AM, the item appears as overdue. - No background notification — SF cannot send a system notification when an item becomes due unless SF is open and the user is interacting with it.
- No recurring fire precision —
kind: recurringentries are stored but the recurring fire mechanism is deferred to a future iteration.
These limitations are accepted trade-offs for the portability and simplicity benefits. A future iteration could add an optional lightweight notification helper (e.g. a separate binary that reads the schedule and posts system notifications) without changing the core design.
Implementation Notes
schedule-store.js— append-only JSONL store withfindDue()andfindUpcoming()queriesloader.ts— callsfindDue()on both scopes at startup; prints banner if any items are dueheadless-query.ts— populatesschedule: { due, upcoming }inQuerySnapshotsf scheduleCLI — add, list, done, cancel, snooze, run subcommandssf_plan_milestoneYAML — supportsschedule[]array withinandon_completeduration fields
Alternatives Considered
In-Process Timer (Rejected)
A long-lived SF process could maintain a timer queue and fire items at their due time. Rejected because it conflicts with SF's session architecture — each unit runs in isolation with no shared timer state across dispatch cycles.
External Cron Wrapper (Rejected)
A sf-schedule-daemon sidecar process managed by the user. Rejected because it adds an installation and运维 burden that conflicts with the "install and use immediately" experience goal.
Third-Party Scheduling Service (Rejected)
Using a hosted service (e.g. cron-job.org, AWS EventBridge) to fire webhook calls. Rejected because it introduces an external dependency and network requirement that does not fit SF's self-contained model.