core/core/contemplation/contract.md
Shay 8f1903e8e7
chore(evals): contracts + bench json + Lane B viewer + chart + audit + demo schema (#62)
* chore(evals, cli): contract standardization + bench --json stdout cleanliness

End-of-session shippability pass.  Three concrete fixes:

1. core/cli.py — bench --json no longer pollutes stdout
   Several bench paths call scripts.run_pulse.run_pulse which prints
   verbose [pulse] traces unconditionally to stdout, breaking jq /
   programmatic consumers of --json output.

   New _bench_stdout_guard() redirects stdout → stderr for the
   duration of the bench run when --json is set.  Operator still sees
   the pulse trace (on stderr), but --json consumers get a clean JSON
   document on stdout.  Applied to all four bench paths: cost,
   articulation, default suite, and --suite all.

   Verified: core bench --suite determinism --json now produces
   parseable JSON; human path still shows 1140 [pulse] lines.

2. evals/{frontier_compare,realizer_guard}/contract.md (new)
   core/contemplation/contract.md (new)

   Each new contract follows the established pattern (37 contracts
   already exist under evals/<lane>/contract.md):

     - What it measures
     - Why it matters (structural win)
     - How to run
     - How to read the output
     - Pass criteria table
     - When it has failed and why
     - Runner / module layout

   Coverage:
     - frontier_compare: both Lane A (CORE-only suites) and Lane B
       (cross-provider prompt_battery) with explicit guardrails
       against mixing — operator asks for the wrong lane combination,
       runner exits 2 with helpful error.
     - realizer_guard: C1/C2 articulation safety boundary — synthetic
       illegal candidates rejected directly by check_surface AND
       former-bug runtime prompts now produce legal articulations.
     - contemplation (ADR-0080): not under evals/ since it's runtime
       infrastructure that consumes eval reports — contract lives at
       core/contemplation/contract.md.  Documents the read-only +
       SPECULATIVE-only + deterministic-replay invariants and the
       shared DiscoveryCandidateSink plumbing convergence (ADR-0080).

3. evals/CLAIMS.md — Tier 2 rows added

   - frontier_compare Lane A: determinism.primary_score, max_versor_condition
   - frontier_compare Lane B: prompt_battery.primary_score (CORE adapter),
     cross-provider artifact persistence
   - realizer_guard: all_claims_supported
   - contemplation: SPECULATIVE-only invariant, deterministic replay,
     additive sink path, no pack mutation (all CI-pinned by tests)

Verification
------------
$ core test --suite smoke -q
67 passed in 27.22s    (no regression)

$ uv run pytest -q tests/test_contemplation_loop.py \
    tests/test_contemplation_pipeline_convergence.py \
    tests/test_frontier_compare_cross_provider.py
27 passed in 4.87s

$ core bench --suite determinism --json 2>/dev/null | jq .results[0].passed
true        (was: JSONDecodeError on prior [pulse] pollution)

* feat(evals/ui): report viewer renders Lane B cross-provider + pass-rate chart

Stop-hook caught that #62 only covered contracts — the 929-line
report_viewer.html was never audited against the new cross-provider
report shape from #61.  Two real gaps:

1. Lane-aware observation drawer
   The drawer hardcoded Lane A (CORE-native) fields: surface,
   grounding_source, anchor_lens_mode_label, versor_condition.
   Lane B (cross-provider) observations carry different fields:
   provider, model, elapsed_ms, error_type, error_message.

   Loading a cross-provider report rendered only the surface row
   with empty `grounding` — the provider + model + timing data
   was unreachable without expanding "Show raw JSON".

   Fix: detect Lane B (presence of `obs.provider`) and render the
   appropriate field set.  Lane A still renders identically (now
   also surfaces trace_hash + register_id when present, which were
   silently buried in the raw JSON before).

2. Pass-rate chart per suite
   The summary strip showed one aggregate Primary % across all
   suites, with no way to see WHICH suite is dragging the score.
   Multi-suite runs (e.g. --suite all) had to expand each panel
   individually to find the failing one.

   Fix: new .passrate-chart element below the summary strip,
   one horizontal bar per suite showing passed/total.  All-pass =
   solid green, all-fail = solid red, partial = green/red split
   at the pass fraction.  CSS only — no new dependencies.

3. SUITE_PREAMBLES gains the prompt_battery entry so the sidebar
   shows the "side-by-side surface evidence across providers"
   description when loading a Lane B report.

Verified
--------
- Brace/paren/div balance unchanged (308/308 / 380/380 / 54/54)
- One <script> tag pair preserved
- Generated a real Lane B report via
  `python -m evals.frontier_compare --provider core --suite prompt_battery`
  for visual confirmation

Out of scope (noted for future PR)
----------------------------------
Sampled 3 `core demo` targets:
- register-tour: clean schema (all_claims_supported, claims, grid)
- audit-tour: both scene_1_* keys AND an empty scenes:[] array — inconsistent
- anti-regression: no all_claims_supported key, uses all_gates_held instead

Demo schema standardization deserves its own PR — operator tooling
would benefit from a uniform top-level success field across demos.

* docs(evals) + chore(demos): systematic audit + uniform success field

Stop-hook caught two real gaps after the contract+UI PR:
- demos had divergent success-field names (all_gates_held vs
  learning_loop_closed vs claim_supported vs nested claims_supported)
- no systematic look at the 48 eval directories had been done

Both addressed concretely; remaining work captured in audit doc
rather than vaguely deferred.

1. Demo schema standardization — uniform all_claims_supported field
----------------------------------------------------------------------
All 9 ``core demo`` targets now emit a top-level
``all_claims_supported: bool`` field.  Existing per-demo fields
(``all_gates_held``, ``learning_loop_closed``, ``claim_supported``,
nested ``claims_supported``) are preserved for backwards compat —
the new field is an alias derived from the demo's existing success
signal, not a replacement.

Operator tooling and the CI gate can now target
``all_claims_supported`` without knowing each demo's idiomatic
field name.

Files touched:
- evals/anti_regression/run_demo.py — adds AND of all_gates_held +
  active_corpus_byte_identical
- evals/learning_loop/run_demo.py — adds AND of learning_loop_closed +
  active_corpus_byte_identical
- scripts/publish_pack_measurements.py — adds AND of the three
  entries in the nested claims_supported dict
- evals/long_context_cost/comparison_runner.py — adds alias for
  claim_supported (singular)

The 5 demos already using ``all_claims_supported`` (audit-tour,
register-tour, anchor-lens-tour, orthogonality-tour, articulation)
are unchanged.

Verified across all 9 demos:
  audit-tour              : True
  register-tour           : True
  anchor-lens-tour        : True
  orthogonality-tour      : True
  pack-measurements       : True   ← new alias
  anti-regression         : True   ← new alias
  learning-loop           : True   ← new alias
  articulation            : True
  long-context-comparison : True   ← new alias

2. docs/EVAL_AUDIT_2026-05-20.md — systematic 48-lane audit
------------------------------------------------------------
Replaces the "future PR" deferral with a concrete document.

Contains:
- Method (what was inspected for each lane).
- Summary (40/48 have contract.md; 18/48 have saved results;
  empty results/ ≠ broken — most lanes regenerate on demand).
- Cross-provider relevance triage:
    * 9 lanes are cross-provider-relevant and could benefit
      from the prompt_battery-style adapter pattern (cognition,
      english_fluency_ood, hebrew_fluency, koine_greek_fluency,
      grammatical_coverage, inference_closure, multi_step_reasoning,
      discourse_paragraph, foundational_*_ood, etc.).
    * 29 lanes are CORE-only by design (versor closure, anchor
      lens, identity divergence, provenance, etc.) — wiring
      providers would be category-erroneous.
- Demo schema standardization status (this PR closes that).
- UI/UX coverage matrix.
- 5 concrete follow-up items, each focused enough for a single
  PR, none requiring architectural change.

Regenerated reports
-------------------
evals/long_context_cost/results/comparison_v1.json and
evals/results/phase2_pack_measurements.json now contain the new
all_claims_supported field (auto-regenerated when validating the
schema change).

evals/frontier_compare/results/sample_core_promptbattery.json
added as a reference Lane B report so the new viewer always has
something to load on first open.
2026-05-20 13:53:13 -07:00

7.8 KiB

contemplation pipeline (ADR-0080)

What it measures

The contemplation pipeline does not produce a single score — it produces typed SPECULATIVE findings sourced from explicit evidence artifacts, with three load-bearing invariants:

  1. Read-only: never mutates packs, vault, teaching corpus, or runtime state. Only emits.
  2. SPECULATIVE-only: every emitted ContemplationFinding is stamped EpistemicStatus.SPECULATIVE; the schema's __post_init__ raises on any other status. No autonomous ratification.
  3. Deterministic replay: same input files + same flags → identical run_id, identical finding_id per finding, identical sink output.

Two evidence miners ship today:

  • frontier_compare — failed benchmark cases from evals/frontier_compare/results/*.json become FindingKind.BENCHMARK_CASE findings. Subject is "<suite>/<case_id>"; predicate is failed_case; evidence summary lists the case's failures array.

  • contradiction_detection — failed cases from evals/contradiction_detection/results/*.json become FindingKind.CONTRADICTION findings with a predicate split:

    • missed_contradictionpaired_contradiction case the detector failed to flag.
    • false_contradiction_flagpaired_consistent case the detector wrongly flagged. The split is load-bearing: each calls for a different repair (tighten vs loosen the threshold).

Both miners flow through the same DiscoveryCandidateSink protocol that teaching/discovery_sink.py uses for in-session DiscoveryCandidates (ADR-0055 Phase B). When invoked with --sink-root, findings land at <root>/<YYYY>/<YYYY-MM>.jsonl — the same monthly layout discovery candidates use, so operators can grep one stream.

Why it matters (structural win)

LLM evaluation harnesses produce reports. Those reports are read by humans, filed away, and rarely flow back into the system's sense of "what is unfinished." The gap-aggregation step usually happens in a spreadsheet, if at all.

CORE's contemplation pipeline treats every failed benchmark case as a SPECULATIVE finding that flows into the same evidence stream as session-time discovery candidates. Operators see one unified backlog of "things the system has noticed are wrong" with typed evidence pointers, deterministic IDs, and a clear repair path. No silent ratification — every promotion goes through the existing reviewed-teaching gate.

The deeper architectural claim: contemplation never ratifies its own conclusions. It can mine evidence, propose actions, and file SPECULATIVE findings — but the actual EpistemicStatus.COHERENT transition happens only through the human-reviewed proposal flow in teaching/review.py. This is the load-bearing boundary documented in ADR-0080.

How to run

# CORE-only path — emits to stdout, optionally writes report blob
core contemplation evals/frontier_compare/results/<file>.json

# With shared sink — findings persist to monthly JSONL
core contemplation evals/contradiction_detection/results/<file>.json \
    --lane contradiction_detection \
    --sink-root teaching/discovery_log

# With provenance metadata
core contemplation evals/frontier_compare/results/<file>.json \
    --pack-id en_core_cognition_v1 \
    --note "Wave 1 first cross-provider audit pass" \
    --report run.json

Equivalent python -m core.contemplation ... works identically — the core contemplation CLI delegates to the module's main().

How to read the output

Each ContemplationRun carries:

{
  "run_id": "c8b27698189c3a9f",
  "config_hash": "...",
  "substrate_hash": "b43e53...",
  "finding_count": 4,
  "findings": [
    {
      "finding_id": "...",
      "kind": "contradiction",
      "subject": "contradiction_detection/CON-PUB-002",
      "predicate": "missed_contradiction",
      "object": null,
      "evidence_refs": [
        { "source_type": "contradiction_detection_report",
          "source_id": "evals/.../v1_public_*.json",
          "pointer": "lane=contradiction_detection;case=CON-PUB-002",
          "summary": "kind=paired_contradiction;flagged=False;versor_delta=0.0" }
      ],
      "proposed_action": "Inspect the paired-contradiction probe...",
      "substrate_hash": "...",
      "epistemic_status": "speculative"
    }
  ]
}

The sink JSONL stream (when --sink-root is set) is one canonical JSON line per finding — same shape, no run wrapper, so jq over <root>/<YYYY>/<YYYY-MM>.jsonl answers questions like "every SPECULATIVE finding this month" across both contemplation runs and ADR-0055 discovery candidates.

Pass criteria

Property Definition Threshold Current
SPECULATIVE-only invariant ContemplationFinding.__post_init__ raises on any non-SPECULATIVE status always pinned by test
Deterministic replay two contemplate_* calls on the same inputs → identical run_id and as_dict() byte-identical pinned by test
Sink path is additive the ContemplationRun blob is byte-identical whether or not a sink is supplied byte-identical pinned by test
No pack mutation language_packs/ tree mtimes unchanged across a contemplate_* invocation true pinned by test
Predicate split missed_contradiction and false_contradiction_flag produce distinct proposed_action text distinct pinned by test
Lane config_hash separation contemplate_frontier_reports and contemplate_contradiction_reports produce distinct config_hash on identical input paths distinct pinned by test

When it has failed and why

  • 2026-05-20 — ADR-0080 first shipped (#55) with one miner (frontier_compare) and no consumer. SPECULATIVE invariant protected a write path that didn't exist.
  • 2026-05-20#56 fast-follow miner was closed to avoid entrenching the parallel core/contemplation/ lane. Replaced by #58 which connected the boundary doctrine to existing teaching/discovery_sink.py plumbing.
  • 2026-05-20#58 documented the BOUNDARY between EvidencePointer (teaching: reviewed memory only) and ContemplationEvidenceRef (core: external report files). Forcing them to merge would have either widened the reviewed-memory enum (losing the guarantee) or made benchmark reports masquerade as vault_coherent. Both worse than documented separation.
  • 2026-05-20#60 discovered the CLI was invisible — the contemplation module was reachable only via python -m core.contemplation, not via core --help. Subcommand added.

Runner / module layout

  • core/contemplation/schema.pyContemplationFinding, ContemplationRun, ContemplationEvidenceRef, FindingKind, the BOUNDARY doc.
  • core/contemplation/runner.pycontemplate_frontier_reports(...), contemplate_contradiction_reports(...), _emit_findings(...) (shared sink emission helper).
  • core/contemplation/miners/frontier_compare.pymine_frontier_compare_report(report_path, *, substrate_hash).
  • core/contemplation/miners/contradiction_detection.pymine_contradiction_detection_report(report_path, *, substrate_hash).
  • core/contemplation/snapshot.pyContemplationSubstrate (pack ids + report file digests).
  • core/contemplation/__main__.py — module-level CLI.
  • core/cli.py:cmd_contemplation — delegating subcommand wrapper.

Tests

  • tests/test_contemplation_loop.py — schema invariants, frontier miner, runner replay determinism, CLI write path.
  • tests/test_contemplation_pipeline_convergence.py — shared sink protocol, contradiction miner, BOUNDARY doc regression guard, per-finding canonical JSONL, config_hash separation.
  • tests/test_architectural_invariants.py — pack-tree non-mutation guard (cross-lane).

Total: 18 tests pinning the contract.