rab-agent 0.1.0

rab is a lightweight, extensible, Rust-based coding agent.
Documentation
# Sessions — Implementation Status

## Overview

Session persistence and session-related commands for rab, matching pi's session
feature set. **Reference:** `~/src/cvstree/pi/packages/coding-agent/src/core/session-manager.ts`

## Implemented ✅

### `src/session.rs` — SessionManager core (~900 lines)

**Entry types** (serde tagged enum, all pi-compatible):
- `Message` (user/assistant/tool_result), `ThinkingLevelChange`, `ModelChange`,
  `Compaction`, `BranchSummary`, `SessionInfo` (display name), `Label`
  (bookmarking), `Custom`, `CustomMessage`

**SessionManager struct:**
```
session_id, session_file, session_dir, cwd, persist, flushed,
file_entries, by_id, labels_by_id, leaf_id
```

**Lifecycle methods:**
| Method | Status |
|---|---|
| `create(cwd, session_dir?)` ||
| `open(path, session_dir?, cwd_override?)` ||
| `continue_recent(cwd, session_dir?)` ||
| `in_memory(cwd)` ||
| `fork_from(...)` | ❌ deferred |

**Append methods:**
| Method | Status |
|---|---|
| `append_message` ||
| `append_thinking_level_change` ||
| `append_model_change` ||
| `append_compaction` ||
| `append_session_info` ||
| `append_label_change` ||
| `append_custom_entry` ||
| `append_branch_summary` ||

**Navigation:**
| Method | Status |
|---|---|
| `leaf_id`, `entries`, `entry(id)`, `children(parent_id)` ||
| `branch(from_id?)` — walk to root ||
| `build_session_context()` — resolved messages for LLM ||
| `tree()` — full tree for `/tree` UI | ❌ deferred |
| `label(id)` ||

**Branching:**
| Method | Status |
|---|---|
| `set_branch(from_id)` ||
| `reset_leaf()` ||
| `branch_with_summary(...)` | ❌ deferred |
| `create_branched_session(leaf_id)` | ❌ deferred |

**Session name:** `session_name()` walks entries in reverse to find latest
`session_info` entry. Empty string clears the name. ✅

**Deferred flush:** Session file not created until first assistant message
arrives. After initial write, subsequent entries are appended line-by-line. ✅

**Corruption handling** (matches pi exactly):
| Scenario | Behavior |
|---|---|
| Empty file | Truncate, start fresh, keep file path |
| All garbage lines | All fail to parse → empty entries → truncate + fresh |
| Header only (no entries yet) | Keep session identity, zero entries |
| Malformed lines mixed with valid | Skip bad lines, load good ones |
| Missing header, entries exist | Generate new UUID, keep entries |
| Recover then append | File rewritten with valid content |

**Storage layout:**
```
~/.rab/sessions/--home-user-project--/2026-06-19T12-00-00_UUID.jsonl
```

**Helpers:** `find_most_recent_session`, `read_session_header`,
`load_entries_from_file`, `parse_session_entry_line`, `write_entries_to_file`,
`append_entry_to_file`, `encode_cwd_for_dir`, `get_default_session_dir`,
`generate_entry_id`, `parse_session_header_line`, `SessionInfo`, `SessionContext`.

### `src/main.rs` — CLI flags ✅

| Flag | Status |
|---|---|
| `-c`, `--continue` ||
| `--session <path>` ||
| `--no-session` ||
| `--name`, `-n <name>` ||
| `--session-dir <dir>` ||
| `-r`, `--resume` | ❌ needs session selector |
| `--fork <path\|id>` | ❌ needs forkFrom |
| `--session-id <id>` ||
| `--export <file>` | ❌ needs HTML export |

Session is created/opened/continued in `main()` and passed to both print mode
and TUI.

### `src/agent.rs` — History parameter ✅

`run_agent_loop()` accepts `history: Vec<AgentMessage>` parameter. History is
prepended before prompts. Caller (main/tui) persists messages after loop returns.

### `src/extension.rs` — CommandResult variants ✅

Added: `SessionSwitched { path }`, `SessionInfo { session_id, file_path, name, message_count }`,
`OpenSessionSelector`, `SessionNamed { name }`.

### `src/builtin/commands.rs` — Slash commands ✅

| Command | Returns | Handler |
|---|---|---|
| `/resume` | `OpenSessionSelector` | `ResumeCommand` |
| `/session` | `SessionInfo` (or Info if no session) | `SessionInfoCommand` (reads shared session info) |
| `/name <text>` | `SessionNamed` (or Info usage) | `NameCommand` (trims whitespace) |
| `/new` | `NewSession` | `NewCommand` (was already implemented) |

Session info is shared via `Arc<Mutex<Option<SessionInfoInternal>>>` so
`/session` command has access to live data.

### `src/tui.rs` — TUI integration ✅

- `run()` accepts `SessionManager` by value, extracts history, passes to `run_app()`
- `run_app()` loads history via `build_session_context()`, converts to `DisplayMsg`,
  prepopulates `messages` and `conversation` vectors
- `session_messages_to_display()` — extracted helper converting `AgentMessage``DisplayMsg`
- `submit_message()` captures `app.conversation.clone()` as history, passes to agent loop
- `handle_agent_event()` persists all new messages to session on `AgentEnd`
- New `CommandResult` variants handled with info messages

### Tests — 227 total ✅

| Area | Tests |
|---|---|
| `src/session.rs` — entry round-trips | 10 |
| `src/session.rs` — pi format deserialization | 5 |
| `src/session.rs` — JSONL I/O | 7 |
| `src/session.rs` — SessionManager lifecycle + navigation | 17 |
| `src/session.rs` — corruption handling | 11 |
| `src/tui.rs` — DisplayMsg conversion | 6 |
| `tests/session_tests.rs` — agent loop + persistence | 12 |
| `tests/commands_tests.rs` — /resume, /session, /name | 9 new (27 total) |
| Other pre-existing tests | 150 |

---

## Not yet implemented ❌

### Core SessionManager gaps
- `fork_from()` — fork session into new project
- `create_branched_session()` — extract branch to new file (needed for `/fork`, `/clone`)
- `tree()` — full tree structure for `/tree` UI
- `branch_with_summary()` — branch with summary of abandoned path
- `list()` / `list_all()` — session listing for selectors
- `build_session_context()` — does NOT handle compaction summaries yet
- Session migration v1→v2→v3 — stub only
- Session CWD mismatch handling

### CLI gaps
- `-r`/`--resume` — needs session selector UI
- `--fork <path|id>` — needs `forkFrom`
- `--session-id <id>` — exact ID matching
- `--export <file>` — needs HTML export

### TUI gaps
| Component | Status |
|---|---|
| Session selector (for `/resume`, `-r`) ||
| Tree selector (for `/tree`) | ❌ deferred |
| Session name in footer ||
| User message selector (for `/fork`) | ❌ deferred |

### Commands not yet wired
| Command | Status |
|---|---|
| `/tree` | ❌ deferred |
| `/fork` | ❌ deferred |
| `/clone` | ❌ deferred |
| `/compact` | ❌ deferred |
| `/export` | ❌ deferred |
| `/copy` | ❌ deferred |
| `/share` | ❌ deferred |

### Whole subsystems
- `compaction.rs` — context compaction (deferred)
- `export.rs` — HTML export (deferred)

---

## Design decisions (all implemented)

1. **SessionManager owns persistence** — Agent loop stays pure. Caller
   (print mode / TUI) persists messages.
2. **Deferred file creation** — No file until first assistant response.
   Avoids orphaned empty session files from aborted runs.
3. **Serde tagged enum for entries**`#[serde(tag = "type")]` with
   `#[serde(rename_all = "camelCase")]` for pi-compatible JSON.
4. **Sync I/O for SessionManager** — Append is sync. Listing sessions would
   be async if implemented.
5. **uuid v4 for IDs** — pi uses v7 (time-sortable). v4 is fine; sorting
   is by timestamp field, not ID.
6. **Corruption handling** — Exactly mirrors pi: skip malformed lines,
   truncate only when both entries and header are missing, keep header-only
   sessions, recover-and-append rewrites file cleanly.