Parse cron expressions in `Schedule::Expression` so the adaptive scheduler can use them
- Dominant language
- Rust
- Stars
- 3
- Forks
- 1
- Avg merge
- 4h 50m
- Merged PRs (30d)
- 1
Description
`agent.toml` supports three schedule types (see [`docs/reference/agent-spec.md`](docs/reference/agent-spec.md#schedule-types)):
| Type | Example |
|--------------|----------------------------------------|
| `interval` | every N minutes |
| `cron` | `weekday + hour:minute` (structured) |
| `expression` | free-form cron string (e.g. `*/15 * * * *`) |
The `expression` variant is documented as "Linux-only, via systemd OnCalendar" — the OS handles it for installed units. But the **adaptive scheduler** inside the daemon does not know how to compute the next fire time for `Schedule::Expression`, so it returns `None`. Two consequences:
- `dotagent tick` and the daemon loop silently skip these schedules when computing `next_event`.
- Cross-platform parity is broken: macOS users can't use `expression` at all (no systemd to delegate to).
This is flagged with `TODO: parse cron expression` in `crates/dotagent-scheduler/src/lib.rs` (two call sites).
## Proposal
Teach the scheduler to compute `next_after(expr, now)` and `prev_before(expr, now)` for the `expression` variant, using a standard 5-field cron string (minute hour day-of-month month day-of-week).
Once that lands:
- `expression` works on macOS (daemon computes the next fire time itself instead of relying on systemd).
- `dotagent next` (sibling issue) can show upcoming fires for `expression` schedules.
- `dotagent status` / `dotagent why` give correct answers for `expression` schedules.
## Acceptance criteria
- [ ] `Schedule::Expression { cron: "*/15 * * * *" }` resolves to the next quarter-hour from any `now`.
- [ ] Edge cases covered with unit tests: month boundary, year boundary, DST transitions, day-of-month vs day-of-week with `OR` semantics (POSIX).
- [ ] Invalid expressions are rejected at `validate()` time with a clear error pointing at the bad field.
- [ ] No new IO in `dotagent-scheduler` — the pure-function invariant from [`CLAUDE.md`](CLAUDE.md) is preserved.
- [ ] `agent-spec.md` no longer says "Linux only" for `expression`.
## Where to start
- `crates/dotagent-scheduler/src/lib.rs` — the two `TODO` sites.
- `crates/dotagent-core/src/manifest.rs` — the `Schedule` enum and its `Expression` variant.
- Existing cron-string crates in the Rust ecosystem are fine to use (workspace dep); evaluate maintenance status and license before picking.
## Non-goals
- Quartz extensions (seconds, years, `L`, `W`, `#`). POSIX 5-field is enough for v1.
- Changing the manifest schema.
Contributor guide
Research direction
Start with the two TODO sites in crates/dotagent-scheduler/src/lib.rs and the Schedule::Expression definition in crates/dotagent-core/src/manifest.rs. Read CLAUDE.md for the pure-function constraint, review the existing schedule tests, and evaluate workspace cron crates for maintenance and licensing. Done means valid POSIX five-field expressions resolve next and previous times across the listed edge cases, invalid fields fail validation clearly, and agent-spec.md no longer says Linux-only.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100