GitHub - angristan/fast-resume: Find that one coding agent session you want to get back to!

12 min read Original article β†—

fast-resume

PyPI version PyPI downloads

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.

demo

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:

  1. Load known sessions from Tantivy index with their mtime values
  2. Scan session files, compare mtimes against known values
  3. Only parse files where current_mtime > known_mtime + 0.001
  4. Detect deleted sessions (in index but not on disk)
  5. 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, not fr
  • 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 mtime changed. 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 --rebuild

License

MIT