"""Tests for TOK-06 active-inference retrieval gate (Plan 02-04 Task 2, D-26). D-26 contract: skip full pipeline_recall when expected free-energy reduction is less than 0.2 bits. Trivial cues (greetings, "thanks", very short strings) short-circuit to L0-only. """ from __future__ import annotations from datetime import datetime, timezone from uuid import uuid4 import pytest from iai_mcp.store import MemoryStore from iai_mcp.types import EMBED_DIM, MemoryRecord def test_theta_skip_constant(): from iai_mcp.gate import THETA_SKIP assert THETA_SKIP == 0.2 def test_efer_empty_is_zero(): from iai_mcp.gate import expected_free_energy_reduction assert expected_free_energy_reduction("") == 0.0 def test_efer_trivial_greeting_is_below_theta(): from iai_mcp.gate import THETA_SKIP, expected_free_energy_reduction for cue in ("hi", "hello", "thanks", "ok", "yes", "no"): val = expected_free_energy_reduction(cue) assert val < THETA_SKIP, f"cue={cue!r} val={val}" def test_efer_rich_is_above_theta(): from iai_mcp.gate import THETA_SKIP, expected_free_energy_reduction rich = ( "explain how CLS replay interacts with schema induction under " "monotropic attention" ) val = expected_free_energy_reduction(rich) assert val > THETA_SKIP def test_should_skip_retrieval_trivial(): from iai_mcp.gate import should_skip_retrieval skip, reason = should_skip_retrieval("hi") assert skip is True assert reason def test_should_skip_retrieval_informative(): from iai_mcp.gate import should_skip_retrieval skip, _reason = should_skip_retrieval( "What did we discuss about auth last week?" ) assert skip is False def test_should_skip_very_short_cue(): """Cues shorter than 3 chars always skip (no discriminable signal).""" from iai_mcp.gate import should_skip_retrieval skip, _ = should_skip_retrieval("a") assert skip is True skip, _ = should_skip_retrieval("") assert skip is True def test_pipeline_recall_skip_path_returns_minimal_response(tmp_path, monkeypatch): """When gate triggers, pipeline_recall must return the L0 record only.""" from iai_mcp import embed as embed_mod from iai_mcp.core import _seed_l0_identity, dispatch class _FakeEmbedder: DIM = EMBED_DIM DEFAULT_DIM = EMBED_DIM DEFAULT_MODEL_KEY = "fake" def __init__(self, *args, **kwargs): self.DIM = EMBED_DIM def embed(self, text: str) -> list[float]: return [1.0] + [0.0] * (EMBED_DIM - 1) def embed_batch(self, texts): return [self.embed(t) for t in texts] monkeypatch.setattr(embed_mod, "Embedder", _FakeEmbedder) store = MemoryStore(path=tmp_path) _seed_l0_identity(store) # Insert extra records so the pipeline branch would normally run. now = datetime.now(timezone.utc) for i in range(3): rec = MemoryRecord( id=uuid4(), tier="episodic", literal_surface=f"extra fact {i}", aaak_index="", embedding=[1.0] + [0.0] * (EMBED_DIM - 1), community_id=None, centrality=0.0, detail_level=2, pinned=False, stability=0.0, difficulty=0.0, last_reviewed=None, never_decay=False, never_merge=False, provenance=[], created_at=now, updated_at=now, tags=[], language="en", ) store.insert(rec) resp = dispatch(store, "memory_recall", {"cue": "hi", "session_id": "s-trivial"}) assert "budget_used" in resp # Retrieval skip reduces budget dramatically (<50 tokens typical). assert resp["budget_used"] < 200