feat(runtime): surface the determination — the engine answers from accrued knowledge (Step B-2)

When accrue_realized_knowledge is on and a question turn is Determined over realized
knowledge, the user-facing surface IS that answer (generate.determine.render_determination):
'Yes — as I was told, truth is a concept.' The realizer's articulation_surface is
RETAINED as evidence — the determination is a user-facing SELECTION (like the
unknown-domain gate), not a rewrite. An Undetermined turn keeps the default
articulation surface (the honest 'I don't know'); flag-off never takes this path.

Basis is rendered honestly: SPECULATIVE grounds read 'as I was told', never 'verified'
(D0 only asserts answer=True, so the surface is an affirmation — no fabricated or
asserted-False string). Selection only: no field op, no normalization, proposes no
learning (HITL untouched).

Updates docs/runtime_contracts.md selection policy + adds the contract test in the
same PR (per the surface-contract discipline).
This commit is contained in:
Shay 2026-06-06 10:52:53 -07:00
parent 70902393a7
commit dc06bd5a3c
5 changed files with 168 additions and 4 deletions

View file

@ -950,9 +950,29 @@ class ChatRuntime:
# not surfaced).
if self.config.accrue_realized_knowledge:
self._accrue_in_turn(self._last_input_text)
response = self._maybe_surface_determination(response)
self.checkpoint_engine_state()
return response
def _maybe_surface_determination(self, response: ChatResponse) -> ChatResponse:
"""Step B-2 — when the turn DETERMINED an answer over realized knowledge,
select that determination as the user-facing ``surface``. The realizer's
``articulation_surface`` is retained as evidence (the determination does not
replace it). An ``Undetermined`` turn keeps the default articulation surface
(the honest "I don't know"). Off-flag turns never reach here. See the
ChatResponse selection policy in ``docs/runtime_contracts.md``.
"""
accrual = self._last_turn_accrual
if accrual is None or accrual.kind != "determined":
return response
from generate.determine import Determined, render_determination
if not isinstance(accrual.determination, Determined):
return response # Undetermined → keep the default surface
return replace(
response, surface=render_determination(accrual.determination)
)
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."""

View file

@ -34,9 +34,12 @@ changing tests or silently downgrading the invariant.
Current selection policy:
```text
surface = articulation_surface (when no unknown-domain gate fired)
surface = _UNKNOWN_DOMAIN_SURFACE (when the gate fired)
walk_surface = retained telemetry/evidence (always)
surface = determination_surface (when accrue_realized_knowledge AND the turn
DETERMINED an answer over realized knowledge)
surface = _UNKNOWN_DOMAIN_SURFACE (when the unknown-domain gate fired)
surface = articulation_surface (otherwise — the default)
walk_surface = retained telemetry/evidence (always)
articulation_surface = retained always (the determination surface does not replace it)
```
### Unknown-domain gate honour
@ -53,6 +56,26 @@ gated. This closes `evals/calibration/gaps.md` Finding 2.
Future realizer work may change the selection policy, but must update this
document and the contract tests in the same PR.
### Determination surface (Step B-2)
When `accrue_realized_knowledge` is enabled and a question turn is **Determined**
over realized knowledge (`generate.determine.determine` returns a `Determined`),
the user-facing `surface` is the rendered determination
(`generate.determine.render_determination`) — the engine answers *from what it
accrued in the conversation*. The basis is rendered **honestly**: SPECULATIVE
grounds (today's only case) read as "as I was told", never "verified"; D0 only
asserts `answer=True`, so the surface is an affirmation, never a fabricated or
asserted-False string.
The realizer's `articulation_surface` is **retained unchanged** as evidence — the
determination is a user-facing *selection*, exactly like the unknown-domain gate,
not a rewrite of the realizer output. An **Undetermined** turn (refused,
open-world) keeps the default articulation surface — the honest "I don't know".
With the flag off, this path is never taken and the surface is unchanged. This is
selection only: it adds no field op, no normalization, and proposes no learning
(`accrue_in_turn` writes SESSION memory through the INV-21 vault writer; the HITL
teaching path is untouched).
### Refusal contract (ADR-0024 Phase 2)
When the inner-loop admissibility check leaves no admissible destination

View file

@ -1,5 +1,6 @@
"""DETERMINE — reason over realized structure → assert (as-told) / refuse (roadmap Step 4)."""
from generate.determine.determine import Determined, Undetermined, determine
from generate.determine.render import render_determination
__all__ = ["Determined", "Undetermined", "determine"]
__all__ = ["Determined", "Undetermined", "determine", "render_determination"]

View file

@ -0,0 +1,26 @@
"""Deterministic surface rendering for a Determined answer (Step B-2).
Turns a ``Determined`` into the user-facing string the runtime surfaces. The basis is
rendered HONESTLY: SPECULATIVE grounds (today's only case) render as "as I was told",
never "verified" only COHERENT-admissible grounds (not yet reachable) would. D0 only
ever asserts ``answer=True``, so the surface is an affirmation; there is no fabricated
or asserted-False surface to render.
"""
from __future__ import annotations
from generate.determine.determine import Determined
#: Predicate → surface phrase. ``member`` reads as "is a"; the relational pack
#: predicates fall back to their lemma with underscores spaced ("less_than" → "less
#: than"). Closed and deterministic; an unknown predicate still renders legibly.
_PREDICATE_PHRASE: dict[str, str] = {
"member": "is a",
}
def render_determination(d: Determined) -> str:
"""The user-facing surface for a Determined answer — deterministic, honest basis."""
phrase = _PREDICATE_PHRASE.get(d.predicate, d.predicate.replace("_", " "))
qualifier = "as I was told" if d.basis == "as_told" else "verified"
return f"Yes — {qualifier}, {d.subject} {phrase} {d.object}."

View file

@ -0,0 +1,94 @@
"""Step B-2 — surface the determination: the engine answers from accrued knowledge.
When a question turn is Determined over realized knowledge, the user-facing ``surface``
IS that answer (rendered honestly: "as I was told", never "verified"). The realizer's
``articulation_surface`` is retained as evidence the determination is a selection, not
a rewrite. Undetermined and flag-off keep the default surface. This is the contract test
for the selection policy in docs/runtime_contracts.md.
"""
from __future__ import annotations
from dataclasses import replace
import pytest
from chat.runtime import ChatRuntime
from core.config import RuntimeConfig
from generate.determine import Determined, render_determination
# --------------------------------------------------------------------------- #
# render_determination — deterministic, honest basis (unit, no runtime)
# --------------------------------------------------------------------------- #
def test_render_member_reads_naturally() -> None:
d = Determined(answer=True, basis="as_told", predicate="member",
subject="truth", object="concept", grounds=())
assert render_determination(d) == "Yes — as I was told, truth is a concept."
def test_render_relational_predicate_is_legible() -> None:
d = Determined(answer=True, basis="as_told", predicate="parent_of",
subject="alice", object="bob", grounds=())
assert render_determination(d) == "Yes — as I was told, alice parent of bob."
def test_render_basis_is_honest_never_overclaims() -> None:
# SPECULATIVE grounds → "as I was told" (today's only case); COHERENT would be the
# only thing that renders "verified". The render must never overclaim.
as_told = Determined(True, "as_told", "member", "truth", "concept", ())
assert "as I was told" in render_determination(as_told)
assert "verified" not in render_determination(as_told)
# --------------------------------------------------------------------------- #
# Surface selection in the live turn (integration)
# --------------------------------------------------------------------------- #
@pytest.fixture(scope="module")
def accrued_runtime() -> ChatRuntime:
rt = ChatRuntime(
config=replace(RuntimeConfig(), accrue_realized_knowledge=True),
no_load_state=True,
)
rt.chat("Truth is a concept.") # accrue the fact this module's questions probe
return rt
def _default_surface(text: str) -> str:
rt = ChatRuntime(config=RuntimeConfig(), no_load_state=True)
rt.chat("Truth is a concept.")
return rt.chat(text).surface
def test_determined_question_surfaces_the_answer(accrued_runtime) -> None:
response = accrued_runtime.chat("Is truth a concept?")
assert response.surface == "Yes — as I was told, truth is a concept."
# the realizer surface is retained as evidence, distinct from the selection
assert response.articulation_surface
assert response.surface != response.articulation_surface
def test_determined_surface_does_not_rewrite_articulation(accrued_runtime) -> None:
# The determination SELECTS the surface; the articulation_surface is the same
# evidence the default (flag-off) turn produces.
response = accrued_runtime.chat("Is truth a concept?")
off = ChatRuntime(config=RuntimeConfig(), no_load_state=True)
off.chat("Truth is a concept.")
off_response = off.chat("Is truth a concept?")
assert response.articulation_surface == off_response.articulation_surface
def test_undetermined_question_keeps_default_surface(accrued_runtime) -> None:
response = accrued_runtime.chat("Is truth a virtue?") # never told → Undetermined
# not an affirmation; the honest default surface (== the flag-off surface)
assert not response.surface.startswith("Yes — as I was told")
assert response.surface == _default_surface("Is truth a virtue?")
def test_flag_off_never_surfaces_a_determination() -> None:
surface = _default_surface("Is truth a concept?")
assert not surface.startswith("Yes — as I was told")