Unified, high-performance TUI to index and search your local coding agent history. Aggregates sessions from Codex, Claude Code, Gemini CLI, Cline, OpenCode, Amp, Cursor, ChatGPT, Aider, and Pi-Agent into a single, searchable timeline.
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/coding_agent_session_search/main/install.sh \
| bash -s -- --easy-mode --verify# Windows (PowerShell)
irm https://raw.githubusercontent.com/Dicklesworthstone/coding_agent_session_search/main/install.ps1 | iex
install.ps1 -EasyMode -VerifyThree-pane layout: filter bar, results list with color-coded agents (Claude, Codex, Gemini, Pi-Agent, etc.), and syntax-highlighted detail preview
Full conversation rendering with markdown formatting, code blocks, headers, and structured content
Built-in help screen (press F1 or ?) with all shortcuts, filters, modes, and navigation tips
AI coding agents are transforming how we write software. Claude Code, Codex, Cursor, Copilot, Aider, Pi-Agent; each creates a trail of conversations, debugging sessions, and problem-solving attempts. But this wealth of knowledge is scattered and unsearchable:
- Fragmented storage: Each agent stores data differentlyβJSONL files, SQLite databases, markdown logs, proprietary JSON formats
- No cross-agent visibility: Solutions discovered in Cursor are invisible when you're using Claude Code
- Lost context: That brilliant debugging session from two weeks ago? Good luck finding it by scrolling through files
- No semantic search: File-based grep doesn't understand code structure or natural language queries
cass treats your coding agent history as a unified knowledge base. It:
- Normalizes disparate formats into a common schema
- Indexes everything with a purpose-built full-text search engine
- Surfaces relevant past conversations in milliseconds
- Respects your privacyβeverything stays local, nothing phones home
- Individual developers: Find that solution you know you've seen before
- Teams: Share institutional knowledge across different tool preferences
- AI agents themselves: Let your current agent learn from all your past agents (via robot mode)
- Power users: Build workflows that leverage your complete coding history
- "Search-as-you-type": Results update instantly with every keystroke.
- Edge N-Gram Indexing: We frontload the work by pre-computing prefix matches (e.g., "cal" -> "calculate") during indexing, trading disk space for O(1) lookup speed at query time.
- Smart Tokenization: Handles
snake_case("my_var" matches "my" and "var"), hyphenated terms, and code symbols (c++,foo.bar) correctly. - Zero-Stall Updates: The background indexer commits changes atomically;
reader.reload()ensures new messages appear in the search bar immediately without restarting.
- Wildcard Patterns: Full glob-style pattern support:
foo*- Prefix match (finds "foobar", "foo123")*foo- Suffix match (finds "barfoo", "configfoo")*foo*- Substring match (finds "afoob", "configuration")
- Auto-Fuzzy Fallback: When exact searches return sparse results, automatically retries with
*term*wildcards to broaden matches. Visual indicator shows when fallback is active. - Query History Deduplication: Recent searches deduplicated to show unique queries; navigate with
Up/Downarrows. - Match Quality Ranking: New ranking mode (cycle with
F12) that prioritizes exact matches over wildcard/fuzzy results. - Match Highlighting: Use
--highlightin robot mode to wrap matching terms with markers (**bold**for text,<mark>for HTML output).
- Three-Pane Layout: Filter bar (top), scrollable results (left), and syntax-highlighted details (right).
- Multi-Line Result Display: Each result shows location and up to 3 lines of context; alternating stripes improve scanability.
- Live Status: Footer shows real-time indexing progressβagent discovery count during scanning, then item progress with sparkline visualization (e.g.,
π¦ Indexing 150/2000 (7%) βββββ)βplus active filters. - Multi-Open Queue: Queue multiple results with
Ctrl+Enter, then open all in your editor withCtrl+O. Confirmation prompt for large batches (β₯12 items). - Find-in-Detail: Press
/to search within the detail pane; matches highlighted withn/Nnavigation. - Mouse Support: Click to select results, scroll panes, or clear filters.
- Theming: Adaptive Dark/Light modes with role-colored messages (User/Assistant/System). Toggle border style (
Ctrl+B) between rounded Unicode and plain ASCII. - Ranking Modes: Cycle through
recent/balanced/relevance/qualitywithF12; quality mode penalizes fuzzy matches.
Ingests history from all major local agents, normalizing them into a unified Conversation -> Message -> Snippet model:
- Codex:
~/.codex/sessions(Rollout JSONL) - Cline: VS Code global storage (Task directories)
- Gemini CLI:
~/.gemini/tmp(Chat JSON) - Claude Code:
~/.claude/projects(Session JSONL) - OpenCode:
.opencodedirectories (SQLite) - Amp:
~/.local/share/amp& VS Code storage - Cursor:
~/Library/Application Support/Cursor/User/global + workspace storage (SQLitestate.vscdb) - ChatGPT:
~/Library/Application Support/com.openai.chat(v1 unencrypted JSON; v2/v3 encryptedβsee Environment) - Aider:
~/.aider.chat.history.mdand per-project.aider.chat.history.mdfiles (Markdown) - Pi-Agent:
~/.pi/agent/sessions(Session JSONL with thinking content)
Pi-Agent parses JSONL session files with rich event structure:
- Location:
~/.pi/agent/sessions/(override withPI_CODING_AGENT_DIRenv var) - Format: Typed eventsβ
session_start,message,model_change,thinking_level_change - Features: Extracts extended thinking content, flattens tool calls with arguments, tracks model changes
- Detection: Scans for
*_*.jsonlpattern in sessions directory
OpenCode reads SQLite databases from workspace directories:
- Location:
.opencode/directories (scans recursively from home) - Format: SQLite database with sessions table
- Detection: Finds directories named
.opencodecontaining database files
Search across agent sessions from multiple machinesβyour laptop, desktop, and remote serversβall from a single unified index. cass uses SSH/rsync to efficiently sync session data, tracking provenance so you know where each conversation originated.
# Add a remote machine using platform presets
cass sources add [email protected] --preset macos-defaults
# Or specify paths explicitly
cass sources add dev@workstation --path ~/.claude/projects --path ~/.codex/sessions
# Sync sessions from all configured sources
cass sources sync
# Check source health and connectivity
cass sources doctorSources are configured in the platform config directory (Linux: ~/.config/cass/sources.toml, macOS: ~/Library/Application Support/cass/sources.toml):
[[sources]]
name = "laptop"
type = "ssh"
host = "[email protected]"
paths = ["~/.claude/projects", "~/.codex/sessions"]
sync_schedule = "manual"
[[sources]]
name = "workstation"
type = "ssh"
host = "[email protected]"
paths = ["~/.claude/projects"]
sync_schedule = "daily"
# Path mappings rewrite remote paths to local equivalents
[[sources.path_mappings]]
from = "/home/dev/projects"
to = "/Users/me/projects"
# Agent-specific mappings
[[sources.path_mappings]]
from = "/opt/work"
to = "/Volumes/Work"
agents = ["claude_code"]Configuration Fields:
| Field | Description |
|---|---|
name |
Friendly identifier (becomes source_id) |
type |
Connection type: ssh or local |
host |
SSH host (user@hostname) |
paths |
Paths to sync (supports ~ expansion) |
sync_schedule |
manual, hourly, or daily |
path_mappings |
Rewrite remote paths to local equivalents |
# List configured sources
cass sources list [--verbose] [--json]
# Add a new source
cass sources add <user@host> [--name <name>] [--preset macos-defaults|linux-defaults] [--path <path>...] [--no-test]
# Remove a source
cass sources remove <name> [--purge] [-y]
# Check connectivity and config
cass sources doctor [--source <name>] [--json]
# Sync sessions
cass sources sync [--source <name>] [--no-index] [--verbose] [--dry-run] [--json]When viewing sessions from remote machines, workspace paths may not exist locally. Path mappings rewrite these paths so file links work on your local machine:
# List current mappings
cass sources mappings list laptop
# Add a mapping
cass sources mappings add laptop --from /home/user/projects --to /Users/me/projects
# Test how a path would be rewritten
cass sources mappings test laptop /home/user/projects/myapp/src/main.rs
# Output: /Users/me/projects/myapp/src/main.rs
# Agent-specific mappings (only apply for certain agents)
cass sources mappings add laptop --from /opt/work --to /Volumes/Work --agents claude_code,codex
# Remove a mapping by index
cass sources mappings remove laptop 0In the TUI, filter sessions by origin:
- F11: Cycle source filter (all β local β remote β all)
- Shift+F11: Open source filter menu to select specific sources
Remote sessions display with a source indicator (e.g., [laptop]) in the results list.
Each conversation tracks its origin:
source_id: Machine identifier (e.g., "laptop", "workstation")source_kind:localorremoteworkspace_original: Original path on the remote machine (before path mapping)
These fields appear in JSON/robot output and enable filtering:
cass search "auth error" --source laptop --json
cass timeline --days 7 --source remote
cass stats --by-sourcecass is purpose-built for consumption by AI coding agentsβnot just as an afterthought, but as a first-class design goal. When you're an AI agent working on a codebase, your own session history and those of other agents become an invaluable knowledge base: solutions to similar problems, context about design decisions, debugging approaches that worked, and institutional memory that would otherwise be lost.
Imagine you're Claude Code working on a React authentication bug. With cass, you can instantly search across:
- Your own previous sessions where you solved similar auth issues
- Codex sessions where someone debugged OAuth flows
- Cursor conversations about token refresh patterns
- Aider chats about security best practices
This cross-pollination of knowledge across different AI agents is transformative. Each agent has different strengths, different context windows, and encounters different problems. cass unifies all this collective intelligence into a single, searchable index.
cass teaches agents how to use itβno external documentation required:
# Quick capability check: what features exist?
cass capabilities --json
# β {"features": ["json_output", "cursor_pagination", "highlight_matches", ...], "connectors": [...], "limits": {...}}
# Full API schema with argument types, defaults, and response shapes
cass introspect --json
# Topic-based help optimized for LLM consumption
cass robot-docs commands # All commands and flags
cass robot-docs schemas # Response JSON schemas
cass robot-docs examples # Copy-paste invocations
cass robot-docs exit-codes # Error handling guide
cass robot-docs guide # Quick-start walkthroughAI agents sometimes make syntax mistakes. cass aggressively normalizes input to maximize acceptance when intent is clear:
| What you type | What cass understands |
Correction note |
|---|---|---|
cass serach "error" |
cass search "error" |
"Did you mean 'search'?" |
cass -robot -limit=5 |
cass --robot --limit=5 |
Single-dash long flags normalized |
cass --Robot --LIMIT 5 |
cass --robot --limit 5 |
Case normalized |
cass find "auth" |
cass search "auth" |
find/query/q β search |
cass --robot-docs |
cass robot-docs |
Flag-as-subcommand detected |
cass search --limt 5 |
cass search --limit 5 |
Levenshtein distance β€2 corrected |
The CLI applies multiple normalization layers:
- Typo correction: Flags within edit distance 2 are auto-corrected
- Case normalization:
--Robot,--LIMITβ--robot,--limit - Single-dash recovery:
-robotβ--robot(common LLM mistake) - Subcommand aliases:
find/query/qβsearch,ls/listβstats - Global flag hoisting: Position-independent flag handling
When corrections are applied, cass emits a teaching note to stderr so agents learn the canonical syntax.
Every command supports machine-readable output:
# Pretty-printed JSON (default robot mode)
cass search "error" --robot
# Streaming JSONL: header line with _meta, then one hit per line
cass search "error" --robot-format jsonl
# Compact single-line JSON (minimal bytes)
cass search "error" --robot-format compact
# Include performance metadata
cass search "error" --robot --robot-meta
# β { "hits": [...], "_meta": { "elapsed_ms": 12, "cache_hit": true, "wildcard_fallback": false, ... } }Design principle: stdout contains only parseable JSON data; all diagnostics, warnings, and progress go to stderr.
LLMs have context limits. cass provides multiple levers to control output size:
| Flag | Effect |
|---|---|
--fields minimal |
Only source_path, line_number, agent |
--fields summary |
Adds title, score |
--fields score,title,snippet |
Custom field selection |
--max-content-length 500 |
Truncate long fields (UTF-8 safe, adds "...") |
--max-tokens 2000 |
Soft budget (~4 chars/token); adjusts truncation dynamically |
--limit 5 |
Cap number of results |
Truncated fields include a *_truncated: true indicator so agents know when they're seeing partial content.
Errors are structured, actionable, and include recovery hints:
{
"error": {
"code": 3,
"kind": "index_missing",
"message": "Search index not found",
"hint": "Run 'cass index --full' to build the index",
"retryable": false
}
}Exit codes follow a semantic convention:
| Code | Meaning | Typical action |
|---|---|---|
| 0 | Success | Parse stdout |
| 2 | Usage error | Fix syntax (hint provided) |
| 3 | Index missing | Run cass index --full |
| 4 | Not found | Try different query/path |
| 5 | Idempotency mismatch | Retry with new key |
| 9 | Unknown error | Check retryable flag |
| 10 | Timeout exceeded | Increase --timeout or reduce scope |
The retryable field tells agents whether a retry might succeed (e.g., transient I/O) vs. guaranteed failure (e.g., invalid path).
Beyond search, cass provides commands for deep-diving into specific sessions:
# Export full conversation to shareable format
cass export /path/to/session.jsonl --format markdown -o conversation.md
cass export /path/to/session.jsonl --format html -o conversation.html
cass export /path/to/session.jsonl --format json --include-tools
# Expand context around a specific line (from search result)
cass expand /path/to/session.jsonl -n 42 -C 5 --json
# β Shows 5 messages before and after line 42
# Activity timeline: when were agents active?
cass timeline --today --json --group-by hour
cass timeline --since 7d --agent claude --json
# β Grouped activity counts, useful for understanding work patternsThe --highlight flag wraps matching terms for visual/programmatic identification:
cass search "authentication error" --robot --highlight
# In text output: **authentication** and **error** are bold-wrapped
# In HTML export: <mark>authentication</mark> and <mark>error</mark>Highlighting is query-aware: quoted phrases like "auth error" highlight as a unit; individual terms highlight separately.
For large result sets, use cursor-based pagination:
# First page
cass search "TODO" --robot --robot-meta --limit 20
# β { "hits": [...], "_meta": { "next_cursor": "eyJ..." } }
# Next page
cass search "TODO" --robot --robot-meta --limit 20 --cursor "eyJ..."Cursors are opaque tokens encoding the pagination state. They remain valid as long as the index isn't rebuilt.
For debugging and logging, attach a request ID:
cass search "bug" --robot --request-id "req-12345"
# β { "hits": [...], "_meta": { "request_id": "req-12345" } }For safe retries (e.g., in CI pipelines or flaky networks):
cass index --full --idempotency-key "build-$(date +%Y%m%d)"
# If same key + params were used in last 24h, returns cached resultDebug why a search returned unexpected results:
cass search "auth*" --robot --explain
# β Includes parsed query AST, term expansion, cost estimates
cass search "auth error" --robot --dry-run
# β Validates query syntax without executingFor debugging agent pipelines:
cass search "error" --robot --trace-file /tmp/cass-trace.json
# Appends execution span with timing, exit code, and command details| Flag | Purpose |
|---|---|
--robot / --json |
JSON output (pretty-printed) |
--robot-format jsonl|compact |
Streaming or single-line JSON |
--robot-meta |
Include _meta block (elapsed_ms, cache stats, index freshness) |
--fields minimal|summary|<list> |
Reduce payload size |
--max-content-length N |
Truncate content fields to N chars |
--max-tokens N |
Soft token budget (~4 chars/token) |
--timeout N |
Timeout in milliseconds; returns partial results on expiry |
--cursor <token> |
Cursor-based pagination (from _meta.next_cursor) |
--request-id ID |
Echoed in response for correlation |
--aggregate agent,workspace,date |
Server-side aggregations |
--explain |
Include query analysis (parsed query, cost estimate) |
--dry-run |
Validate query without executing |
--source <source> |
Filter by source: local, remote, all, or specific source ID |
--highlight |
Highlight matching terms in output |
| Flag | Purpose |
|---|---|
--idempotency-key KEY |
Safe retries: same key + params returns cached result (24h TTL) |
--json |
JSON output with stats |
π cass β Search All Your Agent History
What: cass indexes conversations from Claude Code, Codex, Cursor, Gemini, Aider, ChatGPT, and more into a unified, searchable index. Before solving a problem from scratch, check if any agent already solved something similar.
β οΈ NEVER run bare cass β it launches an interactive TUI. Always use --robot or --json.
Quick Start
# Check if index is healthy (exit 0=ok, 1=run index first)
cass health
# Search across all agent histories
cass search "authentication error" --robot --limit 5
# View a specific result (from search output)
cass view /path/to/session.jsonl -n 42 --json
# Expand context around a line
cass expand /path/to/session.jsonl -n 42 -C 3 --json
# Learn the full API
cass capabilities --json # Feature discovery
cass robot-docs guide # LLM-optimized docs
Why Use It
- Cross-agent knowledge: Find solutions from Codex when using Claude, or vice versa
- Forgiving syntax: Typos and wrong flags are auto-corrected with teaching notes
- Token-efficient: --fields minimal returns only essential data
Key Flags
| Flag | Purpose |
|------------------|--------------------------------------------------------|
| --robot / --json | Machine-readable JSON output (required!) |
| --fields minimal | Reduce payload: source_path, line_number, agent only |
| --limit N | Cap result count |
| --agent NAME | Filter to specific agent (claude, codex, cursor, etc.) |
| --days N | Limit to recent N days |
stdout = data only, stderr = diagnostics. Exit 0 = success.
cass supports a rich query syntax designed for both humans and machines.
| Query | Matches |
|---|---|
error |
Messages containing "error" (case-insensitive) |
python error |
Messages containing both "python" AND "error" |
"authentication failed" |
Exact phrase match |
auth fail |
Both terms, in any order |
| Pattern | Type | Matches | Performance |
|---|---|---|---|
auth* |
Prefix | "auth", "authentication", "authorize" | Fast (uses edge n-grams) |
*tion |
Suffix | "authentication", "function", "exception" | Slower (regex scan) |
*config* |
Substring | "reconfigure", "config.json", "misconfigured" | Slowest (full regex) |
test_* |
Prefix | "test_user", "test_auth", "test_helpers" | Fast |
Tip: Prefix wildcards (foo*) are optimized via pre-computed edge n-grams. Suffix and substring wildcards fall back to regex and are slower on large indexes.
# Field-specific search (in robot mode)
cass search "error" --agent claude --workspace /path/to/project
# Time-bounded search
cass search "bug" --since 2024-01-01 --until 2024-01-31
cass search "bug" --today
cass search "bug" --days 7
# Combined filters
cass search "authentication" --agent codex --workspace myproject --weekSearch results include a match_type indicator:
| Type | Meaning | Score Boost |
|---|---|---|
exact |
Query terms found verbatim | Highest |
prefix |
Matched via prefix expansion (e.g., auth*) |
High |
suffix |
Matched via suffix pattern | Medium |
substring |
Matched via substring pattern | Lower |
fuzzy |
Auto-fallback match when exact results sparse | Lowest |
When an exact query returns fewer than 3 results, cass automatically retries with wildcard expansion:
authβ*auth*- Results are flagged with
wildcard_fallback: truein robot mode - TUI shows a "fuzzy" indicator in the status bar
| Key | Action |
|---|---|
Ctrl+C |
Quit |
F1 or ? |
Toggle help screen |
F2 |
Toggle dark/light theme |
Ctrl+B |
Toggle border style (rounded/plain) |
Ctrl+Shift+R |
Force re-index |
Ctrl+Shift+Del |
Reset all TUI state |
| Key | Action |
|---|---|
| Type | Live search as you type |
Enter |
Open selected result in $EDITOR |
Esc |
Clear query / exit search |
Up/Down |
Navigate query history |
Ctrl+R |
Cycle through query history |
Backspace |
Delete character; if empty, remove last filter chip |
| Key | Action |
|---|---|
Up/Down |
Move selection in results list |
Left/Right |
Switch focus between results and detail pane |
Tab/Shift+Tab |
Cycle focus: search β results β detail |
PageUp/PageDown |
Scroll by page |
Home/End |
Jump to first/last result |
Alt+h/j/k/l |
Vim-style navigation (left/down/up/right) |
| Key | Action |
|---|---|
F3 |
Open agent filter palette |
F4 |
Open workspace filter palette |
F5 |
Set "from" time filter |
F6 |
Set "to" time filter |
Shift+F3 |
Scope to currently selected result's agent |
Shift+F4 |
Clear workspace filter |
Shift+F5 |
Cycle time presets: 24h β 7d β 30d β all |
Ctrl+Del |
Clear all active filters |
| Key | Action |
|---|---|
F7 |
Cycle context window size: S β M β L β XL |
F9 |
Toggle match mode: prefix (default) β standard |
F12 |
Cycle ranking: recent β balanced β relevance β quality β newest β oldest |
Shift+/= |
Increase items per pane (density) |
- |
Decrease items per pane |
| Key | Action |
|---|---|
m |
Toggle selection on current result |
Ctrl+A |
Select/deselect all visible results |
A |
Open bulk actions menu (when items selected) |
Ctrl+Enter |
Add to multi-open queue |
Ctrl+O |
Open all queued items in editor |
y |
Copy current item (path or content to clipboard) |
Ctrl+Y |
Copy all selected items |
| Key | Action |
|---|---|
Space |
Toggle full-screen detail view |
/ |
Start find-in-detail search |
n |
Jump to next match (in find mode) |
N |
Jump to previous match |
g |
Scroll to top (in full-screen) |
G |
Scroll to bottom (in full-screen) |
c |
Copy visible content |
o |
Open in external viewer |
- Click on result to select
- Click on filter chip to edit/remove
- Scroll in any pane
- Double-click to open result
Cycle through modes with F12:
-
Recent Heavy (default): Strongly favors recent conversations
- Score =
text_relevance Γ 0.3 + recency Γ 0.7 - Best for: "What was I working on?"
- Score =
-
Balanced: Equal weight to relevance and recency
- Score =
text_relevance Γ 0.5 + recency Γ 0.5 - Best for: General-purpose search
- Score =
-
Relevance: Prioritizes text match quality
- Score =
text_relevance Γ 0.8 + recency Γ 0.2 - Best for: "Find the best explanation of X"
- Score =
-
Match Quality: Penalizes fuzzy/wildcard matches
- Score =
text_relevance Γ 0.7 + recency Γ 0.2 + match_exactness Γ 0.1 - Best for: Precise technical searches
- Score =
-
Date Newest: Pure chronological order (newest first)
- Ignores relevance scoring entirely
- Best for: "Show me all recent activity"
-
Date Oldest: Pure reverse chronological order (oldest first)
- Ignores relevance scoring entirely
- Best for: "When did I first work on this?"
-
Text Relevance (BM25): Tantivy's implementation of Okapi BM25, considering:
- Term frequency in document
- Inverse document frequency across corpus
- Document length normalization
-
Recency: Exponential decay from current time
- Documents from today: ~1.0
- Documents from last week: ~0.7
- Documents from last month: ~0.3
-
Match Exactness: Bonus for exact matches vs wildcards
- Exact phrase: 1.0
- Prefix match: 0.8
- Suffix/Substring: 0.5
- Fuzzy fallback: 0.3
Each connector transforms agent-specific formats into a unified schema:
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Agent Files β βββΆ β Connector β βββΆ β Normalized β
β (proprietary) β β (per-agent) β β Conversation β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
JSONL detect() agent_slug
SQLite scan() workspace
Markdown messages[]
JSON created_at
Different agents use different role names:
| Agent | Original | Normalized |
|---|---|---|
| Claude Code | human, assistant |
user, assistant |
| Codex | user, assistant |
user, assistant |
| ChatGPT | user, assistant, system |
user, assistant, system |
| Cursor | user, assistant |
user, assistant |
| Aider | (markdown headers) | user, assistant |
Agents store timestamps inconsistently:
| Format | Example | Handling |
|---|---|---|
| Unix milliseconds | 1699900000000 |
Direct conversion |
| Unix seconds | 1699900000 |
Multiply by 1000 |
| ISO 8601 | 2024-01-15T10:30:00Z |
Parse with chrono |
| Missing | null |
Use file modification time |
Tool calls, code blocks, and nested structures are flattened for searchability:
// Original (Claude Code)
{"type": "tool_use", "name": "Read", "input": {"path": "/foo/bar.rs"}}
// Flattened for indexing
"[Tool: Read] path=/foo/bar.rs"The same conversation content can appear multiple times due to:
- Agent file rewrites
- Backup files
- Symlinked directories
- Re-indexing
cass uses a multi-layer deduplication strategy:
-
Message Hash: SHA-256 of
(role + content + timestamp)- Identical messages in different files are stored once
-
Conversation Fingerprint: Hash of first N message hashes
- Detects duplicate conversation files
-
Search-Time Dedup: Results are deduplicated by content similarity
- Even if stored twice, shown once in results
Common low-value content is filtered from results:
- Empty messages
- Pure whitespace
- System prompts (unless searching for them)
- Repeated tool acknowledgments
# Find past solutions for similar errors
cass search "TypeError: Cannot read property" --days 30
# In TUI: F12 to switch to "relevance" mode for best matches# What has ANY agent said about authentication in this project?
cass search "authentication" --workspace /path/to/project
# Export findings for a new agent's context
cass export /path/to/relevant/session.jsonl --format markdown# What did I work on today?
cass timeline --today --json | jq '.groups[].conversations'
# TUI: Press Shift+F5 to cycle through time filters# Find all debugging sessions for a specific file
cass search "debug src/auth/login.rs" --agent claude
# Expand context around a specific line in a session
cass expand /path/to/session.jsonl -n 150 -C 10# Current agent searches what previous agents learned
cass search "database migration strategy" --robot --fields minimal
# Get full context for a relevant session
cass view /path/to/session.jsonl -n 42 --json# Export high-quality problem-solving sessions
cass search "bug fix" --robot --limit 100 | \
jq '.hits[] | select(.score > 0.8)' > training_candidates.jsonPress Ctrl+P to open the command paletteβa fuzzy-searchable menu of all available actions.
| Command | Description |
|---|---|
| Toggle theme | Switch between dark/light mode |
| Toggle density | Cycle Compact β Cozy β Spacious |
| Toggle help strip | Pin/unpin the contextual help bar |
| Check updates | Show update assistant banner |
| Filter: agent | Open agent filter picker |
| Filter: workspace | Open workspace filter picker |
| Filter: today | Restrict results to today |
| Filter: last 7 days | Restrict results to past week |
| Filter: date range | Prompt for custom since/until |
| Saved views | List and manage saved view slots |
| Save view to slot N | Save current filters to slot 1-9 |
| Load view from slot N | Restore filters from slot 1-9 |
| Bulk actions | Open bulk menu (when items selected) |
| Reload index/view | Refresh the search reader |
- Press
Ctrl+Pto open - Type to fuzzy-filter commands
- Use
Up/Downto navigate - Press
Enterto execute - Press
Escto close
Save your current filter configuration to one of 9 slots for instant recall.
- Active filters (agent, workspace, time range)
- Current ranking mode
- The search query
| Key | Action |
|---|---|
Shift+1 through Shift+9 |
Save current view to slot |
1 through 9 |
Load view from slot |
Ctrl+Pβ "Save view to slot N"Ctrl+Pβ "Load view from slot N"Ctrl+Pβ "Saved views" to list all slots
Views are stored in tui_state.json and persist across sessions. Clear all saved views with Ctrl+Shift+Del (resets all TUI state).
Control how many lines each search result occupies. Cycle with Shift+D or via the command palette.
| Mode | Lines per Result | Best For |
|---|---|---|
| Compact | 3 | Maximum results visible, scanning many items |
| Cozy (default) | 5 | Balanced view with context |
| Spacious | 8 | Detailed preview, fewer results |
The pane automatically adjusts how many results fit based on terminal height and density mode.
Save important search results with notes and tags for later reference.
- Persistent storage: Bookmarks saved to
bookmarks.db(SQLite) - Notes: Add annotations explaining why you bookmarked something
- Tags: Organize with comma-separated tags (e.g., "rust, important, auth")
- Search: Find bookmarks by title, note, or snippet content
- Export/Import: JSON format for backup and sharing
{
"id": 1,
"title": "Auth bug fix discussion",
"source_path": "/path/to/session.jsonl",
"line_number": 42,
"agent": "claude_code",
"workspace": "/projects/myapp",
"note": "Good explanation of JWT refresh flow",
"tags": "auth, jwt, important",
"snippet": "The token refresh logic should..."
}Bookmarks are stored separately from the main index:
- Linux:
~/.local/share/coding-agent-search/bookmarks.db - macOS:
~/Library/Application Support/coding-agent-search/bookmarks.db - Windows:
%APPDATA%\coding-agent-search\bookmarks.db
To achieve sub-60ms latency on large datasets, cass implements a multi-tier caching strategy in src/search/query.rs:
- Sharded LRU Cache: The
prefix_cacheis split into shards (default 256 entries each) to reduce mutex contention during concurrent reads/writes from the async searcher. - Bloom Filter Pre-checks: Each cached hit stores a 64-bit Bloom filter mask of its content tokens. When a user types more characters, we check the mask first. If the new token isn't in the mask, we reject the cache entry immediately without a string comparison.
- Predictive Warming: A background
WarmJobthread watches the input. When the user pauses typing, it triggers a lightweight "warm-up" query against the Tantivy reader to pre-load relevant index segments into the OS page cache.
The system is designed for extensibility via the Connector trait (src/connectors/mod.rs). This allows cass to treat disparate log formats as a uniform stream of events.
classDiagram
class Connector {
<<interface>>
+detect() DetectionResult
+scan(ScanContext) Vec~NormalizedConversation~
}
class NormalizedConversation {
+agent_slug String
+messages Vec~NormalizedMessage~
}
Connector <|-- CodexConnector
Connector <|-- ClineConnector
Connector <|-- ClaudeCodeConnector
Connector <|-- GeminiConnector
Connector <|-- OpenCodeConnector
Connector <|-- AmpConnector
Connector <|-- CursorConnector
Connector <|-- ChatGptConnector
Connector <|-- AiderConnector
Connector <|-- PiAgentConnector
CodexConnector ..> NormalizedConversation : emits
ClineConnector ..> NormalizedConversation : emits
ClaudeCodeConnector ..> NormalizedConversation : emits
GeminiConnector ..> NormalizedConversation : emits
OpenCodeConnector ..> NormalizedConversation : emits
AmpConnector ..> NormalizedConversation : emits
CursorConnector ..> NormalizedConversation : emits
ChatGptConnector ..> NormalizedConversation : emits
AiderConnector ..> NormalizedConversation : emits
PiAgentConnector ..> NormalizedConversation : emits
- Polymorphic Scanning: The indexer runs connector factories in parallel via rayon, creating fresh
Box<dyn Connector>instances that are unaware of each other's underlying file formats (JSONL, SQLite, specialized JSON). - Resilient Parsing: Connectors handle legacy formats (e.g., integer vs ISO timestamps) and flatten complex tool-use blocks into searchable text.
cass employs a dual-storage strategy to balance data integrity with search performance.
- Ingestion: Connectors scan proprietary agent files and normalize them into standard structs.
- Storage (SQLite): The Source of Truth. Data is persisted to a normalized SQLite schema (
messages,conversations,agents). This ensures ACID compliance, reliable storage, and supports complex relational queries (stats, grouping). - Search Index (Tantivy): The Speed Layer. New messages are incrementally pushed to a Tantivy full-text index. This index is optimized for speed:
- Fields:
title,content,agent,workspace,created_at. - Prefix Fields:
title_prefixandcontent_prefixuse Index-Time Edge N-Grams (not stored on disk to save space) for instant prefix matching. - Deduping: Search results are deduplicated by content hash to remove noise from repeated tool outputs.
flowchart LR
classDef pastel fill:#f4f2ff,stroke:#c2b5ff,color:#2e2963;
classDef pastel2 fill:#e6f7ff,stroke:#9bd5f5,color:#0f3a4d;
classDef pastel3 fill:#e8fff3,stroke:#9fe3c5,color:#0f3d28;
classDef pastel4 fill:#fff7e6,stroke:#f2c27f,color:#4d350f;
classDef pastel5 fill:#ffeef2,stroke:#f5b0c2,color:#4d1f2c;
subgraph Sources["Local Sources"]
A1[Codex]:::pastel
A2[Cline]:::pastel
A3[Gemini]:::pastel
A4[Claude]:::pastel
A5[OpenCode]:::pastel
A6[Amp]:::pastel
A7[Cursor]:::pastel
A8[ChatGPT]:::pastel
A9[Aider]:::pastel
A10[Pi-Agent]:::pastel
end
subgraph Remote["Remote Sources"]
R1["sources.toml"]:::pastel
R2["SSH/rsync\nSync Engine"]:::pastel2
R3["remotes/\nSynced Data"]:::pastel3
end
subgraph "Ingestion Layer"
C1["Connectors\nDetect & Scan\nNormalize & Dedupe"]:::pastel2
end
subgraph "Dual Storage"
S1["SQLite (WAL)\nSource of Truth\nRelational Data\nMigrations"]:::pastel3
T1["Tantivy Index\nSearch Optimized\nEdge N-Grams\nPrefix Cache"]:::pastel4
end
subgraph "Presentation"
U1["TUI (Ratatui)\nAsync Search\nFilter Pills\nDetails"]:::pastel5
U2["CLI / Robot\nJSON Output\nAutomation"]:::pastel5
end
A1 --> C1
A2 --> C1
A3 --> C1
A4 --> C1
A5 --> C1
A6 --> C1
A7 --> C1
A8 --> C1
A9 --> C1
A10 --> C1
R1 --> R2
R2 --> R3
R3 --> C1
C1 -->|Persist| S1
C1 -->|Index| T1
S1 -.->|Rebuild| T1
T1 -->|Query| U1
T1 -->|Query| U2
- Non-Blocking: The indexer runs in a background thread. You can search while it works.
- Parallel Discovery: Connector detection and scanning run in parallel across all CPU cores using rayon, significantly reducing startup time when multiple agents are installed.
- Watch Mode: Uses file system watchers (
notify) to detect changes in agent logs. When you save a file or an agent replies,cassre-indexes just that conversation and refreshes the search view automatically. - Real-Time Progress: The TUI footer updates in real-time showing discovered agents during scanning (e.g., "π Discovering (5 agents found)") and indexing progress with sparkline visualization (e.g., "π¦ Indexing 150/2000 (7%) βββββ").
The interactive interface (src/ui/tui.rs) is the largest component (~3.5k lines), implementing a sophisticated Immediate Mode architecture using ratatui.
- Application State: A monolithic struct tracks the entire UI state (search query, cursor position, scroll offsets, active filters, and cached details).
- Event Loop: A polling loop handles standard inputs (keyboard/mouse) and custom events (Search results ready, Progress updates).
- Debouncing: User input triggers an async search task via a
tokiochannel. To prevent UI freezing, we debounce keystrokes (150ms) and run queries on a separate thread, updating the state only when results return. - Optimistic Rendering: The UI renders the current state immediately (60 FPS), drawing "stale" results or loading skeletons while waiting for the async searcher.
graph TD
Input([User Input]) -->|Key/Mouse| EventLoop
EventLoop -->|Update| State[App State]
State -->|Render| Terminal
State -->|Query Change| Debounce{Debounce}
Debounce -->|Fire| SearchTask[Async Search]
SearchTask -->|Results| Channel
Channel -->|Poll| EventLoop
Data integrity is paramount. cass treats the SQLite database (src/storage/sqlite.rs) as an append-only log for conversations:
- Immutable History: When an agent adds a message to a conversation, we don't update the existing row. We insert the new message linked to the conversation ID.
- Deduplication: The connector layer uses content hashing to prevent duplicate messages if an agent re-writes a file.
- Versioning: A
schema_versionmeta-table and strict migration path ensure that upgrades (like the recent move to v3) are safe and atomic.
cass is designed to handle index corruption gracefully and recover automatically.
Every Tantivy index stores a schema_hash.json file containing the schema version:
{"schema_hash":"tantivy-schema-v4-edge-ngram-agent-string"}| Scenario | Detection | Recovery |
|---|---|---|
| Missing index | No meta.json |
Clean create |
| Schema mismatch | Hash differs from current | Full rebuild |
Corrupted schema_hash.json |
Invalid JSON or missing | Delete and recreate |
Missing schema_hash.json |
File not found | Assume outdated, rebuild |
# Force complete rebuild
cass index --full --force-rebuild
# Check index health
cass health --json
# Diagnostic information
cass diag --verbose- Never lose source data:
cassonly reads agent files, never modifies them - Rebuild is always safe: Worst case, re-index from source files
- Atomic commits: Index writes are transactional
- Graceful degradation: Search falls back to SQLite if Tantivy fails
The --watch flag enables real-time index updates as agent files change.
File change detected
β
[2 second debounce window] β Accumulate more changes
β
[5 second max wait] β Force flush if changes keep coming
β
Re-index affected files
- Debounce: 2 seconds (wait for burst of changes to settle)
- Max wait: 5 seconds (don't wait forever during continuous activity)
Each file system event is routed to the appropriate connector:
~/.claude/projects/foo.jsonl β ClaudeCodeConnector
~/.codex/sessions/rollout-*.jsonl β CodexConnector
~/.aider.chat.history.md β AiderConnector
Watch mode maintains watch_state.json:
{
"last_scan_ts": 1699900000000,
"watched_paths": [
"~/.claude/projects",
"~/.codex/sessions"
]
}- File-level filtering only: When a file is modified, the entire file is re-scanned
- 1-second mtime slack: Accounts for filesystem timestamp granularity
- No per-message filtering: Prevents data loss when new messages are appended
Generate tab-completion scripts for your shell.
Bash:
cass completions bash > ~/.local/share/bash-completion/completions/cass
# Or: cass completions bash >> ~/.bashrcZsh:
cass completions zsh > "${fpath[1]}/_cass"
# Or add to ~/.zshrc: eval "$(cass completions zsh)"Fish:
cass completions fish > ~/.config/fish/completions/cass.fishPowerShell:
cass completions powershell >> $PROFILE- Subcommands (
search,index,stats, etc.) - Flags and options (
--robot,--agent,--limit) - File paths for relevant arguments
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/coding_agent_session_search/main/install.sh \
| bash -s -- --easy-mode --verifycassOn first run, cass performs a full index. You'll see progress in the footer. Search works immediately (falling back to SQLite or partial results until complete).
- Type to search: "python error", "refactor auth", "c++".
- Wildcards: Use
foo*(prefix),*foo(suffix), or*foo*(contains) for flexible matching. - Navigation:
Up/Downto select,Rightto focus detail pane.Up/Downin search bar navigates query history. - Filters:
F3: Filter by Agent (e.g., "codex").F4: Filter by Workspace/Project.F5/F6: Time filters (Today, Week, etc.).
- Modes:
F2: Toggle Dark/Light theme.F12: Cycle ranking mode (recent β balanced β relevance β quality β newest β oldest).Ctrl+B: Toggle rounded/plain borders.
- Actions:
Enter: Open original log file in$EDITOR.Ctrl+Enter: Add current result to queue (multi-open).Ctrl+O: Open all queued results in editor.m: Toggle selection on current item.A: Bulk actions menu (when items selected).y: Copy file path or snippet to clipboard./: Find text within detail pane.Ctrl+Shift+R: Trigger manual re-index (refresh search results).Ctrl+Shift+Del: Reset TUI state (clear history, filters, layout).
Aggregate sessions from your other machines into a unified index:
# Add a remote machine
cass sources add [email protected] --preset macos-defaults
# Sync sessions from all sources
cass sources sync
# Filter by source in TUI: F11 cycles, Shift+F11 opens menuSee Remote Sources (Multi-Machine Search) for full documentation.
The cass binary supports both interactive use and automation.
# Interactive
cass [tui] [--data-dir DIR] [--once]
# Indexing
cass index [--full] [--watch] [--data-dir DIR] [--idempotency-key KEY]
# Search
cass search "query" --robot --limit 5 [--timeout 5000] [--explain] [--dry-run]
cass search "error" --robot --aggregate agent,workspace --fields minimal
# Inspection & Health
cass status --json # Quick health snapshot
cass health # Minimal pre-flight check (<50ms)
cass capabilities --json # Feature discovery
cass introspect --json # Full API schema
cass context /path/to/session --json # Find related sessions
cass view /path/to/file -n 42 --json # View source at line
# Session Analysis
cass export /path/to/session --format markdown -o out.md # Export conversation
cass expand /path/to/session -n 42 -C 5 --json # Context around line
cass timeline --today --json # Activity timeline
# Remote Sources
cass sources add user@host --preset macos-defaults # Add machine
cass sources sync # Sync sessions
cass sources doctor # Check connectivity
cass sources mappings list laptop # View path mappings
# Utilities
cass stats --json
cass completions bash > ~/.bash_completion.d/cass| Command | Purpose |
|---|---|
cass (default) |
Start TUI + background watcher |
index --full |
Complete rebuild of DB and search index |
index --watch |
Daemon mode: watch for file changes, reindex automatically |
search --robot |
JSON output for automation pipelines |
status / state |
Health snapshot: index freshness, DB stats, recommended action |
health |
Minimal health check (<50ms), exit 0=healthy, 1=unhealthy |
capabilities |
Discover features, versions, limits (for agent introspection) |
introspect |
Full API schema: commands, arguments, response shapes |
context <path> |
Find sessions related by workspace, day, or agent |
view <path> -n N |
View source file at specific line (follow-up on search) |
export <path> |
Export conversation to markdown/HTML/JSON |
expand <path> -n N |
Show messages around a specific line number |
timeline |
Activity timeline with grouping by hour/day |
sources |
Manage remote sources: add/list/remove/doctor/sync/mappings |
-
Verified Install: The installer enforces SHA256 checksums.
-
Sandboxed Data: All indexes/DBs live in standard platform data directories (
~/.local/share/coding-agent-searchon Linux). -
Read-Only Source:
cassnever modifies your agent log files. It only reads them.
The project ships with a robust installer (install.sh / install.ps1) designed for CI/CD and local use:
-
Checksum Verification: Validates artifacts against a
.sha256file or explicit--checksumflag. -
Rustup Bootstrap: Automatically installs the nightly toolchain if missing.
-
Easy Mode:
--easy-modeautomates installation to~/.local/binwithout prompts. -
Platform Agnostic: Detects OS/Arch (Linux/macOS/Windows, x86_64/arm64) and fetches the correct binary.
-
Config: Loads
.envviadotenvy::dotenv().ok(); configure API/base paths there. Do not overwrite.env. -
Data Location: Defaults to standard platform data directories (e.g.,
~/.local/share/coding-agent-search). Override withCASS_DATA_DIRor--data-dir. -
ChatGPT Support: The ChatGPT macOS app stores conversations in versioned formats:
- v1 (legacy): Unencrypted JSON in
conversations-{uuid}/β fully indexed. - v2/v3: Encrypted with AES-256-GCM, key stored in macOS Keychain (OpenAI-signed apps only) β detected but skipped.
Encrypted conversations require keychain access which isn't available to third-party apps. Legacy unencrypted conversations are indexed automatically.
- v1 (legacy): Unencrypted JSON in
-
Logs: Written to
cass.log(daily rotating) in the data directory. -
Updates: Interactive TUI checks for GitHub releases on startup. Skip with
CODING_AGENT_SEARCH_NO_UPDATE_PROMPT=1orTUI_HEADLESS=1. -
Cache tuning:
CASS_CACHE_SHARD_CAP(per-shard entries, default 256) andCASS_CACHE_TOTAL_CAP(total cached hits across shards, default 2048) control prefix cache size; raise cautiously to avoid memory bloat. -
Cache debug: set
CASS_DEBUG_CACHE_METRICS=1to emit cache hit/miss/shortfall/reload stats via tracing (debug level). -
Watch testing (dev only):
cass index --watch --watch-once path1,path2triggers a single reindex without filesystem notify (also respectsCASS_TEST_WATCH_PATHSfor backward compatibility); useful for deterministic tests/smoke runs.
-
Checksum mismatch: Ensure
.sha256is reachable or pass--checksumexplicitly. Check proxies/firewalls. -
Binary not on PATH: Append
~/.local/bin(or your--dest) toPATH; re-open shell. -
Nightly missing in CI: Set
RUSTUP_INIT_SKIP=1if toolchain is preinstalled; otherwise allow installer to run rustup. -
Watch mode not triggering: Confirm
watch_state.jsonupdates and that connector roots are accessible;notifyrelies on OS file events (inotify/FSEvents). -
Reset TUI state: Run
cass tui --reset-state(or pressCtrl+Shift+Delin the TUI) to deletetui_state.jsonand restore defaults.
We target Rust Nightly to leverage the latest optimizations.
# Format & Lint
cargo fmt --check
cargo clippy --all-targets -- -D warnings
# Build & Test
cargo build --release
cargo test
# Run End-to-End Tests
cargo test --test e2e_index_tui
cargo test --test install_scriptsThe release profile is aggressively optimized for binary size and performance:
[profile.release]
lto = true # Link-time optimization across all crates
codegen-units = 1 # Single codegen unit for better optimization
strip = true # Remove debug symbols from binary
panic = "abort" # Smaller panic handling (no unwinding)
opt-level = "z" # Optimize for size over speedTrade-offs:
- Build time is significantly longer (~3-5x)
- Binary size is ~40-50% smaller
- No stack traces on panic (use debug builds for development)
The CI pipeline (.github/workflows/ci.yml) runs on every PR and push to main:
| Job | Purpose | Artifacts |
|---|---|---|
check |
fmt, clippy, tests, benches, UBS scan | None |
e2e |
Integration tests (install, index, filters) | test-artifacts-e2e (traces, logs) |
coverage |
Code coverage with llvm-cov | coverage-report (lcov.info, summary) |
Coverage Reports:
lcov.info- LCOV format for tools like codecovcoverage-summary.txt- Human-readable summary- Coverage % shown in GitHub Actions step summary
Test Artifacts:
- Trace files from
--trace-fileruns - Test run summary logs
- Retained for 7 days (e2e) / 30 days (coverage)
# Generate coverage locally
cargo install cargo-llvm-cov
cargo llvm-cov --all-features --workspace --text
# Run specific e2e tests
cargo test --test e2e_filters -- --test-threads=1The TUI automatically saves your preferences to tui_state.json in the data directory.
| Setting | Description |
|---|---|
match_mode |
Prefix vs standard matching |
ranking |
Current ranking mode (recent/balanced/relevance/quality/newest/oldest) |
density_mode |
Compact/Cozy/Spacious |
context_window |
S/M/L/XL preview size |
query_history |
Recent searches (deduplicated, max 100) |
saved_views |
Filter/query snapshots for slots 1-9 |
help_pinned |
Whether help strip is always visible |
pane_limit |
Items per pane (overrides density default) |
- Linux:
~/.local/share/coding-agent-search/tui_state.json - macOS:
~/Library/Application Support/coding-agent-search/tui_state.json - Windows:
%APPDATA%\coding-agent-search\tui_state.json
# Via CLI flag
cass tui --reset-state
# Via keyboard (in TUI)
Ctrl+Shift+DelThis deletes tui_state.json and restores all defaults.
- Schema v4 (hash
tantivy-schema-v4-edge-ngram-preview) stores agent/workspace/source_path/msg_idx/created_at/title/content plus edge-ngrams (title_prefix,content_prefix) for type-ahead matching. - New
previewfield keeps a short, stored excerpt (~200 chars + ellipsis) so prefix-only queries can render snippets without pulling full content. - Rebuilds auto-trigger when the schema hash changes; index directory is recreated as needed. Tokenizer:
hyphen_normalizeto keep βcma-esβ searchable while enabling prefix splits.
- Wildcard patterns:
WildcardPatternenum supportsExact,Prefix(foo*),Suffix(*foo), andSubstring(foo). Prefix uses edge n-grams; suffix/substring use TantivyRegexQuerywith escaped special characters. - Auto-fuzzy fallback:
search_with_fallback()wraps the base search; if results < threshold and query has no wildcards, retries with*term*patterns and setswildcard_fallbackflag for UI indicator. - Cache-first: per-agent + global LRU shards (env
CASS_CACHE_SHARD_CAP, default 256). Cached hits store lowered content/title/snippet and a 64-bit bloom mask; bloom + substring keeps validation fast. - Fallback order: Tantivy (primary) β SQLite FTS (consistency) with deduping/noise filtering. Prefix-only snippet path tries cached prefix snippet, then a cheap local snippet, else Tantivy
SnippetGenerator. - Warm worker: runtime-aware, debounced (env
CASS_WARM_DEBOUNCE_MS, default 120β―ms), runs a tiny 1-doc search to keep the reader hot; reloads are debounced (300β―ms) and counted in metrics (cache hit/miss/shortfall/reloads tracked internally).
- Opens SQLite + Tantivy;
--fullclears tables/FTS and wipes Tantivy docs;--force-rebuildrecreates index dir when schema changes. - Parallel connector loop: detect β scan runs concurrently across all connectors using rayon's parallel iterator, with atomic progress counters updating discovered agent count and conversation totals in real-time. Ingestion into SQLite and Tantivy happens sequentially after all scans complete. Watch mode: debounced filesystem watcher, path classification per connector, since_ts tracked in
watch_state.json, incremental reindex of touched sources. TUI startup spawns a background indexer with watch enabled.
- Normalized relational model (agents, workspaces, conversations, messages, snippets, tags) with FTS mirror on messages. Single-transaction insert/upsert, append-only unless
--full.schema_versionguard; bundled modern SQLite.
- Three-pane layout (agents β results β detail), responsive splits, focus model (Tab/Shift+Tab), mouse support. Detail tabs (Messages/Snippets/Raw) plus full-screen modal with role colors, code blocks, JSON pretty-print, highlights. Footer packs shortcuts + mode badges; state persisted in
tui_state.json.
- Each connector implements
detect(root discovery) andscan(since_ts-aware ingestion). External IDs preserved for dedupe; workspace/source paths carried through; roles normalized.
- Checksum-verified easy/normal modes, optional quickstart (index on first run), rustup bootstrap if needed. PATH hints appended with warnings; SHA256 required.
- Benches:
index_perfmeasures full index build;runtime_perfcovers search latency + indexing micro-cases. - Tests: unit + integration + headless TUI e2e; installer checksum fixtures; watch-mode and index/search integration; cache/bloom UTF-8 safety and bloom gate tests.
1. Speed Over Space
cass makes deliberate trade-offs favoring query latency over storage efficiency:
- Edge N-grams: We pre-compute prefix substrings (length 2-20) for every token during indexing. This multiplies index size but enables O(1) prefix lookups instead of O(n) regex scans.
- Dual Storage: Data lives in both SQLite (relational queries, ACID guarantees) and Tantivy (full-text search). This redundancy costs disk space but provides optimal performance for each access pattern.
- Bloom Filter Caching: Each cached search hit stores a 64-bit bloom mask plus lowercase copies of content/title/snippet. Memory cost per cached hit: ~500 bytes. Benefit: sub-millisecond cache filtering.
2. Local-First, Privacy by Design
Your coding sessions contain sensitive informationβAPI keys, internal codenames, debugging strategies. cass is architected to never transmit data:
- No telemetry, analytics, or crash reporting
- No network calls except optional GitHub release checks (easily disabled)
- Index and database stored in user-controlled directories
- Source agent files are read-onlyβnever modified
3. Resilience Over Strictness
AI agents make mistakes. Humans make typos. cass is aggressively forgiving:
- Typo Correction: Levenshtein distance β€2 flags are auto-corrected with teaching feedback
- Case Normalization:
--Robot,--LIMITβ--robot,--limit - Command Aliases:
find/query/qall resolve tosearch - Graceful Degradation: Encrypted ChatGPT conversations are detected and skipped rather than crashing
4. Incremental by Default
Full reindexing is expensive. cass minimizes work through careful state tracking:
- File Modification Times: Connectors skip unchanged files using mtime comparison with 1-second slack for filesystem granularity
- Append-Only Messages: When a conversation grows, only new messages (where
idx > max_existing_idx) are inserted - Watch State Persistence: Per-connector timestamps in
watch_state.jsonenable surgical re-scanning
| Decision | Rationale | Trade-off |
|---|---|---|
| Rust | Memory safety without GC pauses; excellent async/parallelism | Steeper learning curve; longer compile times |
| SQLite + Tantivy | Best-in-class for each job (relational vs. FTS) | Data duplication; two systems to maintain |
| Edge N-grams | Sub-60ms prefix search on 100K+ documents | 3-5x index size increase |
| Sharded LRU Cache | Reduces mutex contention in async searcher | Memory overhead; cache coherence complexity |
| Connector Trait | Clean abstraction for diverse agent formats | Each new agent requires dedicated connector code |
| Atomic Progress Counters | Lock-free UI updates during indexing | Relaxed ordering may show slightly stale counts |
Traditional full-text search requires expensive wildcard expansion for prefix queries. cass inverts this by pre-computing all prefixes at index time:
Input token: "authentication"
Generated n-grams: "au", "aut", "auth", "authe", "authen", "authent", "authenti",
"authentic", "authentica", "authenticat", "authenticati",
"authenticatio", "authentication"
Implementation (src/search/tantivy.rs:288-305):
- N-gram lengths: 2 to 20 characters
- Stored in separate Tantivy fields:
title_prefix,content_prefix - These fields are indexed but NOT stored (saves disk space)
- Query "auth*" becomes a simple term lookup on the prefix field
Performance Impact:
- Index build time: ~20% slower
- Index size: ~3x larger
- Query time for prefixes: O(1) instead of O(n) regex scan
When filtering cached search results against a refined query, string comparison is expensive. cass uses a 64-bit bloom filter as a fast negative gate:
fn hash_token(tok: &str) -> u64 {
let mut h: u64 = 5381; // djb2 initial value
for b in tok.as_bytes() {
h = ((h << 5).wrapping_add(h)).wrapping_add(u64::from(*b));
}
1u64 << (h % 64) // Map to single bit position
}How It Works:
- During caching, compute
bloom64 = hash(token1) | hash(token2) | ...for all content tokens - On cache lookup, compute query bloom mask the same way
- If
(cached.bloom64 & query_bloom) != query_bloom, the cached hit cannot matchβskip expensive string comparison - If bloom passes, proceed with actual substring matching
False Positive Rate: With 64 bits and typical 5-15 tokens per query, ~70% of non-matching hits are rejected by bloom alone.
Tantivy provides BM25 (Best Match 25) scoring out of the box. cass extends this with:
Match Type Quality Factors:
| Match Type | Quality Factor |
|---|---|
| Exact | 1.0 |
| Prefix | 0.9 |
| Suffix | 0.8 |
| Substring | 0.7 |
| Implicit Wildcard (fallback) | 0.6 |
Ranking Mode Formulas:
Recent Heavy: score = bm25 Γ 0.3 + recency Γ 0.7
Balanced: score = bm25 Γ 0.5 + recency Γ 0.5
Relevance Heavy: score = bm25 Γ 0.8 + recency Γ 0.2
Match Quality: score = bm25 Γ 0.7 + recency Γ 0.2 + quality_factor Γ 0.1
Where recency is an exponential decay: e^(-age_days / decay_constant)
cass uses Rayon's work-stealing thread pool for parallel agent discovery:
let pending_batches: Vec<_> = connector_factories
.into_par_iter()
.filter_map(|(name, factory)| {
let conn = factory(); // Each thread gets fresh instance
if !conn.detect().detected { return None; }
conn.scan(&ctx).ok().map(|convs| (name, convs))
})
.collect(); // Parallel collectionWhy This Matters:
- 9 connectors Γ ~100ms average scan time = 900ms sequential
- With 4 cores: ~250ms parallel (3.6x speedup)
- Atomic counters provide lock-free progress updates to UI
cass automatically selects the optimal query strategy based on pattern:
| Pattern | Strategy | Implementation |
|---|---|---|
foo (exact) |
Term query + edge n-gram | Direct Tantivy lookup |
foo* (prefix) |
Edge n-gram field query | Uses pre-computed prefixes |
*foo (suffix) |
Regex query | RegexQuery(".*foo") |
*foo* (substring) |
Regex query | RegexQuery(".*foo.*") |
Auto-Fallback Logic: When exact search returns <3 results and query has 1-2 terms, automatically retry with *term* wildcards. Results are flagged with wildcard_fallback: true.
| Operation | Typical Latency | Conditions |
|---|---|---|
| Prefix search (cached) | 2-8ms | Warm cache, <1000 results |
| Prefix search (cold) | 40-60ms | First query, index in page cache |
| Substring search | 80-200ms | Regex fallback required |
| Full reindex | 5-30s | Depending on total conversation count |
| Incremental reindex | 50-500ms | Single conversation update |
| TUI render frame | <16ms | 60 FPS target achieved |
| Component | Typical Size | Notes |
|---|---|---|
| Base process | ~30MB | Rust binary + runtime |
| SQLite connection | ~5MB | WAL mode, shared cache |
| Tantivy reader | ~20-50MB | Segment metadata + mmap overhead |
| Search cache | ~10-50MB | 2048 entries Γ ~500 bytes + hit data |
| TUI state | ~2MB | Result buffers, render state |
Total typical footprint: 70-140MB for a 50K message corpus.
| Component | Size Formula | Example (50K messages) |
|---|---|---|
| SQLite database | ~200 bytes/message | ~10MB |
| Tantivy index (base) | ~150 bytes/message | ~7.5MB |
| Edge n-gram overhead | ~3x base index | ~22MB |
| Total | ~600 bytes/message | ~30MB |
cass is designed for individual developer use (1K-500K messages). Beyond that:
| Message Count | Search Latency | Index Build | Recommendation |
|---|---|---|---|
| <10K | <20ms | <5s | Excellent performance |
| 10K-100K | 20-60ms | 5-30s | Target operating range |
| 100K-500K | 60-150ms | 30-120s | Consider periodic pruning |
| >500K | >200ms | >2min | Archive old sessions |
What cass Reads:
- Agent session files (JSONL, JSON, SQLite, Markdown)
- File modification times (for incremental indexing)
- Environment variables (configuration only)
What cass Writes:
~/.local/share/coding-agent-search/(or platform equivalent):agent_search.db- SQLite databasetantivy_index/- Full-text search indextui_state.json- UI preferenceswatch_state.json- Incremental index statecass.log- Rotating log file
What cass NEVER Does:
- Modify source agent files (strictly read-only)
- Make network requests (except optional update checks)
- Execute code from indexed content
- Access files outside known agent directories
ChatGPT Encrypted Conversations:
- Versions 2 and 3 of the ChatGPT macOS app use AES-256-GCM encryption
- Keys are stored in the macOS Keychain, accessible only to OpenAI-signed apps
cassdetects encrypted files and gracefully skips them- Optional: Provide your own key via
CHATGPT_ENCRYPTION_KEY(base64) or~/.config/cass/chatgpt_key.bin
No Sensitive Data in Logs:
- Log files contain operation traces, not message content
- Error messages are sanitized to avoid leaking paths/content
cass assumes:
- The local filesystem is trusted
- The user running
cassshould have access to all indexed content - Network is untrusted (hence no network calls)
cass does NOT protect against:
- Root/admin access to the machine
- Memory forensics while running
- Physical access to the storage device
| Capability | grep/rg | cass |
|---|---|---|
| Raw text search | β Excellent | β Good |
| Structured JSON parsing | β Manual | β Automatic |
| Cross-format unification | β No | β 9 formats |
| Relevance ranking | β No | β BM25 + recency |
| Prefix search | β Regex only | β O(1) via n-grams |
| Incremental indexing | β N/A | β Built-in |
| Interactive TUI | β No | β Rich UI |
Verdict: grep is better for one-off searches in known files. cass excels at exploring your entire coding history across agents.
| Aspect | Cloud Search | cass |
|---|---|---|
| Privacy | β Data leaves your machine | β 100% local |
| Latency | ~100-500ms (network) | ~20-60ms (local) |
| Cost | π° Usage-based pricing | β Free |
| Offline use | β Requires internet | β Always available |
| Setup | Minutes (API keys, etc.) | `curl |
Verdict: Cloud search makes sense for team collaboration. cass is for individual developers who want speed and privacy.
cass actually uses SQLite FTS5 as a fallback! But Tantivy provides:
| Feature | SQLite FTS5 | Tantivy |
|---|---|---|
| BM25 scoring | β Basic | β Tunable |
| Prefix queries | β No native support | β Via edge n-grams |
| Concurrent reads | β Designed for it | |
| Index compaction | β Manual VACUUM | β Automatic merges |
| Memory mapping | β No | β Efficient mmap |
Verdict: SQLite FTS5 is great for simple search. Tantivy handles the sophisticated queries cass needs.
Every agent connector implements this interface (src/connectors/mod.rs):
pub trait Connector {
/// Check if this agent's data exists on the system
fn detect(&self) -> DetectionResult;
/// Scan and normalize all conversations (respecting since_ts for incremental)
fn scan(&self, ctx: &ScanContext) -> anyhow::Result<Vec<NormalizedConversation>>;
}
pub struct DetectionResult {
pub detected: bool,
pub root_paths: Vec<PathBuf>, // Where to watch for changes
pub version: Option<String>,
}
pub struct ScanContext {
pub data_root: PathBuf, // Base data directory
pub since_ts: Option<i64>, // Only scan files modified after this timestamp
}Step 1: Create the connector file (src/connectors/my_agent.rs):
use super::*;
pub struct MyAgentConnector;
impl Connector for MyAgentConnector {
fn detect(&self) -> DetectionResult {
let root = dirs::home_dir()
.map(|h| h.join(".my-agent/sessions"));
DetectionResult {
detected: root.as_ref().map(|p| p.exists()).unwrap_or(false),
root_paths: root.into_iter().collect(),
version: None,
}
}
fn scan(&self, ctx: &ScanContext) -> anyhow::Result<Vec<NormalizedConversation>> {
let mut conversations = Vec::new();
// Find session files
for entry in walkdir::WalkDir::new(&ctx.data_root) {
let entry = entry?;
if !entry.path().extension().map(|e| e == "json").unwrap_or(false) {
continue;
}
// Skip unchanged files for incremental indexing
if let Some(since) = ctx.since_ts {
if !file_modified_since(entry.path(), since) {
continue;
}
}
// Parse and normalize
let conv = parse_my_agent_file(entry.path())?;
conversations.push(conv);
}
Ok(conversations)
}
}Step 2: Register in the connector factory (src/indexer/mod.rs):
let connector_factories: Vec<(&'static str, fn() -> Box<dyn Connector + Send>)> = vec![
("codex", || Box::new(CodexConnector::new())),
("my_agent", || Box::new(MyAgentConnector)), // Add here
// ...
];Step 3: Add watch path classification (src/indexer/mod.rs:classify_paths):
if path_str.contains(".my-agent") {
kinds.insert(ConnectorKind::MyAgent);
}When converting agent-specific formats to NormalizedConversation:
- Roles: Map to
"user","assistant","system","tool", or"agent" - Timestamps: Convert to milliseconds since Unix epoch (i64)
- External ID: Use a stable identifier unique within the agent (file path, UUID, etc.)
- Content Flattening: Extract searchable text from nested structures:
// Tool calls become searchable text "[Tool: ReadFile] path=/src/main.rs, lines=1-50"
- Workspace Detection: Extract working directory from session metadata when available
- Semantic Search: Embed conversations using local models (Ollama integration) for meaning-based retrieval
- Session Grouping: Automatically cluster related conversations by project/topic
- Export Improvements: Better markdown/HTML export with syntax highlighting
- Windows Native: First-class Windows support with native installer
- MCP Server Mode: Run as a Model Context Protocol server for direct agent integration
- Conversation Analytics: Token usage tracking, agent comparison dashboards
- Collaborative Features: Optional encrypted sync between machines
- Plugin System: User-defined connectors without recompiling
- Agent Memory Layer: Let AI agents query their own history as working memory
- Team Knowledge Base: Shared, searchable repository of team coding sessions
- Learning from History: Surface patterns in how problems were solved across sessions
These are explicitly out of scope:
- Cloud hosting:
casswill always be local-first - Real-time collaboration: That's a different tool category
- Agent execution:
casssearches history, it doesn't run agents - Universal file search: Focus remains on AI coding agent sessions
MIT. See LICENSE for details.