core/docs/decisions/ADR-0050-pack-grounded-comparison.md
Shay ecd580479a feat(adr-0050): pack-grounded COMPARISON surface
Sibling to ADR-0048's DEFINITION/RECALL pack-grounded surface for
the COMPARISON intent.  `pack_grounded_comparison_surface(a, b)` in
`chat/pack_grounding.py` composes a deterministic side-by-side
surface from both lemmas' pack `semantic_domains`, joined by the
fixed connective "contrasts with":

  "{a} (d_a1; d_a2) contrasts with {b} (d_b1; d_b2) — pack-grounded
   ({pack_id}). No session evidence yet."

`chat/runtime.py:_maybe_pack_grounded_surface` gains a COMPARISON
branch that runs before the DEFINITION/RECALL check.  Engages only
when both `intent.subject` and `intent.secondary_subject` are pack
lemmas and differ (identical-lemma comparison defers to disclosure).
Order-sensitive by design — matches the graph-layer's directional
CONTRAST edge.

Cognition eval (13-case public split):
  surface_groundedness  61.5% → 69.2%  (+7.7 pp)
  term_capture_rate     50.0% → 58.3%  (+8.3 pp)
  intent_accuracy            100.0%        (=)
  versor_closure_rate        100.0%        (=)

Case lifted: comparison_memory_recall_030 ("Compare memory and
recall").  Remaining unlift cases (CAUSE×2, VERIFICATION×1,
CORRECTION×1) need teaching-store chains or operator-driven
inference — pack lookup cannot supply causal explanations,
verifications, or corrections without fabrication.

Tests: tests/test_pack_grounded_comparison.py (15 tests).
Lanes green: smoke (67), cognition (121), runtime (19), algebra
(132), teaching (17), packs (6).
2026-05-18 06:59:53 -07:00

9.9 KiB
Raw Blame History

ADR-0050 — Pack-Grounded Surface for Cold-Start COMPARISON

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


Context

ADR-0048 added a pack-grounded surface for cold-start DEFINITION / RECALL intents — a deterministic, verbatim composition of the cognition pack's semantic_domains for a single subject lemma. ADR-0049 then tightened intent classifier subject extraction so that prompts like "What is a procedure?" produce a clean lemma the pack can match.

The cognition lane's COMPARISON case (comparison_memory_recall_030"Compare memory and recall") still missed. Investigation:

  • Intent classifier correctly tags the prompt COMPARISON, subject="memory", secondary_subject="recall".
  • Both lemmas are in en_core_cognition_v1 with curated semantic_domains:
    • memory("cognition.memory", "memory.semantic", ...)
    • recall("operation.recall", "cognition.memory", ...)
  • _maybe_pack_grounded_surface was scoped to DEFINITION / RECALL only; COMPARISON fell through to the universal disclosure.

The structure is exactly the situation ADR-0048's pattern was designed for: two pack-known lemmas, no session evidence, deterministic surface compositable entirely from pack atoms. The doctrinally clean fix is a sibling pack-path branch for COMPARISON, with the same trust-boundary discipline.


Decision

Add a deterministic COMPARISON-shaped pack-grounded surface as a second branch of _maybe_pack_grounded_surface, identical guardrails to the DEFINITION / RECALL branch.

Surface format

{a} ({d_a1}; {d_a2}) contrasts with {b} ({d_b1}; {d_b2}) — pack-grounded ({pack_id}). No session evidence yet.

Up to two semantic_domains per side are emitted to keep the surface compact. Every visible non-template token is either one of the two lemmas or a verbatim pack semantic_domains string.

The connective "contrasts with" is a fixed-template constant — identical to the human-readable form of the contrasts_with relation predicate already produced by generate/semantic_templates.py:humanize_predicate("contrasts_with"). The COMPARISON intent's downstream graph node already uses this predicate (graph_planner.graph_from_intent builds a Relation.CONTRAST edge), so this ADR preserves the existing COMPARISON connective vocabulary rather than introducing a new one.

Engagement conditions

pack_grounded_comparison_surface(a, b) returns a non-None surface only when all hold:

  • both a and b are non-empty strings,
  • both a and b are pack lemmas (with semantic_domains),
  • a ≠ b after lowercasing.

_maybe_pack_grounded_surface invokes this path only when all hold:

  • gate fired with source="empty_vault" (cold-start session),
  • config.output_language == "en",
  • intent is COMPARISON,
  • both intent.subject and intent.secondary_subject are non-empty.

Any other condition returns None and the runtime falls through to the universal disclosure unchanged. Safety / ethics refusal still takes priority above this branch.

Identical-lemma defer

pack_grounded_comparison_surface("memory", "memory") returns None. A comparison between a term and itself carries no contrastive evidence; the universal disclosure is the correct surface in that case. Callers that want a single-lemma surface for "X" should use pack_grounded_surface("X") directly via the DEFINITION / RECALL path.

Order sensitivity

The COMPARISON surface is order-sensitive by design: compare(a, b) and compare(b, a) produce distinct surfaces because the "contrasts with" connective is directional. This matches the graph-layer behaviour where Relation.CONTRAST(a → b) and Relation.CONTRAST(b → a) are distinct edges.


Why this is doctrine-aligned

CLAUDE.md prohibits opaque LLM fallbacks, stochastic sampling, hidden normalisation, hot-path repair, and approximate recall. This ADR is:

  • Not opaque. Every visible atom is either a lemma supplied by the intent classifier or a verbatim pack semantic_domains string. Pack ID is named in the surface.
  • Not stochastic. Deterministic JSONL read, deterministic string composition; identical input produces byte-identical output (test_comparison_surface_is_deterministic).
  • Not hidden normalisation. The pack lookup is a separate source of grounding, not a normalisation step inside an existing operator. No versor, no manifold, no field state touched.
  • Not hot-path repair. UnknownDomainGate semantics are unchanged — the gate still fires; this ADR only broadens what the stub-path emits after the gate fires, in a narrow intent-typed branch.
  • Not approximate recall. Exact dictionary lookup on the pack lexicon by lemma. No metric, no neighbourhood, no threshold.

The fundamental architectural move is the same as ADR-0048: the cognition pack contributes a second source of grounding alongside the session vault, with provenance preserved end-to-end. This ADR extends that contribution from the DEFINITION / RECALL shape to the COMPARISON shape.


Characterisation — core eval cognition

A/B run on the 13-case public cognition split, identical RuntimeConfig except for the merge of this ADR (build on top of ADR-0049's article-stripping):

Metric Pre-ADR-0050 Post-ADR-0050 Δ
intent_accuracy 100.0 % 100.0 % 0
surface_groundedness 61.5 % 69.2 % +7.7 pp
term_capture_rate 50.0 % 58.3 % +8.3 pp
versor_closure_rate 100.0 % 100.0 % 0
versor_condition < 1e-6 preserved preserved invariant

The case that lifts is exactly comparison_memory_recall_030:

"Compare memory and recall"
  -> intent.tag = COMPARISON, subject="memory", secondary_subject="recall"
  -> both pack lemmas
  -> "memory (cognition.memory; memory.semantic) contrasts with
     recall (operation.recall; cognition.memory) — pack-grounded
     (en_core_cognition_v1). No session evidence yet."
  -> grounding_source = "pack"

The remaining unlift cases (CAUSE × 2, VERIFICATION × 1, CORRECTION × 1) need either teaching-store chains (ADR-0018) or operator-driven inference — pack lookup cannot supply causal explanations, verifications, or corrections without fabrication.


Consequences

What changes

  • chat/pack_grounding.py gains pack_grounded_comparison_surface(lemma_a, lemma_b) -> str | None.
  • chat/runtime.py:_maybe_pack_grounded_surface gains a COMPARISON branch that runs before the DEFINITION / RECALL check.
  • One new case lifts in the cognition eval.

What does not change

  • _UNKNOWN_DOMAIN_SURFACE constant retained; non-pack-lemma, non-English, non-DEFINITION/RECALL/COMPARISON paths still return the universal disclosure unchanged.
  • UnknownDomainGate semantics unchanged.
  • ChatResponse.grounding_source / TurnEvent.grounding_source enum unchanged — COMPARISON pack-grounded surfaces carry the same "pack" tag as single-lemma pack-grounded surfaces. Downstream audit consumers do not need a new value to filter on; they already distinguish via the surface contents if needed.
  • Safety / ethics refusal still takes priority above pack grounding.
  • versor_condition(F) < 1e-6 invariant unaffected (no algebra changes).

Scope limits

  • English only (en_core_cognition_v1). Same constraint as ADR-0048; multilingual extension is a separate ADR.
  • Exactly two operands. "Compare a, b, and c" is not yet supported by the COMPARISON regex in generate/intent.py and is out of scope here.
  • Connective is fixed to "contrasts with" — matching the contrasts_with predicate already in the relation vocabulary. Other comparison flavours (similar to, differs from, is like) are not modelled; they would need their own predicates in the pack.
  • Identical-lemma comparison defers to disclosure. Callers wanting a single-lemma surface should use the DEFINITION / RECALL path.

Cross-References

  • ADR-0018DialogueIntent and the COMPARISON regex this ADR consumes.
  • ADR-0048 — the DEFINITION / RECALL pack-grounded surface this ADR mirrors for COMPARISON; same trust-boundary discipline, same grounding_source tag.
  • ADR-0049 — clean subject extraction the COMPARISON path inherits (though COMPARISON uses its own named-group regex, not the post-processor; "memory" and "recall" are already clean from the _COMPARE_RE capture).
  • ADR-0029 / ADR-0033 — sibling-pack-grounded contributor pattern this ADR continues to extend from verdict surfaces to the answer surface.

Verification

tests/test_pack_grounded_comparison.py           — 15 tests, all green
tests/test_pack_grounding.py                     — 18 pre-existing tests still green
tests/test_intent_subject_extraction.py          — 30 pre-existing tests still green
tests/test_semantic_realizer_integration.py      — pre-existing tests still green

Lanes (all green on this branch):
  core test --suite smoke         67 passed
  core test --suite cognition    121 passed
  core test --suite runtime       19 passed
  core test --suite packs          6 passed

core eval cognition (pre → post):
  intent_accuracy        100.0% → 100.0%   (=)
  surface_groundedness    61.5% →  69.2%   (+7.7 pp)
  term_capture_rate       50.0% →  58.3%   (+8.3 pp)
  versor_closure_rate    100.0% → 100.0%   (=)

The non-negotiable field invariant (versor_condition(F) < 1e-6) is preserved: this ADR adds a surface-construction branch on the existing stub path — no algebra changes, no rotor construction, no field update.