Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

0064 - Session

  • Feature Name: Session System
  • Start Date: 2026-02-25
  • Discussion: #64
  • Crates: core, daemon

Summary

Append-only JSONL session persistence with compact markers, identity-based file naming, and an auto-injected message lifecycle that separates ephemeral context from durable history.

Motivation

An agent daemon needs conversation persistence that is simple, inspectable, and crash-safe. Database-backed persistence adds operational weight for what is fundamentally a sequential log. The session format must support:

  • Resuming conversations across daemon restarts.
  • Compaction — summarizing long histories without losing them.
  • Multiple identities — the same agent can talk to different users/platforms.
  • Ephemeral context injection — memory recall, environment blocks, and agent descriptions must be fresh each run, never accumulating in history.

Design

File format

Each session is a JSONL file. Line 1 is metadata, subsequent lines are messages or compact markers.

{"agent":"crab","created_by":"user","created_at":"...","title":"","uptime_secs":0}
{"role":"user","content":"hello"}
{"role":"assistant","content":"hi there"}
{"compact":"Summary of conversation so far..."}
{"role":"user","content":"what were we talking about?"}

Naming

Files live in a flat sessions/ directory: {agent}_{sender_slug}_{seq}.jsonl

  • sender_slug — sanitized identity (e.g. user, tg-12345).
  • seq — monotonically increasing per (agent, sender) pair.
  • After set_title, the file is renamed to append a title slug.

Compact markers

When history exceeds a threshold, the agent compacts: the LLM summarizes the conversation, and a {"compact":"..."} line is appended. On load, load_context reads from the last compact marker forward. The compact summary is injected as a {"role":"user"} message — the agent sees it as context, not as a special marker.

History before the last compact marker is archived in place — still in the file, but not loaded. Nothing is deleted.

Auto-injected messages

Messages marked auto_injected: true are:

  • Not persisted to JSONL (skipped in append_messages).
  • Stripped before each run (prevents accumulation).
  • Re-injected fresh via Hook::on_before_run() every execution.

This covers memory recall results, environment blocks, agent description lists, and working directory announcements. They must be current, not stale from a previous run.

Session identity

Sessions are bound to an (agent, sender) pair. find_latest_session scans the directory for the matching prefix and returns the highest seq number. New chats increment the seq.

Uptime tracking

Each session tracks uptime_secs — accumulated active time, persisted to the meta line. The meta line is rewritten by reading the full file and writing it back with the updated first line. This is the one non-append operation — it trades the append-only guarantee for keeping metadata current. Crash during rewrite can lose the meta line but not the conversation history (messages are append-only and survive).

Alternatives

SQLite. Adds a dependency and operational surface for what is a sequential append log. JSONL files are inspectable with standard tools and trivially backupable. Appends are crash-safe (partial last line is just a truncated write).

One file per message. Too many files. The append-only JSONL approach gives one file per conversation with clear boundaries.

No compaction. Works for short conversations but becomes expensive as history grows. The compact marker approach keeps the file intact while bounding the working context.

Unresolved Questions

  • Should session files be organized in date-based subdirectories for easier cleanup?
  • Should compact threshold be per-agent configurable or global?