bfb81428ab
Complete the consolidation pipeline: summaries -> profile + eras -> narrative.
- memory: eras table (per-month digests) + Era, summaries_by_month, store_era,
list_eras, recall_eras; narrative table + set/get_narrative
- lyra/era.py (lyra-era): groups session gists by the month the session occurred
(real timestamps) and map-reduces each month into a "what was happening" digest
- lyra/narrative.py (lyra-narrative): distills profile + recent eras into the
current arc/trends/callbacks ("remember when…", "you're trending toward…")
- chat.build_messages injects the narrative alongside the profile
Verified on the real corpus: 17 monthly eras (Dec 2024-Jun 2026) + a narrative
that surfaces specific callbacks (the $573 Hollywood session, 4 years sober).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2.4 KiB
Python
67 lines
2.4 KiB
Python
"""Narrative engine (consolidation step 4): the current arc, trends, callbacks.
|
|
|
|
Where the profile is timeless ("who Brian is"), the narrative is time-aware
|
|
("what's going on lately, where things are trending"). It distills the profile
|
|
plus the most recent monthly era digests into the current story — recent focus,
|
|
notable trends or changes, mood/arc, and a few specific callbacks worth
|
|
referencing. Injected into chat so Lyra follows along like a friend who's been
|
|
paying attention. Runs on the consolidation backend (MI50 in steady state).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
|
|
from lyra import config, llm, logbus, memory
|
|
from lyra.llm import Backend, Message
|
|
|
|
RECENT_ERAS = 4
|
|
|
|
_PROMPT = """You are distilling the CURRENT narrative about Brian — what a close \
|
|
friend who has been following along would keep in mind right now. From his profile \
|
|
and recent monthly digests below, write: what he's been focused on lately, any \
|
|
notable trends or changes (improving, slipping, new patterns), his current arc and \
|
|
mood, and 2-4 specific things worth referencing back to him ("remember when…"). \
|
|
Third person, referring to him as "Brian". 6-10 sentences. This is a memory note, \
|
|
not a reply. No preamble."""
|
|
|
|
|
|
def rebuild_narrative(backend: Backend | None = None) -> str | None:
|
|
"""(Re)derive the current narrative from the profile + recent era digests."""
|
|
backend = backend or config.load().summary_backend
|
|
profile = memory.get_profile()
|
|
eras = memory.list_eras()
|
|
if not profile and not eras:
|
|
return None
|
|
|
|
parts = []
|
|
if profile:
|
|
parts.append("PROFILE (timeless):\n" + profile)
|
|
recent = eras[-RECENT_ERAS:]
|
|
if recent:
|
|
parts.append(
|
|
"RECENT MONTHS (oldest first):\n"
|
|
+ "\n\n".join(f"[{e.month}]\n{e.content}" for e in recent)
|
|
)
|
|
body = "\n\n".join(parts)
|
|
|
|
messages: list[Message] = [
|
|
{"role": "system", "content": _PROMPT},
|
|
{"role": "user", "content": body},
|
|
]
|
|
narrative = llm.complete(messages, backend=backend)
|
|
memory.set_narrative(narrative)
|
|
logbus.log("info", "narrative rebuilt", chars=len(narrative), eras=len(recent))
|
|
return narrative
|
|
|
|
|
|
def main() -> int:
|
|
narrative = rebuild_narrative()
|
|
if narrative is None:
|
|
print("Need a profile and/or eras first — run lyra-profile and lyra-era.")
|
|
return 1
|
|
print(narrative)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|