Skip to content

0.17.0 — 2026-10-17 — stream, never materialize

Choose a tag to compare

@github-actions github-actions released this 17 Oct 21:29

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_count counts 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 title is 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_title prefers the row's title.
  • Search hits match the exchange. Injected instructions, system prompts, skill bodies, hook
    output, notices and compaction summaries match only with --kind context (or all); 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-Note header on /api/search.
  • Claude rewind and port default to native when every recorded model is claude-*, else
    portable: plaintext thinking demoted to text, signature-only reasoning removed (no placeholder),
    illegal tool IDs mapped collision-free at both ends. rewind reports which. --thinking native
    writes what 0.16 wrote.
  • prune --drop-thinking omits 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 doctor and prune separate 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 against count_tokens replaces 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;
    --json keys 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's next: --since) as a second content block. Read content[0]; do not concatenate.
  • -q: a|b is OR and field="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 serve answers 400 to malformed numeric parameters (/api/sessions?limit=abc was 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}/export downloads the complete session as Markdown or OpenSession.
  • ls --json --limit 0 prints [] after argument validation without touching the inventory;
    cv index --json prints {indexed, path, unreadable: [{id, harness, reason}]}.
  • cv dataset keeps a session-level system prompt without duplicating explicit system messages.
    cv port --harness opencode writes OpenCode 2.x rows and can bootstrap a fresh store; 1.x stores
    still read.
  • Claude checkpoint counts keep restorable results, so has:checkpoint can 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 index first. 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 suggesting cv index when 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 --semantic needs cv index --semantic once more: vectors embed the exchange only and
    a pre-binary embeddings.json store 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_ledger among 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 --enrich display_title uses the row's title when 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_json row 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 principal through 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 --timeline composes workflow, agent and tool filters; cv workflow views 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 --semantic re-embeds once. /api/search?semantic=1&kind=context is a 400.
  • cv search with 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 search answer, 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/search already did.
  • cv search, ls and show work in a home they may not write (a sandbox deny-write rule): the index and catalog open read-only.
  • -q supports 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 index exits 0 when only some sessions are unreadable: a changed source is retried once, the count warned; --json lists them, --strict fails.
  • 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 --subagents fails 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 portable on port, rewind, splice and pack demotes plaintext Claude reasoning to text and maps illegal tool IDs collision-free at both ends (#21).
  • prune --drop-thinking omits old thinking blocks with no inline marker, removing emptied records and relinking their children; cv cat still 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_tokens replaces 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 --to refuses an existing session or sidecar name.
  • --force on prune --to, dataset, pack, distill --pack and share replaces a taken file by atomic rename; hard links keep the old bytes.
  • prune --copy-resources refuses an existing resource directory; with --force the 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 opencode writes 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 --upto selects.
  • Dataset exports keep session-level system prompts without duplicating explicit system messages, honor --limit 0 without 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 --generate bounds 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} takes since= and all= 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 serve runs 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}/export and the browser's download export the complete session as Markdown or OpenSession, including messages beyond the loaded page.
  • cv archive sync takes 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_stream polls 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 doctor on 1.3 GB peaks at 22 MB, not 2.6 GB.
  • cv index parses on every core: a full counts recount of a 95 GB fleet went from 329 s to 61-68 s.
  • cv index rebuilds 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 show about 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 unchanged cv index detect 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 .jsonl beside 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: .jsonl sources may grow; truncation and rewrites still fail.
  • Large text spanned from a whole-document .json transcript fails its demand once the file changes, grown or not; only .jsonl logs 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 -shm or creates WAL sidecars: WAL stores open immutable=1 when frameless, otherwise through a private snapshot.
  • A copied or moved CLUSTERVISION_HOME keeps 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 --version build provenance recognizes linked worktrees and release archives.
  • Manual: search defaults and fallbacks, affordance views, MCP window exceptions, the calibrated estimator, --drop-thinking and the prompt-row schema read as the binary behaves.

Removals

  • Breaking: GET /api/backlog is removed (410 pointer); the inbox page reads /api/tasks?detail=1, which adds the backlog columns.
  • Breaking: POST /api/task/{id}/reopen is 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 at hooks_install.
  • Pre-binary embeddings.json semantic stores are no longer read or migrated (querying them already failed); cv index --semantic rebuilds.

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 | sh

Install 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