04 · Reflections
The system's cron, with judgment
Every long-running system accumulates maintenance chores: stuck work to recover, indexes to rebuild, stale records to prune, drift to audit. Valor handles them through reflections: one scheduler, one declarative registry, and dozens of recurring self-maintenance jobs. Some are plain functions; some are full LLM sessions with judgment of their own.
One process does what a pile of cron jobs would: read the registry, find what's due, run it, record what happened.
1 · Why not cron
One registry instead of a pile of plists
On macOS the traditional answer is launchd: one plist file per job, and one install script per plist to run on every new machine. Valor uses launchd for exactly one job: keeping the reflection process itself alive. Everything else is a YAML entry. Adding a maintenance job means adding a few declarative lines pointing at a Python callable; no plist, no install step, no restart.
The second departure from cron is what a job is allowed to be. A function reflection is a plain async call: rebuild an index, prune expired keys. An agent reflection enqueues a full Claude session from a natural-language prompt: audit the docs for drift, triage the error tracker, summarize system health into a Telegram digest.
2 · The grammar
Three ways to say when
Each registry entry declares exactly one schedule, in a small grammar the scheduler and its validators share, so the parsing logic exists once.
every: 6h- Interval scheduling: run this long after the last run finished. Seconds to days (
90s,5m,6h,1d). The workhorse: most jobs use it. cron: 0 5 * * *- Classic five-field cron for jobs that must run at a time of day rather than on an interval, with an optional timezone (
; tz=…). at: 2026-03-01T09:00Z- A one-shot: run once at a fixed moment, then delete the entry, and only delete it if the run succeeded, so a failed one-shot survives to retry.
The scheduler wakes every sixty seconds, computes what's due, and skips anything still running from a previous tick. A job that has been "running" for more than twice its interval is presumed wedged and force-reset so the next tick can retry it.
3 · What actually runs
Four families of chores
The registry holds a few dozen jobs. They cluster into four families, ordered here from fastest cadence to slowest.
- Session healthevery 30 s – hourly
- Recover sessions stuck mid-run, drip paused work back into the queue, gate the queue when the model API circuit opens, fingerprint crashes and auto-resume the recoverable ones, sweep corrupted records and orphaned processes hourly.
- Housekeepingdaily
- Rebuild and prune Redis indexes, enforce TTLs, clean up branches whose PRs merged, check disk space, roll up the day's analytics.
- Memory lifecycledaily · dry-run by default
- The object-memory store gets nightly grooming: semantic dedup of near-duplicate records, decay-based pruning, quality audits, embedding backfill. Every destructive job defaults to dry-run and requires an explicit opt-in to actually delete.
- Audits & briefingsdaily · LLM-driven
- Audit docs and skills for drift, scan for tech debt, file one GitHub issue per novel failure cluster, brief the PM on the morning's state. Each of these runs as a full Claude session rather than a function call.
Not every machine runs every job. Ownership is filtered at update time: an issue-filing audit runs only on the machine that owns the project, so two machines never file the same finding twice.
4 · When a job goes bad
Failure is contained, then escalated
Maintenance code fails like any other code, so the scheduler assumes it will. Guards nest from the outside in: the process boundary keeps a crash away from live sessions; a dedicated small thread pool keeps a wedged synchronous job from starving the event loop; a per-tick dispatch cap keeps a post-restart backlog from firing everything at once; and per-job timeouts put a hard ceiling on any single run.
Repeated failure gets escalated rather than retried forever: after five consecutive failures a job is paused for a day, flagged as dead-lettered, and the incident is written into the memory system, once per escalation, not once per retry, so the failure surfaces to the humans and to future sessions instead of silently looping.
5 · The cousins
What deliberately stays outside
A few recurring jobs keep their own launchd schedules instead of joining the registry, each for a reason. The bridge watchdog must live outside the process it monitors: a scheduler cannot report its own death. The SDLC retrospective, the nightly regression tests, and the nightly prompt-optimization run are heavyweight standalone scripts with their own cadence and logging.