Skip to content

CLI reference

The selvedge binary is your interface to the SQLite store. Every read command supports --json for machine-readable output. Relative time strings (15m, 24h, 7d, 5mo, 1y) and ISO 8601 timestamps are accepted everywhere --since shows up.

selvedge init [--path PATH] Initialize Selvedge in a project
selvedge setup Interactive first-run wizard
selvedge prompt [--install FILE] Print canonical agent-instructions block
selvedge watch Live-tail new events
selvedge status Recent activity summary
selvedge doctor [--agent CLIENT] [--json] Health check
selvedge verify [--strict] [--json] Correctness checks against the store
selvedge diff ENTITY [--limit N] History for an entity / prefix
selvedge blame ENTITY Most recent change + context
selvedge history [...filters] Browse all history
selvedge changeset [ID|--list] Events grouped by changeset
selvedge search QUERY [--limit N] Full-text search
selvedge prior-attempts [ENTITY] Prior attempts on an entity + outcome ([--fuzzy TEXT] since 0.3.9.1)
selvedge supersede ENTITY --reasoning Re-open a reverted decision, append-only (since 0.3.9.1)
selvedge index [--model NAME] Build the optional semantic embeddings index (since 0.3.9.1)
selvedge stale [...filters] Decisions due for a revisit (date, stale_when, or expires_when since 0.3.11)
selvedge stats [--since SINCE] Tool-call coverage report
selvedge install-hook [--path PATH] Install git post-commit hook
selvedge backfill-commit --hash HASH Backfill git_commit on recent events
selvedge import PATH Import migrations (SQL / Alembic) or an Agent Trace file
selvedge import --from-git [--since] Seed pre-Selvedge reverts from git history (since 0.3.9.1)
selvedge export [--format json|csv|agent-trace|markdown] Export history (Agent Trace since 0.3.9, markdown since 0.3.10)
selvedge log ENTITY CHANGE_TYPE Manually log a change ([--constraint] [--stale-when] since 0.3.9.1, [--expires-when] since 0.3.11)
selvedge migrate-paths [--apply] Re-canonicalize stored entity paths
selvedge backup [--output FILE] Snapshot the store via VACUUM INTO
selvedge prune [--days N] Trim old tool_calls telemetry (90-day default)
selvedge prune --include-events Destructive events prune, double-gated (since 0.3.10)

Creates .selvedge/selvedge.db in the current directory (or --path). Writes the schema and registers the bootstrap migration. Idempotent — running on an existing project is a no-op.

Interactive first-run wizard. Detects which AI tools you have and walks through every install step in one pass:

  • Adds the Selvedge MCP entry to each tool’s config
  • Drops the canonical agent-instructions block into the project’s prompt file
  • Runs selvedge init if .selvedge/ doesn’t exist
  • Installs the post-commit hook

Every modified file gets a .bak written next to it before any change reaches disk. Re-running on an already-set-up project is a no-op (idempotent). Existing-but-different MCP entries trigger a conflict warning rather than silent overwrite — pass --force to overwrite, or update by hand.

For CI / devcontainer postCreateCommand:

Terminal window
selvedge setup --non-interactive --yes

Prints the recommended system-prompt block to stdout. Pipe-friendly:

Terminal window
selvedge prompt | tee -a CLAUDE.md

--install <file> installs the block idempotently. The block is sentinel-bracketed (<!-- selvedge:start --> / <!-- selvedge:end -->) so re-running --install updates the bracketed region without disturbing anything else in the file.

The block source is selvedge.prompt.PROMPT_BLOCK — a public constant, importable for templating into your own onboarding flows.

selvedge install-hook [--path PATH] [--window MIN]

Section titled “selvedge install-hook [--path PATH] [--window MIN]”

Installs a git post-commit hook that automatically backfills git_commit on Selvedge events after each commit. Safe to run on repos with existing post-commit hooks (appends rather than overwrites). Idempotent.

The hook’s --window (default 60 minutes) controls how far back it’ll look for events to associate with the commit.


Recent activity summary. Surfaces:

  • Total events
  • Events in the last 24h / 7d / 30d
  • Count of events missing git_commit (nudges you toward install-hook)
  • Last tool_calls entry timestamp (proxy for “is the agent wired up?”)
  • MCP wiring detection — distinguishes “installed but agent hasn’t reloaded” from “not installed anywhere” with config-path-aware diagnostics

Single-command health check. Each row is PASS / WARN / FAIL / INFO:

  • Which DB path is being resolved (and which precedence step matched)
  • Whether .selvedge/ exists where you think it does
  • Whether the schema is at the latest migration version
  • Whether the post-commit hook is installed
  • Whether the post-commit hook has been failing silently
  • Last tool_calls entry timestamp
  • Whether SELVEDGE_LOG_LEVEL is set to a recognized value

Exits 1 if any FAIL row is present so doctor can be wired into CI.

Add --agent claude-code|codex|cursor|copilot|gemini|windsurf to inspect that client’s project hook configuration, executable availability and bypass setting. Missing, malformed or customized entries include next steps. Configuration presence does not verify that the client has activated its hooks.

selvedge ledger [--entity PATH] [--limit N] [--json]

Section titled “selvedge ledger [--entity PATH] [--limit N] [--json]”

Read a consistent snapshot of recorded decisions with actor/session attribution, explicit revision links, counts and chain verification. Repeat --entity for multiple paths; omit it for the whole store. The default limit is 100 events (maximum 1,000); counts cover all matching events. The command does not create or migrate a database. Actor labels are self-reported.

See the shared ledger and PR review guide for same-host sharing and the optional GitHub Action. Authenticated remote access is not included.

Correctness checks against the Selvedge store, in two tiers:

  • must_fail — SQLite corruption, schema mismatch, invariant violations (empty entity_path, unknown change_type, bad timestamps), and — since v0.3.11 — chain_intact: an end-to-end recomputation of the tamper-evidence hash chain (hashes, prev_hash threading, sequence contiguity, tombstone accounting). A chained row that was edited, deleted, or reordered out-of-band fails this check, which names the exact sequence number. Nothing legitimate can trip it — migrate-paths --apply and a destructive-gated prune append boundary records instead of breaking the chain.
  • should_warn — soft signals (singleton changesets, events past the backfill window with no git_commit), plus chain_coverage (v0.3.11): a count of events rows with no chain record. Expected on every install upgraded from pre-0.3.11 — pre-existing rows are unchained, not invalid — so it warns without breaking CI.

Exit 0 when clean or only should-warn rows triggered; exit 1 on any must-fail row. --strict escalates should-warn rows to failures too, so you can wire selvedge verify into CI without || true on day one and promote the soft tier once the team is ready.

--json additionally carries chain_manifest (v0.3.11): the storage mechanism, chain algorithm, canonical-form version, verification-procedure reference, and a coverage declaration for the chain — the attestation fields SEP-3004 §2.7 describes, which Selvedge is assessed against (divergences documented; this is not a conformance claim). Honest scope, stated in the code itself: the chain detects casual and accidental modification of the log; it is not proof against a motivated local attacker, who controls the file and can recompute every digest.

selvedge watch [--interval N] [--since] [--entity] [--project] [--agent] [--json]

Section titled “selvedge watch [--interval N] [--since] [--entity] [--project] [--agent] [--json]”

Polls the SQLite store at --interval (default 1s) and prints each new event as it lands, Rich-formatted. Filters mirror selvedge history exactly. --json emits one compact JSON object per line for piping into jq. Ctrl-C exits cleanly.

WAL mode means the polling SELECT never blocks the writer; runtime cost is one indexed query per second while the command is running.

History for an exact entity (users.email) or prefix (users returns all users.*). Newest first. --json for machine-readable output.

Most recent change for an exact entity, plus full context — agent, timestamp, commit, reasoning. The query everyone runs first.

On miss, returns a stable shape with every field empty and error populated with the “no history found” message — easier to type-check without branching.

Browse all history with combined filters:

--since SINCE 15m | 24h | 7d | 5mo | 1y | ISO 8601
--entity ENTITY exact or prefix match
--project PROJECT limit to a project name
--changeset CS limit to a changeset
--summarize one-line-per-event summary instead of full panels
--limit N

Without an ID, lists all changesets with summary stats (event count, agent, time range). With an ID, returns the events grouped under that changeset, oldest-first so you can reconstruct the full scope of a feature.

Full-text search across reasoning + diff + entity_path. Properly escapes SQL LIKE wildcards (_, %, \) so selvedge search "stripe_customer_id" won’t accidentally match stripeXcustomerXid.

selvedge prior-attempts [ENTITY] [--description TEXT] [--fuzzy TEXT] [--all] [--window WHEN] [--limit N]

Section titled “selvedge prior-attempts [ENTITY] [--description TEXT] [--fuzzy TEXT] [--all] [--window WHEN] [--limit N]”

Prior change attempts on an entity, each with an inferred outcome — the CLI parity (new in v0.3.8) for the prior_attempts MCP tool, which shipped in v0.3.7. A thin presenter over the same get_prior_attempts store, so --json is byte-identical to what the MCP tool returns and the two surfaces can’t diverge.

Pass an ENTITY positional xor --description TEXT (free-text, when you don’t have an exact path) — not both. By default only high-confidence rows come back: attempts closed by an explicit revert or reject event report confidence: "exact" (v0.3.11 — the outcome is stated in the log, not inferred) and always clear the default floor, alongside the clear tried-then-reverted proximity cases (proximity_high). --all widens recall to proximity_low. --window (e.g. 7d, 60m) maps onto the add→remove proximity window, which since v0.3.11 is the tiebreaker for implicit removals only. Since v0.3.9.1 every row carries outcome (including reopened, and — v0.3.11 — rejected for a standalone rejection), current_status, and the supersede-trail fields, so the output reads tried → reverted → re-opened.

If you script against this output: rows that used to report proximity_high can now report exact, so a filter on proximity_high should accept exact too.

Terminal window
selvedge prior-attempts users.auth_token # exact entity
selvedge prior-attempts --description "persistent login token"
selvedge prior-attempts users.auth_token --all --json

--fuzzy TEXT (since v0.3.9.1) adds semantically similar records on top — it matches on the reasoning, not the entity string, so a rename from card_token to payment_token still trips the warning. Fuzzy rows are labeled match_type="fuzzy" with a similarity score. It needs the optional selvedge[semantic] extra and a selvedge index run; without them it falls back to substring matching and prints a one-line pointer. The core stays zero-LLM either way.

Terminal window
selvedge prior-attempts users.card_token --fuzzy "tokenized payment credentials"

An empty result is the normal, good answer — exit 0, nothing clearly tried-and-rejected. Records on the same coverage counter as the MCP tool, so selvedge stats reflects both surfaces.

selvedge supersede ENTITY --reasoning TEXT [-d/--diff TEXT] [--constraint TEXT] [--stale-when TEXT] [--expires-when COND] [--revisit-after WHEN] [--supersedes ID] [--json]

Section titled “selvedge supersede ENTITY --reasoning TEXT [-d/--diff TEXT] [--constraint TEXT] [--stale-when TEXT] [--expires-when COND] [--revisit-after WHEN] [--supersedes ID] [--json]”

Re-open a reverted decision — append-only, never rewrites history (v0.3.9.1). When the constraint that killed a decision no longer holds, this logs a new change_type="supersede" event that links the prior closing event (auto-resolving the entity’s most recent removal — remove / delete / index_remove / revert / reject — when --supersedes is omitted, so it also re-opens a standalone rejection). prior_attempts / blame / diff then read the full trail: tried → reverted → re-opened. There is deliberately no automatic un-retiring — this command is the explicit re-open step.

Three flags arrived in v0.3.11 (#31), with the same semantics as selvedge log: -d/--diff records the change that re-applies the decision (e.g. the migration), --revisit-after sets a revisit date for the re-opened decision, and --expires-when attaches a machine-checkable expiry condition for the new verdict (closed grammar, validated — see selvedge stale above for the four shapes). The storage layer accepted all three all along; the guided flow now records everything the raw selvedge log path could, and its diff and reasoning pass through the same size-bound and secret-shape checks as every other write path.

Terminal window
selvedge supersede payments.card_token \
-r "Provider now vaults card data — PCI constraint gone." \
-d "migrations/0042_readd_card_token.sql" \
--revisit-after 6mo --expires-when "entity:payments.card_token:changes"

Build or update the optional semantic embeddings index over reasoning text (v0.3.9.1) — the backing store for prior-attempts --fuzzy. Requires pip install "selvedge[semantic]". Incremental (only new or changed reasoning is re-embedded); the first run downloads the model (~30 MB, cached), and indexing + querying are fully local after that. The core store is untouched — embeddings live in their own table and nothing in Selvedge’s read/write paths depends on it.

selvedge stale [--entity ENTITY] [--project PROJECT] [--agent AGENT] [--limit N]

Section titled “selvedge stale [--entity ENTITY] [--project PROJECT] [--agent AGENT] [--limit N]”

Decisions that are due for a revisit — the CLI mirror of the stale_decisions MCP tool (new in v0.3.8). Each row’s flag names which rule surfaced it:

  • expired (v0.3.11) — the decision’s expires_when condition fired. expires_when is a machine-checkable expiry condition in a closed four-shape grammar — library:NAME>=VERSION (a named dependency reached a version), entity:PATH:changes (a named entity changed again), date:ISO (a date passed), manual:LABEL (an opaque label for human review) — validated at write time; values outside the grammar are rejected, not stored. Evaluation is local-only: installed package metadata, the event log itself, and the clock. No network, no LLM. expired_pattern names the grammar shape that fired.
  • manual_review (v0.3.11) — a library: condition whose dependency isn’t locally observable, presented for a human instead of guessed at. manual:LABEL conditions never auto-fire; they surface here too.
  • revisit_due — revisit_after has passed and the entity is still in active use (pure age never surfaces).
  • review_suggested (v0.3.9.1) — a later change event keyword-matched the decision’s stale_when condition (follow up with selvedge supersede).

Most-overdue-first. --json for cron / Slack / digest jobs.

Terminal window
selvedge stale # everything due: expired, past revisit date, or stale_when matched
selvedge stale --entity deps/stripe # scope to one entity
selvedge stale --json # for cron / morning reports

Each row carries the flag, the revisit due date, days overdue, the active-use signals that fired, any matched terms, the expiry status and pattern where relevant, and a templated reason. Composes with reporting jobs the same way selvedge digest (planned for v0.3.16) will.

Tool-call coverage report:

  • Per-agent breakdown — total calls, log_change calls, coverage ratio
  • Missing-reasoning count — events whose stored reasoning fails the quality validator (empty, too short, generic placeholder)
  • Recent call history

Catches the case where one agent (e.g. claude-code) is well-instrumented but another (e.g. cursor) is only querying history and never logging changes.


selvedge log ENTITY CHANGE_TYPE [--diff TEXT] [--reasoning TEXT] ...

Section titled “selvedge log ENTITY CHANGE_TYPE [--diff TEXT] [--reasoning TEXT] ...”

Manually log a change. Useful for backfilling, post-hoc annotation, or scripts. change_type is validated via click.Choice against the canonical set:

add | remove | modify | rename | retype | create | delete |
index_add | index_remove | migrate | revert | reject | supersede

Invalid types are caught at argument parsing with the full list of valid choices. revert (“we tried this and rolled it back”) and supersede (re-open a reverted decision) were added in v0.3.9.1 — for the guided re-open flow prefer selvedge supersede above. reject (v0.3.11) records “we considered this and decided against it” without writing the change — the counterpart to revert for paths never taken. Good reject reasoning names what was rejected and what was chosen instead; the validator nudges toward that, and suggests a --stale-when or --expires-when when a reject or revert lands without one — the reason a decision was made is also the condition under which it should die.

Other flags: --agent, --commit, --project, --changeset, --rename-from, --revisit-after, (v0.3.9.1) --constraint (the testable principle behind the decision) / --stale-when (what would invalidate it, matched by selvedge stale), and (v0.3.11) --expires-when (a machine-checkable expiry condition in the closed four-shape grammar — library:NAME>=VERSION, entity:PATH:changes, date:ISO, manual:LABEL — validated at write time and evaluated locally by selvedge stale).

Terminal window
selvedge log users.auth_token reject \
-r "Considered a DB-stored auth token; rejected — can't revoke without a write. Kept stateless JWTs." \
--expires-when "entity:src/auth/session.py:changes"

--revisit-after WHEN (new in v0.3.8) sets a revisit date on the change — an ISO-8601 date or a relative offset (90d, 6mo), normalized like --since. The decision then surfaces in selvedge stale / the stale_decisions MCP tool once the date passes and the entity is still in active use:

Terminal window
selvedge log deps/stripe add --reasoning "Pinned to v11 for billing." --revisit-after 180d

--rename-from OLD (with CHANGE_TYPE = rename, and the new path as ENTITY) records the dual-event rename — a rename on the old path and a create on the new path with metadata.renamed_from set — so history follows the entity:

Terminal window
selvedge log src/auth/session.py::login rename --rename-from src/auth.py::login

selvedge migrate-paths [--apply] [--agent NAME] [--json]

Section titled “selvedge migrate-paths [--apply] [--agent NAME] [--json]”

Re-canonicalizes every stored entity_path (strip leading ./, collapse //, normalize separators, trim — case preserved), so older rows written before v0.3.7 match the canonical form used on every write today.

Dry-run is the default — nothing is written until you pass --apply. A dry run prints the planned rewrites (old → canonical) and a collisions report: distinct pre-canonicalization paths that would converge to the same value, i.e. histories the migration would merge. Inspect those before applying.

Terminal window
selvedge migrate-paths # dry run: rewrites + collisions report
selvedge migrate-paths --apply # write the canonical paths

Idempotent on the event data — re-running after --apply rewrites nothing. Each --apply run records one audit row in the path_migrations table (surfaced by selvedge doctor).


selvedge import PATH [--format auto|sql|alembic|agent-trace] [--project NAME] [--dry-run]

Section titled “selvedge import PATH [--format auto|sql|alembic|agent-trace] [--project NAME] [--dry-run]”

Parse migration files and backfill schema history.

  • Raw SQL DDL — CREATE TABLE, ALTER TABLE ADD/DROP/RENAME/ALTER COLUMN, DROP TABLE, CREATE/DROP INDEX, RENAME TABLE
  • Alembic Python migrations — op.add_column, op.drop_column, op.create_table, op.drop_table, op.alter_column, op.rename_table, op.create_index, op.drop_index, op.execute() (with inline SQL parsing)
  • Agent Trace (--format agent-trace, since v0.3.9) — PATH is an Agent Trace JSON/NDJSON file. Round-trips a selvedge export --format agent-trace losslessly (entity, change type, and reasoning survive in dev.selvedge metadata); foreign producers import best-effort (change_type="modify", empty reasoning).

Directories are walked recursively; files are sorted by name for chronological order. Bulk inserts are wrapped in a single transaction so a long Alembic history imports atomically.

A CREATE TABLE users (id INT, email TEXT) emits a table.create event for users and a column.add event for each column, so selvedge blame users.email works even when the column was defined only in the initial schema.

--from-git [--since REF|DATE] (since v0.3.9.1) — instead of migration files, PATH is a git repository root (default .) and the import walks history for reverts that predate Selvedge: commits whose message mentions “revert” plus commits that deleted files. Each becomes a change_type="revert" event (agent="git-import", reasoning = the commit subject + body) so prior_attempts and the enforcement hook see them. Idempotent on the (commit, entity) pair, so re-runs don’t duplicate. --since takes a git ref (exclusive REF..HEAD) or a date git understands.

Terminal window
selvedge import --from-git --dry-run # preview what would be seeded
selvedge import --from-git --since v0.3.0 # only reverts after a tag

Honest limits: a revert folded into an unrelated commit (no “revert” marker, nothing deleted) is invisible even to the grep, and only SQL DROP TABLE / DROP COLUMN seed entity-level records today — every other diff shape is seeded at the file level. Widening that (and provenance-based trust tiers) is on the roadmap.

selvedge export [--format json|csv|agent-trace|markdown] [--since] [--entity] [--output FILE]

Section titled “selvedge export [--format json|csv|agent-trace|markdown] [--since] [--entity] [--output FILE]”

Dump change history to JSON or CSV with full filter support.

--format markdown (since v0.3.10) renders the store as a deterministic, human-readable digest — grouped by entity, reverted decisions first — designed to be committed next to .selvedge/ so captured intent is reviewable in a pull request. Regenerating with no new events produces a zero-line diff.

--format agent-trace (since v0.3.9) emits Agent Trace v0.1.0 records — one per change event by default, wrapped in a self-describing bundle ({agent_trace_version, producer, note, records: [...]}). Two agent-trace-only flags: --ndjson streams one record per line for large histories, and --collapse-by-session merges events sharing a session_id into a single record. Selvedge’s reasoning and entity-level provenance ride along in each record’s metadata under the reverse-domain dev.selvedge namespace. See the Agent Trace interop page for the full mapping.

selvedge backfill-commit --hash HASH [--window MIN]

Section titled “selvedge backfill-commit --hash HASH [--window MIN]”

Manually backfill git_commit on recent events within a configurable time window. Called by the post-commit hook automatically; you only run this manually if the hook isn’t installed and you want to associate a recent commit with already-logged events.


Snapshots the store with SQLite VACUUM INTO. Defaults to .selvedge/backups/selvedge-YYYYMMDD-HHMMSS.db, keeping the most recent 7 snapshots and pruning older ones (custom --output destinations outside that directory are never pruned). The backups directory is kept out of git — selvedge init (v0.3.5+) appends it to the project .gitignore, and the first selvedge backup run backfills that entry on older repos.

selvedge prune [--days N] [--include-events] [--event-days N] [--json]

Section titled “selvedge prune [--days N] [--include-events] [--event-days N] [--json]”

Trims old tool_calls telemetry rows only by default — the plain command never deletes change events. Telemetry retention defaults to 90 days (retention_days_tool_calls in config.toml); pass --days N to override. Every run appends a one-liner to .selvedge/prune.log so the cadence shows up in selvedge doctor.

--include-events (since v0.3.10) is the one path that can delete captured reasoning, so it is double-gated on purpose: it requires an interactive confirmation and SELVEDGE_DESTRUCTIVE=1 in the environment. Neither alone is enough — --yes in a cron entry defeats a prompt, and a shell profile defeats an env var. Events retention comes from retention_days_events (default 0 = never delete anything) or --event-days N, and each run is recorded in .selvedge/prune.log.

Terminal window
selvedge prune # trim tool_calls older than 90 days
selvedge prune --days 30
selvedge prune --json
SELVEDGE_DESTRUCTIVE=1 selvedge prune --include-events --event-days 730

--since and other time-flavored flags accept:

FormMeaning
15mLast 15 minutes (m = minutes)
24hLast 24 hours
7dLast 7 days
5mo or 5monLast 5 months (mo / mon = months)
1yLast year
2026-04-22T10:00:00ZISO 8601 timestamp (any tz, normalized to UTC)

Unparseable inputs (e.g. --since yesterday) exit with a clear error rather than silently returning empty results.

m means minutes, not months. This was a v0.3.0 fix — earlier versions used m for months, which contradicted every CLI convention (sleep 5m, kubectl --since=5m, Prometheus). If you’re on v0.2.x, 5m will read as five months — upgrade.


VariablePurpose
SELVEDGE_DBForce a specific DB path (per-session override)
SELVEDGE_LOG_LEVELDEBUG / INFO / WARNING / ERROR (default WARNING)
SELVEDGE_QUIET=1Suppress the global-fallback stderr warning
SELVEDGE_DESTRUCTIVE=1Second gate for prune --include-events — the only command that can delete events (v0.3.10)

Configuration page → for full details on DB resolution and project / global precedence.