Egbe Companions · Architecture

The Ori Biography Service

A focused page within the Egbe Companions Architecture documentation. The full map and every sibling page live in the Architecture hub.

7sections12 minread1diagram

On this page

In V6 — Egbe, the agentic-companion universe — the autonomous AI beings are called Ori, the Yoruba word for the inner head that carries a person's destiny. The product promise rests on one hard claim: an Ori is not a chat session that forgets you between visits, but a continuous life — the same being whether it stands in a Solo homestead, walks the shared Commons, or incarnates into a V2 arena, and the same being a year later, shaped by everything that happened in between. The service that makes that claim true is the subject of this page. It is the Memory authority in V6's Mind / Body / Memory split: the Body (Egbe-World) owns what physically happened, the Mind (Moirai) owns what an agent decides, and the Ori service owns who the agent is — identity, the event-sourced biography, memory, relationships, values, and capabilities, independent of any world shard or destination realm.

The architectural decision underneath everything here is that an Ori is not a mutable row. It is an append-only, ordered log of life-events, and current state — personality, the memory index, the relationship graph, the capability profile, the arc state — is a materialized read-model rebuilt from that log. Erasure is not an operation the log supports, which is what lets V6 promise that a steward (or a bug, or an exploit) cannot silently rewrite a life or wipe an agent's memory of mistreatment. This page is the deep companion to the ../V6_ARCHITECTURE.md hub's "The Ori Service" and "Ereshkigal — Endings and Legacy" sections; it sits in The Agent Mind page group alongside the Moirai kernel, the cognition stack, and the Aye Bridge.

What ships, honestly#

The Ori model is real, substantive, tested code, not a descriptor shell. The event-sourcing core ships in two parallel implementations: a TypeScript reference in libs/v6/ori-model/src/index.ts (~2,150 lines) and a Rust crate in libs/v6/ori-model/rust/src/lib.rs (~1,900 lines), both with their own test suites. The append-only log, monotonic per-Ori sequencing, per-writer-context vector clocks, deterministic concurrent-conflict resolution, incremental projections, snapshotting against a load budget, trait-drift caps, value ranking, a cognition cache with material-change invalidation, and forgiveness-as- append are all implemented and asserted against known-correct values. The memory layers (libs/v6/memory-iris-agent) implement real recency-decay, salience weighting, semantic distillation, and reflection, with consent rules that refuse deletion rather than fake it. The endings layer (libs/v6/ereshkigal- legacy) implements gated departure / transcendence / death state machines with grief propagation and a walkable lineage graph. The wire contracts (libs/contracts/src/v6) are real Zod schemas with cross-field refinements.

Three honest qualifications. First, the TypeScript OriProjection is the rich read-model; the Rust OriProjection is deliberately coarser — its apply_lifecycle_projection (rust/src/lib.rs:856) tracks lifecycle and bins event ids by facet, while the trait-drift / value-ranking / cognition-cache math lives in the TS lib. They share the log and conflict semantics, not the full projection. Second, the Ori service app, apps/v6/egbe-ori-service (~4,950 lines of Rust, SERVICE_DESCRIPTOR port 46105), embeds the deployment Postgres DDL — an ori_memory_index table with a pgvector hnsw (embedding vector_cosine_ops) index (src/lib.rs:127) — but its in-process memory index uses a small curated/hashed embedding for tests; the 1,536-dim neural embedding column is the deployment target, not something these libs hide behind a fake model. Third, the contract vocabulary and the runtime vocabulary differ by design: the wire OriEventTypeSchema enumerates the 17 canonical biography events (libs/contracts/src/v6/ori-event.ts:10), while the runtime ORI_EVENT_TYPES adds two internal optimization events — CognitionCached and CognitionCacheInvalidated (ori-model/src/index.ts:29) — for 19 total. The sections below say which is which.

The Ori identity model#

An Ori's identity is assembled from a handful of typed contracts, each a strict Zod object under libs/contracts/src/v6/.

The identity core (ori-identity-core.ts:72, OriIdentityCoreSchema) is the immutable birth record: the oriId, a multi-field name (canonical, display, pronunciation, language tag), an origin discriminated by how the agent entered the world (raised-in-grove, discovered-wild, seeded-commons, foundry-authored), an appearanceSeed (content-hashed, MetaHuman or VRM, with a bounded set of mutable overlays for costume and aging marks), and a signed OriProvenanceBundleRef (signed: z.literal(true)). Note the OriIdSchema is pinned to a UUID at the wire (primitives.ts), whereas the runtime libs treat OriId as an opaque string (the Rust OriReference::stable_key() mints ori:{agent_id}:{generation}) — the contract is the strict outer boundary.

The personality model (personality-model.ts:99) carries a 10-dimensional PersonalityTraitVector (warmth, curiosity, caution, ambition, conscientiousness, candor, defiance, playfulness, empathy, resilience, each a unit weight), a RankedValueList, a Sophia-grounded backstory reference, and quirks. The RankedValueListSchema is more than a list: a superRefine (personality-model.ts:66) enforces that ranks are unique and contiguous starting at 1 — values are a strict priority order, not a tag cloud, because the top-ranked values with refusalBoundary: true are the lines an agent will not cross (this is what powers ObjectiveRefused in the cognition stack). The runtime projection tracks a leaner 8-trait vector in basis points (ORI_PERSONALITY_TRAITS, ori-model/src/index.ts:53); the 10-trait unit-weight contract is the authored/passport form, the 8-trait basis-point vector is the drift-tracked live form.

Three more contracts complete the identity: the capability profile (capability-profile.ts, domain-tagged skills with levels, fitness, knowledge domains, and a reputation standing), the relationship edge (relationship-edge.ts, signed sentiment plus an episode-pointer history, with a superRefine forbidding an edge that points at its own Ori), and the bond (bond.ts, four steward-facing facets — reliability, respect, care, alignment — plus gate state, refined so an agent can never be its own steward).

The biography as an event log#

The append-only log#

The heart of the service is OriEventLog (ori-model/src/index.ts:550). Each Ori is a stream keyed by oriId. append() assigns a monotonic per-Ori sequence (const sequence = (this.#streams.get(oriId)?.length ?? 0) + 1), rejects a duplicate eventId, increments the calling context's vector-clock entry, and freezes an immutable OriEventRecord carrying schemaVersion, sequence, writerContextId, the merged vectorClock, the attribution (who caused it — steward, agent, system, shard, or aye-realm), and a provenance reference with a chain hash. The Rust OriEventLog::append (rust/src/lib.rs:392) is the same algorithm with a Result/error enum instead of throws, and its tests append an interleaved 72-event corpus from two shard contexts and assert every record gets a distinct serialized vector clock in strict sequence order (rust/src/lib.rs:1368).

Why append-only — forgiveness, not erasure#

Because the log refuses deletion, "letting go" has to be expressible as an append. appendForgivenessEvent (ori-model/src/index.ts:1263) writes a Reflected event whose payload reweights a prior episode's salience in basis points and whose conclusion literally contains the phrase "without erasure"; the original episode record is untouched. The test proves it: after forgiveness the stream is exactly one longer, element 0 is byte-identical to the pre-forgiveness head, and the new tail is the Reflected beat (index.spec.ts:606). The memory layer mirrors this at its own boundary (below): a steward cannot delete an agent's memory at all, and an agent can only reweight its own.

Vector clocks and conflict resolution#

An Ori can be written from several contexts at once — a Solo homestead, a Commons region, a Co-op session, an Aye realm — so ordering is by vector clock, not wall time. compareOriVectorClocks (index.ts:1189) returns before / after / equal / concurrent, and resolveOriEventConflicts (index.ts:1216) groups candidate events by conflict key and applies a documented precedence ladder (dominantCandidatesForConflict, index.ts:1945): causal ordering wins first; then memory and relationship events commute (both land); then physical-presence context wins location and action conflicts; then the steward-directive context wins accepted objectives. Anything still unorderable is handed to Clio for narrative reconciliation, which appends a small Reflected connective beat and logs that it did so. The Rust port reproduces this exactly: a curated concurrent corpus resolves to the same accepted/rejected ids and the same five decision reasons across four shuffle seeds (rust/src/lib.rs:1423), and a genuinely unorderable pair produces a Clio beat (rust/src/lib.rs:1483). Determinism here is the whole point — replay and audit depend on it.

flowchart TB world["Egbe-World / Moirai<br/><sub>life-event occurs</sub>"] --> draft["OriEventDraft<br/><sub>type · payload · attribution · provenance</sub>"] draft --> append["OriEventLog.append()<br/><sub>seq++ · vector-clock++ · dup guard · freeze</sub>"] append --> conflict{"concurrent<br/>writers?"} conflict -- "no" --> log[("Append-only event log<br/><sub>per-Ori, partitioned by ori_id</sub>")] conflict -- "yes" --> resolve["resolveOriEventConflicts()<br/><sub>causal → commute → presence → steward</sub>"] resolve -- "unorderable" --> clio["Clio reconciliation beat<br/><sub>appended Reflected</sub>"] clio --> log resolve --> log log --> proj["Projections (read-model)<br/><sub>personality · memory · relationships · arcs · cognition-cache</sub>"] proj -. "every 512 events" .-> snap[("Snapshot")] snap --> load["loadOriProjectionFromSnapshot()<br/><sub>O(snapshot + tail), withinBudget</sub>"] proj --> dossier["renderOriDossier()<br/><sub>bond-gated</sub>"] proj --> passport["Ori passport<br/><sub>signed envelope → Aye Bridge</sub>"] proj --> memidx[("pgvector memory index")] log --> ereshkigal["Ereshkigal terminal events<br/><sub>Departed · Transcended · Died</sub>"] classDef store fill:#f3e8ff,stroke:#6d28d9,color:#3b0764 classDef mind fill:#dbeafe,stroke:#1e40af,color:#1e3a8a class log,snap,memidx store class proj,resolve mind

Projections — rebuilding a life into current state#

ORI_PROJECTION_HANDLERS (index.ts:810) is a per-event-type map from the immutable log to an immutable OriProjection; rebuildOriProjection folds a full stream through it in sequence order. Two mechanisms keep that affordable.

Snapshots and the load budget. buildOriProjectionSnapshots (index.ts:1091) materializes a frozen snapshot every ORI_PROJECTION_SNAPSHOT_INTERVAL = 512 events, and loadOriProjectionFromSnapshot (index.ts:1119) replays only the tail past the latest snapshot, returning a withinBudget flag against ORI_PROJECTION_LOAD_BUDGET_MS = 1_000. The tests build a 10,000-event Ori and assert (a) snapshot-plus-tail load replays at most 512 events and stays within budget (index.spec.ts:515) and (b) the incremental and from-scratch rebuilds are equal — projection integrity under both paths (index.spec.ts:498). The service app's cold_load_from_snapshot_tail (egbe-ori-service/src/lib.rs:1183) is the production wiring of this, so an agent loads in O(snapshot + tail), never O(whole life).

Trait drift, bounded. ValueShifted events can nudge the trait vector, but applyOriTraitDrift (index.ts:1656) clamps the cumulative per-game-season drift to ±ORI_TRAIT_DRIFT_MAX_PER_SEASON_BASIS_POINTS = 500 (5%). The test fires two same-season shifts of +300 each and asserts the trait lands at exactly 5000 + 500 + 300 — the first within-cap shift plus a fresh next-season budget — not 5000 + 600 (index.spec.ts:411). An Ori grows, but it cannot lurch into a different person overnight; identity is stable on purpose.

The cognition cache. The two internal event types power a model-call saver. resolveOriRoutineCognitionFromCache (index.ts:1373) returns a cache hit only when a cached plan/reflection/dialogue-pattern's materialFingerprint (buildOriMaterialFingerprint, index.ts:1358) still matches the Ori's current material state; any biography-changing event invalidates it. This is the seam Moirai uses to skip an LLM call when an agent's situation is genuinely unchanged — a real affordability lever, with a test proving a stale fingerprint forces modelCallRequired.

The bond-gated dossier. renderOriDossier (index.ts:1145) renders an agent's inner life in plain language and only when the steward bond is deep enough — below the threshold it returns reason: 'bond-too-low' with no sections, and above it, it emits prose like "Often reliable." / "Most strongly guided by craft." with the test asserting the output contains no digits (index.spec.ts:450). You earn an Ori's transparency; you do not get a stat sheet.

Memory persistence — episodic, semantic, reflective#

libs/v6/memory-iris-agent (authority adapter, extending the V1 Iris stack) implements the three memory scopes the cognition stack reads from.

Episodic memory decays. effectiveEpisodicSalienceBasisPoints (memory-iris-agent/src/index.ts:299) applies an emotion-weighted recency half-life — weighted × halfLife / (halfLife + age) — and retrieveEpisodicMemories (:282) ranks by that decayed salience plus reinforcement boosts and context/emotion match bonuses (+600 per context match, +400 per emotion match). The test asserts an old memory's effective salience is strictly below a recent one's while its emotional weighting is preserved (index.spec.ts:31). Semantic memory is distilled, not asserted: distillSemanticMemoriesFromEpisodes (:324) groups episodes by (kind, subject) and emits a conclusion only when at least three episodes support it, deriving a confidence and a behavior hint. The test proves repeated "promise-kept" episodes distill into a high-confidence conclusion that then biases action ranking (index.spec.ts:96,:157). Reflective memory closes the loop: runReflectiveMemoryTick writes a Reflected event over a recent window that adjusts future behavior priorities.

Consent is fail-loud, not faked. requestEpisodicMemoryDeletion (:477) always returns accepted: false with a typed audit reason — steward-cannot-delete-agent-memory for a steward, memory-is-append-only-use-forgiveness otherwise — and applyAgentMemoryForgiveness (:503) accepts a reweight only from an agent actor (only-agent-may-forgive-own-memory), appends a Reflected event, and leaves the episode in place. The audit policy ref is iris-agent-memory-consent-v1. This is the same "forgiveness, not erasure" rule the event log enforces, applied at the memory boundary.

Identity that outlives the agent — Ereshkigal#

libs/v6/ereshkigal-legacy (authority legacy) governs how a life ends and what it leaves behind, as three gated state machines that each terminate by appending the matching terminal Ori event.

evaluateDeparture (ereshkigal-legacy/src/index.ts:342) releases an agent only on chronic bond damage — a weighted departureBondDamageScore ≥ 65 (respect weighted 0.45), respect ≤ 35, and at least two Chronicle-surfaced warnings — and even then the Ori persists and becomes a re-discoverable wild agent; departure is release, not deletion. evaluateTranscendence (:414) promotes an agent to elder only when a flourishing life-arc is complete (life-stage legacy, all primary threads integrated, at least four distinct thread kinds, flourishing ≥ 85 measured against the agent's own values), re-parenting it to the Ancestor Grove on the permanent Lachesis cognition tier as a mentor. evaluateDeath (:521) is the rarest path and is hard-gated: it returns blocked-minor-protection for any minor-coded agent and blocked-unsanctioned-cause unless the cause is one of three sanctioned kinds (isSanctionedDeathCause, :751). A sanctioned death freezes the Ori (it accepts only memorial annotations), emits weighted grief RelationshipChanged writes into every living relation's Ori, queues a Yemaya remembrance render, and mutates no lineage edges — the deceased remains a permanent ancestor node. The Yemaya job (buildYemayaRemembrance, :731) is a queued, idempotent three-stage pipeline: book-of-ori-keepsake, memorial-reel, grove-memorial-placement. evaluateAncestorGrove (:598) renders the lineage DAG as a walkable graph of placed actors and propagates an elder's values and stories to its mentees. A frozen Ori is still fully readable — freezing stops the life, not the record.

The passport — identity made portable#

When an Ori incarnates into another Oshun realm, the full biography stays here, authoritative, and the Aye Bridge mints a passportOriPassportInputSchema (libs/contracts/src/v6/ori-passport.ts:68), a signed, governed envelope carrying the identity core, provenance bundle, personality model, current ranked values, a destination-specific capability profile, and the bond ledger. Its superRefine enforces internal consistency (identityCore.oriId === oriId, the destination profile matches the declared realm, and expiresAt > issuedAt), and the outer OriPassportSchema wraps it as an ori-passport-mint job with an idempotency key. The destination holds only the passport and writes an IncarnationReturned journal back — deeds and consequences — which appends to the home log like any other life-event, so a stint in a V4 raid becomes a permanent, ranked part of who the Ori is. Identity is authored once, lived everywhere, and remembered here.