feat(experiment): sealed experiments — the registered rule is the executed rule - #564
Conversation
…cuted rule Add the ./experiment subpath. Rules are data: a 14-family typed AST (predicates, admission, selection, estimands, intervals, decisions, obligations, validity gates, halt, budgets, matched budget, reissue). sealExperiment canonicalizes and hashes the whole tree; the only execution surface takes sealed rules plus evidence, with no parameter for alpha, threshold, metric, or stopping rule. New objects close the four measured gaps: - defineExperiment / sealExperiment / openSealedExperiment with the kill-test AST; an amendment is a re-seal with a digest chain. - clusteredPower: whole-cluster bootstrap power simulation with a refusal verdict. The closed-form sign-flip floor (2^(1-C)) refuses 4-cluster and 3-cluster designs at alpha 0.05 at any effect size. - buildFunnel / executeAdmissionRule: the denominator chain as a first-class object that refuses a stage that gains rows and renders itself as text and JSON. - verifyMatchedBudgets: realized-token arm matching as a refusal artifact, never prose. The statistics underneath are re-exported from their existing homes; this subpath adds registration and refusal, not estimator forks. Acceptance: the week's three hand-written preregistrations (killtest, freelunch, tbench milestone 2) are expressed with the module, sealed, and each recorded decision is reproduced by executing the sealed rules against the recorded evidence — including the amendment-6 ledger drift the seal makes unrepresentable. The analyst benchmark dependency-lock digest moves because package.json gains the ./experiment export.
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — f438a176
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: drewstone_author · 2026-08-10T20:12:50Z
tangletools
left a comment
There was a problem hiding this comment.
🟡 Value Audit — sound-with-nits
| Verdict | sound-with-nits |
| Concerns | 2 (1 low, 1 weak-concern) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 135.2s (2 bridge agents) |
| Total | 135.2s |
💰 Value — sound-with-nits
Adds a sealed-experiment subpath where every registered rule is a hashed, typed AST node and the only execution surface takes evidence alone — coherent, deeply evidence-backed, and composes existing stats rather than forking them; one genuine overlap with trace-repair's denominator chain is the only
- What it does: Adds the @tangle-network/agent-eval/experiment subpath (src/experiment/{ast,define,funnel,budget,power,index}.ts). Experiment rules become a 14-family typed data AST (predicates, admission funnel stages+waivers, deterministic selections with a closed read-set, estimands, interval specs with embedded seeds, ordered decision tables plus a 'report-only' variant, obligations, validity gates, halt rule
- Goals it achieves: Make the registered rule and the executed rule the same object, so design drift (changing alpha, swapping the metric, dropping a stopping rule after peeking) is impossible rather than audited-after-the-fact. Move every refusal — an underpowered cluster count, a non-monotone funnel, a mismatched arm budget, a non-total decision table — inside a typed verdict artifact instead of prose beside a numbe
- Assessment: Sound and unusually well-evidenced. The grain matches the codebase exactly: typed data nodes over closures, typed outcome objects with refusals inside them, content-hash sealing, and fail-loud ValidationError/CaptureIntegrityError throws — all established patterns (pre-registration.ts, clustered-paired-binary.ts, campaign/gates/*). The acceptance suite (tests/experiment/preregistration-acceptance.
- Better / existing approach: One genuine overlap exists. The new buildFunnel (src/experiment/funnel.ts:67, assertFunnelReconciles at :142) and the pre-existing buildDenominatorChain / assertChainReconciles (src/trace-repair/admission-records.ts:283 / :340) implement the identical monotone-reconciliation invariant (input = surviving + excluded; throw on a stage that gains rows or a chain that doesn't add up). docs/experiment.m
- Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 2
- Bridge warning: opencode/kimi-for-coding/k2p7: opencode: opencode error
🎯 Usefulness — sound
A coherent sealed-experiment substrate that composes the existing statistics and pre-registration primitives, closes four named charter gaps, and is validated by a real three-preregistration acceptance suite — landing ahead of its first production caller (Wave 3 runtime wiring), which is the expecte
- Integration: Fully wired and reachable. The
./experimentsubpath is registered in package.json:142-146 and scripts/build-entries.mjs:27. All 14 AST node families have interpreters routed through the single execution surfaceRegisteredExperiment(define.ts:379-502):decide,admit,select,gate,halt,runUniformPassBudget,projectNLadderBudget,matchedBudgets,estimate,interval. No produ - Fit with existing patterns: Composes, does not compete. (1) Sealing reuses the existing
hashJson/canonicalizefrom pre-registration.ts:19,113 — the same primitivesignManifestis built on — extending the established single-metric pattern to a full-design registration. docs/experiment.md:98 explicitly positionsHypothesisManifestas the lightweight form andsealExperimentas the full form; clear layering, no duplica - Real-world viability: Holds up past the happy path. The acceptance suite is the charter's demanded kill test: three real preregistrations reproduced, including the registered-vs-ran divergence the seal makes unrepresentable (freelunch amendment-6 ledger) and the $0.00 halt (killtest four-gate failure). Edge inputs are fail-loud everywhere: zero rows throw (ast.ts:297,322), empty intersect throws (ast.ts:240), non-total
- Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 1
🔎 Heuristic Signals
🟡 Cruft: magic number added tests/experiment/budget-and-seal.test.ts
resamples: 1000,
💰 Value Audit
🟡 Two monotone denominator-chain implementations now coexist [maintenance] ``
src/experiment/funnel.ts:67 (buildFunnel) and src/trace-repair/admission-records.ts:283 (buildDenominatorChain) both enforce 'input = surviving + excluded, throw on non-monotone stage, throw on non-reconciling chain' — the core invariant is duplicated. The new one is generic (arbitrary stage ids, partitions, composition, text render); the trace-repair one is domain-bound (closed AdmissionExclusionReason enum, strata, versioned artifact). docs/experiment.md:99 declares the split intentional. No a
What this audit checks
It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.
| Pass | What it asks |
|---|---|
| Heuristic | Vague title? Whitespace-only or cruft-bearing diff? (content signals only) |
| Duplication | Do added function/class names already exist elsewhere in the repo? |
| Value Audit | What does it do? What goal does it achieve? Is it good? Better architecture or already-exists? |
| Usefulness Audit | Does it integrate and fit? Will it hold up in real use and actually get used? |
Findings are concerns, not blocks — the human reviewer decides what to do with them.
What
Adds the
@tangle-network/agent-eval/experimentsubpath: experiments as sealed objects (charter Wave 2).Rules are data. A 14-family typed AST carries every registered rule — predicates, admission funnels with registered waivers, deterministic selections with a closed read set, estimands, interval specs with seeds, ordered decision tables (and
report-onlyfor registrations that decline a threshold), obligations, validity gates, halt rules, budget schedules with named ledgers, matched-budget rules, and reissue policies.sealExperimentcanonicalizes and hashes the whole tree;openSealedExperimentverifies the digest and returns the only execution surface. No executor takes a parameter for alpha, threshold, metric, or stopping rule, so registered-vs-ran drift is unrepresentable rather than checked. An amendment is a re-seal with a{at, reason, blind[], digest}chain.New objects close the four measured gaps; everything statistical underneath is re-exported from its existing home (statistics.ts, clustered-paired-binary.ts, sequential.ts, the campaign gates, paired-arms.ts, pre-registration.ts, experiment-tracker.ts):
clusteredPower— whole-cluster percentile-bootstrap power simulation with a refusal verdict inside the artifact. The closed-form floor refuses before simulation: with C clusters the exact sign-flip test can never produce a two-sided p below 2^(1-C), so the{6,3,3,2}structure (0.125) and any 3-cluster design (0.25) are refused at alpha 0.05 at ANY effect size — the burn that was previously learned by running the experiment.buildFunnel/executeAdmissionRule— the denominator chain as a first-class object: refuses a stage that gains rows, named exclusions that do not sum, and partition overdraw; composes across boundaries; renders itself (text table + JSON); partitions arepooling: 'never'.verifyMatchedBudgets— realized-token arm matching under the registered tolerance, returning a verdict whose refusal carriesonFail: 'refuse-contrast'.defineExperiment— cross-reference validation: a decision condition can only read a registered interval, estimand, or obligation; a halt rule only registered gates; a filter-of base only a registered selection or sealed subset.Acceptance — the three preregistrations
tests/experiment/preregistration-acceptance.test.tsexpresses the week's three hand-written preregistrations with the module, seals them, and reproduces each recorded decision by executing the sealed rules against recorded evidence (vendored with provenance inrecorded-fixtures.ts):refuse-spend($0.00, contrast never run); positive interval without the registered control lands inblocked-pending-registered-controlnot-certified-at-this-nChecks
pnpm typecheck— cleanpnpm test— 386 files passed, 5341 tests passed, 0 failed (3 pre-existing skips)pnpm build— clean;dist/experiment/index.jssmoke-imported, refusal fires from the built artifactpnpm verify:package— exit 0 (publint + attw + packed-exports resolution incl../experiment)npx biome check src/experiment tests/experiment— cleanThe analyst-benchmark dependency-lock digest is bumped because package.json gains the
./experimentexport (same flow as #553).Docs:
docs/experiment.md(the covenant + fold-in map), cross-linked from the charter's Wave 2 line and CLAUDE.md's doc map.