Merge pull request #601 from AssetOverflow/feat/surface-determination

feat(runtime): surface the determination — the engine answers from accrued knowledge (Step B-2)
This commit is contained in:
Shay 2026-06-06 11:01:48 -07:00 committed by GitHub
commit ea4ed4abb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 168 additions and 4 deletions

View file

@ -950,9 +950,29 @@ class ChatRuntime:
# not surfaced). # not surfaced).
if self.config.accrue_realized_knowledge: if self.config.accrue_realized_knowledge:
self._accrue_in_turn(self._last_input_text) self._accrue_in_turn(self._last_input_text)
response = self._maybe_surface_determination(response)
self.checkpoint_engine_state() self.checkpoint_engine_state()
return response 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: def last_turn_accrual(self) -> TurnAccrual | None:
"""The most recent turn's inline-realization outcome (Step B), or None when """The most recent turn's inline-realization outcome (Step B), or None when
accrual is off or did not complete. Introspection only never surfaced.""" 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: Current selection policy:
```text ```text
surface = articulation_surface (when no unknown-domain gate fired) surface = determination_surface (when accrue_realized_knowledge AND the turn
surface = _UNKNOWN_DOMAIN_SURFACE (when the gate fired) DETERMINED an answer over realized knowledge)
walk_surface = retained telemetry/evidence (always) 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 ### 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 Future realizer work may change the selection policy, but must update this
document and the contract tests in the same PR. 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) ### Refusal contract (ADR-0024 Phase 2)
When the inner-loop admissibility check leaves no admissible destination 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).""" """DETERMINE — reason over realized structure → assert (as-told) / refuse (roadmap Step 4)."""
from generate.determine.determine import Determined, Undetermined, determine 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")