Ariadne · Architecture

Daedalus Compiler & Theseus Evaluation

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

6sections13 minread1diagram1table

On this page

V8 (Ariadne — the self-authoring detective universe) mints a mystery in two halves: a symbolic skeleton that Clew, Minos & Palimpsest prove is uniquely solvable, and an experienced surface that Anansesem, Loom & the Ori suspects realize into prose, art, voice, and interrogation. Daedalus and Theseus are the last mile: where those two halves become a thing the shipping game can load, and where the pipeline earns the right to call a case shippable. Daedalus is the compiler — it lowers a verified MysterySession plus its resolved asset manifest into the exact V5 structs the existing cold-cases plugin reads, then runs the same Python schema gate the human Case Author tool runs. Theseus is the automated playtester and eval harness — an autonomous solver that plays the compiled case against V5's real data model, an 8-dimension LLM-as-judge quality panel, a difficulty calibrator, and a regression corpus, composing into a single all-or-nothing release decision. Between them they answer V8's version of V2's shipping question — how do you know a generated case is good enough to ship? — with proofs in game data rather than attestations. This page is the deep companion to the section hub at ../V8_ARCHITECTURE.md, whose §4.7–§4.8 introduce Daedalus and Theseus.

What ships, honestly#

The compile mapping is real and deterministicClues, clue logic, and accusations lower into the FV5* structs (libs/yemaya/case-compiler/src) and a v2 cold_cases_manifest.json that the authoritative Python validator V5/tools/cold-cases/validate-cold-cases.py --generated must accept as a build gate (compile.ts:108). Theseus is equally real: the solver reasons over the compiled structs and is tested to reach the intended culprit decisively across 20 seeds (eval.test.ts:56); G4 in-game solvability is proven to fail when a required edge is dropped (eval.test.ts:67); the PCA aggregation is a genuine power-iteration eigenvector, not a mean (pca.ts:40); and a judge error propagates rather than being swallowed (eval.test.ts:108). The seven-gate platform suite (libs/v8/case-gates) and the C2PA-stamping bundle forge (libs/v8/case-bundle) are real and spec-anchored too.

What is a seam, not a fabrication: the LLM judge is an injected JudgeFn that refines the deterministic heuristic scores; absent, the heuristic still grades from real case structure (judge.ts:152). The media providers are injected CaseMediaGenerator boundaries — an absent provider yields a not-configured slot, never a fabricated asset (case-bundle.ts:262).

Two honestly labeled divergences from the architecture prose, because the doc and the code disagree. First, §4.7 names the app apps/v8/daedalus; the real app is apps/v8/daedalus-compiler, a thin CLI wrapper — the compile core was lifted into a library, @yemaya/case-compiler, so other libs and apps can import it without a lib↔app dependency (daedalus-compiler/src/index.ts:7). The "runtime-relay" the same comment mentions is aspirational; the actual on-demand relay is the separate apps/v8/loom-service (it owns the server.ts). Second, §3.4 says deduction edges set Kind=Generated; the shipping V5 enum is only 'Authored' | 'Weak' (v5-target.generated.ts:15), so the compiler maps confidence to those two real values — its comment says so: "the shipping enum has no Generated value" (deductions.ts:9). The code is honest to the engine; the doc over-specified.

Daedalus — compiling a verified skeleton into a V5 pack#

Daedalus's contract is narrow and strict: turn a MysterySession Minos already proved (plus an optional AssetManifest) into a CompiledCase — the FV5* structs, the v2 manifest, and a resolved asset bundle (compiled-case.ts:27). CompiledCase lives in @yemaya/case-contracts so the compiler (an app) and Theseus (a lib) agree on the shape without depending on each other.

The local pipeline: generate → verify → compile#

The CLI is the offline pipeline in one command (apps/v8/daedalus-compiler/src/cli.ts:55):

text
daedalus --seed 42 --cell Period --difficulty standard --suspects 4 --out ./out

runCli builds a CaseSpec from the flags (difficulty drives the suspect/clue bands — standard ⇒ 4 suspects, 11 clues, cli.ts:38), calls generateCaseSync (Clew), then verifyCase (Minos). It prints the Minos verdict — g1 uniqueness, g2 completeness, g3 fair-play, plus balance — and refuses to compile a case that failed verification, returning exit code 1 (cli.ts:85). Only on a green verdict does it compile, writing <caseId>.compiled.json and .manifest.json (cli.ts:108).

The draft-vs-release distinction is load-bearing. A standalone compile is a draft: Minos's G1–G3 are proven here, but G4–G8 (Theseus, calibrated judge evidence, safety, canon, human-quality launch evidence) are downstream, so a bare invocation labels it a draft and skips the Python build gate unless --release is passed with every gate asserted green (cli.ts). A draft produces the structs Theseus needs to play without pretending the case cleared gates it has not been run through. In release mode the CLI runs G4, the diagnostic judge, G6, and G7, loads exact-artifact calibrated G5 and human-quality G8 evidence, calls decideRelease, and only then re-compiles through the Python gate. Missing evidence blocks; no downstream gate is inferred from --release.

The FV5 mapping#

compileCase (compile.ts:59) builds a shared context, then runs four deterministic mappers:

  • Evidence (evidence.ts): one FV5MindPalaceEvidenceNode per clue. The fiction timestamp is t+NN% from the clue's introducedAt.percentage (what Theseus later reads to score pacing), bFalseLead mirrors isRedHerring, and Image binds to the generated asset when the manifest has one.
  • Deductions (deductions.ts): two pairing kinds form the Mind Palace graph. The culprit chain pairs the incrimination clues in the order they surface (means → opportunity → motive → broken alibi), with confidence climbing 0.7 → 0.9 as it converges on the alibi flaw — the 0.95 in the Math.min(0.95, …) clamp is a never-binding cap, since the formula's realized top is 0.7 + 0.2·1 = 0.9 (deductions.ts:69); each herring↔clearance pair links a refuted red herring to its suspect's clearance, at 0.55. Kind is deductionKindFromConfidence≥0.7 ⇒ Authored, else Weak (v5-target.ts:31) — and a cross-cell pairing sets bCrossEra (deductions.ts:47).
  • Accusations (accusations.ts): the culprit's outcome is Rating: 'Brilliant', OutcomeBranch: 'TruthRevealed', carrying the culprit chain's edge ids as RequiredEdgeIds (accusations.ts:30) — the spine Theseus G4 walks. Each innocent gets a plausible-wrong outcome: one who kept a motive is Doubtful/PlausibleConviction ("a wrongful conviction waiting to unravel," the hook Palimpsest's freed-vengeful write-back seizes), one cleared by alibi is Wrong/Unproven (accusations.ts:53).

The manifest and the Python build gate#

compilePack (pack.ts) emits the FV5ColdCasePackDefinition header and the v2 GeneratedColdCaseManifest. VO counts come from the asset manifest's real voice-over summary (summariseVoiceOver), defaulting to zero when no audio was generated rather than inventing line counts (pack.ts:43). The pack records a full generation provenance block — seed, the C2PA root, the asset-manifest id, the Minos verifier-report id, and the (seed, model-versions, canon-hash) repro key (pack.ts:89) — alongside the releaseGates map (pack.ts:110).

Two gates then bite, in order. The in-process validateGeneratedManifest (manifest.ts:72) mirrors the Python validator: ≥3 evidence nodes and ≥2 deduction pairs, no duplicate ids, sourceCells containing the lead cell and MindPalace, a non-empty title/brief, a provenance block whose seed matches and that cites a verifier report and C2PA root — and that every release gate is green (manifest.ts:99). A draft tolerates not-yet-green release-gate problems by filtering them out (compile.ts:100); a publish does not. Then, on a publish only, runPythonBuildGate writes the manifest to a temp file and runs validate-cold-cases.py --generated, throwing CompileError on a non-zero exit (compile.ts:125). The CLI test proves the path end to end: a --release run of seed 5 exits 0 and validateGeneratedManifest of the emitted manifest returns [] (cli.test.ts:9). The schema is v2, backward-compatible — the 25 authored launch packs (v1) still validate; v2 only adds generation provenance (manifest.ts:1). One more compile-time guard: resolveBundle throws if any FSoftObjectPath the structs reference is unresolved (compile.ts:91) — a case cannot compile pointing at art that does not exist.

Theseus — playtesting the compiled case#

Theseus (libs/yemaya/case-eval, @yemaya/case-eval) is the architecture's named eval lib (§4.8 — here the doc and code agree). Its defining choice: it reasons over the compiled FV5* data, not the IR. Minos proved the IR admits a unique solution; Theseus proves that property survived the compile — catching a dropped edge, a mis-bound node, or a herring that lost its clearance, losses an IR-level proof structurally cannot see (solver.ts:1).

The autonomous solver#

solveCompiledCase(compiled, skill) (solver.ts:30) is a deterministic graph solver. It scores each suspect by the confidence of the deduction edges converging on them; a herring↔clearance edge refutes (subtracts), modeling "looked guilty, then cleared" (solver.ts:63). The skill ∈ [0,1] parameter is a confidence floor — (1−skill)·0.6 — so a low-skill solver ignores buried chains and may miss the answer (solver.ts:39). It accuses the highest positive-net suspect and reports decisive only when the margin over the runner-up is ≥ 0.5 (solver.ts:88). The full-skill solver is authoritative; an LLM "detective" is an optional realism layer, not required to prove solvability.

G4 — in-game solvability#

checkInGameSolvable(compiled) (g4.ts:24) is the end-to-end proof in game data. It finds the Brilliant outcome, then demands four things: the solver accuses that culprit, the accusation is decisive, every RequiredEdgeIds edge exists and is walkable (both endpoints resolve to real evidence nodes, g4.ts:54), and the cited EvidenceChain nodes resolve. Any gap fails the gate with a reason (g4.ts:75). The teeth: a test removes one required edge from a verified case and asserts G4 flips to false (eval.test.ts:67) — the exact compile-loss class this gate catches.

The judge-panel diagnostic and G5 calibration#

scoreHeuristic (judge.ts:81) grades eight rubric dimensions — coherence, surprise, fairness-feel, pacing, character, prose, voice-fit, difficulty-accuracy — each from real structure, never a random number. Coherence runs the solver (culprit + decisive ⇒ 0.9); surprise peaks at a ~⅓ red-herring density; pacing is 1 − normalized std-dev of the evidence staging gaps; difficulty-accuracy measures how closely the a-priori grade, the empirical 1 − solveRate, and the target align (judge.ts:116). An injected JudgeFn may then refine any subset of scores, and a judge that throws propagates (fail-loud, eval.test.ts:108). judgeCase aggregates equal-weight; judgePanel learns weights across a batch via real PCA: pca1Weights mean-centers the score matrix, builds the covariance, and takes its top eigenvector by power iteration (pca.ts:40), sign- and L1-normalized to non-negative weights summing to 1 (pca.ts:58). The spec checks the eigenvector of a diagonal [[5,0],[0,1]] favors axis 0 (eval.test.ts:123) — a stub mean would fail it. The default pass threshold is 0.6 (judge.ts:164).

Those heuristic/PCA outputs are diagnostics, not release authority. G5 requires independently calibrated human-aligned judge evidence bound to the exact artifact, licensed gold, current approval, bias probes, held-out champion/challenger evidence, and a clean contamination analysis (calibratedG5EvidenceProblems, release.ts). Missing or invalid evidence fails G5 even when the heuristic score passes.

Calibration and the regression corpus#

calibrateDifficulty (calibration.ts:24) runs Theseus at five skill levels (0.2 … 1.0) to estimate a solve-rate, then reports the empirical difficulty 1 − solveRate and the drift from Minos's a-priori grade — a case the strongest solver cannot crack is mis-graded, a case every weak solver cracks is easier than its grade claims, and that signal feeds back to the Oracle's player model. runRegressionCorpus (corpus.ts:26) anchors the suite: every corpus case must be G4-solved by Theseus and judged above threshold (V5's 25 authored cases are the intended golden fixtures once cooked). Its test forges six generated cases and asserts all six solve and pass (eval.test.ts:166).

The two gate contracts#

One thing to internalize before reading any gate code: V8 has a seven-gate platform suite and a distinct eight-gate pipeline ReleaseDecision, both real. Confusing them makes the code look contradictory when it is not.

# Theseus ReleaseDecision (case-eval/src/release.ts) Platform suite (@oshun/v8-case-gates, case-gates/src/index.ts)
G1 Minos formal uniqueness fair-play (every solution clue presented before the reveal)
G2 deductive completeness solvability (the CSP proved one solution)
G3 fair-play + mechanic balance clue-grounding (every cited clue exists)
G4 Theseus in-game solvable suspect voice-distinctiveness
G5 calibrated human-aligned judge evidence misdirection (≥1 herring, ≤ max — fair, not a maze)
G6 Sekhmet safety (§11.4) prose quality
G7 canon consistency (§1.4) content safety
G8 preregistered human-quality launch evidence

The ReleaseDecision framing (release.ts:40) is ordered by the pipeline stage that produces each verdict — Minos owns G1–G3, Theseus and calibrated judge evidence G4–G5, the safety and canon stages G6–G7, and preregistered human-quality launch evidence G8 — and decideRelease publishes only when all eight pass, attaching a specific blocking reason per failure; its test confirms a single failing gate (G6) blocks publication (eval.test.ts:179). The platform-suite framing (case-gates/src/index.ts:99) is ordered by kind of check and registers seven GateDefinitions on the shared @oshun/content-release-gates ReleaseGateService via the platform's gateFromManifestCheck / gateFromEvalScore / createGroundingGate builders — composed, not forked. evaluateV8Case returns cleared only when all seven pass, and its spec asserts the exact gate-id list and that each fault blocks its own gate (case-gates.spec.ts:30); like V9's lesson-gates suite it reads a pre-stamped uniqueSolutionProven flag for G2 (index.ts:60) rather than re-solving. There is even a third naming — Minos's internal g1Uniqueness/g2Completeness/g3FairPlay the CLI prints (cli.ts:82), which supplies the ReleaseDecision's G1–G3. The common thread: one false gate blocks, and the manifest validator enforces the same rule in process — every release gate green or no compile (manifest.ts:99).

The libs/v8/case-bundle forge is where the platform suite bites in the libs/v8 vertical: forgeCaseBundle proves uniqueness before any generation (throwing CaseNotSolvableError, case-bundle.ts:237), fails loud with no prose writer, C2PA-stamps each produced asset with a verifiable Ed25519 manifest (stampAsset, :184), and runs the assembled case through evaluateV8Case — a quality or fairness failure leaves blocked: true. Its spec proves no prose/media is generated for an unsolvable skeleton and that tampering any signed field flips verification to false (case-bundle.spec.ts:147,:169).

flowchart TD S[CaseSpec + seed] --> GEN[Clew: generateCaseSync] GEN --> MIN{Minos: verifyCase<br/>G1 unique · G2 complete · G3 fair-play} MIN -- fail --> X1[exit 1: not compiling] MIN -- pass --> DRAFT[Daedalus draft compile<br/>FV5 structs, skipPythonGate] DRAFT --> THE[Theseus eval on compiled data] THE --> G4{G4 in-game solvable?<br/>solver hits culprit, edges walkable} G4 -- no: dropped edge / mis-bound node --> BLK[blocked: no publish] G4 -- yes --> G5{G5 calibrated human-aligned<br/>judge evidence valid?} G5 -- no --> BLK G5 -- yes --> ALL{all eight gates green?<br/>+ G6 safety, G7 canon, G8 human quality} ALL -- no --> BLK ALL -- yes --> PUB[Daedalus publish compile<br/>validateGeneratedManifest + validate-cold-cases.py] PUB -- python gate rejects --> X2[CompileError] PUB -- accepted --> PACK[v2 cold_cases_manifest pack<br/>+ provenance + C2PA → cold-cases plugin]

How a case fails eval — edge cases#

  • In-game solvability lost. A compile that drops a required edge, mis-binds a node, or strips a herring's clearance makes the solver miss or under-decide: G4 returns pass: false with the reason and decideRelease refuses to publish (g4.ts:75, eval.test.ts:67).
  • G5 calibration absent or invalid. A passing heuristic/PCA aggregate cannot publish without exact-artifact, current, independently calibrated human-aligned judge evidence (calibratedG5EvidenceProblems, release.ts); the platform-suite parallels are low voice, low prose, or unsafe content (case-gates.spec.ts:106).
  • Under-determined skeleton. Caught before any generation in the bundle forge (CaseNotSolvableError); in the suite it fails G2 solvability (case-gates.spec.ts:65).
  • A solution clue withheld, invented, or mis-balanced misdirection. Fail platform-suite G1 fairness (case-gates.spec.ts:50), G3 clue-grounding (:71), or G5 misdirection — zero herrings or past maxRedHerrings (:79).
  • A non-green release gate at compile. A draft tolerates it; a publish does not — validateGeneratedManifest then the Python gate reject the pack (manifest.ts:99, compile.ts:108).
  • Honest absences. No python3skipPythonGate keeps a draft buildable while labeling it a draft; an absent media provider ⇒ a not-configured slot, not a faked asset; a wired LLM judge that errors ⇒ propagated, not swallowed.

On any of these the case does not ship — nothing is published or cached — and the failing gate's reason is the signal a repair pass (Clew) or a human reviewer acts on.