The cognitive state, as data structures
The Vault is not an undifferentiated pile of text. It holds distinct kinds of state with distinct update rules — the lesson BDI (→ ch. 02 §1, BDI and cognitive architectures) and CoALA (→ ch. 02 §4) both teach and naive single-scratchpad agents ignore. Seven surfaces matter:
SOUL: constitution vs persona
The character's identity is split into two surfaces with opposite mutability, and this split is the keystone of the whole safety story:
- Constitution (
CONSTITUTION.md) — immutable, signed. The invariants: the fiduciary duty, the hard limits on capability and egress, the safety rails, the things that make her her. The engine cannot edit this file; the host refuses writes to it. (The Ouroboros lesson: if the constraints are themselves editable by the thing they constrain, they are not constraints.) - Persona (
PERSONA.md) — editable. Voice, manner, appearance, inner life, values-in-practice, learned habits, and the growth/reveal tiers; her preferences live here too, in frontmatter and prose, rather than in a separate file. The engine may propose changes here, through the gated flow below.
Around these sit the rest of the editable identity files —
SCENARIO.md (the situation and the return
greetings), EXAMPLES.md (demonstrated voice),
WORLD.md (the lorebook), NOTES.md (creator
notes) — and the soul.yaml manifest that declares how they
all flatten into a portable character card. This split-by-mutability and
this exact file set are the shipped yuri-soul
reference implementation (→ ch. 07, the V3 .PNG; ch. 33,
Build #3): the SOUL is the working home the runtime reads on every wake,
and the card is the export you hand to someone else. A fresh card starts
the relationship at zero — the runtime-only MEMORY.md and
USER.md are part of the soul but never baked in.
One more file has a lifecycle of its own: BOOTSTRAP.md,
the consumed-once first session (→ ch. 28, the first
chapter). It's author-shipped like the persona, but loaded only
while it exists — it carries the first-ever cold open and a
scripted getting-to-know-you journey (each beat pinned to a
USER.md/MEMORY.md slot). On first run the
engine works it into the opening conversation, then
retires it —
git mv soul/BOOTSTRAP.md soul/onboarded/BOOTSTRAP.done.md,
committed, so the git history is the record rather than a stray
.bak. File-presence is the "has she been
introduced yet?" flag, which is exactly the kind of state the git-backed
vault stores as a file rather than a hidden bit. Only its cold open is
baked into an exported card (as the card's first_mes); the
journey and the retirement never leave the box. This is why
SCENARIO.md above holds only the return greetings
— the first-ever meeting lives in the bootstrap, the coming-home lives
in the scenario.
Memory tiers
The SOUL ships a single flat MEMORY.md (empty on a fresh
card); at runtime the autonomy engine elaborates it into four tiers plus
a consolidation process, covered in full in ch. 15 and summarised here
only as it sits in the architecture:
- Working — small, in-context, current scope (held by the engine, not a persisted file).
- Episodic (
memory/episodic/) — append-only log of events and her own autonomous acts. This is simultaneously the audit trail and the "what I did while you slept" product surface; it is written proactively, every tick. - Semantic (
memory/semantic/) — consolidated, embedded, durable facts. - Procedural (
memory/procedural/) — a self-authored skill library, one folder per skill. Each skill is a self-contained directory: aSKILL.mdmanifest (what it does, when to use it, its interface) beside the versioned, tested code it describes, plus any assets it needs. The folder is the unit — the same "the unit is a directory you can copy" bet the whole Vault makes — so a skill is portable (share one by handing over its folder) and drop-in compatible with the wider skill ecosystem (Agent Skills, OpenClaw), rather than a looseskill.mdlinked-by-convention to a sibling script. This is the Voyager lineage (→ ch. 02 §4), packaged the way 2026 skill systems actually package: she can acquire and keep new capabilities, not just recall facts.
The promotion of episodic → semantic happens in the DREAM activity state (→ ch. 18, ch. 15): overnight consolidation, the thing that makes her wake up changed by yesterday rather than merely holding a longer log.
The memory contract
The four tiers above describe the data; this is the interface over them — the narrow contract the engine sees so that the backend underneath stays swappable (→ ch. 12; the "replace the file-Vault with a database and the loop never knows" promise earlier in this chapter). Define it first, before Build #5 (→ ch. 35), because the seam is cheap to specify now and expensive to retrofit once a concrete store has leaked its shape into the loop. Language-neutral, the engine depends only on this:
MemoryStore # interface; the backend (files · pgvector · graph) is the implementation
# WRITE — hand over raw material; the store decides what is worth keeping
remember(record) -> WriteResult
# extract → embed → store. Low-confidence or externally-sourced facts land in
# QUARANTINE, not durable semantic memory, until a second turn corroborates
# them — promotion, not capture, is the trust boundary (→ ch. 15).
# READ — blended ranking (similarity · recency · salience, minus an over-recency
# penalty), not raw similarity; returns nothing when nothing clears the
# retrieval threshold
recall(query, k) -> list[Memory] # ranked; may be empty by design
# MAINTENANCE — the DREAM pass calls this; never on the hot path
consolidate() -> ConsolidationReport
# dedupe, promote episodic→semantic, decay/archive cold low-salience entries,
# reconcile conflicts (newer supersedes older — supersede, don't delete)
# TRUST — "forget that" has to actually work
forget(selector) -> int # count removed; for facts, supersede-not-delete
# OBSERVABILITY — the dashboard reads memory *through* this, never around it
inspect(selector) -> list[Memory] # what she knows and why: source_turn, confidence, last_recalled_at
Three things make this a real contract rather than a wishlist of method names:
- It travels as a bundle. A backend swap moves the
store and its extraction prompts, its ranking weights, and its
consolidate()policy together — they are one replaceable unit, not four (→ ch. 15, the pipeline). The partner model, affective state, and goal store are sibling Vault surfaces behind their own parallel contracts, not part ofMemoryStore. - Its tests are part of the interface. Any implementation must clear the ch. 15 eval battery — recall accuracy and false-memory rate — in CI before it ships. The contract is the signatures plus that battery; a backend that passes the types and fails the honesty probe does not satisfy it.
inspect()is load-bearing, and it is what keeps swaps honest. The dashboard and the partner's "what does she know about me?" are answered throughinspect(), which is why the file-Vault implementation gets it almost free (cat,git diff). A hosted store (Mem0, Letta) can satisfy every signature here and still break the project's thesis, because it cannot answerinspect()ownably — so the intended swaps stay local (files → local pgvector → local graph), and a cloud memory service is a clean swap that quietly forfeits the moat (→ ch. 15 on the economics; ch. 05 on sovereignty). Which backend a given card uses is declared in itsextensions.yuriosblock.
The knowledge layer (RAG) and its contract
Memory is the state layer — what's true about you and this
relationship. Knowledge is the world layer — what's true about
the canon, the docs she's been given, the sources she's read (→ ch. 16,
which will not let the two be conflated). For a reactive companion the
knowledge layer is optional polish; for an agentic waifu who reads,
researches, and keeps her own reference shelf it is first-class
infrastructure. It lives in knowledge/, a sibling to
memory/, behind its own contract — pointedly not
folded into MemoryStore, because the one rule ch. 16 holds
is that knowledge and state are different stores with different write
paths:
KnowledgeStore # sibling to MemoryStore; same swappable-backend discipline
# INGEST — a doc or a fetched URL becomes retrievable chunks
ingest(source) -> IngestResult
# chunk → contextualize (a 50–100-tok situating blurb, → ch. 16) → embed → hybrid-index.
# source = a dropped file (.md/.pdf/.txt) or something the network effector fetched.
# SEARCH — hybrid retrieve + rerank; every chunk carries its provenance
search(query, k) -> list[Chunk] # ranked; each Chunk cites its source doc + span
# AUTHOR — she compiles/updates a wiki page from what she's read
author(page) -> WriteResult # DREAM-driven; routed through the gated self-edit flow
# TRUST + OBSERVABILITY — same shape as memory
forget(selector) -> int # drop a doc and its chunks
inspect(selector) -> list[Chunk] # what's on the shelf, and where each chunk came from
The boundary that keeps the two from collapsing into each other:
knowledge is citable to a document; memory is citable
to a conversation turn. The book you hand her is
knowledge; "you told me you play bass" is memory; her self-authored wiki
is knowledge she wrote — still citable (to her page and its
sources), still not relationship state. Two seams wire it into the rest
of the architecture. First, research she pulls off the network (→
broker, below) is ingested here rather than dumped into a
prompt — the intake path is the workshop's downloads/, the
durable home is knowledge/. Second, the page-authoring —
compiling a wiki out of what she read — is exactly the kind of
consolidation the DREAM pass does for memory (→ ch.
18), and it lands through the gated self-edit flow, because a durable
reference doc she wrote should be as diffable and revertible as anything
else in the mind. Groundedness is load-bearing here in a way it isn't
for chit-chat: when she cites her own shelf to justify a
self-improvement, inspect() and per-chunk provenance are
what let you audit "she changed X because the book said Y" (→
ch. 16 on evaluating retrieval).
The world model (situation model)
Memory is the past and knowledge is the world's
facts; neither is the present. The world model is her
live, structured picture of the situation she is in right
now — the entities present and their current states, the active
threads, the time and social context, and her expectations of what comes
next. It is the B in BDI (→ ch. 02 §1, BDI and
cognitive architectures) given a home: the surface SENSE writes to and
APPRAISE/DECIDE reason over, so the loop sees a situation and
not a stream of unrelated signals (→ ch. 18, "The world model"). It
lives in world/, sibling to memory/ and
knowledge/, behind its own contract — and pointedly
distinct from both, by the same boundary discipline that keeps memory
and knowledge apart:
- knowledge is timeless and cites a document;
- memory is past and cites a conversation turn;
- the world model is present-tense and cites a live, time-stamped belief about the situation now.
The fitting implementation is a temporal knowledge
graph — entities and relations carrying a time dimension
(valid-time + system-time), so "what was true when" is a
first-class query rather than an inference over a pile of episodes (the
Zep/Graphiti pattern, → ch. 02 §4.8; ch. 15). Keep it local and
inspectable for the same reason memory stays local — the moat
is in inspect() (→ above). Two layers: a durable
graph/, and a small situation snapshot
(situation.md, the Grounded-Situation-Model "stage")
assembled each tick and injected into the prompt as "here is the state
of things now." Build #5 can ship with just the snapshot —
assembled from working memory + the partner model + a
recall() — and graduate to the graph when multi-hop /
temporal queries actually bite (→ ch. 35).
WorldModelStore # sibling to MemoryStore/KnowledgeStore; same local, swappable discipline
# OBSERVE — fold a SENSE signal (text · fused vision/audio) into the situation
observe(signal) -> UpdateResult
# extract entities/relations/state-deltas, write as bi-temporal, time-stamped
# BELIEFS (not facts); perceived/low-confidence state stays QUARANTINED (→ ch. 15)
# SITUATION — the live "stage" the prompt is built from
situation(scope) -> Situation # present entities, states, active threads, time/context
# QUERY — point-in-time + relational: "what was true when"
query(q, *, at=now) -> list[Fact] # bi-temporal; the temporal-KG query
# PREDICT — roll out expectations (LLM-as-world-model); store as checkable beliefs
expect(situation) -> list[Expectation] # a later observe() scores surprise = prediction-error
# TRUST + OBSERVABILITY — same shape as memory and knowledge
forget(selector) -> int
inspect(selector) -> list[Fact] # what she believes is the case, and since when
Two properties make it load-bearing for this project.
Surprise is salience. When observe()
contradicts a stored expect(), the prediction-error is a
first-rate signal for the APPRAISE pass — often the very thing worth a
tick or a gentle word (→ ch. 18, the salience model).
Belief-tracking beats state-tracking. The
companion-specific upgrade is to annotate facts with whose
belief they are, and to model where your picture of the
situation diverges from hers — theory-of-mind / common ground (→ ch. 02
§4.8). That divergence is what turns "she remembers facts about me" into
"she understands my situation," and it is why the partner model lives
inside the world model rather than beside it.
Partner model — the structure behind "she actually knows you"
The most under-built data structure in commercial companions, and the
one that most directly produces the felt sense of being known: an
explicit, evolving USER.md — her theory of mind about
you (the partner model). It is the most important
region of the world model above — you are the central entity in
her situation — pulled out into its own file because it is the one
region worth versioning by hand and reading on every wake. Your state,
your preferences, the arc of your relationship, the things you've told
her not to forget. Two design notes make it load-bearing rather than
decorative:
- It is a file the partner can read. Transparency is the feature: this is Paradot's "learned facts about you" UI done honestly, where the model of you is not a hidden engagement asset but a document you can open, correct, and delete (→ ch. 05, ch. 28).
- It is distinct from semantic memory. Semantic memory is what she knows; the partner model is what she believes about you specifically and how she should act on it. Collapsing the two is why generic chatbots "remember" facts but still feel like they don't know you.
Affective state
A small, persistent emotional_state.json — valence,
arousal, and a few named drives (curiosity, bonding, helpfulness) — that
modulates prompt assembly: tone, risk tolerance, what gets
attention this tick. It is cheap to implement and has an outsized effect
on aliveness, because it gives behaviour a slow-moving mood rather than
resetting her to neutral every turn. It is state, not
performance: the file is the cause of the tone, not a label
describing it after the fact. Which means it needs an update
rule, or it degenerates into exactly that label: events apply
bounded deltas (REFLECT proposes them like any other write); between
events, every value decays toward a per-persona baseline with a time
constant measured in hours (applied each tick, on the injected clock);
DREAM re-centres the baseline slowly, across weeks. The split this
yields is worth saying aloud — mood is state, temperament is the
SOUL: the baseline and its time constants ship in
PERSONA.md as who she is, the JSON is only how
she is right now, and both are numbers you can open.
Goals & intentions
A persistent goals.md of projects, todos, and intentions
with priority, state, and deadlines. This is what gives the background
loop direction — what keeps ACT from being random busywork. The
DECIDE phase of the tick loop draws its candidate intention from here (→
ch. 18). Without it, an always-on agent is a screensaver; with it, she
is pursuing something while you're away. Where goals enter the
store is a designed thing, not an assumption — four sources (the user,
her own extracted promises, curiosity, DREAM maintenance), each goal
carrying its provenance, with self-created goals passing the same risk
gate as self-edits (→ ch. 18, "Where goals come from").
The context assembler: how the surfaces become her
Every surface above exists to be composed into a prompt, and the thing that does the composing is the most load-bearing code in the runtime — it runs on every model call and decides what she is this turn — so it gets a name and a contract rather than living as the phrase "prompt assembly." The context assembler takes the SOUL, the situation snapshot, the partner model, the recalled memories, the knowledge chunks, the affective state, and the recent turns, and produces one composition, under explicit policy:
ContextAssembler # deterministic composition of Vault surfaces → one prompt
assemble(intent, budget) -> Context
# SOUL + situation() + USER.md + recall() + search() + affect + recent turns,
# under an explicit PER-SURFACE TOKEN BUDGET — when recall, knowledge, and
# situation all want the window, who loses is a declared policy, not an
# accident of assembly order; identity is never crowded out by retrieval.
# Deterministic given (vault state, template_version): same state, same prompt.
template_version -> str # pinned and logged with every turn (→ ch. 30/31,
# the corpus-log schema field this names)
Three things make it contract-worthy. The token budget is a
real decision currently made nowhere else: every store can only
offer candidates; something must decide how much of the finite window
each surface gets, and if that decision isn't declared policy it becomes
an accident of code order — the quiet way retrieval crowds out identity
and she stops sounding like herself on busy days. Determinism is
what makes evaluation possible: golden transcripts (→ ch. 23)
and the corpus log (→ ch. 30/31) are reproducible because
template_version names the exact composition that produced
a turn — the field was already in the corpus schema; this is the module
it points at. And ENGAGED and autonomy prompts are two profiles
of one assembler, not two assemblers — the fast conversational
path and the slow deliberate tick differ in budget and emphasis, never
in which mind gets composed (→ ch. 18, "One loop").