Skip to main content
Reference · Information

senkani schedule

Live

Schedule recurring commands via launchd.

Syntax

senkani schedule

Behavior

Generates a launchd plist per schedule. Each fire gets its own worktree + pane pair. Budget cap per run. preset install reuses the same PresetInstaller plist generator as create, so the two code paths produce byte-identical plists.

Amplification guard. create AND preset install both call AmplificationGuard.validate before any disk write. Crons whose first two fires sit at or below the 60-second amplification floor (default defaultMinIntervalSeconds) are refused with a structured error; no JSON is written under ~/.senkani/schedules/ and no plist is written under ~/Library/LaunchAgents/. The canonical reproduction case is --cron '* * * * *' (every-minute, 60s gap == floor); the guard refuses up front so the operator sees a clear rejection instead of a silent downstream clamp by the rate limiter. For preset install the guard runs AFTER cron-override resolution AND AFTER the secret-scan, so an amplifying --cron override against a shipped preset (or a malformed user-shipped preset record) is refused with a message naming the preset name + reason + floor (schedule-preset-install-amplification-guard-not-wired-2026-05-21, shipped 2026-05-21).

Counter-cadence schedules. Pass --counter-cadence "every N event_name" (mutually exclusive with --cron) to register a schedule that fires from HookRouter post-tool reactions instead of launchd. Counter cadences DO NOT get a launchd plist — only the ScheduledTask JSON is written under ~/.senkani/schedules/, with cronPattern set to the sentinel COUNTER:<event>:<N> (the JSON schema requires a non-optional cronPattern; dispatch readers filter on task.eventCounterCadence != nil or CounterCadence.isSentinel(task.cronPattern)). Validation refuses N < 2 up front (every-event fires would be clamped to one fire per minute by the rate limiter); the parser tolerates every 10 tool_calls, every 10th tool_call, and every 10 tool_call equivalently. senkani schedule list renders the SCHEDULE column with the operator's prose (e.g. every 10 tool_calls) for counter rows rather than the raw COUNTER: sentinel — cron rows continue to render via CronToLaunchd.humanReadable.

Natural-language cadence (--prose). Pass --prose "every weekday at 9am" instead of a raw cron. The three cadence flags form a three-way XOR: exactly one of --cron, --counter-cadence, or --prose is required. The prose is compiled to a 5-field cron by CompositeProseCadenceCompiler (U.8b-4 production default — rule-first / MLX-fallback) before any disk write; the compiled cron then runs through the same CronToLaunchd.convert validation and AmplificationGuard as --cron, and a launchd plist is generated (prose resolves to a real time-based schedule). The composite tries RuleBasedProseCadenceCompiler (a pure, MLX-free, English-only compiler in Core) first — deterministic phrases below hit in sub-ms — and falls through to MLXProseCadenceCompiler (Gemma 4 VLM, English-only, lazy cold-load on the first fallback call) on .unrecognizedPhrase or .unsupportedLocale from the rule arm. Every other rule-side error (.invalidCron, .unavailable, .invalidJSON, .cancelled) re-throws unchanged; MLX-side errors also re-throw unchanged so the operator sees clear messages when no Gemma tier is downloaded ("No prose-cadence model is available. Pass an explicit cron with --cron instead."). Rule arm phrases: hourly, daily, weekly, every minute, every hour, every day, every N minutes (1–59), every N hours (1–23), every day at <time>, every weekday at <time> (Mon–Fri), and every <weekday> at <time>; <time> accepts 9, 9am, 9:30, 9:30am, 14:00, 2pm. Irregular phrases like every other Tuesday at 6pm fall through to the MLX arm. There is no silent fallback beyond the rule→MLX seam — an unrecognized phrase on both arms or a non-English locale is refused with a clear error pointing at --cron or --locale en-US, and nothing is written. --locale <BCP-47> defaults to the system locale (Locale.current.identifier); both compiler arms accept only en-*. The persisted ScheduledTask records proseCadence (the phrase), compiledCadence + cronPattern (the cron), and locale. Lazy MLX invariant: constructing the composite (which the CLI does on every senkani invocation) does NOT load the Gemma model — model load happens lazily inside MLXProseCadenceCompiler.ensureModel() on the first MLX-fallback call.

Example

$ senkani schedule create --name nightly-lint --cron "0 3 * * *" --command "swift test"
$ senkani schedule create --name learn-after-10 --counter-cadence "every 10 tool_calls" --command "senkani learn"
$ senkani schedule create --name weekday-brief --prose "every weekday at 9am" --command "senkani learn"
$ senkani schedule preset list
$ senkani schedule preset install autoresearch --topic "AI workstation"

# Amplification guard refuses fast-firing crons:
$ senkani schedule create --name fast --cron "* * * * *" --command "echo x"
Validation Error: Refused: schedule would amplify (cron fires every 60s,
at or below the 60s amplification floor). The amplification floor is 60s.
Pick a cron whose fire interval is above the floor.

# Counter-cadence with N=1 is refused at parse time:
$ senkani schedule create --name every-call --counter-cadence "every 1 tool_call" --command "echo x"
Validation Error: Counter cadence N=1 is at or below the amplification floor. Pick N >= 2 so the schedule doesn't fire on every event.

# Amplification guard also refuses fast-firing --cron overrides on preset install:
$ senkani schedule preset install log-rotation --cron "* * * * *"
Validation Error: Refused: preset `log-rotation` would amplify (cron fires every 60s,
at or below the 60s amplification floor). The amplification floor is 60s.
Pick a `--cron` override whose fire interval is above the floor.

Subcommands

Subcommand
Description
create
Create a scheduled task from --name / --command / --budget / --worktree plus exactly one cadence flag — three-way XOR of --cron, --counter-cadence, or --prose (with optional --locale, default system locale). --cron and --prose drive launchd; --counter-cadence drives HookRouter (no plist); --prose compiles an English phrase to a cron via CompositeProseCadenceCompiler (rule-first / MLX-fallback, U.8b-4) before save.
list
Show active schedules.
remove --name <n>
Delete a schedule.
run --name <n>
Execute a scheduled task (called by launchd).
preset list
List shipped + user presets with engine class and prerequisite readiness.
preset show <name>
Print the raw JSON record for a preset.
preset install <name>
Install a preset as a running ScheduledTask. Accepts --topic / --competitor / --budget / --cron overrides; secret-scans the resolved command; warns on missing prerequisites.

Shipped presets

Five installable presets ship by default under Sources/Core/Presets/Defaults/:

See also

Source: Sources/CLI/ScheduleCommand.swift