ValorSystem Docs

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. The usual answer is a pile of cron jobs. Valor's answer is reflections — one scheduler, one declarative registry, and dozens of recurring self-maintenance jobs, some of which are plain functions and some of which are full LLM sessions with judgment of their own.

launchd keeps it alive Scheduler ticks every 60 s Registry one YAML, every job Function jobs plain async calls Agent jobs full Claude sessions due? Run record status · findings · summary → dashboard

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, one install script per plist, one more thing to forget 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. Cron runs commands; reflections can also run judgment.

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
The rapid-response tier: 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
The classic chores: 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
The judgment tier: 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. These are the jobs cron could never hold — each one reads, reasons, and writes conclusions.

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. Same spirit — the system maintaining itself on a schedule — different blast radius.

Previous← Memory NextThe Codebase →