|
| 1 | +# Session save / resume / compaction plan |
| 2 | + |
| 3 | +> Status: draft — pre.25 lands auto-resume + `/new` + `/debug context`; the rest is sequenced below. |
| 4 | +
|
| 5 | +The TUI starts a fresh agent on every launch by default (until pre.25). Sessions ARE saved on every `agent_end` (`src/agent/agent.ts:239`) but the interactive path never sets `opts.resume`, so all that persistence does nothing for the in-CLI experience. This plan closes that gap and lays out the compaction story. |
| 6 | + |
| 7 | +## What pre.25 ships |
| 8 | + |
| 9 | +1. **Auto-resume by default.** `codebase` in a directory with a session ≤7 days old + same model loads the prior messages back into the agent before the user can prompt. `--new` flag opts out per-launch. |
| 10 | +2. **`/new` slash command.** Wipes both `state.messages` (display) and `bundle.agent.state.messages` (model context). The mid-session "start over" button. |
| 11 | +3. **`/debug` slash command.** Shows display message count vs agent internal message count side by side — diagnostic for "the model isn't remembering me" complaints. |
| 12 | + |
| 13 | +## Today, on disk |
| 14 | + |
| 15 | +``` |
| 16 | +~/.codebase/sessions/{sha256(cwd)[:8]}.json |
| 17 | +{ |
| 18 | + "formatVersion": 1, |
| 19 | + "workDir": "/abs/path", |
| 20 | + "modelId": "MiniMax-M2.7", |
| 21 | + "title": null, |
| 22 | + "messages": [...], |
| 23 | + "usage": {...}, |
| 24 | + "updatedAt": 1747... |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +One file per cwd. Overwritten on every `agent_end`. Auto-discarded if older than 7 days or if the saved `modelId` doesn't match the currently-resolved model. Path computed from `sha256(cwd).slice(0, 8)` so multiple worktrees stay separate. |
| 29 | + |
| 30 | +## Gaps to close (priority order) |
| 31 | + |
| 32 | +### 1. Save on abort and graceful exit, not just agent_end |
| 33 | +**Why:** A turn cancelled mid-flight (Ctrl-C) or a clean exit (`/exit`, Ctrl-D) leaves the most recent in-flight assistant message unsaved. Reopening the CLI loses that last message. |
| 34 | +**How:** Hook into `bundle.agent.abort()` and the process exit path; call `sessions.save(...)` with the agent's current `_state.messages`. |
| 35 | +**Effort:** S |
| 36 | + |
| 37 | +### 2. Multi-session per cwd (rotation) |
| 38 | +**Why:** Today one file per cwd; new session overwrites the old one. Users can't browse history. |
| 39 | +**How:** |
| 40 | +- Keep last N sessions per cwd (e.g. N=5). Rename current to `{hash}.{ts}.json` on rotation. |
| 41 | +- `/sessions` lists them with timestamps + first-message previews. |
| 42 | +- `/resume {id}` loads a specific archived session. |
| 43 | +**Effort:** M |
| 44 | + |
| 45 | +### 3. Allow resume across model changes (with a warning) |
| 46 | +**Why:** Today `parsed.modelId !== modelId` returns null — switching from Claude to MiniMax discards your session. Compaction summaries and tool call shapes mostly survive a model swap, just not perfectly. |
| 47 | +**How:** On model mismatch, still resume but emit a soft warning ("Resumed from a session on a different model — some context may be re-interpreted"). |
| 48 | +**Effort:** S |
| 49 | + |
| 50 | +### 4. Session metadata for findability |
| 51 | +**Why:** Resumed-from cards in the welcome banner only show "N hours ago, M messages." If users have several worktrees, that's not enough to distinguish. |
| 52 | +**How:** |
| 53 | +- Generate a short title via the glue LLM on first save (one-line summary of the first user prompt). |
| 54 | +- Display "Resumed: 'add OAuth flow' · 47 messages · 2h ago". |
| 55 | +- `/sessions` list uses titles too. |
| 56 | +**Effort:** S (glue call is already wired) |
| 57 | + |
| 58 | +### 5. Per-session log of file operations |
| 59 | +**Why:** Compaction loses the body of tool calls. If we want to answer "did I edit `src/foo.ts` in this session?" after a compaction pass, we need a separate audit log. |
| 60 | +**How:** Append to `~/.codebase/sessions/{hash}.fileops.jsonl` on every successful `edit_file` / `write_file` / `multi_edit`. Survives compaction. |
| 61 | +**Effort:** M |
| 62 | + |
| 63 | +## Compaction — what's there and what's missing |
| 64 | + |
| 65 | +`src/compaction/engine.ts` already does the work: |
| 66 | +- Threshold: 75% of model context window (`DEFAULT_THRESHOLD = 0.75`). |
| 67 | +- `transformContext` hook on the agent loop calls `compact()` when the threshold is crossed. |
| 68 | +- `glue` LLM summarizes older messages, keeps recent N verbatim. |
| 69 | +- `compaction_start` / `compaction_end` events emitted (we don't render them yet). |
| 70 | + |
| 71 | +What's missing: |
| 72 | + |
| 73 | +### A. Visible "Compacting…" notification |
| 74 | +**Why:** When the agent pauses for several seconds during a long session, users have no signal that it's compaction (not a hang). |
| 75 | +**How:** Subscribe to `compaction_start` in the reducer → set a `status: "compacting"` state → render in the status bar. `compaction_end` clears it. |
| 76 | +**Effort:** S |
| 77 | + |
| 78 | +### B. CompactionDetails with file-op tracking |
| 79 | +**Why:** Per pi-mono pattern (`.settings/compare/pi-mono.md:103-122` in polyvibe-poc). After compaction, the summary should preserve which files were read / modified across the compacted range. The model uses this to maintain coherent file references when older context is summarized away. |
| 80 | +**How:** Extend `CompactionResult.details` with `{ readFiles: string[], modifiedFiles: string[] }` extracted from the compacted message range's tool calls. Surface via `/context` and `/debug`. |
| 81 | +**Effort:** M |
| 82 | + |
| 83 | +### C. Snip-then-summarize pre-step (Claude Code pattern) |
| 84 | +**Why:** Today compaction summarizes the whole compactable range in one LLM call. If half of that range is verbose tool output that the model already extracted what it needs from, we're paying for summarization tokens twice. |
| 85 | +**How:** Before the summarize call, "snip": drop tool_result bodies for tool calls whose result the assistant clearly used (e.g. the assistant message immediately after quoted the file content). Summarize what remains. |
| 86 | +**Effort:** L (heuristics are tricky) |
| 87 | + |
| 88 | +### D. User control: `/compact` already exists |
| 89 | +**Why:** Already shipped — forces a compaction pass on demand. Good. Just needs the visible notification (item A). |
| 90 | + |
| 91 | +## Where to look for inspiration |
| 92 | + |
| 93 | +| Topic | Source | |
| 94 | +|---|---| |
| 95 | +| Compaction with file-op preservation | `.pi-mono/packages/coding-agent/src/core/compaction/compaction.ts` | |
| 96 | +| Session JSONL format with branches | `.pi-mono/packages/coding-agent/src/core/session-manager.ts` | |
| 97 | +| Multi-session UX | Claude Code's `claude --continue` / `claude --resume {id}` pattern (`~/claude-code-source/`) | |
| 98 | +| Web app's conversation manager | `web/backend/agent/conversationManager.js` in polyvibe-poc | |
| 99 | + |
| 100 | +## Sequencing recommendation |
| 101 | + |
| 102 | +1. **pre.25 (this release):** auto-resume default, `--new`, `/new`, `/debug context`. → unblocks the "across launches" complaint. |
| 103 | +2. **pre.26:** items 1 + 3 + A. → makes the auto-resume safer (save on abort) and visible (compacting indicator). |
| 104 | +3. **pre.27:** items 2 + 4 (multi-session rotation + titles). → makes history navigable. |
| 105 | +4. **pre.28+:** B / C / 5. → polish for power users / very long sessions. |
0 commit comments