fix: witness role parametric in prompt assembly (T71.1)
Phase 2 T46 pinned the witness mask contract on search_memories with a witness_role parameter (host/guest/you). The prompt-assembly call site in assemble_narrative_prompt was hardcoded to "host", which silently returned the wrong rows when the speaker was the guest bot. Derive the witness role from chat membership via a new private helper _witness_role_for(speaker_bot_id, host_bot_id), and apply it at the search_memories call. Behaviour is identical when the speaker is the host (or when no guest is present); the fix is load-bearing only when the guest bot is the speaker — exactly the scenario Phase 2 T43 added support for. Tests: pin both directions (host-as-speaker and guest-as-speaker) by patching the imported search_memories reference and asserting the witness_role argument the call site emits.
This commit is contained in:
+18
-1
@@ -273,6 +273,18 @@ def _resolve_previous_scene_summary(
|
||||
return mem[0]
|
||||
|
||||
|
||||
def _witness_role_for(speaker_bot_id: str, host_bot_id: str | None) -> str:
|
||||
"""Return the witness POV role for the speaker's memory query.
|
||||
|
||||
The host bot of a chat queries memories with ``witness_role="host"``;
|
||||
the guest bot queries with ``witness_role="guest"``. Phase 2 T46
|
||||
pinned the contract on ``search_memories``; this helper applies it
|
||||
at the call site so a guest-as-speaker doesn't silently retrieve
|
||||
memories under the wrong POV mask.
|
||||
"""
|
||||
return "host" if speaker_bot_id == host_bot_id else "guest"
|
||||
|
||||
|
||||
def _resolve_addressee(
|
||||
conn: Connection, addressee: str, you: dict | None
|
||||
) -> tuple[str, str]:
|
||||
@@ -433,7 +445,12 @@ def assemble_narrative_prompt(
|
||||
memory_summaries = []
|
||||
if query:
|
||||
try:
|
||||
hits = search_memories(conn, speaker_bot_id, "host", query, k=4)
|
||||
witness_role = _witness_role_for(
|
||||
speaker_bot_id, chat.get("host_bot_id")
|
||||
)
|
||||
hits = search_memories(
|
||||
conn, speaker_bot_id, witness_role, query, k=4
|
||||
)
|
||||
memory_summaries = [h["pov_summary"] for h in hits]
|
||||
except Exception:
|
||||
memory_summaries = []
|
||||
|
||||
@@ -452,6 +452,68 @@ def test_assemble_when_speaker_is_guest_orients_edges_correctly(tmp_path):
|
||||
assert "68/100" in body
|
||||
|
||||
|
||||
def test_speaker_is_guest_uses_guest_witness_role(tmp_path, monkeypatch):
|
||||
"""T71.1: when the guest is the speaker, ``search_memories`` is
|
||||
called with ``witness_role="guest"``, not the previously-hardcoded
|
||||
``"host"``. Pins the parametric witness role at the prompt call site
|
||||
so guest-as-speaker honours the witness mask via Phase 2 T46.
|
||||
"""
|
||||
db = tmp_path / "t.db"
|
||||
apply_migrations(db)
|
||||
captured: dict = {}
|
||||
|
||||
def _fake_search(conn, owner_id, witness_role, query, k=4):
|
||||
captured["owner_id"] = owner_id
|
||||
captured["witness_role"] = witness_role
|
||||
captured["query"] = query
|
||||
return []
|
||||
|
||||
# Patch the imported reference inside the prompt module so the
|
||||
# production call site uses the fake.
|
||||
import chat.services.prompt as prompt_mod
|
||||
monkeypatch.setattr(prompt_mod, "search_memories", _fake_search)
|
||||
|
||||
with open_db(db) as conn:
|
||||
_seed_with_guest(conn)
|
||||
# Guest as speaker — must request memories with witness_role="guest".
|
||||
assemble_narrative_prompt(
|
||||
conn,
|
||||
chat_id="chat_bot_a",
|
||||
speaker_bot_id="bot_b",
|
||||
recent_dialogue=[],
|
||||
# retrieved_memory_summaries=None forces the search path.
|
||||
retrieved_memory_summaries=None,
|
||||
)
|
||||
assert captured["owner_id"] == "bot_b"
|
||||
assert captured["witness_role"] == "guest"
|
||||
|
||||
|
||||
def test_speaker_is_host_uses_host_witness_role(tmp_path, monkeypatch):
|
||||
"""T71.1 (regression): host-as-speaker still queries with
|
||||
``witness_role="host"``."""
|
||||
db = tmp_path / "t.db"
|
||||
apply_migrations(db)
|
||||
captured: dict = {}
|
||||
|
||||
def _fake_search(conn, owner_id, witness_role, query, k=4):
|
||||
captured["witness_role"] = witness_role
|
||||
return []
|
||||
|
||||
import chat.services.prompt as prompt_mod
|
||||
monkeypatch.setattr(prompt_mod, "search_memories", _fake_search)
|
||||
|
||||
with open_db(db) as conn:
|
||||
_seed_with_guest(conn)
|
||||
assemble_narrative_prompt(
|
||||
conn,
|
||||
chat_id="chat_bot_a",
|
||||
speaker_bot_id="bot_a", # host as speaker
|
||||
recent_dialogue=[],
|
||||
retrieved_memory_summaries=None,
|
||||
)
|
||||
assert captured["witness_role"] == "host"
|
||||
|
||||
|
||||
def test_assemble_with_tight_budget_drops_guest_activity_first(tmp_path):
|
||||
"""Under tight budget MUST blocks survive but SHOULD-tier guest
|
||||
activity is dropped first."""
|
||||
|
||||
Reference in New Issue
Block a user