179 lines
5.7 KiB
Python
179 lines
5.7 KiB
Python
"""Per-turn memory writes (T21).
|
|
|
|
After ``assistant_turn`` lands, the turn flow records a ``memory_written``
|
|
event for each present POV owner. Phase 1 single-bot turns only have the
|
|
host bot as a memory-store owner — ``you`` doesn't have a memory store in
|
|
v1 — so we write exactly one row per turn.
|
|
|
|
Phase 1 simplifications (per plan §11.1, T27 will refine):
|
|
|
|
- ``pov_summary`` is the assistant's raw narrative text. T27 rewrites at
|
|
scene close into per-POV summary form.
|
|
- ``significance`` defaults to ``1`` (Notable). T22's async significance
|
|
pass overwrites via a follow-up event.
|
|
- Witness flags are hard-coded ``[you=1, host=1, guest=0]``. Phase 2 will
|
|
derive them from ``chat.guest_bot_id`` once a guest can be present.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from sqlite3 import Connection
|
|
|
|
from chat.eventlog.log import append_and_apply
|
|
|
|
|
|
def record_turn_memory(
|
|
conn: Connection,
|
|
*,
|
|
chat_id: str,
|
|
host_bot_id: str,
|
|
narrative_text: str,
|
|
scene_id: int | None = None,
|
|
chat_clock_at: str | None = None,
|
|
source: str = "direct",
|
|
significance: int = 1,
|
|
) -> tuple[int, int | None]:
|
|
"""Append a ``memory_written`` event for the host bot's POV of this turn.
|
|
|
|
Uses :func:`chat.eventlog.log.append_and_apply` (not raw
|
|
:func:`append_event`) so the new memory row is projected immediately
|
|
without re-running prior non-idempotent handlers (e.g. ``edge_update``
|
|
deltas).
|
|
|
|
Returns ``(event_id, memory_id)``. ``event_id`` is the row id of the
|
|
just-appended ``memory_written`` event in ``event_log``. ``memory_id``
|
|
is the autoincrement PK of the corresponding ``memories`` row — these
|
|
are *different* numbers (event_log and memories use independent
|
|
rowid sequences) so callers needing to update significance or pin
|
|
state must use ``memory_id``. Falls back to ``None`` if the projected
|
|
row can't be located, which shouldn't happen but keeps the return
|
|
shape stable.
|
|
"""
|
|
payload: dict = {
|
|
"owner_id": host_bot_id,
|
|
"chat_id": chat_id,
|
|
"pov_summary": narrative_text,
|
|
"witness_you": 1,
|
|
"witness_host": 1,
|
|
"witness_guest": 0,
|
|
"source": source,
|
|
"reliability": 1.0,
|
|
"significance": significance,
|
|
"pinned": 0,
|
|
"auto_pinned": 0,
|
|
}
|
|
if scene_id is not None:
|
|
payload["scene_id"] = scene_id
|
|
if chat_clock_at is not None:
|
|
payload["chat_clock_at"] = chat_clock_at
|
|
|
|
event_id = append_and_apply(conn, kind="memory_written", payload=payload)
|
|
row = conn.execute(
|
|
"SELECT id FROM memories "
|
|
"WHERE owner_id = ? AND chat_id = ? "
|
|
"ORDER BY id DESC LIMIT 1",
|
|
(host_bot_id, chat_id),
|
|
).fetchone()
|
|
memory_id = row[0] if row else None
|
|
return event_id, memory_id
|
|
|
|
|
|
def _write_one_memory(
|
|
conn: Connection,
|
|
*,
|
|
owner_id: str,
|
|
chat_id: str,
|
|
narrative_text: str,
|
|
witness_you: int,
|
|
witness_host: int,
|
|
witness_guest: int,
|
|
scene_id: int | None,
|
|
chat_clock_at: str | None,
|
|
source: str,
|
|
significance: int,
|
|
) -> tuple[int, int | None]:
|
|
"""Append a single ``memory_written`` event for ``owner_id`` and return
|
|
``(event_id, memory_id)`` for the projected row."""
|
|
payload: dict = {
|
|
"owner_id": owner_id,
|
|
"chat_id": chat_id,
|
|
"pov_summary": narrative_text,
|
|
"witness_you": witness_you,
|
|
"witness_host": witness_host,
|
|
"witness_guest": witness_guest,
|
|
"source": source,
|
|
"reliability": 1.0,
|
|
"significance": significance,
|
|
"pinned": 0,
|
|
"auto_pinned": 0,
|
|
}
|
|
if scene_id is not None:
|
|
payload["scene_id"] = scene_id
|
|
if chat_clock_at is not None:
|
|
payload["chat_clock_at"] = chat_clock_at
|
|
|
|
event_id = append_and_apply(conn, kind="memory_written", payload=payload)
|
|
row = conn.execute(
|
|
"SELECT id FROM memories "
|
|
"WHERE owner_id = ? AND chat_id = ? "
|
|
"ORDER BY id DESC LIMIT 1",
|
|
(owner_id, chat_id),
|
|
).fetchone()
|
|
memory_id = row[0] if row else None
|
|
return event_id, memory_id
|
|
|
|
|
|
def record_turn_memory_for_present(
|
|
conn: Connection,
|
|
*,
|
|
chat_id: str,
|
|
host_bot_id: str,
|
|
guest_bot_id: str | None,
|
|
narrative_text: str,
|
|
scene_id: int | None = None,
|
|
chat_clock_at: str | None = None,
|
|
source: str = "direct",
|
|
significance: int = 1,
|
|
) -> dict[str, tuple[int, int | None]]:
|
|
"""Write a ``memory_written`` event for each present bot witness.
|
|
|
|
Host is always written. Guest is written iff ``guest_bot_id is not
|
|
None``. Witness flags are ``[you=1, host=1, guest=1]`` when a guest
|
|
is present, ``[you=1, host=1, guest=0]`` otherwise.
|
|
|
|
Returns a mapping ``{bot_id: (event_id, memory_id)}`` so callers can
|
|
look up the freshly-projected memory id per owner without re-querying
|
|
the database.
|
|
"""
|
|
witness_guest = 1 if guest_bot_id is not None else 0
|
|
|
|
result: dict[str, tuple[int, int | None]] = {}
|
|
result[host_bot_id] = _write_one_memory(
|
|
conn,
|
|
owner_id=host_bot_id,
|
|
chat_id=chat_id,
|
|
narrative_text=narrative_text,
|
|
witness_you=1,
|
|
witness_host=1,
|
|
witness_guest=witness_guest,
|
|
scene_id=scene_id,
|
|
chat_clock_at=chat_clock_at,
|
|
source=source,
|
|
significance=significance,
|
|
)
|
|
if guest_bot_id is not None:
|
|
result[guest_bot_id] = _write_one_memory(
|
|
conn,
|
|
owner_id=guest_bot_id,
|
|
chat_id=chat_id,
|
|
narrative_text=narrative_text,
|
|
witness_you=1,
|
|
witness_host=1,
|
|
witness_guest=1,
|
|
scene_id=scene_id,
|
|
chat_clock_at=chat_clock_at,
|
|
source=source,
|
|
significance=significance,
|
|
)
|
|
return result
|