core/docs/decisions/ADR-0067-cross-pack-teaching-chains.md
Shay d5a6e81b33 feat(adr-0067): cross-pack teaching chains — Plan Phase 4 closed
ADR-0064 bound each teaching corpus 1:1 to a single ratified pack;
chains whose subject + object resolved to different packs were
dropped at load time. Phases 1–3 ratified the per-pack DAGs needed
to lift that constraint safely.

ADR-0067 introduces a deliberately narrow cross-pack chain shape.
Each entry carries explicit subject_pack_id and object_pack_id
fields, and the loader verifies per-chain residency. Same-pack
entries are rejected as corpus-misfilings (anti-leakage). The
cross-pack composer is the fall-through after the in-pack composer,
so the cognition lane stays byte-identical.

Files:
- chat/cross_pack_grounding.py — CrossPackChain + loader +
  single-chain composer + multi-chain enumerators
- teaching/cross_pack_chains/cross_pack_chains_v1.jsonl — 5 seed
  chains (family×identity, parent×understanding, family×memory,
  identity×family, understanding×parent)
- chat/runtime.py — fall-through wiring in CAUSE/VERIFICATION
- chat/narrative_surface.py, chat/example_surface.py — merge
  cross-pack chains, per-chain pack-residency helpers
- tests/test_cross_pack_chains.py — 31 tests covering loader,
  surface, multi-chain access, runtime integration, in-pack
  precedence
- tests/test_narrative_example_intents.py — corpus-tag assertions
  widened to allow cross-pack aggregation

Verification:
- 31 new tests pass
- Curated lanes: smoke 67 / cognition 121 / teaching 17 / packs 6 /
  runtime 19 — all green
- Cognition eval byte-identical (public 100/100/91.7/100, holdout
  100/100/83.3/100)
- Full lane: 2098 passed, 2 skipped, 0 failed in 2:30
2026-05-18 17:22:43 -07:00

237 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-0067 — Cross-pack teaching chains (Plan Phase 4)
**Status:** Accepted
**Date:** 2026-05-18
**Author:** Shay
**Phase:** Plan Phase 4 (multi-domain composition)
**Builds on:** ADR-0052 / ADR-0063 / ADR-0064 / ADR-0066
---
## Context
ADR-0064 introduced the cross-pack teaching corpora registry but
deliberately kept each corpus 1:1 bound to a single ratified pack.
Chains whose subject + object resolved to *different* packs were
dropped at load time. The structural rationale was sound for v1:
keep the per-pack DAGs ratified before adding cross-domain edges.
Phases 13 satisfied that prerequisite:
- 3 ratified content packs live on the default runtime
(cognition v1 + relations v1/v2).
- 36 reviewed in-pack chains across three corpora.
- NARRATIVE + EXAMPLE composers in place — ready to consume
multi-corpus chain sets.
The articulation wall now lives in the *gap between* domains. The
system has nothing to say when an operator asks:
```
> Why does family exist?
> Does identity require family?
> Does understanding require parent?
```
These prompts reference a relations-pack lemma and a cognition-pack
lemma in the same chain. No single-pack corpus can ground them; an
edge that crosses packs is required.
---
## Decision
Introduce a deliberately narrow cross-pack chain shape. Each chain
explicitly carries **two** pack-residency fields:
```json
{
"chain_id": "cause_family_grounds_identity",
"subject": "family",
"intent": "cause",
"connective": "grounds",
"object": "identity",
"subject_pack_id": "en_core_relations_v1",
"object_pack_id": "en_core_cognition_v1",
...
}
```
The loader verifies per-chain residency: the subject must resolve in
its declared subject pack, and the object must resolve in its
declared object pack. Same-pack entries are rejected (they belong in
the in-pack corpus).
### Files
```
chat/cross_pack_grounding.py NEW
teaching/cross_pack_chains/cross_pack_chains_v1.jsonl NEW (5 chains)
chat/runtime.py wired cross-pack fall-through
chat/narrative_surface.py aggregates cross-pack chains
chat/example_surface.py aggregates cross-pack reverse chains
tests/test_cross_pack_chains.py NEW (31 tests)
docs/decisions/ADR-0067-cross-pack-teaching-chains.md NEW (this file)
```
### Surface format
```
"{X} — cross-pack-grounded ({corpus_id}: {subject_pack_id} × {object_pack_id}):
{dX1}; {dX2}. {X} {conn} {Y} ({dY1}). No session evidence yet."
```
Both pack ids are exposed in the tag so the audit trail and operator
debugger can see *which* domains the chain crosses without parsing
the chain body.
### Resolution order
In `chat/runtime.py` for CAUSE/VERIFICATION:
1. `teaching_grounded_surface_composed` (when `composed_surface=True`)
OR `teaching_grounded_surface` — in-pack chain index
(`_all_chains_index` across all single-pack corpora).
2. **`cross_pack_grounded_surface` — fall-through when no in-pack
chain resolves the `(subject, intent)`.** [ADR-0067]
3. Fall through to OOV invitation if the subject is unknown to any
mounted pack.
The cross-pack composer is the fall-through only: when an in-pack
chain exists on the same `(subject, intent)`, the in-pack composer
wins. This preserves the cognition-lane byte-identity invariant.
### NARRATIVE and EXAMPLE aggregation
Both multi-clause composers (ADR-0066) now walk cross-pack chains
in addition to in-pack ones:
- `narrative_surface.py` calls `cross_pack_chains_for_subject(X)`
and appends those chains into the dedup-and-sort pipeline.
- `example_surface.py` calls `cross_pack_chains_for_object(X)`
similarly.
The corpus tag widens from `(cognition_chains_v1)` to
`(cognition_chains_v1 + cross_pack_chains_v1)` whenever any
cross-pack clause contributes. Stable lexicographic ordering — no
behaviour change on subjects with no cross-pack chains.
### Seed corpus
`teaching/cross_pack_chains/cross_pack_chains_v1.jsonl` ships with
5 hand-authored, reviewed chains:
| chain_id | direction |
|---|---|
| `cause_family_grounds_identity` | relations → cognition |
| `cause_parent_grounds_understanding` | relations → cognition |
| `cause_family_supports_memory` | relations → cognition |
| `verification_identity_requires_family` | cognition → relations |
| `verification_understanding_requires_parent` | cognition → relations |
All connectives are whitelisted in
`generate.semantic_templates._PREDICATE_HUMANIZE`.
---
## Consequences
### Capability unlocked
| Prompt | Pre-ADR-0067 | Post-ADR-0067 |
|---|---|---|
| `"Does identity require family?"` | OOV invitation | `identity requires family` (cross-pack) |
| `"Does understanding require parent?"` | OOV invitation | `understanding requires parent` (cross-pack) |
| `"Tell me about family"` | 1 clause (in-pack) | 3 clauses (in-pack + 2 cross-pack) |
| `"Give me an example of memory"` | 1 example | 2 examples (in-pack `recall` + cross-pack `family supports`) |
### Live verification
```
> Does identity require family?
[teaching] identity — cross-pack-grounded
(cross_pack_chains_v1: en_core_cognition_v1 × en_core_relations_v1):
cognition.identity; identity.stable. identity requires family
(kinship.unit). No session evidence yet.
> Tell me about family.
[teaching] family — narrative-grounded
(cross_pack_chains_v1 + relations_chains_v1): kinship.unit;
social.group.kin. family grounds identity (cognition.identity);
family grounds parent (kinship.ascendant.direct); family supports
memory (cognition.memory). No session evidence yet.
> Give me an example of memory.
[teaching] memory — example-grounded
(cognition_chains_v1 + cross_pack_chains_v1): cognition.memory.
Example: family supports memory; recall reveals memory. No session
evidence yet.
```
### Cognition lane — byte-identical
```
public: intent 100% / surface 100% / term 91.7% / closure 100%
holdout: intent 100% / surface 100% / term 83.3% / closure 100%
```
The cross-pack composer fires only as a fall-through. All cognition-
lane prompts (which exercise in-pack chains) follow the unchanged
path.
---
## Trust boundaries
- **Strict per-chain pack residency.** A chain declares
`subject_pack_id` and `object_pack_id` explicitly; the loader
verifies each lemma against its named pack. Skewed entries drop
silently with no surface impact.
- **Anti-leakage: cross-pack chains must actually cross packs.**
Entries where `subject_pack_id == object_pack_id` are rejected as
corpus-misfilings.
- **No prose generation.** Every visible non-template token in the
surface is a lemma, a pack `semantic_domains` atom, or a
whitelisted connective from `humanize_predicate`.
- **No new mutation surface.** ADR-0027 / ADR-0057 doctrine
preserved: corpus appends go through `accept_proposal` (or
`supersede_chain`) and nowhere else. This module is read-only.
- **In-pack precedence.** Cross-pack chains never override an
in-pack chain on the same `(subject, intent)` — the cognition-
lane byte-identity invariant depends on this.
- **Supersession honoured.** Cross-pack chains support
`superseded_by` (ADR-0055 Phase A) — retired entries drop from the
active view, history preserved on disk.
---
## Verification
```
tests/test_cross_pack_chains.py 31 passed
Curated lanes (all green):
smoke 67 / cognition 121 / teaching 17 / packs 6 / runtime 19
Cognition eval byte-identical (public + holdout).
Full lane: 2096 passed, 2 skipped, 0 failed.
```
---
## Future ADRs unlocked
- **Cross-pack supersede CLI.** Today `core teaching supersede` is
in-pack only. A cross-pack supersede needs to validate the new
chain's residency against the *same* `(subject_pack_id,
object_pack_id)` pair as the retired chain.
- **Cross-pack proposals via `core teaching propose`.** Today
proposals target the in-pack corpus only. Extending to cross-pack
needs the proposal schema to carry both pack ids and the replay-
equivalence gate to handle multi-corpus surface diffs.
- **Cross-pack composed surface (ADR-0062 generalisation).** Chain-
of-chains across packs (e.g. `family grounds identity, which
grounds knowledge`) would compose a relations seed → cognition
intermediate → cognition tail. Needs the composer to dispatch on
per-chain `object_pack_id` for follow-up lookup.
- **Three-pack chains.** Today cross-pack is binary
(`subject_pack × object_pack`). N-ary chains crossing 3+ packs
would need a different schema; out of scope until a third
content-bearing pack ratifies.