core/docs/decisions/ADR-0063-cross-pack-surface-resolver.md
Shay 9f83b27a7c feat(adr-0063): cross-pack surface resolver — kinship lemmas ground on live path
ADR-0063 closes the ADR-0048/0050/0053/0061 hardcoded-cognition-pack
asymmetry. New chat/pack_resolver.py provides resolve_lemma(lemma,
pack_ids) → (resolving_pack_id, semantic_domains) across an ordered
tuple of mounted lexicon packs (first-match-wins, lru_cache per-pack).

Surface composers in chat/pack_grounding.py now consult the resolver
instead of a hardcoded en_core_cognition_v1. en_core_relations_v1
joins RuntimeConfig.input_packs defaults; kinship lemmas now ground
on the live path:

  > What is a parent?
  parent — pack-grounded (en_core_relations_v1):
  kinship.ascendant.direct; kinship.parent; biology.progenitor.
  No session evidence yet.

Cross-pack comparison (knowledge × parent) renders composite tag
(en_core_cognition_v1 × en_core_relations_v1). Cognition lane
remains byte-identical: cognition is resolved first and the surface
format for cognition lemmas is unchanged.

Cognition eval (byte-identical to pre-ADR baseline):
  public  → intent 100% / surface 100% / term 91.7% / closure 100%
  holdout → intent 100% / surface 100% / term 83.3% / closure 100%

Curated lanes green: smoke 67 / cognition 121 / teaching 17 /
packs 6 / runtime 19 / algebra 132.

New tests: test_pack_resolver.py (28) + test_cross_pack_grounding.py
(17). test_en_core_relations_v1_pack.py: default-input-packs guard
inverted. test_pack_grounding.py: two stale ADR-0048 tests rewritten
(premises invalidated by ADR-0052/0061; now use fully-out-of-pack
prompts).

chat/teaching_grounding.py UNCHANGED — cognition_chains_v1 corpus
stays cognition-only. Cross-pack teaching corpora are the natural
ADR-0064.
2026-05-18 15:00:58 -07:00

9.9 KiB
Raw Permalink Blame History

ADR-0063 — Cross-pack surface resolver

Status: Accepted Date: 2026-05-18 Author: Shay Supersedes: none (extends ADR-0048 / ADR-0050 / ADR-0052 / ADR-0053 / ADR-0060 / ADR-0061 / ADR-0062) Builds on: the kinship starter pack en_core_relations_v1 (commit f0c57eb).


Context

ADR-0048 introduced the first pack-grounded surface composer — a deterministic cold-start surface for DEFINITION / RECALL intents whose subject lemma is present in the ratified cognition pack. ADR-0050 extended that to COMPARISON. ADR-0053 + ADR-0060 added a CORRECTION acknowledgement composer (cold-start branch). ADR-0061 added a PROCEDURE composer. All four composers shared a hardcoded reference to a single lexicon pack:

# chat/pack_grounding.py — pre-ADR-0063
PACK_ID: str = "en_core_cognition_v1"

That asymmetry was the rate-limiter on domain expansion. Mounting the en_core_relations_v1 kinship starter pack (already ratified — checksum 1a013b46…) would silently widen the runtime's mounted manifold (vault recall + intent classification) without a corresponding surface composer for kinship lemmas. The cold-start What is a parent? prompt would fall through to the universal "insufficient grounding" disclosure even though parent is in a ratified, immutable pack on disk.

The previous ADR's memory recorded this explicitly:

Engagement status: OPT-IN ONLY. Pack is NOT in RuntimeConfig.input_packs defaults… until cross-pack teaching-grounded composition exists, mounting silently exposes lemmas to vault recall and intent classification without a corresponding ratified surface composer.

ADR-0063 is the one-PR architectural unlock that closes that gap: the surface composers consult an abstract resolver across all mounted lexicon packs instead of one hardcoded pack, and the relations pack joins the default mount.


Decision

1. New abstraction: chat/pack_resolver.py

A small (~140 lines) pure module that maps a lemma to a (resolving_pack_id, semantic_domains) pair across an ordered tuple of mounted lexicon packs:

DEFAULT_RESOLVABLE_PACK_IDS: tuple[str, ...] = (
    "en_core_cognition_v1",
    "en_core_relations_v1",
)

def resolve_lemma(
    lemma: str,
    pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
) -> tuple[str, tuple[str, ...]] | None: ...

def is_resolvable(lemma, pack_ids=...) -> bool: ...
def mounted_lemmas(pack_ids=...) -> frozenset[str]: ...

Properties:

  • First-match-wins. Order matters: cognition is listed first so that any future cross-pack collision resolves cognition-side, preserving cognition-lane byte-identity.
  • lru_cache per-pack. Each pack's lexicon.jsonl is loaded at most once per process (ratified packs are immutable).
  • No mutation. Returns tuples, frozensets, plain strings.
  • Pure. No I/O outside the cached read; no network; no LLM; no approximation.

2. Surface composers consult the resolver

chat/pack_grounding.py:

  • pack_grounded_surface(lemma) — surface tag follows the resolving pack id. Cognition lemmas keep emitting pack-grounded (en_core_cognition_v1) byte-identically; kinship lemmas emit pack-grounded (en_core_relations_v1).
  • pack_grounded_comparison_surface(a, b) — each side resolved independently. When both resolve to the same pack, the tag stays single (pack-grounded (en_core_cognition_v1)). When the two resolve to different packs, the tag becomes composite (pack-grounded (en_core_cognition_v1 × en_core_relations_v1)).
  • pack_grounded_correction_surface(text) — anchor pack stays cognition (correction is a cognition lemma); topic domains come from whichever pack resolves the topic lemma. Topic extraction scans mounted_lemmas() instead of the cognition-only index.
  • pack_grounded_procedure_surface(subject_text) — topic extraction scans mounted_lemmas(); resolving pack id drives the surface tag.

chat/teaching_grounding.py is unchanged in this ADR. The reviewed teaching corpus (cognition_chains_v1.jsonl) still references cognition-pack lemmas only. Cross-pack teaching chains (e.g. a relations_chains_v1.jsonl) are the natural next ADR.

3. Backward compatibility

  • chat/pack_grounding.py:PACK_ID = "en_core_cognition_v1" and _pack_index() (cognition-only) are retained for back-compat with consumers in teaching/contemplation.py, teaching/discovery.py, chat/teaching_grounding.py, and various tests whose semantics are scoped to the cognition pack. They are NOT removed — only the composers' internal lookups switch to the resolver.
  • is_pack_lemma(lemma) keeps its cognition-only semantics; cross-pack residency is chat.pack_resolver.is_resolvable.

4. Default mount adds the relations pack

core/config.py:

input_packs: tuple[str, ...] = (
    "en_minimal_v1",
    "en_core_cognition_v1",
    "en_core_relations_v1",   # added in ADR-0063
    "he_logos_micro_v1",
    "grc_logos_micro_v1",
)

Mounting is now safe because the composers ground kinship lemmas deterministically.

5. The relations-pack default-input test inverts

tests/test_en_core_relations_v1_pack.py:

  • Before: test_pack_is_not_in_default_input_packs
  • After: test_pack_is_in_default_input_packs

The earlier test was an explicit guard against premature mounting. It guarded the asymmetry that ADR-0063 closes; its inverse is now the correct invariant.


Consequences

Capability unlocked

Surface composer Cognition lemmas Kinship lemmas
pack_grounded_surface (DEFINITION / RECALL) byte-identical now grounds
pack_grounded_comparison_surface (COMPARISON) byte-identical when both cognition now grounds kinship × kinship; composite tag for cross-pack
pack_grounded_correction_surface (CORRECTION) byte-identical when topic ∈ cognition now anchors topic on kinship lemmas
pack_grounded_procedure_surface (PROCEDURE) byte-identical when topic ∈ cognition now grounds kinship topics

Cognition lane: byte-identical

The cognition lane's prompts use only cognition lemmas. The resolver picks cognition first, the surface tag stays en_core_cognition_v1, the surface bytes are identical:

core eval cognition                      → 100/100/91.7/100 (public)
core eval cognition --split holdout      → 100/100/83.3/100 (holdout)

Both byte-identical to the pre-ADR baseline.

Live-path grounding on kinship lemmas

$ core chat
> What is a parent?
parent — pack-grounded (en_core_relations_v1): kinship.ascendant.direct;
kinship.parent; biology.progenitor. No session evidence yet.
grounding_source = pack

Future ADRs unlocked

  1. ADR-0064 — Cross-pack teaching chains. Add relations_chains_v1.jsonl (reviewed kinship chains) and extend chat/teaching_grounding.py to consult both corpora. The natural next ADR.
  2. Pronoun + role-filler v2. mother, father, son, etc. as specializations of the v1 kinship primitives — extends en_core_relations_v1 itself.
  3. Cross-domain triples. family causes belonging, parent grounds identity — opens after cognition + relations both have ratified internal DAGs of reviewed chains.

Trust boundaries

  • The resolver does NOT execute pack metadata, validators, or scripts. It reads lexicon.jsonl only.
  • A pack that fails to load produces an empty index — callers see None. No exception leaks into the runtime.
  • No mutation site introduced. The resolver is read-only over immutable, ratified pack data.
  • Tag tokens emitted in surfaces are always one of: en_core_cognition_v1, en_core_relations_v1, or the composite {a} × {b} form derived from mounted-pack ids — never from user input.
  • pack_resolver.clear_resolver_cache() is a test-only escape hatch; production paths never call it.

Verification

tests/test_pack_resolver.py                    28 passed
tests/test_cross_pack_grounding.py             17 passed
tests/test_pack_grounding.py                   13 passed
tests/test_pack_grounded_comparison.py         passed
tests/test_pack_grounded_correction.py         passed
tests/test_correction_topic_lemma.py           passed
tests/test_procedure_surface.py                passed
tests/test_teaching_grounding.py               passed
tests/test_composed_surface.py                 11 passed
tests/test_en_core_relations_v1_pack.py        6 passed  (inverted)

Lanes:
  core test --suite smoke         67 passed
  core test --suite cognition    121 passed
  core test --suite teaching      17 passed
  core test --suite packs          6 passed
  core test --suite runtime       19 passed
  core test --suite algebra      132 passed
  core test --suite full         <pending — see PR>

Cognition eval (byte-identical to pre-ADR baseline):
  public  split → intent 100% / surface 100% / term 91.7% / closure 100%
  holdout split → intent 100% / surface 100% / term 83.3% / closure 100%

The non-negotiable field invariant versor_condition(F) < 1e-6 is unaffected — this ADR is a routing/dispatch change over immutable pack data; no algebra, no field operators, no normalization sites were touched.


Files changed

chat/pack_resolver.py                                NEW (~140 lines)
chat/pack_grounding.py                               composers use resolver
core/config.py                                       relations pack joins defaults
tests/test_pack_resolver.py                          NEW (28 tests)
tests/test_cross_pack_grounding.py                   NEW (17 tests)
tests/test_pack_grounding.py                         2 stale tests rewritten
tests/test_en_core_relations_v1_pack.py              default-input test inverted
docs/decisions/ADR-0063-cross-pack-surface-resolver.md   NEW (this file)
docs/decisions/README.md                             ADR-0063 index entry

Lines of net change: small. The architectural unlock is in chat/pack_resolver.py; the rest of the diff is wiring + tests.