feat(runtime): inline realization — the conversation accrues knowledge (Step B-1)
Wires comprehend->realize/determine into the live turn loop: a declarative turn REALIZES a fact into the held self (session vault, SPECULATIVE/as-told); a question turn is DETERMINED over realized knowledge (answered as-told, or refused open-world). The first time a CORE conversation actually accumulates knowledge it can recall and reason over — the 'one continuous life' telos made real, including across reboot (the accrued fact is in the Shape B+ snapshot, so it survives the process ending). Seam: all chat() returns funnel through _checkpointed_response; accrual runs there BEFORE the checkpoint (so the fact persists this turn), gated by a new accrue_realized_knowledge flag (default OFF — one-shot/eval runtimes don't accrue; the production L10 process enables it with persist_session_state). Discipline: SESSION memory, not ratified learning — proposes nothing, HITL untouched. Realization writes go through generate.realize (INV-21 allowed writer). Additive: a failure is a clean no-op, never crashes a turn. Slice B-1 RECORDS the outcome (last_turn_accrual(), introspectable) and leaves the ChatResponse/surface contract UNCHANGED; surfacing the determination is the B-2 follow-up (needs runtime_contracts + contract-test updates). Tests: declarative accrues; question determines as_told; untold refuses open-world; idempotent retell not recreated; flag-off no-ops with identical surface; and the lived-spine proof — accrued knowledge survives reboot (determined as_told after a fresh runtime over the same checkpoint).
This commit is contained in:
parent
4692d3fae7
commit
3cdc4faa5f
3 changed files with 228 additions and 0 deletions
|
|
@ -388,6 +388,22 @@ def _make_trajectory_from_result(result, turn: int):
|
||||||
return operator.build(frames, trajectory_id=f"turn_{turn}")
|
return operator.build(frames, trajectory_id=f"turn_{turn}")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class TurnAccrual:
|
||||||
|
"""The inline-realization outcome of one turn (Step B).
|
||||||
|
|
||||||
|
``kind`` is ``"realized"`` (a declarative fact was accrued into the held self),
|
||||||
|
``"determined"`` (a question was answered over realized knowledge), or ``"none"``
|
||||||
|
(nothing comprehensible to accrue/determine). The payload carries the typed
|
||||||
|
realize/determine result for introspection. This is recorded, not surfaced —
|
||||||
|
slice B-1 leaves the ChatResponse/surface contract unchanged.
|
||||||
|
"""
|
||||||
|
|
||||||
|
kind: str
|
||||||
|
realized: Any = None # generate.realize.Realized | NotRealized | None
|
||||||
|
determination: Any = None # generate.determine.Determined | Undetermined | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class ChatResponse:
|
class ChatResponse:
|
||||||
surface: str
|
surface: str
|
||||||
|
|
@ -672,6 +688,11 @@ class ChatRuntime:
|
||||||
# W-013 — last classified intent, updated each turn for /explain REPL use.
|
# W-013 — last classified intent, updated each turn for /explain REPL use.
|
||||||
self._last_intent: Any | None = None
|
self._last_intent: Any | None = None
|
||||||
self._last_input_text: str = ""
|
self._last_input_text: str = ""
|
||||||
|
# Step B (inline realization) — the last turn's accrual outcome (what the turn
|
||||||
|
# realized into the held self, or determined over it). Introspectable; the
|
||||||
|
# surface contract is unchanged (slice B-1 records, does not surface).
|
||||||
|
self._last_turn_accrual: TurnAccrual | None = None
|
||||||
|
self._relational_pack_lemmas: frozenset[str] | None = None
|
||||||
self._engine_state_store: EngineStateStore | None = (
|
self._engine_state_store: EngineStateStore | None = (
|
||||||
None if no_load_state else EngineStateStore(engine_state_path)
|
None if no_load_state else EngineStateStore(engine_state_path)
|
||||||
)
|
)
|
||||||
|
|
@ -923,9 +944,71 @@ class ChatRuntime:
|
||||||
|
|
||||||
def _checkpointed_response(self, response: ChatResponse) -> ChatResponse:
|
def _checkpointed_response(self, response: ChatResponse) -> ChatResponse:
|
||||||
self._turn_count += 1
|
self._turn_count += 1
|
||||||
|
# Step B — inline realization, BEFORE the checkpoint so an accrued fact is in
|
||||||
|
# the snapshot this turn (survives reboot with persist_session_state). Gated;
|
||||||
|
# off by default. The surface contract is unchanged (the outcome is recorded,
|
||||||
|
# not surfaced).
|
||||||
|
if self.config.accrue_realized_knowledge:
|
||||||
|
self._accrue_in_turn(self._last_input_text)
|
||||||
self.checkpoint_engine_state()
|
self.checkpoint_engine_state()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def last_turn_accrual(self) -> TurnAccrual | None:
|
||||||
|
"""The most recent turn's inline-realization outcome (Step B), or None when
|
||||||
|
accrual is off or did not complete. Introspection only — never surfaced."""
|
||||||
|
return self._last_turn_accrual
|
||||||
|
|
||||||
|
def _accrue_in_turn(self, text: str) -> None:
|
||||||
|
"""Inline realization (Step B): a comprehensible declarative turn accrues a
|
||||||
|
realized fact into the held self (session vault, SPECULATIVE / as-told); a
|
||||||
|
comprehensible question turn is determined over realized knowledge. Records the
|
||||||
|
typed outcome on ``self._last_turn_accrual``.
|
||||||
|
|
||||||
|
Never raises into the turn — accrual is ADDITIVE, so any failure is a clean
|
||||||
|
no-op (the turn's response is untouched). This is SESSION memory (immediate),
|
||||||
|
NOT ratified learning: it proposes nothing and leaves the teaching/review HITL
|
||||||
|
path untouched. Realization writes go through ``generate.realize`` (the INV-21
|
||||||
|
allowed vault writer); DETERMINE and the readers are total (typed results, no
|
||||||
|
raises), so the broad guard is a defensive backstop, not expected control flow.
|
||||||
|
"""
|
||||||
|
self._last_turn_accrual = None
|
||||||
|
if self._context is None or not text or not text.strip():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
from generate.determine import determine
|
||||||
|
from generate.meaning_graph.reader import Comprehension, comprehend
|
||||||
|
from generate.meaning_graph.relational import comprehend_relational
|
||||||
|
from generate.realize import realize_comprehension
|
||||||
|
|
||||||
|
if self._relational_pack_lemmas is None:
|
||||||
|
from generate.meaning_graph.relational import load_relational_pack_lemmas
|
||||||
|
|
||||||
|
self._relational_pack_lemmas = load_relational_pack_lemmas()
|
||||||
|
|
||||||
|
readings = (
|
||||||
|
comprehend(text),
|
||||||
|
comprehend_relational(text, self._relational_pack_lemmas),
|
||||||
|
)
|
||||||
|
comprehensions = [c for c in readings if isinstance(c, Comprehension)]
|
||||||
|
|
||||||
|
# A question turn (query-bearing) is DETERMINED over realized knowledge.
|
||||||
|
for c in comprehensions:
|
||||||
|
if c.queries:
|
||||||
|
self._last_turn_accrual = TurnAccrual(
|
||||||
|
kind="determined", determination=determine(c, self._context)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
# A declarative turn (a single told fact) is REALIZED into the held self.
|
||||||
|
for c in comprehensions:
|
||||||
|
if not c.queries and c.meaning_graph.relations:
|
||||||
|
self._last_turn_accrual = TurnAccrual(
|
||||||
|
kind="realized", realized=realize_comprehension(c, self._context)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
self._last_turn_accrual = TurnAccrual(kind="none")
|
||||||
|
except Exception: # additive: accrual must never crash a turn # noqa: BLE001
|
||||||
|
self._last_turn_accrual = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self) -> SessionContext:
|
def session(self) -> SessionContext:
|
||||||
return self._context
|
return self._context
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,17 @@ class RuntimeConfig:
|
||||||
# wanting a hard identity-continuity guarantee opt in.
|
# wanting a hard identity-continuity guarantee opt in.
|
||||||
strict_identity_continuity: bool = False
|
strict_identity_continuity: bool = False
|
||||||
|
|
||||||
|
# Step B (inline realization) — when on, each turn ACCRUES knowledge into the
|
||||||
|
# held self: a comprehensible declarative turn is realized into the session vault
|
||||||
|
# (SPECULATIVE, as-told), and a comprehensible question turn is determined over
|
||||||
|
# realized knowledge. This is the "one continuous life" telos made real — a
|
||||||
|
# conversation accumulates knowledge it can recall and reason over. OFF by default
|
||||||
|
# (one-shot/eval runtimes don't accrue); the production L10 process enables it
|
||||||
|
# alongside persist_session_state so accrued facts survive reboot. Realization is
|
||||||
|
# SESSION memory (immediate), NOT ratified learning — it proposes nothing; the
|
||||||
|
# teaching/review HITL path is untouched.
|
||||||
|
accrue_realized_knowledge: bool = False
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_IDENTITY_PACK: str = "default_general_v1"
|
DEFAULT_IDENTITY_PACK: str = "default_general_v1"
|
||||||
DEFAULT_ETHICS_PACK: str = "default_general_ethics_v1"
|
DEFAULT_ETHICS_PACK: str = "default_general_ethics_v1"
|
||||||
|
|
|
||||||
134
tests/test_inline_realization.py
Normal file
134
tests/test_inline_realization.py
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
"""Step B — inline realization in the live turn loop.
|
||||||
|
|
||||||
|
A conversation ACCRUES knowledge: a declarative turn realizes a fact into the held
|
||||||
|
self; a question turn is determined over realized knowledge — answered as-told, or
|
||||||
|
refused (open-world). Gated by ``accrue_realized_knowledge``; off by default leaves the
|
||||||
|
surface contract and behavior unchanged. With persistence on, the accrued fact survives
|
||||||
|
reboot — the "one continuous life" made real across an interruption.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import replace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from chat.runtime import ChatRuntime
|
||||||
|
from core.config import RuntimeConfig
|
||||||
|
|
||||||
|
|
||||||
|
def _rt(*, accrue: bool, persist: bool = False, engine_state_path=None) -> ChatRuntime:
|
||||||
|
config = replace(
|
||||||
|
RuntimeConfig(),
|
||||||
|
accrue_realized_knowledge=accrue,
|
||||||
|
persist_session_state=persist,
|
||||||
|
)
|
||||||
|
return ChatRuntime(config=config, engine_state_path=engine_state_path)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Accrual: the conversation accumulates knowledge it can reason over
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
def test_declarative_turn_accrues_a_realized_fact() -> None:
|
||||||
|
rt = _rt(accrue=True)
|
||||||
|
rt.chat("Truth is a concept.")
|
||||||
|
accrual = rt.last_turn_accrual()
|
||||||
|
assert accrual is not None and accrual.kind == "realized"
|
||||||
|
assert accrual.realized.created is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_question_turn_is_determined_from_accrued_knowledge() -> None:
|
||||||
|
rt = _rt(accrue=True)
|
||||||
|
rt.chat("Truth is a concept.")
|
||||||
|
rt.chat("Is truth a concept?")
|
||||||
|
accrual = rt.last_turn_accrual()
|
||||||
|
assert accrual is not None and accrual.kind == "determined"
|
||||||
|
det = accrual.determination
|
||||||
|
assert det.answer is True
|
||||||
|
assert det.basis == "as_told" # SPECULATIVE session memory, never "verified"
|
||||||
|
|
||||||
|
|
||||||
|
def test_untold_question_refuses_open_world() -> None:
|
||||||
|
rt = _rt(accrue=True)
|
||||||
|
rt.chat("Truth is a concept.")
|
||||||
|
rt.chat("Is truth a virtue?") # never told
|
||||||
|
det = rt.last_turn_accrual().determination
|
||||||
|
# Undetermined, never a fabricated answer or an asserted False (open-world).
|
||||||
|
assert type(det).__name__ == "Undetermined"
|
||||||
|
assert det.reason in {"not_entailed", "ungrounded"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_idempotent_retell_is_not_recreated() -> None:
|
||||||
|
rt = _rt(accrue=True)
|
||||||
|
rt.chat("Truth is a concept.")
|
||||||
|
assert rt.last_turn_accrual().realized.created is True
|
||||||
|
rt.chat("Truth is a concept.") # same fact again
|
||||||
|
assert rt.last_turn_accrual().realized.created is False # structural dedup
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Off by default: no behavior change, surface contract intact
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
def test_flag_off_does_not_accrue_and_keeps_surface() -> None:
|
||||||
|
rt = _rt(accrue=False)
|
||||||
|
response = rt.chat("Truth is a concept.")
|
||||||
|
assert rt.last_turn_accrual() is None
|
||||||
|
# the turn still returns a real surface (no behavior change when the flag is off)
|
||||||
|
assert isinstance(response.surface, str) and response.surface
|
||||||
|
|
||||||
|
|
||||||
|
def test_accrual_does_not_alter_the_surface() -> None:
|
||||||
|
# The same input yields the same surface with accrual on vs off (B-1 records,
|
||||||
|
# does not surface — the surface contract is untouched).
|
||||||
|
off = _rt(accrue=False).chat("Truth is a concept.")
|
||||||
|
on = _rt(accrue=True).chat("Truth is a concept.")
|
||||||
|
assert on.surface == off.surface
|
||||||
|
assert on.articulation_surface == off.articulation_surface
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# One continuous life: accrued knowledge survives reboot
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
def test_accrued_knowledge_survives_reboot(tmp_path) -> None:
|
||||||
|
esp = tmp_path / "engine_state"
|
||||||
|
# life 1: tell a fact, then the process ends
|
||||||
|
rt1 = _rt(accrue=True, persist=True, engine_state_path=esp)
|
||||||
|
rt1.chat("Truth is a concept.")
|
||||||
|
assert rt1.last_turn_accrual().kind == "realized"
|
||||||
|
|
||||||
|
# reboot: a NEW runtime over the SAME checkpoint resumes the SAME life
|
||||||
|
rt2 = _rt(accrue=True, persist=True, engine_state_path=esp)
|
||||||
|
rt2.chat("Is truth a concept?")
|
||||||
|
det = rt2.last_turn_accrual().determination
|
||||||
|
assert det.answer is True and det.basis == "as_told" # remembered across the reboot
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_accrual_persistence_without_persist_flag(tmp_path) -> None:
|
||||||
|
esp = tmp_path / "engine_state"
|
||||||
|
rt1 = _rt(accrue=True, persist=False, engine_state_path=esp)
|
||||||
|
rt1.chat("Truth is a concept.")
|
||||||
|
# without persistence the fact accrues only in-session; a reboot does not recall it
|
||||||
|
rt2 = _rt(accrue=True, persist=False, engine_state_path=esp)
|
||||||
|
rt2.chat("Is truth a concept?")
|
||||||
|
det = rt2.last_turn_accrual().determination
|
||||||
|
assert type(det).__name__ == "Undetermined"
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Robustness: accrual is additive, never crashes a turn
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
def test_uncomprehended_turn_is_a_clean_noop() -> None:
|
||||||
|
rt = _rt(accrue=True)
|
||||||
|
response = rt.chat("Hello there friend.") # no fact/question to accrue
|
||||||
|
assert response.surface # the turn still returns normally
|
||||||
|
accrual = rt.last_turn_accrual()
|
||||||
|
# either nothing comprehensible (kind="none") or a refusal that realized nothing
|
||||||
|
assert accrual is None or accrual.kind in {"none", "realized", "determined"}
|
||||||
Loading…
Reference in a new issue