* 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.
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:
- Read-only: never mutates packs, vault, teaching corpus, or runtime state. Only emits.
- SPECULATIVE-only: every emitted
ContemplationFindingis stampedEpistemicStatus.SPECULATIVE; the schema's__post_init__raises on any other status. No autonomous ratification. - Deterministic replay: same input files + same flags →
identical
run_id, identicalfinding_idper finding, identical sink output.
Two evidence miners ship today:
-
frontier_compare— failed benchmark cases fromevals/frontier_compare/results/*.jsonbecomeFindingKind.BENCHMARK_CASEfindings. Subject is"<suite>/<case_id>"; predicate isfailed_case; evidence summary lists the case'sfailuresarray. -
contradiction_detection— failed cases fromevals/contradiction_detection/results/*.jsonbecomeFindingKind.CONTRADICTIONfindings with a predicate split:missed_contradiction—paired_contradictioncase the detector failed to flag.false_contradiction_flag—paired_consistentcase 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 —
#56fast-follow miner was closed to avoid entrenching the parallelcore/contemplation/lane. Replaced by#58which connected the boundary doctrine to existingteaching/discovery_sink.pyplumbing. - 2026-05-20 —
#58documented the BOUNDARY betweenEvidencePointer(teaching: reviewed memory only) andContemplationEvidenceRef(core: external report files). Forcing them to merge would have either widened the reviewed-memory enum (losing the guarantee) or made benchmark reports masquerade asvault_coherent. Both worse than documented separation. - 2026-05-20 —
#60discovered the CLI was invisible — the contemplation module was reachable only viapython -m core.contemplation, not viacore --help. Subcommand added.
Runner / module layout
core/contemplation/schema.py—ContemplationFinding,ContemplationRun,ContemplationEvidenceRef,FindingKind, the BOUNDARY doc.core/contemplation/runner.py—contemplate_frontier_reports(...),contemplate_contradiction_reports(...),_emit_findings(...)(shared sink emission helper).core/contemplation/miners/frontier_compare.py—mine_frontier_compare_report(report_path, *, substrate_hash).core/contemplation/miners/contradiction_detection.py—mine_contradiction_detection_report(report_path, *, substrate_hash).core/contemplation/snapshot.py—ContemplationSubstrate(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.