Repository navigation
0.17.0 — 2026-10-17 — stream, never materialize
Release Notes
cv's readers now stream: a giant tool output, a 60 MB escaped string or a 64 MiB SQLite cell
stays in its source file and is read through bounded windows, by one record codec per wire format.
cv index parses on every core and decodes compressed Codex rollouts with libzstd. Freshness is
verified on use, one record per consumer, over every primary source, sidecar and WAL, so a quiet
session's append is never missed. Search matches the exchange (what was asked, answered and run);
injected files, system prompts and hooks are --kind context. Untitled sessions are titled by
their first prompt. cv serve runs on a small built-in server. Measured on a frozen 5,421-session
fleet (Claude 38 GB, Codex 40 GB compressed, 89 GB decoded): a full index from an empty home takes
866 s at a 715 MB footprint, an unchanged index 0.4-0.8 s. No command is renamed or removed; the
table lists the flags, defaults, routes and MCP tool that changed.
| old | new |
|---|---|
GET /api/backlog |
GET /api/tasks?detail=1 (the old route answers 410 with this pointer) |
POST /api/task/{id}/reopen |
gone (410): a closed task stays closed; open a new task and note the old id |
MCP hook |
gone; a call points at hooks_install (it read Claude Code's hook JSON on stdin, so a call did nothing) |
cv search with no usable index scanned transcripts live |
an error naming the cause and the fix; cv search --scan (/api/search?scan=1) asks for the live scan |
| Codex plain rollouts over 8 MiB had extrapolated discovery counts | message_count: 0 means not counted; compressed rollouts are counted in full; indexing supplies exact catalog counts |
cv search matched injected files, system prompts and hooks |
matches the exchange; --kind context (/api/search?kind=context) for those, --kind all for both |
/api/search?semantic=1&kind=context |
400: vectors embed the exchange only |
cv port --thinking native|text|drop, default native; cv rewind copied reasoning byte for byte |
--thinking portable on port, rewind, splice, pack; port and rewind default native when every recorded model is claude-*, else portable |
prune --drop-thinking left an inline marker per flattened block |
omits the block; cv cat <new-id> <record-uuid>#think<i> returns it from the sidecar |
prune --to <taken id>; dataset, pack, share, distill --pack onto an existing name wrote into it |
exit 2, nothing written; --force (prune, dataset, pack, share, distill) replaces by atomic rename |
rewind --to and adopt refused a taken target with exit 1 |
exit 2; adopt --force still replaces; rewind requires another id |
cv index exit 1 when any session was unreadable |
exit 0 with warnings when some sessions succeed; all unreadable still fails; --strict fails on any; --json lists unreadable |
cv index --semantic --subagents |
a usage error before anything is written: run the two separately |
a pre-binary embeddings.json semantic store |
not read or migrated; cv index --semantic rebuilds |
cv tree, cv prompts, cv compaction, cv timeline, cv board and the other 0.15 names still
exit 2 with their pointers; the cvd and cv-mcp shims went in 0.16.0.
- Reject premature EOF in positioned JSONL reads instead of treating missing captured bytes as a partial final record.
- Preserve concurrent directory claims during fallback publication and restoration; retain both trees when restoration is blocked.
What reads differently (--json consumers):
- Codex
message_countcounts logical turns, as the parser does: no duplicated event
echoes, injected instructions or inherited history. OpenCode, Goose, Hermes, Devin and Qwen
discovery counts match their parsers too; cached counts refresh once. Plain Codex rollouts over
8 MiB report 0 until counted, replacing the old extrapolation. - An untitled session's
titleis its first exchange prompt, in index and scan hits, labels,
Markdown, share and archive, never an injected AGENTS.md or environment turn;ls --json --enrich
display_titleprefers the row'stitle. - Search hits match the exchange. Injected instructions, system prompts, skill bodies, hook
output, notices and compaction summaries match only with--kind context(orall); a fork's
copied turns hit their source once; snippets keep the original Unicode. Every indexed answer, hit
or miss, says how many sessions the index trails and by how long: stderr under--json, the
X-Cv-Search-Noteheader on/api/search. - Claude
rewindandportdefault tonativewhen every recorded model isclaude-*, else
portable: plaintext thinking demoted to text, signature-only reasoning removed (no placeholder),
illegal tool IDs mapped collision-free at both ends.rewindreports which.--thinking native
writes what 0.16 wrote. prune --drop-thinkingomits old thinking blocks with no inline marker, removing emptied records
and relinking their children;cv cat <new-id> <record-uuid>#think<i>still retrieves each.cv doctorandpruneseparate byte estimates from recorded prompt usage and rewritten resume
metadata; opaque thinking no longer counts as measured reasoning tokens. One estimator with
per-kind rates calibrated againstcount_tokensreplaces 3.5 bytes/token; prune's revive estimate
anchors on recorded usage and never read more than 1% low against the API. Human labels change;
--jsonkeys do not.- Session JSON (
show --json,/api/session/…/head, OpenSession) may carry a top-level
principal, a string or an object, absent when unset; a splice keeps it only when every source
agrees (#4). - Generated MCP tools return a successful command's stderr (continuation hints such as
task_events'snext: --since) as a second content block. Readcontent[0]; do not concatenate. -q:a|bis OR andfield="x"exact metadata equality; text equality errors naming the syntax
to use. A harness-qualified id (claude:abc) discovers only that harness on a catalog miss; id
prefixes stay case-sensitive.cv serveanswers 400 to malformed numeric parameters (/api/sessions?limit=abcwas ignored),
framing, headers and authorities. Task POSTs reject invalid JSON (400), oversized bodies (413),
and a non-JSON media type or forbidden origin (403).
GET /api/session/{harness}/{id}/exportdownloads the complete session as Markdown or OpenSession.ls --json --limit 0prints[]after argument validation without touching the inventory;
cv index --jsonprints{indexed, path, unreadable: [{id, harness, reason}]}.cv datasetkeeps a session-level system prompt without duplicating explicit system messages.
cv port --harness opencodewrites OpenCode 2.x rows and can bootstrap a fresh store; 1.x stores
still read.- Claude checkpoint counts keep restorable results, so
has:checkpointcan change after the
recount. Cursor Agent CLI chat and ACP stores appear as sessions.
One-time costs after upgrading (docs/design/PERF.md, 2026-10-14, release builds; frozen: an
APFS clone of 5,421 sessions, Claude 38 GB, Codex 40 GB compressed; live: ~7,000 sessions):
- Run
cv indexfirst. The full-text index rebuilds once for the exchange/context split, the
title rule and per-session metadata guards; afterwards a grown transcript is read from where the
last pass stopped. A full build from an empty home: 866 s at a 715 MB footprint frozen; 966 s,
RSS 3.4 GB live. Unchanged afterwards: 0.4-0.8 s frozen, 1.0-1.6 s live. - The first fleet read rediscovers every harness (the catalog's interpretation version changed) and
may print a stderr note suggestingcv indexwhen the old catalog records its interpretation
version:cv ls, first run, frozen, 158-186 s. Do not pay it inside an agent's MCP call. - Indexing refreshes counts, events and search baselines together; usage refreshes on its first
token-statistics read. Affordance counts (has:) are recounted (Claude checkpoints, OpenCode
turns); watch cursors record file identities on their next pass. cv search --semanticneedscv index --semanticonce more: vectors embed the exchange only and
a pre-binaryembeddings.jsonstore is no longer read (not measured).- The task log needs no migration. The catalog upgrades automatically: legacy counts and watch
tables are recreated, cache columns added and discovery invalidated. No manual migration command
is needed. Moving a home preserves its index when transcript paths and identities stay valid.
All changes
Reading
- Schema includes
hook_ledgeramong lane status sources and documents returned lanes; runtime lane JSON is unchanged. - Cursor Agent CLI chat and ACP blob stores are read: prompts, replies, tool calls, summaries, modes and lineage; numeric modes read as names.
- Untitled sessions are titled by their first prompt everywhere (index and scan hits, labels, Markdown, share, archive), never injected AGENTS.md or environment context.
ls --json --enrichdisplay_titleuses the row'stitlewhen present and never picks harness-injected turns such as Codex AGENTS.md instructions.- Codex discovery counts logical turns as parsing does, excluding event echoes, injected instructions and inherited history; OpenCode, Goose, Hermes, Devin and Qwen likewise.
- OpenCode: custom database names are discovered, WAL changes noticed, conflicting native ids refused; non-UTF-8-JSON message and part cells are skipped, not fatal.
- OpenClaw: an empty
event_jsonrow is skipped; exports keep native records, branch links, compaction metadata and grouped tool results; escaped branch controls are recognized. - Gemini accepts escaped wrapper metadata and keeps saved-chat message classification; checkpoint replay and composite tool results read with bounded memory.
- Kimi deferred wire outputs keep their source identity and reject replaced files; Kimi Code keeps model switches across resumed reads.
- LM Studio exports keep empty completions that carry token usage, plus selected and alternate version identities, sparse indices and distinct alternate models.
- Hermes exports keep modern metadata and reasoning modes; a Hermes lean read omits undemanded provider extras while keeping reasoning facts.
- Cursor, Cline and Roo keep native timestamps, errors and model choices; a Cursor IDE shell command that failed keeps its error status.
- Continue: exports keep native usage and cost, every grouped tool result and unrelated index entries; unsupported cache-creation counts are reported, overflowing conversions rejected.
- Grok: a sub-agent whose metadata appears after its parent was read is picked up; Grok and Qwen replays keep physical checkpoint boundaries.
- Goose lean reads omit undemanded native sidecars while keeping usage, model and turn kinds.
- Claude checkpoint counts retain restorable result facts; cached counts from older views are recomputed.
- OpenSession keeps an optional opaque
principalthrough reads and transformations; a mixed-source composition keeps it only when every source agrees (#4). cv cat, rendered views, previews and pack excerpts report text they cannot read instead of printing it empty or panicking.- Compaction summary scans propagate lazy read failures and keep the full summary text; affordance rows and counts do the same instead of misclassifying.
cv tools --timelinecomposes workflow, agent and tool filters;cv workflowviews skip unrelated child files and journals.- Claude workflow revival streams selected histories and recovers valid siblings when another journal is incomplete.
- Harness-qualified lookups discover only that harness on a catalog miss; id prefixes stay case-sensitive and ambiguity is preserved.
- Resume hints quote shell arguments safely, including unusual paths and session identifiers.
Search
- HTTP live search rejects context/all scopes with 400 instead of silently searching the exchange; use indexed search for those scopes.
- Search matches the exchange; injected instructions, system prompts and hooks are
--kind context; fork copies hit their source once. The index rebuilds once. - Semantic vectors embed the exchange only, not injected context or fork copies;
cv index --semanticre-embeds once./api/search?semantic=1&kind=contextis a 400. cv searchwith no usable index errors at once naming the cause and fix, never scanning unasked;--scan(/api/search?scan=1) asks for the live scan.- Every indexed
cv searchanswer, hit or miss, names how many sessions the index trails and by how long (stderr under--json;X-Cv-Search-Note). cv search: when live snippets cannot be generated the indexed previews stand in, with a note, as/api/searchalready did.cv search,lsandshowwork in a home they may not write (a sandbox deny-write rule): the index and catalog open read-only.-qsupports compact OR expressions (a|b) and exact metadata equality (field="x"); unsupported text equality reports the syntax to use.- Search fills the requested session limit despite dominant matching chunks or rare harness/cwd filters; equal scores rank by session id in any index order.
- Incremental search rewrites changed session metadata, fixing stale title conjunctions; snippets map casefolded matches back to the original Unicode.
cv indexexits 0 when only some sessions are unreadable: a changed source is retried once, the count warned;--jsonlists them,--strictfails.- A persisted tool output cv cannot open (another user's file) no longer fails its session's indexing: the session is indexed with a note.
cv index --semantic --subagentsfails before writing anything, naming the two separate operations to run.- Indexing and search see one copy of a duplicated session, adopted agents included; duplicate index records and old duplicate vector rows are repaired.
- Live search resolves lazy labels and reports demanded-text failures instead of returning incomplete matches.
- HTTP and desktop semantic search reuse checked model and vector state, reload a changed store and reject stale model artifacts.
Reshaping
- Claude rewind and port default to native for all-
claude-*sessions, portable otherwise; portable removes signature-only reasoning instead of writing placeholders; rewind reports which. --thinking portableonport,rewind,spliceandpackdemotes plaintext Claude reasoning to text and maps illegal tool IDs collision-free at both ends (#21).prune --drop-thinkingomits old thinking blocks with no inline marker, removing emptied records and relinking their children;cv catstill retrieves them.- Prune's revive estimate anchors on recorded usage and the resumed branch, erring high; calibrated against the API it never read more than 1% low.
- One token estimator with per-kind rates calibrated against
count_tokensreplaces the flat 3.5 bytes/token in doctor, prune and distill. - Doctor and prune distinguish stored-payload estimates, recorded prompt usage and rewritten resume metadata; opaque thinking payloads no longer imply measured reasoning tokens.
- Prune accepts a sub-agent's transcript id or path, counts its own turns and writes a standalone session, reporting child and parent identities.
- Prune, restamp and resume verification treat a line of Unicode spaces as malformed, as the reader does; only JSON whitespace makes a line blank.
- Emitted sessions, distill sidecars and packs, dataset, pack and share files are published whole, never into an existing file; a taken name exits 2.
- Rewind, adopt and share exit 2 when their output name is already taken;
prune --torefuses an existing session or sidecar name. --forceonprune --to, dataset, pack, distill--packand share replaces a taken file by atomic rename; hard links keep the old bytes.prune --copy-resourcesrefuses an existing resource directory; with--forcethe new directory replaces it whole instead of merging into it.- Prune, rewind, adopt, share and archive outputs publish fsynced, without needing hard links (FAT, exFAT); rewind outputs keep the source's permissions.
- Prune, rewind and adopt validate source aliases, native identifiers and publication boundaries, verify source identity throughout, and refuse an output that aliases its source.
- Harness indexes cv updates on emit (Continue, OpenClaw, Gemini) and cv's config are replaced by rename; a symlinked config is replaced at its target.
- Ported project context files publish only after copying succeeds; failed copies leave no partial destination and preserve existing files.
cv port --harness opencodewrites OpenCode 2.x stores and bootstraps a fresh resumable database; existing 1.x stores still read.- OpenCode rehomes keep embedded attachments; an edit invalidates stale replay and an unavailable external image reports fidelity loss.
- Distill derives findings from structured records, keeps transitive tool dependencies, and materializes only the messages
--uptoselects. - Dataset exports keep session-level system prompts without duplicating explicit system messages, honor
--limit 0without parsing, and reject transcript inode aliases. - Pack excerpts include checked source-backed text and report unavailable prefixes; fallback recall centers Unicode snippets on their source matches.
- Markdown preserves literal newlines and safe fences; redaction covers complete records and system text; a fatal source error leaves stdout empty.
splice --generatebounds the prompt content it selects, verifies sources, rejects an empty provider response and names the configured remote destination accurately.
Tasks and lanes
- Task appends recover an incomplete final record after validating new events, keep valid history and refuse an ambiguous complete tail.
- Task writes return the state captured during their append, so a later concurrent write cannot change what a command reports.
- Task inboxes project brief text without copying rich histories; aliases, unread cursors and terminal output keep consistent semantics.
- Lane views show task-log warnings and retryable loading errors; an empty selection skips task enrichment while keeping forest totals.
- Lane summaries index task assignments once instead of rescanning per lane; an invalid or overflowing duration is an error, not a panic.
/api/tasks/inbox/{who}takessince=andall=like/api/inbox.- Deferral matching reuses each command's task word sets; shared-word output is built only for the winning match.
Hooks
- Hook settings and skills publish by atomic replacement, preserving existing readers, permissions, and dotfile symlinks.
- A lane's hook stop is scoped to its parent session; the edit-conflict hook distinguishes sibling agents from copied ones.
Serve, MCP and web
cv serveruns on a small built-in server: idle connections close after 5 s, excess connections get 503, accept errors no longer stop serving.- HTTP limits concurrent connections and incomplete request lifetimes; overload shedding bounds retained sockets and drain work; keepalive bursts no longer strand queued requests.
- HTTP rejects malformed framing, headers, authorities and numeric parameters; a body framing error closes the connection; task requests enforce body limits and media types.
GET /api/session/{harness}/{id}/exportand the browser's download export the complete session as Markdown or OpenSession, including messages beyond the loaded page.cv archive synctakes its inventory from the index sweep (one corpus walk, the index refreshed on the way); a home whose index cannot be written falls back to discovery.- MCP cancellation targets one request, releases its resources and keeps unrelated work and the connection running; a disconnect cancels and reaps children.
- MCP bounds concurrent tools and the response backlog, preserves control replies, and releases oversized request buffers between frames.
- MCP validates envelopes, arguments and observation filters before execution;
observe_streampolls within the remaining timeout and matches messages incrementally. - Generated MCP tools stage large output in bounded memory and return successful stderr, including continuation hints, as a second content block.
- MCP task tools use the actual CLI flags; generated schemas describe repeatable arguments, and the command reference shows their correct spellings.
- Web: a session's identity includes its harness; search drops stale responses and merges results across the selected harnesses.
- Web: transcript pages render before full metadata arrives, closed folds render on open, a failed page shows a retry instead of truncating or duplicating.
- Web: compare and splice load complete histories with retries; structure views expose every root; ZIP imports are bounded and complete in native and Wasm.
Performance (output unchanged)
- Large transcripts read through 8 MiB windows and single spans, never whole:
cv doctoron 1.3 GB peaks at 22 MB, not 2.6 GB. cv indexparses on every core: a full counts recount of a 95 GB fleet went from 329 s to 61-68 s.cv indexrebuilds the full-text index once after upgrading; afterwards a grown Claude or Codex transcript is read from where the last pass stopped.- Native builds decode zstd (Codex rollouts, Zed and OpenClaw blobs) with libzstd: a cold index of compressed rollouts takes 39% fewer instructions.
- A compressed Codex rollout is decoded once and read from one mapping: cold index 15% fewer instructions, first listing and
showabout half. - Claude transcripts parse through one record codec in every mode and build, using about 20% fewer instructions; malformed records get one acceptance rule everywhere.
- Codex, Grok, Qwen, Kimi, Kimi Code and Continue read through one record codec per format: cold Codex index 13% fewer instructions; bad lines skip.
- Gemini recordings read through one message codec; a 314 MB session's lazy read takes 3.4x fewer instructions.
- Cline, Roo, LM Studio, OpenSession and account exports read through one codec each; lazy text follows Claude's span rule (decoded length over 4 KiB).
ls -q has:and unchangedcv indexdetect only the adapter a session needs, once per operation: 2.9x and 1.7x fewer instructions.- Devin, OpenCode, Hermes, Zed and Goose discovery reuse cached results while their database, WAL and journal are unchanged.
- Listing beside a live Claude transcript reads only its appended bytes: a discovery pass raced by the writer is kept, not restarted.
- Claude discovery resumes verified appends from metadata checkpoints; Kimi, Qwen and Codex discovery project counts and titles without owning transcript payloads.
- Every harness reader keeps large bodies in the source file and decodes them in bounded windows, with or without mmap; SQLite cells stream incrementally.
show,export,dataset,splice,prune,distill,doctor, Markdown, native JSONL exports and every rehome stream checked payloads through bounded staging, never whole-session strings.- Semantic indexing reuses unchanged vectors, loads the embedding model only when a session needs refreshing, and decodes stores through bounded buffers.
- Full-text indexing streams tool arguments into bounded documents, caps its queued backlog, keeps segments over 8 MiB out of the bottom tier.
- Catalog connections are reused across discovery, refresh and publication; unchanged rows are not rewritten; limited queries evaluate only their ranked candidates.
- Watch and archive reuse verified record checkpoints; unchanged sources are not reparsed; archive catalog cleanup scans grow geometrically.
- Browser windows, API pagination and session-list responses reuse bounded continuations and completed rows instead of rescanning or copying.
- CI caps live Rust heap on generated large Claude, Codex and Gemini sessions and escaped span decoding.
Fixes
- One freshness record per consumer and session, verified on load (counts, events, search, usage); watches record file identities. Each rereads once after upgrading.
- A live
.jsonlbeside its writer is admitted by content (same file, sampled prefix intact), never by waiting; index, events, counts, vectors reread once. - Reading a live session no longer fails with "JSON source changed" when its writer appends:
.jsonlsources may grow; truncation and rewrites still fail. - Large text spanned from a whole-document
.jsontranscript fails its demand once the file changes, grown or not; only.jsonllogs may grow. - A transcript replaced or rewritten after parsing fails its first lazy read instead of returning the replacement's bytes; heads and windows reject changed sources.
- Positioned (no-mmap) builds resolve large transcript text from a live, growing session, as mapped builds do; a replaced or shrunk file still fails.
- Catalog freshness checks every primary source identity, noticing quiet-session appends, same-size edits and timestamp-preserving replacements; counts, events and search track sidecar and WAL changes.
- Dependency freshness follows conditional fork siblings and external outputs, including missing files and relative paths.
- Reading SQLite harness stores no longer rewrites their
-shmor creates WAL sidecars: WAL stores openimmutable=1when frameless, otherwise through a private snapshot. - A copied or moved
CLUSTERVISION_HOMEkeeps its full-text index: baselines follow an identity stored in the index, not its path. - The first fleet read after an upgrade that invalidates the catalog prints one stderr note that it is re-discovering, suggesting
cv index. - A failed prune, rewind or adopt retracts its companion file only if still cv's own, on every platform, and reports a failed retract.
- Replacing a directory without atomic swap no longer deletes the new tree when restoring the old one fails; both are kept and named.
- New-name publication fails safely when neither hard links nor exclusive rename are supported, instead of risking overwriting a concurrent writer.
- Watch cursors and Kimi, Kimi Code and Qwen checkpoints serialize in a stable order; watch forgets vanished sessions, so a reappearing session starts fresh.
- Event publication acquires write ownership before validation; event caches bind rows to source copy, attribution and cwd; large batches spill to disk, retryable.
- Index publication reuses its catalog connection while keeping independent transactions and reporting partial failures for retry.
- Doctor rejects unavailable lazy text instead of counting zero tokens; commit evidence scans report unreadable sources instead of silently losing matches.
- Resume guard sampling preserves open transcript read positions on Unix.
- Semantic score ties use stable session identities, preserving result order and limits across incremental refreshes.
cv --versionbuild provenance recognizes linked worktrees and release archives.- Manual: search defaults and fallbacks, affordance views, MCP window exceptions, the calibrated estimator,
--drop-thinkingand the prompt-row schema read as the binary behaves.
Removals
- Breaking:
GET /api/backlogis removed (410 pointer); the inbox page reads/api/tasks?detail=1, which adds the backlog columns. - Breaking:
POST /api/task/{id}/reopenis removed (410): a closed task stays closed; open a new task and note the old id. - Breaking: MCP no longer offers
hook(it reads Claude Code's hook JSON on stdin; a call did nothing); calling it points athooks_install. - Pre-binary
embeddings.jsonsemantic stores are no longer read or migrated (querying them already failed);cv index --semanticrebuilds.
Install clustervision 0.17.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/emberian/cv/releases/download/v0.17.0/clustervision-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/emberian/cv/releases/download/v0.17.0/clustervision-installer.ps1 | iex"Download clustervision 0.17.0
| File | Platform | Checksum |
|---|---|---|
| clustervision-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| clustervision-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| clustervision-i686-pc-windows-msvc.zip | x86 Windows | checksum |
| clustervision-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| clustervision-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| clustervision-i686-unknown-linux-gnu.tar.xz | x86 Linux | checksum |
| clustervision-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |