Egbe Companions · Architecture

The Moirai Simulation Kernel & Cost Tiering

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

6sections14 minread1diagram

On this page

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.

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::ActionIntents 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).

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.

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.

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.
  • ./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 — 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 — 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 — the "The Agent Mind" section and the "AI Cost and Fidelity Tiering" engineering bet.