core/evals/calibration/runner.py
Shay 64268436fb feat(evals): calibration lane v1 — typed cognitive signals
Adds the third Phase 2 lane: calibration measures whether CORE's runtime
emits distinguishable, typed evidence for three cognitive states:

  no_grounding         vault_hits == 0 (gate fired, no recall)
  coherent             vault_hits > 0  (vault recall fired)
  correction_proposed  pack_mutation_proposal is not None

Each case runs on its own fresh CognitiveTurnPipeline to avoid
cross-case field-state drift (the gate's geometric recall score is
sensitive to vault content drift across turns).

v1 results: dev 12/12, public/v1 24/24, holdouts/v1 18/18 — all classes
score 1.0 across all splits.

Architectural findings logged in evals/calibration/gaps.md:

  1. The ingest gate fires on a *geometric* CGA-recall score, not on
     semantic OOD. 6/42 hand-chosen OOD prompts fire the gate with a
     warmed vault; the other 36 land geometrically near in-pack
     versors after morphological grounding. v1 measures the reliable
     recall/correction signals, not semantic OOD detection.

  2. CognitiveTurnPipeline.run() unconditionally overrides the
     runtime's gate-safety surface with the realizer surface. The OOD
     marker survives in walk_surface but not in surface. v1 classifies
     on vault_hits (preserved) rather than surface (overridden).

Both findings are filed as suggested follow-up work, not v1 blockers.
2026-05-16 12:22:16 -07:00

137 lines
4.3 KiB
Python

"""Calibration eval lane runner.
Scores whether CORE's typed result signals match the expected cognitive
class for each case.
no_grounding — result.vault_hits == 0 (gate fired, no recall)
coherent — result.vault_hits > 0 (vault recall fired)
correction_proposed — result.pack_mutation_proposal is not None
Each case runs on its own fresh CognitiveTurnPipeline so field-state
drift from prior cases does not poison the gate / recall geometry.
See contract.md for the structural claim; see gaps.md for the
architectural findings underlying the choice of signals.
Conforms to the framework interface: run_lane(cases, config=None) -> report.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from chat.runtime import ChatRuntime
from core.cognition.pipeline import CognitiveTurnPipeline
from core.cognition.result import CognitiveTurnResult
from core.config import RuntimeConfig
VALID_CLASSES = frozenset({"no_grounding", "coherent", "correction_proposed"})
@dataclass(slots=True)
class LaneReport:
metrics: dict[str, Any] = field(default_factory=dict)
case_details: list[dict[str, Any]] = field(default_factory=list)
def _infer_class(result: CognitiveTurnResult) -> str:
if result.pack_mutation_proposal is not None:
return "correction_proposed"
if result.vault_hits > 0:
return "coherent"
return "no_grounding"
def _run_case(case: dict[str, Any], config: RuntimeConfig | None) -> dict[str, Any]:
runtime = ChatRuntime(config=config) if config else ChatRuntime()
pipeline = CognitiveTurnPipeline(runtime)
for prime_prompt in case.get("prime", []):
try:
pipeline.run(prime_prompt, max_tokens=8)
except ValueError:
pass
expected = case.get("expected_class", "")
prompt = case["prompt"]
try:
result = pipeline.run(prompt, max_tokens=8)
inferred = _infer_class(result)
vault_hits = result.vault_hits
proposal_present = result.pack_mutation_proposal is not None
except ValueError:
inferred = "no_grounding"
vault_hits = 0
proposal_present = False
passed = inferred == expected
return {
"id": case.get("id", ""),
"expected_class": expected,
"inferred_class": inferred,
"vault_hits": vault_hits,
"proposal_present": proposal_present,
"passed": passed,
}
def run_lane(
cases: list[dict[str, Any]],
*,
config: RuntimeConfig | None = None,
) -> LaneReport:
if not cases:
return LaneReport(metrics={}, case_details=[])
invalid = [c.get("id", "?") for c in cases if c.get("expected_class") not in VALID_CLASSES]
if invalid:
raise ValueError(f"Unknown expected_class in cases: {invalid}")
case_details: list[dict[str, Any]] = []
class_correct: dict[str, int] = {c: 0 for c in VALID_CLASSES}
class_total: dict[str, int] = {c: 0 for c in VALID_CLASSES}
for case in cases:
detail = _run_case(case, config)
case_details.append(detail)
ec = detail["expected_class"]
class_total[ec] += 1
if detail["passed"]:
class_correct[ec] += 1
def acc(cls: str) -> float | None:
total = class_total[cls]
if total == 0:
return None
return class_correct[cls] / total
total_cases = len(case_details)
total_correct = sum(1 for d in case_details if d["passed"])
overall_accuracy = total_correct / total_cases if total_cases > 0 else 0.0
ng_acc = acc("no_grounding")
co_acc = acc("coherent")
cp_acc = acc("correction_proposed")
def _passes(a: float | None) -> bool:
return a is None or a >= 0.80
overall_pass = (
_passes(ng_acc)
and _passes(co_acc)
and _passes(cp_acc)
and overall_accuracy >= 0.80
)
metrics: dict[str, Any] = {
"no_grounding_accuracy": round(ng_acc, 4) if ng_acc is not None else None,
"coherent_accuracy": round(co_acc, 4) if co_acc is not None else None,
"correction_proposed_accuracy": round(cp_acc, 4) if cp_acc is not None else None,
"overall_accuracy": round(overall_accuracy, 4),
"class_counts": {c: class_total[c] for c in VALID_CLASSES},
"overall_pass": overall_pass,
}
return LaneReport(metrics=metrics, case_details=case_details)