Session logging & post-session writer pass¶
How the familiar captures what just happened when a conversation ends — both as a raw transcript on disk and as a distilled update to its long-term memory.
Motivation¶
Two separate problems share the same trigger (a session ending) and deserve a single page:
- Keep the raw data forever. Conversation transcripts and the original (unmodified) character card should be preserved, so that if context-management strategies change in the future no data is lost to a smarter-later-on version of the bot. This is a "don't throw things away" principle.
- Distil the session into memory. Raw history isn't useful to the LLM in full — it's what
HistoryProvidercompresses into a rolling summary for the system prompt. On top of that, a cheap side-model writer pass reads the just-ended session, produces a session summary file, and proposes targeted edits to the relevantpeople/andtopics/files. This is the only code path that mutatesmemory/under normal operation (see Memory § writing into memory).
Both answer different questions: raw history answers "what was actually said?"; the writer pass answers "what should the familiar remember about this going forward?"
Raw history preservation¶
HistoryStoreis the source of truth for turns — partitioned by(familiar_id, channel_id)for the per-channel recent window and by(familiar_id,)for the global rolling summary.- The base character card is preserved by the unpacker as
memory/self/.original.png, alongside the unpacked per-field Markdown files. - No pruning policy. History rows stay forever unless the operator deletes them manually. Disk is cheap; lost transcripts are expensive.
Post-session writer pass¶
Implemented in src/familiar_connect/memory/writer.py (MemoryWriter) and scheduled by src/familiar_connect/memory/scheduler.py (MemoryWriterScheduler).
Triggers¶
The scheduler runs the writer on any of:
- Turn-count threshold. After
turn_threshold(default 50) new turns have accumulated past the last watermark,notify_turnfires a run. - Idle timeout. After
idle_timeoutseconds (default 1800) of silence, a timer-driven run checks for unsummarized turns. - Explicit flush.
flush()is called on unsubscribe events so an ending session is written out promptly rather than waiting for the idle timer.
A per-familiar asyncio.Lock serialises runs; overlapping triggers collapse into a single write.
Inputs¶
- The session's unsummarized turns from
HistoryStore, bounded by the writer watermark. - The familiar's current
memory/people/andmemory/topics/files for anyone who participated. - The current character card. The rest of
memory/is reachable viaMemoryStoreif the writer asks for it.
Channel context header¶
When the writer is constructed with a channel_context_lookup callable (wired to ConversationMonitor.format_channel_context in the normal process), the prompt transcript is prefixed with a ## Context block listing every distinct channel the turns came from — for example:
## Context
- #general
- #general -> feature-brainstorm
- forum:announcements -> hotfix-rollout
- DM:alice
- GroupDM:squad
- voice:#lounge
- stage:#announcements
- forum-root:#ideas
- category:#off-topic
The labels come from ConversationMonitor, which stores a ChannelContext(name, kind, parent_name) for each channel. The kind field controls the prefix style. Unknown channels (no context registered) are suppressed, keeping the block empty in test setups and during early boot.
Model¶
A cheap side-model LLM (LLMClient) configured alongside the other side-model calls (rolling summary, side-model gate).
Outputs¶
Three kinds of file mutation, all routed through MemoryStore so they hit the audit log under the memory_writer source:
- A new
sessions/<date>-<slot>.mdfile. One per session. The slot suffix disambiguates multiple sessions on one day. - Edits (append or rewrite) to
people/<platform>-<user_id>.mdfor each person who showed up. Updates impressions, adds notable moments, harvests new display names into the## Aliasessection (see Memory § multi-username handling). - Edits to
topics/<slug>.mdfor recurring subjects. Updates opinions, adds events, links back to the session file.
After a successful run the writer advances the watermark in HistoryStore so the same turns are never written twice.
Session boundary heuristics¶
Exact boundaries:
| Modality | Start | End |
|---|---|---|
| Text | First message after a quiet period (or after /subscribe-text) |
Explicit /unsubscribe-text, idle timeout, or turn-count threshold |
| Voice | /subscribe-my-voice joins the channel |
/unsubscribe-voice, idle timeout, or turn-count threshold |
The same session can legitimately span both modalities if someone typed in a voice channel's paired text channel. The writer treats interleaved voice and text turns as a single session; see Voice input § text input during a voice session.
Non-goals¶
- In-conversation memory writes. Letting the main LLM write to
memory/during a reply is strictly more powerful than the writer pass and strictly more dangerous (bot rewriting its memory in real time during latency-sensitive voice turns). Not in scope. If it comes later, it should be feature-flagged per character and audited via theMemoryStoreaudit log. - Automatic history pruning. Disk space is cheap; lost transcripts aren't.
- Cross-familiar writer passes. Each familiar's writer pass runs only against its own memory directory.
- Housekeeping passes (duplicate detection, conflict reconciliation, stale-belief flagging). A related but distinct roadmap item — see Memory § future add-ons.
Future work¶
- Human-in-the-loop review. The writer currently mutates files directly. A dry-run mode that writes proposed diffs to
sessions/.pending/for operator review would be safer for early iterations on a new familiar. - Partial-session recovery. If the bot crashes between the last turn and the next scheduler tick, the raw turns are still in
HistoryStorebut no writer pass has run. The watermark covers startup catch-up automatically for the next scheduled run; a dedicated startup sweep would just tighten the worst-case latency. - Integration with voice channel logging. Once Voice logging ships its live-edited transcript thread, the writer could read the thread as a human-readable cross-check alongside
HistoryStore.