Recurring schedules with spawn leases
Problem
The schedule tool is one-shot: a wake fires once and is gone. Recurrence is
emulated by agents rescheduling themselves, which breaks permanently when a
wake is missed, delivery fails, or the chain errors — the next occurrence
only exists if the previous run successfully creates it. There is also no
guard against overlap: nothing stops occurrence N+1 from spawning while
occurrence N's worker is still running against the same memory graph.
Proposal
Interval recurrence plus a per-series spawn lease, additive to the existing
queue:
ScheduleRequest gains repeat: {every_minutes, max_iterations?}.
Interval-based, no new dependency. Direct targets (resume/spawn) only:
ambient cycles already re-read queued items every run, so repeating there
would double-fire — scheduling it is a validation error, not silent
behavior. every_minutes < 1 and max_iterations == 0 are rejected.
- Each item carries
RepeatState {every_minutes, remaining, recurrence_id, active_run?}, all serde-defaulted so old queue files load unchanged.
recurrence_id is stable per series, so a whole series cancels at once
(cancel_recurrence; the tool accepts recur_* IDs in action=cancel).
- The next occurrence is re-queued before delivery runs, in the same save:
the chain survives delivery failures, crashes, and missed wakes. A stale
process coming back finds the next occurrence due, never a burst —
scheduling is anchored at max(now, previous_due) + interval, so there is
no catch-up storm.
- Spawn lease: after a spawn delivery, the series is stamped with
{owner_session, started_at}. A due occurrence whose owner is still live
defers in place (due time pushed one interval, iteration count untouched).
Dead owners (session file gone, closed/crashed/error status) and stale
leases (older than three intervals) read as free, so a hung run blocks only
its own series, and only boundedly. Session-target reminders take no lease:
overlap there is harmless.
- Tool surface:
repeat_every_minutes / repeat_max_iterations on create,
series shown in list output, debug queue JSON carries the new fields.
Reference implementation
Built and verified on origin/master (983b532). Two commits on the fork:
SK-DEV-AI/jcode@6562596 — the feature
SK-DEV-AI/jcode@7a4921b — zero-iteration rejection, import cleanup
Full diff:
SK-DEV-AI/jcode@1jehuang:master...SK-DEV-AI:feat/recurring-schedules
Files: ambient.rs (schema), ambient/persistence.rs (requeue + lease +
shared pop path), ambient/manager.rs (validation, series cancel, lease
stamp), ambient/runner.rs (stamp after spawn), tool/ambient.rs (params,
list, cancel), tests in ambient_tests.rs + tool/ambient/tests.rs.
Verification
- 16 new tests pass: chain countdown, exhaustion at last iteration,
unbounded mode, both pop paths, series cancel, both validation rejections
plus the zero-iteration case, lease free/stale/live/closed transitions,
stamp targeting, tool create→list→cancel round-trip.
- Full
jcode-app-core suite baselined against clean origin/master via
worktree: identical pre-existing failure sets, zero regressions.
cargo fmt clean; no new clippy warnings in touched code.
- One honest boundary: mechanics are verified against real queue and session
files, but the long-horizon loop (occurrences days apart) cannot be
compressed into a test — that part rides on the requeue-before-delivery
ordering, which is tested.
Happy for this to land rewritten in your own shape, as with #1174.
Recurring schedules with spawn leases
Problem
The schedule tool is one-shot: a wake fires once and is gone. Recurrence is
emulated by agents rescheduling themselves, which breaks permanently when a
wake is missed, delivery fails, or the chain errors — the next occurrence
only exists if the previous run successfully creates it. There is also no
guard against overlap: nothing stops occurrence N+1 from spawning while
occurrence N's worker is still running against the same memory graph.
Proposal
Interval recurrence plus a per-series spawn lease, additive to the existing
queue:
ScheduleRequestgainsrepeat: {every_minutes, max_iterations?}.Interval-based, no new dependency. Direct targets (resume/spawn) only:
ambient cycles already re-read queued items every run, so repeating there
would double-fire — scheduling it is a validation error, not silent
behavior.
every_minutes < 1andmax_iterations == 0are rejected.RepeatState {every_minutes, remaining, recurrence_id, active_run?}, all serde-defaulted so old queue files load unchanged.recurrence_idis stable per series, so a whole series cancels at once(
cancel_recurrence; the tool acceptsrecur_*IDs in action=cancel).the chain survives delivery failures, crashes, and missed wakes. A stale
process coming back finds the next occurrence due, never a burst —
scheduling is anchored at
max(now, previous_due) + interval, so there isno catch-up storm.
{owner_session, started_at}. A due occurrence whose owner is still livedefers in place (due time pushed one interval, iteration count untouched).
Dead owners (session file gone, closed/crashed/error status) and stale
leases (older than three intervals) read as free, so a hung run blocks only
its own series, and only boundedly. Session-target reminders take no lease:
overlap there is harmless.
repeat_every_minutes/repeat_max_iterationson create,series shown in list output, debug queue JSON carries the new fields.
Reference implementation
Built and verified on
origin/master(983b532). Two commits on the fork:SK-DEV-AI/jcode@6562596— the featureSK-DEV-AI/jcode@7a4921b— zero-iteration rejection, import cleanupFull diff:
SK-DEV-AI/jcode@1jehuang:master...SK-DEV-AI:feat/recurring-schedules
Files:
ambient.rs(schema),ambient/persistence.rs(requeue + lease +shared pop path),
ambient/manager.rs(validation, series cancel, leasestamp),
ambient/runner.rs(stamp after spawn),tool/ambient.rs(params,list, cancel), tests in
ambient_tests.rs+tool/ambient/tests.rs.Verification
unbounded mode, both pop paths, series cancel, both validation rejections
plus the zero-iteration case, lease free/stale/live/closed transitions,
stamp targeting, tool create→list→cancel round-trip.
jcode-app-coresuite baselined against cleanorigin/masterviaworktree: identical pre-existing failure sets, zero regressions.
cargo fmtclean; no new clippy warnings in touched code.files, but the long-horizon loop (occurrences days apart) cannot be
compressed into a test — that part rides on the requeue-before-delivery
ordering, which is tested.
Happy for this to land rewritten in your own shape, as with #1174.