scheduler: file-driven scheduled tasks - #103
Merged
Merged
Conversation
…l mount, auto permission mode
Wires scheduler_loop (Task 11) into _run_with_pebble, gated by settings.scheduler_enabled, mirroring the existing freeshard gating. Adds _scheduler_state_path() for the state file location. Scheduler jobs share the worker's pebble_timeout; settings.scheduler_timeout is intentionally not wired into the worker in v1 (per-origin timeout is a deferred follow-up). Also sets scheduler_enabled=False explicitly on the freeshard gather test's mock settings, since the new gate would otherwise default to truthy on a bare MagicMock and hang that test in an infinite scheduler loop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a file-driven scheduler to Clayde: markdown task files in a host-mounted
~/clayde-tasks/directory run their body as a prompt on a schedule (one-off timestamp or recurring cron), independent of any interactive session or the Pebble watch. Due tasks flow through the existing job pipeline (fresh Claude CLI session, skills available, cwd = KB).Motivating case: a recurring keep-warm task whose CLI run refreshes the container's OAuth token, so the CLI login stops lapsing from disuse.
Design doc:
docs/superpowers/specs/2026-09-20-scheduled-tasks-design.md· Plan:docs/superpowers/plans/2026-09-21-scheduled-tasks.md.What's in it
service/package. The job-execution core (queue, worker, runner, notify, skills) moved out ofwebhook/intosrc/clayde/service/;PebbleJob→Jobwith anoriginfield.webhook/is now just the HTTP producer;scheduler/is the second producer. Behaviour-preserving move.scheduler/package. Task-file parsing (tasks.py), recurring-task run-state (state.py), pure due-ness/lateness helpers + the tick loop (loop.py).scheduler_loopjoins the orchestrator'sasyncio.gather, gated byCLAYDE_SCHEDULER_ENABLED.--permission-mode auto --permission-prompts noneinstead of--dangerously-skip-permissions, so headless runs auto-deny dangerous actions. Applies to Pebble jobs too (a hardening)./skills/kb; discovery is nowSKILL.md-aware (ignores reference markdown).timeout:frontmatter (4h/90m/45s/ bare seconds), defaulting toCLAYDE_SCHEDULER_TIMEOUT(300s), hard-capped at 4h. Pebble jobs keep 300s. The budget is surfaced in the system prompt so long tasks scope their work.Task file format
Fired one-offs are moved to
~/clayde-tasks/done/. Recurring dedup lives in/data/scheduler_state.json. Overdue tasks fire once on the next tick after downtime (no backfill), with a lateness note prepended to the prompt.Notifications
Scheduler jobs are silent on success; the framework still fires an ntfy on failure (timeout / usage limit / CLI / auth / crash). Intentional success notifications are prompt-driven (the task asks the agent to use the
ntfy-pingskill). Pebble jobs are unchanged (notify on every outcome).Testing
uv run pytest→ 416 passing, output pristine. Every task was built TDD with an independent review gate; a final whole-branch review surfaced one dedup-persistence edge (a failing task could drop an earlier cron task's dedup) which is fixed and covered by a test.Known limitations (v1, by design)
Deployment (not done here)
The running stack (
~/clayde/docker-compose.yml,data/config.env) is separate from this repo's compose and untouched. To enable: add the~/clayde-tasks:/tasksand~/knowledge_base/skills:/skills/kb:romounts, setCLAYDE_SCHEDULER_ENABLED=true, rebuild the image, create~/clayde-tasks/with akeep-warm.md, and ensure a CLI login exists (and doesn't lapse before the first keep-warm tick).