# The Moirai Simulation Kernel & Cost Tiering

V6 — **Egbe**, the agentic-companion universe — makes one expensive promise: a
world full of autonomous, LLM-driven minds (the **Ori**) that keep living
whether or not a player is watching. That promise is only payable if **most
minds think cheaply most of the time**, and the subsystem that enforces it is
**Moirai** — the deterministic simulation kernel, named for the three Fates,
that decides for every agent every tick _how much compute that agent gets_.
Moirai is not a model and it never calls one directly; it is a scheduler. Each
world tick it takes a perception batch from the world server, recomputes every
agent's **cognition tier** (Clotho for the handful in a player's live scene,
Lachesis for the off-screen-but-near, Atropos for the offline and distant),
dispatches the right _kind_ of thinking at the right cadence, guards against
behavioural loops, and hands back an action batch — all as pure, replayable
functions of the inputs. The companion half of the page is the **cost/fidelity
tiering**: the per-tier token budgets, the per-Solo-world cognition cap, the
smallest-qualified model routing, the cross-tier batching, and the
cognition-call audit log that together turn "how much thinking" into a _bounded,
attributable bill_. The architectural bet is stated bluntly in the monolith:
**cost scales with story relevance, not with agent count**, and a player must
never feel the tiering while the cost model always must. This page is the deep
companion to the "The Moirai Simulation Kernel" and "AI Cost and Fidelity
Tiering" sections of [../V6_ARCHITECTURE.md](../V6_ARCHITECTURE.md).

## What ships, honestly

The kernel is **real and tested**, not a sketch. `MoiraiKernel::tick`
(`libs/v6/moirai-kernel/rust/src/lib.rs:1354`) is a genuine per-tick scheduler
over an `egbe-protocol` `PerceptionBatch`, and the crate carries 34 Rust unit
tests. The headline one drives **10,000 Lachesis + 200 Clotho agents** through a
single tick and asserts it finishes inside a 50 ms budget
(`lib.rs:4373`,`:4414`) — and it runs the same batch through three fresh kernels
and keeps the _minimum_ elapsed time, which only makes sense because the tick is
deterministic. Tier assignment, transition damping, BT/HTN dispatch, loop
guards, escalation rehydration, and de-escalation consolidation are all pure
functions of the perception frame plus per-agent state. The cost machinery in
the TypeScript facade (`@oshun/moirai-kernel`,
`libs/v6/moirai-kernel/src/index.ts`) is equally real: per-tier budget clamping
and the Solo-world cap (`evaluateMoiraiCognitionBudget`, `:751`),
smallest-qualified model routing (`selectMoiraiModelRoute`, `:479`), an FNV-1a
config fingerprint (`:603`), and a replayability-validated cognition-call audit
log (`:705`) — each anchored to a spec that asserts _computed_ results, e.g. the
worst-case Solo population's 96,000,000-token saturation (`index.spec.ts`).

Three things are **honest seams, not fabrications**, in exactly the V6 fail-loud
spirit:

- **The kernel emits intents and _estimates_, not model output.** A Moirai tick
  produces deterministic `wire::ActionIntent`s and _token estimates_; the actual
  language-model run is the cognition stack's job, behind an injected gateway.
  The per-tier `tierBudgetTokens` field is labelled **ACCOUNTING-ONLY** in both
  the Rust and TS source and is explicitly _not_ sent to a model as a
  `budget_tokens` extended-thinking parameter — "the kernel validates/audits
  budgets, it does not allocate model thinking" (`lib.rs:138`,
  `src/index.ts:151`). Wiring a real `budget_tokens` call is named as a separate
  ledger task (7.6).
- **The cognition gateway is an injected structural handle.** `moirai-kernel` is
  a buildable lib (it sets `rootDir`), so it cannot import the
  `@iris/agents-core` source without TS6059; the gateway, run manager, and
  kill-switch registry are therefore _injected_ structural handles to which the
  real `createCognitionGateway(...)` is assignable. The TS budget/kill/routing
  logic is real; the on-engine/HTTP host that constructs the gateway "stays
  `[~]`" (`src/cognition-gateway-mount.ts:1`).
- **Cost and quality numbers are documented approximations / policy inputs.**
  The per-call dollar estimate uses fixed micro-USD-per-1k-token class rates
  (opus 1,500 / sonnet 300 / haiku 75, `lib.rs:1123`, `src/index.ts:979`) over a
  `len/4` token estimate — not a live price feed or a real tokenizer. Model refs
  are _class aliases_ (`anthropic:claude-{haiku,sonnet,opus}-class-latest`), and
  each candidate's quality basis-points are **configuration the eval gate
  `verify:v6 agent-behavior-evals` must back** — the lib validates the
  _selection logic_ (smallest qualified), not that opus "really scores 9800."

## The simulation kernel: one tick

`MoiraiKernel::tick(&PerceptionBatch, KernelTickConfig)` is the whole loop
(`lib.rs:1354`). The world runs at `MOIRAI_WORLD_TICK_HZ = 20` (`lib.rs:25`), so
a game-day is `MOIRAI_GAME_DAY_TICKS = 20·60·60·24 = 1,728,000` ticks
(`lib.rs:26`) — the unit Atropos and the offscreen simulators reason in. For
each perception frame the kernel (1) derives a tier-assignment input from the
frame, (2) recomputes the tier with damping, (3) dispatches a tier-appropriate
action, (4) runs the loop/drift guard, and (5) records a cognition-audit
candidate when (and only when) the tier _actually issued_ an expensive call this
tick. After the frame loop it applies cross-tier batching, measures elapsed
microseconds against the `latency_budget_micros` (default
`KERNEL_TICK_LATENCY_BUDGET_MICROS = 50_000`, `lib.rs:11`), and assembles the
`ActionBatch` + `KernelTickReport` + `MoiraiCognitionCallAuditLog`
(`lib.rs:1528`).

### Tier assignment and the escalation priority ladder

`tier_input_from_perception_frame` (`lib.rs:2972`) reads the raw frame — visual
LOD, player proximity in millimetres, who addressed whom over squad comms,
crossroads / irreversible-action signals — into a typed `TierAssignmentInput`.
`assign_cognition_tier_from_perception` (`lib.rs:2743`) then applies a strict
**priority ladder** via `clotho_signal_reason` (`lib.rs:3889`): an open
**Crossroads** wins first, then an **imminent irreversible action**, then
**addressed-by-player**, then **on-screen visibility**, then **proximity**
within `clotho_proximity_mm` (default 2,500 mm), then bare scene co-presence.
Any of those promotes the agent to **Clotho**. With no Clotho signal, an agent
in a world that still has a nearby player lands in **Lachesis**
(`ResidentWorld`); otherwise it falls to **Atropos** (`OfflineOrDistant`). This
is why escalation is event-driven, not merely proximity-driven — being _spoken
to_ or reaching a _decision point_ outranks distance.

### Transition damping (anti-thrash)

A naive recompute every 20 Hz would make agents flicker between tiers at the
edge of a scene. `assign_cognition_tier_with_transition_damping` (`lib.rs:2779`)
holds the previous tier for up to `transition_damping_ticks` (default 4) when
the candidate tier differs — _unless_ the change is urgent.
`bypasses_transition_damping` (`lib.rs:3910`) lets the four high-salience
reasons (addressed-by-player, crossroads, imminent-irreversible-action,
on-screen) jump instantly, so a player who walks up and talks gets a live agent
_this tick_, while an agent drifting out of frame de-escalates only after a
grace window. The de-escalation grace is symmetric: a Clotho agent stays Clotho
for `deescalation_grace_ticks` after its last Clotho signal (`lib.rs:2750`). The
named tests pin both directions —
`walk_up_escalates_lachesis_to_clotho_within_one_tick` (`lib.rs:4004`) and
`walking_away_deescalates_after_grace_period` (`lib.rs:4019`), plus
`transition_damping_blocks_immediate_proximity_rebound` (`lib.rs:4118`).

### Per-tier dispatch

Each tier gets its own dispatch with its own cadence and its own cost shape:

- **Clotho** (`dispatch_clotho_cognition_action`, `lib.rs:3013`) plans live, but
  only every `decision_interval_ticks` (default `20/4 = 5` ticks, i.e. ~4 Hz);
  between planning ticks it returns a _cached_ action, and it measures its own
  planning latency against `CLOTHO_PLANNING_LATENCY_BUDGET_MICROS = 10_000`
  (`lib.rs:12`). Only a tick that actually planned becomes an audit candidate —
  cached ticks cost nothing.
- **Lachesis** (`dispatch_lachesis_cognition_action`, `lib.rs:3513`) steps
  cached BT/HTN plans at 10 Hz and enqueues a batched **reflection** only when
  one is due (default every 10 game-minutes,
  `LACHESIS_DEFAULT_REFLECTION_GAME_MINUTES`, `lib.rs:15`). The reflection tick
  is the only one that emits a cognition-call candidate.
- **Atropos** (`dispatch_atropos_cognition_action`, `lib.rs:3640`) emits a
  narrative-summary beat only when a summary is due (default 4 calls per
  game-day, `lib.rs:19`).

The off-screen behaviour is itself a tested invariant, not a hope:
`simulate_lachesis_offscreen_day` (`lib.rs:1889`) proves a full game-day of
Lachesis stays "believable" and inside the per-game-minute token budget
(`lib.rs:4472`), and `simulate_atropos_offline_absence` builds a coherent 14-day
chronicle within the per-game-day budget (`lib.rs:4554`).

### Loop and drift guards

An off-screen agent must never get _stuck_. `apply_loop_drift_guards`
(`lib.rs:2046`) tracks a per-agent action signature and goal-progress signature.
Three identical actions in a row (`repeated_action_limit = 3`) force a
reflection tick; a goal held for `stale_goal_tick_limit = 6` ticks with no
observed progress forces a goal change (`lib.rs:265`). Observing real progress
resets the counter (`goal_progress_observation_resets_stale_goal_drift_guard`,
`lib.rs:5030`). The guard rewrites the action's kind, goal-ref, and priority and
stamps a `v6.moirai.loop-drift-guard.1` payload so the intervention is itself
auditable (`lib.rs:2179`).

```mermaid
flowchart TD
  PB[(PerceptionBatch<br/>egbe-protocol)] --> TICK["MoiraiKernel::tick (lib.rs:1354)"]
  subgraph TICK_LOOP["per frame"]
    TI["tier_input_from_perception_frame (:2972)"] --> LADDER{"clotho_signal_reason ladder (:3889)<br/>crossroads &gt; irreversible &gt; addressed &gt; on-screen &gt; proximity"}
    LADDER -->|signal| C["Clotho dispatch (:3013)<br/>plan ~4Hz, else cached"]
    LADDER -->|nearby player| L["Lachesis dispatch (:3513)<br/>BT/HTN 10Hz + reflection"]
    LADDER -->|offline/distant| A["Atropos dispatch (:3640)<br/>narrative summary/game-day"]
    C & L & A --> DAMP["transition damping (:2779)<br/>urgent reasons bypass"]
    DAMP --> GUARD["loop/drift guard (:2046)"]
    GUARD --> AUD{"issued a real call?"}
  end
  AUD -->|yes| CAND["audit candidate"]
  AUD -->|no: cached/not-due| SKIP["no cost recorded"]
  CAND --> BATCH["cross-tier batching (:3367)"]
  SKIP --> BATCH
  BATCH --> OUT["ActionBatch + KernelTickReport<br/>+ CognitionCallAuditLog (:1528)"]
  OUT -.->|injected| GW["MoiraiCognitionGatewayMount<br/>(structural handle, [~] host)"]
```

## Determinism, replay, and the steward-not-owner invariant

V6 splits the world into a **deterministic core** and **non-deterministic, fully
logged cognition** (monolith §"Determinism, Replay, and Audit"). Moirai's hot
path is squarely on the deterministic side: there is no RNG in `tick`, which is
exactly what lets the 10,200-agent latency test re-run the same batch through
three kernels and compare elapsed times (`lib.rs:4391`). The _only_ seeded
randomness in the crate is the steward-not-owner fuzzer, and it is deterministic
from its seed by construction.

### Escalation rehydration and de-escalation consolidation

Continuity across a fidelity change is the product-level reason determinism
matters. When an Atropos/Lachesis agent escalates,
`rehydrate_clotho_working_context` (`lib.rs:1557`) reconstructs full working
context — recent episodic memory, active relationships, current arc refs,
personality events — by replaying the agent's Ori event log within a load
budget, _before_ the first high-fidelity decision
(`first_clotho_decision_from_rehydrated_context`, `lib.rs:1596`). The continuity
bar is a real eval: `evaluate_continuity_cases` (`lib.rs:1644`) requires a ≥
`CONTINUITY_EVAL_PASS_THRESHOLD_BASIS_POINTS = 9_500` (95%) pass rate that the
rehydrated decision cites the expected memory/relationship/arc refs after a full
game-day off-screen (`continuity_eval_set_passes_after_one_game_day_offscreen`,
`lib.rs:5138`). Going the other way, `consolidate_deescalation_to_ori_events`
(`lib.rs:1993`) folds pending fine-grained state into durable Ori events grouped
by kind, and `verify_deescalation_consolidation_no_state_loss` (`lib.rs:2021`)
is a **property check** that every input fragment id appears in some emitted
event payload — nothing is silently dropped when fidelity falls
(`deescalation_state_loss_report_flags_missing_fragment_payload_refs`,
`lib.rs:5258`). These write into the Ori biography, the subject of
[./ori-biography-service.md](./ori-biography-service.md).

### The fuzz-proven steward invariant

A V6 steward _guides_ an Ori; it does not _own_ it. Moirai encodes that as a
typed directive surface: six exposed directives (offer-objective, offer-counsel,
ask-consent, support-refusal, request-pause, accept-counteroffer) and four
**forbidden** ones (delete-ori, erase-agent-will, wipe-memory,
force-past-refusal, `lib.rs:524`). `enforce_steward_not_owner_directive`
(`lib.rs:1769`) also catches a _laundered_ coercion — an "offer objective" whose
text tries to force past a prior refusal is reclassified to force-past-refusal
and blocked, returning a `RefuseObjective` action under
`policy:v6:steward-not-owner`. The guarantee is fuzzed:
`run_moirai_steward_not_owner_fuzz` (`lib.rs:1809`) runs 512 deterministic
iterations and passes only if _no_ forbidden directive is reachable through the
exposed surface and every forbidden attempt is denied with a refusal action
(`steward_not_owner_fuzz_cannot_reach_forbidden_moirai_directives`,
`lib.rs:4917`).

## Cost & fidelity tiering

The TS facade turns "how much thinking" into a bill with three composable
levers.

### Per-tier token budgets and the Solo-world cap

`MOIRAI_TIER_TOKEN_BUDGETS` (`src/index.ts:329`) is the canonical shape:
**Clotho 50,000 tokens / active-minute** (decisions at 1–4 Hz plus live
dialogue), **Lachesis 10,000 / reflection** (amortized to 1,200 / game-minute),
**Atropos 15,000 / game-day** (batched). `evaluateMoiraiCognitionBudget`
(`src/index.ts:751`) allocates in two stages: it first clamps each request to
its tier cap (`tierCappedTokens = min(requested, tierMax)`), then draws against
a per-Solo-world `MoiraiSoloWorldCognitionCap` (default `96,000,000` tokens /
real-hour, `src/index.ts:32`), processing requests in tier-priority order
(Clotho → Lachesis → Atropos, `compareBudgetRequests`, `:1181`). A request over
its tier cap is flagged `overTierBudget`; a request starved by the world cap is
flagged `blockedBySoloWorldCap`. The "homestead rest" pace control is a
basis-point multiplier that can only _lower_ the cap, never raise it
(`resolveSoloWorldCap`, `:1120`).

The worst-case is computed, not asserted.
`simulateWorstCaseSoloWorldCognitionBudget` (`src/index.ts:810`) builds the
launch-ceiling Solo population — **24 Clotho** (active-scene hard cap), **400
Lachesis** (resident hard cap), **1,000 Atropos** sample agents — and proves the
cap holds. The numbers land exactly: Clotho `24 · 60 · 50,000 = 72,000,000` and
Lachesis `400 · 6 · 10,000 = 24,000,000` **saturate the 96 M cap to the token**,
so the 1,000 Atropos agents' `15,000,000` fully defer —
`totalAllowedTokens === 96,000,000`, `totalDeferredTokens === 15,000,000`,
`soloWorldCapHeld === true` (`index.spec.ts`). That is the whole tiering thesis
in one assertion: even at the population ceiling the bill is bounded, and the
work that defers is the work no player can see.

### Model right-sizing (smallest-qualified)

Each tier routes to **the smallest model that clears its quality bar**, not the
best model available. `MOIRAI_DEFAULT_TIER_MODEL_ROUTING_CONFIG`
(`src/index.ts:351`) sets the bars — Clotho 9,500 bp on
`social-judgment-live-dialogue`, Lachesis 9,000 bp on
`offscreen-reflection-coherence`, Atropos 8,000 bp on
`narrative-summary-continuity` — and `smallestQualifiedModelCandidate`
(`src/index.ts:1026`) filters to enabled candidates meeting the bar and sorts by
`sizeRank`. The result: Clotho → opus-class (only it clears 9,500), Lachesis →
sonnet-class, Atropos → haiku-class, each `smallestQualified: true`
(`model_right_sizing_routes_each_tier_to_smallest_quality_passing_model`,
`lib.rs:3955`; mirrored in `index.spec.ts`). Routing changes are governed:
`validateMoiraiModelRoutingConfig` (`:511`) rejects a config that names a
selected model _larger_ than the smallest qualified one, and requires the
`verify:v6 agent-behavior-evals` gate be among the re-run scripts (`:526`) — you
cannot quietly down-route a tier without re-proving the quality the bar claims.
A stable FNV-1a `configFingerprint` (`:603`) makes a routing change detectable.

### Cross-tier batching

Lachesis reflection and Atropos summary are cheap individually but carry a fixed
per-call overhead. `apply_cross_tier_cognition_batching` (`lib.rs:3367`)
coalesces eligible same-model-group calls in a tick: it only batches when there
are at least `CROSS_TIER_BATCH_MIN_REQUESTS = 2` eligible requests and the model
groups are fewer than the requests, then re-annotates payloads and reports the
overhead tokens saved (`CROSS_TIER_BATCH_OVERHEAD_TOKENS = 800` per call,
`lib.rs:23`). The savings are a measured basis-point figure in the tick report,
pinned by
`cross_tier_batching_amortizes_lachesis_reflection_and_atropos_summary_overhead`
(`lib.rs:4594`).

### The cognition-call audit log

Every expensive call is attributable. `buildMoiraiCognitionCallAuditLog`
(`src/index.ts:615`, Rust `lib.rs:877`) emits one record per tier-bearing frame
with a stable `callId`, the agent, the tier, the cause (`clotho-live-planning` /
`lachesis-reflection` / `atropos-summary`), the selected model ref + class, the
full input context and output (for replay), a token-usage breakdown, an
estimated micro-USD cost, and an `v1:isis-behavior-policy` safety decision —
under retention policy `v1:audit-platform:retention:cognition-call`.
`validateMoiraiCognitionCallAuditLog` (`:705`) flags any record missing
identity, replay context, or a safety decision, and only reports `valid` when
every record is replayable
(`cognition_calls_are_logged_with_v1_retention_for_audit_replay`,
`lib.rs:4267`). This log is the substrate the eval harness and the cost
telemetry read.

### The injected gateway and per-tier kill-switches

When a real model run _is_ needed, `createMoiraiCognitionGatewayMount`
(`src/cognition-gateway-mount.ts:119`) routes an Ori dialogue request through
the injected Iris `CognitionGateway`, applying a per-tier `AgenticBudget`
(`MOIRAI_DEFAULT_TIER_BUDGETS`, `:88` — clotho cheap/reflexive at 4k tokens up
to atropos 64k for deliberation) and exposing operator **kill-switches** at
agent, tier-family, and tenant scope (`:103`). It is the seam, honestly
labelled, where the deterministic scheduler hands off to the governed,
non-deterministic model — the cognition stack itself, in
[./cognition-stack-and-agent-behavior.md](./cognition-stack-and-agent-behavior.md).

## Edge cases & failure modes

- **Tier flicker at a scene edge.** Damping holds the prior tier for up to 4
  ticks unless an urgent reason fires; rebound is blocked
  (`lib.rs:4118`,`bypasses_transition_damping`, `:3910`).
- **A stuck off-screen agent.** Three repeated actions force reflection; a stale
  goal forces a goal change; real progress resets the guard (`lib.rs:2046`,
  `:5030`).
- **An over-budget agent.** A request above its tier cap is clamped and flagged
  `overTierBudget`; one starved by the world cap is flagged
  `blockedBySoloWorldCap` and its tokens defer rather than overspend
  (`evaluateMoiraiCognitionBudget`, `src/index.ts:751`).
- **Population at the launch ceiling.** Clotho + Lachesis saturate the 96
  M/real- hour cap exactly and Atropos fully defers — bounded by construction,
  not by luck (`index.spec.ts`).
- **A down-routed tier.** A routing config selecting a model larger than the
  smallest qualified, or omitting the eval gate, is rejected
  (`validateMoiraiModelRoutingConfig`, `src/index.ts:511`).
- **A steward trying to own an Ori.** Forbidden directives — including coercion
  laundered as an objective — are blocked with a refusal action and proven
  unreachable across 512 fuzz iterations (`lib.rs:1769`,`:4917`).
- **Cognition outage / capacity pressure.** The cheap tier-assignment + BT/HTN
  path runs co-located with the world server and survives a cognition outage;
  the kernel still returns a believable action batch (monolith §"The Moirai
  Simulation Kernel"). The model run itself is the injected gateway's `[~]`
  host, not faked here.
- **Cost/quality figures.** The dollar estimate is a documented class-rate
  approximation (`lib.rs:1123`) and the candidate quality scores are eval-gated
  config, not measured in this lib — read them as policy inputs, not ground
  truth.

## Related

- [./ori-biography-service.md](./ori-biography-service.md) — the **Memory**
  authority Moirai rehydrates from on escalation and consolidates into on
  de-escalation; where every cognition-derived event is durably recorded.
- [./cognition-stack-and-agent-behavior.md](./cognition-stack-and-agent-behavior.md)
  — the cognition stack Moirai schedules (context assembly → Sophia grounding →
  Psyche generation → Isis policy gate → commit) and the HTN/BT/LLM arbitration
  it drives.
- [./determinism-data-and-integration.md](./determinism-data-and-integration.md)
  — the deterministic-core / logged-cognition split, replay, the audit-platform
  retention, and how the kernel integrates with the Egbe world server.
- Hub: [../V6_ARCHITECTURE.md](../V6_ARCHITECTURE.md) — the "The Agent Mind"
  section and the "AI Cost and Fidelity Tiering" engineering bet.
