core/docs/decisions/ADR-0060-correction-acknowledgment-topic-lemma.md
Shay c9e858c266 feat(adr-0060): correction acknowledgement carries corrected-topic lemma
ADR-0053's cold-start CORRECTION surface was topic-blind: a user who
said "Actually, truth requires evidence" got a response referencing
`correction` but never `truth`.  The holdout case correction_truth_040
expected `term=['truth']` and missed — one of the architectural gaps
surfaced by the epistemology v1 curriculum unit.

ADR-0060 closes that gap by weaving the first pack-resident topical
lemma from the utterance into a fixed-template extension:

  correction received — pack-grounded ({pack_id}):
  {correction_domains}. Noted topic: {lemma} ({lemma_domains}).
  No prior turn in this session to correct yet.

Selection rule (deterministic, left-to-right token order):
  - skip stopwords: `correction`, `correct`, `be`, `have`
  - pick first pack-resident lemma
  - if none found → ADR-0053 topic-less template byte-identically

Trust-boundary invariants preserved:
  - Every visible non-template token is still lemma / pack-domain / template
  - Deterministic: same text → same bytes
  - Backward compatible: existing 15 ADR-0053 tests pass byte-identically
  - "No prior turn in this session to correct yet." trust label kept

Cognition lane lift:
  public  : intent 100% / surface 100% / term 91.7% / versor 100%   (unchanged)
  holdout : intent 100% / surface 94.7% / term 75.0%→79.2% / versor 100%

The +4.2pp matches the single-case fix exactly (correction_truth_040).
Remaining 3 holdout misses (procedure_define_010, unknown_spirit_041,
unknown_word_018) are out of scope for this ADR.

- chat/pack_grounding.py — `_extract_correction_topic_lemma` helper +
  optional `text` parameter on `pack_grounded_correction_surface`.
- chat/runtime.py — single-line call-site change to pass `text` through.
- tests/test_correction_topic_lemma.py — 14 new tests pin:
  extraction (first lemma / skips correction / skips fillers / None on
  empty / strips punctuation / case-insensitive); surface (contains
  corrected lemma / contains topic domains / degrades to ADR-0053
  byte-identically / preserves trust label / deterministic / correct
  pack_id); end-to-end (correction_truth_040 emits 'truth' / no-pack-
  lemma still grounds).

Why text-level extraction, not intent.subject:
  `intent.subject` after ADR-0049 head-noun extraction returns
  ", truth requires evidence" for the test prompt — the CORRECTION
  intent's subject-extractor preserves the post-marker tail.  Parsing
  the raw text at the surface layer is cleaner; isolates the fix;
  doesn't perturb upstream classification logic.

Lanes (regression): smoke 67 / cognition 121 / teaching 17 /
correction tests 29 (15 ADR-0053 backward-compat + 14 ADR-0060 new) —
all green.

The non-negotiable field invariant (versor_condition < 1e-6) is
unaffected: this ADR changes surface composition only.
2026-05-18 14:14:27 -07:00

7.6 KiB

ADR-0060 — CORRECTION Acknowledgement Carries the Corrected-Topic Lemma

Status: Accepted Date: 2026-05-18 Author: Shay


Context

ADR-0053 introduced pack_grounded_correction_surface() — the cold-start CORRECTION acknowledgement. When a user begins a session with a meta-cognitive correction utterance ("No, that's wrong", "Actually, X means Y"), there is no prior turn to apply the correction to, so the runtime emits a deterministic pack-grounded surface stating that fact:

correction received — pack-grounded (en_core_cognition_v1):
cognition.correction; teaching.review; dialogue.repair.
No prior turn in this session to correct yet.

This surface was honest but topic-blind: a user who said "Actually, truth requires evidence" got a response that referenced correction but never truth. The holdout case correction_truth_040 expected ["truth"] in expected_terms and missed — contributing one of the 5 term-capture misses on the holdout split.

The first curriculum unit (epistemology v1, 2acf71f) closed one corpus-fixable miss (verification_wisdom_036). Three of the remaining four were architectural; correction_truth_040 was the cleanest to address.


Decision

Extend pack_grounded_correction_surface to accept an optional text: str | None argument. When supplied, the surface composer extracts the first pack-resident topical lemma from the utterance (left-to-right token order, excluding the meta-cognition lemma correction itself and dialogue fillers be / have) and weaves it into a fixed template:

correction received — pack-grounded ({pack_id}):
{correction_domains}. Noted topic: {lemma} ({lemma_domains}).
No prior turn in this session to correct yet.

When no topical lemma is found (or text is None), the surface degrades to the ADR-0053 topic-less template byte-identically.

Trust-boundary invariants preserved

  • Every visible non-template token is still either the lemma correction, the topical lemma, or a verbatim semantic_domains string from the ratified pack. No inference, no rewording.
  • Determinism. Same text → same surface bytes. The selector is left-to-right token order; no scoring, no NLP heuristic, no LLM.
  • Backward compatibility. pack_grounded_correction_surface() with no argument returns the ADR-0053 template byte-identically. Existing 15 tests in tests/test_pack_grounded_correction.py continue to pass.
  • The "No prior turn in this session to correct yet." trust-boundary label — distinguishing this cold-start surface from the post-correction teaching-repair path (teaching/correction.py) — is preserved in both variants.

Stopword selection

Two stopword classes are excluded from topic-lemma selection:

  1. The meta-cognition lemma itself (correction, correct) — echoing it as the topic would be circular; it's already the subject of the acknowledgement template.
  2. Dialogue fillers (be, have) — pack-resident lemmas that classify but carry no topical signal in a correction utterance.

This stopword set is deliberately tiny. Expanding it requires an amendment to this ADR — pack-resident lemmas that survive both the "have semantic_domains" gate and the natural-language flow of a correction utterance are real topic candidates by default.

Token normalization

Tokens are lowercased and stripped of attached punctuation (,.;:!?"'()[]{}) before pack-lookup. This handles common surface forms like "truth.", 'truth', truth, without requiring a full tokenizer.


Why text-level extraction, not intent.subject

intent.subject after ADR-0049 head-noun extraction returns ", truth requires evidence" for the prompt "Actually, truth requires evidence" — the correction intent's subject extractor preserves the post-marker tail rather than extracting a single head noun. Using intent.subject would require either:

  1. Extending ADR-0049's head-noun normalization to the CORRECTION intent — substantial change to upstream classification logic.
  2. Re-parsing intent.subject at the surface composer — equivalent work to parsing the raw text.

Parsing the raw text at the surface layer is cleaner: it isolates the fix, doesn't perturb upstream classification, and lets the correction acknowledgement own its own topic-extraction policy.


Verification

tests/test_correction_topic_lemma.py        14 passed
  - extraction: first lemma / skips correction / skips fillers /
    None on empty / strips punctuation / case-insensitive
  - surface: contains corrected lemma / contains topic domains /
    degrades to ADR-0053 / preserves trust label / deterministic /
    correct pack_id
  - end-to-end: correction_truth_040 emits 'truth' / no-pack-lemma
    still grounds

tests/test_pack_grounded_correction.py      15 passed   (ADR-0053; backward compat)

Lanes (regression check):
  core test --suite smoke           67 passed
  core test --suite cognition      121 passed
  core test --suite teaching        17 passed

Cognition lane lift

Split Metric Pre-ADR-0060 Post-ADR-0060
public intent / surface / term / versor 100 / 100 / 91.7 / 100 100 / 100 / 91.7 / 100 (unchanged)
holdout intent / surface / term / versor 100 / 94.7 / 75.0 / 100 100 / 94.7 / 79.2 / 100 (+4.2pp term_capture)

The +4.2pp matches the single-case fix: correction_truth_040 now captures truth. The remaining three holdout misses (procedure_define_010, unknown_spirit_041, unknown_word_018) are out of scope for this ADR.


Consequences

What changes

  • chat/pack_grounding.py_extract_correction_topic_lemma helper and an optional text parameter on pack_grounded_correction_surface.
  • chat/runtime.py — call site passes text through. Single line.

What does not change

  • ADR-0053's contract for the no-text path: byte-identical surface.
  • Refusal priority: a SafetyVerdict violation still pre-empts the acknowledgement (per ADR-0036).
  • The pack-grounded discipline: zero LLM-generated tokens in the surface; every visible word is either lemma, pack-domain, or fixed template constant.
  • versor_condition(F) < 1e-6 invariant: untouched — this ADR only changes surface composition.

Scope limits

  • Single topical lemma per surface. A correction utterance containing multiple pack-resident lemmas ("Actually, truth requires evidence") currently surfaces only the first (truth). Extending to "Noted topics: truth, evidence" is a reasonable follow-up but expands the trust surface (more tokens, more template branches) and was deferred.
  • English path only. The stopword set and tokenizer are English-specific. Non-English correction utterances will not match pack lemmas and will degrade to the topic-less template — no behaviour change vs ADR-0053.
  • No subject-claim propagation to corpus. The corrected claim (truth requires evidence) is acknowledged as a topic, not as a candidate teaching chain. Promoting corrections into the corpus is the teaching/correction.py repair path, which engages only once a prior turn exists.

Cross-References

  • ADR-0053 — the CORRECTION acknowledgement contract this ADR extends.
  • ADR-0049 — the head-noun extraction whose contract this ADR consciously did not extend.
  • Curriculum: epistemology v1 — the first unit that surfaced this gap as one of the architectural misses.