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).
9.9 KiB
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_v1with curatedsemantic_domains:memory→("cognition.memory", "memory.semantic", ...)recall→("operation.recall", "cognition.memory", ...)
_maybe_pack_grounded_surfacewas 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
aandbare non-empty strings, - both
aandbare pack lemmas (withsemantic_domains), a ≠ bafter 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.subjectandintent.secondary_subjectare 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_domainsstring. 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.
UnknownDomainGatesemantics 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.pygainspack_grounded_comparison_surface(lemma_a, lemma_b) -> str | None.chat/runtime.py:_maybe_pack_grounded_surfacegains a COMPARISON branch that runs before the DEFINITION / RECALL check.- One new case lifts in the cognition eval.
What does not change
_UNKNOWN_DOMAIN_SURFACEconstant retained; non-pack-lemma, non-English, non-DEFINITION/RECALL/COMPARISON paths still return the universal disclosure unchanged.UnknownDomainGatesemantics unchanged.ChatResponse.grounding_source/TurnEvent.grounding_sourceenum 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-6invariant 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 ingenerate/intent.pyand is out of scope here. - Connective is fixed to
"contrasts with"— matching thecontrasts_withpredicate 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-0018 —
DialogueIntentand 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_sourcetag. - 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_REcapture). - 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.