Spruce Grove OS · Mechanics

The grove tends itself.

Every mechanism that keeps this system alive on one page: the breathing self-update loop, the four contracts, the sovereignty heartbeat, the gates and judges, and the war story that proved the design. Every mechanism here is a value with a test attached — traced back to the sentence of ethos that demands it.

01 · The root system

Ethos as mechanism why before how

The lifecycle design is not an engineering preference that happens to have a slogan. The slogan, executed. The ethos fixes three truths, and each one becomes a concrete mechanism you can open in the repo.

Ethos truthMechanism it becomesWhere to see it
It verifies against the world before it acts
love truth for truth's sake
PyPI's JSON API is the source of truth for “latest”; a fetch failure means observe-only — never actdocs/SELF-UPDATE.md
It reports every self-heal in plain words
no silent mutations
Every actuation reports through the message bus, in three languagesversion_checker.py
It can always be told to wait
consent survives autonomy
NO_AUTO_UPDATE=1 and NO_VERSION_UPDATE=1 — opt-outs are first-class featuresdocs/SELF-UPDATE.md

Three verbs organize the whole machine: it checks (the breathing core), it acts (the heartbeat), it heals (the gates and judges). If you can say which verb a given log line belongs to, you understand this system.

02 · The breathing core

The loop that never blocks you checks

Every startup, on a daemon thread, without ever blocking the user:

launch → daemon thread → GET pypi.org/pypi/spruce-grove/json
           → compare (installed vs latest)
           → equal  → idle (one "current version" line)
           → newer → report status (message bus, plain words)
                      → uv tool upgrade spruce-grove
                          → exit 0 → "next launch starts on the new version"
                          → failure → warn + the manual command

The design question: how do you swap the code of a running program without breaking it? The answer: you don't. You swap the disk, and let process boundaries do the rest. A running session keeps its warm, resolved imports and executes the version it started with, to the end of its life. The next launch boots on the new code. No restarts, no mid-session mixing.

PieceRole
version_checker.pyThe observer — fetch, compare, report. Decides whether to actuate.
self_update.pyThe actor — actuation, guards, reporting. Never decides.
Startup daemon threadNon-blocking, never joined — a hung network cannot hang a session.
03 · The four contracts

The part that must never rot guarantees

1 · Never raise

The updater returns and reports on every path; the caller adds a last-resort try/except anyway. Defense in depth.

2 · Never actuate in tests

The test suite runs with NO_AUTO_UPDATE=1 set suite-wide. No test can ever mutate a real installation.

3 · Never touch source checkouts

Actuation requires the uv binary and an installed-tool path. Editable installs are sacred.

4 · Subprocess with a timeout

Actuation runs as a subprocess with a 180-second cap, never joined. Worst case is a warning line.

Contracts two and three are care turned into code: the system never surprises someone who didn't ask. Escape hatches exist for every intent — keep checking but never upgrade, silence the whole check, pin a known-good version — each documented in docs/SELF-UPDATE.md.

04 · The heartbeat

The daily pipeline acts

Once a day, on your word — not on a schedule — the full self-healing pipeline runs. Five verbs, the same ethos table in motion:

snapshot consent→ rebase verify→ tests the gate→ reinstall the act→ regen + push report
Say this out loud when teaching The sovereignty brief predates the 1.0 rebrand: its architecture is current, its paths and version numbers are historical. Verify against the working tree before quoting it as fact — love truth for truth's sake, including about our own docs.

The ownership map is the disaster plan: working source clone, public fork, private insurance mirror, the installed tool, an offline wheel, and the user profile. If the public repo vanishes — nothing breaks immediately. Installed binary keeps running; clone the private mirror; reinstall from local source; nuclear offline fallback from the wheel. That sentence — nothing breaks immediately — is the whole deliverable.

05 · Gates & judges

How the grove says no heals

Gates are the hook engine: event-driven checks — before a tool runs, after it runs, at session start and stop — with pattern matching, per-hook timeouts, and blocking power. The contract is three exit codes:

exit 0 · allow

The tool call proceeds; stdout joins the transcript.

exit 1 · block

The veto. The tool call does not happen; stderr becomes the reason.

exit 2 · feedback

Error context returned without blocking — the model can correct course.

Judges are the evaluation layer: model-backed verdicts on whether a goal is actually complete. They vote PASS, FAIL — or ABSTAIN. And that third vote is the one the whole design warns about.

An abstaining judge does not vote

A gate that fails is loud. A judge that abstains is silent — the system keeps working, politely, forever, on the wrong premise. The war story below is what that looks like in practice, and how it was proven.

06 · The war story

Nine rounds, zero defects the proof

Nine consecutive sprint re-verification rounds passed locally, with fresh evidence. Yet every evaluation cycle returned the same judge note: the endpoint's metadata.weight_versions field — a list — failed strict validation, and the judge abstained. The loop remediated forever. Nine rounds of “it still fails” reads to a human as nine failures, even though every check passed. The work wasn't broken — the referee couldn't vote. Those are different problems, and we proved which one we had.

pydantic-ai releaseStrict metadata field declaredBehavior on the list payload
2.33.0YesValidationError — judge abstains
2.35.0 (repo pin)NoPasses untouched — why local dev never failed
2.36.0 / 2.40.0YesValidationError — judge abstains
  1. Reproduce or don't trust.

    The failure never reproduced in local dev — itself evidence: the trigger is environmental, not code.

  2. Isolate the variable.

    The judge harness resolves its own pydantic-ai release; the repo pins another. Same code, same payload, different environments.

  3. Prove it byte-exact.

    Isolated environments per release: the stock model raises the byte-exact judge error under the strict release; the pinned one passes untouched.

  4. Fix minimal, scope tight.

    A tolerant model wrapper — strict validation first, one metadata-only retry, unrelated errors propagate unchanged, wired only into the third-party endpoint branch. Blast radius, bounded.

  5. Gate the fix.

    Nine passing tests, a strict-environment regression proof, and a written validation plan naming exactly what the next cycle must show.

The remediation doc ends with a gap register — every residual gap owned, statused, with a next action. Two gaps stay deliberately open, with the reason written down: extend only on observed failure, to keep the blast radius small. An honest open gap beats a fake closed one. The full narrative lives in docs/judge-abstain-remediation.md; the training version in MASTER-CLASS.md, both in the repo.

07 · The human layer

Troubleshooting is emotional work the service counter

A user reading an error message is not reading text; they're reading a verdict on their day. The lifecycle core exists so humans stop babysitting updates — and that only works if the conversation about it is as well-engineered as the code.

Validate before you verify

“That would frustrate me too” is not filler — it lowers the temperature so the signal can be heard.

Name the mechanism plainly

“The update happens beside your session, never inside it. What you're working on finishes; the next launch is current.”

Consent first

“It does this by itself, and you can tell it to wait — and it will wait, every startup, until you say otherwise.”

Show, don't assert

Every claim one command away from evidence: a log line, a test name, a doc section.

Leave them the ladder

End with the next rung: the doc to open, the command to pin, the gap register to read. Self-sufficient is complete.

Never blame person-shaped

“The code was right, the referee's glasses were wrong.” That sentence teaches more than a redacted postmortem.

08 · Source map

What to open, and how much to trust it receipts

Artifact (repo root)CoversFidelity
docs/SELF-UPDATE.mdThe breathing core: loop, session-safety model, contracts, escape hatches, release racesCleanest current-state doc — written the day it shipped
docs/judge-abstain-remediation.mdRoot cause, version matrix, fix, gap register G1–G5, validation planDetailed, self-auditing; gap rows are living
ETHOS.md“The grove tends itself” — the three truths, the creedStable; the why behind every mechanism
SOVEREIGNTY.mdOwnership map, daily pipeline, disaster ladderArchitecture current · paths/versions stale
spruce_grove/hook_engine/ + docs/HOOKS.mdGate mechanics: events, matchers, exit codesCurrent
MASTER-CLASS.mdThe seven-module training guide this page condensesCurrent — the teach-backs live there