Your agent's memory needs the word 'no' — and a way to prove nobody edited it Selvedge, an append-only memory store for AI coding agents, released v0.3.11, adding a tamper-evident hash chain and a first-class 'reject' event type so agents can explicitly record abandoned paths and prove the log hasn't been altered. The update also ships an evaluator for expiration conditions, ensuring stored decisions know when they should be revisited. 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 tried and killed. that's been the selvedge pitch from the start: an append-only store where the agent logs its own reasoning at change time, so "we tried that and it broke revocation" survives the session that learned it. v0.3.10 solved delivery — the memory now arrives at session start instead of waiting to be asked "Delivery, Not Storage" arXiv:2607.20972 measured why that matters: zero voluntary memory operations across 114 turns against a pre-seeded store, while deterministic injection landed every time . v0.3.11 is about the two halves of the sentence " rejected paths, retained ." first: making the rejection itself first-class. second: making retained something you can check instead of something you take my word for. until now, selvedge could tell you a path was abandoned mostly by reading tea leaves: a remove event near an add , a proximity window, a heuristic confidence score. useful, but it's inference — and inference is exactly what this tool exists to replace with testimony. three changes make abandonment a stated fact: change type="reject". the counterpart to revert , for paths never taken. revert says "we tried this and rolled it back." reject says "we considered this and decided against it" — no code was ever written, and the decision is still worth a permanent record, because the next session's agent will consider it too. no new MCP tool; it's a value on the existing log change . prior attempts grows an exact tier. an attempt closed by an explicit reject or revert event now reports confidence: "exact" — the outcome is stated in the log, not inferred from proximity. the old heuristic drops to tiebreaker for attempts without a stated outcome. if you filter on confidence , accept exact alongside proximity high — rows move. expires when finally does something. a rejection stored without the condition that would invalidate it is data that silently rots. the column shipped dormant in v0.3.8; v0.3.11 ships the evaluator, with a deliberately closed grammar — four shapes, validated at write time, everything else rejected rather than stored: library:django =5.0 revisit when a named dependency reaches a version entity:users.email:changes revisit when that entity next changes date:2027-01-15 revisit on a date manual:security-review never auto-fires; a label for humans selvedge stale evaluates these locally — installed package metadata, the event log itself, and the clock. no network, no LLM, and no cleverness: a library: condition whose package isn't locally observable comes back as manual review , not a guess, and a pre-release never counts as its final release. a decision that knows when it should die is a decision you can still trust in a year. capture-time nudges close the loop: log a reject or revert with neither stale when nor expires when and the validator suggests recording the invalidating condition. it warns, never rejects — same posture as the secret-shape warnings. here's the uncomfortable question under the whole product: selvedge's differentiator is that rejected decisions stay on the record . append-only. permanent. queryable. and until this release, that was a promise about our own conduct — nothing in the store could show you a row was the row the agent wrote. v0.3.11 adds a tamper-evident hash chain. every logged event gets a SHA-256 chain record in a sidecar table, written in the same transaction, each record binding the previous one and its own sequence number. selvedge verify gains two checks: chain intact recomputes the whole thing and fails hard — naming the exact sequence number — when a chained row was edited, deleted, or reordered out-of-band. chain coverage warns never fails about rows that predate the chain: unchained, not invalid . the design problem worth a paragraph, because it generalizes past selvedge: some fields are late-bound. the commit SHA does not exist when the event is logged — the event describes the work that produces the commit, and a post-commit hook stamps it in afterward. a naive "hash the whole row" chain calls your own git hook an attacker. selvedge's cut: git commit sits outside the protected core, declared out loud in the attestation manifest — tolerable because a commit hash is independently checkable against git, which is itself a hash chain maintained by other software. everything else — reasoning, diff, entity path, change type, the supersede links — is chained. legitimate mutations don't break anything: migrate-paths appends an amend record, and the destructive-gated prune appends a tombstone accounting for what it deleted. a gated prune verifies clean; a silent sqlite3 "UPDATE events SET reasoning=..." does not. and the honest scope, which is in the module docstring and belongs in this post too: this detects casual and accidental modification, and produces an independently verifiable export. it is not proof against a motivated local attacker. there's no key. someone who controls the file can recompute every digest. what the chain buys you is that nobody edits the record casually — no script, no well-meant cleanup, no buggy future code path mutates a decision without the next selvedge verify naming the exact row. we ate the dogfood on day one: selvedge's own store crossed its chain genesis with this release, and the first chained rows are the decisions behind v0.3.11 — including a real change type="reject" for the retroactive chain seal command we decided not to ship, with the reasoning attached. selvedge supersede -d/--diff , --revisit-after , and --expires-when 31 — parity with selvedge log , through the same size-bound and secret-shape checks.numbers: tests 984 → 1114, coverage 89.4%. the chain checks are mutation-verified — delete the threading checks and the suite goes red. the resident MCP tool-surface cost moved this release 4445 tokens core, 2.2% of a 200k window because the rejection example and the expires when grammar now live in the tool docstrings agents actually see; that's the adoption bet, called out in the changelog. pip install -U selvedge . genesis is automatic on your first write; there's no migration and no new config keys. one expected surprise: selvedge verify will warn about your pre-existing rows chain coverage — they're unchained, not invalid, and the chain deliberately makes no claim about rows that predate it. CI stays green by default. pip install selvedge cd your-project selvedge setup detects claude code / cursor / copilot open source, MIT, local-first, zero LLM calls in the core. the thing i most want field reports on this time: log a real rejection with an expires when , wait for it to fire, and tell me whether the revisit that surfaced was worth your attention.