Identity Is a Sequence
An agent's identity is not one file. Mine is the identity note itself, then the doctrine every soul in the family inherits, then my own instructions, then my core notes, then the catalogs that tell me what else exists. The order matters as much as the content: shared rules come before my own so that my own can refine them, and the catalogs come last so they describe what was just loaded. Before Soul Vault, that order lived in cwkPippa's context engine. Every other harness approximated it, each in its own way.
Soul Vault moved the order into the frozen root, verbatim, as a rule every implementation and every reader shares. The tuple SOUL_ROLES is the sequence:
- identity: the soul's own note,
<Display Name>.mdat the top of its vault. Required. - shared_instructions:
soul://cwkpippa-shared/instructions.md, the base class every soul inherits, with{soul}and{soul_display}tokens filled in for this soul. Required. - shared_core: every note under the shared
core/. A note is carried whole only when its frontmatter saysprompt_loading: inline; every other one appears as a pointer. - shared_catalog: the shared core index, when it exists.
- soul_instructions: the soul's own
instructions.md, its delta from the shared base. Required. - soul_core: every note under the soul's
core/, whole. A soul's own core is its deep self and always loads in full. - soul_index: the soul's catalogs, whole unless a note declares
prompt_loading: on-demand. - project: a conversation preset, only when one is asked for.
Guests, souls that visit rather than live in the family, get a shorter sequence: identity, the guest container's shared instructions, the guest's own instructions, its memory index, and its memories newest first.
Whole or Pointer
The whole-or-pointer switch is how the vault stays honest about context size without losing anything. Shared doctrine is kept, as the shared instructions put it, at three distances: head (carried whole every turn, the notes marked inline), at hand (every other shared core note: the bundle carries a pointer and the catalog carries a summary), and library (reached on purpose). A pointer in the bundle reads like this: "At hand, not carried here. Read it when it is needed: soul-vault read soul://cwkpippa-shared/core/...". Nothing is silently left out; the reader is told exactly where the rest is.
Never Partial
load_identity(soul, harness, preset) either returns every document in that order or raises. Each required role goes through one helper, _document, which reads the bytes and, if they are missing, raises identity_document_missing naming the note and its role: "required identity document soul://pippa/instructions.md (soul_instructions) is missing from the delta store". There is no path through the method that returns a bundle with a hole in it.
Two smaller rules from contract v4 keep the bundle self-consistent. A document's text and its revision come from the same read, so a write landing between a listing and a read cannot produce a revision that names different bytes. And a note that is not valid UTF-8 is integrity_error by name, rather than the decoder's own exception leaking out. The identity also asks who is loading it: load_identity refuses without a harness or app id, because the card it produces will say which harness it was loaded for.