core/docs/adr/ADR-0064-cross-pack-teaching-chains.md
Shay 310aed9ff0
chore: Refactor CLI and Governance Anchors (#926)
* docs: consolidate governance anchors and clean up test registries

* refactor(cli): decompose cli into dedicated modules

* test: fix broken test baselines and formatting

* docs: add domain boundary READMEs for governance anchors

* test: update baseline for determination lane

* test: fix capability_pass expectation

* test: fix CORE_SHOWCASE_SKIP_BUDGET enforcement

* chore: cleanup CLI extraction and unreachable code
2026-07-03 12:34:56 -07:00

8.2 KiB

ADR-0064 — Cross-pack teaching chains

Status: Accepted Date: 2026-05-18 Author: Shay Supersedes: none (extends ADR-0052 / ADR-0062 / ADR-0063) Phase: Plan Phase 1 (corpus flywheel)


Context

ADR-0052 introduced reviewed teaching chains as a third grounding source alongside vault and pack-grounded surfaces. The corpus (teaching/cognition_chains/cognition_chains_v1.jsonl) is reviewed, immutable, append-only memory. ADR-0063 brought en_core_relations_v1 (kinship pack) onto the live runtime, but the teaching-grounded surface composer was still hardcoded to the cognition corpus:

# chat/teaching_grounding.py — pre-ADR-0064
TEACHING_CORPUS_ID: str = "cognition_chains_v1"
_CORPUS_PATH = .../cognition_chains/cognition_chains_v1.jsonl

Every cold-start CAUSE/VERIFICATION prompt on a kinship lemma fell through to the universal disclosure even though the relations pack was mounted, because no kinship chain corpus existed and no path existed to register one.

ADR-0064 closes that gap architecturally. ADR-0063 was the resolver unlock at the pack layer; ADR-0064 is the same unlock at the teaching corpus layer.


Decision

1. Teaching corpus registry

A new dataclass + constant in chat/teaching_grounding.py:

@dataclass(frozen=True, slots=True)
class TeachingCorpusSpec:
    corpus_id: str
    path: Path
    pack_id: str

TEACHING_CORPORA: tuple[TeachingCorpusSpec, ...] = (
    TeachingCorpusSpec("cognition_chains_v1", .../cognition_chains_v1.jsonl, "en_core_cognition_v1"),
    TeachingCorpusSpec("relations_chains_v1", .../relations_chains_v1.jsonl, "en_core_relations_v1"),
)

Each corpus is 1:1-bound to exactly one lexicon pack. The 1:1 binding is the structural invariant that prevents cross-domain leakage during cold-start surface composition: a relations chain cannot accidentally surface a cognition-pack atom (or vice versa) because pack-residency at load time is scoped to the corpus's declared pack. Cross-domain chain shapes are out of scope for v1 per docs/teaching_order.md §5.

2. Aggregated chain index

_all_chains_index()lru_cached aggregator that loads every registered corpus via _load_corpus(spec) and unions them into a single {(subject, intent): TeachingChain} view. Registration order is the resolution order; cognition is registered first so cognition- lane byte-identity is preserved on any future cross-corpus collision.

TeachingChain gains a corpus_id field so the surface tag and audit trail are unambiguous when multiple corpora are active.

3. Surface composers consult the aggregated view

teaching_grounded_surface and teaching_grounded_surface_composed now call _all_chains_index(). The surface tag follows the chain's resolving corpus_id:

parent  → teaching-grounded (relations_chains_v1)
light   → teaching-grounded (cognition_chains_v1)

Cognition-lane surfaces remain byte-identical; relations-pack lemmas now ground on the live path.

4. Discovery gate updated for cross-corpus residency

teaching/discovery.py previously gated on (lemma in cognition_pack) AND ((lemma, intent) not in cognition_corpus). Updated to:

from chat.pack_resolver import is_resolvable
from chat.teaching_grounding import _all_chains_index

if not is_resolvable(lemma):           # any mounted pack
    return ()
if (lemma, intent_name) in _all_chains_index():   # any registered corpus
    return ()

A kinship CAUSE prompt that lacks a relations chain is now correctly flagged as a discovery candidate, instead of being suppressed because the cognition pack doesn't carry the lemma.

5. Replay-equivalence gate registers the swap

teaching/replay.py's _swap_corpus_path was extended to also rewrite the registry entry's path for the swapped corpus AND invalidate _all_chains_index cache, so surface composers re-read the swapped corpus during the gate's transient phase. The active corpus on disk remains byte-identical to its pre-swap state — the replay invariant is preserved.

6. Back-compat

_corpus_index(), _CORPUS_PATH, TEACHING_CORPUS_ID retain cognition-corpus-specific semantics for consumers whose scope is explicitly the cognition corpus (audit, replay's cognition-public runner, replay state-tracking). They are not removed. The aggregated _all_chains_index is the new abstraction for surface composers and the discovery gate.

A new helper clear_teaching_caches() drops every teaching-related lru_cache atomically — replaces ad-hoc _corpus_index.cache_clear() calls in replay code paths.


Consequences

Capability unlocked

Path Cognition lemmas Kinship lemmas
teaching_grounded_surface(CAUSE/VERIFICATION) byte-identical now grounds for cells in relations_chains_v1
teaching_grounded_surface_composed byte-identical composes within relations corpus when chain-of-chains exists
Discovery gate byte-identical now emits candidates for kinship cells absent from the relations corpus

Cognition lane: byte-identical

Cognition lemmas resolve to the cognition corpus first; the orthogonal-pack invariant prevents any (subject, intent) collision between corpora. Public/holdout eval baselines unchanged:

public:  intent 100% / surface 100% / term 91.7% / closure 100%
holdout: intent 100% / surface 100% / term 83.3% / closure 100%

Live verification

$ core chat
> Why does parent exist?
parent — teaching-grounded (relations_chains_v1): kinship.ascendant.direct;
kinship.parent. parent precedes child (kinship.descendant.direct).
No session evidence yet.
grounding_source = teaching

Future ADRs unlocked

  1. Cross-domain triples. Once relations corpus saturates internally, a follow-up ADR can extend the chain shape to allow subject and object in different packs (e.g. family grounds identity — family ∈ relations, identity ∈ cognition).
  2. Relations eval lane. Mirror the cognition lane harness with relations-domain holdout cases. The seed corpus is the ground truth.
  3. Audit + supersede for the relations corpus. teaching audit and teaching supersede are cognition-corpus-only today; the registry layer makes generalizing them mechanical.

Trust boundaries

  • Each corpus is 1:1-bound to one lexicon pack. The binding is declared statically in TEACHING_CORPORA — runtime cannot introduce a new corpus or rebind a pack.
  • Chain loading is read-only over immutable, reviewed, append-only files. Same trust boundary as ADR-0052.
  • Surface tag tokens emitted are corpus_id strings declared in TEACHING_CORPORA — never derived from user input.
  • The replay-gate swap rewrites the registry tuple in-place for the duration of the gate's transient phase and restores it on exit. Side-effect-free from outside the contextmanager.

Files changed

chat/teaching_grounding.py                                      registry layer
teaching/discovery.py                                           cross-corpus gate
teaching/replay.py                                              swap-the-registry
teaching/relations_chains/relations_chains_v1.jsonl             NEW (seed corpus, 7 chains)
tests/test_relations_chains_v1.py                               NEW (17 tests)
docs/adr/ADR-0064-cross-pack-teaching-chains.md           NEW (this file)
docs/adr/README.md                                        index entry
docs/curriculum/relations_chains_v1.md                          NEW (curriculum unit doc)

Verification

tests/test_relations_chains_v1.py                  17 passed
tests/test_teaching_audit.py                       23 passed
tests/test_composed_surface.py                     11 passed
tests/test_teaching_grounding.py                   passed
tests/test_discovery_candidates.py                 24 passed

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

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