Skip to content

feat(scheduler): add durable persistence for cron tasks#120

Merged
yishuiliunian merged 1 commit into
mainfrom
worktree-tranquil-purring-crab
Apr 17, 2026
Merged

feat(scheduler): add durable persistence for cron tasks#120
yishuiliunian merged 1 commit into
mainfrom
worktree-tranquil-purring-crab

Conversation

@yishuiliunian
Copy link
Copy Markdown
Contributor

Summary

  • CronCreate gains a durable: bool parameter; durable jobs persist to ~/.loopal/sessions/<id>/cron.json and rehydrate on session resume.
  • No catch-up semantics: missed one-shots are dropped; recurring tasks clamp last_fired to now to avoid immediate "catch-up" fires. Durable tasks are exempt from the 3-day lifetime cap.
  • Robust against corrupt files (quarantine to .bad-<ts>) and save failures (dirty-flag retry + store_disabled latch if quarantine itself fails).

Changes

  • loopal-scheduler (new persistence.rs / persistence_file.rs / scheduler_persistence.rs / id.rs):
    • DurableStore trait + FileDurableStore with atomic .tmp+rename writes.
    • CronScheduler::with_store(...) + load_persisted() with filter/clamp/truncate/dedup rules.
    • Tick loop batches durable saves once per tick; dirty flag enables retry.
  • loopal-agent-server (agent_setup.rs / session_start.rs): wires FileDurableStore only for root agents (depth=0); load_persisted runs before cron_bridge::spawn on resume.
  • loopal-agent (tools/cron.rs): CronCreate schema + description updated with durable param and at-least-once note.
  • loopal-protocol (cron_snapshot.rs): CronJobSnapshot.durable with #[serde(default)] for backward compat.
  • loopal-agent-server/cron_bridge.rs: CronIdentity + to_snapshot carry durable through to the TUI.

Test plan

  • CI passes (bazel build, clippy, rustfmt, bazel test //... — 52/52 locally)
  • End-to-end resume (cron_resume_e2e_test): real IPC server + seed cron.json → resume → CronsChanged carries rehydrated task with durable=true
  • Scheduler unit coverage: file I/O, load filter, durable add/remove, tick persistence, store_disabled, R1/R5 clamp & lifetime edge cases

CronCreate now supports `durable: true` to persist jobs across session
restarts. Durable tasks are written atomically to
`~/.loopal/sessions/<id>/cron.json` and rehydrated on resume before the
cron bridge starts, so the TUI panel sees the restored set immediately.

Key properties:
- Root agent only — sub-agents keep in-memory schedulers to avoid
  orphan cron files.
- No catch-up: one-shot tasks that missed their window are dropped on
  load; recurring tasks clamp `last_fired` to `now` so the next tick
  fires on schedule instead of immediately.
- Durable tasks bypass the 3-day lifetime cap — persistence would
  otherwise silently shed user data every few days.
- Corrupt files are quarantined as `cron.json.bad-<ts>` rather than
  overwritten; if quarantine itself fails, the scheduler latches into a
  read-only mode so subsequent saves cannot clobber the original.
- At-least-once delivery: a crash between fire and save may re-fire a
  one-shot once on resume — documented in the tool description.

Architecture: new `DurableStore` trait + `FileDurableStore` with atomic
`.tmp`+rename writes. `CronScheduler::with_store` wires the store;
`load_persisted` filters expired / missed / unparsable entries; tick
loop saves once per tick with a dirty flag for retry.
@yishuiliunian yishuiliunian merged commit ff74b3b into main Apr 17, 2026
4 checks passed
@yishuiliunian yishuiliunian deleted the worktree-tranquil-purring-crab branch April 17, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant