Skip to content

Language Maturity

Language Maturity — an evolutionary specimen plate of a service as it transitions from organic Python sapling to fully-armored ferric pillar

The rule: organic stuff is Python. Once it hardens, it rusts.

Every service in Sanctum starts as a Python script, because Python is what you write while you’re still discovering what the service is. The curfew engine didn’t know it needed credits, or homework mode, or guest approvals, until the first time somebody’s kid found a loophole. Iterating that in Rust — where every schema change costs an hour of borrow-checker yoga — would have meant shipping less, slower, with fewer features your family actually needed.

Once a service stops discovering what it is, the cost function flips. The reward of Python (fast iteration) is moot because nothing is iterating, and the costs (no compile-time guarantees, requirements.txt drift, 400 MB of .venv, a process that silently OOMs at 3 AM) are paid every day. That’s the moment to port to Rust, where the single-binary deploy and launchd-friendly lifecycle pay off forever afterward.

This page is the doctrine for that transition, plus the tool that tells us which service is where.

organic ← Python, features
│ still evolving
hardening ← velocity slowing
ready ← quiet for months,
│ port scheduled
porting ← Rust source exists,
│ runtime still original
rusted / ← Rust binary built and
graduated deployed (services/)

Services move left-to-right, usually slowly, and can regress — a dormant service that gets a new feature ticket slides from hardening back to organic, which is fine. The whole point is to not rewrite in Rust anything still in motion.

Each stage has a precise, testable definition. No vibes.

StageCriterionVerdict
organic≥ 5 commits touching the main source file in the last 30 daysKeep iterating in Python
hardening1–4 commits in the last 30 days, or activity in the last 90 daysWatch — shape still settling
ready0 commits in the last 30 days and ≤ 2 commits in the last 90 daysPort to sanctum-rs/services/<name>/
portingRust source exists in sanctum-rs/services/, but the binary is not built (or built-but-stubbed) and the deployed runtime is still the original (Python/Node)Finish the build + cutover, then graduate
rustedRust binary built, but the deployed runtime is still mixed — a Python sidecar is still in the loop (e.g. tts proxies a Python backend; memory-vault shells out to the Python consolidator)Working Rust skin over a Python organ — finish the port to graduate
graduatedRust binary built and the whole job runs from Rust on the target host(terminal — precedent)
skipCriticality below mediumStays Python forever — rewrite cost not justified

The thresholds live in tools/rust_readiness.yaml and are deliberately conservative: “hardening” is the default for anything not firmly in either corner, which is the correct bias — do not rewrite what is still changing.

Before a service actually gets ported, it must clear all four:

  1. Velocity floor. The ready signal from rust_readiness must have held for at least 60 days. One quiet month can be a coincidence; two is a trend.
  2. Criticality justifies the rewrite. Low-criticality scripts (a one-off backup utility, a fun demo) stay in Python. Rust buys reliability, and reliability has a price; spend it where it matters.
  3. Interface surface is documented. HTTP routes, CLI flags, env vars, on-disk state format — written down before anyone opens a Rust editor. If you can’t describe it, you can’t port it.
  4. Behavioural parity tests exist. A black-box HTTP/CLI suite the Python version passes today, and the Rust version must pass before it takes the launchd slot.

Two services are currently serving production traffic from a Rust binary: sanctum-watchdog and sanctum-proxy. Both are recent and both are worth being honest about.

sanctum-watchdog — the Rust implementation was imported into the sanctum-rs workspace on 2026-04-09 (commit 83a376a, “Import Mac Mini Sanctum Rust services” — so the Rust code predated the consolidation). Within four days it absorbed the bandwidth monitor (84027b4) and had its self-health reporting normalised in stack checks (8a0ee66). The live process is the Rust binary — but it ships as sanctumd (the crate at services/sanctum-watchdog/), not a binary literally named sanctum-watchdog. Go looking for a process called sanctum-watchdog and you’ll find nothing; the registry name and the deployed binary name diverge, and that’s the first thing worth knowing.

sanctum-proxy — the cloud-tier model router on :4040, dispatched by the com.sanctum.proxyd LaunchDaemon through ~/.sanctum/bin/proxyd-launch (a wrapper that injects the Gemini/OpenRouter keys before exec). Same name trap: the live binary is proxyd, built from services/sanctum-proxy/. The deployed copy at ~/.sanctum/sanctum-proxy/sanctum-proxy (last touched 2026-04-19) is now an orphan — the runtime execs target/release/proxyd directly, and that directory survives only as the launcher’s working dir and the home of config.yaml.

The crucial observation isn’t a timeline — it’s that both rewrites were boring. No surprises, no redesign, no “while we’re at it” features. If the Rust port is interesting, the service wasn’t ready.

Forward-looking: with Sanctum only weeks old, this doctrine is mostly aspirational. The 30-day and 60-day windows below assume a system mature enough to have 60 days of quiet to point at; most services here don’t yet. That’s a feature — the rules will bite as the codebase ages, which is exactly when you want them to.

The source of truth is tools/rust_readiness.yaml. As of this writing:

ServiceLanguageCriticalityStage
force-flowPythoncriticalhardening
screen-timePythoncriticalorganic
memory-vault-mcpRust (+ Python consolidator)mediumrusted
sanctum-watchdogRust (sanctumd)criticalgraduated
sanctum-proxyRust (proxyd)criticalgraduated
sanctum-firewallaRust binary / Node runtimehighporting
sanctum-ttsRust adapter / Python runtimemediumrusted

To get the live view, run the tool:

Terminal window
python3 tools/rust_readiness.py # pretty table
python3 tools/rust_readiness.py --json # for the dashboard
python3 tools/rust_readiness.py --strict # exit 1 if any service is "ready" (CI gate)
python3 tools/rust_readiness.py --find living-force # resolve an alias to its service

Services carry aliases in the registry to reconcile doctrine names with runtime names. The Living Force is documented on its own architecture page but runs as the sanctum-watchdog crate (the sanctumd binary) — --find living-force lands exactly there, and the table prints the alias dimly under the service name so the link is visible at a glance.

tools/rust_readiness.py is a single-file Python script with no deps beyond PyYAML. It reads the registry, runs git log against each service’s source path, computes loc, and derives a stage per the YAML policy. Output is an aligned table on a TTY (with stage colours) and JSON otherwise, so dashboards can ingest it directly.

The intention is for this to be:

  • Cheap to run — no network calls, only local git and file reads. Takes under a second.
  • Cheap to trust — the velocity rule is three comparisons in derive_stage(); no ML, no heuristic stack, no override magic beyond explicit stage_override fields. The one bit of cleverness earns its keep: a graduated override is honoured only if target/release/<name> actually exists on disk (_graduated_is_real()), so the registry physically cannot print a graduation that hasn’t compiled. A claim the build can veto beats a claim it can’t.
  • Cheap to wire up--strict exits 1 if any service is ready, so a nightly cron can page someone when a service quietly finishes cooking.

Future work: surface the table in the Holocron UI, and emit a weekly “language maturity report” into Jocasta’s Monday digest summarising which services moved stages.

Because the most tempting way to lose a weekend is to rewrite a working service in a language you like more. This doctrine is the speed bump between “I have a weekend” and “I have a production outage.” A service still growing limbs isn’t ready; a service that has stopped will pay you back for years if you make it boring and ferric. The trap is “just one more turn” — one more refactor, one more crate, one more abstraction that looks clean in the diff and breaks something invisible on the machine. The harness exists to tell you when the turn is actually free.

Python while the shape still moves. Rust once it stops. Watch the tool.