diff --git a/docs/decisions/ADR-0056-contemplation-loop-c1.md b/docs/decisions/ADR-0056-contemplation-loop-c1.md new file mode 100644 index 00000000..3b8bfa96 --- /dev/null +++ b/docs/decisions/ADR-0056-contemplation-loop-c1.md @@ -0,0 +1,499 @@ +# ADR-0056 — Contemplation Loop: Question Decomposition + Polarity + Domain Typing (Phase C1) + +**Status:** Proposed +**Date:** 2026-05-18 +**Author:** Shay +**Supersedes part of:** ADR-0055 §Decision Phase C (split into C1 + C2) + +--- + +## Context — how we got here + +ADR-0055 originally proposed a single Phase C: "`TeachingChainProposal`, +a sibling to `PackMutationProposal`, sits between discovery and the +active corpus." Conversation while landing Phase B exposed that this +single-phase framing collapsed two distinct concerns: + +1. **Cognitive work** — taking a posed question (a Phase B candidate) + and *thinking through it*: decomposing into sub-questions, + gathering both confirming and falsifying evidence, composing the + result. +2. **Review surface** — taking a fully-composed proposal and + gating it through human review + eval-lane replay-equivalence + before any corpus mutation. + +Conflating these into one phase had three concrete problems: + +- The cognitive surface (the part where the system actually *thinks*) + would be invisible — only the input (a Phase B candidate) and the + output (an accepted corpus entry) would be auditable. The thinking + in between would happen during review prep, not as a first-class, + inspectable runtime artifact. +- The riskiest machinery (auto-apply on replay-equivalence pass) + would ship at the same time as the most interesting, least-tested + cognitive machinery. Both new at once. +- Three load-bearing distinctions the user surfaced ("contemplation + starts with a question," "truths AND falsities both count," + "domain-relative humility") have no natural home in a pure review + ADR — they belong to the cognitive surface. + +The user's framing was: *"contemplation always starts with a question +… recursion … refining by finding truths and/or finding falsities … +remain humble and think and reason with humility."* + +That language is precise enough to drive architecture. This ADR +extracts the cognitive half as Phase C1; the review-and-apply half +moves to a separate future ADR (Phase C2). + +### Decision record — four load-bearing calls + +These were debated in conversation; recording them here so future +sessions can re-derive the reasoning instead of re-arguing it. + +#### Call 1 — Stopping condition for recursive sub-question decomposition + +**Choice:** Epistemic rule = "stop when the sub-question cannot be +decomposed further." Engineering failsafe = bounded depth ceiling +whose hit emits a telemetry signal. + +**Why:** + +- A "success" rule alone ("stop when every sub-question grounded") + cannot terminate on un-decomposable gaps — the loop spins forever + on questions whose answers don't exist yet. +- A pure depth-ceiling rule pretends the limit is epistemically + meaningful — it is not. Depth 5 may be productive; depth 3 may + fully ground. Hardcoding a number is arbitrary. +- (c) "record the gap and stop" is doctrinally right: a recorded + gap *is information* — the system has truthfully reported what it + does not yet know, and that gap becomes a new Phase B candidate + on its own merits. This composes with the rest of the loop. +- The depth ceiling stays in the design as a *failsafe* whose + triggering is itself an audit event (`recursion_overflow`), + never as the "real" stop. Silent truncation of the system's own + thinking is exactly the opaque shortcut CLAUDE.md forbids. + +**Rejected alternatives:** + +- (a) bounded depth alone — discussed above. +- (b) "all sub-questions grounded" — success condition masquerading + as stop condition. +- "stop on first sub-question failure" — too eager; throws away + partial structure that may itself be promotable. + +#### Call 2 — What counts as falsification evidence + +**Choice:** Only **reviewed evidence** in the *same pack family* — +a corpus chain with opposite connective on the same `(subject, +object)`, or a ratified pack contradiction within the cognition-pack +family — falsifies a claim. Session-tier evidence contests but does +not falsify. Cross-pack falsification (ethics-pack vs cognition-pack) +is out of scope. + +**Why:** + +- CLAUDE.md: "session memory may be immediate; reviewed memory must + go through `teaching/*`." Allowing session-tier corrections to + falsify by themselves would smuggle in a parallel learning path. +- ADR-0021's `EpistemicStatus` already encodes this split: + `COHERENT` promotes, `FALSIFIED` falsifies, `SPECULATIVE` and + `CONTESTED` do neither. C1 doesn't add a new tier; it uses the + existing one. +- Cross-pack arbitration is a real future problem (does an ethics + commitment override a cognition claim?) but mixing it into C1 + would make the loop un-shippable. Same-pack falsification is the + 90% case; cross-pack is its own ADR. + +**Rejected alternatives:** + +- Pack-grounded surfaces as falsification evidence — pack + `semantic_domains` don't actually express negation; they describe + a subject's facets. Treating a pack-grounded surface as + falsification would over-claim what the pack asserts. +- User corrections as direct falsification — corrections must go + through `teaching/correction.py` review first. They become + reviewed evidence only after that path completes. + +#### Call 3 — Order: C1 (cognitive) before C2 (review-and-apply) + +**Choice:** C1 lands first. C1's output is an *enriched* +`DiscoveryCandidate` with `proposed_chain.connective`, +`proposed_chain.object`, `polarity`, `claim_domain`, and accumulated +evidence pointers populated by the contemplation loop. Still +`review_state="unreviewed"`. **C1 never mutates the corpus.** C2 +ships later as the review surface that finally permits append-on- +accept. + +**Why:** + +- The interesting work is the cognitive surface. Landing it first + means the contemplation loop is exercised, inspected, and tested + while it is *physically incapable* of touching the active corpus. +- The risky work is auto-apply on replay-equivalence pass. Landing + it last means it ships with the maximum testing lead time and + with an already-populated backlog of enriched candidates ready + for review. +- Matches CLAUDE.md sequencing: "Expand curriculum teaching after + replay/eval/calibration remain deterministic." C1 IS the + curriculum-teaching surface; C2 is the bigger downstream commit. +- Reverses the earlier (instinctive) "land the small thing first" + inclination. The "small" review surface is *useless* without + enriched input; the cognitive surface is *useful* even without + auto-apply (humans can review the JSONL pile directly). + +**Rejected alternative:** C2 first (the original instinct). It +would ship as machinery with no real input — only Phase B's +partials with `connective=None, object=None`. A human reviewer +could hand-author the answer, but then C2 is a glorified discovery +log, not learning. + +#### Call 4 — Sync vs async for the contemplation loop + +**Choice:** Synchronous probe-list iteration. **No `asyncio.gather`, +no concurrency primitives, no thread pool.** + +**Why:** + +- CORE's hot path is deterministic by construction (exact CGA + recall, no stochastic sampling, no clock-time reads). Async + introduces nondeterministic completion order; tests that rely on + trace-hash equivalence across runs would become flaky-by-design. +- Every grounding probe C1 runs is *fast and local*: vault recall + is one matrix sweep (ADR-0054), pack lookup is a dict get, + corpus lookup is a dict get. Concurrency overhead exceeds probe + cost on a 32-component vault under a few thousand entries. +- Sync iteration preserves the canonical order + (vault → pack → teaching corpus → gap) declared in + `_maybe_pack_grounded_surface`. That order is itself + audit-relevant — the first probe that grounds wins, and "which + source grounded first" is part of the candidate's provenance. +- Async is a real architectural option *only* when a probe blocks + on I/O (future remote pack fetch, network-backed knowledge + source, etc.). None exist today. When one does, the right move + is a separate ADR introducing async at that boundary, not + retrofitting it everywhere. + +**Rejected alternative:** `asyncio.gather` over sub-question probes. +Deferred to a future ADR if/when a blocking probe surface exists. + +--- + +## Decision — Phase C1 spec + +### Data shape + +C1 enriches `DiscoveryCandidate` with four typed fields. The +existing Phase B fields stay; the new fields default to values +that make a Phase B candidate trivially valid as an unenriched +C1 candidate: + +```python +@dataclass(frozen=True, slots=True) +class DiscoveryCandidate: + # Phase B fields (unchanged) + candidate_id: str + proposed_chain: dict[str, Any] # subject, intent, connective?, object? + trigger: DiscoveryTrigger + source_turn_trace: str + pack_consistent: bool + boundary_clean: bool + review_state: Literal["unreviewed"] = "unreviewed" + + # Phase C1 fields (NEW) + polarity: Literal["affirms", "falsifies", "undetermined"] = "undetermined" + claim_domain: ClaimDomain = "factual" + evidence: tuple[EvidencePointer, ...] = () + sub_questions: tuple[SubQuestion, ...] = () + contemplation_depth: int = 0 + recursion_overflow: bool = False +``` + +#### `polarity` + +Three values: +- `"affirms"` — composed evidence supports the proposed + `(subject, connective, object)` relation. +- `"falsifies"` — composed evidence supports the *negation* of the + proposed relation. Falsified candidates are first-class: the + corpus learns what is NOT the case as much as what is. +- `"undetermined"` — the contemplation loop terminated without + enough reviewed evidence on either side. The candidate records + the gap but does not assert direction. Phase B candidates that + have not yet been contemplated start here. + +Placing polarity *on the chain itself* (not in a separate "anti- +chain" file) keeps the corpus single-source-of-truth and lets the +existing `superseded_by` mechanism work uniformly across affirming +and falsifying entries. + +#### `claim_domain` taxonomy + +```python +ClaimDomain = Literal["factual", "relational", "evaluative"] +``` + +- **`factual`** — claims whose truth-value is independent of + context, observer, or value judgment. Example: + `light reveals truth` is factual within the cognition pack: + pack atoms compose; the relation either holds or it doesn't. + Evidence threshold: **one consistent line of reviewed evidence.** +- **`relational`** — claims whose truth-value depends on the + relation/context between the subject and the surrounding frame. + Example: `wisdom orders judgment` is partly relational — + whether wisdom *orders* judgment vs *informs* it vs *constrains* + it depends on which other concepts are co-active in the frame. + Evidence threshold: **multiple consistent reviewed lines AND no + reviewed contradictions.** +- **`evaluative`** — claims that carry a value or aesthetic + judgment, especially about people, style, intent, character. + Example: `this user is direct` or `this argument is elegant`. + Evidence threshold: **highest** — multiple reviewed lines, no + reviewed contradictions, AND a strong-tier hedge surface MUST + be attached. The doctrinal commitment is humility: in evaluative + territory the system speaks hedged or not at all. + +The thresholds are *guidance for the future C2 review gate* — +C1 does not enforce them. C1 only *classifies*. Classification +itself is deterministic: + +- Default `"factual"` for pack-resident cognition lemmas where + both subject and object are in `en_core_cognition_v1`. +- `"relational"` triggered by intent ∈ {COMPARISON, CAUSE} with + a frame-dependent connective (e.g., `orders`, `grounds`, + `informs`) — a small reviewed table in the cognition pack + declares which connectives are frame-dependent. +- `"evaluative"` triggered by intent classification surfacing a + person/style/intent referent (today: not classified — reserved + for when intent classification grows that axis). Until then, + `evaluative` is only assignable by an operator on review. + +The classification table is itself versioned pack data, not code +constants, so refining the taxonomy doesn't require a code change. + +#### `EvidencePointer` + +```python +@dataclass(frozen=True, slots=True) +class EvidencePointer: + source: Literal["corpus", "pack", "vault_coherent"] + ref: str # chain_id, pack_lemma, or vault_index + polarity: Literal["affirms", "falsifies"] + epistemic_status: str # mirrors ADR-0021 EpistemicStatus +``` + +Only `"corpus"` (reviewed teaching chains), `"pack"` (ratified pack +contradictions within the same family), and `"vault_coherent"` +(session vault entries stamped `EpistemicStatus.COHERENT`) are +admissible evidence pointers. SPECULATIVE / CONTESTED / +FALSIFIED vault entries are ignored — they contest but do not +contribute as evidence. + +#### `SubQuestion` + +```python +@dataclass(frozen=True, slots=True) +class SubQuestion: + sub_id: str # deterministic hash; suffix on parent candidate_id + proposed_subject: str + proposed_intent: str + outcome: Literal["grounded", "gap_recorded", "depth_failsafe"] + evidence: tuple[EvidencePointer, ...] = () +``` + +`outcome="gap_recorded"` is the load-bearing case from Call 1: the +sub-question couldn't be decomposed further, so the system records +the gap as its own first-class artifact. Gap-recorded sub-questions +spawn new top-level `DiscoveryCandidate` entries via the existing +Phase B sink — the recursion is reified into the same stream. + +### Contemplation loop shape + +```text +def contemplate(candidate: DiscoveryCandidate, *, max_depth: int = 8) -> DiscoveryCandidate: + if candidate.contemplation_depth >= max_depth: + return replace(candidate, recursion_overflow=True) # failsafe, audited + + decomposition = decompose_question(candidate.proposed_chain) + if decomposition.terminal: + # Cannot decompose further — record gap, return undetermined. + return replace(candidate, sub_questions=(_gap_subquestion(candidate),)) + + sub_results: list[SubQuestion] = [] + for sub in decomposition.sub_questions: + sub_candidate = _materialise_sub_candidate(sub, parent=candidate) + # Synchronous probe: vault → pack → corpus → recurse if still ungrounded. + ev = probe_for_evidence(sub_candidate) + if ev.grounded: + sub_results.append(_grounded(sub, ev)) + else: + recursed = contemplate(sub_candidate, max_depth=max_depth) + sub_results.append(_summarise(recursed)) + + composed = compose(candidate, tuple(sub_results)) + return composed +``` + +Every step is a pure function over its inputs. `_materialise_sub_candidate` +derives a `sub_id` deterministically from `(parent.candidate_id, sub.index)`. +`probe_for_evidence` calls — in order — vault.recall (matrix-cached +ADR-0054 path), pack lookup, corpus lookup. The order is canonical; +the first grounding source wins and is recorded in the evidence +pointer. + +### Composition rules + +After all sub-questions return, `compose` reduces them to a single +polarity verdict on the parent: + +- All sub-evidence affirms ⇒ parent polarity `affirms`. +- All sub-evidence falsifies (or one direct same-pack contradiction + on the parent itself) ⇒ parent polarity `falsifies`. +- Mixed evidence with no clear majority of reviewed lines ⇒ + parent polarity `undetermined` and `claim_domain` upgraded one + tier (factual → relational, relational → evaluative) so the + later review gate demands more before accepting. + +Composition is deterministic: no thresholds with floating-point +math, no scoring weights — every rule is a typed predicate over +the evidence tuple. + +### What gets written + +Enriched candidates emit through the **same** Phase B sink — the +JSONL line just has more fields populated. No new file, no new +path. The on-disk record stays append-only. + +Gap-recorded sub-questions emit as **separate** Phase B candidates +on the same sink — each gap is its own first-class question the +system has identified. This is the recursion-into-the-stream +property. + +--- + +## Trust boundary + +- **No corpus mutation.** C1 reads `_pack_index()`, + `_corpus_index()`, vault, and the most recent `TurnEvent`. It + writes only to the discovery sink. The active corpus on disk is + byte-identical before and after a contemplation pass. +- **No identity / safety / ethics pack mutation.** Ratified packs + are read-only from this loop. +- **No clock-time reads.** Trace hashes and candidate ids are + derived from content, not wall-clock. +- **No external I/O.** All probes hit in-process indices. + +--- + +## Non-goals (explicit) + +- No `TeachingChainProposal` typed object (that is C2). +- No `core teaching propose` / `core teaching review` CLI (C2). +- No replay-equivalence eval-lane gating (C2). +- No corpus append-on-accept (C2). +- No async or concurrency primitives — per Call 4, deferred to a + future ADR only if a blocking probe surface emerges. +- No cross-pack falsification arbitration — per Call 2, deferred. +- No LLM judgement step, anywhere in the loop. + +--- + +## Verification (acceptance criteria for the eventual C1 PR) + +- `contemplate(candidate)` is deterministic: same inputs produce + identical enriched-candidate JSONL bytes across runs. +- An empty corpus + empty pack still terminates (every probe + fails, every sub-question gap-records, the parent returns + `undetermined` with non-empty `sub_questions`). +- A factual candidate whose evidence is one reviewed corpus line + composes to `polarity="affirms"`, `claim_domain="factual"`. +- A candidate whose direct same-pack contradiction exists composes + to `polarity="falsifies"`. +- Mixed-evidence candidates upgrade `claim_domain` by exactly one + tier and stay `polarity="undetermined"`. +- Recursion-overflow flips `recursion_overflow=True` and emits the + telemetry signal — never silently truncates. +- Cognition eval lane unchanged on dev / public / holdout splits. +- `versor_condition(F) < 1e-6` invariant preserved (C1 touches no + algebra path). + +--- + +## Open questions (deferred, but named) + +1. **Frame-dependent connective table.** `claim_domain="relational"` + classification relies on a reviewed list of frame-dependent + connectives (`orders`, `grounds`, `informs`, …). That list is + pack data, not code — but who authors v1? Likely a small PR + alongside the C1 implementation. +2. **Evaluative classifier without person-axis intent.** Today's + intent classifier has no person/style axis. Until it does, + `claim_domain="evaluative"` is operator-assigned only. That is + the conservative default — the system never silently classifies + a claim as evaluative. +3. **Telemetry signal shape for recursion overflow.** Likely a new + `TurnEvent` flag or a sibling sink to the discovery sink. C2 + may need to consult overflow signals when scoring proposals. +4. **Sub-question deduplication.** Two parallel candidates may + produce the same sub-question independently. The current design + emits both — the receiving sink could dedupe by `sub_id`, but + that conflicts with the per-trace audit story. Probably leave + it: duplication IS information about which parent asked. + +--- + +## Cross-References + +- [ADR-0021](./ADR-0021-epistemic-status.md) — the + `EpistemicStatus` substrate this loop reuses. `COHERENT` promotes, + `FALSIFIED` falsifies, `SPECULATIVE`/`CONTESTED` contest. +- [ADR-0027](./ADR-0027-identity-packs.md) — ratified pack + authority. C1 reads packs; never mutates them. +- [ADR-0038](./ADR-0038-hedge-injection.md) — the hedge surface + C2 will plumb into for `claim_domain="evaluative"` humility + enforcement. +- [ADR-0040](./ADR-0040-structured-logging-sink.md) / + [ADR-0041](./ADR-0041-fanout-sink-cli-verdicts.md) — sink + pattern this loop reuses. +- [ADR-0052](./ADR-0052-teaching-grounded-surface.md) — the + reviewed teaching corpus this loop reads as evidence. +- [ADR-0053](./ADR-0053-cognition-lane-closure.md) — the + `teaching/correction.py` repair path that produces *reviewed* + evidence after a session correction passes review. +- [ADR-0054](./ADR-0054-vault-recall-indexing-batching.md) — the + cached-matrix vault recall the loop's per-probe vault lookup + uses. +- [ADR-0055](./ADR-0055-inter-session-memory-discovery-promotion.md) + — the parent design; this ADR extracts and refines its + Phase C cognitive half. +- Future ADR (Phase C2) — `TeachingChainProposal` + review + + replay-equivalence + corpus append-on-accept. + +--- + +## Verification of provenance (for future re-derivation) + +The four calls above were made in conversation on 2026-05-18 after +Phase B (`07d35c0`) landed. The user surfaced three load-bearing +distinctions: + +> "contemplation always starts with a question" +> "refining by finding truths and/or finding falsities" +> "needing more evidence or proof before deciding on things as to +> not be 'judgemental' or better put, to remain humble and think +> and reason with humility" + +Each maps directly onto a C1 design element: + +- "question" → `DiscoveryCandidate` is the posing of the question; + contemplation is the answering step. +- "truths and/or falsities" → `polarity ∈ {affirms, falsifies, + undetermined}` on the chain itself. +- "humility" → `claim_domain` taxonomy with escalating evidence + thresholds and mandatory hedge surfaces in evaluative territory. + +The split of Phase C into C1 + C2, and the choice to land the +cognitive half before the auto-apply half, was an explicit +re-decision against my earlier instinct to land C2 first. Reasoning +recorded in Call 3 above. Future sessions that re-encounter the +question "should the review surface ship first?" can re-derive +the no-answer from that record without re-arguing. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 61d6cabb..2ecf8bcd 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -64,7 +64,8 @@ ADRs record significant architectural decisions: what was decided, why, what alt | [ADR-0052](ADR-0052-teaching-grounded-surface.md) | Teaching-grounded surface for cold-start CAUSE / VERIFICATION | Accepted (2026-05-18) | | [ADR-0053](ADR-0053-cognition-lane-closure.md) | Cognition lane closure: dev-driven corpus expansion + CORRECTION acknowledgement | Accepted (2026-05-18) | | [ADR-0054](ADR-0054-vault-recall-indexing-batching.md) | Vault recall matrix-cache indexing + batched API; holdout split wired into eval CLI | Accepted (2026-05-18) | -| [ADR-0055](ADR-0055-inter-session-memory-discovery-promotion.md) | Inter-session memory: reviewed discovery promotion (phased design — DiscoveryCandidate, TeachingChainProposal, replay-equivalence gate) | **Proposed** (2026-05-18) | +| [ADR-0055](ADR-0055-inter-session-memory-discovery-promotion.md) | Inter-session memory: reviewed discovery promotion (phased design — DiscoveryCandidate, TeachingChainProposal, replay-equivalence gate); Phase A + Phase B Accepted | **Phase A + B Accepted**; C–E Proposed (2026-05-18) | +| [ADR-0056](ADR-0056-contemplation-loop-c1.md) | Contemplation loop (Phase C1): question decomposition, polarity (affirms/falsifies/undetermined), claim_domain typing (factual/relational/evaluative), sync-only by design | **Proposed** (2026-05-18) | ---