diff --git a/docs/decisions/ADR-0145-energy-modulated-surface-readback.md b/docs/decisions/ADR-0145-energy-modulated-surface-readback.md new file mode 100644 index 00000000..e2908c84 --- /dev/null +++ b/docs/decisions/ADR-0145-energy-modulated-surface-readback.md @@ -0,0 +1,64 @@ +# ADR-0145: Energy-Modulated Vault Surface Readback + +**Status:** Proposed +**Date:** 2026-05-25 +**Related:** ADR-0006 (field energy operator), ADR-0004 (vault), W-004 (rethaw) + +--- + +## Context + +Under the current architecture: +- Vault recall re-thaws to energy class `E2` (defined in W-004). +- The realized output surface currently ignores the energy class of the grounded turn. +- ADR-0006 §Integration Points explicitly specifies that surface readback should be modulated by the energy class of the grounding source. + +This creates a gap where vault-grounded turns are realized without their appropriate energy-modulated prefix, violating the specification of ADR-0006. + +--- + +## Decision + +To align surface readback with the energy-modulation requirements of ADR-0006: + +1. **Energy Modulation Function:** + Implement `energy_modulated_surface(base: str, energy_class: EnergyClass | str | None) -> str` in [realizer.py](file:///Users/kaizenpro/.gemini/antigravity/worktrees/core/adr-w005-surface-readback/generate/realizer.py). This function will prepend the appropriate prefix based on the energy class: + + | Energy Class | Prefix | Example Output | + |---|---|---| + | `E0` | `"From memory: "` | `"From memory: {surface}"` | + | `E1` | `"I seem to recall: "` | `"I seem to recall: {surface}"` | + | `E2` | `"I recall: "` | `"I recall: {surface}"` | + | `E3`, `E4` | *None* | `"{surface}"` | + + If the base string is empty, the returned value remains empty. + +2. **Threading the Energy Class:** + - Add `recall_energy_class: str | None` to `CognitiveTurnResult` in [result.py](file:///Users/kaizenpro/.gemini/antigravity/worktrees/core/adr-w005-surface-readback/core/cognition/result.py). + - Thread the `recall_energy_class` from the pipeline to `ChatResponse` in [runtime.py](file:///Users/kaizenpro/.gemini/antigravity/worktrees/core/adr-w005-surface-readback/chat/runtime.py). + +3. **Modulation Application:** + Apply the modulation explicitly in [runtime.py](file:///Users/kaizenpro/.gemini/antigravity/worktrees/core/adr-w005-surface-readback/chat/runtime.py) on grounding pathways where `grounding_source == "vault"`. + +--- + +## What is NOT done + +- **E3/E4 Vault Modulation:** While the prefix table handles `E3` and `E4` by prepending nothing, no current vault recall pathways produce energy states reaching `E3` or `E4`. +- **Readback from Intent:** Rules relating to `readback_from_intent` in `runtime_rules.py` are deferred to W-006 scope. + +--- + +## Consequences + +- Surfaces retrieved from the vault (which default to `E2` on re-thaw) will now be prefixed with `"I recall: "`. +- Pack-grounded and teaching-grounded surfaces remain unaffected and will not receive energy modulation prefixes. +- `recall_energy_class` is exposed as an inspectable property on `ChatResponse` and `CognitiveTurnResult`. + +--- + +## References + +- **ADR-0006**: Field Energy Operator +- **ADR-0004**: Vault Design +- **W-004**: Vault Re-thaw diff --git a/tests/test_adr_0145_energy_modulated_surface.py b/tests/test_adr_0145_energy_modulated_surface.py new file mode 100644 index 00000000..f59fb3bc --- /dev/null +++ b/tests/test_adr_0145_energy_modulated_surface.py @@ -0,0 +1,80 @@ +"""Tests for ADR-0145: Energy-Modulated Vault Surface Readback. + +These tests pin the contract that surface readback is modulated by the energy +class of the grounding source. In particular, vault-grounded turns should prepended +with energy-dependent prefixes, and the recall energy class must be threaded to +ChatResponse. +""" + +from __future__ import annotations + +import pytest + +from chat.runtime import ChatRuntime, ChatResponse +from core.physics.energy import EnergyClass +from generate.realizer import energy_modulated_surface + + +def test_energy_modulated_surface_e2_prepends_recall_prefix() -> None: + assert ( + energy_modulated_surface("Light is coherence.", EnergyClass.E2) + == "I recall: Light is coherence." + ) + + +def test_energy_modulated_surface_e0_prepends_from_memory() -> None: + assert ( + energy_modulated_surface("Truth is settled.", EnergyClass.E0) + == "From memory: Truth is settled." + ) + + +def test_energy_modulated_surface_e1_prepends_seem_to_recall() -> None: + result = energy_modulated_surface("Light is coherence.", EnergyClass.E1) + assert result.startswith("I seem to recall: ") + assert result == "I seem to recall: Light is coherence." + + +def test_energy_modulated_surface_e3_no_prefix() -> None: + assert ( + energy_modulated_surface("Clarity.", EnergyClass.E3) + == "Clarity." + ) + + +def test_energy_modulated_surface_empty_base_unchanged() -> None: + assert energy_modulated_surface("", EnergyClass.E2) == "" + + +def test_vault_grounded_response_has_recall_prefix() -> None: + runtime = ChatRuntime() + # Establish a turn state + runtime.chat("logos arche") + + # Store the turn manually in the session vault + runtime.session.vault.store(runtime.session.state.F, metadata={"role": "assistant"}) + + # Recall the turn + response = runtime.chat("logos arche") + + assert response.grounding_source == "vault" + assert response.surface.startswith("I recall: ") + assert response.recall_energy_class == "E2" + + +def test_pack_grounded_response_has_no_recall_prefix() -> None: + runtime = ChatRuntime() + response = runtime.chat("What is truth?") + + assert response.grounding_source in {"pack", "teaching"} + assert not response.surface.startswith("I recall: ") + assert response.recall_energy_class is None + + +def test_recall_energy_class_is_e2_on_vault_hit() -> None: + runtime = ChatRuntime() + runtime.chat("logos arche") + runtime.session.vault.store(runtime.session.state.F, metadata={"role": "assistant"}) + response = runtime.chat("logos arche") + + assert response.recall_energy_class == "E2"