"I'm 99% sure that grep won't find your commit because you rejected 'oauth-library' and grepping for 'auth' rejection. Given that LLM will make up category name, it will just get worse unless there is deterministic enforcement."
let me be honest about where that quote comes from, because it's not an
endorsement. it's one commenter's adversarial objection on the Contextual
Commits thread — a thread that is, overall, skeptical of structured capture,
including the kind selvedge does. i'm borrowing an objection, not a blurb.
but it's worth borrowing, because even the critics of structured capture
agree on what the failure mode is: nondeterministic labels. if a model
invents the category name at capture time, the search misses at read time,
and grep never saves you. hold that thought — the whole release is downstream
of it.
the failure that actually costs money
here's a transcript i keep showing people
(the full version is checked into the repo).
months ago, an agent session added a users.auth_token column — a
long-lived per-user token so the mobile app could stay signed in across
restarts. two days later the team reverted it. the revert reasoning, captured
at the time: tokens in the DB meant revocation required a write, so they
moved to short-lived JWTs verified statelessly. decision made, price paid,
session over.
months later, a new session, zero shared context. the user asks: mobile
users get logged out every time they reopen the app — can we keep them
signed in? the agent's first instinct is the obvious one. add a persistent
auth token column to users.
it's a plausible plan. it's delivered confidently. and it walks straight
back into a decision the team already paid to learn — the incident, the
revert, the migration, all of it, about to be purchased a second time.
that's the failure this product is aimed at. the expensive failure in
AI-assisted coding isn't a bad line of code — linters and tests catch those.
it's an agent confidently re-implementing something your team already
killed. the knowledge existed. somebody wrote it down, even. it just lived
in a chat session that ended.
in the demo, the story goes the other way: the agent calls prior_attempts
on users.auth_token before writing the migration, gets back the reverted
verdict with the reason it was rejected, and pivots — refresh token in the
OS keychain, stateless /auth/refresh endpoint, no rejected column
reintroduced. then it logs its own decision so the next session inherits
that too.
what makes that answer possible is the shape of the store: append-only
testimony. the reasoning is what the agent itself wrote, at change time, in
the same context that produced the change — and a rejection is a permanent,
queryable record. both halves matter. the post-hoc explainer tools ask a
second LLM to look at your diff later; a model that never saw the original
prompt produces a paraphrase, and re-running it can produce different
category names — which is exactly 0x457's objection. and OpenLore, which
shares selvedge's zero-LLM determinism, purges rejected decisions from its
queryable store after sync — an annotation survives in the synced spec
markdown, but the queryable record doesn't. none of the line-attribution
tools can answer the question the agent actually had: has this been tried
before, and how did it turn out? the rejected path staying queryable is the
wedge.
the part i had wrong: storage isn't delivery
v0.3.7 shipped prior_attempts, the asking primitive. it works — when it's
called. that "when it's called" turns out to be doing an enormous amount of
work, and this year two independent papers put a number on it:
"Delivery, Not Storage" (arXiv
2607.20972) and PROJECTMEM (arXiv
2606.12329) both measured pull-model memory tools going unused — zero
voluntary memory operations across 114 turns against a pre-seeded store,
while deterministic injection landed every time.
zero. across 114 turns. with relevant memory sitting right there.
a memory the agent has to remember to consult is a memory that mostly
doesn't get consulted. selvedge already had the gate half of the answer (the
PreToolUse hook that fires when an agent touches a watched entity). what was
missing was delivery when there's nothing to veto. so v0.3.10's headline
theme is that the memory now comes to the agent:
SessionStart digest. a hook injects a compact, relevance-gated digest as
the session begins: decisions due for revisit, entities whose standing
verdict is reverted, recent changesets. the march revert is in front of
the june agent before it plans anything — no tool call required, no
remembering to ask. it's quiet when there's nothing to say, size-capped via
digest_max_bytes, read-only, fail-open, and templated like everything else
in the core.
PreCompact. context compaction is where a session's reasoning goes to
die, so selvedge now fires right before it and names the watched entities
that were edited this session with no log_change recorded — subtracting
what's already in the store. this one is advisory only, deliberately: the
hook API allows blocking, and selvedge doesn't use it. blocking a tool call
inconveniences an agent; blocking compaction wedges the whole session.
there's a test asserting we never do it.
selvedge export --format markdown. a deterministic, stably-anchored
digest of the store, grouped by entity with reverted decisions first,
designed to be committed next to .selvedge/ so captured intent is
reviewable in an ordinary diff. regenerating with no new events produces a
zero-line diff. zero LLM calls, and — notably — no new MCP tool.
that last point generalizes: the entire delivery half shipped without
touching the tool surface. still 8 MCP tools, schema tax unchanged at
3705/3800 tokens. delivery didn't cost the agent a single extra token of
tool schema.
the config half
the second theme: the store gets its dials. .selvedge/config.toml is now
first-class, with the full key set — retention_days_events (default:
never), retention_days_tool_calls, backup_keep_last, diff_bytes,
reasoning_bytes, db_size_warn_mb, stale_days, digest_max_bytes,
redaction_patterns — and one canonical precedence chain: CLI flag > env
var > project config > global ~/.selvedge/config.toml > default
(SELVEDGE_DB is the one exception and always wins for database
resolution). selvedge doctor prints the effective value and which
precedence step produced it, per setting, so "why is this 30?" has a
one-command answer.
two entries deserve a paragraph each:
selvedge prune --include-events is the first code path that can delete
captured reasoning, so it's double-gated: an interactive confirmation AND
SELVEDGE_DESTRUCTIVE=1 in the environment, plus an audit line in
.selvedge/prune.log. neither gate alone is enough — a --yes in a cron
entry defeats a prompt, and a line in a shell profile defeats an env var.
you need both, on purpose.
secret-shape warnings at log_change: a conservative built-in set —
vendor-prefixed keys, PEM headers, bearer tokens, SECRET= assignments,
credentialed connection strings — extendable via redaction_patterns. it
warns, never rejects; losing the reasoning to an overzealous filter is the
exact loss this tool exists to prevent. and since a write-time check can't
see the past, doctor grew a scan of what's already stored. (event-size
bounds follow the same philosophy: oversized diffs and reasoning truncate
loudly, with a marker, a warning, and a count in selvedge stats.)
and quickly, from the fixed column: the PreToolUse hook's allow path is ~40%
faster — p50 33.6 ms → 20.1 ms per gated call against a 14 ms interpreter
floor, measured n=60 interleaved; the hook's own logic was always 0.58 ms
and the rest was import cost. SELVEDGE_HOOK_DISABLE=1 now actually
short-circuits (it was being checked after the imports had already run).
log_change no longer silently discards revisit_after, constraint, or
stale_when on the rename and supersede branches. the docker image no
longer ships my own database (yes, really). the CLI and the MCP server now
return identical structures through one shared presenter layer. and a
mutation pass found ~12 deliberate guards that were executed but never
asserted — mutation score on the sampled set went 64% → 100%. the suite is
826 → 984 tests, coverage 88.3% → 89.0%.
if pip install selvedge broke for you in late july
two separate things happened, and both are closed:
-
mcp2.0.0 (released 2026-07-28) removedmcp.server.fastmcp, and selvedge declaredmcp>=1.0.0with no upper bound — so every fresh install from 2026-07-28 to 2026-08-01 resolved 2.0.0 and died at import. v0.3.9.3 pinnedmcp<2.0.0. - the official MCP Registry's latest-resolution is semver-shaped and
couldn't rank four-segment PEP 440 releases above
0.3.9— so?version=latestkept serving 0.3.9, which is precisely the release that dies at import under mcp 2.0.0. three-segment0.3.10is the durable correction. as of today the registry serves 0.3.10 as latest — verified.
so if you tried selvedge in late july and it fell over at import: that was a
real window, and it's closed. pip install -U selvedge gets you 0.3.10.
try it
selvedge is decision provenance for AI-coded codebases: the why, and what
was already tried and rejected.
pip install selvedge
cd your-project
selvedge setup # detects claude code / cursor / copilot
then work a session, end it, and start another. the second session opens
with a digest of what the first one decided — including anything that got
reverted, before the new agent proposes it again.
- site: selvedge.sh
- repo: github.com/masondelan/selvedge
- pypi: pypi.org/project/selvedge
- changelog: v0.3.10 release notes
- the demo transcript: docs/demos/prior-attempts.md
it's open source, MIT, local-first, zero LLM calls in the core. the thing i
most want field reports on is the SessionStart digest's relevance gating —
if it ever surfaces something useless at the top of your session, that's an
issue i want to hear about.
Top comments (1)
The storage-versus-delivery distinction is the part I’d test most aggressively. A useful regression fixture could seed a reverted decision, start a fresh session with the SessionStart hook enabled, and compare three cases: deterministic injection, voluntary retrieval, and no memory delivery. Track whether the agent avoids the rejected path, digest bytes added, false-positive digests, and time-to-first-tool-call. That turns “the memory was available” into a measurable delivery contract.
I’d also add a race test around the digest boundary: update or supersede a decision while a new session is starting, then assert the digest is internally consistent and names the effective policy version. For PreCompact, the advisory-only choice makes sense, but the warning should be idempotent across repeated compactions and clearly distinguish “edited entity with no log” from “log exists but was truncated.” Those invariants seem more valuable than another retrieval tool because they protect the handoff between sessions without relying on the model to remember the protocol.