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>.jsonlExample: ~/.luna/sessions/2026-04-22-143052-a3f8.jsonl
Dual-Write Architecture
Luna writes to two backends simultaneously:
| Backend | Format | Purpose |
|---|---|---|
| JSONL | Plain text, one JSON object per line | Portable, human-readable, editor-friendly |
| SQLite | WAL mode with FTS5 index | Full-text search, concurrent reads |
If SQLite is unavailable, Luna gracefully falls back to JSONL-only mode. No data is lost.
export LUNA_SQLITE_PATH=/opt/zerg/data/sessions.dbSession Lifecycle
- New session created on startup (unless
--resumeis used) - Messages appended incrementally during conversation -- no rewriting the entire file
- Session rotation when file exceeds 256KB -- current context preserved, old file archived
- Old sessions remain in
~/.luna/sessions/for future reference
Starting a Session
luajit client/main.lua --demo
luajit client/main.lua --resume
luajit client/main.lua --session ~/.luna/sessions/2026-04-22-143052-a3f8.jsonlSession Format (JSONL)
Sessions use a tree-structured JSONL format. Each line is a JSON object with type, parentId, and branch fields for tree navigation:
{"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 Type | Fields | Description |
|---|---|---|
mode_change | mode | Mode switch event |
model_change | model | Provider/model switch |
compaction | message_count | Context compaction event |
branch_summary | branch, summary | Branch point summary |
checkpoint | name, metadata | Named restore point |
label | label | User-assigned session label |
Message Types
| Type | Fields | Description |
|---|---|---|
user | content | User input message |
assistant | content, tool_uses | Agent response (may include tool calls) |
tool_result | tool_use_id, content | Result of a tool execution |
Context Compaction
When the conversation context exceeds the model's token threshold, Luna compacts the session automatically:
- Preserves system prompt and recent messages
- Summarizes older messages into a compact form
- Tool use/results are preserved or summarized depending on relevance
- All messages remain in
_all_messagesfor 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
/branchcommand - 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:
luajit client/main.lua --resumeMode is stored as the mode field in session metadata.
Auto-Generated Titles
Sessions automatically get a descriptive title generated by a hidden utility agent:
- After the first assistant response, a title generation agent runs in the background
- Uses a lightweight LLM call to summarize the conversation into a short title
- Falls back to truncating the first user message if the LLM call fails
- Title is stored in session metadata and used by
--sessionsand--search
Session Search
Search past sessions by content, model, or workspace:
luajit client/main.lua --search "quicksort"
luajit client/main.lua --sessions
luajit client/main.lua --resume-latestSessions 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
/rollbackAuto-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:
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 Size | Typical Range |
|---|---|
| Average | 10--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
grepor ripgrep - Archive them by moving files out of
~/.luna/sessions/ - Share them as conversation logs