Proofs are the unit of account
Daedalus does not ask “is it done”. It asks which of seventeen named proofs a goal requires, which evaluators claim to establish each of them, and which proofs nobody in the registry can currently prove. Everything the system knows about quality is expressed in that vocabulary.
The seventeen proof kinds
Proof is a closed union at types.ts:12–29. It is not extensible at
runtime; adding a proof means editing the type, which forces every switch and manifest to be
re-checked by the compiler. The names fall into four rough groups, though the source itself
imposes no grouping.
Universal gates
boots— the artifact startsbuilds— the build command exits zerotests_pass— the test command exits zero
Interface & appearance
user_interactionvisual_nontrivial— not a placeholder screenresponsive_layout
Cross-cutting
performance_budgetsecurity_baseline
Gameplay
game_controlsgame_collisiongame_combatgame_win_loss
Domain contracts
api_contractauth_behaviorcli_golden_outputdata_invariantscitation_quality
“For build me Doom, the evaluator cannot be an LLM saying ‘looks good.’ It
must run the artifact and prove behavior.”
Four of the gameplay proofs exist because of that sentence. The proof vocabulary is deliberately weighted toward things a program can observe from outside the artifact — pixels, exit codes, HTTP responses, frame timings — rather than toward things an agent can assert about its own work.
Fig. 3.1 Evaluator / proof coverage plan
proves array is a declaration in a TypeScript literal; nothing verifies that a tool
capable of establishing game_collision exists on the machine. Note also that only
one of the seven manifests would ever overlap another on a real goal:
performance_budget is claimed by both browser-game and
data-invariants, which never co-occur because no domain selects both.The registry in full
Each manifest is a plain object literal. Six fields are required by
EvaluatorManifest (types.ts:55–70); the rest —
publicTests, hiddenTests, mutationTests,
artifacts, antiCheatChecks, scoreWeight,
command — are optional annotations.
| id | Domains | Proves | Runtime requirements | scoreWeight |
|---|---|---|---|---|
| shell-core | all except research |
boots, builds, tests_pass | package scripts or explicit commands | 0.20 |
| playwright-web | web_app, browser_game | boots, user_interaction, visual_nontrivial, responsive_layout | local web server, Playwright | 0.25 |
| browser-game | browser_game | game_controls, game_collision, game_combat, game_win_loss, performance_budget | browser game, input driver, test telemetry or black-box probes | 0.35 |
| api-contract | api | api_contract, auth_behavior, security_baseline | local API server, contract definition or generated schema | 0.30 |
| cli-golden | cli | cli_golden_output | CLI command, fixture inputs | 0.30 |
| data-invariants | data_ml | data_invariants, performance_budget | fixture dataset, metric command | 0.30 |
| research-sources | research | citation_quality | source list, claim mapping | 0.30 |
The scoreWeight column is inert. It appears seven times in
evaluatorRegistry.ts and once as an optional field at
types.ts:69, and is read nowhere. scoring.ts weights every required
proof identically. Likewise command is declared optional on
EvaluatorManifest and is not set on any of the seven manifests — there is no
string in the registry that could be executed.
What the manifests do carry, and carry seriously, is anti-cheat intent. Every non-trivial
evaluator declares mutation tests that must fail on a deliberately broken artifact. The
browser-game manifest is the most explicit:
41 mutationTests: [ 42 "disabled movement must fail", 43 "disabled collision must fail", 44 "fake telemetry without pixel evidence must fail" 45 ], 46 artifacts: ["screenshots", "video", "frame stats", "gameplay transcript"], 47 antiCheatChecks: [ 48 "cross-check telemetry against canvas pixels", 49 "store random seed", 50 "reject animated noise as gameplay" 51 ], 52 scoreWeight: 0.35
“Reject animated noise as gameplay” is the kind of rule that only gets written by someone who has watched an agent produce a canvas full of moving pixels and call it Doom. These strings are instructions to a human or agent writing the evaluator, not assertions the registry can enforce.
Selection is two array filters
112export function selectEvaluators(domain: Domain, requiredProofs: Proof[]): EvalSelection { 113 const candidates = evaluatorRegistry.filter((e) => 113 e.domains.includes(domain) || e.domains.includes("unknown")); 114 const selected = candidates.filter((e) => 114 e.proves.some((proof) => requiredProofs.includes(proof))); 115 const covered = new Set(selected.flatMap((e) => e.proves)); 116 const uncoveredProofs = requiredProofs.filter((proof) => !covered.has(proof)); 117 118 return { domain, requiredProofs, evaluators: selected, uncoveredProofs }; 124}
Line 113 treats "unknown" in an evaluator’s domains array as a
wildcard, not as the literal unknown domain. That is why shell-core — which lists
eight domains including "unknown" — is a candidate for every goal. It is a useful
behaviour, but the type does not distinguish “applies to unclassified goals” from “applies
everywhere”, so an evaluator author cannot express one without the other.
Line 115 also has a quiet consequence. covered is built from the full
proves array of every selected evaluator, not from the intersection with
requiredProofs. Selecting api-contract for its
api_contract proof therefore also marks security_baseline covered,
whether or not the goal asked for it. Coverage is transitive through selection.
Selection outcome for every domain’s default proof set
| Domain | Selected evaluators | Uncovered proofs |
|---|---|---|
| browser_game | shell-core, playwright-web, browser-game | none |
| web_app | shell-core, playwright-web | none |
| api | shell-core, api-contract | none |
| cli | shell-core, cli-golden | none |
| data_ml | shell-core, data-invariants | none |
| research | research-sources | none |
| library | shell-core | none |
| unknown | shell-core | none |
| infra | shell-core | security_baseline |
Eight of nine domains are fully covered by construction, which is unsurprising: the default
proof lists and the evaluator manifests were written together. infra is the
exception and the only place the missing-evaluator path fires without a hand-edited contract.
Fig. 3.2 Missing-evaluator path — elevation
docs/ARCHITECTURE.md and enforced only by
convention.Asking for the gap directly
The missing-evals command compiles a goal and returns a proposal for each proof
no selected evaluator claims. Because infra is the one domain with a real gap, it
is the natural demonstration.
$ daedalus missing-evals "deploy a kubernetes cluster with terraform"
# classifyDomain -> "infra" (keywords: kubernetes, terraform)
# defaultProofsForDomain("infra") -> builds, tests_pass, security_baseline
# selectEvaluators -> [shell-core] uncoveredProofs -> [security_baseline]
[
{
"id": "eval_proposal_IkuDsIk1",
"proof": "security_baseline",
"domain": "infra",
"status": "proposal",
"purpose": "Create a reproducible evaluator for security_baseline.",
"suggestedFiles": [
"evals/custom/evaluator.test.ts",
"evals/custom/fixtures/"
],
"validationPlan": [
"define observable evidence for the proof",
"write a passing check for a correct artifact",
"mutate or mock a broken artifact",
"confirm the evaluator fails on the broken artifact"
],
"antiCheatChecks": [
"avoid checking only static text",
"prefer artifact behavior over agent trace claims",
"add hidden randomized cases where possible"
]
}
]
Field values transcribed from the fallback branch at
evaluatorRegistry.ts:176–191; the identifier suffix is nanoid(8) and
varies per invocation.
Note what the fallback branch does not know. It has no idea that a security baseline
for Terraform means Checkov or tfsec, or that RESEARCH.md:241 already lists
Semgrep, gitleaks, OSV, Trivy, npm audit and pip-audit as the intended security tooling. The
proposal is domain-blind boilerplate outside the two hand-written branches.
Evaluator packs
Six JSON documents sit under evaluator-packs/, one per pack. They restate the
domain-to-evaluator mapping and add a list of recommended file paths. No TypeScript source
imports them, and package.json has no YAML or glob dependency, so they function as
documentation for whoever writes the evaluators by hand.
| Pack | Domains | Evaluators | Recommended files |
|---|---|---|---|
| game | browser_game | shell-core, playwright-web, browser-game | evals/playwright/game.spec.ts evals/gameplay/bot.ts evals/visual/canvas-check.ts evals/perf/frame-budget.ts |
| web | web_app, browser_game | shell-core, playwright-web | evals/playwright/app.spec.ts evals/visual/responsive.spec.ts |
| api | api | shell-core, api-contract | evals/api/contract.test.ts evals/api/auth.test.ts evals/api/invalid-inputs.test.ts |
| cli | cli | shell-core, cli-golden | evals/cli/golden.test.ts evals/cli/fixtures/ |
| data | data_ml | shell-core, data-invariants | evals/data/invariants.test.ts evals/data/fixtures/ |
| research | research | research-sources | evals/research/source-audit.json evals/research/claim-map.json |
The game pack carries one field the others do not: a test_api_guidance object
naming window.__DAEDALUS_GAME_TEST__ as the sanctioned telemetry hook, with the
rule “Test telemetry is allowed, but it must be cross-checked against
screenshots/canvas/video evidence.” That is the same distrust of self-reported state that runs
through the whole design — a game can be instrumented to say it is working, so the instrument
is only admissible alongside pixels.
There is no infra pack and no security pack, which matches the one
red cell in Fig. 2.2 and the outstanding item at RESEARCH.md:319.
Sheet notes
- The registry array is exported as
evaluatorRegistryand is mutable at runtime by any importer. Nothing freezes it, and no code re-reads it aftercreateRun()has captured a selection into run state. EvalSelectionstores wholeEvaluatorManifestobjects, not identifiers, sorun.jsonembeds a full copy of each selected manifest. A run’s record of what was selected therefore survives edits to the registry — a genuine and deliberate property.templates/eval-plan.yamlsketches a three-part structure —public_evals,hidden_evals,anti_cheat— that has no counterpart in the type system.FrozenEvalSnapshotis the closest thing, and it holds only identifiers and hashes.- Nothing in the codebase distinguishes a public test from a hidden one at runtime. The
publicTestsandhiddenTestsarrays are adjacent string lists in the same object, readable by anyone who can read the module.