feat(contemplation): land ADR-0080 phase 1 (#119)

This commit is contained in:
Shay 2026-05-22 13:10:03 -07:00 committed by GitHub
parent 5cad0a4b72
commit 5f149340cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 185 additions and 14 deletions

View file

@ -4,7 +4,7 @@ ADR-0080: contemplation can emit speculative findings about current
substrate/report evidence, but it cannot ratify, promote, or mutate packs.
"""
from .runner import contemplate_frontier_reports
from .runner import contemplate_frontier_reports, run_contemplation
from .schema import (
ContemplationEvidenceRef,
ContemplationFinding,
@ -20,4 +20,5 @@ __all__ = [
"ContemplationSubstrate",
"FindingKind",
"contemplate_frontier_reports",
"run_contemplation",
]

View file

@ -104,6 +104,33 @@ def contemplate_frontier_reports(
)
def run_contemplation(
report_paths: Iterable[str | Path] | None = None,
*,
pack_ids: Iterable[str] = (),
notes: Iterable[str] = (),
) -> ContemplationRun:
"""Run ADR-0080 Phase 1 over frontier-compare reports.
This is the stable operator-facing entry point for Phase 1. If no
explicit paths are supplied it reads the checked-in
``evals/frontier_compare/results/*.json`` reports in deterministic
path order. It never writes packs, teaching examples, proposal logs,
or discovery sinks.
"""
if report_paths is None:
root = Path(__file__).resolve().parents[2]
paths = tuple(sorted(root.glob("evals/frontier_compare/results/*.json")))
else:
paths = tuple(Path(p) for p in report_paths)
return contemplate_frontier_reports(
paths,
pack_ids=pack_ids,
notes=notes,
sink=None,
)
def contemplate_contradiction_reports(
report_paths: Iterable[str | Path],
*,
@ -161,5 +188,6 @@ def write_contemplation_run(run: ContemplationRun, path: str | Path) -> None:
__all__ = [
"contemplate_contradiction_reports",
"contemplate_frontier_reports",
"run_contemplation",
"write_contemplation_run",
]

View file

@ -151,6 +151,28 @@ class ContemplationFinding:
payload["finding_id"] = self.finding_id
return payload
@property
def confidence(self) -> EpistemicStatus:
"""ADR-0080 Phase 1 confidence alias.
Phase 1 deliberately reuses the repository's epistemic lattice:
every contemplation confidence is SPECULATIVE by construction.
"""
return self.epistemic_status
@property
def evidence(self) -> tuple[ContemplationEvidenceRef, ...]:
"""ADR-0080 Phase 1 evidence alias for ``evidence_refs``."""
return self.evidence_refs
@property
def provenance(self) -> dict[str, str]:
"""Deterministic provenance for the finding's read-only substrate."""
return {
"source": "contemplation",
"substrate_hash": self.substrate_hash,
}
@dataclass(frozen=True, slots=True)
class ContemplationRun:

View file

@ -1,7 +1,8 @@
# ADR-0080 — Contemplation Loop: self-interrogation without self-ratification
**Status:** Proposed
**Status:** Accepted
**Date:** 2026-05-20
**Accepted:** 2026-05-22
**Author:** Shay
**Builds on:** [ADR-0021](./ADR-0021-epistemic-grade-policy.md) (epistemic grade policy), [ADR-0055](./ADR-0055-teaching-corpus-audit.md) (reviewed teaching corpus), [ADR-0078](./ADR-0078-composer-graph-atom-equivalence.md) (composer/graph atom telemetry)
**Series:** L0 — learning safety / self-contemplation boundary

View file

@ -10,6 +10,7 @@ ADRs record significant architectural decisions: what was decided, why, what alt
| ADR | Title | Status |
|---|---|---|
| [ADR-0080](ADR-0080-contemplation-loop.md) | Contemplation Loop | Accepted (2026-05-22) |
| [ADR-0091](ADR-0091-domain-pack-contract-v1.md) | Domain Pack Contract v1 | Accepted (2026-05-22) |
| [ADR-0092](ADR-0092-reviewer-registry-v1.md) | Reviewer Registry v1 | Accepted (2026-05-22) |
| [ADR-0093](ADR-0093-domain-pack-contract-v1-implementation.md) | Domain Pack Contract v1 implementation | Accepted (2026-05-22) |
@ -53,6 +54,8 @@ The ADR-0091..0105 slate is fully accepted and mechanically evidenced:
- Curriculum-Sourced Teaching Proposals — ADR-0104
- Sealed Holdout Encryption via age — ADR-0105
ADR-0080 has also landed: Contemplation Loop Phase 1 adds a read-only frontier-compare miner that emits `SPECULATIVE` findings only.
Seven lanes are SHA-pinned in `scripts/verify_lane_shas.py` and gated by the `lane-shas` GitHub Actions workflow:
- `reviewer_registry`
@ -67,11 +70,10 @@ Seven lanes are SHA-pinned in `scripts/verify_lane_shas.py` and gated by the `la
Sequencing per ADR-0108. Listed in priority order:
1. **[ADR-0080](ADR-0080-contemplation-loop.md) — Contemplation Loop.** Sandboxed, read-only Phase 1 self-interrogation; emits `SPECULATIVE` findings from `frontier_compare` reports. Converts gap-finding from human-driven to system-emitted-and-reviewed.
2. **[ADR-0084](ADR-0084-definitional-layer.md) — Definitional Layer for Lexicon Packs.** Optional per-entry definitional block. Deferred — value surfaces during a worked expert promotion that needs definitional depth.
3. **[ADR-0087](ADR-0087-rhetorical-style-axis.md) — Rhetorical Style Axis.** A third substantive selection axis sibling to anchor-lens. Lowest current priority — no active downstream consumer; register + anchor-lens already demonstrate the orthogonality pattern.
1. **[ADR-0084](ADR-0084-definitional-layer.md) — Definitional Layer for Lexicon Packs.** Optional per-entry definitional block. Deferred — value surfaces during a worked expert promotion that needs definitional depth.
2. **[ADR-0087](ADR-0087-rhetorical-style-axis.md) — Rhetorical Style Axis.** A third substantive selection axis sibling to anchor-lens. Lowest current priority — no active downstream consumer; register + anchor-lens already demonstrate the orthogonality pattern.
ADR-0110 has landed — `mathematics_logic` is the first domain at `expert_demo=true`. The remaining three ratified domains (`physics`, `systems_software`, `hebrew_greek_textual_reasoning`) need their own promotion ADRs.
ADR-0080 (Contemplation Loop, Phase 1) and ADR-0110 (math expert-demo) have both landed — `mathematics_logic` is the first domain at `expert_demo=true`; the contemplation loop emits read-only `SPECULATIVE` findings from `frontier_compare` reports. The remaining three ratified domains (`physics`, `systems_software`, `hebrew_greek_textual_reasoning`) need their own promotion ADRs.
### Open candidate directions (no ADR yet)

View file

@ -0,0 +1,42 @@
{
"benchmark_family": "frontier_compare_adr_0080_phase1",
"mode": "core",
"model": "core-native",
"suites": [
{
"case_count": 1,
"cases": [
{
"case_id": "planner_relation_gap",
"details": {
"observation": {
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "Explain how reviewed correction changes future recall.",
"provider": "core",
"surface": "reviewed correction changes future recall"
}
},
"elapsed_ms": 1.0,
"failures": [
"missing_reviewed_teaching_path_explanation"
],
"passed": false,
"prompt": "Explain how reviewed correction changes future recall.",
"score": 0.0,
"suite": "contemplation_phase1"
}
],
"passed": false,
"primary_score": 0.0,
"suite": "contemplation_phase1"
}
],
"summary": {
"case_count": 1,
"passed": false,
"primary_score": 0.0,
"suite_count": 1
}
}

View file

@ -1,9 +1,10 @@
"""Sealed :class:`ProposalSource` provenance type (ADR-0094/ADR-0104).
"""Sealed :class:`ProposalSource` provenance type (ADR-0094/ADR-0104/ADR-0080).
Widens :class:`teaching.proposals.TeachingChainProposal` and
:class:`teaching.store.PackMutationProposal` with a typed source field.
Operator and miner provenance landed in ADR-0094/ADR-0095; curriculum
source activation is governed by ADR-0104.
source activation is governed by ADR-0104. ADR-0080 activates the
reserved contemplation source kind for read-only, SPECULATIVE findings.
The kind field is a sealed :data:`ProposalKind` literal. Adding a new
kind requires a new ADR adding a branch to every consumer.
@ -19,6 +20,8 @@ Consumers must branch on :attr:`ProposalSource.kind` using exhaustive
...
case "curriculum":
...
case "contemplation":
...
case _: # pragma: no cover - exhaustiveness
assert_never(proposal.source.kind)
"""
@ -29,7 +32,7 @@ from dataclasses import dataclass
from typing import Any, Literal, Mapping, get_args
ProposalKind = Literal["operator", "miner", "curriculum"]
ProposalKind = Literal["operator", "miner", "curriculum", "contemplation"]
ALLOWED_KINDS: frozenset[str] = frozenset(get_args(ProposalKind))
@ -42,8 +45,9 @@ class ProposalSource:
"""Typed provenance for one proposal.
:param kind:
One of ``"operator"``, ``"miner"``, ``"curriculum"``. The set is
sealed; adding a new kind requires a new ADR.
One of ``"operator"``, ``"miner"``, ``"curriculum"``, or
``"contemplation"``. The set is sealed; adding a new kind
requires a new ADR.
:param source_id:
Empty for ``kind="operator"``. For other kinds, the originating
miner id or curriculum course id.
@ -85,6 +89,8 @@ class ProposalSource:
-> ``"miner:articulation_quality"``
- ``ProposalSource("curriculum", "math_logic_v1", "<sha>")``
-> ``"curriculum:math_logic_v1"``
- ``ProposalSource("contemplation", "frontier_compare", "<sha>")``
-> ``"contemplation:frontier_compare"``
"""
if not self.source_id:
return self.kind

View file

@ -7,7 +7,11 @@ import pytest
from core.contemplation.__main__ import main
from core.contemplation.miners.frontier_compare import mine_frontier_compare_report
from core.contemplation.runner import contemplate_frontier_reports, write_contemplation_run
from core.contemplation.runner import (
contemplate_frontier_reports,
run_contemplation,
write_contemplation_run,
)
from core.contemplation.schema import (
ContemplationEvidenceRef,
ContemplationFinding,
@ -15,6 +19,7 @@ from core.contemplation.schema import (
)
from core.contemplation.snapshot import ContemplationSubstrate
from teaching.epistemic import EpistemicStatus
from teaching.source import ProposalSource
def _sample_frontier_report(path: Path) -> None:
@ -104,6 +109,9 @@ def test_frontier_report_miner_emits_failed_cases_only(tmp_path: Path) -> None:
assert finding.predicate == "failed_case"
assert finding.object == "Why does xylomorphic matter?"
assert finding.epistemic_status is EpistemicStatus.SPECULATIVE
assert finding.confidence is EpistemicStatus.SPECULATIVE
assert finding.evidence == finding.evidence_refs
assert finding.provenance["source"] == "contemplation"
assert finding.evidence_refs[0].summary == "unexpected_grounding_source:vault"
@ -142,6 +150,43 @@ def test_contemplation_runner_does_not_mutate_pack_tree(tmp_path: Path) -> None:
assert before == after
def test_contemplation_runner_does_not_write_teaching_store_without_review(
tmp_path: Path,
) -> None:
report = tmp_path / "frontier_wave1.json"
_sample_frontier_report(report)
proposal_store = Path("teaching/proposals/proposals.jsonl")
before = proposal_store.read_bytes() if proposal_store.exists() else b""
run = contemplate_frontier_reports((report,))
after = proposal_store.read_bytes() if proposal_store.exists() else b""
assert run.findings
assert before == after
def test_run_contemplation_default_entrypoint_is_deterministic() -> None:
first = run_contemplation()
second = run_contemplation()
assert first.as_dict() == second.as_dict()
assert all(
finding.confidence is EpistemicStatus.SPECULATIVE
for finding in first.findings
)
def test_contemplation_proposal_source_parses() -> None:
source = ProposalSource(
kind="contemplation",
source_id="frontier_compare",
emitted_at_revision="abc123",
)
assert source.serialize() == "contemplation:frontier_compare"
assert ProposalSource.from_dict(source.as_dict()) == source
def test_contemplation_write_and_cli(tmp_path: Path, capsys) -> None:
source_report = tmp_path / "frontier_wave1.json"
output_report = tmp_path / "contemplation.json"

View file

@ -35,6 +35,10 @@ class TestProposalSourceConstruction:
with pytest.raises(ProposalSourceError, match="non-empty source_id"):
ProposalSource(kind="curriculum", source_id="", emitted_at_revision="abc")
def test_contemplation_requires_source_id(self) -> None:
with pytest.raises(ProposalSourceError, match="non-empty source_id"):
ProposalSource(kind="contemplation", source_id="", emitted_at_revision="abc")
def test_operator_rejects_source_id(self) -> None:
with pytest.raises(ProposalSourceError, match="empty source_id"):
ProposalSource(kind="operator", source_id="something", emitted_at_revision="abc")
@ -78,6 +82,14 @@ class TestSerialization:
)
assert src.serialize() == "curriculum:math_logic_v1"
def test_contemplation_serializes_kind_and_id(self) -> None:
src = ProposalSource(
kind="contemplation",
source_id="frontier_compare",
emitted_at_revision="abc",
)
assert src.serialize() == "contemplation:frontier_compare"
def test_round_trip_operator(self) -> None:
src = ProposalSource.operator(emitted_at_revision="abc")
roundtrip = ProposalSource.from_dict(src.as_dict())
@ -125,6 +137,8 @@ class TestExhaustiveMatchPattern:
return f"m:{src.source_id}"
case "curriculum":
return f"c:{src.source_id}"
case "contemplation":
return f"q:{src.source_id}"
case _: # pragma: no cover - exhaustiveness
assert_never(src.kind)
@ -139,8 +153,18 @@ class TestExhaustiveMatchPattern:
src = ProposalSource(kind="curriculum", source_id="cur", emitted_at_revision="x")
assert self._describe(src) == "c:cur"
def test_kinds_sealed_at_three(self) -> None:
assert ALLOWED_KINDS == frozenset({"operator", "miner", "curriculum"})
def test_covers_contemplation(self) -> None:
src = ProposalSource(
kind="contemplation",
source_id="frontier_compare",
emitted_at_revision="x",
)
assert self._describe(src) == "q:frontier_compare"
def test_kinds_sealed_at_four(self) -> None:
assert ALLOWED_KINDS == frozenset(
{"operator", "miner", "curriculum", "contemplation"}
)
class TestMigrationDeterminism: