feat(contemplation): land ADR-0080 phase 1 (#119)
This commit is contained in:
parent
5cad0a4b72
commit
5f149340cc
9 changed files with 185 additions and 14 deletions
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
42
evals/frontier_compare/results/adr_0080_failed_case.json
Normal file
42
evals/frontier_compare/results/adr_0080_failed_case.json
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue