Introduces teaching/ module with three-stage correction pipeline: 1. correction.py — extracts CorrectionCandidate from correction intents, binding correction text to the prior turn it references 2. review.py — validates candidates: rejects identity overrides (17 marker patterns) and empty corrections; produces ReviewedTeachingExample with deterministic SHA-256 review hash 3. store.py — bounded FIFO store for accepted examples; emits PackMutationProposal objects instead of mutating the vocab manifold directly; retrievable by subject Design invariants: - Identity override attempts are rejected at the review gate - Pack mutations are proposal-only (applied=False by default) - All traces are deterministic: same input → same candidate_id and review_hash Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
64 lines
1.9 KiB
Python
64 lines
1.9 KiB
Python
"""Correction capture — extract a typed correction from dialogue context.
|
|
|
|
A CorrectionCandidate binds the correction text to the prior turn it
|
|
corrects, carrying both the intent classification and the prior turn's
|
|
proposition for downstream review.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from dataclasses import dataclass
|
|
|
|
from generate.intent import DialogueIntent, IntentTag
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CorrectionCandidate:
|
|
correction_text: str
|
|
intent: DialogueIntent
|
|
prior_surface: str
|
|
prior_turn: int
|
|
candidate_id: str
|
|
|
|
def as_dict(self) -> dict[str, object]:
|
|
return {
|
|
"correction_text": self.correction_text,
|
|
"intent_tag": self.intent.tag.value,
|
|
"intent_subject": self.intent.subject,
|
|
"prior_surface": self.prior_surface,
|
|
"prior_turn": self.prior_turn,
|
|
"candidate_id": self.candidate_id,
|
|
}
|
|
|
|
|
|
def _candidate_id(correction_text: str, prior_surface: str, prior_turn: int) -> str:
|
|
payload = json.dumps(
|
|
{"correction_text": correction_text, "prior_surface": prior_surface, "prior_turn": prior_turn},
|
|
sort_keys=True,
|
|
ensure_ascii=False,
|
|
)
|
|
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
|
|
|
|
|
|
def extract_correction(
|
|
correction_text: str,
|
|
intent: DialogueIntent,
|
|
prior_surface: str,
|
|
prior_turn: int,
|
|
) -> CorrectionCandidate | None:
|
|
"""Extract a correction candidate from a correction-tagged intent.
|
|
|
|
Returns None if the intent is not a correction.
|
|
"""
|
|
if intent.tag is not IntentTag.CORRECTION:
|
|
return None
|
|
|
|
return CorrectionCandidate(
|
|
correction_text=correction_text,
|
|
intent=intent,
|
|
prior_surface=prior_surface,
|
|
prior_turn=prior_turn,
|
|
candidate_id=_candidate_id(correction_text, prior_surface, prior_turn),
|
|
)
|