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:
commit
ea4ed4abb3
5 changed files with 168 additions and 4 deletions
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
26
generate/determine/render.py
Normal file
26
generate/determine/render.py
Normal 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}."
|
||||
94
tests/test_determination_surface.py
Normal file
94
tests/test_determination_surface.py
Normal 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")
|
||||
Loading…
Reference in a new issue