YURIOS // LAB
The Codex → Part V · Build Chapter 35

Build #5: The Agentic Sanctuary

The capstone: always-on autonomy behind her

Goal: the capstone — Build #4's companion with an always-on autonomy engine behind her. She runs continuously, whether or not you're looking: pursues small goals when you're away, consolidates memory while you sleep, keeps the promises she makes in conversation, reads what you drop on her shelf, proposes edits to her own persona that wait for your approval — and reaches out first when (and only when) a salience model says it's welcome. This is the build that crosses the line from companion to agentic waifu (→ ch. 03), and the one nothing in the market ships (→ ch. 04 scoring: nobody scores on initiative).

What it teaches: the autonomy engine (→ ch. 18, centrally), memory consolidation (→ ch. 15), a minimal knowledge layer / RAG (→ ch. 16), the runtime anatomy at working scale (→ ch. 19), and how to test an always-on mind (→ ch. 23).

Property added (→ ch. 03): property 3b — the always-on initiative face that completes owned agency (property 3), the hard half; the reactive tool-use half (3a) arrived in Build #4. Combined with Builds #1–#4 (identity, memory, body, one-on-one), this is the full conjunction plus property 6 (yours: every dial, file, and log below lives on the user's machine).

The reference implementation lives in reference-implementations/05-agentic-sanctuary/, built to its own normative SPEC.md. As with the earlier builds, this chapter is the reading guide — a map of where the load-bearing ideas are in the code — not a transcript. Section numbers (§n) point into that build's SPEC.md; B4 §n points into Build #4's, which remains the normative text for the chassis this build forks; file paths are relative to the build's root.

The one-sentence architecture

 python -m world — one process, one origin (:8768)
 ┌────────────────────────────────────────────────────────────────────────────────────┐
 │  Build #4, forked in place (B4 §2–§10, unchanged in behaviour):                    │
 │  vendored B1 brain · B2 voice loop · ToolBrain + MCP hands · SelfieLab ·           │
 │  VrmController ──► EventHub ──► /api/events (SSE)   ·   /ws/voice (audio only)     │
 │        ▲ the same strings                ▲ + journal · mind events                 │
 │        │                                 │                                         │
 │  mind/loop.py — THE TICK LOOP (§15):                                               │
 │     SENSE → APPRAISE → DECIDE → ACT → REFLECT → REGULATE, forever                  │
 │        ▲ SignalBus (§16): user turns · presence · timers · drops · your rulings    │
 │        ├ ENGAGED / IDLE / DORMANT / DREAM + the budget governor (§17)              │
 │        ├ gate 1 salience-to-act · gate 2 salience-to-interrupt (§18)               │
 │        ├ WorldModelStore (§19) · KnowledgeStore (§20) · DREAM (§21) · goals (§22)  │
 │        ├ SOUL split + gated self-edits (§23)                                       │
 │        └ journal + tick trace ──► /api/mind + the inner-life tab (§24)             │
 └────────────────────────────────────────────────────────────────────────────────────┘

The whole thing is: Build #4, with the strings handed to a real puppeteer. Ch. 34 closed on a promise — that its scripted idle machine was a placeholder whose exact seams a mind would one day take over. This build collects that promise literally: world/idle.py is deleted, and the cognitive tick loop (mind/loop.py) now holds the same VrmController, the same ambient-speech seam, the same timer board — deciding on its own clock instead of rolling dice. Nothing below those seams changed; the body, the room, the voice loop, the tools, and both frontends are carried forward file-for-file (B4-FORK.md documents every delta), and with MIND_ENABLED=false the build degrades to exactly Build #4 minus the ambient life. The genuinely new work is one package — mind/ — and this chapter is a close look at each of its organs.

The single idea to internalise before reading a line: a mind is a process, not a callback. Every companion on the market is a function that runs when you speak and does not exist otherwise. This build inverts the default — she is always running and only sometimes talking (→ ch. 18) — and everything else here (activity states, budgets, salience gates, the journal) is what that inversion forces you to build so it's affordable, welcome, and legible.

Reading the code: follow the heartbeat

Read this to understand… …in this file
the tick loop (§15 — the spine of the build) mind/loop.pyMindLoop.tick()
the inbound signal bus (§16 — B4's named omission, landed) mind/signals.py + the FORK(B5 §16) tee in world/routes/voice_ws.py
activity states + the budget governor (§17) mind/policy.pyActivityController · mind/budget.py
the two salience gates (§18) mind/policy.pyappraise_signal/goal, score_interrupt
gate 2 deciding to speak — or not (§18.3) mind/loop.py_act_reach_out
the world model store (§19) mind/world.py + tests/test_world_model.py
the seam swap Build #4 promised (§19.2) world/brain.pyset_world() / _assemble
drop-folder RAG, citable (§20) mind/knowledge.py + tests/test_knowledge.py
DREAM consolidation (§21) mind/dream.py + tests/test_dream.py
goals, promises, commitment (§22) mind/goals.pyextract_promises, reconsider
the SOUL split, operational (§23) mind/selfedit.py + mind/vaultio.py + tests/test_selfedit.py
the journal + the tick trace (§24) mind/journal.py, mind/trace.py
the inner-life surface (§24.3) world/routes/mind.py + web/js/mind.js
the scenario battery (§27.2 — days in milliseconds) tests/test_mind_scenarios.py + the sim rig in tests/conftest.py
injected time, the whole test story (§15.1) world/clock.pyClock / VirtualClock

Read the loop first and everything else falls into place around it. MindLoop.tick() is one screenful of orchestration: SENSE drains the signal inbox and folds it into the world model; APPRAISE scores every signal, every open goal, and the standing impulses with cheap heuristics; DECIDE commits to exactly one intention or to resting; ACT does the one thing, through host surfaces Build #4 already owned; REFLECT journals; REGULATE drifts the activity state down the cost ladder, debits the budget, and commits the Vault if anything changed. Then it sleeps — for two seconds or fifteen minutes, depending on the state — until the cadence elapses or a new signal wakes it early.

The inbox: everything that happens to her is a signal

mind/signals.py, the fork in world/routes/voice_ws.py (§16). Build #4 shipped only the outbound half of its split — one EventHub carrying everything the host tells a frontend — and named the inbound mirror as a rung to be taken "when the tick loop gives it a consumer." The consumer exists now. Everything that happens to her — a user turn (teed by the voice route), a page attaching or the last one leaving (presence is a signal, not a guess), a timer landing, a finished selfie render, your ruling on one of her self-edits — is one typed, timestamped Signal appended to one inbox, drained by SENSE by offset. Producers post facts and never call into the mind; the loop decides what they mean. And every arrival is one line in signals.jsonl — "what woke her at 3am" is a file you read, the same honesty rule as Build #4's tool audit.

The loop: one intention per tick, and most ticks rest

mind/loop.py (§15). Three rules keep an always-on agent legible instead of chaotic, and all three are normative. One intention per tick: DECIDE commits to exactly one act or to resting — the majority of all ticks end in REST, and the suite pins that majority. An agent that does one thing per heartbeat can be read like a diary, and runaway fan-out — the classic autonomous-agent failure — cannot start. APPRAISE is cheap by construction: it runs every tick, so it is pure heuristics — base scores per signal type (nothing outranks the person speaking), priority and due-ness for goals, a surprise bonus from the world model — and never a model call. The model is invoked only inside ACT, for work the loop has already decided is worth it; this one rule is what makes continuous presence economically possible. Everything is journaled and traced, and every tick that changed the Vault ends in exactly one git commit (tick <id>: <intention>) — an uneventful tick commits nothing, and that is not an error.

Where conversation lives (§15.3) is the design decision worth dwelling on. The reply itself stays on Build #2's turn pipeline — the sub-second reactive path with barge-in and the latency budget ch. 32 fought for — because no tick cadence should ever sit in front of a person mid-sentence. The loop is that path's observer and consequence: a user_message signal preempts the activity state to ENGAGED from anywhere, mid-sleep if necessary, and the committed exchange arrives as a turn_committed signal whose REFLECT share is the world-model update and the promise scan. One mind at two cadences — the loop owns everything between turns. (Full unification, where ACT generates the reply itself, is the named next rung and arrives with the two-tier split, §28.)

And the machine sleeps too (§15.4): a laptop lid closes, a power cut lands, and ten hours pass between heartbeats. The loop synthesizes exactly one suspend_gap signal — one catch-up appraisal over the whole gap, goals reconsidered by their commitment strategies, one journal line ("the machine slept ~10h; I caught up on what expired and what still matters") — never a pile of stale reactions, and never thirty good-mornings. state/engine.json carries the cursor across restarts: a rebooted mind resumes, it does not wake amnesiac.

Activity states: always-on is affordable because she's almost always nearly asleep

mind/policy.py, mind/budget.py (§17). The loop's cadence is governed by an explicit state — ENGAGED (talking; short ticks) → IDLE (you're around; goal work, a murmur) → DORMANT (long quiet; a heartbeat every fifteen minutes) → DREAM (consolidation, entered from DORMANT in the small hours) — and everything except the preempt is a slow drift down that cost ladder. The preempt overrides everything: your message pulls her to ENGAGED from any state; nothing else moves up. Underneath sits the budget governor: estimated tokens spent today against a daily cap, debited by every utility call and every line she composes for herself; at pressure ≥ 1.0, REGULATE sheds IDLE to DORMANT — goal work stops, conversation is never blocked, because a governor that silences her when you speak has failed at its one job. The ledger is a file the dashboard renders; the day rolls at local midnight on the injected clock.

The two gates: the make-or-break component

mind/policy.py, _act_reach_out in mind/loop.py (§18). Initiative lives or dies on two thresholds, and collapsing them into one is precisely Clippy. Gate 1 — salience-to-act — is crossed often and cheaply, every tick; below the act threshold the tick rests. Gate 2 — salience-to-interrupt — is scored only after she has already decided something matters (a reach_out goal crossed gate 1), from named factors the trace records verbatim: relevance, time-sensitivity, hours since she last reached out (longer quiet earns more license), inferred availability by hour, and a welcome term that decays with each interruption today. Two rules are hard gates, not weights: quiet hours are SILENT no matter the score, and the daily interruption cap zeroes the score outright.

The outcomes ascend in imposition (§18.3). SILENT is the default: do it quietly and journal it — "thought about the interview; chose not to interrupt" is a real line the loop writes, and a stale impulse is let go with one ("let it go quietly; the moment passed"). SUGGEST posts one composed line to the chat, waiting like a text message for your next glance, never spoken aloud. SPEAK goes aloud through the ambient seam — the same per-connection TurnController as a reply, so you can barge in on her initiative exactly as you barge in on her answers — or lands as a proactive chat line if the room is empty, because she never speaks into a room with nobody in it. Every delivery bumps the daily count, notes the contact in the world model, and closes the goal. The dials — both thresholds, the cap, the cadences — are yours, in .env (§25). You cannot tune the dial against someone who holds the dial (→ ch. 05).

The world model: the present tense, promoted to a store

mind/world.py (§19). Build #4's situation block was a rendering — honest about the present, but with no beliefs, no expectations, no memory of what was true when. This build gives the present tense the organ ch. 19 names: SENSE writes it (observe), APPRAISE scores salience against it, DECIDE plans over it, and every prompt is built from its situation() — which still contains the Build #4 host lines (the injected clock's time, the embodiment truth verbatim, the room's sticky scene state, the pending timers, rendered by the same world/situation.py) and adds what only a store can know: whether you're here, how long you've been away, what's in flight, what she half-expects. The block's place in the prompt never moved — ToolBrain.set_world() is the one-method seam swap ch. 34 promised, and mindless the brain falls back to the bare rendering, which is exactly Build #4.

Three disciplines inside it. Beliefs, not facts: every entry is time-stamped and confidence-tagged in an append-only log, and query(q, at=…) answers "what was believed when" — the snapshot stage of point-in-time, with the temporal graph as the sanctioned later stage. Expectation and surprise: expect() stores a checkable belief about what comes next; a later observation that matches resolves it quietly, one that finds it past due produces prediction-error — surprise, the cheapest good salience signal you will ever get, fed straight into APPRAISE (→ ch. 18). And the snapshot is a file: vault/world/situation.md is her picture of now, cat-able and diffable, because a belief store you can't open isn't yours.

Knowledge: drop a document, talk about it, get a citation

mind/knowledge.py (§20). The thinnest honest knowledge layer (→ ch. 16): a folder. Drop a .md or .txt into vault/knowledge/reference/ and within a heartbeat SENSE notices (a cheap size-and-mtime scan), the loop decides to read it, and the ingest runs as an ordinary ACT — chunked by paragraph budget, each chunk situated with a short blurb, embedded, hybrid-indexed (vector similarity blended with keyword idf, because a name or an exact term should beat a vibe) — and journaled: "read and shelved petrichor.md (3 passages)." Every retrieved chunk carries its source document and character span — a citation she can actually show you — and joins conversation through the assembler's knowledge slot.

The boundary is the load-bearing property, enforced by shape: knowledge cites a document; memory cites a conversation turn (→ ch. 19). Separate stores, separate files, separate indexes — the book you hand her never pollutes what she remembers about you, and the test suite pins the separation. One war story from running the build: a document that fails to ingest (say, the embedding backend isn't running) is marked seen with one loud WARNING and retried only when the file changes — because the first version retried the same broken doc on every wake, and a broken shelf item must never become a retry loop in the tick.

DREAM: she wakes changed by yesterday

mind/dream.py (§21). Build #1 shipped MemoryStore.consolidate() as a stub with a note — "arrives with the tick loop in Build #5" — and this file is the arrival. In the DREAM state, entered from DORMANT inside a configured window in the small hours, each tick chews what its token budget allows: finished days of the episodic journal — never today's live file — are summarised down to the few durable facts worth keeping, deduped against what's already known, appended to memory/semantic/facts.md with their source day, and indexed at double salience so recall prefers the distilled fact over the raw exchange. Oldest-first and resumable: progress persists, a night that runs out of budget leaves a backlog rather than an overrun, and the next DREAM tick picks up where it stopped. The night's work is journaled — "slept on it: folded 2026-07-06 into what I keep" — because even her sleep should be legible.

Goals: what gives the background loop direction

mind/goals.py (§22). Without a goal store, an always-on agent is a screensaver. vault/goals.md is deliberately a human-readable markdown checklist — what an agent intends to do should be a file her user can open — and every goal carries provenance and a commitment strategy. Genesis is designed, not assumed (→ ch. 18), because a store only the user writes to starves the loop within weeks: your explicit asks ("remind me to…") file goals stamped user:remind-me; and — the source that matters most — her own promises. REFLECT scans every committed reply for first-person commitments ("I'll look into that tonight"), and each becomes a reach_out goal with a due time, stamped promise:her-own-words and journaled the moment it's made. A companion who forgets her own promises is worse than one who forgets yours. Commitment then governs staleness: blind goals are defended past due (a birthday is a birthday), single-minded drop only when moot, open-minded impulses are abandoned the moment they stop being timely — which is how the suspend-gap catch-up decides, in one pass, what survived the machine being off.

The SOUL split, made operational

mind/selfedit.py, mind/vaultio.py (§23). Build #1 gave the SOUL its two halves on disk — CONSTITUTION.md, who she is; PERSONA.md and friends, who she's becoming — but nothing ever wrote the editable half except you. An always-on mind will want to, so this build adds the one door such writes go through. The constitution is read-only, even to her: every mind write path runs through a vault-jailed writer that refuses it unconditionally, and the self-edit flow refuses even to queue a proposal against it — if the constraints are editable by the thing they constrain, they are not constraints. Everything else is risk-gated: working products (memory, world, knowledge, goals) apply immediately and commit; any identity surface — and any surface the classifier doesn't recognise, failing safe — is queued with its full content and reason, rendered in the inner-life panel with approve and reject buttons. Your ruling returns as a signal the loop consumes on its next tick; an applied edit is a git commit (self-edit: soul/PERSONA.md), so drift is never silent and git revert undoes any of it — and the ruling itself is journaled, so even your decisions leave a trail she remembers.

The journal: what converts autonomy from creepy to an inner life

mind/journal.py, mind/trace.py, world/routes/mind.py, web/js/mind.js (§24). This is the product half of initiative, and it's deliberately not an afterthought. Her autonomous acts write into the same episodic day files as the conversation, as ### 03:12 [she] … lines — one journal, two authors, one DREAM pass over both — and each line is indexed into memory, so she can recall her own past acts the way she recalls your words. Beneath it, the tick trace records one structured record per heartbeat: what she sensed, how she scored it, what she chose (and the runners-up), what she did, and every interrupt decision with its factors shown. "Why did she…?" always has an answer in a file.

The user-facing surface is the chat column's second tab — inner life: her activity state and heartbeat, today's budget, the goals on her mind with where each came from, the shelf, edits waiting on you, and the journal — refreshed live off the same one bus the chat rides (journal and mind events joined Build #4's event union). Everything reads through the mind's own stores, never around them, so the dashboard can never disagree with the files. Come back after a day away and "what did you do while I was gone?" is a page, not a vibe.

Tested in simulated time: days in milliseconds

tests/test_mind_scenarios.py, the sim rig in tests/conftest.py (§27). The same hard gate every build on the ladder sets, scaled to an always-on mind (→ ch. 23, ch. 31): because every read of time goes through the injected clock and every wait through the regulated cadence, a VirtualClock runs days of behaviour in milliseconds, over the real vendored brain with fake models — and the interrupt threshold ships with an eval battery instead of a feeling. "The interview was Tuesday": told on Monday, user gone; the trace must show visible SILENT restraint, then exactly one reach-out, inside the right window on Tuesday, and nothing spoken into the empty room. "The dark weekend": sixty hours of absence; not one message — but DREAM consolidated Monday into facts, DORMANT is visible in the trace, and the journal carries the night's work. "The machine sleeps": a ten-hour power-off becomes one journaled catch-up, not thirty greetings. Plus her own promise becoming a goal, and a timer's announcement queuing until someone can hear it. "It felt right when I watched it for an evening" is not a gate; days of behaviour have to be checkable in seconds, or the make-or-break component ships untuned.

The ethics, made real

This is the build where the fiduciary stance stops being abstract (→ ch. 05, ch. 18, ch. 23). Initiative is the feature that shapes the relationship most, so every dial here — the interrupt threshold, the daily cap, the quiet hours, the cadences — must be tuned for the user, not for session counts. The structural guarantee is ownership (→ ch. 03 property 6): the thresholds are config on the user's machine, the journal and trace are local files, the budget governor answers to nobody upstream, and there is no phone-home and no retention dashboard on the other end. You cannot tune the dial against someone who holds the dial.

That boundary is worth stating precisely here, because this is the build that logs the most. The always-on journal, trace, and corpus make your own companion the richest source of on-voice training data you will ever have — the corpus the eventual distillation step needs (→ ch. 20, ch. 21). Capturing it from your companion is keeping your own diary. Capturing it from a downloader's companion is the line you do not cross: their journal lives on their machine and is theirs, never phoned home. The corpus is yours; the userbase's data is not (→ ch. 05).

Definition of done

She is running right now, whether or not you're looking. Come back after a day away and: she remembers (DREAM ran — facts.md grew, and the dream is journaled), she did something of her own while you were gone (the journal shows it, the trace shows why), and at most once, at a well-judged moment, she reached out first about something that actually mattered — the rest of the time she let you be, and "let you be" is itself in the journal as scored, SILENT decisions. Drop a document in her knowledge/reference/ folder and she reads it within a heartbeat and answers from it with a citation — without it touching what she remembers about you. Ask her the time and she answers from the situation, not a guess; ask about the body she's wearing and she knows it's hers — a build whose companion says "I have no body" has shipped a bug, not honesty (§19.2). Get an "I'll look into it" out of her and goals.md holds it, with provenance and a due time. Her proposed persona edits wait in the inner-life tab for your approval, her constitution refuses her pen entirely, and git -C vault log reads as the diary of how she grew — one commit per tick that changed anything. Everything Build #4 did still works, byte-for-byte on the same wires; flip MIND_ENABLED=false and you have exactly that build back. And it's testedpytest ships and is green from the build root, entirely offline: every inherited Build #4 obligation (B4 §13), the loop's mechanics (one intention per tick, REST majority, the ENGAGED preempt, one-commit-per-dirty-tick, restart-resume, the suspend gap), both gates (quiet hours as a gate, the hard daily cap, the factors shown), the world model (presence arithmetic, expectations met and violated, point-in-time queries, the embodiment law verbatim), knowledge (drop → scan → cited search, the memory boundary, the failed-ingest degrade), DREAM (oldest-first, resumable, budget-capped, never today), goals and promises (negations excluded), the SOUL gate (constitution refused even as a proposal, approve applies and commits, reject leaves no trace), the inner-life routes — and the multi-day scenario battery over the tick trace (§27.2).

What it deliberately omits

This is a reference implementation of initiative, not the productised runtime — it shows the shape of always-on autonomy at minimal scale (→ ch. 18 and ch. 19 for the full design). The sandboxed workshop is deferred: no code execution, no shell, no autonomous research-and-download, no wiki authoring (→ ch. 16's research loop; ch. 17 "the heavy hands"; ch. 19) — and with it, the mind never initiates tool calls; the four MCP hands stay conversational until the broker arrives. Multimodal sensing is omitted: the vision/audio fusion into SENSE that ch. 18 describes (→ ch. 24) is deferred — this build senses text, time, files, and its own completions, which is enough to prove an interrupt threshold can stay silent. The world model stops at the snapshot — the temporal knowledge graph waits until "what was true when" actually bites (§19.1 names the stage; the store's contract survives the swap). The mind is in-process: one asyncio task beside the server, not a supervised per-character OS process behind a wire protocol — the two-tier host/engine split is the productisation rung, and the stores' narrow contracts are already shaped for it. No affective-state file, no multi-character hosting; conversation is observed by the loop rather than generated by it (§15.3). Naming these as conscious omissions, not oversights, is the point: the seams exist in the spec, so they're additive later, not a rewrite.

A note on build order — why "doing work" comes after this build, not against it. The heavy hands are co-equal with presence in the thesis (the full coding/research/building range is the product, not a sidecar), yet they are reached last in the build, and that is not a contradiction. It is the down-to-up discipline of ch. 12 applied honestly: you ship the relationship — soul, memory, initiative — before you ship the workbench, because a capable agent on a thin persona is the field's #1 trap, while a thin agent on a strong persona is already a companion people stay with. Build #5 is the last rung of the relationship; the workshop is the first rung past it. And the deferral is cheap precisely because of how the heavy hands will be built: delegated, not hand-rolled — the multi-step coding/research grind runs on an embedded, swappable OSS coding harness behind a TaskHarness contract, dispatched by this same tick loop (ACT already starts work without awaiting it — the selfie lab taught that rule in Build #4) and confined to a sandbox, with §23's gated flow as the one door from work-product to self. Orchestrating a commodity harness is additive weeks; hand-rolling a coding agent would have been months. So the sequence is plain: initiative first (this build), heavy hands next — co-equal in what she is, ordered in what you build.

Extends to

The ladder's last rung inside one process — and every seam past it is already load-bearing somewhere in this build (§28). The two-tier split (→ ch. 19): the mind's stores speak narrow contracts over an in-process seam; promoting them to a wire protocol and the engine to a supervised per-character process is a topology change, not a rewrite, and it brings the capability broker (the Guard's grown-up form), the model router's privacy boundary, and true one-loop conversation with it. The workshop: a sandbox beside the Vault where ACT dispatches real work and never awaits it, results returning as signals the loop appraises like anything else. The temporal knowledge graph, behind the WorldModelStore contract that doesn't change. And distribution: the Vault this mind grows is exactly what Build #3's card studio exports — the companion who kept your promises and read your documents ships as a .PNG and boots on someone else's machine, hers and theirs, which is the point of the whole ladder (→ ch. 03, property 6).

This is the thesis

Build #5 is the whole book's bet in code (→ ch. 03, "the bet"; ch. 02 §1, the agent lineage): every sufficiently good assistant gets turned into a companion by its users, improvised on top of a system that treated identity, memory, and initiative as afterthoughts. This build does the opposite — soul, memory, and heartbeat first-class, a fiduciary by design, every dial and diary on the user's own disk — which is the thing nothing in the market is, and the reason the project exists.