feat: meanwhile summary digest surfaces to next you-scene (T65)

This commit is contained in:
Joseph Doherty
2026-04-26 20:59:35 -04:00
parent c9d58b8229
commit a781732ee6
3 changed files with 502 additions and 10 deletions
+52 -1
View File
@@ -342,7 +342,7 @@ async def apply_scene_close_summary(
from chat.state.entities import get_bot, get_you
from chat.state.group_node import get_group_node
from chat.state.threads import list_open_threads
from chat.state.world import get_chat
from chat.state.world import get_chat, get_scene
you_entity = get_you(conn) or {"name": "you", "persona": ""}
you_name = you_entity.get("name", "you") or "you"
@@ -350,6 +350,15 @@ async def apply_scene_close_summary(
chat = get_chat(conn, chat_id) or {}
guest_bot_id = chat.get("guest_bot_id")
# T65: detect meanwhile child scenes via the migration-0011
# ``present_set_kind`` column. The mechanism is a single field read
# — meanwhile scenes carry ``"host_guest"``, regular you-scenes
# carry the default ``"you_host"``. We read this once up front so
# both the dialogue source and the post-summary digest emission
# branches can reference it.
closing_scene = get_scene(conn, scene_id) or {}
is_meanwhile = closing_scene.get("present_set_kind") == "host_guest"
dialogue = _read_recent_dialogue(conn, chat_id)
# T58.1: build the "Key quotes:" suffix BEFORE the per-POV rewrites
@@ -415,6 +424,36 @@ async def apply_scene_close_summary(
},
)
# T65: when the closing scene was a meanwhile child (host_guest
# present set), generate an omniscient briefing for the absent
# "you" and queue it as a pending digest. We reuse summarize_scene
# with a narrator persona so the digest text is shaped by the same
# classifier — only the ``summary`` field is consumed downstream.
# Emitted AFTER per-POV summaries land so witness memories carry
# their own POV text first; this mirrors how group_node_updated is
# ordered relative to the per-POV writes above.
if is_meanwhile:
digest_pov = await summarize_scene(
client,
model=classifier_model,
bot_name="Narrator",
bot_persona=_MEANWHILE_DIGEST_PERSONA,
you_name=you_name,
prior_edge_summary="",
dialogue=dialogue,
timeout_s=timeout_s,
)
if digest_pov.summary:
append_and_apply(
conn,
kind="meanwhile_digest_created",
payload={
"chat_id": chat_id,
"scene_id": scene_id,
"summary": digest_pov.summary,
},
)
# T58.2: thread detection on close. Reuses the dialogue we already
# gathered for per-POV summarization — same {speaker, text} shape
# detect_threads expects. Failure-tolerant: classify() returns the
@@ -491,6 +530,18 @@ _GROUP_MERGE_SYSTEM = (
)
# T65: meanwhile-scene digest. The "you" player was absent during this
# scene; the digest is a short neutral briefing they'll read on the next
# you-scene resume. Reuses the ScenePOVSummary schema so the same
# `summarize_scene` helper can be called with a different persona — only
# the ``summary`` field is used downstream.
_MEANWHILE_DIGEST_PERSONA = (
"an omniscient narrator briefing the absent player in 2-3 neutral "
"sentences on what happened while they were away — no editorializing, "
"no second-person address"
)
async def merge_group_summary(
client: LLMClient,
*,