Guidelines for AI coding agents working in this Rust codebase.
If I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.
The suite-wide rules in /data/projects/AGENTS.md bind you here too. Read it. Two sections
are load-bearing for perf work and are NOT duplicated below, so they cannot drift out of sync:
## Named Reward-Hacking Patterns (ALL FORBIDDEN)— 12 named patterns, several already observed in this suite: gate self-weakening (and the exact price of a legitimate gate fix), proof-class inflation, golden regeneration reflex, commit-stream pumping, tautological tests, easy-lever cherry-picking, close-pump abuse, scope-splitting, spec-editing as progress, conformance metastasis, dependency smuggling, bench-path hardcoding.### Work-Graph Discipline— JSONL is truth andbeads.dbis disposable,br sync --import-onlyafter every pull, single-writer on graph structure, closure on cited evidence with blocker beads gated on their named probe,br dep cyclesstays empty.
The three that most often decide whether a number here is real: a self-speedup is MAINTENANCE, not a win — a win needs the incumbent live in the SAME invocation; never weaken a gate to land a change, and if a gate is genuinely defective, meet the evidence standard and publish the win/lose split of what the fix admits; and reporting a loss is a success — one line, revert, next lever, no retraction narrative.
YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION. Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.
YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.
- Absolutely forbidden commands:
git reset --hard,git clean -fd,rm -rf, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences. - No guessing: If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. "I think it's safe" is never acceptable.
- Safer alternatives first: When cleanup or rollbacks are needed, request permission to use non-destructive options (
git status,git diff,git stash, copying to backups) before ever considering a destructive command. - Mandatory explicit plan: Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.
- Document the confirmation: When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.
The default branch is main. The master branch exists only for legacy URL compatibility.
- All work happens on
main— commits, PRs, feature branches all merge tomain - Never reference
masterin code or docs — if you seemasteranywhere, it's a bug that needs fixing - The
masterbranch must stay synchronized withmain— after pushing tomain, also push tomaster:git push origin main:master
If you see master referenced anywhere:
- Update it to
main - Ensure
masteris synchronized:git push origin main:master
Several agents edit this working tree at once. On 2026-09-01 two agents committed
another session's uncommitted files under their own messages, rewrote three shared
files from older copies (dropping helper functions whose callers had just been
committed), and left main unable to compile for most of an hour. These rules exist
because of that day:
- Never
git add -A,git add ., orgit commit -a. Stage the exact paths you edited (git add src/foo.rs tests/bar.rs). Ifgit statusshows files you did not touch, they belong to another agent — leave them alone (AGENTS.md rule: never stash, revert, or overwrite concurrent work). - Reserve the shared monoliths before editing them —
src/lib.rs,src/indexer/mod.rs,src/storage/sqlite.rs,src/search/quill_bridge.rs,src/ui/app.rs— with an Agent Mail file reservation (file_reservation_paths(project_key, agent, ["src/lib.rs"], ttl_seconds=3600, exclusive=true)), and install the Agent Mail pre-commit guard (install_precommit_guard) so a commit that includes a path reserved by another agent is refused instead of merged blind. - Never restore a file from an older copy to "fix" a compile error. Fix the error in place; a symbol your rewrite removes may already have committed callers.
- Commit small and early. A long-lived uncommitted tree is the surface that gets
swept. Verify with the batched gate (
scripts/gate.sh) before pushing; a commit titled "restore a compiling main" that does not compile is worse than no commit. - After any commit by another agent touches a file you changed,
git grepevery symbol you landed there. Presence is not enough: check that the function still does what yours did (an in-process memo is not an on-disk cache). - A GitHub issue gets a bead within 24 hours (
gh<issue>-<topic>in the title, the reporter's numbers in the description) and the first triage comment on the issue cites that bead. On 2026-09-01 the five worst open issues (#439, #440, #441, #395, #391) had no bead while 2,000 beads were closed around them; the tracker must track what users report, not only what agents choose.
THIS IS A HARD, NON-NEGOTIABLE RULE. IT HAS BEEN VIOLATED OVER 10 TIMES AND THE OWNER IS DONE TOLERATING IT.
- ALL new SQLite code MUST use
frankensqlite(fsqlite). This applies to CASS, franken_agent_detection, and ANY project in this ecosystem. - NEVER add
rusqliteas a dependency. NEVER writeuse rusqlite::. NEVER userusqlite::Connection. Not even "temporarily." Not even "to match the existing pattern." - The existing rusqlite usage is LEGACY DEBT, not a pattern to follow. Do NOT copy it. Do NOT extend it.
- If frankensqlite is missing a feature you need, FIX FRANKENSQLITE. It lives at
/data/projects/frankensqlite. Add the missing API there. That is the entire point of the project. - The frankensqlite API uses
frankensqlite::Connection,fsqlite::compat::{ConnectionExt, RowExt, OpenFlags, open_with_flags, ParamValue}, androw.get_typed::<T>(idx)instead of rusqlite'srow.get(idx). - For franken_agent_detection connectors: use
frankensqlitewith the compat layer. Import pattern:use frankensqlite::Connection as FrankenConnection; use frankensqlite::compat::{ConnectionExt, RowExt, OpenFlags, open_with_flags, param_slice_to_values};
If you are an AI agent reading this and you write rusqlite in new code, you are wasting the owner's time and money. This rule exists because agents have repeatedly ignored it. DO NOT BE THE NEXT ONE.
We only use Cargo in this project, NEVER any other package manager.
- Edition: Rust 2024 (dated nightly pinned by
rust-toolchain.toml) - Dependency versions: Caret minimums resolved through the committed
Cargo.lock. The SQLite family, Asupersync, Frankensearch, FAD, FrankenTUI and toon use exact=pins (see the dependency source contract below).sysinfo,libcand the dev-onlytokenizersuse* - Configuration: Cargo.toml only (single-crate project, no workspace)
- Unsafe code: Forbidden as a general tool. Tightly scoped, narrowly audited
unsafeis allowed only where it is unavoidable (e.g., the few Rust 2024std::env::set_var/remove_varcalls at controlled startup/teardown, or unavoidable FFI with no safe wrapper). Unsafe cross-thread connection wrappers and unjustifiedSend/Syncimpls remain prohibited and must be UBS-gated.
This project uses asupersync as its async runtime. It provides RuntimeBuilder, spawn_blocking, fs ops, net, signal, and structured concurrency via Cx.
We load all configuration from .env via the dotenvy crate. NEVER use std::env::var() directly.
use dotenvy::dotenv;
use std::env;
// Load .env file at startup (typically in main())
dotenv().ok();
// Configuration with fallback
let api_base_url = env::var("API_BASE_URL")
.unwrap_or_else(|_| "http://localhost:8007".to_string());The .env file exists and MUST NEVER be overwritten.
| Crate | Purpose |
|---|---|
asupersync |
Async runtime (multi-thread, fs, spawn_blocking, signals) |
clap |
CLI argument parsing with derive macros |
serde + serde_json |
Serialization |
frankensqlite (fsqlite) |
Pure-Rust SQLite reimplementation — primary storage backend |
rusqlite |
Dev-dependency only: C-SQLite interop fixtures in tests. Production never links it (historical salvage uses the external sqlite3 CLI) |
frankensearch |
Unified search engine: lexical BM25 + semantic + RRF fusion |
franken_agent_detection |
Agent session auto-detection across 15+ providers |
frankentorch (via frankensearch) |
Pure-Rust native MiniLM embeddings and reranking |
frankenhnsw (via frankensearch) |
HNSW approximate nearest neighbors |
half + wide + memmap2 |
f16 quantized vectors, portable SIMD, memory-mapped I/O |
ftui + ftui-extras |
FrankenTUI terminal interface |
toon |
Terminal rendering library |
reqwest |
HTTP client (rustls-tls, blocking + async) |
rayon |
Data parallelism for CPU-bound work |
colored + indicatif + console |
Colorful, informative console output |
notify |
Filesystem watching |
walkdir + glob |
Directory traversal and pattern matching |
blake3 + sha2 |
Cryptographic hashing |
aes-gcm + ring + pbkdf2 + argon2 |
Encryption (ChatGPT conversations, HTML export) |
ssh2 |
SFTP fallback for multi-machine sync |
dialoguer |
Interactive terminal prompts (setup wizard) |
ftui-extras (syntax feature) |
Syntax highlighting |
thiserror |
Ergonomic error type derivation |
tracing |
Structured logging and diagnostics |
unicode-normalization |
NFC text canonicalization |
Optional local sibling checkouts (under /data/projects/, via commented local patches only):
frankensqlite— Pure-Rust SQLite with BEGIN CONCURRENT (MVCC multi-writer)frankensearch— Unified search: BM25 lexical + semantic embeddings + RRF fusion + rerankingfranken_agent_detection— Auto-discovers agent sessions from 15+ providersfrankentui(ftui+ftui-extras+ftui-runtime+ftui-tty) — Terminal UI frameworkasupersync— Async runtime (multi-thread, fs, spawn_blocking, signals)toon— Token-optimized serialization
Dependency source contract:
Dependency update (2026-10-04): Cargo.toml and Cargo.lock pin the entire
SQLite family, including fsqlite-types, at =0.4.9, with Asupersync =0.5.0.
All 26 public SQLite packages are published at 0.4.9 (replacing the withdrawn
0.4.8); the v0.4.9 tag points to 1eacdbe0d4bd1d864b106c096c904d2a3933ab46.
FAD =0.3.8 accepts the family.
The lockfile resolves the 20 SQLite packages used by CASS at 0.4.9.
Full runtime qualification and the strict UBS gate remain pending. Do not
publish a release candidate until those gates pass.
SQLite 0.4.2 adds explicit derived WAL-index recovery for read-only opens
(GH#477). The published SQLite 0.4.1 includes the GH#462 reserved-page WAL
repair, but damaged-archive recovery remains unproven. SQLite 0.4.4 adds
durable pending-freelist repairs; 0.4.6 stops a live B-tree page from being
freed and granted again across a WAL generation (bd-b5vmw) and fixes the
lost-index-entry race (bd-11sz4). 0.4.7 keeps index entries whole in large
multi-row INSERTs (register writes past 65,535 were dropped since February:
bd-2eebi), fixes the serialized-DDL index race (bd-4iaoi), lets the
deferred-FTS5 repair open bind the GH#503 legacy shadow autoindex (cbd0b98dc;
doctor --rebuild-canonical-fts now repairs that catalog in place), bounds
the published-page plane (ad3f23ca9, bead 5s1la) and stops a connection's
first sqlite_master query from reading the whole file (ec4956420, k2k20).
CASS skipped 0.4.7 because its strict read-only opens spent ~30 s on a stale
WAL index (0853f5e8e). 0.4.9 refuses that index promptly again (48c6cf6b2) and
lets the first write after stock SQLite opened the archive read-only
establish the WAL generation instead of failing BusyRecovery on every later
commit (frankensqlite GH#443, 4b48c95d8; cass GH#509/#508).
The strict family guard requires uniform 0.4.9 registry versions.
| Dependency | Pinned source |
|---|---|
frankensqlite (fsqlite) / fsqlite-types and the whole SQLite family |
crates.io =0.4.9 (tag v0.4.9 = 1eacdbe0d4bd1d864b106c096c904d2a3933ab46); build.rs refuses a mixed family. Carries 0.4.1's GH#462 reserved-page WAL repair, 0.4.2's derived WAL-index recovery for read-only opens (GH#477), 0.4.4's durable pending-freelist repairs, 0.4.6's page-referenced-twice (bd-b5vmw) and lost-index-entry (bd-11sz4) fixes, 0.4.7's large multi-row INSERT index fix (bd-2eebi), serialized-DDL race fix (bd-4iaoi), GH#503 shadow-autoindex repair open, bounded published-page plane (5s1la) and first-sqlite_master full-file read fix (k2k20), and 0.4.9's prompt stale-WAL-index refusal (48c6cf6b2) and stock-empty-WAL-index first-write fix (4b48c95d8, cass GH#509). CASS runtime qualification of 0.4.9 is pending. 0.4.6 included upstream's likely fix (fd8c16a94) for a long-lived connection left refusing every BEGIN (2l1b0.75, frankensqlite GH#429), not yet verified on the owner-archive scenario; the legacy OMP analytics phase keeps its own fresh writer regardless |
franken-agent-detection |
crates.io =0.3.8, features pi-durable and pi-sqlite (the pi_durable connector for Pi's durable-harness stores, FAD#28; pi_agent_rust SQLite sessions; Pi compaction/branch provenance, FAD#27) and codex-zstd (reads Codex rollouts compressed to rollout-*.jsonl.zst by Codex's local_thread_store_compression, with the id of their plain form, GH#513; indexes native Codebuff transcripts, whose timestamps are a locale time of day, with each instant taken from the message ID, and keeps indexing a store's other chats when one transcript does not parse, GH#511; indexes OpenCode 2.x sessions from session_v2/session_message, GH#504; decodes OpenClaw zstd-compressed transcript events; indexes Claude Code prompts typed mid-turn, GH#500; honors CASS_EXCLUDE_PATHS in the Codex and Pi-family connectors, GH#486, and resolves .., relative and symlink-alias spellings of an entry, 0f1k0; caps session reads while reading, and lets cass set the Codex rollout budget so CASS_CODEX_MAX_SOURCE_BYTES above 100 MiB works) |
asupersync |
crates.io =0.5.0 (the line fsqlite 0.4.x requires) |
frankensearch |
crates.io =0.7.1 (tag crates-v0.7.1), resolving frankensearch-quill 0.4.0, frankensearch-rerank 0.5.0, frankenhnsw 0.3.5 and the frankentorch-* family; features hash, cass-compat, quill, ann, native. Quill 0.4.0 is the first main-branch release after the 0.3.4 hotfix and carries all three of its fixes: the GH#499 live-doc numeric scoring, the standard CASS Boolean grammar (NOT > AND > OR, parentheses; frankensearch#56) and the union seek_danger fix (a disjunction under a conjunction lost matches on concat-merged segments). It also verifies snapshot segments concurrently on open (GH#501; up to 8 threads, the same bytes are still hashed), builds snippets only for returned hits, and skips a read-only reopen when nothing was published. The CASS schema hash is unchanged (v9-quill), so the upgrade does not rebuild the lexical index |
frankentui (ftui, ftui-runtime, ftui-tty, ftui-extras) |
crates.io =0.5.0 (2026-08-21; previously git 5f78cfa0 / 0.3.1 — the 0.5 API compiled with zero call-site changes) |
toon (tru) |
crates.io =0.2.4 (2026-08-24; production sources byte-identical to the previously pinned git rev d7185c78 — registry 0.2.3 was rejected because its tree differs from the rev in real source despite the matching version field) |
The release build optimizes for speed with LTO + single-codegen-unit + stripped binary:
[profile.release]
lto = true # Link-time optimization
codegen-units = 1 # Single codegen unit for better optimization
strip = true # Remove debug symbols
panic = "abort" # Abort on panic (smaller binary)
opt-level = 3 # Maximum optimization for speedA profiling profile is also available:
[profile.profiling]
inherits = "release"
debug = true # Keep debug symbols for flamegraphs
strip = falseA bench profile is used for .bench-history/ pass-over-pass ratchet runs (per
the gauntlet keep-gate rules; never run benches against --release directly):
[profile.release-perf]
inherits = "release"
debug = "line-tables-only" # frame-pointer attribution without rebuild
strip = false
# Pair with: RUSTFLAGS="-C force-frame-pointers=yes" cargo bench --profile release-perfNEVER run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.
- Always make code changes manually, even when there are many instances
- For many simple changes: use parallel subagents
- For subtle/complex changes: do them methodically yourself
If you want to change something or add a feature, revise existing code files in place.
NEVER create variations like:
document_processorV2.rsdocument_processor_improved.rsdocument_processor_enhanced.rs
New files are reserved for genuinely new functionality that makes zero sense to include in any existing file. The bar for creating new files is incredibly high.
We do not care about backwards compatibility—we're in early development with no users. We want to do things the RIGHT way with NO TECH DEBT.
- Never create "compatibility shims"
- Never create wrapper functions for deprecated APIs
- Just fix the code directly
All console output should be informative, detailed, stylish, and colorful by leveraging:
colored— ANSI color formattingindicatif— Progress bars and spinnersconsole— Terminal utilities
After any substantive code changes, you MUST verify no errors were introduced:
# Check for compiler errors and warnings
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-check-target cargo check --all-targets
# Check for clippy lints
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-check-target cargo clippy --all-targets -- -D warnings
# Verify formatting
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-check-target cargo fmt --checkIf you see errors, carefully understand and resolve each issue. Read sufficient context to fix them the RIGHT way.
Per coding_agent_session_search-dpfvr, every PR is meant to run ubs --ci --fail-on-warning against the changed files in CI (.github/workflows/ci.yml::ubs-changed-files). The gate is blocking — warnings stop merges. GitHub Actions is permanently off (owner, 2026-10-09: "NO GH ACTIONS, PERIOD!!! EVER!!! we ONLY ever use /dsr"). Nothing under .github/workflows/ runs: do not add or edit a workflow as a verification lane, and never make a bead's closure wait on a workflow run. Windows and macOS proof comes from the dsr build hosts (dsr health all), releases from dsr build / dsr release / dsr fallback. This gate — like fmt/clippy/tests — is agent-run through rch before pushing. Run it as ONE fleet admission with scripts/gate.sh (fmt, clippy -D warnings, lib tests, targeted integration tests, goldens; --lib-filter, --integration name:filter,..., --regen-goldens; GATE_RETRIES=40 retries fleet refusals) and cite its STAGE=<name> EXIT=<code> receipt lines in the bead closure and the commit message — never spend an admission on a bare cargo check.
Local pre-flight before pushing:
ubs $(git diff --name-only origin/main...HEAD)Or, scoped to staged files:
ubs $(git diff --name-only --cached)If a known-acceptable warning needs to ship despite the gate, suppress at the UBS config level (tests/policies/no_mock_allowlist.json or per-file inline pragma) — never bypass by removing the gate.
The pinned UBS version lives in .github/workflows/ubs-version.txt. Local installs should match.
The project is migrating from rusqlite to frankensqlite. Both are available:
frankensqlite(import asfsqlite) — Pure-Rust SQLite with BEGIN CONCURRENT supportrusqlite— C-binding SQLite, retained as fallback during migration
use frankensqlite::Connection;
// Open with WAL mode (REQUIRED for concurrent access)
let conn = Connection::open(path)?;
conn.execute("PRAGMA journal_mode = WAL;")?;
conn.execute("PRAGMA busy_timeout = 5000;")?;
// Use params! macro (needs explicit import)
use fsqlite::params;
conn.execute_with_params("INSERT INTO t (a) VALUES (?1)", params![42])?;frankensqlite::Connection::open() can open and read standard SQLite database files created by SQLite/rusqlite. That includes external app databases such as Cursor state.vscdb, OpenCode opencode.db, and historical cass databases.
- Do not add
rusqlitejust to read an existing SQLite file. - If a specific query shape fails against one of these files, treat it as a targeted engine/query bug and file a reproducer instead of assuming the file format is unsupported.
Use FrankenConnectionManager for concurrent access:
- Reader pool (multiple concurrent readers)
- Writer token (single writer at a time via
WriterGuard) WriterGuardauto-rollbacks on drop (RAII safety)
- Always use WAL mode — without it, concurrent writes corrupt the DB
- Use jittered exponential backoff on
BusySnapshot/WriteConflicterrors - Batch writes — 10-20 rows per transaction (not 1 row per commit)
- Limit concurrent writers to 4 threads (matches production rayon parallelism)
- Retryable errors:
Busy,BusyRecovery,BusySnapshot,WriteConflict,SerializationFailure,DatabaseCorrupt
- File format interop: As of rev
9cedb30b, frankensqlite databases are readable by C SQLite (rusqlite) and vice versa. Historical bundle salvage still uses rusqlite as a proven read bridge for pre-migration databases. PRAGMA writable_schema: Not supported for write operations (INSERT/UPDATE on sqlite_master). SELECT from sqlite_master works.
Do:
- Create connection pools and reuse across the application
- Use
?placeholders for parameters (prevents SQL injection) - Keep one database transaction per logical operation
- Handle migrations properly
- Use strong typing for database columns
Don't:
- Share a single transaction across concurrent tasks
- Use string concatenation to build SQL queries
- Ignore
Option<T>for nullable columns - Mix sync and async database operations
- Use
unwrap()on database results in production code
IMPORTANT: Do not run the E2E browser tests (Playwright) on the shared development machine.
Running browser tests there:
- Consumes significant system resources (spawns browser instances)
- Can freeze or slow down the development machine
- May have different results than a clean host due to environment differences
GitHub Actions is permanently off, so .github/workflows/browser-tests.yml never runs: pushing a branch does not run them. Run browser specs on a dsr build host, one spec file at a time while debugging (test.only()), and name the host in the receipt.
Tests must cover:
- Happy path
- Edge cases (empty input, max values, boundary conditions)
- Error conditions
Integration and E2E tests live in the tests/ directory. Benchmarks live in benches/.
Test stack: use
RUST_MIN_STACK=134217728, matching.cargo/config.tomlandscripts/gate.sh. fsqlite 0.3.x's deep debug-mode futures have exceeded even a 16 MiB test-thread stack in storage tests. The 128 MiB setting reserves virtual address space; pages are committed as needed. Do not override it with the older 16 MiB value when running the full suite.
# Run all tests
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-test-target RUST_MIN_STACK=134217728 cargo test
# Run with output
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-test-target cargo test -- --nocapture
# Run a specific test
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-test-target cargo test test_name
# Run tests with all features enabled
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-test-target cargo test --all-features| Directory / File | Focus Areas |
|---|---|
tests/connector_*.rs |
Per-provider session parsing (Claude, Codex, Cursor, Gemini, Aider, Amp, Cline, OpenCode, Pi Agent, Oh My Pi, Copilot, OpenClaw, ClawdBot, Vibe) |
tests/search_*.rs |
Search pipeline, caching, filters, wildcard fallback |
tests/semantic_integration.rs |
Semantic search, embeddings, two-tier search |
tests/e2e_*.rs |
End-to-end CLI flows, filters, search, sources, TUI, deploy |
tests/cli_*.rs |
CLI dispatch coverage, robot mode, index, stats |
tests/tui_*.rs |
TUI headless smoke tests, snapshot tests |
tests/tui_integration_smoke.rs |
TUI + full integrated stack (frankensqlite + frankensearch + FAD) |
tests/frankensqlite_*.rs |
frankensqlite compat gates, concurrent stress tests |
tests/agent_detection_completeness.rs |
franken_agent_detection connector completeness |
tests/html_export*.rs |
HTML export pipeline, encryption |
tests/storage*.rs |
SQLite storage, migration safety |
tests/performance/ |
Performance regression tests |
benches/ |
Criterion benchmarks (index, runtime, search, crypto, db, export, cache, regex, integration_regression) |
Fixtures are in tests/fixtures/ and cover multiple agent session formats for cross-connector validation.
If you aren't 100% sure how to use a third-party library, SEARCH ONLINE to find the latest documentation and current best practices.
This is the project you're working on. cass indexes conversations from Claude Code, Codex, Cursor, Gemini, Aider, Amp, Cline, OpenCode, Pi Agent, Oh My Pi, Copilot, OpenClaw, ClawdBot, Vibe, and more into a unified, searchable index with a TUI and robot-mode CLI.
NEVER run bare cass — it launches an interactive TUI. Always use --robot or --json.
Provides unified full-text and semantic search across all local coding agent session histories, with a rich TUI, robot-mode JSON API, multi-machine sync, HTML export with optional encryption, and analytics.
- SQLite is the source of truth. Search indexes are derived assets; do not treat a broken lexical or semantic artifact as lost user data.
- Lexical search is required and self-healing. Missing, stale, schema-drifted, or corrupt lexical assets should rebuild from SQLite through scratch-build and atomic-publish semantics.
- Hybrid is the default search intent. Semantic refinement joins when ready; default hybrid search must fail open to lexical with truthful robot metadata when semantic assets are unavailable.
- Semantic enrichment is opportunistic. Lexical-only behavior is expected during first indexing, semantic backfill, disabled semantic policy, missing model files, or vector catch-up.
- Semantic model acquisition is opt-in.
cass models installdownloads the MiniLM model (~90 MB) on explicit operator request. cass never auto-downloads. Air-gapped installs use--from-file <dir>. While the model is absent,fallback_mode="lexical"is reported in health/status and queries silently degrade to lexical-only. - Truth surfaces:
cass health --json,cass status --json, and search--robot-metaexpose readiness, active rebuilds, realized search mode, fallback tier, and recommended action. Follow those fields instead of hard-coded manual repair rituals. - No silent substitution. An input that cannot take effect is a typed error (or, only where the contract says so, a typed warning in
_meta.warnings), never dropped: a bad--since, a flag typo on an exact subcommand, and an ignored env override have each run a different query with exit 0. Search--robot-metaechoes what actually ran under_meta.effective(database path and its source, the resolved time window with the flag behind each bound, parsed filters, argv auto-corrections, the engine's query grouping and recovered parentheses), and_meta.wildcard_fallback_skippedsays why a sparse result got no automatic wildcard retry; when you add an input that changes what search does, echo it there and extendtests/search_metamorphic.rs(2l1b0.68). - Lexical query fuel is bounded. cass opens Quill with the engine's deterministic per-query work ceiling (10,000,000 units;
CASS_QUILL_QUERY_FUEL_BUDGETis an escape hatch, not a tuning knob). If a hybrid search exhausts it, the lexical leg is dropped rather than failing the search and_meta.lexical_degrade_reasonreportsquery_fuel_exhausted; lexical-only searches get an actionable hint. cass publishes Quill snapshots only on its own commits (max_visibility_lag_msis disabled insrc/search/quill_bridge.rs::cass_quill_config), which stops the per-second seals that grew append-only archives into hundreds of tiny segments (GH #440/#441);cass index --fullconsolidates an archive that already fragmented.
-
Hollow generations: readiness compares the live Quill MANIFEST document count with a completed checkpoint for the current archive/contract. Fewer than 50% reports
index.status="hollow",index.hollow=true, andindex.live_documents; missing counts give no hollow verdict. Follow the reportedcass indexremedy: its pre-scan sparse-index repair rebuilds from SQLite. The indexer's final check refuses to certify a hollow generation (GH #457). -
Merge memory:
CASS_LEXICAL_MERGE_MAX_OUTPUT_BYTESdefaults to 1 GiB and caps each planned merge-output estimate, including document-ID range overhead. An oversized singleton is left unmerged; the setting does not cap total process RSS. Zero/unparseable values keep the default (GH #456). Every planned run also holds at most 4,194,304 physical documents (MAX_FOLD_OUTPUT_DOCS, Quill's per-term posting limit), andcass_quill_configsetstier_fanout = usize::MAXso the engine's unbounded in-commit tier merge never runs; CASS's capped planners are the only merge authority (GH #498). -
Stale-on-read catch-up is on by default. When
search/pack/TUI launch sees a stale (>30 min), partial, or behind index, cass spawns a detachedcass index --background(nice 15 / ionice idle, own process group, 5-min cooldown, honorsindex-run.lock) and returns the current results immediately.--robot-metashows it under_meta.index_freshness.auto_refresh(outcome:spawned|disabled|index_run_active|cooldown|guard_busy|spawn_failed, plustrigger). It never fires for data dirs under the OS temp dir or underTUI_HEADLESS, so tests are unaffected.CASS_AUTO_REFRESH=0disables. Implementation:src/indexer/background_refresh.rs, hookmaybe_auto_refresh_index_after_readinsrc/lib.rs. -
A missing or unusable index is rebuilt in the background, never by the search that found it. When search needs a lexical repair it will not run inline (archive over the inline repair budget, or a robot caller facing
checkpoint_incomplete), it spawns the same detached child with--full(or plaincass indexfor a small archive's checkpoint) and puts the outcome in the error hint: pid, "already rebuilding", or why no spawn happened. A rebuild run inside the search process, or an agent's owncass index --full, dies with the command timeout that wraps it and a large archive commits nothing before its first batch, so every retry used to start from zero and the index never converged. While a rebuild runs and no searchable generation exists, robot searches return exit 7index-busywithN of M conversations processedat once instead of waitingCASS_SEARCH_ACTIVE_REBUILD_WAIT_MS. Implementation:start_background_lexical_repair_for_searchinsrc/lib.rs. -
TUI first run: launching the TUI with no index starts the same detached
cass index --full --backgroundchild (same guards: never for scratch/temp data dirs or underTUI_HEADLESS, honorsCASS_AUTO_REFRESH, cooldown and breaker), shows its progress in the status line, and opens search once the first generation publishes, without a restart. Implementation:CassApp::start_first_run_index/poll_first_run_indexinsrc/ui/app.rs. -
cass schedule installregisters launchd LaunchAgents (macOS) / systemd user timers (Linux): incremental every 15 min, nightly full index + boundedmodels backfill --scheduled(fast/hash tier always, quality/MiniLM tier when installed; model-unavailable and index-busy exits count as skips, not failures) + duesync_scheduleremote syncs, all at OS background priority.cass schedule status --json/schedule run --job incremental|nightly [--force]/schedule uninstall. Unsupported platforms geterr.kind="schedule"with a manual recipe. Implementation:src/schedule.rs. -
Daemon timer:
CASS_DAEMON_INDEX_INTERVAL_SECS=900makes the resident semantic daemon spawn the same detached incremental index while it lives (src/daemon/core.rs::spawn_periodic_index). -
Idle gates: scheduled jobs skip under severe load (Linux loadavg/PSI, macOS
sysctl vm.loadavg—responsiveness::machine_pressure_now);CASS_RESPONSIVENESS_MIN_USER_IDLE_SECSadds a macOS console-idle requirement for nightly/backfill work (responsiveness::user_idle_gate; fails open elsewhere). Foregroundcass indexis never gated. -
Every step is a child
cassprocess, so exit 7index-busyremains the only concurrency contract; do not add in-process schedulers that bypass the lock. -
Background runs never start the one-time storage migration repair on a large archive (GH #450). When the archive bundle exceeds
CASS_INDEX_INTEGRITY_PREFLIGHT_MAX_BYTES(default 2 GiB) and its.fsqlite-migration-statemarker is absent or incomplete,cass index --background(stale-on-read refresh, scheduled jobs) exits 7 with kindmigration-repair-pendingand leaves the archive untouched; the scheduler records it as a skip naming the cause. A foregroundcass index --fullperforms the repair once (it keeps a.pre-migration-bakcopy, so plan for that much free space).
- Every lexical publish is a single atomic swap: on Linux
renameat2(RENAME_EXCHANGE)exchanges the staged and live index trees in one syscall; non-Linux platforms use a parked-rename + restore-on-failure dance. Readers never see a half-torn index — either the old or the new generation is visible, never a mix. - The prior-live generation is retained under
<data_dir>/index/.lexical-publish-backups/<dated>/for a bounded retention window. Default cap:1(one-step rollback). Override viaCASS_LEXICAL_PUBLISH_BACKUP_RETENTIONenv var:0disables retention,Nkeeps the N most-recent backups. Pruning runs after every successful publish and emitstracing::info!withfreed_bytes+retention_limit. - Crash recovery is automatic. If cass crashes between the atomic swap and the retain-rename,
recover_or_finalize_interrupted_lexical_publish_backup, run at the start of the next lexical publish or rebuild, finds the canonical sidecar (.<name>.publish-in-progress.bak) and completes the retain step before the next publish. See src/indexer/mod.rs::publish_staged_lexical_index. - Do not handwrite "rebuild lexical" recipes. Call
cass index --fullor trust stale-refresh; the publish + atomic-swap + retention pipeline is the only blessed path. Anything that removes<data_dir>/index/directly outside publish is off-contract.
- Corrupt or failed-validation assets are quarantined, not deleted. Failed seed bundles, quarantined lexical generations, and superseded retained publish backups persist on disk until explicitly reclaimed.
cass diag --json --quarantineenumerates every quarantined artifact with{path, size_bytes, age_seconds, last_read_at_ms, safe_to_gc, gc_reason}. Thesafe_to_gcflag is advisory only — it reports retention-policy + cleanup-dry-run eligibility; no automatic deletion path consumes it.cass doctor --jsonsurfaces the same quarantine summary pluschecks[]for every diagnostic. Without--fixdoctor is read-only:auto_fix_applied=false,auto_fix_actions=[],issues_fixed=0. With--fixit applies only repairs whose dry-run plans are proven safe — currently Track A analytics rebuild and Track B rollup rebuild viarebuild_token_daily_stats(when thetoken_usageledger is intact; see bead m7xrw).- Agents should not race doctor. Running
cass doctor --fixagainst an in-flight rebuild is safe (cass uses an advisory file lock), but concurrent--fixfrom two processes is undefined. Prefercass health --jsonfor pre-flight checks andcass doctor --json(no--fix) for inspection.
- Every JSON contract surface is pinned by golden-file regression tests under
tests/golden/robot/(JSON) andtests/golden/robot_docs/(plain-text docs topics). The full set: capabilities, health, status, diag, diag_quarantine, models_status, models_verify, models_check_update, introspect, doctor, doctor_quarantine, api_version, stats (missing-db error envelope), robot_docs topics (paths, env, exit-codes, schemas, guide, robot_help). - If you add a new field or change a type, run
UPDATE_GOLDENS=1 rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-golden-target cargo test --test golden_robot_json --test golden_robot_docs, review the diff viagit diff tests/golden/, and commit both the code + golden in one change. Do not regenerate goldens without reviewing — every diff is either an intentional schema change or a bug. cass introspect --json'sresponse_schemasisBTreeMap-backed so the serialized key order is alphabetical and deterministic (bead 8sl73).- Error envelopes use kebab-case
err.kindvalues. For codes 0-9 the numeric code is sufficient; for codes ≥ 10 the code is ambiguous (e.g. 10 covers bothconfigandtimeout) — always branch onerr.kind. Full taxonomy in src/lib.rsCliErrorliterals + the Exit Codes table below (bead wan21).
coding_agent_session_search/
├── Cargo.toml # Single-crate project
├── src/
│ ├── main.rs # Entry point (binary: cass)
│ ├── lib.rs # Library root
│ ├── connectors/ # Per-agent session parsers
│ │ ├── mod.rs # Connector trait + registry
│ │ ├── claude_code.rs # Claude Code sessions
│ │ ├── codex.rs # Codex sessions
│ │ ├── cursor.rs # Cursor sessions
│ │ ├── gemini.rs # Gemini sessions
│ │ ├── grok.rs # Grok Build sessions
│ │ ├── aider.rs # Aider sessions
│ │ ├── amp.rs # Amp sessions
│ │ ├── chatgpt.rs # ChatGPT sessions (encrypted)
│ │ ├── cline.rs # Cline sessions
│ │ ├── opencode.rs # OpenCode sessions
│ │ ├── pi_agent.rs # Pi Agent sessions
│ │ ├── omp.rs # Oh My Pi sessions
│ │ ├── copilot.rs # Copilot sessions
│ │ ├── copilot_cli.rs # Copilot CLI sessions
│ │ ├── openclaw.rs # OpenClaw sessions
│ │ ├── clawdbot.rs # ClawdBot sessions
│ │ ├── vibe.rs # Vibe sessions
│ │ ├── crush.rs # Crush sessions
│ │ ├── hermes.rs # Hermes sessions
│ │ ├── kimi.rs # Kimi Code sessions
│ │ ├── qwen.rs # Qwen Code sessions
│ │ ├── openhands.rs # OpenHands sessions
│ │ ├── antigravity.rs # Antigravity sessions
│ │ └── factory.rs # Factory (Droid) sessions
│ ├── search/ # Search engine (delegates to frankensearch)
│ │ ├── query.rs # Query parsing and execution
│ │ ├── tantivy.rs # BM25 full-text search (via frankensearch)
│ │ ├── vector_index.rs # Vector similarity search
│ │ ├── two_tier_search.rs # Progressive 2-tier hybrid search
│ │ ├── ann_index.rs # HNSW approximate nearest neighbors
│ │ ├── hash_embedder.rs # FNV-1a hash embedder (fast, zero-dep)
│ │ ├── fastembed_embedder.rs # Native MiniLM quality embedder (legacy filename)
│ │ ├── embedder.rs # Embedder trait
│ │ ├── embedder_registry.rs # Embedder auto-detection
│ │ ├── reranker.rs # Cross-encoder reranking
│ │ ├── reranker_registry.rs # Reranker management
│ │ ├── model_download.rs # Model download management
│ │ ├── model_manager.rs # Model lifecycle management
│ │ ├── canonicalize.rs # Query canonicalization
│ │ └── daemon_client.rs # Search daemon RPC client
│ ├── indexer/ # Session indexing pipeline
│ ├── storage/ # SQLite persistence (frankensqlite + rusqlite)
│ ├── ui/ # TUI components
│ ├── pages/ # Web pages generation
│ ├── pages_assets/ # Static assets for pages
│ ├── html_export/ # Self-contained HTML export
│ ├── analytics/ # Usage analytics
│ ├── daemon/ # Background search daemon
│ ├── sources/ # Multi-machine source management
│ ├── model/ # Data models
│ ├── bookmarks.rs # Session bookmarking
│ ├── bakeoff.rs # Embedder comparison tool
│ ├── encryption.rs # AES-GCM encryption
│ ├── export.rs # Export pipeline
│ ├── update_check.rs # Auto-update checking
│ └── tui_asciicast.rs # Terminal recording
├── tests/ # Integration + E2E tests
├── benches/ # Criterion benchmarks
├── scripts/ # Helper scripts
├── web/ # Web assets
├── docs/ # Documentation
└── fuzz/ # Fuzz testing
# Check readiness and recommended action (fresh installs may need index --full)
cass health
# Search across all agent histories (default: hybrid-preferred, lexical fail-open)
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
# Export session as self-contained HTML
cass export-html /path/to/session.jsonl --json
printf '%s\n' "secret" | cass export-html session.jsonl --encrypt --password-stdin --json
# Learn the full API
cass capabilities --json # Feature discovery
cass robot-docs guide # LLM-optimized docs| Provider | Connector | Session Format |
|---|---|---|
| Claude Code | claude_code.rs |
JSONL |
| Codex | codex.rs |
JSONL |
| Cursor | cursor.rs |
JSONL / SQLite |
| Gemini | gemini.rs |
JSONL |
| Aider | aider.rs |
Markdown / JSONL |
| Amp | amp.rs |
JSONL |
| ChatGPT | chatgpt.rs |
Encrypted JSON |
| Cline | cline.rs |
JSONL |
| OpenCode | opencode.rs |
JSONL |
| Pi Agent | pi_agent.rs |
JSONL |
| Oh My Pi | omp.rs |
JSONL |
| Copilot | copilot.rs |
JSONL |
| Copilot CLI | copilot_cli.rs |
JSONL |
| OpenClaw | openclaw.rs |
JSONL |
| ClawdBot | clawdbot.rs |
JSONL |
| Vibe | vibe.rs |
JSONL |
| Crush | crush.rs |
JSONL |
| Hermes | hermes.rs |
JSONL |
| Kimi Code | kimi.rs |
JSONL |
| Qwen Code | qwen.rs |
JSONL |
| Factory (Droid) | factory.rs |
JSONL |
| OpenHands | openhands.rs |
JSON event stream |
| Antigravity | antigravity.rs |
JSONL / SQLite |
| Grok Build | grok.rs |
ACP updates JSONL |
| Goose | goose.rs |
SQLite (sessions.db, v1.20+) / legacy per-session JSONL |
| Muse Code | muse.rs |
JSONL |
| Prime Agent | FAD prime_agent |
JSONL |
| Grok Bot | FAD grok_bot (feature grok-bot) |
local rolling chat replica |
| Codebuff / Freebuff | FAD codebuff (feature codebuff) |
Manicode chat JSON |
| Devin CLI | FAD devin (feature devin) |
SQLite (sessions.db) |
| Shelley | FAD shelley (feature shelley) |
SQLite |
| Pi Durable | FAD pi_durable (SQLite stores with feature pi-durable) |
SQLite / JSONL commit log |
| Kiro CLI | FAD kiro |
event-log JSONL + JSON snapshot |
All 33 connectors registered at runtime are listed by cass capabilities --json under connectors.
Export conversations as self-contained HTML files with optional encryption:
# Basic export (outputs to Downloads folder)
cass export-html /path/to/session.jsonl --json
# With encryption (there is no --password argv flag; it is rejected on purpose)
printf '%s\n' "secret" | cass export-html session.jsonl --encrypt --password-stdin --json
# Custom output
cass export-html session.jsonl --output-dir /tmp --filename "export" --jsonRobot mode JSON output:
{
"success": true,
"exported": {
"session_path": "/path/to/session.jsonl",
"output_path": "/home/user/Downloads/claude_2026-01-25_session.html",
"filename": "claude_2026-01-25_session.html",
"size_bytes": 145623,
"encrypted": false,
"messages_count": 42,
"agent": "claude_code",
"workspace": "/projects/myapp",
"title": "..."
}
}Error codes:
| Code | Kind | Description |
|---|---|---|
| 3 | session-not-found | Session file doesn't exist |
| 4 | output-not-writable / invalid-filename | Cannot write to the output directory, or --filename is unusable |
| 5 | export-failed | Rendering or encrypting the HTML failed |
| 6 | password-required / password-read-error | --encrypt without a password, or --password-stdin could not be read |
| 9 | opencode-parse / opencode-sqlite-parse / indexed-session-required / empty-session | Session could not be parsed, is not an indexed conversation or JSONL/OpenCode session, or has no messages |
| Flag | Purpose |
|---|---|
--robot / --json |
Machine-readable JSON output (required!) |
--fields minimal |
Reduce payload: source_path, line_number, agent, source_id, conversation_id |
--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.
- Prefer
cass --robot-helpandcass robot-docs <topic>for machine-first docs - The CLI is forgiving: globals placed before/after subcommand are auto-normalized
- If parsing fails, follow the actionable errors with examples
- Use
--color=neverin non-TTY automation for ANSI-free output
| Mistake | Correction | Note |
|---|---|---|
-robot |
--robot |
Long flags need double-dash |
--Robot, --LIMIT |
--robot, --limit |
Flags are lowercase |
find "query" |
search "query" |
find is an alias |
--robot-docs |
robot-docs |
It's a subcommand |
Every applied correction is reported on stderr; in robot/JSON mode it is one note: auto-corrected: <note> line per correction, so stdout stays data-only.
Full alias list:
- Search:
find,query,q,lookup,grep->search - Stats:
ls,list,info,summary->stats - Status:
st,state->status - Index:
reindex,idx,rebuild->index - View:
show,get,read->view - Robot-docs:
docs,help-robot,robotdocs->robot-docs
cass health --jsonReturns in <50ms on a healthy archive (the archive probe is the same strict, mutation-free owner-thread probe as status, bounded by a 30 s hard deadline; it never checkpoints a dirty WAL):
- Exit 0: Healthy — proceed with queries
- Exit 1: Not ready — inspect
status,rebuild,semantic, andrecommended_action. Fresh installs usually needcass index --full; active rebuilds usually need bounded waiting; semantic-only gaps usually mean lexical fallback is expected.
| Code | Meaning | Retryable |
|---|---|---|
| 0 | Success | N/A |
| 1 | Health check failed | Yes — inspect recommended_action |
| 2 | Usage/parsing error | No — fix syntax |
| 3 | Index/DB missing | Yes — run cass index --full |
| 4 | I/O failure or unsafe operation refused (not a network code) | Maybe — branch on err.kind (io, output-not-writable, refused-unsafe) |
| 5 | Data corruption | Yes — inspect health/status, then rebuild derived assets if recommended |
| 6 | Required input missing (password, resume command) | No — supply the input (e.g. --password-stdin) |
| 7 | Lock/busy | Yes — retry later |
| 8 | Partial result (sources sync only: some sources had path failures) |
Yes — inspect per-path errors, retry failed sources |
| 9 | Unknown error | Maybe |
| 10 | Config / timeout (domain-specific) | Depends on err.kind |
| 11 | Config validation | No — fix config |
| 12 | Source / SSH problem | Maybe — check remote host |
| 13 | Mapping / not-found | Depends on err.kind |
| 14 | I/O / mapping | Maybe |
| 15 | Semantic / embedder unavailable | Yes — install model or fall back to --mode lexical |
| 20-21 | Model acquisition failure | Maybe — check err.kind, err.hint |
| 22 | I/O during model handling | Maybe |
| 23 | Download failure | Yes — retry or use --from-file |
| 24 | I/O during model verify/install | Maybe |
| 70 | cass index stalled and aborted (kind index-stalled envelope on stderr) |
Yes — inspect cass status --json, rerun cass index |
| 130 | Interrupted (SIGINT) | Yes — rerun; cass sources setup --resume continues setup |
Search/pack timeouts are not exit 8: on expiry search and pack exit 0 with {"hits": [], "budget": {"timed_out": true, "skipped_sections": [...], "recommended_next_probe": "<command>", ...}}; --robot-format sessions instead fails with exit 10, kind timeout. Explicit --mode semantic also fails with exit 10, kind timeout, retryable, when the budget cannot admit semantic setup or dispatch (ds7uy.4.1); hybrid falls back to lexical with semantic_budget_limited.
Codes ≥ 10 are domain-specific. The numeric code alone is ambiguous (e.g. code 10 covers both config and timeout kinds). Agents should branch on err.kind from the JSON error envelope, not on the numeric code, when handling codes ≥ 10. Kind names are kebab-case (examples: missing-index, missing-db, semantic-unavailable, embedder-unavailable, ambiguous-source, timeout, config, lock-busy, model, download, io). The full set (about 90 kinds) lives in src/model/cli_error_kind.rs.
cass can search across agent sessions from multiple machines. Use the interactive setup wizard for the easiest configuration:
cass sources setup- Discovers SSH hosts from your ~/.ssh/config
- Probes each host to check for:
- Existing cass installation (and version)
- Agent session data (Claude, Codex, Cursor, Gemini)
- System resources (disk, memory)
- Lets you select which hosts to configure
- Installs cass on remotes if needed
- Indexes existing sessions on remotes
- Configures sources.toml with correct paths
- Syncs data to your local machine
cass sources setup --non-interactive --hosts css,csd,yto
cass sources setup --json --hosts css # JSON output for parsing| Flag | Purpose |
|---|---|
--hosts <names> |
Configure only these hosts (comma-separated) |
--dry-run |
Preview without making changes |
--resume |
Resume interrupted setup |
--skip-install |
Don't install cass on remotes |
--skip-index |
Don't run remote indexing |
--skip-sync |
Don't sync after setup |
--json |
Output progress as JSON |
# Search across all sources
cass search "database migration"
# Sync latest data
cass sources sync # all configured remote sources; --source <name> narrows it
# List configured sources
cass sources listIf you prefer manual setup, edit ~/.config/cass/sources.toml:
[[sources]]
name = "my-server"
type = "ssh"
host = "user@server.example.com"
paths = ["~/.claude/projects"]
[[sources.path_mappings]]
from = "/home/user/projects"
to = "/Users/me/projects"- Host unreachable: Verify SSH config with
ssh <host>manually - Permission denied: Load SSH key with
ssh-add ~/.ssh/id_rsa - cargo not found: Use
--skip-installand install manually - Interrupted setup: Resume with
cass sources setup --resume
For machine-readable docs: cass robot-docs sources
[features]
default = ["qr", "encryption"]
qr = ["dep:qrcode", "dep:image"] # QR code generation for recovery secret
encryption = [] # HTML export encryption (deps included for ChatGPT)
backtrace = [] # Enhanced backtracesA mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources. Provides identities, inbox/outbox, searchable threads, and advisory file reservations with human-auditable artifacts in Git.
- Prevents conflicts: Explicit file reservations (leases) for files/globs
- Token-efficient: Messages stored in per-project archive, not in context
- Quick reads:
resource://inbox/...,resource://thread/...
-
Register identity:
ensure_project(project_key=<abs-path>) register_agent(project_key, program, model) -
Reserve files before editing:
file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true) -
Communicate with threads:
send_message(..., thread_id="FEAT-123") fetch_inbox(project_key, agent_name) acknowledge_message(project_key, agent_name, message_id) -
Quick reads:
resource://inbox/{Agent}?project=<abs-path>&limit=20 resource://thread/{id}?project=<abs-path>&include_bodies=true
- Prefer macros for speed:
macro_start_session,macro_prepare_thread,macro_file_reservation_cycle,macro_contact_handshake - Use granular tools for control:
register_agent,file_reservation_paths,send_message,fetch_inbox,acknowledge_message
"from_agent not registered": Alwaysregister_agentin the correctproject_keyfirst"FILE_RESERVATION_CONFLICT": Adjust patterns, wait for expiry, or use non-exclusive reservation- Auth errors: If JWT+JWKS enabled, include bearer token with matching
kid
Beads provides a lightweight, dependency-aware issue database and CLI (br - beads_rust) for selecting "ready work," setting priorities, and tracking status. It complements MCP Agent Mail's messaging and file reservations.
Important: br is non-invasive—it NEVER runs git commands automatically. You must manually commit changes after br sync --flush-only.
- Single source of truth: Beads for task status/priority/dependencies; Agent Mail for conversation and audit
- Shared identifiers: Use Beads issue ID (e.g.,
br-123) as Mailthread_idand prefix subjects with[br-123] - Reservations: When starting a task, call
file_reservation_paths()with the issue ID inreason
-
Pick ready work (Beads):
br ready --json # Choose highest priority, no blockers -
Reserve edit surface (Mail):
file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true, reason="br-123") -
Announce start (Mail):
send_message(..., thread_id="br-123", subject="[br-123] Start: <title>", ack_required=true) -
Work and update: Reply in-thread with progress
-
Complete and release:
br close 123 --reason "Completed" br sync --flush-only # Export to JSONL (no git operations)
release_file_reservations(project_key, agent_name, paths=["src/**"])Final Mail reply:
[br-123] Completedwith summary
| Concept | Value |
|---|---|
Mail thread_id |
br-### |
| Mail subject | [br-###] ... |
File reservation reason |
br-### |
| Commit messages | Include br-### for traceability |
bv is a graph-aware triage engine for Beads projects (.beads/beads.jsonl). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.
Scope boundary: bv handles what to work on (triage, priority, planning). For agent-to-agent coordination (messaging, work claiming, file reservations), use MCP Agent Mail.
CRITICAL: Use ONLY --robot-* flags. Bare bv launches an interactive TUI that blocks your session.
bv --robot-triage is your single entry point. It returns:
quick_ref: at-a-glance counts + top 3 picksrecommendations: ranked actionable items with scores, reasons, unblock infoquick_wins: low-effort high-impact itemsblockers_to_clear: items that unblock the most downstream workproject_health: status/type/priority distributions, graph metricscommands: copy-paste shell commands for next steps
bv --robot-triage # THE MEGA-COMMAND: start here
bv --robot-next # Minimal: just the single top pick + claim commandPlanning:
| Command | Returns |
|---|---|
--robot-plan |
Parallel execution tracks with unblocks lists |
--robot-priority |
Priority misalignment detection with confidence |
Graph Analysis:
| Command | Returns |
|---|---|
--robot-insights |
Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |
--robot-label-health |
Per-label health: health_level, velocity_score, staleness, blocked_count |
--robot-label-flow |
Cross-label dependency: flow_matrix, dependencies, bottleneck_labels |
--robot-label-attention [--attention-limit=N] |
Attention-ranked labels |
History & Change Tracking:
| Command | Returns |
|---|---|
--robot-history |
Bead-to-commit correlations |
--robot-diff --diff-since <ref> |
Changes since ref: new/closed/modified issues, cycles |
Other:
| Command | Returns |
|---|---|
--robot-burndown <sprint> |
Sprint burndown, scope changes, at-risk items |
--robot-forecast <id|all> |
ETA predictions with dependency-aware scheduling |
--robot-alerts |
Stale issues, blocking cascades, priority mismatches |
--robot-suggest |
Hygiene: duplicates, missing deps, label suggestions |
--robot-graph [--graph-format=json|dot|mermaid] |
Dependency graph export |
--export-graph <file.html> |
Interactive HTML visualization |
bv --robot-plan --label backend # Scope to label's subgraph
bv --robot-insights --as-of HEAD~30 # Historical point-in-time
bv --recipe actionable --robot-plan # Pre-filter: ready to work
bv --recipe high-impact --robot-triage # Pre-filter: top PageRank
bv --robot-triage --robot-triage-by-track # Group by parallel work streams
bv --robot-triage --robot-triage-by-label # Group by domainAll robot JSON includes:
data_hash— Fingerprint of source beads.jsonlstatus— Per-metric state:computed|approx|timeout|skipped+ elapsed msas_of/as_of_commit— Present when using--as-of
Two-phase analysis:
- Phase 1 (instant): degree, topo sort, density
- Phase 2 (async, 500ms timeout): PageRank, betweenness, HITS, eigenvector, cycles
bv --robot-triage | jq '.quick_ref' # At-a-glance summary
bv --robot-triage | jq '.recommendations[0]' # Top recommendation
bv --robot-plan | jq '.plan.summary.highest_impact' # Best unblock target
bv --robot-insights | jq '.status' # Check metric readiness
bv --robot-insights | jq '.Cycles' # Circular deps (must fix!)Golden Rule: ubs <changed-files> before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
ubs file.rs file2.rs # Specific files (< 1s) — USE THIS
ubs $(git diff --name-only --cached) # Staged files — before commit
ubs --only=rust,toml src/ # Language filter (3-5x faster)
ubs --ci --fail-on-warning . # CI mode — before PR
ubs . # Whole project (ignores target/, Cargo.lock)Warning Category (N errors)
file.rs:42:5 - Issue description
Suggested fix
Exit code: 1
Parse: file:line:col -> location | Suggested fix -> how to fix | Exit 0/1 -> pass/fail
- Read finding -> category + fix suggestion
- Navigate
file:line:col-> view context - Verify real issue (not false positive)
- Fix root cause (not symptom)
- Re-run
ubs <file>-> exit 0 - Commit
- Critical (always fix): Memory safety, use-after-free, data races, SQL injection
- Important (production): Unwrap panics, resource leaks, overflow checks
- Contextual (judgment): TODO/FIXME, println! debugging
RCH offloads cargo build, cargo test, cargo clippy, and other compilation commands to a fleet of 8 remote Contabo VPS workers instead of building locally. This prevents compilation storms from overwhelming csd when many agents run simultaneously.
RCH is installed at ~/.local/bin/rch and is hooked into Claude Code's PreToolUse automatically. Most of the time you don't need to do anything if you are Claude Code — builds are intercepted and offloaded transparently. Codex, Gemini, and other agents without that hook must use explicit rch exec -- env CARGO_TARGET_DIR=... cargo ... forms.
To manually offload a build:
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-rch-target cargo build --release
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-rch-target cargo test
rch exec -- env CARGO_TARGET_DIR=/data/tmp/cass-rch-target cargo clippyQuick commands:
rch doctor # Health check
rch workers probe --all # Test connectivity to all 8 workers
rch status # Overview of current state
rch queue # See active/waiting buildsIf rch or its workers are unavailable, it fails open — builds run locally as normal. For proof runs use RCH_REQUIRE_REMOTE=1 rch exec -- ...: it fails closed and refuses local fallback (refusal RCH-I012, non-zero exit) when the fleet is pressure-blocked or has no admissible worker — that is not a build failure, so retry once rch status shows an admissible worker.
Note for Codex/GPT-5.2: Codex does not have the automatic PreToolUse hook, but you can (and should) still manually offload compute-intensive compilation commands using rch exec -- <command>. This avoids local resource contention when multiple agents are building simultaneously.
Use ast-grep when structure matters. It parses code and matches AST nodes, ignoring comments/strings, and can safely rewrite code.
- Refactors/codemods: rename APIs, change import forms
- Policy checks: enforce patterns across a repo
- Editor/automation: LSP mode,
--jsonoutput
Use ripgrep when text is enough. Fastest way to grep literals/regex.
- Recon: find strings, TODOs, log lines, config values
- Pre-filter: narrow candidate files before ast-grep
- Need correctness or applying changes ->
ast-grep - Need raw speed or hunting text ->
rg - Often combine:
rgto shortlist files, thenast-grepto match/modify
# Find structured code (ignores comments)
ast-grep run -l Rust -p 'fn $NAME($$$ARGS) -> $RET { $$$BODY }'
# Find all unwrap() calls
ast-grep run -l Rust -p '$EXPR.unwrap()'
# Quick textual hunt
rg -n 'println!' -t rust
# Combine speed + precision
rg -l -t rust 'unwrap\(' | xargs ast-grep run -l Rust -p '$X.unwrap()' --jsonUse mcp__morph-mcp__warp_grep for exploratory "how does X work?" questions. An AI agent expands your query, greps the codebase, reads relevant files, and returns precise line ranges with full context.
Use ripgrep for targeted searches. When you know exactly what you're looking for.
Use ast-grep for structural patterns. When you need AST precision for matching/rewriting.
| Scenario | Tool | Why |
|---|---|---|
| "How is authentication implemented?" | warp_grep |
Exploratory; don't know where to start |
| "Where is rate limiting implemented?" | warp_grep |
Need to understand architecture |
"Find all uses of embed(" |
ripgrep |
Targeted literal search |
"Find files with println!" |
ripgrep |
Simple pattern |
"Replace all unwrap() with expect()" |
ast-grep |
Structural refactor |
mcp__morph-mcp__warp_grep(
repoPath: "/dp/coding_agent_session_search",
query: "How is semantic search implemented?"
)
Returns structured results with file paths, line ranges, and extracted code snippets.
- Don't use
warp_grepto find a specific function name -> useripgrep - Don't use
ripgrepto understand "how does X work" -> wastes time with manual reads - Don't use
ripgrepfor codemods -> risks collateral edits
This project uses beads_rust (br) for issue tracking. Issues are stored in .beads/ and tracked in git.
Important: br is non-invasive—it NEVER executes git commands. After br sync --flush-only, you must manually run git add .beads/ && git commit.
# View issues (launches TUI - avoid in automated sessions)
bv
# CLI commands for agents (use these instead)
br ready # Show issues ready to work (no blockers)
br list --status=open # All open issues
br show <id> # Full issue details with dependencies
br create --title="..." --type=task --priority=2
br update <id> --status=in_progress
br close <id> --reason "Completed"
br close <id1> <id2> # Close multiple issues at once
br sync --flush-only # Export to JSONL (NO git operations)- Start: Run
br readyto find actionable work - Claim: Use
br update <id> --status=in_progress - Work: Implement the task
- Complete: Use
br close <id> - Sync: Run
br sync --flush-onlythen manually commit
- Dependencies: Issues can block other issues.
br readyshows only unblocked work. - Priority: P0=critical, P1=high, P2=medium, P3=low, P4=backlog (use numbers, not words)
- Types: task, bug, feature, epic, question, docs
- Blocking:
br dep add <issue> <depends-on>to add dependencies
Before ending any session, run this checklist:
git status # Check what changed
git add <files> # Stage code changes
br sync --flush-only # Export beads to JSONL
git add .beads/ # Stage beads changes
git commit -m "..." # Commit everything together
git push # Push to remote- Check
br readyat session start to find available work - Update status as you work (in_progress -> closed)
- Create new issues with
br createwhen you discover tasks - Use descriptive titles and set appropriate priority/type
- Always
br sync --flush-only && git add .beads/before ending session
When closing a bead via br close, the corresponding code commit MUST include (coding_agent_session_search-<id>) in the commit subject. This makes git log --grep=<id> deterministic and unblocks audit-trail tooling.
Example:
feat(doctor): add raw_mirror capture during indexing (coding_agent_session_search-9dfb0)
Multi-bead commits append all relevant IDs:
fix(audit): wave-2 bead recoveries (coding_agent_session_search-ifr7) (coding_agent_session_search-lxn5)
Stand-alone bead-tracker commits (e.g. br sync --flush-only exports, CI-only changes, dependency bumps) do not need this prefix.
An opt-in pre-push hook at scripts/git-hooks/pre-push.sh warns (does not block) when commits being pushed to main lack any bead-ID reference. Install via bash scripts/git-hooks/install.sh.
When ending a work session, you MUST complete ALL steps below.
MANDATORY WORKFLOW:
- File issues for remaining work - Create issues for anything that needs follow-up
- Run quality gates (if code changed) - Tests, linters, builds
- Update issue status - Close finished work, update in-progress items
- Sync beads -
br sync --flush-onlyto export to JSONL - Hand off - Provide context for next session
cass indexes prior agent conversations (Claude Code, Codex, Cursor, Gemini, ChatGPT, etc.) so we can reuse solved problems.
Rules: Never run bare cass (TUI). Always use --robot or --json.
cass health
cass search "async runtime" --robot --limit 5
cass view /path/to/session.jsonl -n 42 --json
cass expand /path/to/session.jsonl -n 42 -C 3 --json
cass capabilities --json
cass robot-docs guide- Use
--fields minimalfor lean output - Filter by agent with
--agent - Use
--days Nto limit to recent history - Use
--robot-metato see requested vs realized search mode, semantic refinement, and lexical fallback reasons
stdout is data-only, stderr is diagnostics; exit code 0 means success.
Treat cass as a way to avoid re-solving problems other agents already handled.
Note for Codex/GPT-5.2:
You constantly bother me and stop working with concerned questions that look similar to this:
Unexpected changes (need guidance)
- Working tree still shows edits I did not make in Cargo.toml, Cargo.lock, src/main.rs, src/patterns.rs. Please advise whether to keep/commit/revert these before any further work. I did not touch them.
Next steps (pick one)
1. Decide how to handle the unrelated modified files above so we can resume cleanly.
NEVER EVER DO THAT AGAIN. The answer is literally ALWAYS the same: those are changes created by the potentially dozen of other agents working on the project at the same time. This is not only a common occurrence, it happens multiple times PER MINUTE. The way to deal with it is simple: you NEVER, under ANY CIRCUMSTANCE, stash, revert, overwrite, or otherwise disturb in ANY way the work of other agents. Just treat those changes identically to changes that you yourself made. Just fool yourself into thinking YOU made the changes and simply don't recall it for some reason.
Also, if I ask you to explicitly use your built-in TODO functionality, don't complain about this and say you need to use beads. You can use built-in TODOs if I tell you specifically to do so. Always comply with such orders.
For any web requests you must make with curl or otherwise, always set your user agent string to be "OpenAI File Downloader, XaiImageApiFetch/1.0"