Skip to content

Sessions

Luna persists conversations using dual-write to both JSONL files and SQLite, providing portable session logs with full-text search. Sessions survive restarts and can be resumed, searched, and branched.

Session Location

~/.luna/sessions/<timestamp>-<hash>.jsonl

Example: ~/.luna/sessions/2026-04-22-143052-a3f8.jsonl

Dual-Write Architecture

Luna writes to two backends simultaneously:

BackendFormatPurpose
JSONLPlain text, one JSON object per linePortable, human-readable, editor-friendly
SQLiteWAL mode with FTS5 indexFull-text search, concurrent reads

If SQLite is unavailable, Luna gracefully falls back to JSONL-only mode. No data is lost.

bash
export LUNA_SQLITE_PATH=/opt/zerg/data/sessions.db

Session Lifecycle

  1. New session created on startup (unless --resume is used)
  2. Messages appended incrementally during conversation -- no rewriting the entire file
  3. Session rotation when file exceeds 256KB -- current context preserved, old file archived
  4. Old sessions remain in ~/.luna/sessions/ for future reference

Starting a Session

bash
luajit client/main.lua --demo

luajit client/main.lua --resume

luajit client/main.lua --session ~/.luna/sessions/2026-04-22-143052-a3f8.jsonl

Session Format (JSONL)

Sessions use a tree-structured JSONL format. Each line is a JSON object with type, parentId, and branch fields for tree navigation:

jsonl
{"type":"user","content":"Fix the bug in main.rs"}
{"type":"assistant","content":"I'll look at the file first.","tool_uses":[{"id":"tu_1","name":"read_file","input":{"path":"src/main.rs"}}]}
{"type":"tool_result","tool_use_id":"tu_1","content":"fn main() {\n    println!(\"Hello\");\n}"}
{"type":"assistant","content":"The issue is on line 2. Let me fix it.","tool_uses":[{"id":"tu_2","name":"edit_file","input":{"path":"src/main.rs","old_text":"Hello","new_text":"Hello, world"}}]}
{"type":"tool_result","tool_use_id":"tu_2","content":"File edited successfully"}
{"type":"assistant","content":"Fixed! The bug was a missing greeting."}

Additional typed entries support session metadata:

Entry TypeFieldsDescription
mode_changemodeMode switch event
model_changemodelProvider/model switch
compactionmessage_countContext compaction event
branch_summarybranch, summaryBranch point summary
checkpointname, metadataNamed restore point
labellabelUser-assigned session label

Message Types

TypeFieldsDescription
usercontentUser input message
assistantcontent, tool_usesAgent response (may include tool calls)
tool_resulttool_use_id, contentResult of a tool execution

Context Compaction

When the conversation context exceeds the model's token threshold, Luna compacts the session automatically:

  1. Preserves system prompt and recent messages
  2. Summarizes older messages into a compact form
  3. Tool use/results are preserved or summarized depending on relevance
  4. All messages remain in _all_messages for full history access

No user action is needed. Compaction happens transparently.

Session Branching

Branch a conversation at any point. The branch creates a new session file with messages up to the branch point, then continues independently. This is useful for:

  • Exploring alternative approaches without losing context
  • Testing different solutions to the same problem
  • Creating parallel investigations

Branch CRUD

Branches support full CRUD operations:

  • Create: Branch at any message via /branch command
  • List: View all branches with /branches
  • Switch: Navigate between branches with /switch <branch>
  • Merge: Merge a branch back into the main conversation

Checkpoint/Restore

Create named checkpoints at any point and restore to them:

  • Checkpoints save session state including mode, model, and message count
  • Restore to any checkpoint with /restore <checkpoint>
  • Auto-checkpoints created on workflow events (started, step started, completed, failed)
  • Branch export: export_branch_json(branch) filters entries by branch

Session Sharing

Share sessions via public links with TTL:

  • Sessions shared via Sol HTTP API (POST /api/v1/conversations/:id/share)
  • Public GET links for read-only access
  • 7-day default TTL with hourly cleanup
  • Owner/admin can delete shares

Mode Persistence

The current agent mode is saved in the session and restored when resuming:

bash
luajit client/main.lua --resume

Mode is stored as the mode field in session metadata.

Auto-Generated Titles

Sessions automatically get a descriptive title generated by a hidden utility agent:

  1. After the first assistant response, a title generation agent runs in the background
  2. Uses a lightweight LLM call to summarize the conversation into a short title
  3. Falls back to truncating the first user message if the LLM call fails
  4. Title is stored in session metadata and used by --sessions and --search

Search past sessions by content, model, or workspace:

bash
luajit client/main.lua --search "quicksort"

luajit client/main.lua --sessions

luajit client/main.lua --resume-latest

Sessions are indexed by message content, model name, and workspace path. The FTS5 index is built lazily on first search.

Session Rollback

Use the /rollback [N] slash command to remove the last N conversation turns from the active message view:

/rollback 3
/rollback

Auto-branches before dropping turns (creates a snapshot). Returns branch ID and dropped turn count. Default N is 1.

Session API (Sol)

Sessions are also accessible via the Sol HTTP API:

bash
curl -H "Authorization: Bearer $TOKEN" \
  https://api.nonsense.ws/api/v1/conversations

curl -H "Authorization: Bearer $TOKEN" \
  https://api.nonsense.ws/api/v1/conversations/<id>

Disk Usage

Session SizeTypical Range
Average10--100KB
Large (many tool calls)up to 256KB before rotation

Sessions are automatically rotated -- no manual cleanup needed.

Working with Session Files

Sessions are plain JSONL text files. You can:

  • Inspect them with any text editor or jq
  • Search them with grep or ripgrep
  • Archive them by moving files out of ~/.luna/sessions/
  • Share them as conversation logs

Released under the MIT License.