Completes the three-layer pack architecture:
identity (who CORE is) + safety (universal red lines)
+ ethics (deployment-specific propositional commitments)
manifold.boundary_ids = identity.boundary_ids
∪ safety.boundary_ids
∪ ethics.commitment_ids
Ethics packs are swappable like identity (fall back to default on load
failure) but propositional like safety (commitment ids union into the
manifold). EthicsPackError inherits from ValueError; only when both
the requested and default packs fail does startup refuse.
Ships default_general_ethics_v1 with five commitments:
- acknowledge_uncertainty
- defer_high_stakes_to_human_review
- disclose_limitations
- no_manipulation
- respect_user_autonomy
Ratified through identity_anchor template at SHA 81fc9b61c828….
Test coverage: 20 new tests; combined identity/safety/ethics surface
suite is 81 tests, all green. Cognition (121), teaching (17), runtime
(19), smoke (67), and cognition eval all unaffected.
606 lines
25 KiB
Python
606 lines
25 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, replace
|
|
import re
|
|
from collections.abc import Sequence
|
|
from typing import List
|
|
|
|
import numpy as np
|
|
|
|
from algebra.versor import versor_condition
|
|
from core.config import DEFAULT_CONFIG, DEFAULT_IDENTITY_PACK, RuntimeConfig
|
|
from core.physics.drive import DriveGradientMap, GradientField
|
|
from core.physics.energy import EnergyProfile
|
|
from core.physics.exertion import CycleCost, ExertionMeter
|
|
from core.physics.identity import (
|
|
CharacterProfile,
|
|
IdentityCheck,
|
|
IdentityScore,
|
|
TurnEvent,
|
|
)
|
|
from packs.ethics.loader import (
|
|
DEFAULT_ETHICS_PACK as _DEFAULT_ETHICS_PACK,
|
|
EthicsPackError,
|
|
load_ethics_pack,
|
|
)
|
|
from packs.identity.loader import load_identity_manifold
|
|
from packs.safety.check import SafetyCheck
|
|
from packs.safety.loader import load_safety_pack
|
|
from field.state import FieldState
|
|
from generate.articulation import ArticulationPlan, realize
|
|
from generate.dialogue import DialogueRole, classify_dialogue_blade, propose_dialogue
|
|
from generate.intent_bridge import articulate_with_intent
|
|
from generate.proposition import FrameRegistry, Proposition, propose
|
|
from generate.result import GenerationResult
|
|
from generate.stream import generate
|
|
from generate.surface import SentenceAssembler, SentencePlan, SurfaceContext
|
|
from ingest.gate import inject
|
|
from language_packs import OOVPolicy, load_mounted_packs, load_pack, load_pack_entries
|
|
from persona.motor import PersonaMotor
|
|
from session.context import SessionContext
|
|
from session.correction import CorrectionPass
|
|
from vault.decompose import default_decomposer, default_gate
|
|
|
|
_TOKEN_RE = re.compile(r"\w+", re.UNICODE)
|
|
_SEED_ALIASES = {
|
|
"logos": "\u03bb\u03cc\u03b3\u03bf\u03c2",
|
|
"dabar": "\u05d3\u05d1\u05e8",
|
|
"or": "\u05d0\u05d5\u05e8",
|
|
"phos": "\u03c6\u03c9\u03c2",
|
|
"zoe": "\u03b6\u03c9\u03ae",
|
|
"arche": "\u1f00\u03c1\u03c7\u03ae",
|
|
"aletheia": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1",
|
|
}
|
|
_QUESTION_WORDS = frozenset({"what", "who", "how", "why", "when", "where", "which"})
|
|
_TERMINALS = frozenset({".", "?", ";", "!"})
|
|
_UNKNOWN_DOMAIN_SURFACE = "I don't know — insufficient grounding for that yet."
|
|
|
|
|
|
def _energy_scalar(energy_obj) -> float:
|
|
if energy_obj is None:
|
|
return 1.0
|
|
if isinstance(energy_obj, EnergyProfile):
|
|
return float(energy_obj.raw)
|
|
try:
|
|
return float(energy_obj)
|
|
except (TypeError, ValueError):
|
|
return 1.0
|
|
|
|
|
|
def _is_question_input(raw_text: str, tokens: Sequence[str]) -> bool:
|
|
if raw_text.strip().endswith("?"):
|
|
return True
|
|
return bool(tokens and tokens[0].casefold() in _QUESTION_WORDS)
|
|
|
|
|
|
def _stable_dialogue_role(role: DialogueRole, *, raw_text: str, tokens: Sequence[str]) -> DialogueRole:
|
|
if role in {"question", "refute"} and not _is_question_input(raw_text, tokens):
|
|
return "elaborate"
|
|
return role
|
|
|
|
|
|
def _terminal_for_role(role: DialogueRole, output_language: str) -> str:
|
|
if role == "question":
|
|
return ";" if output_language == "grc" else "?"
|
|
return "."
|
|
|
|
|
|
def _terminate_surface(surface: str, *, role: DialogueRole, output_language: str) -> str:
|
|
stripped = surface.strip()
|
|
if not stripped:
|
|
return stripped
|
|
if stripped[-1] in _TERMINALS:
|
|
return stripped
|
|
return f"{stripped}{_terminal_for_role(role, output_language)}"
|
|
|
|
|
|
def _prefer_prompt_anchor(
|
|
articulation: ArticulationPlan,
|
|
filtered_tokens: Sequence[str],
|
|
*,
|
|
output_language: str,
|
|
) -> ArticulationPlan:
|
|
if output_language != "en" or len(filtered_tokens) < 2:
|
|
return articulation
|
|
content_tokens = [
|
|
token
|
|
for token in filtered_tokens
|
|
if token.casefold() not in _QUESTION_WORDS and token.casefold() not in {"is", "are", "was", "were"}
|
|
]
|
|
if not content_tokens:
|
|
return articulation
|
|
anchor = content_tokens[-1]
|
|
if anchor == articulation.subject:
|
|
return articulation
|
|
return replace(
|
|
articulation,
|
|
subject=anchor,
|
|
surface=" ".join(part for part in (anchor, articulation.predicate, articulation.object) if part),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class _StubBindingFrame:
|
|
frame_id: str
|
|
coherence_magnitude: float
|
|
region_ids: frozenset
|
|
cycle_index: int
|
|
|
|
|
|
def _make_trajectory_from_result(result, turn: int):
|
|
from core.physics.reasoning import TrajectoryOperator
|
|
|
|
operator = TrajectoryOperator()
|
|
states = result.trajectory or (result.final_state,)
|
|
frames = [
|
|
_StubBindingFrame(
|
|
frame_id=f"t{turn}_s{i}",
|
|
coherence_magnitude=_energy_scalar(getattr(fs, "energy", None)),
|
|
region_ids=frozenset({str(getattr(fs, "node", 0))}),
|
|
cycle_index=turn,
|
|
)
|
|
for i, fs in enumerate(states)
|
|
]
|
|
return operator.build(frames, trajectory_id=f"turn_{turn}")
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ChatResponse:
|
|
surface: str
|
|
proposition: Proposition
|
|
articulation: ArticulationPlan
|
|
articulation_surface: str
|
|
dialogue_role: DialogueRole
|
|
versor_condition: float
|
|
output_language: str
|
|
frame_pack: str
|
|
walk_surface: str
|
|
salience_top_k: int | None
|
|
candidates_used: int | None
|
|
vault_hits: int
|
|
identity_score: IdentityScore | None
|
|
character_profile: CharacterProfile
|
|
flagged: bool
|
|
# ADR-0023 §2 — per-transition admissibility evidence and region
|
|
# provenance flag. An empty tuple is the contract for "no
|
|
# admissibility was checked this turn" (cold start, refusal, stub).
|
|
admissibility_trace: tuple = ()
|
|
region_was_unconstrained: bool = True
|
|
|
|
|
|
class ChatRuntime:
|
|
def __init__(
|
|
self,
|
|
pack_id: str | Sequence[str] | None = None,
|
|
*,
|
|
frame_pack: str | None = None,
|
|
config: RuntimeConfig = DEFAULT_CONFIG,
|
|
) -> None:
|
|
if pack_id is not None or frame_pack is not None:
|
|
pack_ids = (pack_id,) if isinstance(pack_id, str) else tuple(pack_id or config.input_packs)
|
|
resolved_config = RuntimeConfig(
|
|
input_packs=pack_ids,
|
|
output_language=config.output_language,
|
|
frame_pack=frame_pack or config.frame_pack,
|
|
max_tokens=config.max_tokens,
|
|
allow_cross_language_recall=config.allow_cross_language_recall,
|
|
allow_cross_language_generation=config.allow_cross_language_generation,
|
|
vault_reproject_interval=config.vault_reproject_interval,
|
|
use_salience=config.use_salience,
|
|
salience_top_k=config.salience_top_k,
|
|
inhibition_threshold=config.inhibition_threshold,
|
|
inner_loop_admissibility=config.inner_loop_admissibility,
|
|
admissibility_threshold=config.admissibility_threshold,
|
|
admissibility_mode=config.admissibility_mode,
|
|
admissibility_margin=config.admissibility_margin,
|
|
)
|
|
else:
|
|
resolved_config = config
|
|
pack_ids = tuple(config.input_packs)
|
|
|
|
self.config = resolved_config
|
|
manifests = []
|
|
manifolds = []
|
|
entries = []
|
|
for mounted_pack_id in pack_ids:
|
|
manifest, manifold = load_pack(mounted_pack_id)
|
|
manifests.append(manifest)
|
|
manifolds.append(manifold)
|
|
entries.extend(load_pack_entries(mounted_pack_id))
|
|
|
|
manifold = manifolds[0] if len(pack_ids) == 1 else load_mounted_packs(pack_ids)
|
|
self._manifests = tuple(manifests)
|
|
identity_pack_id = resolved_config.identity_pack or DEFAULT_IDENTITY_PACK
|
|
# ADR-0027 Phase 5 complete: v1 packs are ratified. Loader defaults
|
|
# to production mode (require_ratified=None -> require unless
|
|
# CORE_ALLOW_UNRATIFIED_IDENTITY=1).
|
|
identity_manifold = load_identity_manifold(identity_pack_id)
|
|
# ADR-0029: safety pack is always loaded; its boundary_ids are
|
|
# unioned into the runtime manifold. Identity packs may add
|
|
# boundaries but cannot remove safety boundaries. Failure to
|
|
# load the safety pack is fail-closed; SafetyPackError propagates
|
|
# and prevents runtime startup.
|
|
self.safety_pack = load_safety_pack()
|
|
# ADR-0033 — ethics pack composes alongside identity + safety.
|
|
# Swappable like identity; falls back to the default pack on
|
|
# load failure rather than refusing startup (safety is the
|
|
# fail-closed layer, not ethics).
|
|
ethics_pack_id = resolved_config.ethics_pack or _DEFAULT_ETHICS_PACK
|
|
try:
|
|
self.ethics_pack = load_ethics_pack(ethics_pack_id)
|
|
except EthicsPackError:
|
|
if ethics_pack_id == _DEFAULT_ETHICS_PACK:
|
|
raise
|
|
self.ethics_pack = load_ethics_pack(_DEFAULT_ETHICS_PACK)
|
|
ethics_pack_id = _DEFAULT_ETHICS_PACK
|
|
self.ethics_pack_id = ethics_pack_id
|
|
self.identity_manifold = type(identity_manifold)(
|
|
value_axes=identity_manifold.value_axes,
|
|
boundary_ids=(
|
|
identity_manifold.boundary_ids
|
|
| self.safety_pack.boundary_ids
|
|
| self.ethics_pack.commitment_ids
|
|
),
|
|
alignment_threshold=identity_manifold.alignment_threshold,
|
|
surface_preferences=identity_manifold.surface_preferences,
|
|
)
|
|
self.identity_pack_id = identity_pack_id
|
|
# Keep the generic runtime neutral. Identity/persona motivation belongs
|
|
# behind an explicit IdentityProfile contract, not the baseline chat path.
|
|
persona_motor = PersonaMotor.identity()
|
|
self._context = SessionContext(
|
|
manifold,
|
|
persona=persona_motor,
|
|
vault_reproject_interval=resolved_config.vault_reproject_interval,
|
|
)
|
|
self._frame_registry = FrameRegistry.from_pack(resolved_config.frame_pack, self._context.vocab)
|
|
self._surface_by_fold = {e.surface.casefold(): e.surface for e in entries}
|
|
self._surface_by_fold.update(_SEED_ALIASES)
|
|
self._pos_by_surface = {e.surface: (e.pos or e.part_of_speech or "X") for e in entries}
|
|
self.exertion_meter = ExertionMeter(capacity_ceiling=128.0)
|
|
self.drive_gradients = tuple(GradientField(axis=axis, magnitude=0.75) for axis in self.identity_manifold.value_axes)
|
|
self._drive_map = DriveGradientMap(gradients=self.drive_gradients)
|
|
self.character_profile = CharacterProfile.from_manifold(
|
|
self.identity_manifold,
|
|
drive_summaries={g.axis.name: g.magnitude for g in self.drive_gradients},
|
|
fatigue_index=0.0,
|
|
)
|
|
self._identity_check = IdentityCheck()
|
|
# ADR-0032 — structural safety surface. Observational at v1:
|
|
# ChatRuntime exposes ``safety_check`` for callers (audit /
|
|
# logging / future enforcement), but does not auto-invoke it in
|
|
# the turn loop. Wiring violations into refusal paths is a
|
|
# future ADR.
|
|
self.safety_check = SafetyCheck()
|
|
self.turn_log: List[TurnEvent] = []
|
|
self._correction_pass = CorrectionPass()
|
|
self._last_valence: float = 0.0
|
|
|
|
@property
|
|
def session(self) -> SessionContext:
|
|
return self._context
|
|
|
|
def _tokenize(self, text: str) -> list[str]:
|
|
return [self._surface_by_fold.get(m.group(0).casefold(), m.group(0)) for m in _TOKEN_RE.finditer(text)]
|
|
|
|
def tokenize(self, text: str) -> list[str]:
|
|
return self._tokenize(text)
|
|
|
|
def _apply_oov_policy(self, tokens: list[str]) -> list[str]:
|
|
kept: list[str] = []
|
|
for token in tokens:
|
|
try:
|
|
self._context.vocab.get_versor(token)
|
|
kept.append(token)
|
|
except KeyError:
|
|
if all(manifest.oov_policy is OOVPolicy.FAIL_CLOSED for manifest in self._manifests):
|
|
raise
|
|
if any(manifest.oov_policy is OOVPolicy.PROPOSE_VOCAB_EXPANSION for manifest in self._manifests):
|
|
raise KeyError(f"OOV token requires vocab proposal: {token}")
|
|
kept.append(token)
|
|
return kept
|
|
|
|
def _syntactic_guard(self, tokens: tuple[str, ...]) -> list[str]:
|
|
out: list[str] = []
|
|
prev_pos: str | None = None
|
|
for token in tokens:
|
|
pos = self._pos_by_surface.get(token, "X")
|
|
if pos == prev_pos:
|
|
continue
|
|
out.append(token)
|
|
prev_pos = pos
|
|
return out
|
|
|
|
def _dialogue_reference(self) -> np.ndarray | None:
|
|
blade = self._context.last_dialogue_blade
|
|
if blade is None or float(np.linalg.norm(blade)) < 1e-8:
|
|
return None
|
|
return blade
|
|
|
|
def _apply_drive_bias(self, field_state: FieldState) -> FieldState:
|
|
"""Generic runtime keeps motivation/drive disabled.
|
|
|
|
Motivation is an identity-profile concern, not a free runtime field
|
|
mutation. Keeping this a no-op preserves the neutral baseline while
|
|
generic chat closure and cognition evals are being stabilized.
|
|
"""
|
|
return field_state
|
|
|
|
def _build_surface_context(self, identity_score, current_valence: float) -> SurfaceContext:
|
|
active = self._context.referents.active_referent()
|
|
alignment = float(identity_score.alignment) if identity_score is not None else 1.0
|
|
deviation_axes = (
|
|
frozenset(identity_score.deviation_axes)
|
|
if identity_score is not None
|
|
else frozenset()
|
|
)
|
|
prefs = self.identity_manifold.surface_preferences
|
|
# ADR-0031 — flatten the manifold's axis_hedges (tuple of
|
|
# (axis_id, AxisHedge)) into the wire-format quadruples that
|
|
# SurfaceContext carries. Order is preserved (loader emits in
|
|
# lex order); _axis_specific_phrase relies on this.
|
|
axis_hedges = tuple(
|
|
(axis_id, hedge.strong, hedge.soft, hedge.qualifier)
|
|
for axis_id, hedge in prefs.axis_hedges
|
|
)
|
|
return SurfaceContext(
|
|
active_referent_surface=active.surface if active is not None else "",
|
|
active_referent_slot=active.slot if active is not None else "neut_sg",
|
|
identity_alignment=alignment,
|
|
valence_delta=current_valence - self._last_valence,
|
|
elab_conjunction="",
|
|
hedge_threshold_strong=prefs.hedge_threshold_strong,
|
|
hedge_threshold_soft=prefs.hedge_threshold_soft,
|
|
preferred_hedge_strong=prefs.preferred_hedge_strong,
|
|
preferred_hedge_soft=prefs.preferred_hedge_soft,
|
|
claim_strength=prefs.claim_strength,
|
|
qualified_band_high=prefs.qualified_band_high,
|
|
preferred_qualifier=prefs.preferred_qualifier,
|
|
deviation_axes=deviation_axes,
|
|
axis_hedges=axis_hedges,
|
|
)
|
|
|
|
def _stub_response(self, field_state: FieldState) -> ChatResponse:
|
|
zero = np.zeros(field_state.F.shape, dtype=np.float32)
|
|
prop = Proposition(
|
|
subject="",
|
|
predicate="",
|
|
object_=None,
|
|
surface=_UNKNOWN_DOMAIN_SURFACE,
|
|
frame_id="unknown_domain",
|
|
subject_versor=zero,
|
|
predicate_versor=zero,
|
|
object_versor=None,
|
|
relation=zero,
|
|
)
|
|
art = ArticulationPlan(
|
|
subject="",
|
|
predicate="",
|
|
object=None,
|
|
surface=_UNKNOWN_DOMAIN_SURFACE,
|
|
output_language=self.config.output_language,
|
|
frame_id="unknown_domain",
|
|
)
|
|
return ChatResponse(
|
|
surface=_UNKNOWN_DOMAIN_SURFACE,
|
|
proposition=prop,
|
|
articulation=art,
|
|
articulation_surface=_UNKNOWN_DOMAIN_SURFACE,
|
|
dialogue_role="assert",
|
|
versor_condition=versor_condition(field_state.F),
|
|
output_language=self.config.output_language,
|
|
frame_pack=self.config.frame_pack,
|
|
walk_surface=_UNKNOWN_DOMAIN_SURFACE,
|
|
salience_top_k=None,
|
|
candidates_used=None,
|
|
vault_hits=0,
|
|
identity_score=None,
|
|
character_profile=self.character_profile,
|
|
flagged=False,
|
|
)
|
|
|
|
def chat(self, text: str, max_tokens: int | None = None) -> ChatResponse:
|
|
tokens = self._tokenize(text)
|
|
filtered = self._apply_oov_policy(tokens)
|
|
if not filtered:
|
|
raise ValueError("ChatRuntime.chat() received no in-vocabulary tokens.")
|
|
|
|
probe_state = self._context.probe_ingest(filtered)
|
|
# INV-24 recall role: RECOGNITION. Feeds UnknownDomainGate — asks
|
|
# "have we seen anything like this before?", not "what is admissible
|
|
# evidence?". Session-tier SPECULATIVE memory must count here, so
|
|
# no min_status filter is applied.
|
|
direct_hits = self._context.vault.recall(probe_state.F, top_k=3)
|
|
direct_best = max((h["score"] for h in direct_hits), default=0.0)
|
|
gate_decision = default_gate.check(
|
|
direct_best,
|
|
vault=self._context.vault,
|
|
query=probe_state.F,
|
|
decomposer=default_decomposer,
|
|
)
|
|
if gate_decision.fire:
|
|
committed = self._context.commit_ingest(filtered)
|
|
empty_result = GenerationResult(tokens=(), final_state=committed, vault_hits=0)
|
|
self._context.finalize_turn(
|
|
empty_result,
|
|
tokens_in=tuple(filtered),
|
|
input_versor=committed.F,
|
|
dialogue_role="assert",
|
|
metadata={"unknown": True, "unknown_source": gate_decision.source},
|
|
)
|
|
return self._stub_response(committed)
|
|
|
|
field_state = self._context.commit_ingest(filtered)
|
|
field_state = self._apply_drive_bias(field_state)
|
|
reference_blade = self._dialogue_reference()
|
|
base_proposition = propose(
|
|
field_state,
|
|
None,
|
|
self._context.vocab,
|
|
self._frame_registry,
|
|
output_lang=self.config.output_language,
|
|
)
|
|
dialogue_role = _stable_dialogue_role(
|
|
classify_dialogue_blade(base_proposition.relation, reference_blade),
|
|
raw_text=text,
|
|
tokens=tokens,
|
|
)
|
|
proposition = propose_dialogue(
|
|
field_state,
|
|
self._context.vault,
|
|
self._context.vocab,
|
|
self._frame_registry,
|
|
reference_blade,
|
|
output_lang=self.config.output_language,
|
|
)
|
|
articulation = realize(proposition, self._context.vocab, output_language=self.config.output_language)
|
|
articulation = _prefer_prompt_anchor(articulation, filtered, output_language=self.config.output_language)
|
|
self._context.record_dialogue(proposition)
|
|
|
|
result = generate(
|
|
field_state,
|
|
self._context.vocab,
|
|
self._context.persona,
|
|
max_tokens=self.config.max_tokens if max_tokens is None else max_tokens,
|
|
record_trajectory=True,
|
|
vault=self._context.vault,
|
|
recall_top_k=3 if self.config.allow_cross_language_recall else 0,
|
|
output_lang=self.config.output_language,
|
|
allow_cross_language_generation=self.config.allow_cross_language_generation,
|
|
use_salience=self.config.use_salience,
|
|
salience_top_k=self.config.salience_top_k,
|
|
inhibition_threshold=self.config.inhibition_threshold,
|
|
inner_loop_admissibility=self.config.inner_loop_admissibility,
|
|
admissibility_threshold=self.config.admissibility_threshold,
|
|
admissibility_mode=self.config.admissibility_mode,
|
|
admissibility_margin=self.config.admissibility_margin,
|
|
)
|
|
|
|
# --- Articulation fidelity: replace bare S-P-O join with intent-aware surface ---
|
|
# articulate_with_intent() classifies the input intent, builds a proposition
|
|
# graph grounded on the generation result's recalled tokens, and calls the
|
|
# realize_semantic() path (13-construction realizer) that was previously
|
|
# implemented but never connected to the chat hot path.
|
|
# Falls back to the existing articulation.surface when bridge returns "".
|
|
if self.config.output_language == "en":
|
|
recalled_words = tuple(
|
|
tok for tok in (result.tokens or ()) if tok and tok.isalpha()
|
|
)
|
|
intent_surface = articulate_with_intent(text, articulation, recalled_words)
|
|
if intent_surface:
|
|
articulation = replace(articulation, surface=intent_surface)
|
|
# --- end articulation fidelity fix ---
|
|
|
|
reasoning_trajectory = _make_trajectory_from_result(result, self._context.turn)
|
|
identity_score = self._identity_check.check(reasoning_trajectory, self.identity_manifold)
|
|
flagged = identity_score.flagged
|
|
cycle_cost = CycleCost(
|
|
cycle_index=self._context.turn,
|
|
attention_cost=float(result.candidates_used or 0),
|
|
inhibition_cost=float(self.config.inhibition_threshold),
|
|
digest_cost=0.0,
|
|
trajectory_cost=float(len(result.trajectory or ())),
|
|
)
|
|
self.exertion_meter.record(cycle_cost)
|
|
fatigue = self.exertion_meter.fatigue(at_cycle=self._context.turn)
|
|
self.character_profile = CharacterProfile.from_manifold(
|
|
self.identity_manifold,
|
|
drive_summaries={g.axis.name: g.magnitude * (1.0 - fatigue.value) for g in self.drive_gradients},
|
|
fatigue_index=fatigue.value,
|
|
)
|
|
|
|
self._context.finalize_turn(
|
|
result,
|
|
tokens_in=tuple(filtered),
|
|
dialogue_role=str(dialogue_role),
|
|
)
|
|
current_valence = _energy_scalar(getattr(result.final_state, "valence", None))
|
|
surface_ctx = self._build_surface_context(identity_score, current_valence)
|
|
self._last_valence = current_valence
|
|
surface = _terminate_surface(articulation.surface, role=dialogue_role, output_language=self.config.output_language)
|
|
articulation = replace(articulation, surface=surface)
|
|
sentence_plan: SentencePlan = SentenceAssembler().assemble(
|
|
articulation,
|
|
result.tokens,
|
|
role=dialogue_role,
|
|
context=surface_ctx,
|
|
)
|
|
walk_surface = sentence_plan.surface
|
|
vault_hits = int(result.vault_hits)
|
|
turn_event = TurnEvent(
|
|
turn=self._context.turn - 1,
|
|
input_tokens=tuple(filtered),
|
|
surface=surface,
|
|
walk_surface=walk_surface,
|
|
articulation_surface=articulation.surface,
|
|
dialogue_role=str(dialogue_role),
|
|
identity_score=identity_score,
|
|
cycle_cost_total=cycle_cost.total,
|
|
vault_hits=vault_hits,
|
|
versor_condition=versor_condition(result.final_state.F),
|
|
flagged=flagged,
|
|
elaboration=sentence_plan.elaboration,
|
|
)
|
|
self.turn_log.append(turn_event)
|
|
return ChatResponse(
|
|
surface=walk_surface,
|
|
proposition=proposition,
|
|
articulation=articulation,
|
|
articulation_surface=articulation.surface,
|
|
dialogue_role=dialogue_role,
|
|
versor_condition=versor_condition(result.final_state.F),
|
|
output_language=self.config.output_language,
|
|
frame_pack=self.config.frame_pack,
|
|
walk_surface=walk_surface,
|
|
salience_top_k=result.salience_top_k,
|
|
candidates_used=result.candidates_used,
|
|
vault_hits=vault_hits,
|
|
identity_score=identity_score,
|
|
character_profile=self.character_profile,
|
|
flagged=flagged,
|
|
admissibility_trace=result.admissibility_trace,
|
|
region_was_unconstrained=result.region_was_unconstrained,
|
|
)
|
|
|
|
def _unknown_domain_response(self, field_state: FieldState, filtered: list[str]) -> ChatResponse:
|
|
return self._stub_response(field_state)
|
|
|
|
def correct(self, text: str, target_turn: int = -1, max_tokens: int | None = None) -> ChatResponse:
|
|
tokens = self._tokenize(text)
|
|
filtered = self._apply_oov_policy(tokens)
|
|
if not filtered:
|
|
raise ValueError("correct() received no in-vocabulary tokens.")
|
|
correction_state = inject(filtered, self._context.vocab)
|
|
correction_result = self._correction_pass.apply(
|
|
self._context.graph,
|
|
correction_state.F,
|
|
from_turn=target_turn,
|
|
)
|
|
self._context.apply_corrected_outputs(correction_result.records)
|
|
regen_tokens = self._context.last_input_tokens
|
|
if not regen_tokens:
|
|
return self._stub_response(correction_state)
|
|
return self.chat(" ".join(regen_tokens), max_tokens=max_tokens)
|
|
|
|
def respond(self, text: str, max_tokens: int | None = None) -> str:
|
|
try:
|
|
return self.chat(text, max_tokens=max_tokens).surface
|
|
except ValueError:
|
|
return ""
|
|
|
|
async def achat(self, text: str, max_tokens: int | None = None) -> ChatResponse:
|
|
return self.chat(text, max_tokens=max_tokens)
|
|
|
|
async def arespond(self, text: str, max_tokens: int | None = None) -> str:
|
|
try:
|
|
return (await self.achat(text, max_tokens=max_tokens)).surface
|
|
except ValueError:
|
|
return ""
|
|
|
|
|
|
# The previous ``_default_identity_manifold()`` constructor was removed as
|
|
# part of ADR-0027. Identity is now loaded from a pack at runtime via
|
|
# ``packs.identity.loader.load_identity_manifold`` using
|
|
# ``RuntimeConfig.identity_pack`` (default ``DEFAULT_IDENTITY_PACK``).
|
|
# The previously-hardcoded three axes (truthfulness / coherence /
|
|
# reverence) live in ``packs/identity/default_general_v1.json``.
|