# ADR-0143: Teaching-Derived Structural Recognition via Multi-Resolution Anti-Unification **Status:** Accepted **Date:** 2026-05-24 **Scope doc:** [teaching-derived-recognition-scope](./teaching-derived-recognition-scope.md) **Related:** ADR-0142 (epistemic state taxonomy), ADR-0144 (PropositionGraph — integration gate) --- ## Context CORE's recognition path currently uses hand-coded regex patterns that the engine neither derived nor can introspect. These patterns store finds rather than teaching finding. The thesis requires the engine's capacity to recognize proposition structure to emerge from reviewed teaching examples — not from hand-authored scaffolding. The recognition scope document (v2, 2026-05-24) evaluated four candidate mechanisms and selected **Mechanism D: multi-resolution anti-unification over token sequences** as the only option that is simultaneously deterministic, exact, structural, introspectable, and well-defined on token sequences. The scope committed the spike to a two-phase acceptance test. This ADR records the decision to implement Mechanism D and defines the output contract that all recognition work must conform to. ## Decision **Adopt multi-resolution anti-unification over token sequences as the recognition mechanism.** The recognizer is derived deterministically from a reviewed teaching example set. It operates at two resolutions: 1. **Chunk level** — anti-unify at noun-phrase / verb-phrase / quantifier-phrase chunks. If every chunk resolves with complete feature evidence, emit the bundle. 2. **Word-level fallback** — for any chunk that fails at chunk level, drop to word-by-word anti-unification on just that chunk and attempt to lift the relevant feature slot. At both resolutions, the recognizer refuses rather than guesses when evidence is absent or contradictory. Refusal is the primary signal for what to learn next. ## Output contract Every recognition output is a `RecognitionOutcome` (defined in `recognition/outcome.py`). The contract is frozen; implementers must not add alternative output types. ``` RecognitionOutcome: state: EVIDENCED | UNDETERMINED | CONTRADICTED | AMBIGUOUS proposition: FeatureBundle | None refusal_reason: ShapeRefusal | FeatureEvidenceRefusal | FeatureConsistencyRefusal | None provenance: RecognitionProvenance ``` **Invariants:** - `state == EVIDENCED` → `proposition` is a complete `FeatureBundle` with evidence on every feature; `refusal_reason` is `None`. - Any refusal state → `proposition` is `None`; `refusal_reason` is a typed instance naming exactly what is missing or contradictory. - `provenance` is always present. It carries `mechanism`, `teaching_set_id` (SHA-256 of the canonical example set), and `resolution_level`. **Epistemic states emitted.** Recognition produces only this subset of the ADR-0142 taxonomy: EVIDENCED (admitted), UNDETERMINED (shape refused), CONTRADICTED (feature contradiction), AMBIGUOUS (unresolvable ambiguity). VERIFIED and DECODED are downstream of substrate cross-reference work and are never emitted by the recognizer itself. ## Three-layer refusal | Layer | Class | Trigger | |---|---|---| | 1 — Shape | `ShapeRefusal` | Input does not match any derived pattern | | 2 — Feature evidence | `FeatureEvidenceRefusal` | Shape matched; a required feature has no evidence span | | 3 — Feature consistency | `FeatureConsistencyRefusal` | Two evidence spans contradict each other on the same feature | Every layer produces a deterministic, typed, introspectable refusal. The engine does not approximate or default — it points at exactly which substrate is missing. ## Feature bundle requirements Every `BoundFeature` in an admitted bundle carries: - `name`: the feature dimension (agent, relation, count, unit, polarity, modality, tense, intentionality, ...) - `value`: the typed feature value (str | int | float) - `evidence`: an `EvidenceSpan` (token indices + verbatim text) or a `NegativeEvidence` record (for features established by absence, e.g. `polarity=affirmative` from the absence of a negator) No silent defaults. If a feature cannot be evidenced, the recognizer refuses at Layer 2. ## Determinism requirements - `derive_recognizer(examples)` → byte-identical `DerivedRecognizer` on the same input across runs. - `recognize(recognizer, tokens)` → byte-identical `RecognitionOutcome` on the same recognizer and input across runs. - `DerivedRecognizer` must be serializable to/from JSON for replay. - `teaching_set_id` is SHA-256 of the sorted canonical example token sequences; it must be byte-identical across runs on the same examples. ## Acceptance test (two-phase spike) ### Phase 1 — Mechanism on uniform examples Four teaching examples (all `has`-relation, all affirmative, all actual- modality — see scope doc). Derived recognizer must: 1. Admit `"A baker has 24 loaves"` with full feature bundle and evidence spans. 2. Refuse `"John gave 5 apples to Mary"` with `ShapeRefusal` (Layer 1). 3. Produce byte-identical output on both cases across two runs. 4. Every feature in the admitted bundle has non-None evidence. Phase 1 pass → Phase 2 is warranted. Phase 1 fail → mechanism is wrong. ### Phase 2 — Variation lifting and adversarial robustness Eight teaching examples (varying polarity / modality / tense / intentionality). Derived recognizer must: 1. Admit three positive variation cases with correct feature bundles. 2. Refuse five adversarial cases at the correct refusal layer (Layer 2 or 3). 3. Produce byte-identical output across two runs. Full test cases in scope doc. ## What this ADR does NOT commit - **Storage layer.** Where derived recognizers live (pack / vault / substrate state) is deferred to the ADR that follows the spike. - **Integration into Engine A.** Gated on ADR-0144 (PropositionGraph). Until then, the recognizer is a standalone module. - **Parsing framework.** Token-sequence anti-unification is the starting point; syntactic parse trees are a fallback if token-level fails. ## Vocabulary isolation is intentional (W-010) `derive_recognizer()` and `recognize()` operate on raw token sequences and taught `FeatureBundle` evidence without consuming `VocabManifold`, pack lexicons, or domain namespaces. This is a deliberate design choice, not a gap. Anti-unification derives its own structural vocabulary from the teaching set; importing pack-resident vocabulary would create a dependency on L3 that adds no information at the anti-unification level — the feature bundle carries the semantically relevant structure. If a future scope requires pack-vocabulary-typed slots (e.g. "subject must be a `logos.*` domain atom"), that is a new ADR, not a retrofit of this one. Confirmed intentional by operator review 2026-05-25 (W-010 closure). - **Counter-evidence vocabulary.** `"Alleged"`, `"claimed"`, `"(this is a lie)"` are refused at Layer 2/3 on first encounter. Teaching-loop consumption of those refusals as correction candidates is its own future scope. - **Lens-conditional recognition.** How different anchor lenses interact with derived recognizers is deferred. ## Consequences - All future recognition work targets `RecognitionOutcome`. No alternative output contract is permitted without a new ADR. - Refusal is first-class. Every refusal carries a typed reason consumable by the teaching loop. Silent failure is a bug. - The recognizer is not a classifier. It does not assign a proposition type directly — type emerges from the feature bundle via a downstream mapping that is itself derived from teaching. - Integration into the runtime is gated on ADR-0144. Until then, the spike lives in `recognition/` as a standalone testable module.