{"slug": "your-agent-s-memory-needs-the-word-no-and-a-way-to-prove-nobody-edited-it", "title": "Your agent's memory needs the word 'no' — and a way to prove nobody edited it", "summary": "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.", "body_md": "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.\n\nv0.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).\n\nv0.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.\n\nuntil now, selvedge could tell you a path was abandoned mostly by reading tea leaves: a `remove`\n\nevent near an `add`\n\n, a proximity window, a heuristic confidence score. useful, but it's inference — and inference is exactly what this tool exists to replace with testimony.\n\nthree changes make abandonment a stated fact:\n\n** change_type=\"reject\".** the counterpart to\n\n`revert`\n\n, for paths never taken. `revert`\n\nsays \"we tried this and rolled it back.\" `reject`\n\nsays \"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`\n\n.** prior_attempts grows an exact tier.** an attempt closed by an explicit\n\n`reject`\n\nor `revert`\n\nevent now reports `confidence: \"exact\"`\n\n— 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`\n\n, accept `exact`\n\nalongside `proximity_high`\n\n— 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:\n\n```\nlibrary:django>=5.0      # revisit when a named dependency reaches a version\nentity:users.email:changes   # revisit when that entity next changes\ndate:2027-01-15          # revisit on a date\nmanual:security-review   # never auto-fires; a label for humans\n```\n\n`selvedge stale`\n\nevaluates these locally — installed package metadata, the event log itself, and the clock. no network, no LLM, and no cleverness: a `library:`\n\ncondition whose package isn't locally observable comes back as `manual_review`\n\n, 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.\n\ncapture-time nudges close the loop: log a `reject`\n\nor `revert`\n\nwith neither `stale_when`\n\nnor `expires_when`\n\nand the validator suggests recording the invalidating condition. it warns, never rejects — same posture as the secret-shape warnings.\n\nhere'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.\n\nv0.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`\n\ngains two checks: `chain_intact`\n\nrecomputes 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`\n\nwarns (never fails) about rows that predate the chain: *unchained, not invalid*.\n\nthe 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`\n\nsits 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`\n\nappends an `amend`\n\nrecord, and the destructive-gated prune appends a `tombstone`\n\naccounting for what it deleted. a gated prune verifies clean; a silent `sqlite3 \"UPDATE events SET reasoning=...\"`\n\ndoes not.\n\nand 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`\n\nnaming the exact row.\n\nwe 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\"`\n\nfor the retroactive `chain seal`\n\ncommand we decided *not* to ship, with the reasoning attached.\n\n`selvedge supersede`\n\n`-d/--diff`\n\n, `--revisit-after`\n\n, and `--expires-when`\n\n(#31) — parity with `selvedge log`\n\n, 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`\n\ngrammar now live in the tool docstrings agents actually see; that's the adoption bet, called out in the changelog.\n\n`pip install -U selvedge`\n\n. genesis is automatic on your first write; there's no migration and no new config keys. one expected surprise: `selvedge verify`\n\nwill *warn* about your pre-existing rows (`chain_coverage`\n\n) — they're unchained, not invalid, and the chain deliberately makes no claim about rows that predate it. CI stays green by default.\n\n```\npip install selvedge\ncd your-project\nselvedge setup        # detects claude code / cursor / copilot\n```\n\nopen 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`\n\n, wait for it to fire, and tell me whether the revisit that surfaced was worth your attention.", "url": "https://wpnews.pro/news/your-agent-s-memory-needs-the-word-no-and-a-way-to-prove-nobody-edited-it", "canonical_source": "https://dev.to/masondelan/your-agents-memory-needs-the-word-no-and-a-way-to-prove-nobody-edited-it-2kg8", "published_at": "2026-08-31 03:08:21+00:00", "updated_at": "2026-08-31 03:21:28.813521+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-infrastructure"], "entities": ["Selvedge"], "alternates": {"html": "https://wpnews.pro/news/your-agent-s-memory-needs-the-word-no-and-a-way-to-prove-nobody-edited-it", "markdown": "https://wpnews.pro/news/your-agent-s-memory-needs-the-word-no-and-a-way-to-prove-nobody-edited-it.md", "text": "https://wpnews.pro/news/your-agent-s-memory-needs-the-word-no-and-a-way-to-prove-nobody-edited-it.txt", "jsonld": "https://wpnews.pro/news/your-agent-s-memory-needs-the-word-no-and-a-way-to-prove-nobody-edited-it.jsonld"}}