Search and resume conversations across Claude Code, Codex, and more, all from a single place.
Why fast-resume?
Coding agents are really good right now, so I'm using a bunch of them. Sometimes I remember I, or the LLM, mentioned something specific in a previous session, and I want to go back to it.
The problem is that currently, agents do have a resume feature, but either they don't support searching, or the search is very basic (e.g., title only).
That's why I built fast-resume: a command-line tool that aggregates all your coding agent sessions into a single searchable index, so you can quickly find and resume any session.
Features
- Unified Search: One search box to find sessions across all your coding agents
- Full-Text Search: Search not just titles, but the entire conversation content (user messages and assistant responses)
- Very fast: Built on the Rust-powered Tantivy search engine for blazing-fast indexing and searching
- Fuzzy Matching: Typo-tolerant search with smart ranking (exact matches boosted)
- Direct Resume: Select, Enter, you're back in your session
- Beautiful TUI: fzf-style interface with agent icons, color-coded results, and live preview
- Update Notifications: Get notified when a new version is available
- Multi-Agent Support: Works with Claude Code, Codex, Copilot, OpenCode, Vibe, Crush, and more
Installation
Recommended Terminal
For the best experience, Ghostty π» is recommended. Other terminals may have issues with interactive features and displaying images.
Homebrew
brew tap angristan/tap brew install fast-resume
uv (PyPI)
# Run directly (no install needed) uvx --from fast-resume fr # Or install permanently uv tool install fast-resume fr
Usage
Interactive TUI
# Open the TUI with all sessions fr # Pre-filter search query fr "authentication bug" # Filter by agent fr -a claude fr -a codex # Filter by directory fr -d myproject # Combine filters fr -a claude -d backend "api error"
Keyword Search Syntax
Filter directly in the search box using keywords:
agent:claude # Filter by agent agent:claude,codex # Multiple agents (OR) -agent:vibe # Exclude agent agent:claude,!codex # Include claude, exclude codex dir:myproject # Filter by directory (substring) dir:backend,!test # Include backend, exclude test date:today # Sessions from today date:yesterday # Sessions from yesterday date:<1h # Within the last hour date:<2d # Within the last 2 days date:>1w # Older than 1 week date:week # Within the last week date:month # Within the last month
Combine keywords with free-text search:
fr "agent:claude date:<1d api bug" fr "dir:backend -agent:vibe auth"
Autocomplete: Type agent:cl and press Tab to complete to agent:claude.
Non-Interactive Mode
# List sessions in terminal (no TUI) fr --no-tui # Just list, don't offer to resume fr --list # Force rebuild the index fr --rebuild # View your usage statistics fr --stats
Yolo Mode
Resume sessions with auto-approve / skip-permissions flags:
| Agent | Flag Added | Auto-detected |
|---|---|---|
| Claude | --dangerously-skip-permissions |
No |
| Codex | --dangerously-bypass-approvals-and-sandbox |
Yes |
| Copilot CLI | --allow-all-tools --allow-all-paths |
No |
| Vibe | --auto-approve |
Yes |
| OpenCode | (config-based) | β |
| Crush | (no CLI resume) | β |
| VS Code Copilot | (n/a) | β |
Auto-detection: Codex and Vibe store the permissions mode in their session files. Sessions originally started in yolo mode are automatically resumed in yolo mode.
Interactive prompt: For agents that support yolo but don't store it (Claude, Copilot CLI), you'll see a modal asking whether to resume in yolo mode. Use Tab to toggle, Enter to confirm.
Force yolo: Use fr --yolo to skip the prompt and always resume in yolo mode, if supported.
Command Reference
Usage: fr [OPTIONS] [QUERY]
Arguments:
QUERY Search query (optional)
Options:
-a, --agent [claude|codex|copilot-cli|copilot-vscode|crush|opencode|vibe]
Filter by agent
-d, --directory TEXT Filter by directory (substring match)
--no-tui Output list to stdout instead of TUI
--list Just list sessions, don't resume
--rebuild Force rebuild the session index
--stats Show index statistics
--yolo Resume with auto-approve/skip-permissions flags
--version Show version
--help Show this message and exit
Keybindings
Navigation
| Key | Action |
|---|---|
β / β |
Move selection up/down |
j / k |
Move selection up/down (vim-style) |
Page Up / Page Down |
Move by 10 rows |
Enter |
Resume selected session |
/ |
Focus search input |
Preview & Actions
| Key | Action |
|---|---|
| `Ctrl+`` | Toggle preview pane |
+ / - |
Resize preview pane |
Tab |
Accept autocomplete suggestion |
c |
Copy full resume command to clipboard |
Ctrl+P |
Open command palette |
q/Esc |
Quit |
Yolo Mode Modal
| Key | Action |
|---|---|
Tab / β β |
Toggle selection |
Enter |
Confirm selection |
y |
Select Yolo |
n |
Select No |
Esc |
Cancel |
Statistics Dashboard
Run fr --stats to see analytics about your coding sessions:
Index Statistics
Total sessions 751
Total messages 13,799
Avg messages/session 18.4
Index size 15.5 MB
Index location ~/.cache/fast-resume/tantivy_index
Date range 2023-11-15 to 2025-12-22
Data by Agent
ββββββββββββββββββ¬ββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬ββββββββββββββ
β Agent β Files β Disk β Sessions β Messages β Content β Data Dir β
ββββββββββββββββββΌββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌββββββββββββββ€
β claude β 477 β 312.9 MB β 377 β 10,415 β 3.1 MB β ~/.claude/β¦ β
β copilot-vscode β 191 β 146.0 MB β 189 β 954 β 1.4 MB β ~/Library/β¦ β
β codex β 107 β 23.6 MB β 89 β 321 β 890.6 kB β ~/.codex/β¦ β
β opencode β 9275 β 46.3 MB β 72 β 1,912 β 597.7 kB β ~/.local/β¦ β
β vibe β 12 β 858.2 kB β 12 β 138 β 380.0 kB β ~/.vibe/β¦ β
β crush β 3 β 1.0 MB β 7 β 44 β 15.2 kB β ~/.local/β¦ β
β copilot-cli β 5 β 417.1 kB β 5 β 15 β 6.9 kB β ~/.copilotβ¦ β
ββββββββββββββββββ΄ββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄ββββββββββββββ
Activity by Day
Mon ββββββββββ 89
Tue ββββββββββ 86
Wed βββββ 44
Thu ββββββββββββββ 115
Fri βββββββββββββ 112
Sat ββββββββββββββββββββ 163
Sun βββββββββββββββββ 142
Activity by Hour
0h ββ βββ
βββββββββ
β
β 23h
Peak hours: 23:00 (99), 22:00 (63), 12:00 (63)
Top Directories
βββββββββββββββββββββββββ¬βββββββββββ¬βββββββββββ
β Directory β Sessions β Messages β
βββββββββββββββββββββββββΌβββββββββββΌβββββββββββ€
β ~/git/openvpn-install β 234 β 5,597 β
β ~/lab/larafeed β 158 β 2,590 β
β ~/lab/fast-resume β 81 β 2,027 β
β ... β β β
βββββββββββββββββββββββββ΄βββββββββββ΄βββββββββββ
How It Works
Architecture
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SessionSearch β
β β
β β’ Orchestrates adapters in parallel (ThreadPoolExecutor) β
β β’ Compares file mtimes to detect changes (incremental updates) β
β β’ Delegates search queries to Tantivy index β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
ββββββββββββββ΄βββββββββββββ β
βΌ βΌ βΌ
ββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TantivyIndex β β Adapters β
β β β ββββββββββ βββββββββ βββββββββ βββββββββββ βββββββββ ββββββββββ ββββββ β
β β’ Fuzzy search ββββββ β Claude β β Codex β βCopilotβ β Copilot β β Crush β βOpenCodeβ βVibeβ β
β β’ mtime tracking β β β β β β β CLI β β VS Code β β β β β β β β
β β β βββββ¬βββββ βββββ¬ββββ βββββ¬ββββ ββββββ¬βββββ βββββ¬ββββ βββββ¬βββββ βββ¬βββ β
β ~/.cache/ β β β β β β β β β β
β fast-resume/ β ββββββββΌβββββββββββΌββββββββββΌβββββββββββΌβββββββββββΌββββββββββΌβββββββββΌββββββββββββ
ββββββββββββββββββββ βΌ βΌ βΌ βΌ βΌ βΌ βΌ
~/.claude/ ~/.codex/ ~/.copilot/ VS Code/ crush.db opencode/ ~/.vibe/
Session Parsing
Each agent stores sessions differently. Adapters normalize them into a common Session structure:
| Agent | Format | Parsing Strategy |
|---|---|---|
| Claude Code | JSONL in ~/.claude/projects/<project>/*.jsonl |
Stream line-by-line, extract user/assistant messages, skip agent-* subprocess files |
| Codex | JSONL in ~/.codex/sessions/**/*.jsonl |
Line-by-line parsing, extract from session_meta, response_item, and event_msg entries |
| Copilot CLI | JSONL in ~/.copilot/session-state/*.jsonl |
Line-by-line parsing, extract user.message and assistant.message types |
| Copilot VSCode | JSON in VS Code's workspaceStorage/*/chatSessions/ |
Parse requests array with message text and response values |
| Crush | SQLite DB at <project>/crush.db |
Query sessions and messages tables directly, parse JSON parts column |
| OpenCode | Split JSON in ~/.local/share/opencode/storage/ |
Lazy-load message/ and part/ per session for progressive indexing |
| Vibe | JSON in ~/.vibe/logs/session/session_*.json |
Parse messages array with role-based content |
The normalized Session structure:
@dataclass class Session: id: str # Unique identifier (usually filename or UUID) agent: str # "claude", "codex", "copilot-cli", "copilot-vscode", "crush", "opencode", "vibe" title: str # Summary or first user message (max 100 chars) directory: str # Working directory where session was created timestamp: datetime # Last modified time preview: str # First 500 chars for preview pane content: str # Full conversation text (Β» user, β£β£ assistant) message_count: int # Conversation turns (user + assistant, excludes tool results) mtime: float # File mtime for incremental update detection
What gets indexed:
- User text messages (the actual prompts you typed)
- Assistant text responses
What's excluded from indexing:
- Tool results (file contents, command outputs, API responses)
- Tool use/calls (function invocations)
- Meta messages (system prompts, context summaries)
- Local command outputs (slash commands like
/context)
This keeps the index focused on the actual conversation and avoids bloating it with large tool outputs that are rarely useful for search.
Indexing
Incremental updates avoid re-parsing on every launch:
- Load known sessions from Tantivy index with their
mtimevalues - Scan session files, compare mtimes against known values
- Only parse files where
current_mtime > known_mtime + 0.001 - Detect deleted sessions (in index but not on disk)
- Apply changes atomically: delete removed, upsert modified
Progressive indexing with batched commits:
def handle_session(session): # Buffer session for batched indexing pending_sessions.append(session) if len(pending_sessions) >= BATCH_SIZE: self._index.update_sessions(pending_sessions) # Batch commit pending_sessions.clear() on_progress() # TUI updates # Adapters call on_session as each session is parsed adapter.find_sessions_incremental(known, on_session=handle_session)
Sessions appear in the TUI progressively as they're parsed and batched. OpenCode uses parallel file I/O and processes smaller sessions first for faster initial results.
Schema versioning: A .schema_version file tracks the index schema. If it doesn't match the code's SCHEMA_VERSION constant, the entire index is deleted and rebuilt. This prevents deserialization errors after upgrades.
Search
Tantivy is a Rust full-text search library (powers Quickwit, similar to Lucene). We use it via tantivy-py.
Hybrid search combines exact and fuzzy matching for best results:
# Exact match (boosted 5x) - uses BM25 scoring exact_query = index.parse_query(query, ["title", "content"]) boosted_exact = tantivy.Query.boost_query(exact_query, 5.0) # Fuzzy match (edit distance 1) - for typo tolerance for term in query.split(): fuzzy_title = tantivy.Query.fuzzy_term_query(schema, "title", term, distance=1, prefix=True) fuzzy_content = tantivy.Query.fuzzy_term_query(schema, "content", term, distance=1, prefix=True) ... # Combine: exact OR fuzzy (exact scores higher due to boost) tantivy.Query.boolean_query([ (tantivy.Occur.Should, boosted_exact), (tantivy.Occur.Should, fuzzy_query), ])
This ensures exact matches rank first while still finding typos like auth midleware β "authentication middleware".
Query lifecycle:
βββββββββββββββ 50ms βββββββββββββββ background βββββββββββββββ
β Keystroke β βββββββββΊ β Debounce β ββββββββββββΊ β Worker β
βββββββββββββββ timer βββββββββββββββ thread ββββββββ¬βββββββ
β
βββββββββββββββ ββββββββΌβββββββ
β Render β ββββββββββββ β Tantivy β
β Table β results β Query β
βββββββββββββββ βββββββββββββββ
TUI
Streaming results: Sessions appear as each adapter completes, not after all finish.
- Fast path: Index up-to-date β load synchronously, no spinner
- Slow path: Changes detected β spinner, stream results via
on_progress()callback
Preview context: When searching, the preview pane jumps to the matching portion:
for term in query.lower().split(): pos = content.lower().find(term) if pos != -1: start = max(0, pos - 100) # Show ~100 chars before match preview_text = content[start:start + 1500] break
Matching terms are highlighted with Rich's Text.stylize().
Resume Handoff
When you press Enter on a session, fast-resume hands off to the original agent:
# In cli.py after TUI exits resume_cmd, resume_dir = run_tui(query=query, agent_filter=agent) if resume_cmd: # 1. Change to the session's original working directory os.chdir(resume_dir) # 2. Replace current process with agent's resume command os.execvp(resume_cmd[0], resume_cmd)
os.execvp() replaces the Python process entirely with the agent CLI. This means:
- No subprocess overhead
- Shell history shows
claude --resume xyz, notfr - Agent inherits the correct working directory
- fast-resume process is gone after handoff
Each adapter returns the appropriate command:
| Agent | Resume Command | With --yolo |
|---|---|---|
| Claude | claude --resume <id> |
claude --dangerously-skip-permissions --resume <id> |
| Codex | codex resume <id> |
codex --dangerously-bypass-approvals-and-sandbox resume <id> |
| Copilot CLI | copilot --resume <id> |
copilot --allow-all-tools --allow-all-paths --resume <id> |
| Copilot VSCode | code <directory> |
(no change) |
| OpenCode | opencode <dir> --session <id> |
(no change) |
| Vibe | vibe --resume <id> |
vibe --auto-approve --resume <id> |
| Crush | crush |
(no change) |
Performance
Why fast-resume feels instant:
- Tantivy (Rust): Search engine written in Rust, accessed via Python bindings. Handles fuzzy queries over 10k+ sessions in <10ms
- Incremental updates: Only re-parse files where
mtimechanged. Second launch with no changes: ~50ms total - Parallel adapters: All adapters run simultaneously in ThreadPoolExecutor. Total time = slowest adapter, not sum
- Debounced search: 50ms debounce prevents wasteful searches while typing
- Background workers: Search runs in thread, UI never blocks
- orjson: Rust-based JSON parsing, ~10x faster than stdlib json
- Streaming results: Sessions appear as each adapter completes, not after all finish
Typical performance on a machine with ~500 sessions:
- Cold start (empty index): ~2s
- Warm start (no changes): ~50ms
- Search query: <10ms
Development
# Clone and setup git clone https://github.com/angristan/fast-resume.git cd fast-resume uv sync # Run locally uv run fr # Install pre-commit hooks uv run pre-commit install # Run tests uv run pytest -v # Lint and format uv run ruff check . uv run ruff format .
Project Structure
fast-resume/
βββ src/fast_resume/
β βββ cli.py # Click CLI entry point
β βββ config.py # Constants, colors, paths
β βββ index.py # TantivyIndex - search engine
β βββ search.py # SessionSearch - adapter orchestration
β βββ tui.py # Textual TUI application
β βββ assets/ # Agent icons (PNG)
β βββ adapters/
β βββ base.py # Session dataclass, AgentAdapter protocol
β βββ claude.py # Claude Code adapter
β βββ codex.py # Codex CLI adapter
β βββ copilot.py # GitHub Copilot CLI adapter
β βββ copilot_vscode.py # VS Code Copilot Chat adapter
β βββ crush.py # Crush adapter
β βββ opencode.py # OpenCode adapter
β βββ vibe.py # Vibe adapter
βββ tests/ # pytest test suite
βββ pyproject.toml # Dependencies and build config
βββ README.md
Tech Stack
| Component | Library |
|---|---|
| TUI Framework | Textual |
| Terminal Formatting | Rich |
| CLI Framework | Click |
| Search Engine | Tantivy (via tantivy-py) |
| JSON Parsing | orjson (fast) |
| Date Formatting | humanize |
Configuration
fast-resume uses sensible defaults and requires no configuration.
To clear the index and rebuild from scratch:
rm -rf ~/.cache/fast-resume/
fr --rebuildLicense
MIT

