Skip to content

Configuration

Selvedge has very few knobs by design. Since v0.3.10 the settings live in a first-class .selvedge/config.toml (see below), with environment variables and CLI flags layered on top of it in a canonical precedence order. v0.3.11 adds no new keys — the surface below is current.

Three sources, in order:

  1. SELVEDGE_DB environment variable — explicit override. Wins everything.
  2. Walk-up from CWD — the first .selvedge/selvedge.db found by walking up the directory tree from where selvedge (or the MCP server) was launched.
  3. ~/.selvedge/selvedge.db — global fallback. A one-time stderr warning is printed when this path is selected; suppress with SELVEDGE_QUIET=1.

get_db_path requires the DB file to exist, not just the .selvedge/ directory. A stray empty .selvedge/ upstream won’t hijack resolution.

selvedge.config.resolve_db_path() returns both the resolved path AND which precedence step matched (env, walkup, or global). selvedge doctor uses this so the “which DB are we using?” answer is unambiguous.

VariableDefaultEffect
SELVEDGE_DBunsetPath to a specific SQLite file. Overrides walk-up + global.
SELVEDGE_LOG_LEVELWARNINGDEBUG / INFO / WARNING / ERROR. Controls the selvedge.* logger namespace.
SELVEDGE_QUIETunsetIf set, suppresses the one-time stderr warning when the global fallback DB is used.
SELVEDGE_HOOK_DISABLEunsetv0.3.9.1. If 1, the PreToolUse enforcement hook (selvedge-hook pretooluse) bypasses and allows every tool call for that shell.
SELVEDGE_DESTRUCTIVEunsetv0.3.10. Required for the one command that can permanently delete events (selvedge prune --include-events) — it must be set in the environment AND the command confirmed at the prompt. Neither gate alone is enough.

Enforcement-hook config — .selvedge/config.toml [hook] (v0.3.9.1)

Section titled “Enforcement-hook config — .selvedge/config.toml [hook] (v0.3.9.1)”

The PreToolUse enforcement hook installed by selvedge setup reads one optional block. If the file or block is absent, sensible defaults apply; a malformed file falls back to the defaults rather than failing.

[hook]
# Which paths the hook treats as schema/migration territory. Replaces the
# defaults: **/migrations/**, **/alembic/**, **/schema*, **/*model*, **/*.sql
watch_globs = ["**/migrations/**", "db/**/*.sql"]

The [hook] block is deliberately separate from the first-class settings below — the hook cannot afford the config layer’s import cost on its hot path, so it reads this one block directly.

pip install "selvedge[semantic]" (v0.3.9.1) adds the local embedding backend (model2vec) used by selvedge index and prior-attempts --fuzzy. It is strictly optional — the core never imports it, and Selvedge works identically without it (fuzzy queries fall back to substring matching).

The destructive-action opt-in (v0.3.10) is a deliberate footgun defense — the most common way event-deleting commands get triggered by accident is through --yes flags in cron jobs or non-interactive scripts. SELVEDGE_DESTRUCTIVE=1 is the second factor that prevents that, gating selvedge prune --include-events behind both the env var and an interactive confirmation: --yes in a cron entry defeats a prompt, and a shell profile defeats an env var, so it takes both.

Terminal window
cd your-project
selvedge init

Creates .selvedge/selvedge.db in the current directory, writes the schema, and records the bootstrap migration. This is the canonical place to put a DB.

.selvedge/ lives next to your code, not in a hidden home folder, so:

  • It’s checked-out alongside the project (or .gitignored, your call)
  • Walk-up finds it from any subdirectory
  • Multiple projects on the same machine don’t share a DB unintentionally

All library modules log under the selvedge.* namespace. Entry points (selvedge CLI, selvedge-server MCP) call configure_logging() once at startup.

Set SELVEDGE_LOG_LEVEL=DEBUG to see:

  • DB connection lifecycle
  • Migration runs
  • Tool-call telemetry writes (and any swallowed errors from telemetry — these never crash the parent tool, but they do log under DEBUG)
  • WAL pragma application

record_tool_call() exception handling still swallows errors so telemetry failures never crash the parent tool, but everything routes through logger.exception("…") so the failure is visible at DEBUG.

You probably don’t want to change these, but for reference:

SettingDefaultWhere
WAL modeonSet on every connection
busy_timeout5000msPRAGMA busy_timeout on every connection
Connection-with-retry attempts5selvedge.storage._with_retry
Backoff cap1sexponential, capped

The defaults are tested via tests/test_concurrency.py — 8 threads writing 25 events each, all 200 land. If you push past that and start seeing contention, the right escalation is the v0.4.0 PostgreSQL backend, not raising these knobs.

.selvedge/
├── selvedge.db The SQLite DB (WAL mode → also -wal, -shm next to it)
├── config.toml Optional project config (v0.3.10; [hook] block since v0.3.9.1)
├── backups/ Rotated snapshots from selvedge backup (kept out of git)
├── hook.log Post-commit hook failure log (one line per failure)
├── prune.log One line per prune run, surfaced by doctor
└── selvedge.db-journal Transient — only present during a transaction

You can .gitignore everything except .selvedge/.gitkeep if you want fresh DBs per checkout — useful for monorepos where each microservice maintains its own history. For most projects, committing the DB is fine; SQLite + WAL handles concurrent CI checkouts well.

First-class project config, since v0.3.10: read on every entry point, backwards compatible (a missing file means the defaults below). Keys are flat and top-level; each also has an env-var override (SELVEDGE_<KEY> uppercased, e.g. SELVEDGE_RETENTION_DAYS_EVENTS):

KeyDefaultPurpose
retention_days_events0 (never)Event retention for prune --include-events (opt-in deletion — losing captured reasoning is the one thing this tool exists to prevent)
retention_days_tool_calls90Telemetry retention for selvedge prune
backup_keep_last7Snapshots kept by selvedge backup
diff_bytes65536Per-event diff truncation limit (truncates loudly, with a marker)
reasoning_bytes32768Per-event reasoning truncation limit
db_size_warn_mb500Doctor warns past this DB size
stale_days0 (off)Fallback age for stale_decisions when a decision has no revisit_after
digest_max_bytes4096Hard cap on the SessionStart digest injected into agent context (0 disables the digest)
redaction_patterns[]Extra secret-shaped regexes to warn about at log_change time — extends the built-in set

(The v0.3.11 tamper-evidence chain and expires_when evaluator run with zero configuration — no keys govern them.)

Precedence (canonical): SELVEDGE_DB always wins for DB-path resolution. For every other setting: CLI flags > env vars > project-local .selvedge/config.toml > global ~/.selvedge/config.toml > hardcoded defaults. selvedge doctor prints which precedence step produced each effective setting.

By design:

  • No remote backends in v0.3.x. Postgres is on the v0.4.0 roadmap. Until then, Selvedge is local-only. This keeps the install footprint to three deps and the attack surface to zero network.
  • No LLM hops in core. The reasoning quality validator is regex-and-length-based, not LLM-based. This is a hard rule; PRs that try to add LLM calls inside core get rejected.
  • No telemetry that leaves your machine. The tool_calls table is local-only. There’s no opt-in or opt-out — the data simply doesn’t go anywhere.

CLI reference → — every flag, every subcommand. FAQ → — common gotchas and “why does it work this way”.