Two new intent shapes + composers turn the runtime's corpus
density into operator-visible articulation. Both consult the
cross-corpus aggregator from ADR-0064; no new ratification needed.
P3.3 — chat/narrative_surface.py + IntentTag.NARRATIVE.
Classifier patterns (registered BEFORE generic DEFINITION):
^tell\s+me\s+about\s+
^describe\s+
^what\s+(?:can|do)\s+you\s+(?:say|know)\s+about\s+
narrative_grounded_surface(subject, max_clauses=4) walks every
reviewed chain rooted on subject across all registered teaching
corpora. Dedupes by (connective, object) — cause + verification
carrying the same predicate emit one clause, not two. Sorts by
(intent, connective, object) for replay stability.
Surface format:
"{X} — narrative-grounded ({corpus_ids}): {dX1}; {dX2}.
{X} {conn1} {O1} ({dO1}); {X} {conn2} {O2} ({dO2}).
No session evidence yet."
Cross-corpus subjects (e.g. mother in relations_v2) emit
narrative-grounded (relations_chains_v2) tag; cognition subjects
emit cognition_chains_v1 tag. Multi-corpus subjects (when
applicable) emit composite "corpus_a + corpus_b" tag.
P3.4 — chat/example_surface.py + IntentTag.EXAMPLE.
Classifier patterns:
^(?:give|show)\s+(?:me\s+)?an?\s+(?:example|instance)\s+of\s+
^example\s+of\s+
example_grounded_surface(object_lemma, max_examples=3) walks chains
where the lemma is the OBJECT — inverts the typical subject-keyed
access pattern. Dedupes by subject; sorts by (intent, subject,
connective).
Surface format:
"{X} — example-grounded ({corpus_ids}): {dX1}.
Example: {subj1} {conn1} {X}; {subj2} {conn2} {X}.
No session evidence yet."
Cross-cutting:
- Both intents added to _OOV_INTENT_TAGS — fall through to OOV
invitation when subject is unknown (Phase 2 gradient discipline).
- Both tagged grounding_source="teaching" (same provenance tier
as the existing teaching_grounded_surface).
- No prose generation, no new mutation surface.
Live verification:
> Tell me about truth.
[teaching] truth — narrative-grounded (cognition_chains_v1):
cognition.truth; logos.core. truth grounds knowledge
(cognition.knowledge); truth requires evidence (cognition.evidence).
> Give me an example of knowledge.
[teaching] knowledge — example-grounded (cognition_chains_v1):
cognition.knowledge. Example: truth grounds knowledge;
understanding requires knowledge; evidence grounds knowledge.
> Tell me about mother.
[teaching] mother — narrative-grounded (relations_chains_v2):
kinship.parent.female. mother precedes daughter (kinship.child.female).
> Describe photosynthesis.
[oov] I haven't learned 'photosynthesis' yet (intent: narrative). ...
ADR-0066 (this commit completes the ADR). 30 new tests passed.
Full lane: 2067 passed, 2 skipped, 0 failed in 2:32.
137 lines
4.9 KiB
Python
137 lines
4.9 KiB
Python
"""chat/oov_surface.py — Phase 2.1: OOV "teach me" surface.
|
|
|
|
When the intent classifier extracts a clean subject lemma but that
|
|
lemma is not resident in any mounted lexicon pack, the runtime today
|
|
falls through to the universal disclosure
|
|
(``_UNKNOWN_DOMAIN_SURFACE``). That surface is *honest* (it does
|
|
not pretend to know) but it is also *flat* — it conveys no signal
|
|
that a specific vocabulary gap was hit, and it offers the operator
|
|
no concrete next step.
|
|
|
|
This module replaces that cliff with a gradient. Cold-start prompts
|
|
whose subject is OOV emit a deterministic learning-invitation
|
|
surface that:
|
|
|
|
1. Names the unknown token explicitly so the operator sees which
|
|
word the system could not ground.
|
|
2. Lists the currently-mounted lexicon packs so the operator knows
|
|
where the token could be added.
|
|
3. Points at the existing reviewed-pack-mutation path
|
|
(:mod:`teaching.proposals`) as the way to teach the system the
|
|
new lemma — never "auto-learn", never invent meaning.
|
|
|
|
The surface is tagged ``grounding_source="oov"`` so downstream audit,
|
|
discovery aggregation, and operator tooling can distinguish
|
|
"I haven't learned this yet" from "I refuse" / "I'm unsure" /
|
|
"insufficient evidence".
|
|
|
|
Design constraints (matching ADR-0048..0064 doctrine):
|
|
|
|
- **Deterministic.** Same OOV token + same mounted-pack list →
|
|
byte-identical surface.
|
|
- **No synthesis.** The surface composes only:
|
|
* the OOV token (verbatim user input — safely escaped at the
|
|
:func:`chat._safe_display.safe_display` boundary),
|
|
* the mounted-pack ids (declared statically in
|
|
:data:`chat.pack_resolver.DEFAULT_RESOLVABLE_PACK_IDS`),
|
|
* a fixed-template instruction.
|
|
No new vocabulary is invented; no domain inference is performed.
|
|
- **Trust boundary preserved.** The surface invites a *reviewed*
|
|
pack mutation; it never silently mutates any pack or corpus. The
|
|
ADR-0027 proposal-only invariant is intact.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from chat.pack_resolver import DEFAULT_RESOLVABLE_PACK_IDS, is_resolvable
|
|
from core._safe_display import safe_display
|
|
from generate.intent import IntentTag
|
|
|
|
|
|
# Intent shapes for which the runtime emits a grounded cold-start
|
|
# surface today (ADR-0048 / 0050 / 0052 / 0053 / 0061). OOV
|
|
# invitation fires only when the prompt's intent is one of these —
|
|
# UNKNOWN-intent prompts get the universal disclosure unchanged
|
|
# because the classifier itself could not extract a confident
|
|
# subject.
|
|
_OOV_INTENT_TAGS: frozenset[IntentTag] = frozenset({
|
|
IntentTag.DEFINITION,
|
|
IntentTag.RECALL,
|
|
IntentTag.CAUSE,
|
|
IntentTag.VERIFICATION,
|
|
IntentTag.COMPARISON,
|
|
IntentTag.PROCEDURE,
|
|
IntentTag.CORRECTION,
|
|
IntentTag.NARRATIVE, # P3.3
|
|
IntentTag.EXAMPLE, # P3.4
|
|
})
|
|
|
|
|
|
def oov_learning_invitation_surface(
|
|
token: str,
|
|
intent_tag: IntentTag,
|
|
*,
|
|
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
|
) -> str | None:
|
|
"""Return a deterministic OOV learning-invitation surface, or ``None``.
|
|
|
|
The surface format is fixed:
|
|
|
|
"I haven't learned '{token}' yet (intent: {intent}).
|
|
Mounted lexicon packs: {pack_list}.
|
|
Teach me via a reviewed PackMutationProposal."
|
|
|
|
The trailing instruction is the constant trust-boundary label.
|
|
It points at the existing reviewed-pack-mutation path; the
|
|
surface never invents meaning for the unknown token.
|
|
|
|
Returns ``None`` (caller falls through to the universal disclosure)
|
|
when:
|
|
- ``token`` is empty or not a string,
|
|
- ``token`` IS resolvable in *pack_ids* (caller routed here by
|
|
mistake — keep the explicit fall-through rather than emit a
|
|
misleading surface),
|
|
- the mounted-pack list is empty (no learnable destination —
|
|
emitting an invitation with no targets would be unhelpful).
|
|
"""
|
|
if not token or not isinstance(token, str):
|
|
return None
|
|
cleaned = token.strip()
|
|
if not cleaned:
|
|
return None
|
|
if intent_tag not in _OOV_INTENT_TAGS:
|
|
return None
|
|
if is_resolvable(cleaned, pack_ids):
|
|
return None
|
|
if not pack_ids:
|
|
return None
|
|
safe_token = safe_display(cleaned)
|
|
pack_list = ", ".join(pack_ids)
|
|
intent_name = intent_tag.name.lower()
|
|
return (
|
|
f"I haven't learned '{safe_token}' yet (intent: {intent_name}). "
|
|
f"Mounted lexicon packs: {pack_list}. "
|
|
f"Teach me via a reviewed PackMutationProposal."
|
|
)
|
|
|
|
|
|
def is_oov_for_packs(
|
|
token: str,
|
|
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
|
) -> bool:
|
|
"""Return True iff *token* is non-empty and not resolvable in
|
|
any of *pack_ids*. Convenience predicate for the runtime
|
|
dispatcher (avoids duplicating the ``is_resolvable`` inversion
|
|
in caller code)."""
|
|
if not token or not isinstance(token, str):
|
|
return False
|
|
cleaned = token.strip()
|
|
if not cleaned:
|
|
return False
|
|
return not is_resolvable(cleaned, pack_ids)
|
|
|
|
|
|
__all__ = [
|
|
"oov_learning_invitation_surface",
|
|
"is_oov_for_packs",
|
|
]
|