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
237 lines
8.6 KiB
Markdown
237 lines
8.6 KiB
Markdown
# 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 1–3 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.
|