Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

routines

Presence-gated scheduler for personal machine chores — jobs that should run "the first time I'm active in the morning", not at a fixed wall-clock time on a machine that might be asleep.

A short-lived dispatcher (routines tick) is invoked by a launchd user agent (macOS) or systemd user timer (Linux) at login and on a coarse interval. Each tick it reads a TOML manifest, decides which jobs are due (period + optional time-of-day window + optional guard command), runs them, and exits. The scheduler is deliberately dumb: jobs are opaque executables — shell one-liners, headless AI-agent invocations, or launchers that detach long-running work — and it never branches on what a job is; all variability lives inside the job, behind one small contract.

Install

Homebrew (macOS or Linux):

brew install ramsrib/tap/routines

Or from source, if you've cloned this repo to hack on it (requires Go):

make install        # builds to ~/.local/bin/routines (override with PREFIX=...)

Then point a trigger at it (see Trigger setup).

Quickstart

mkdir -p ~/.config/routines
curl -fsSL https://raw.githubusercontent.com/ramsrib/routines/main/jobs.toml.example \
  -o ~/.config/routines/jobs.toml   # or cp jobs.toml.example from a clone; edit to taste

routines list             # what's registered
routines tick --dry-run   # what would run right now, and why/why not
routines run <name>       # run one job immediately (ignores schedule)
routines status           # last run / status / exit code / next due per job

Manifest

Default location ~/.config/routines/jobs.toml (override with --manifest or $ROUTINES_MANIFEST). Symlinking it from a dotfiles repo is the intended setup; relative entrypoint paths resolve against the symlink-resolved manifest's directory, so job scripts can live next to it in the repo.

[jobs.refresh-auth]
owner = "infra"                 # optional: project the job belongs to; shown in list/status, scheduler ignores
schedule = "daily"              # daily | every:<Go duration> | weekly:<weekday>
window = "05:00-12:00"          # optional: only fire inside this local-time window
cwd = "~"                       # optional working dir (default: manifest dir)
entrypoint = "jobs/refresh-auth"  # executable; relative to the manifest dir
guard = ""                      # optional `sh -c` command; non-zero exit → skip, retry next tick
timeout = "5m"                  # default 10m
isolation = "host"              # host | podman | remote:<alias> — hint for the job, scheduler ignores
requires = []                   # documented needs, e.g. ["gui", "keychain"]; scheduler ignores
enabled = true

The job contract

  • Non-interactive; exit code = status; stdout/stderr captured to ~/.config/routines/logs/<name>/<timestamp>.log (last 10 kept).
  • Environment: ROUTINE_NAME, ROUTINE_TRIGGER (launchd/manual/...), ROUTINE_SCRATCH (fresh per-run dir), ROUTINE_LOG_DIR.
  • Long work must detach itself (tmux, task queue, remote box) and return — the dispatcher stays short-lived.
  • Interactive handoff is one-way: emit a reminder or park a resumable session, never block waiting for a human.

Trigger setup

macOS — a launchd user agent with RunAtLoad + StartInterval (900s works well; launchd coalesces a catch-up run after wake):

<key>ProgramArguments</key>
<array>
  <string>/Users/you/.local/bin/routines</string>
  <string>tick</string>
  <string>--trigger</string>
  <string>launchd</string>
</array>
<key>RunAtLoad</key><true/>
<key>StartInterval</key><integer>900</integer>

Linux — a systemd user timer (OnStartupSec=1min, OnUnitActiveSec=15min, Persistent=true) invoking routines tick --trigger systemd.

tick always exits 0 — a skipped or failed job is logged, never surfaced to launchd/systemd as a crash. State (last runs, lock) lives in ~/.local/state/routines/.

About

Presence-gated local job scheduler — jobs fire on the first tick you're active inside their window, not on a wall clock

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages