for the last few releases selvedge has been a write-only thing. an AI agent edits a column, calls log_change, and the reasoning gets captured into a little SQLite file under .selvedge/ next to your code. useful, but one-directional. the agent wrote to the store and then never looked at it again.
v0.3.7 is the release where that flips. there's a new MCP tool called prior_attempts, and the whole point of it is that the agent calls it before it changes something — not after. before editing users.email, the agent asks selvedge: what happened the last time someone touched this? who tried what, when, and why was it reverted?
this is the brand-defining release, so let me walk through why it's built the way it is.
the default flow used to be one-directional
here's what selvedge looked like through v0.3.6. an agent refactors users.email, and on its way out it calls log_change with a reasoning string — "tightened the email validator to reject plus-addressing per the billing team's request." that reasoning lands in the store. later, a human runs selvedge blame users.email from the CLI and reads it back.
that's the same posture every code-attribution tool has. git blame, AgentDiff, Origin, all of them. the reasoning is captured-then-read. the capture is one event in time; the read is a separate, asynchronous act by a different actor — usually a person, usually weeks later, usually because something already broke.
for a human-written codebase that's fine. people carry context between sessions. they remember "oh right, we tried that in march." the audit trail is a backup for the memory, not the memory itself.
prior_attempts is the asking primitive
so v0.3.7 adds the tool that does the asking. it's pull-model — the agent calls it, selvedge doesn't push. given a description or an entity_path, it returns prior change events at the same path (or the same shape) with their reasoning, their change_type, and an inferred outcome.
> prior_attempts(entity_path="users.email")
2 prior attempts at users.email:
[2026-03-14] rename -> users.email_address (reverted 2026-03-17)
reasoning: "renamed for consistency with users.phone_number"
outcome: reverted — billing reconciliation join keyed on the
literal column name; case-sensitivity broke the nightly job
[2026-01-22] alter -> tighten type to citext (abandoned)
reasoning: "case-insensitive comparison at the db layer"
outcome: no follow-up commit; superseded by app-layer validation
the agent now has the context march-mason wrote down. and it does the thing the whole release is built to enable: it changes plan. it doesn't rename the column. it keeps users.email, hardens the validator at the app layer instead, and notes in its own log_change that it checked prior_attempts first. the loop closes.
no LLM call happens anywhere in there, by the way. the output is templated — selvedge assembles it deterministically from the rows in SQLite. that's a hard rule in the core: no model hops, no API key, no network, no non-determinism that's miserable to test. prior_attempts is a query and a string template, nothing more.
why the trust budget is non-negotiable
here's the design decision i went back and forth on the most. prior_attempts returns high-confidence results only by default.
the way it infers "outcome" in v0.3.7 is by proximity: an add followed closely by a remove of the same entity reads as "tried and pulled back." that's a heuristic, and heuristics have a noisy tail. so every result carries a confidence field — proximity_high or proximity_low — and the default response gives you proximity_high only. if you want the noisy long tail you have to explicitly ask for it with min_confidence="proximity_low".
the reason is the trust budget, and you only get one shot at it. the moment prior_attempts hands an agent a confident-looking false positive, the agent learns the tool is noise and starts ignoring it — and a tool the agent ignores is dead. an empty result is recoverable. a wrong result is not. so the default is tuned to prefer returning nothing over returning something wrong.
this is the same discipline as selvedge verify's two-tier exit codes from v0.3.5: build the strict floor first, then give the caller a knob to relax it if they want. conservative defaults are what keep the mechanism honest. the precise classifier comes later — v0.3.11 ships explicit reject and revert change_types so the high-confidence tier gets more accurate without the API changing at all.
the entity foundation that had to ship in the same release
you cannot ship a lookup tool when ./src/auth.py::login and src/auth.py::login store as two different entities. if they do, the recall problem masquerades as "prior_attempts didn't find anything," the agent proceeds confidently, and the tool has actively misled it — worse than not existing. so the canonicalization had to land first, inside the same release.
canonicalization on write is deterministic and boring on purpose: strip a leading ./, collapse //, normalize separators to /, trim whitespace. one chokepoint that both the MCP write path and the CLI go through, so nothing writes a non-canonical path after this release.
the one decision worth calling out: case is preserved, deliberately. filesystems disagree — macOS and windows are case-insensitive by default, most linux hosts are case-sensitive. if selvedge silently lowercased everything, it would collapse two entities that are genuinely distinct on a case-sensitive host. so it preserves case, and selvedge doctor grows a should-warn row that flags sibling paths differing only by case, so you find out instead of getting a silent merge.
for existing databases there's selvedge migrate-paths. it's --dry-run by default — you have to pass --apply to write anything — and the dry run prints a collisions report showing which pre-canonicalization paths would converge to the same value. you get to look at the merge before it happens. it's idempotent, and every run drops an audit row into a new path_migrations table so the operation stays visible.
and the explicit non-goal, because it comes up every time: no code parser, no AST. selvedge does not read your source and extract entities. it stores what the agent tells it, canonicalized and queryable. AST work is language-specific, drags in a pile of dependencies, and fights the dependency-free-core rule that keeps the install at three deps. it's a line i'm holding.
what the prompt block changes
a new MCP tool that nobody calls is worthless, and agents don't spontaneously discover query tools. so the flywheel is one line in the agent's instruction block. selvedge prompt now leads with:
Before editing an entity, call
prior_attemptson it.
that's the first instruction now, not a footnote under the log_change guidance. it's what turns write-only agents into read-then-write agents. without it, the whole release is a tool sitting in a listing that never fires.
existing users get the change cleanly because the prompt block is sentinel-bracketed (the <!-- selvedge:start --> / <!-- selvedge:end --> markers from v0.3.4). run selvedge prompt --install CLAUDE.md and only the bracketed region updates — the rest of your prompt file is untouched.
this brings the MCP surface to 7 tools. exactly one new tool this release — rename support folded into log_change as a parameter rather than its own tool, and the aggregate-digest helper shipped as a plain library function instead of a tool. keeping the surface small is its own discipline.
what's next
v0.3.7 makes the wedge legible. the next releases make it visible where you already work:
-
v0.3.9 brings the developer-facing CLI surface:
selvedge audit(flags PRs that changed code without logging why),selvedge digest(summarizes recent activity per entity),selvedge pr-comment(drops the relevantprior_attemptshits straight into a PR review). -
v0.3.11 ships explicit
reject/revertchange_types so the proximity classifier becomes a precise one. - v0.4.0 lifts the whole thing onto an optional HTTP + Postgres layer so teams stop fighting per-machine SQLite — still self-hosted, still never sending your data anywhere.
if you want to try the read-before-you-edit flow:
pip install selvedge==0.3.7
selvedge setup # detects claude code / cursor / copilot
then point your agent at a repo with some history and ask it to refactor something you've touched before. watch it call prior_attempts and think twice.
changelog: https://github.com/masondelan/selvedge/releases/tag/v0.3.7
(a 60-second clip of claude code calling prior_attempts mid-task and changing course is coming — i'll add it to this post once it's up.)
it's open source, MIT, runs entirely on your machine. happy to answer anything about the proximity classifier or the canonicalization rules in the comments.
Top comments (0)