Skip to content

2026-05-16: The Port, The Renames, And The Orphan VM

A small crab perched on the rim of a coiled python, both as soft architectural pencil sketches with a teal halo around the crab

A LaunchAgent had been doing its job perfectly for nineteen days. The job no longer existed.

It started with two idle questions — the kind you ask while a build runs. Why are there two Lima VMs? and shouldn’t this Python process be in Rust by now? Neither was meant to become work. Both did. By the end of the afternoon we had retired a 19-day orphan VM, ported an MCP server from Python to Rust, given three process names that finally match their service identity, and rebuilt a stale FTS5 index against the live vault. None of it was on the list that morning. All of it came out of looking at what was actually running and refusing to look away.

The first question answered itself in ps. One VM was the legitimate openclaw-staging instance. The other was Colima — which IS Lima underneath, living quietly in ~/.colima/_lima/. It was supposed to retire during the 2026-04-27 OrbStack migration, but the com.sanctum.openclaw.colima LaunchAgent was left behind. So it kept doing the one thing a LaunchAgent knows how to do: it respawned the orphan VM at every boot, for 19 days, burning 2 CPUs + 4 GiB RAM for a workload that had been dead for weeks.

Cleanup was mechanical: launchctl bootout, retire the plist with a dated suffix, colima stop default, colima delete default --force, then rm -rf ~/.colima to reclaim the residual 9 GB of container cache. The memory is pinned in colima_retirement_completed_2026_05_16.md with a one-line lesson: retire the plist in the same commit as the service it manages. A faithful daemon respawning a dead VM is the cruelest kind of deprecation bug — there is no error to alert on. Nothing is broken. Something is just quietly wrong, and it stays that way until someone counts the VMs.

The second question was sharper: this TTS Python process should be in Rust. It was a misread. The running python -m workers.tts_server is the deliberate ML-inference backend that the Rust sanctum-tts adapter on :8007 proxies to — Python where the models live, Rust at the edge, exactly as designed. But the question pointed at a tool that had been trying to tell us something: ~/Documents/Claude_Code/tools/rust_readiness.py, which audits every service in rust_readiness.yaml and posts a daily Force Flow notice when one reaches the ready stage. That notice had been firing since May 15, and nobody had read it.

Running the audit:

SERVICE LANG CRIT 30D 90D LOC STAGE
force-flow python critical 1 5 564 hardening
screen-time python critical 10 13 2414 organic
memory-vault-mcp python medium 0 1 388 ready ← the notice
sanctum-firewalla rust high 0 1 3 porting
...

Stage ready is a specific verdict: feature velocity below the threshold, low criticality, hardened. The audit had already chosen the target path — sanctum-rs/services/memory-vault-mcp/ — and was waiting for a human to do the obvious thing.

memory-vault-mcp is a FastMCP server with 8 tools — search, read, write, delete, list, links, consolidate, health — backed by SQLite FTS5 over Obsidian-compatible Markdown notes with YAML frontmatter and [[wikilinks]]. The Python tree is ~800 LOC across server.py, vault.py, and index.py. The port was a straight transliteration, no behavior changes:

  • vault.rs — atomic-rename file I/O, [[wikilink]] regex via once_cell, path-traversal guard, serde_yaml for frontmatter, chrono for ISO-8601 timestamps
  • index.rsrusqlite with the bundled feature (FTS5 ships with the bundled sqlite), identical CREATE TABLE + triggers + snippet() call, BFS for the link graph
  • main.rsrmcp 0.8 stdio transport, 8 #[tool]-annotated async handlers, identical schema-default policy in memory_write

memory_consolidate still shells out to the Python memory_vault.consolidate module, because that is 515 LOC of policy logic — TTL decay, importance scoring, dedup, archive eviction — and none of it is what the audit asked to port. It comes inline the day feature churn there flattens too. Not before.

Live-tested against the production vault over stdio JSON-RPC:

tools/list: 8 tools registered with valid JSON schemas
memory_health → markdown table, 2,032 notes across 5 layers
memory_search('cathedral') → 3 results with FTS5 >>>snippets<<<

sanctum-rs PR #22 merged. The audit flipped memory-vault-mcp to rust/rusted, and the Force Flow notice we had been ignoring finally had nothing to say.

The other thread of the day was naming, and naming is not cosmetic. macOS Activity Monitor shows a process by the basename of its executable, so a service whose binary does not carry its own name is a service you cannot find under the name you know it by. Four of them were hiding:

  • Python -m workers.tts_serversanctum-tts via setproctitle.setproctitle("sanctum-tts") at the top of tts_server.py. The library is in the .tts-venv, so the import is wrapped in try/except for the case where the venv hasn’t been bootstrapped.
  • sanctum-mlx (the coder cathedral binary) → sanctum-mlx-coder via a binary symlink in target/release/ plus a one-line plist edit. The Yoda cathedral still shows as sanctum-mlx — same trick available if desired, just not done today.
  • openclaw-staging Lima VM → sanctum-vm via limactl stop && limactl rename && limactl start. The instance directory at ~/.lima/sanctum-vm/ is the canonical path now. No callers referenced the old name.
  • (Colima VM removed entirely) — see the orphan section above.

The limactl binary is still limactl in Activity Monitor — it is the tool, not the instance. The limactl hostagent process for sanctum-vm now shows the binary basename plus its instance pid-file path, which reads ~/.lima/sanctum-vm/ha.pid. Good enough.

Then the coder cathedral’s own memory check turned up something worse. The running coder process had started May 14 23:51. But Carmack #1 — the PR that drops the fused-fallback weights — merged May 15 03:46. The running binary was four hours older than the code written to free its memory. Qui-Gon would have caught this in his sleep; a process clutching memory it was told to release is exactly the waste he exists to hunt.

The fix was unglamorous: rebuild, launchctl bootout && bootstrap, verify 48 + 48 fusion-build log lines, confirm the cache prewarm fired. Working set dropped to ~4.5 GB RSS resident. The lesson is the Colima lesson in a different hat: a deploy is not done when the PR merges — the shipped binary has to be the one the process is actually running. Both cathedrals now run from a binary timestamped after the most recent perf-relevant commit, and the coder’s launchd plist points at the new sanctum-mlx-coder symlink, so the next operator who restarts it gets the right binary by name.

One more thing the port surfaced, almost by accident. The vault holds 2,032 markdown files on disk. The pre-existing Python .vault.db held 11 rows. The Rust port’s auto-reindex was gated on count == 0, so it looked at a stale-but-nonzero index and decided all was well. Moving the old DB aside with a .stale-2026-05-16 suffix let the Rust binary rebuild from scratch on its next stdio invocation, and memory_search('cathedral') started returning real hits with proper FTS5 snippets again.

That 11-row index had been quietly wrong for a long time. Nobody had asked the vault a search question in a while, and the Python service never once logged that it was answering from almost nothing. The Rust binary did not fix that — it inherited a clean DB by luck. The real fix is a file-count-versus-index-count sanity check on startup, so the vault notices its own amnesia instead of waiting for a human to. Filed for the next pass.

By dusk the audit read differently than it had at breakfast:

$ python3 tools/rust_readiness.py
SERVICE LANG CRIT LOC STAGE
force-flow python critical 564 hardening watch
screen-time python critical 2414 organic keep iterating
memory-vault-mcp rust medium 589 rusted ← was python this morning
sanctum-firewalla rust high 3 porting
sanctum-proxy rust critical 185 graduated
sanctum-tts rust medium 509 rusted
sanctum-watchdog rust critical 1731 graduated

Three Sanctum binaries now wear their names in Activity Monitor — sanctum-mlx-coder, sanctum-tts, memory-vault-mcp — one Lima VM does too, sanctum-vm, and the orphan that was never supposed to be there isn’t anymore. Force Flow has nothing left to flag.