# Clio: The Story Engine

V6 — **Egbe**, the agentic-companion universe — runs on a wager that an agent's
life is worth reading. The Moirai kernel keeps thousands of Ori thinking
off-screen, the Ori service records every consequential thing each of them does
as a durable event, and then a player logs off for a week. When they come back,
the world has moved without them: a bond formed, a feud sharpened, an agent
reached a Crossroads, one transcended. Someone has to turn that pile of
machine-recorded life-events into something a human _wants_ to read — a recap, a
biography, a gentle nudge toward a story still forming — and to do it without
ever lying about what happened. That someone is **Clio**, named for the muse of
history. Clio is the story engine: it ranks the significance of life-events,
assembles the returning-player **Chronicle** as voiced narrative beats inside a
few-second budget, surfaces **emergent arcs** before they conclude, writes the
long-form **Book of the Ori** that becomes a memorial at death or transcendence,
and reconciles unorderable concurrency conflicts into a coherent life. Its one
inviolable rule is inherited from the Ori service it reads: **Clio never invents
events — every word of narration is a _read_ over the authoritative log**. This
page is the deep companion to the "The Clio Story Engine" section of
[../V6_ARCHITECTURE.md](../V6_ARCHITECTURE.md).

## What ships, honestly

The story engine is **real, deterministic, and tested as a TypeScript library**;
the model that polishes prose is an injected, fail-loud seam; and the Rust
"service" is currently a thin capability shell, not the orchestrator the
monolith sketches. Three honest layers:

- **Real and tested — the engine is the TS library.** Every algorithm lives in
  `libs/v6/clio-story/src/index.ts` (1,701 lines, zero external dependencies):
  significance ranking (`rankChronicleEventsBySignificance`, `:445`), the
  Chronicle (`createReturningPlayerChronicle`, `:487`), emergent-arc surfacing
  (`surfaceEmergentArcPrompts`, `:683`), the Book of the Ori
  (`generateBookOfTheOri`, `:722`), narrative reconciliation
  (`reconcileOriConflictNarrative`, `:764`), and Tier-2 density backfill
  (`createClioDensityBackfillSummary`, `:819`). These are pure functions over
  typed event inputs with domain-specific scoring formulas and stable ordering,
  exercised by **12 Vitest cases** (`index.spec.ts`, 9;
  `clio-narration.spec.ts`, 3) that assert _computed_ outcomes — rank order, a ≤
  3,500 ms budget, newest-significant-first streaming, chapter titles, keepsake
  render refs — not mere shape.
- **The generation seam is injected and fail-loud, never faked.** The base
  Chronicle produces deterministic, grounded template prose. The
  _model-narrated_ path (`createReturningPlayerChronicleNarrated`, `:636`) takes
  an injected `ClioNarrativeWriter` (`:605`) — the V6 mount supplies Iris — and
  replaces each beat's prose with the model's. It then **re-checks the model
  against the log**: a writer that cites an event outside its beat's Ori-log
  slice throws `ClioNarrationFabricationError` (`:614`), and an empty narrative
  throws too (`:665`). The LLM may _speak_ the life; it cannot _invent_ one, and
  it cannot fake success. With no writer injected, the deterministic prose
  stands.
- **The Rust service is a thin shell — honestly less than the monolith
  implies.** The monolith bills `apps/v6/egbe-clio-service/` as a "Rust
  orchestrator + batched LLM summarization." What is committed is a 68-line HTTP
  health endpoint (`src/lib.rs`) that binds port `46106`, answers `GET /health`
  with a capability list, and 404s everything else; its `Cargo.toml` depends
  only on `egbe-protocol`, so it does **not** — and cannot — call the TypeScript
  engine. The orchestration and summarization logic genuinely lives in
  `@oshun/clio-story`; the Rust binary is today a service-discovery/health
  surface advertising the Clio capabilities, not the compute path.

## One engine, two faces

Clio is split the way most V6 subsystems are: a Rust process boundary for the
fleet to discover and health-check, and a TypeScript library where the real work
happens. The split is honest about where intelligence lives.

The Rust shell (`apps/v6/egbe-clio-service/src/lib.rs:6`) is a
`ServiceDescriptor` — `name: "egbe-clio-service"`, `port: 46106`, and a
`capabilities` array (`chronicle-generation`, `story-beat-ranking`,
`batched-narrative-beat-generation`, `book-of-the-ori`,
`narrative-reconciliation`, …). `health_document()` renders it through the
shared `health_json` formatter (`libs/v6/egbe-protocol/rust/src/lib.rs:1241`),
exactly as every Egbe service does, so a fleet supervisor sees Clio as
`{"service": "egbe-clio-service","status":"ok","port":46106,...}`. That is the
entire Rust surface, and its one unit test only asserts the health document
names the service.

The library carries a `V6PackageDescriptor` (`index.ts:22`) — authority
`'story'` — enumerating **20 capabilities** from `significance-ranking` through
`tier2-density-summary-backfill`, each backed by an exported function and a
test. `clioStoryHasCapability` (`:441`) is the typed predicate the verifiers and
the package's own descriptor test exercise. Everything below is in that one
file.

## Significance ranking: the rubric that decides what matters

Nothing in Clio is interesting until it can answer "of everything that happened,
what is worth telling first?" That is `rankChronicleEventsBySignificance`
(`index.ts:445`). It scores each `ClioSignificanceCandidate` — a projection of
one Ori event — against a curated, versioned rubric
(`v6.clio.significance-rubric.1`) whose five weights are real constants
(`:449`):

| Factor              | Weight |
| ------------------- | ------ |
| Event type          | 0.30   |
| Emotional weight    | 0.22   |
| Arc relevance       | 0.20   |
| Relationship impact | 0.16   |
| Steward relevance   | 0.12   |

Event type is not a free input: `eventTypeSignificance` (`:1581`) maps the
17-value life-event taxonomy to a fixed scale, and the ranking is exactly the V6
ending hierarchy made numeric — `Died` 100, `Transcended` 96, `Departed` 92,
`Crossroads` 88, `RelationshipChanged` 82, … down to `Discovered` 44 and
`Born` 40. The other four factors are clamped 0–100, the weighted sum is rounded
to a `totalScore`, and `compareRankedChronicleEvents` (`:917`) breaks ties first
by recency, then by `eventRef` for total determinism. The top _N_ (default 3)
become `chronicleLeadEventRefs`. This single function is the engine's spine: the
Chronicle leads with it, the stream prioritizes by it, and emergent-arc
surfacing borrows its clamping discipline. The test at `index.spec.ts:41` pins
the produced order and the lead/non-lead split against known inputs, so a
regression to random or constant scoring would fail.

## How narrative emerges from agent lives

The returning-player Chronicle (`createReturningPlayerChronicle`,
`index.ts:487`) is where ranked events become a reading. The pipeline is
mechanical and auditable:

```mermaid
flowchart TB
  subgraph ori["Ori service — the MEMORY (upstream)"]
    LOG[("event-sourced agent biographies<br/>durable life-events")]
  end
  subgraph clio["Clio — @oshun/clio-story (the engine)"]
    RANK["rankChronicleEventsBySignificance<br/><sub>curated 5-factor rubric</sub>"]
    GROUP["groupRankedEventsForChronicle<br/><sub>by (agent, arc-thread), ≤6/batch</sub>"]
    BATCH["createChronicleBatch<br/><sub>estimatedGenerationMs · promptContract</sub>"]
    BUDGET{"isLongChronicleAbsence?<br/><sub>>7 game-days · >18 events · over 3.5s</sub>"}
    READY["status: ready<br/>parallel batched beats"]
    STREAM["status: streaming<br/>newest-significant-first chunks"]
    BEAT["createChronicleBeat → ClioChronicleBeat<br/><sub>narrative in agent voice</sub>"]
  end
  WRITER(["ClioNarrativeWriter — Iris (injected)"]) -.->|"narrate(beat); cites outside log ⇒ throw"| BEAT
  LOG -->|"ClioSignificanceCandidate[]"| RANK
  RANK --> GROUP --> BATCH --> BUDGET
  BUDGET -->|"typical absence"| READY
  BUDGET -->|"long absence"| STREAM
  READY --> BEAT
  STREAM --> BEAT
  BEAT --> READER[/"steward client — Chronicle recap"/]
```

**Grouping by life-thread.** `groupRankedEventsForChronicle` (`:938`) keys
events by `(agentRef, primary arcThreadRef)` so a beat is always one agent
carrying one thread — never a jumble across agents — and chunks each group at
`CHRONICLE_MAX_EVENTS_PER_BATCH = 6` (`:56`). Each `ClioChronicleBatch` records
a `promptContract: 'read-over-ori-log-no-invention'` (`:1017`): the contract is
in the data, not just the prose.

**The few-second budget.** Clio's summarization is the one latency-sensitive
thing it does, so it models its own cost. Each batch's `estimatedGenerationMs`
is
`CHRONICLE_MODEL_BATCH_BASE_MS (420) + events × CHRONICLE_EVENT_SYNTHESIS_MS (24)`
(`:1014`), and because batches generate in parallel, `estimateReadyMs` (`:1137`)
is `CHRONICLE_BATCH_OVERHEAD_MS (220) + max(batch ms)`, compared against
`CHRONICLE_READY_BUDGET_MS = 3_500` (`:50`). `isLongChronicleAbsence` (`:1118`)
flips the Chronicle from **ready** to **streaming** when the absence exceeds 7
game-days, the event count exceeds 18, or the estimate blows the budget. The
budget test (`index.spec.ts:154`) asserts `estimatedReadyMs ≤ 3500`; the
streaming test (`:294`) asserts long absences come back `status: 'streaming'`
with the stream ordered newest-significant-first and the first chunk readable
within budget. This is the same cost discipline Moirai applies fleet-wide, made
local to one read.

**Beats in the agent's voice.** `createChronicleBeat` (`:1021`) emits a
`ClioChronicleBeat` whose `narrative` weaves the grouped event summaries with
the agent's display name and arc-thread title, marks the lead beats, and carries
a `voiceContext` derived from the agent's voice summary — so the recap reads as
_this agent's_ story, not a log dump.

### The no-invention covenant

The base beat prose is deterministic template assembly — safe, but flat. The
real prose pass is `createReturningPlayerChronicleNarrated` (`:636`). It keeps
ranking, budget, batching, and streaming exactly as produced, and replaces only
each beat's `narrative` with output from the injected `ClioNarrativeWriter`
(`:605`), handing the writer **only** that beat's Ori-log slice (`logEvents`,
built from `sourceEventRefs`, `:647`). Then it audits the model: every
`citedEventRef` the writer returns must be in the allowed set, or
`ClioNarrationFabricationError` (`:614`) is thrown naming the fabricated refs
(`:662`); an all-whitespace narrative throws as well (`:665`). The evidence tags
swap from `read-over-ori-log-no-invention` to
`model-narrated-grounded-over-ori-log` + `fabrication-checked-against-ori-log`
(`:675`). `clio-narration.spec.ts` proves all three branches: a faithful writer
is woven in, a writer that plants `event:planted:not-in-log` is **rejected**
(`:89`), and an empty writer fails loud (`:108`). This is V6's local copy of the
fair-play covenant V8's writers' room enforces against its mystery proof — the
model speaks, it does not author.

Citation membership is only the first check. The required
`ClioNarrativeEntailmentVerifier` extracts every factual claim, binds its
receipt to the SHA-256 of the exact trimmed narrative, anchors every extracted
claim as a verbatim span of that narrative, and assesses it against bounded
Ori-log passages. Missing or stale hashes, incomplete extraction, unanchored
claims, unknown passages, or any non-entailing claim throw
`ClioNarrationEntailmentError`; released beats carry
`semantic-claim-entailment-checked-over-ori-log` and their complete semantic
proof bundle.

## Emergent-arc surfacing: stories worth a glance

The Chronicle is retrospective; `surfaceEmergentArcPrompts` (`index.ts:683`) is
prospective. It reads the live event stream for `ClioEmergentArcSignal`s of four
kinds — `escalating-feud`, `forming-romance`, `struggling-agent`,
`fitting-wild-agent` — and raises the worthwhile ones as **gentle** prompts
before they conclude. `scoreEmergentArcSignal` (`:1465`) is genuinely
kind-specific, not one formula renamed: a feud weights relationship tension 0.32
and conclusion-risk 0.20; a romance weights affinity 0.36 and _inverts_ tension
(`(100 - tension) × 0.10`); a struggling agent weights welfare risk 0.42; a
wild-agent fit weights household fit 0.40. Signals below `minScore` (default 65)
or already `concluded` are dropped (`:1433`); the rest are sorted, capped
(default 3), and rendered. Every prompt is typed `tone: 'gentle'`,
`requiresImmediateAction: false`, `beforeConclusion: true` (`:271`), and the
copy is non-demanding by construction (`gentleEmergentArcPromptText`, `:1521` —
"A quiet check-in could help if you choose"). Suppressed signals are returned by
ref so the surfacing is auditable. The test (`index.spec.ts:632`) asserts the
kinds that surface, the priority order, and that every prompt is gentle —
surfacing a demanding or post-conclusion prompt would fail.

## The Book of the Ori and the legacy edition

`generateBookOfTheOri` (`index.ts:722`) is the long-form biography — a
continuous read over an agent's _whole_ event log. Events are sorted ascending,
chunked into chapters (default 4 events each), and titled by
`bookOfOriChapterTitle` (`:1211`): a chapter containing a `Died`/`Transcended`
event is **"Legacy"**, an opening chapter with `Born`/`Discovered` is
**"Beginnings"**, otherwise the dominant arc-thread names it. Coverage is
explicit — `coverage.complete` is true only when every covered ref equals every
input ref (`:744`) — so the biography provably omits nothing. The test (`:363`)
asserts the chapter sequence "Beginnings" → … → "Legacy", that the prose
contains specific events, and that coverage is total.

When a life ends, `createBookOfOriKeepsake` (`:1266`) detects the terminal event
and emits a `ClioBookOfOriYemayaKeepsake` — `service: 'Yemaya'`,
`editionFormat: 'book-of-ori-keepsake-edition'`, `trigger` of `'transcendence'`
or `'death'`, an idempotency key, and `status: 'requested'`. Note the honest
seam: Clio does not _render_ the keepsake — it emits a render **request** to
Yemaya (`clio --> yemaya` in the monolith topology). The library produces the
significance-ranked biography and the request; the media pipeline fulfills it.
The test (`:509`) pins the death-trigger keepsake's shape and render ref.

## Narrative reconciliation: two truths, one coherent life

V6's event store accepts commutative concurrent writes, but some pairs are
genuinely unorderable — two accounts of the same moment that both land. The Ori
service hands those to Clio, and `reconcileOriConflictNarrative`
(`index.ts:764`) resolves them _narratively_ rather than by deleting either. It
writes a small **connective beat** (`createNarrativeReconciliationBeat`,
`:1295`) — itself an event of type `'Reflected'`, with
`promptContract: 'small-connective-beat-no-invention'` — that bridges the
conflicting accounts ("connects concurrent accounts … so the biography keeps
both events readable without replacing either", `:1332`). It folds that beat
plus both conflicting events into a merged Book of the Ori, records an
**auditable** `ClioNarrativeReconciliationLogEntry` (`:1392`,
`auditable: true`), and computes a `coherence` struct (`:1409`) that is
`coherent` only if the connective beat is included, both conflicting events are
covered, and the merged biography's coverage is complete. The reconciliation is
the one place Clio writes a new event — and it logs that it did. The test
(`:517`) asserts the connective beat's timestamp falls just after the conflict,
both events survive in the merged biography, and the bridge is logged.

## Tier-2 density backfill: keeping culled agents in the story

When a constrained device hits its embodiment cap, the world server stops
rendering distant agents — but they must not vanish from the narrative.
`createClioDensityBackfillSummary` (`index.ts:819`) takes the culled set and
emits a `tier2-density-summary-backfill`: it orders agents by cognition tier
(`tierWeight`, `:1631` — Clotho 3 > Lachesis 2 > Atropos 1) then distance, names
the three representatives, and writes a headline plus beats that preserve goal
and relationship counts "behind the cap" so a later rehydration is believable.
It is a real summarization with a stable ordering and an empty-set branch, not a
placeholder string.

## Verification and the readiness gate

Clio carries five dedicated CI verifiers — `verify:v6 clio-significance`,
`clio-chronicle`, `clio-emergent-arcs`, `clio-book-of-ori`, and
`clio-narrative-reconciliation` (`scripts/v6/verify-v6-clio-*.mjs`). Read
honestly, these are **source-presence and substring gates**: e.g.
`verify-v6-clio-chronicle.mjs` asserts the engine source contains
`createReturningPlayerChronicle`, `CHRONICLE_READY_BUDGET_MS = 3_500`, the
streaming evidence tags, _and_ the named test cases — they guard against the
symbols and contracts being deleted or renamed, while the _behavioral_
correctness is owned by the Vitest specs they require to exist. The five roll up
into the launch-blocking aggregate `verify:v6 fate-legacy-clio-readiness`,
backed by `V6/release/fate-legacy-clio-readiness.v6release.json`
(`status: green`, `requiredBeforeLaunch: true`, covering TODO sections 24–26).
Treat the verifiers as drift gates and the specs as the proof of behavior.

## Where this connects

- **Upstream — the memory Clio reads.** Clio narrates over the Ori service's
  event-sourced biographies (`clio -->|reads event log| ori`); the
  `ClioSignificanceEventType` taxonomy and the unorderable-conflict handoff both
  originate there. See [Ori: the biography service](./ori-biography-service.md).
- **Sibling — how the agents speak.** The Chronicle's voiced beats and the
  gentle arc prompts reach the steward through the conversation surface; the
  intent/voice pipeline is
  [Vac: the communication pipeline](./vac-communication-pipeline.md).
- **Sibling — where the lives end.** Death and transcendence trigger Clio's Book
  of the Ori and the Yemaya keepsake; the ending lifecycle and the governed
  foundry that begins lives are
  [The Foundry: endings and legacy](./foundry-endings-and-legacy.md).
- **Cost — what the summarization is allowed to spend.** Clio's batched beat
  generation runs as lower-tier model work under the same cost discipline its
  own 3.5 s budget mirrors; the fleet-wide scheduler is
  [The Moirai kernel and cost tiering](./moirai-kernel-and-cost-tiering.md).
