core/docs/decisions/ADR-0056-contemplation-loop-c1.md
Shay 4e03a7f872 docs(adr-0056): Accepted (Phase C1 implemented at 4eecf73)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 10:07:27 -07:00

499 lines
21 KiB
Markdown

# ADR-0056 — Contemplation Loop: Question Decomposition + Polarity + Domain Typing (Phase C1)
**Status:** Accepted (implemented at `4eecf73`, 2026-05-18)
**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.