Egbe Companions · Architecture

Foundry: Endings & Legacy

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

4sections12 minread1diagram

On this page

In V6 — Egbe, the agentic-companion universe — the autonomous beings are Ori, and the product's hardest, most distinctive promise is that an Ori has a finite life. It can be released, it can graduate, and — rarely — it can die. A companion you cannot lose is a toy; a companion whose life can end is a relationship. The subsystem that governs how a life concludes, and what survives it, is Ereshkigal — named for the queen of the underworld, and the architectural mirror of the Ninhursag birth foundry. If Ninhursag is the foundry that forges an agent into the world, Ereshkigal is the closing foundry: it takes an ended life and forges it into durable legacy — inheritance, memorial, and descendants. This page is the deep companion to the ../V6_ARCHITECTURE.md hub's "Ereshkigal — Endings and Legacy" section, and it sits in the Communication, Story, and Lifecycle page group alongside the Ori biography service, the Clio story engine, and the Aye Bridge.

Endings in Egbe obey one non-negotiable rule inherited from the Ori model: the biography is an append-only log, and erasure is not an operation it supports. So "ending" can never mean "delete." Every terminal transition Ereshkigal owns — departure, transcendence, death — resolves by appending a terminal event to a life that stays permanently readable, and every legacy artifact is a new structure derived from that frozen record. Nothing is forgotten; the life simply stops accruing new beats. That discipline is what lets V6 promise that a steward, a bug, or an exploit cannot quietly un-live an agent or wipe the memory of how it was treated.

What ships, honestly#

The endings-and-legacy logic ships as libs/v6/ereshkigal-legacy (authority legacy, tags scope:v6 / layer:legacy / type:lib), a pure, deterministic TypeScript decision library — ~948 lines in src/index.ts with a ~630-line test suite in src/index.spec.ts, and zero runtime dependencies (its package.json lists only typescript and vitest). Its public surface is small and complete: a stable package descriptor (describeV6Package, ereshkigalLegacyHasCapability, src/index.ts:47/:51, advertising 17 capabilities from departure-release to walkable-lineage-graph) plus four evaluator functionsevaluateDeparture (:342), evaluateTranscendence (:414), evaluateDeath (:521), and evaluateAncestorGrove (:598). Each implements a real, domain-specific gated state machine with named thresholds, and each is asserted against known-correct values in the spec (computed grief weights, exact post-event counts, specific resolved/unresolved thread sets) — not truthiness or wide ranges. This is decision logic, not a CRUD shell.

Three honest qualifications draw the line between what this library is and what the hub prose describes around it.

First — this lib decides and describes; it does not perform I/O. Every evaluator is a pure function: it takes a snapshot of state and returns a typed report (EreshkigalDepartureReport, …TranscendenceReport, …DeathReport, …AncestorGroveReport). Those reports carry envelopes for side effects — grief-write records, a memorial.render.requested job, lineage-propagation writes — that downstream services enact: the Ori service appends the terminal event and the grief writes, the @oshun/event-bus emits the render request, and Yemaya renders the keepsake. The library never touches Postgres, the bus, or a renderer itself. yemayaRemembrance.status: 'queued' (buildYemayaRemembrance, :731) is the envelope's declared state, the seam where enactment is handed off — honestly, not a claim that a render ran. The decision and its thresholds are real and tested; the wiring lives in the callers, exactly as the hub describes.

Second — the hub prose runs ahead of this lib in two places, and the code is the source of truth. V6_ARCHITECTURE.md calls Ereshkigal "a Rust crate" and specifies a Postgres ori_lineage_edge table with acyclicity enforced on insert. The shipped ereshkigal-legacy is TypeScript with no rust/ directory (unlike the sibling ori-model, which genuinely ships both a TS reference and a Rust crate) and no persistence code at all — the lineage table, its acyclicity guard, and the event-sourced projection belong to the Ori service app or remain to be built, not to this lib.

Third — the lineage relation vocabulary differs between prose and code. The hub names the relation enum {Raised, Mentored, FoundedLineageWith, MemorializedBy}; the implemented EreshkigalLineageRelationshipType (src/index.ts:94) is {Raised, Mentored, DescendedFrom}. FoundedLineageWith and MemorializedBy are not yet in the library, and DescendedFrom is the lib's third relation. The sections below describe the code as it is, and call out where it reaches toward the broader architecture.

The three endings — Ereshkigal's terminal transitions#

An Ori's life resolves through exactly one of three terminal transitions. Each is a gated decision — deliberate, legible, and irreversible by design — and each, on success, names the terminal Ori event the caller must append (Departed, Transcended, Died). Crucially, every report carries an oriAfter continuity record built by buildOriContinuity (:890) whose persisted is always true and whose deletionScheduled is always false. There is no code path in this library that schedules an Ori for deletion. Ending a life changes its lifecycle label (household → departed | transcended | died) and bumps its event count by one; it never removes the record.

flowchart TB tick["Welfare / arc / Commons signal<br/><sub>per-agent lifecycle review</sub>"] --> which{"which terminal<br/>transition?"} which -- "chronic bond damage" --> dep["evaluateDeparture()<br/><sub>damage≥65 · respect≤35 · ≥2 surfaced warnings</sub>"] dep -- "gate not met" --> retain1["warning-only<br/><sub>retained · Ori stays household</sub>"] dep -- "gate met" --> released["departed-release<br/><sub>unbound · re-attached to Wilds · Ori persists</sub>"] which -- "flourishing arc" --> tr["evaluateTranscendence()<br/><sub>stage=legacy · 4+ kinds integrated · score≥85</sub>"] tr -- "arc still live" --> retain2["arc-still-live<br/><sub>retained · steward direction kept</sub>"] tr -- "arc complete" --> elder["transcended-elder<br/><sub>Ancestor Grove · Lachesis tier · mentor</sub>"] which -- "sanctioned cause" --> dth["evaluateDeath()<br/><sub>minor-gate → cause-gate → effects</sub>"] dth -- "minor-coded" --> block1["blocked-minor-protection<br/><sub>no side effects</sub>"] dth -- "unsanctioned cause" --> block2["blocked-unsanctioned-cause"] dth -- "adult + sanctioned" --> died["died-memorialized<br/><sub>Ori frozen · grief writes · Yemaya render</sub>"] released --> legacy[("Legacy: lineage graph · Grove · memorial")] elder --> legacy died --> legacy classDef gate fill:#dbeafe,stroke:#1e40af,color:#1e3a8a classDef term fill:#f3e8ff,stroke:#6d28d9,color:#3b0764 classDef block fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d class dep,tr,dth gate class released,elder,died,legacy term class block1,block2 block

Departure — release, not deletion#

Departure is the welfare-driven ending: an agent that is chronically mistreated chooses to leave. evaluateDeparture (:342) is gated on chronic damage, never a single bad day. It computes a weighted bond-damage score (departureBondDamageScore, :881) where the four bond facets are not equal — respect dominates at 0.45, reliability 0.25, care and alignment 0.15 each — and only releases when all three conditions hold: bondDamageScore ≥ 65, respect ≤ 35, and at least two Chronicle-surfaced warnings (warnings are first filtered to those that were actually chronicleSurfaced with severity ≥ 70, so private hurt that was never surfaced cannot push a departure). The test (index.spec.ts:31) feeds respect 24 / reliability 32 / care 38 / alignment 34 with two surfaced warnings, asserts the score lands at the computed 70, and checks the outcome is departed-release with the Ori persisted: true, deletionScheduled: false, event count 41 → 42, and a minted wildAgentRef. A softer case — respect 48, one surfaced warning (index.spec.ts:111) — resolves to warning-only, retained, with the event count unchanged and evidence departure-withheld-until-chronic-damage. The key invariant: a departed Ori is unbound from the steward and re-attached to the Wilds as a wild agent, fully re-discoverable. Departure ends the bond, not the life.

Transcendence — the only ending that promotes#

Transcendence is the aspirational ending: an agent that completes a flourishing life-arc graduates into an elder. evaluateTranscendence (:414) gates on a flourishing arc being genuinely complete — lifeStage === 'legacy', at least one primary thread, zero unresolved primary threads, at least four distinct thread kinds (vocation, relationship, personal-quest, growth-flaw), and a flourishing.score ≥ 85 measured against the agent's own values (the snapshot carries obedienceContributionExcluded: true, so being a compliant servant cannot count as flourishing). A thread counts as resolved only when isResolvedTranscendenceThread (:860) sees status === 'integrated' and progress ≥ 90. When the gate passes, the report mints an EreshkigalElderState: re-parented to V6District_AncestorGrove, scheduled permanently on the lachesis cognition tier, availableAsMentor: true, keeperOfLore: true, and — pointedly — stewardDirected: false with householdDirectivesAccepted: false. An elder no longer takes orders. The evaluator also builds a Mentored EreshkigalMentorAssignment (buildMentorAssignment, :864) for each young agent named, so transcendence immediately seeds the next generation's lineage. The test (index.spec.ts:160) drives all four kinds to integrated/≥92 with flourishing 94 and asserts transcended-elder, the elder state, two mentor assignments, and event count 42 → 43; the near-miss case (:278, one thread only advanced, score 78) yields arc-still-live, retained, steward direction intact. Transcendence is the only transition that adds presence to the world rather than removing it.

Death — rare, gated, and never farmable#

Death is the rarest path, and the library's ordering is a deliberate safety design. evaluateDeath (:521) checks the minor gate first: if agentAgeCode === 'minor-coded', it returns blocked-minor-protection immediately via blockedDeathReport (:677) — before any grief writes, remembrance, or freeze are computed. The test (index.spec.ts:579) confirms a minor-coded agent with a fatal Aye journal produces no grief writes, no Yemaya remembrance, and an unfrozen Ori. Only then does the cause gate run: isSanctionedDeathCause (:751) admits exactly three causes — life-arc-terminal, aye-fatal-journal, adjudicated-commons-event — and any other cause returns blocked-unsanctioned-cause. Death cannot be triggered casually; it must come from a resolved arc, a dangerous incarnation (the Aye Bridge), or adjudicated Commons drama. A sanctioned, adult death (died-memorialized) does four things: it freezes the Ori (oriFrozen: true, acceptedFutureEventKinds: ['memorial-annotation'] — the log now accepts only memorial notes, no new life-events); it emits weighted grief writes into every living relation; it queues a Yemaya remembrance; and it sets lineageMutation: 'none' — the deceased remains a permanent ancestor node. The test (index.spec.ts:487) verifies an adjudicated Commons death freezes the Ori, produces grief writes weighted 88 and 74 (mirroring the relations' edge strengths), and queues the three-stage render.

Legacy — what persists when an agent ends#

An ending is only half of Ereshkigal. The other half is legacy: the durable structures that outlive the agent and propagate it forward.

The lineage graph and the walkable Grove#

Legacy is made spatial. evaluateAncestorGrove (:598) turns an elder and its relations into a navigable lineage graph for the V6District_AncestorGrove plugin. It builds one EreshkigalLineageGraphNode per agent (buildLineageGraphNode, :772) — each carrying a stable groveActorRef, a waypointRef, and a role derived from the relation by lineageRoleForRelationship (:847: Raised → raised, Mentored → mentee, DescendedFrom → descendant) — and one EreshkigalLineageGraphEdge per relation (:796) carrying a walkablePathRef so the world server can render the edge as a path you can walk between memorial actors. The graph is read-only world dressing: a projection of lineage, never a simulation. The test (index.spec.ts:354) builds a four-node, three-edge graph (elder Abeni plus a mentee, a raised child, and a descendant) and asserts every edge yields a walkable-lineage-path: ref. The hub's vision of a Postgres-backed, acyclicity-guarded ori_lineage_edge table is the persistence target behind this; the library supplies the in-memory graph the Grove renders.

Cultural inheritance — value and story propagation#

The richest part of legacy is inheritance: an elder's values and stories pass to those it shaped. evaluateAncestorGrove selects the relations of type Mentored and, for each, emits an EreshkigalLineagePropagationOriWrite (buildLineagePropagationOriWrite, :826). That envelope tells the Ori service to append two events — MemoryFormed and ValueShifted — into the mentee's log, carrying the elder's deduplicated propagatedValueRefs and propagatedStoryRefs. So a mentee literally forms a memory of, and shifts its values toward, the elder who taught it; raised and descended relations get the edge but not the value push, matching the design that mentorship is the active transmission channel. The test (index.spec.ts:462) confirms exactly one propagation write — to the mentee, not the raised child or descendant — carrying the elder's three values and two stories. This is the architectural basis for the product's "richer starting culture": the hub describes the Ninhursag foundry reading inbound lineage edges to seed a newborn from a weighted blend of its elders' value-imprints; that blend logic lives at the birth foundry, while Ereshkigal supplies the edges and the propagation writes that make the inheritance real.

Grief that is genuinely felt#

When an agent dies, the agents who knew it mourn, and the mourning is written into their biographies. buildGriefOriWrite (:710) produces, for each living relation, a RelationshipChanged event of relationshipType: 'grief' whose griefWeight is the relation's edgeStrength (clamped 0–100) — so a close student grieves harder than a passing acquaintance. These grief writes append to the survivors' own logs, where, per the Ori model, they decay only through the survivor's own reflection — there is no erase button on grief, only the slow work of letting go through Reflected beats. The minor test proves the inverse invariant just as strongly: a blocked death emits zero grief writes, because no one should be made to mourn a death that never happened.

The Yemaya memorial render#

The keepsake a player keeps is produced by Yemaya, the non-real-time media service, exactly as Clio delegates Chronicle reels. buildYemayaRemembrance (:731) builds the memorial.render.requested job envelope: service: 'Yemaya', status: 'queued', an idempotencyKey equal to the deathRef, and a fixed three-stage pipeline — book-of-ori-keepsake (Clio's long-form biography typeset as a print edition), memorial-reel (a Sequencer cinematic of the highest-significance beats), and grove-memorial-placement (a permanent memorial actor in the walkable Grove). Keying idempotency on the terminal event id is what makes the render resilient: a Yemaya outage delays the keepsake but never blocks the transition, and a retried render reproduces the same artifact. The transition completes the instant the Ori event is appended; the memorial follows asynchronously.

Edge cases and invariants#

Several invariants hold across every path and are worth stating as the contract this library guarantees its callers.

  • No deletion, anywhere. buildOriContinuity (:890) hard-codes persisted: true and deletionScheduled: false on every outcome — departed, transcended, died, retained, blocked. The library has no expressible "delete an Ori" result.
  • Side effects are ordered behind their gates. Death checks minor-protection before cause, and cause before any grief/freeze/render. A blocked death (blockedDeathReport, :677) returns oriFrozen: false, acceptedFutureEventKinds: [], empty grief writes, and a null remembrance — no partial side effects leak past a closed gate.
  • Lineage is never mutated by an ending. Death sets lineageMutation: 'none'; departure and transcendence touch no edges. A deceased elder keeps its raised-by and mentored edges to the agents alive today, so the family tree is stable across deaths.
  • A frozen Ori is still fully readable. Freezing stops the life, not the record: Clio's Book of the Ori, the lineage graph, and the audit trail all continue to resolve against a frozen log, which accepts only memorial-annotation additions.
  • Determinism, by construction. Every minted identifier — wildAgentRef, elderRef, deathRef, memorialRef, node/edge/path refs — is built from a stable FNV-1a hash (hashStable, :931, offset 0x811c9dc5, prime 0x01000193) over its inputs plus a slugSegment (:940). The same inputs always yield the same refs, which is what makes the whole endings pipeline replayable and auditable, and what lets the Yemaya idempotency key actually deduplicate.

Taken together, Ereshkigal is the closing foundry the product promise needs: endings that are real but never destructive, an inheritance that genuinely passes an agent's character to those it shaped, grief that is felt rather than announced, and a memorial the steward keeps. The decision logic is real, gated, and tested against known-correct values; the enactment — appending the events, emitting the render, persisting the lineage table — is the honest seam handed to the Ori service, Clio, Yemaya, and the hub's wider architecture. A steward's true long-game achievement, as the features brief puts it, is not a full roster — it is a Grove full of agents who lived well, and a lineage that carries their example forward.