feat: prompt assembly renders active events + open threads (T60)

This commit is contained in:
Joseph Doherty
2026-04-26 20:34:26 -04:00
parent 83f94a4325
commit 21c4ffa63c
2 changed files with 219 additions and 6 deletions
+92 -1
View File
@@ -12,12 +12,14 @@ import pytest
from chat.db.connection import open_db
from chat.db.migrate import apply_migrations
from chat.eventlog.log import append_event
from chat.eventlog.log import append_and_apply, append_event
from chat.eventlog.projector import project
import chat.state.entities # noqa: F401 (registers handlers)
import chat.state.edges # noqa: F401
import chat.state.memory # noqa: F401
import chat.state.world # noqa: F401
import chat.state.events # noqa: F401
import chat.state.threads # noqa: F401
from chat.llm.client import Message
from chat.services.prompt import assemble_narrative_prompt
@@ -761,3 +763,92 @@ def test_assemble_with_tight_budget_drops_guest_activity_first(tmp_path):
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
assert len(enc.encode(body)) <= 340
# ---------------------------------------------------------------------------
# Task 60: Active events + open threads in prompt assembly
# ---------------------------------------------------------------------------
def test_assemble_with_no_events_or_threads_omits_blocks(tmp_path):
"""Regression: with the basic 2-entity scenario (no events seeded, no
threads seeded), the assembled prompt must NOT contain the
``Active events:`` or ``Open threads:`` headers — both blocks are
omit-when-empty."""
db = tmp_path / "t.db"
apply_migrations(db)
with open_db(db) as conn:
_seed_basic(conn)
msgs = assemble_narrative_prompt(
conn,
chat_id="chat_bot_a",
speaker_bot_id="bot_a",
recent_dialogue=[],
retrieved_memory_summaries=[],
)
body = msgs[0].content
assert "Active events:" not in body
assert "Open threads:" not in body
def test_assemble_with_active_events_renders_block(tmp_path):
"""Seed a planned event then transition it to active; the assembled
prompt should render the ``Active events:`` block listing the event
by kind."""
db = tmp_path / "t.db"
apply_migrations(db)
with open_db(db) as conn:
_seed_basic(conn)
# event_planned then event_started → status="active". Use
# append_and_apply because _seed_basic already projected; calling
# project() again would replay every prior event (and trip
# UNIQUE constraints on chat_created etc.).
append_and_apply(conn, kind="event_planned", payload={
"event_id": "evt_park",
"chat_id": "chat_bot_a",
"kind": "date_at_park",
"props": {"location": "Riverside Park", "vibe": "casual"},
"planned_for": "2026-04-30T18:00:00+00:00",
})
append_and_apply(conn, kind="event_started", payload={
"event_id": "evt_park",
"started_at": "2026-04-30T18:05:00+00:00",
})
msgs = assemble_narrative_prompt(
conn,
chat_id="chat_bot_a",
speaker_bot_id="bot_a",
recent_dialogue=[],
retrieved_memory_summaries=[],
)
body = msgs[0].content
assert "Active events:" in body
assert "date_at_park" in body
def test_assemble_with_open_thread_renders_block(tmp_path):
"""Seed a single open thread; the assembled prompt should render the
``Open threads:`` block listing the thread by title."""
db = tmp_path / "t.db"
apply_migrations(db)
with open_db(db) as conn:
_seed_basic(conn)
# _seed_basic already projected; use append_and_apply for the
# post-seed event so we don't re-trigger UNIQUE constraint
# collisions on the prior chat_created/etc. events.
append_and_apply(conn, kind="thread_opened", payload={
"thread_id": "thr_job",
"chat_id": "chat_bot_a",
"title": "Maya's job hunt",
"summary": "Maya is looking for a new job",
})
msgs = assemble_narrative_prompt(
conn,
chat_id="chat_bot_a",
speaker_bot_id="bot_a",
recent_dialogue=[],
retrieved_memory_summaries=[],
)
body = msgs[0].content
assert "Open threads:" in body
assert "Maya's job hunt" in body