Skip to content

feat(wonder): #228 bake-off harness — strategies + corpus + evaluator + runner - #397

Merged
robotrocketscience merged 3 commits into
mainfrom
feat/issue-228-wonder-consolidation
May 4, 2026
Merged

feat(wonder): #228 bake-off harness — strategies + corpus + evaluator + runner#397
robotrocketscience merged 3 commits into
mainfrom
feat/issue-228-wonder-consolidation

Conversation

@yoshi280

@yoshi280 yoshi280 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lands the wonder-consolidation bake-off harness from docs/v2_wonder_consolidation.md (#228). Three generation strategies (RW / TC / STS), a deterministic synthetic 200-atom corpus, a feedback simulator, an evaluator producing the four spec-defined metrics, and a multi-seed runner that emits a result JSON the v2.0 ship-decision can cite.

The actual ship-decision (single-strategy / ensemble / defer / drop) is the output of running python -m aelfrice.wonder.runner to convergence — not part of this PR. This PR ships only the harness so that R can be run separately and its output cited in the ship-decision PR per the spec § "Adoption criteria for v2.0 ship".

What's in tree

  • src/aelfrice/wonder/__init__.py — package surface.
  • src/aelfrice/wonder/models.pyPhantom dataclass (kept out of aelfrice.models per planning memo Decision B; Phantom is research-only).
  • src/aelfrice/wonder/strategies.pyrandom_walk, triangle_closure, span_topic_sampling. Pure reads over a MemoryStore; deterministic given seeded RNGs.
  • src/aelfrice/wonder/simulator.pybuild_corpus + populate_store + feedback_verdict + simulate_promotion. Single-topic agreement is the corpus's ground-truth predicate for "real relationship".
  • src/aelfrice/wonder/evaluator.pyevaluate_strategy, pairwise_jaccard, adoption_verdict (the four-rule decision tree from spec § "Adoption criteria").
  • src/aelfrice/wonder/runner.py — orchestrator + CLI entry point. Default sweep is N=10 seeds per planning memo Decision D.
  • src/aelfrice/wonder_consolidation.py — thin shim keeping the bench-gate stub honest (Decision A). Returns a token-overlap relatedness in [0,1]; not the bake-off itself.

Decisions locked in

Calling out the planning-memo decision points so reviewers can contest before merge:

# Decision Picked
A Bench-gate stub disposition Keep stub + add wonder_consolidation.score() shim
B Where Phantom lives aelfrice.wonder.models (not aelfrice.models)
C This issue ships strategies + harness, or harness only? Strategies + harness (one PR)
D Seed sweep size N=10 default
E construction_cost units atoms-touched
F Synthetic-graph density Defaults grid-tested on a 3-topic preview; tunable per-run

Tests

45 tests across four files (test_wonder_strategies.py, test_wonder_simulator.py, test_wonder_evaluator.py, test_wonder_runner.py). Cover: each strategy's empty/no-edge/normal/determinism shape; corpus generator determinism + size invariants; feedback verdict edge cases; α≥12 promotion gate; all four adoption_verdict paths; runner JSON-to-file round-trip.

The existing tests/bench_gate/test_wonder_consolidation.py continues to skip without AELFRICE_CORPUS_ROOT — the shim unblocks the ModuleNotFoundError skip path so once a wonder_consolidation/ corpus lands lab-side, the gate runs.

What this is not

  • Not the R ship-decision. That's a separate PR after running the harness.
  • Not a write-path activation. Strategies are read-only over the store; nothing mutates production state.
  • Not a corpus tuning pass. Default densities land each strategy in its predicted spec regime on a 3-topic preview; the running session will sweep before R.

Spec checks

  • docs/v2_wonder_consolidation.md § "The three candidate strategies" — RW / TC / STS implemented as described.
  • § "Adoption criteria for v2.0 ship" — four metrics + four verdict rules ported with thresholds (10pp floor, Jaccard 0.6 / 0.3, junk_rate 60%) as named constants in evaluator.py.
  • § "Out of scope" honored: no promotion-rule work (that's [v2.0] Phantom promotion-trigger rule — three rejected naive triggers, need a benchmarked rule #229), no lifecycle, no retrieval surface, no aelf reason touch. No lab-fixture imports.

Closes #228 once R is run and the chosen strategy lands in production retrieval (separate PR).

Summary by Sourcery

Introduce a self-contained wonder-consolidation bake-off harness with three phantom generation strategies, a synthetic corpus and feedback simulator, an evaluator implementing spec-defined metrics and adoption rules, and a multi-seed runner producing JSON results for the v2.0 ship decision.

New Features:

  • Add random-walk, triangle-closure, and span-topic-sampling phantom generation strategies over the MemoryStore.
  • Add a deterministic synthetic corpus builder, store populator, feedback simulator, and promotion gate for evaluating strategies offline.
  • Add an evaluator that computes confirmation, junk, and cost-based retrieval metrics plus pairwise Jaccard overlap and adoption verdicts per spec thresholds.
  • Add a bake-off runner CLI that sweeps multiple seeds, aggregates metrics, and writes audit-friendly JSON output.
  • Expose a wonder_consolidation.score shim that returns a token-overlap relatedness score to keep the existing bench-gate harness working.

Tests:

  • Add unit tests for each strategy’s edge cases, determinism, and session/topic constraints.
  • Add tests for corpus generation determinism, size/shape invariants, feedback verdicts, and promotion gating.
  • Add tests for evaluator metrics, Jaccard computation, and adoption-verdict branching logic.
  • Add integration tests for the bake-off runner’s result shape, determinism, CLI defaults, and JSON output.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Wonder bake-off framework for evaluating composition strategies with metrics including confirmation rate, junk rate, and retrieval efficiency.
    • Introduced three composition strategies: random walk, triangle closure, and span topic sampling.
    • Added adoption verdicts (single/ensemble/defer/drop) based on strategy performance comparison.
    • Added synthetic corpus simulator for testing strategies.
    • Added token overlap scoring for composition consolidation.
    • Added command-line interface for running bake-offs.
  • Tests

    • Added comprehensive test coverage for all new modules.

RW (random walk), TC (triangle closure), STS (span-topic sampling)
implementing the candidates from docs/v2_wonder_consolidation.md
"The three candidate strategies". Each is a pure read over a
MemoryStore returning a list of Phantom dataclasses; output sorted
by composition for the bake-off determinism story.

No write-path activation here — this is the research surface only;
ship-decision PR per spec § "Adoption criteria" wires the chosen
strategy into production retrieval.
simulator.py: deterministic corpus generator (n_topics × n_atoms_per_topic)
+ feedback simulator (single-topic agreement = confirm) + populate_store
helper. Synthetic from seed; no lab-fixture dependency.

evaluator.py: four metrics from spec § Adoption criteria — confirmation_rate,
retrieval_freq_per_cost, pairwise_jaccard, junk_rate — plus the verdict
function applying the four ship rules (single / ensemble / defer / drop).

runner.py: orchestrates a multi-seed sweep (default N=10 per planning memo
Decision D), aggregates mean metrics across seeds, emits result JSON.
CLI entry point at `python -m aelfrice.wonder.runner`.

wonder_consolidation.py: thin shim keeping the existing bench-gate stub
honest (Decision A). Token-overlap relatedness in [0,1]; not the bake-off.

Smoke run on 2-seed × 20-walk preview produces sane verdict distribution
(defer at default densities — corpus tuning is the running session's job
for R<final>, not part of this harness PR).
45 tests across four files:

- test_wonder_strategies.py (15) — RW/TC/STS unit coverage incl.
  empty-store, no-edges, determinism-from-seed, edge-type filtering,
  unordered-pair dedup, session-diversity floor.
- test_wonder_simulator.py (14) — corpus determinism, 200-atom
  default, topic partition invariant, populate_store stamping,
  feedback verdict shape, promotion gate at α≥12.
- test_wonder_evaluator.py (11) — metric math, four verdict rules
  (single/ensemble/defer/drop) on synthetic metric shapes.
- test_wonder_runner.py (5) — runner smoke + determinism + JSON
  output to file.

Bench-gate stub (tests/bench_gate/test_wonder_consolidation.py)
continues to skip without AELFRICE_CORPUS_ROOT — the new
`aelfrice.wonder_consolidation` shim is what unblocks the
ModuleNotFoundError skip path.
@sourcery-ai

sourcery-ai Bot commented May 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements the full wonder-consolidation bake-off harness: a synthetic corpus + simulator, three phantom-generation strategies, an evaluator with spec-aligned metrics and adoption rules, a multi-seed runner/CLI producing JSON results, and a bench-gate shim plus tests wiring it all together.

Sequence diagram for run_bakeoff orchestration per seed

sequenceDiagram
    actor Operator
    participant Runner as run_bakeoff
    participant SeedLoop as _run_one_seed
    participant RNG as random.Random
    participant Simulator as simulator
    participant Store as MemoryStore
    participant RW as random_walk
    participant TC as triangle_closure
    participant STS as span_topic_sampling
    participant Evaluator as evaluate_strategy

    Operator->>Runner: run_bakeoff(config)
    Runner->>SeedLoop: _run_one_seed(seed, n_topics, n_atoms_per_topic, n_walks, n_sts_samples, feedback_budget)

    SeedLoop->>RNG: create Random(seed)
    SeedLoop->>Simulator: build_corpus(rng, n_topics, n_atoms_per_topic)
    Simulator-->>SeedLoop: SyntheticCorpus

    SeedLoop->>Store: MemoryStore(":memory:")
    SeedLoop->>Simulator: populate_store(Store, SyntheticCorpus, rng)

    SeedLoop->>RW: random_walk(Store, rng=Random(seed+1), n_walks)
    RW-->>SeedLoop: list Phantom (RW)

    SeedLoop->>TC: triangle_closure(Store)
    TC-->>SeedLoop: list Phantom (TC)

    SeedLoop->>STS: span_topic_sampling(Store, rng=Random(seed+2), n_samples)
    STS-->>SeedLoop: list Phantom (STS)

    SeedLoop->>Evaluator: evaluate_strategy(RW_phantoms, SyntheticCorpus, feedback_budget)
    Evaluator-->>SeedLoop: StrategyMetrics RW

    SeedLoop->>Evaluator: evaluate_strategy(TC_phantoms, SyntheticCorpus, feedback_budget)
    Evaluator-->>SeedLoop: StrategyMetrics TC

    SeedLoop->>Evaluator: evaluate_strategy(STS_phantoms, SyntheticCorpus, feedback_budget)
    Evaluator-->>SeedLoop: StrategyMetrics STS

    SeedLoop->>Evaluator: pairwise_jaccard(RW, TC / STS)
    Evaluator-->>SeedLoop: dict pairwise_jaccard

    SeedLoop->>Evaluator: adoption_verdict(metrics_tuple, jaccard)
    Evaluator-->>SeedLoop: verdict

    SeedLoop-->>Runner: per_seed_result{seed, strategy_metrics, jaccard, verdict}

    loop for each seed
        Runner->>SeedLoop: _run_one_seed(...)
    end

    Runner->>Runner: _aggregate(per_seed_results)
    Runner-->>Operator: result JSON structure{config, per_seed, aggregate}

    Operator-->>Operator: inspect JSON, cite in ship-decision PR
Loading

Class diagram for wonder bake_off harness core models

classDiagram
    class Phantom {
      +tuple~str~ composition
      +str strategy
      +float construction_cost
      +str seed_id
    }

    class CorpusAtom {
      +str belief_id
      +int topic
      +str session_id
    }

    class SyntheticCorpus {
      +tuple~CorpusAtom~ atoms
      +tuple~Edge~ edges
      +int topic_of(belief_id)
    }

    class StrategyMetrics {
      +str strategy
      +int n_phantoms
      +float confirmation_rate
      +float retrieval_freq_per_cost
      +float junk_rate
      +float mean_construction_cost
    }

    class BakeoffResult {
      +tuple~StrategyMetrics~ metrics
      +dict~tuple~str,str~~ pairwise_jaccard
      +str verdict
      +float h0_floor
      +tuple~str~ notes
    }

    class MemoryStore {
      +list~str~ list_belief_ids()
      +Belief get_belief(belief_id)
      +list~Edge~ edges_from(belief_id)
      +iter_all_edges()
      +insert_belief(Belief)
      +insert_edge(Edge)
    }

    class Belief {
      +str id
      +float alpha
      +float beta
      +str type
      +str session_id
    }

    class Edge {
      +str src
      +str dst
      +str type
      +float weight
    }

    %% Strategy identifiers
    class StrategyIds {
      +str STRATEGY_RW
      +str STRATEGY_TC
      +str STRATEGY_STS
      +frozenset~str~ STRATEGIES
    }

    %% Relationships
    SyntheticCorpus "*" --> "*" CorpusAtom : contains
    SyntheticCorpus "*" --> "*" Edge : contains

    Phantom "*" --> "1" StrategyIds : uses_strategy_id
    StrategyMetrics "*" --> "*" Phantom : derived_from

    BakeoffResult "*" --> "*" StrategyMetrics : aggregates

    SyntheticCorpus --> MemoryStore : populate_store
    MemoryStore --> Belief : stores
    MemoryStore --> Edge : stores

    Phantom ..> Belief : composition_ids_ref

    note for Phantom "Represents a speculative composition of beliefs"
    note for SyntheticCorpus "Deterministic synthetic graph used by simulator and strategies"
    note for StrategyMetrics "Per-strategy performance metrics used by adoption_verdict"
Loading

Flow diagram for bake_off data pipeline

flowchart LR
    subgraph SyntheticCorpusBuilder
      Bld["build_corpus\nSyntheticCorpus"]
    end

    subgraph StoreLayer
      Mem["MemoryStore"]
      Pop["populate_store"]
    end

    subgraph Strategies
      RW["random_walk\nstrategy RW"]
      TC["triangle_closure\nstrategy TC"]
      STS["span_topic_sampling\nstrategy STS"]
    end

    subgraph Evaluator
      Eval["evaluate_strategy"]
      Jacc["pairwise_jaccard"]
      Verdict["adoption_verdict"]
    end

    subgraph Runner
      Run["run_bakeoff"]
      SeedLoop["_run_one_seed (multi-seed loop)"]
      Agg["_aggregate"]
    end

    Operator((Operator))
    Output["Result JSON\nconfig + per_seed + aggregate"]

    Operator --> Run
    Run --> SeedLoop

    SeedLoop --> Bld
    Bld -->|SyntheticCorpus| Pop
    Pop --> Mem

    Mem --> RW
    Mem --> TC
    Mem --> STS

    RW -->|Phantoms| Eval
    TC -->|Phantoms| Eval
    STS -->|Phantoms| Eval

    Eval -->|StrategyMetrics| SeedLoop

    RW -->|Compositions| Jacc
    TC -->|Compositions| Jacc
    STS -->|Compositions| Jacc

    Jacc --> Verdict
    Verdict --> SeedLoop

    SeedLoop --> Agg
    Agg --> Output
    Output --> Operator
Loading

File-Level Changes

Change Details Files
Add synthetic corpus builder, store populator, feedback simulator, and promotion gate logic to support deterministic offline evaluation.
  • Introduce CorpusAtom and SyntheticCorpus dataclasses to represent synthetic beliefs and edges with topic/session metadata.
  • Implement build_corpus() to generate a seeded, size/density-configurable synthetic graph with intra-topic dense and cross-topic sparse edges, including parameter validation.
  • Implement populate_store() to stamp beliefs with Beta priors (including a high-uncertainty fraction) and insert them plus edges into a MemoryStore.
  • Implement feedback_verdict() with single-topic agreement as the ground-truth predicate for confirm vs junk.
  • Implement simulate_promotion() that applies the alpha-only promotion threshold gate using a named ALPHA_PROMOTION_THRESHOLD constant.
src/aelfrice/wonder/simulator.py
Introduce three phantom-generation strategies over MemoryStore, encapsulated in a Phantom dataclass and exported from the wonder package.
  • Define Phantom dataclass and STRATEGY_* constants in aelfrice.wonder.models, using atoms-touched construction_cost and sorted compositions for cross-strategy set algebra.
  • Implement random_walk() strategy that seeds from high-uncertainty beliefs, walks fixed-depth over outgoing edges, tracks atoms-touched cost, deduplicates by composition, and is RNG-seeded deterministic.
  • Implement triangle_closure() strategy that deterministically proposes pairs sharing a target via eligible edge types, with fixed construction cost and unordered-pair deduplication.
  • Implement span_topic_sampling() strategy that samples compositions spanning distinct sessions, enforces minimum composition size and session count, and deduplicates/sorts compositions.
  • Expose Phantom and the three strategies via aelfrice.wonder.init for external use.
src/aelfrice/wonder/models.py
src/aelfrice/wonder/strategies.py
src/aelfrice/wonder/__init__.py
Implement evaluation metrics and adoption decision logic matching the v2.0 spec, including Jaccard overlap and retrieval-per-cost calculations.
  • Define StrategyMetrics and BakeoffResult dataclasses capturing per-strategy metrics, pairwise Jaccard, verdict, thresholds, and notes.
  • Implement evaluate_strategy() to simulate fixed-budget feedback per phantom using feedback_verdict/simulate_promotion, and compute confirmation_rate, junk_rate, mean construction_cost, and retrieval_freq_per_cost based on composition size.
  • Implement pairwise_jaccard() over phantom composition tuples, including edge cases for empty sets.
  • Implement adoption_verdict() that encodes the four spec rules (drop, defer, single, ensemble) using named threshold constants for H0 floor, redundancy, complementarity, and junk-rate defer conditions.
  • Export all evaluator primitives and thresholds for use by the runner and tests.
src/aelfrice/wonder/evaluator.py
Add a multi-seed bake-off runner and CLI wiring corpus generation, strategies, and evaluator into a JSON-emitting pipeline.
  • Implement _run_one_seed() to build/populate a corpus, run all three strategies with per-strategy RNGs, evaluate each under a shared feedback budget, compute pairwise Jaccard, and produce a per-seed result dict with metrics and verdict.
  • Implement _aggregate() to compute mean per-strategy metrics across seeds, aggregate Jaccard values, and derive verdict distributions and majority_verdict.
  • Implement run_bakeoff() as the main programmatic entry that orchestrates multi-seed runs with configurable corpus/strategy/budget parameters and echoes H0 thresholds in the config block.
  • Provide a CLI via _build_argparser() and main(), mapping CLI flags to run_bakeoff parameters, handling output to stdout or file, and returning an exit code; make module executable with main guard.
  • Include thresholds from evaluator in the runner config to support auditing of R output.
src/aelfrice/wonder/runner.py
Provide a bench-gate shim that preserves the existing wonder_consolidation.score() contract with a simple token-overlap relatedness metric.
  • Implement a lightweight tokenizer that lowercases and strips punctuation into whitespace-delimited tokens.
  • Implement score() that accepts strings, dict-like objects with content, or lists thereof, computes per-neighbor token-overlap Jaccard scores in [0,1], and averages when given a list of neighbors.
  • Implement _content_of() helpers to robustly extract text from various seed/neighbor representations for compatibility with the existing bench gate tests.
  • Document that this shim is not the bake-off itself and only exists to keep the bench-gate stub honest pending a lab-side corpus.
  • Export only score() as the public surface of this module.
src/aelfrice/wonder_consolidation.py
Add focused unit and integration tests for strategies, simulator, evaluator, and runner behavior, including determinism and edge cases.
  • test_wonder_strategies.py exercises all three strategies on empty/no-edge/normal graphs, validates determinism, edge-type eligibility, uncertainty floor behavior, session diversity, and error conditions.
  • test_wonder_simulator.py verifies corpus determinism and size/shape constraints, topic partitioning, high-uncertainty stamping, feedback_verdict semantics, and simulate_promotion threshold behavior.
  • test_wonder_evaluator.py validates evaluate_strategy on empty/all-confirm/all-junk scenarios, retrieval-per-cost calculation, Jaccard edge cases, and each adoption_verdict rule path (drop, defer, ensemble, single).
  • test_wonder_runner.py smoke-tests run_bakeoff shape and determinism, asserts aggregate metric keys and majority_verdict domain, verifies CLI argparser defaults, and checks main() JSON-to-file output.
  • Collectively, tests ensure the harness is deterministic under fixed RNG seeds and safe for offline experimentation without production-side writes.
tests/test_wonder_strategies.py
tests/test_wonder_simulator.py
tests/test_wonder_evaluator.py
tests/test_wonder_runner.py

Assessment against linked issues

Issue Objective Addressed Explanation
#228 Implement the three offline phantom generation strategies (Random Walk, Triangle Closure, Span-Topic Sampling) over the typed-edge graph / MemoryStore as described in the wonder-consolidation v2.0 spec.
#228 Build the R0 bake-off harness consisting of a deterministic ~200-atom synthetic corpus, a feedback simulator defining confirmation vs junk, and an evaluator that computes the four specified metrics and an adoption verdict for the strategies.
#228 Provide a runnable orchestration layer (runner/CLI) that wires corpus, strategies, simulator, and evaluator together, supports multi-seed sweeps, and emits structured JSON suitable for making the v2.0 ship decision, while keeping promotion-rule and production write-path changes out of scope.

Possibly linked issues

  • #N/A: PR delivers the R0 harness (RW/TC/STS, corpus, simulator, evaluator, runner) requested by the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@yoshi280 yoshi280 added the attn:review Needs review (PR open, awaiting reviewer) label May 4, 2026
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new "wonder" research subpackage implementing a phantom generation strategy bake-off system. Creates three strategy implementations (random walk, triangle closure, span-topic sampling), a synthetic corpus simulator, an evaluator computing per-strategy metrics, and a campaign runner orchestrating multi-seed bake-off execution. Also adds a consolidation-scoring shim and comprehensive test coverage.

Changes

Wonder Bake-off System

Layer / File(s) Summary
Data Models
src/aelfrice/wonder/models.py
Defines Phantom dataclass with composition, strategy, construction_cost, and optional seed_id; introduces strategy identifier constants (STRATEGY_RW, STRATEGY_TC, STRATEGY_STS) and an immutable STRATEGIES set.
Simulator & Corpus
src/aelfrice/wonder/simulator.py
Implements synthetic corpus generation via build_corpus, populating a MemoryStore with atoms and edges via populate_store, computing per-phantom feedback verdicts (feedback_verdict), and simulating promotion via alpha-threshold gate (simulate_promotion).
Strategy Implementations
src/aelfrice/wonder/strategies.py
Implements three phantom-generation strategies: random_walk (seed-based edge traversal), triangle_closure (pairwise closure detection), and span_topic_sampling (session-diverse sampling); each returns sorted Phantom lists with computed costs.
Evaluation Metrics
src/aelfrice/wonder/evaluator.py
Computes per-strategy bake-off metrics (StrategyMetrics) via evaluate_strategy, calculates pairwise Jaccard overlap via pairwise_jaccard, and derives an adoption Verdict via ordered decision logic using confirmation/junk thresholds and strategy redundancy/complement checks.
Campaign Orchestration
src/aelfrice/wonder/runner.py
Implements run_bakeoff to execute strategies across multiple seeds, returning per-seed results and aggregated statistics; exposes a CLI via _build_argparser and main with JSON output support.
Package Export
src/aelfrice/wonder/__init__.py
Re-exports public API (Phantom, strategy functions) at package level via __all__.
Consolidation Shim
src/aelfrice/wonder_consolidation.py
Provides a bench-gate score function computing token-overlap-based relatedness scalars supporting multiple input shapes (string, dict, object with .content attribute).
Tests
tests/test_wonder_*.py
Unit and integration tests for simulator (determinism, corpus shape, feedback verdicts, promotion threshold), strategies (edge handling, sampling diversity, determinism), evaluator (metrics, Jaccard, verdict selection), and runner (multi-seed aggregation, CLI, JSON output).

Sequence Diagram

sequenceDiagram
    actor User
    participant Runner as run_bakeoff()
    participant Corpus as Simulator:<br/>build_corpus()
    participant Store as MemoryStore
    participant Strategies as RW, TC, STS
    participant Evaluator as evaluate_strategy()
    participant Verdict as adoption_verdict()
    participant Output as JSON Output

    User->>Runner: run_bakeoff(seeds=10)
    
    loop per seed
        Runner->>Corpus: build_corpus(rng, n_topics, n_atoms)
        Corpus-->>Store: populate_store(atoms, edges)
        
        par Parallel Strategies
            Runner->>Strategies: random_walk(store, rng)
            Strategies-->>Runner: RW phantoms
        and
            Runner->>Strategies: triangle_closure(store)
            Strategies-->>Runner: TC phantoms
        and
            Runner->>Strategies: span_topic_sampling(store, rng)
            Strategies-->>Runner: STS phantoms
        end
        
        par Evaluate Each Strategy
            Runner->>Evaluator: evaluate_strategy(RW_phantoms, corpus, budget=16)
            Evaluator-->>Runner: RW_metrics
        and
            Runner->>Evaluator: evaluate_strategy(TC_phantoms, corpus, budget=16)
            Evaluator-->>Runner: TC_metrics
        and
            Runner->>Evaluator: evaluate_strategy(STS_phantoms, corpus, budget=16)
            Evaluator-->>Runner: STS_metrics
        end
        
        Runner->>Evaluator: pairwise_jaccard(all strategies)
        Evaluator-->>Runner: jaccard_matrix
        
        Runner->>Verdict: adoption_verdict(metrics, jaccard)
        Verdict-->>Runner: per_seed_verdict
    end
    
    Runner->>Output: aggregate(per_seed results)
    Output-->>User: JSON(per_seed, aggregate, verdict_distribution)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

author-Setr, research, feature, wonder, bake-off

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly identifies the main change: landing the wonder-consolidation bake-off harness from issue #228, comprising strategies, corpus, evaluator, and runner.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, components, decision points, tests, scope clarifications, and spec checks against the linked issue requirements.
Linked Issues check ✅ Passed All primary objectives from #228 are met: three strategies (RW, TC, STS) implemented, synthetic corpus and feedback simulator provided, four spec-defined metrics computed, four-rule adoption decision tree implemented, multi-seed runner with JSON output, and out-of-scope work honored.
Out of Scope Changes check ✅ Passed All changes are scoped to the harness objectives; no promotion-rule work, lifecycle activation, retrieval surface changes, or lab-fixture imports are present. The wonder_consolidation.py shim is intentional per decision A.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-228-wonder-consolidation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

from dataclasses import dataclass, field
from typing import Literal

from .models import STRATEGY_RW, STRATEGY_STS, STRATEGY_TC, Phantom
"""
if h0_floor is None:
h0_floor = H0_NULL_RATE + H0_PLUS_PP_FLOOR
by_strategy = {m.strategy: m for m in metrics}
Comment on lines +16 to +21
from aelfrice.wonder.models import (
STRATEGY_RW,
STRATEGY_STS,
STRATEGY_TC,
Phantom,
)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In build_corpus the high_uncertainty_fraction parameter is currently unused, which is confusing given the docstring; either remove it from the signature or apply it during corpus generation (or clearly delegate that responsibility to populate_store).
  • evaluate_strategy returns strategy="<empty>" for empty phantom lists, which then flows into aggregation in run_bakeoff (and tests expect strategy keys like RW/TC/STS); consider threading the strategy identifier explicitly into evaluate_strategy or handling empty cases in the caller to preserve consistent strategy labels.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `build_corpus` the `high_uncertainty_fraction` parameter is currently unused, which is confusing given the docstring; either remove it from the signature or apply it during corpus generation (or clearly delegate that responsibility to `populate_store`).
- `evaluate_strategy` returns `strategy="<empty>"` for empty phantom lists, which then flows into aggregation in `run_bakeoff` (and tests expect strategy keys like `RW`/`TC`/`STS`); consider threading the strategy identifier explicitly into `evaluate_strategy` or handling empty cases in the caller to preserve consistent strategy labels.

## Individual Comments

### Comment 1
<location path="src/aelfrice/wonder/evaluator.py" line_range="100-101" />
<code_context>
+    share the same verdict — but the rate definition is preserved
+    in case the simulator gains randomness later).
+    """
+    if not phantoms:
+        return StrategyMetrics(
+            strategy="<empty>",
+            n_phantoms=0,
</code_context>
<issue_to_address>
**issue (bug_risk):** Empty-phantom handling sets strategy to "<empty>", which can break aggregation and adoption logic.

When `phantoms` is empty, `evaluate_strategy` returns `StrategyMetrics(strategy="<empty>")`. Since `_run_one_seed` always calls `evaluate_strategy` for RW, TC, and STS, all three can end up labeled `"<empty>"`.

This breaks downstream logic:
* `_aggregate` uses the strategy names as dict keys, so duplicate `"<empty>"` keys collapse per-strategy data.
* `adoption_verdict` also builds a dict keyed by `m.strategy`, so different strategies with no phantoms overwrite each other.

Instead, keep the real strategy name even when `phantoms` is empty (e.g., pass a `strategy_name` into `evaluate_strategy` or derive it from `_run_one_seed` and use that for `StrategyMetrics.strategy`).
</issue_to_address>

### Comment 2
<location path="src/aelfrice/wonder/simulator.py" line_range="84-92" />
<code_context>
+    return f"t{topic:02d}_a{idx:03d}"
+
+
+def build_corpus(
+    *,
+    rng: random.Random,
+    n_topics: int = 8,
+    n_atoms_per_topic: int = 25,
+    n_sessions: int = 8,
+    intra_edge_density: float = 0.25,
+    cross_edge_density: float = 0.02,
+    high_uncertainty_fraction: float = 0.15,
+) -> SyntheticCorpus:
+    """Generate a deterministic synthetic corpus.
</code_context>
<issue_to_address>
**suggestion:** The `high_uncertainty_fraction` parameter on `build_corpus` is unused.

Since this argument is never used, it makes the API misleading, especially given `populate_store` uses a parameter with the same name. Either remove `high_uncertainty_fraction` from `build_corpus` if corpus generation is intended to be independent of this prior, or thread it through to the relevant logic so the parameter has a well-defined effect.

Suggested implementation:

```python
def build_corpus(
    *,
    rng: random.Random,
    n_topics: int = 8,
    n_atoms_per_topic: int = 25,
    n_sessions: int = 8,
    intra_edge_density: float = 0.25,
    cross_edge_density: float = 0.02,
) -> SyntheticCorpus:

```

1. Search the codebase for all call sites of `build_corpus` and remove any `high_uncertainty_fraction=...` keyword argument (or positional argument in that position) to match the updated function signature.
2. If the documentation or docstrings elsewhere mention `high_uncertainty_fraction` in relation to `build_corpus`, update or remove those references to avoid confusion.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +100 to +101
if not phantoms:
return StrategyMetrics(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Empty-phantom handling sets strategy to "", which can break aggregation and adoption logic.

When phantoms is empty, evaluate_strategy returns StrategyMetrics(strategy="<empty>"). Since _run_one_seed always calls evaluate_strategy for RW, TC, and STS, all three can end up labeled "<empty>".

This breaks downstream logic:

  • _aggregate uses the strategy names as dict keys, so duplicate "<empty>" keys collapse per-strategy data.
  • adoption_verdict also builds a dict keyed by m.strategy, so different strategies with no phantoms overwrite each other.

Instead, keep the real strategy name even when phantoms is empty (e.g., pass a strategy_name into evaluate_strategy or derive it from _run_one_seed and use that for StrategyMetrics.strategy).

Comment on lines +84 to +92
def build_corpus(
*,
rng: random.Random,
n_topics: int = 8,
n_atoms_per_topic: int = 25,
n_sessions: int = 8,
intra_edge_density: float = 0.25,
cross_edge_density: float = 0.02,
high_uncertainty_fraction: float = 0.15,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The high_uncertainty_fraction parameter on build_corpus is unused.

Since this argument is never used, it makes the API misleading, especially given populate_store uses a parameter with the same name. Either remove high_uncertainty_fraction from build_corpus if corpus generation is intended to be independent of this prior, or thread it through to the relevant logic so the parameter has a well-defined effect.

Suggested implementation:

def build_corpus(
    *,
    rng: random.Random,
    n_topics: int = 8,
    n_atoms_per_topic: int = 25,
    n_sessions: int = 8,
    intra_edge_density: float = 0.25,
    cross_edge_density: float = 0.02,
) -> SyntheticCorpus:
  1. Search the codebase for all call sites of build_corpus and remove any high_uncertainty_fraction=... keyword argument (or positional argument in that position) to match the updated function signature.
  2. If the documentation or docstrings elsewhere mention high_uncertainty_fraction in relation to build_corpus, update or remove those references to avoid confusion.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/aelfrice/wonder/evaluator.py (2)

115-118: 💤 Low value

Redundant repeated calls for deterministic verdict.

The feedback_verdict call is deterministic given (phantom.composition, corpus), so calling it feedback_budget_per_phantom times always returns identical results. The code documents this as future-proofing for potential simulator randomness.

For the research harness this is fine, but if performance matters, caching or single-call would suffice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aelfrice/wonder/evaluator.py` around lines 115 - 118, The loop building
verdicts repeatedly calls the deterministic function
feedback_verdict(phantom.composition, corpus) feedback_budget_per_phantom times;
replace this with a single call to feedback_verdict and replicate or cache that
single result to produce the list (or memoize feedback_verdict for the
(phantom.composition, corpus) key) so you avoid redundant work while keeping the
same verdicts list semantics used by verdicts.

192-207: 💤 Low value

Clarify the adoption_verdict fallthrough logic.

When 2+ strategies clear the floor:

  • Top-two complementary (Jaccard < 0.3) → "ensemble"
  • Otherwise → "single" (fallback)

The intermediate cost_window check (lines 202-206) appears to be defensive guarding but always passes when clearing is non-empty with positive retrieval. Consider simplifying or adding a comment explaining when this guard would actually trigger a different path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aelfrice/wonder/evaluator.py` around lines 192 - 207, The fallthrough
logic in the adoption decision can be simplified: after computing
sorted_clearing, top_two, pair and checking if their Jaccard <
JACCARD_COMPLEMENT to return "ensemble", the subsequent any(j >=
JACCARD_REDUNDANCY ...) + cost_window check is redundant because the function
ultimately returns "single" in all other cases; remove that intermediate block
(the any(...) check and the cost_window conditional) and simply return "single"
as the fallback, or if you want to preserve it for documentation/defensive
reasons, replace it with a clear comment referencing
sorted_clearing/top_two/pair and why cost_window would ever alter the outcome
(using symbols JACCARD_REDUNDANCY and cost_window) so the intent is explicit.
src/aelfrice/wonder/runner.py (1)

136-142: 💤 Low value

Tie-breaking behavior for majority verdict is arbitrary.

With N=10 seeds, verdict ties are possible. Counter.most_common(1)[0][0] returns an arbitrary winner among tied values (depends on insertion order in Python 3.7+). Consider documenting this or adding deterministic tie-breaking if reproducibility matters for audit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aelfrice/wonder/runner.py` around lines 136 - 142, The current majority
selection uses verdict_counter.most_common(1)[0][0], which can return an
arbitrary value when counts tie; make this deterministic by computing the
highest count from verdict_counter (variable verdict_counter built from
per_seed) and then selecting the tie by a deterministic rule (e.g.,
lexicographic order, fixed priority list, or sort by (-count, verdict) to break
ties consistently) before assigning majority_verdict, and update the returned
"majority_verdict" accordingly so repeated runs yield reproducible results.
src/aelfrice/wonder/simulator.py (1)

137-140: 💤 Low value

Fragile topic detection via string slicing.

The cross-edge logic extracts topic identity using src[:3] which assumes the atom ID format "tNN_..." from _atom_id. If the ID format changes, this check silently fails to detect same-topic pairs correctly.

Consider reusing the topic lookup from by_topic or storing topic info alongside IDs for robustness.

♻️ Suggested fix using existing topic mapping
-    all_ids = [a.belief_id for a in atoms]
-    for src in all_ids:
-        for dst in all_ids:
-            if src == dst:
-                continue
-            src_topic = src[:3]
-            dst_topic = dst[:3]
-            if src_topic == dst_topic:
-                continue
+    id_to_topic = {a.belief_id: a.topic for a in atoms}
+    all_ids = list(id_to_topic.keys())
+    for src in all_ids:
+        for dst in all_ids:
+            if src == dst:
+                continue
+            if id_to_topic[src] == id_to_topic[dst]:
+                continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aelfrice/wonder/simulator.py` around lines 137 - 140, The current
same-topic check uses brittle string slicing (src_topic = src[:3]) which breaks
if atom ID format changes; replace this with a robust lookup using the existing
by_topic mapping or an explicit id->topic dict: determine each atom's topic by
searching by_topic (or a new mapping built once from by_topic) and compare those
topic values instead of src[:3]/dst[:3]; update the code paths that reference
src_topic/dst_topic (the block containing src, dst, src_topic, dst_topic) to use
the topic lookup function or mapping so same-topic pairs are detected reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/aelfrice/wonder/evaluator.py`:
- Around line 115-118: The loop building verdicts repeatedly calls the
deterministic function feedback_verdict(phantom.composition, corpus)
feedback_budget_per_phantom times; replace this with a single call to
feedback_verdict and replicate or cache that single result to produce the list
(or memoize feedback_verdict for the (phantom.composition, corpus) key) so you
avoid redundant work while keeping the same verdicts list semantics used by
verdicts.
- Around line 192-207: The fallthrough logic in the adoption decision can be
simplified: after computing sorted_clearing, top_two, pair and checking if their
Jaccard < JACCARD_COMPLEMENT to return "ensemble", the subsequent any(j >=
JACCARD_REDUNDANCY ...) + cost_window check is redundant because the function
ultimately returns "single" in all other cases; remove that intermediate block
(the any(...) check and the cost_window conditional) and simply return "single"
as the fallback, or if you want to preserve it for documentation/defensive
reasons, replace it with a clear comment referencing
sorted_clearing/top_two/pair and why cost_window would ever alter the outcome
(using symbols JACCARD_REDUNDANCY and cost_window) so the intent is explicit.

In `@src/aelfrice/wonder/runner.py`:
- Around line 136-142: The current majority selection uses
verdict_counter.most_common(1)[0][0], which can return an arbitrary value when
counts tie; make this deterministic by computing the highest count from
verdict_counter (variable verdict_counter built from per_seed) and then
selecting the tie by a deterministic rule (e.g., lexicographic order, fixed
priority list, or sort by (-count, verdict) to break ties consistently) before
assigning majority_verdict, and update the returned "majority_verdict"
accordingly so repeated runs yield reproducible results.

In `@src/aelfrice/wonder/simulator.py`:
- Around line 137-140: The current same-topic check uses brittle string slicing
(src_topic = src[:3]) which breaks if atom ID format changes; replace this with
a robust lookup using the existing by_topic mapping or an explicit id->topic
dict: determine each atom's topic by searching by_topic (or a new mapping built
once from by_topic) and compare those topic values instead of src[:3]/dst[:3];
update the code paths that reference src_topic/dst_topic (the block containing
src, dst, src_topic, dst_topic) to use the topic lookup function or mapping so
same-topic pairs are detected reliably.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ddc0e7da-6f23-4037-ac6f-e8dbc4244a4e

📥 Commits

Reviewing files that changed from the base of the PR and between 6402276 and 61ab575.

📒 Files selected for processing (11)
  • src/aelfrice/wonder/__init__.py
  • src/aelfrice/wonder/evaluator.py
  • src/aelfrice/wonder/models.py
  • src/aelfrice/wonder/runner.py
  • src/aelfrice/wonder/simulator.py
  • src/aelfrice/wonder/strategies.py
  • src/aelfrice/wonder_consolidation.py
  • tests/test_wonder_evaluator.py
  • tests/test_wonder_runner.py
  • tests/test_wonder_simulator.py
  • tests/test_wonder_strategies.py

@yoshi280

yoshi280 commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author

[claim:review:Kulili:2026-05-04T07:58:50Z]

@yoshi280

yoshi280 commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author

[claim:review:Toug:2026-05-04T07:59:25Z]

@yoshi280

yoshi280 commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author

[release:review:Toug:2026-05-04T07:59:30Z]

@robotrocketscience
robotrocketscience merged commit 61ab575 into main May 4, 2026
26 of 30 checks passed
@robotrocketscience
robotrocketscience deleted the feat/issue-228-wonder-consolidation branch May 4, 2026 07:59
@yoshi280

yoshi280 commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author

Reviewed and FF-merged to main as 61ab575.

Diff (1582+ across 11 files): self-contained aelfrice.wonder package + bench-gate shim, no touch to production write paths. All 3 commits SSH-signed (G), CI green (pytest 3.12/3.13, CodeQL, staging-gate, deadcode, typos), FF-clean against current main, discretion grep clean.

Spec adherence checked against docs/v2_wonder_consolidation.md § "Adoption criteria": four metrics + four verdict rules + thresholds (10pp floor, Jaccard 0.6/0.3, junk-rate 60%) all present as named constants. Decisions A–F from planning memo are explicit in the PR body and traceable in the code.

Minor smells (non-blocking, follow-up if useful when R output gets cited):

  • adoption_verdict(): inner for j in jaccard.values() shadows the outer j (the pair-Jaccard for top-two). Functionally fine because the outer j is only read once before the loop, but cosmetically confusing.
  • adoption_verdict(): when len(clearing) == 1 the function returns single without consulting cost_window. Defensible per spec rule 3 paraphrase, but worth a comment if the single-clearer case is ever revisited.

@yoshi280

yoshi280 commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author

[release:review:Kulili:2026-05-04T08:00:10Z]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v2.0 research] wonder-consolidation — phantom generation strategy bake-off (RW vs TC vs STS)

3 participants