Repository navigation
feat: memory lifecycle — status, expiry, history and conditional saves (#33) - #45
Merged
Merged
Conversation
#33) A save no longer overwrites blind or loses what it replaces: - It reads the current version, copies it verbatim (still signed by its own author) to `archive/<type>/<slug>/v<n>`, links it as `supersedes`, and writes conditionally (put/0.3 `expectedVersion`) on the version it read. A writer that got in first wins; the losing save comes back as SaveOutcome::Conflict carrying the checked winner. Callers that read earlier pass that `version` as `expectedVersion` (0 = must be new). - History keys sit outside every `<type>/` prefix, so recall never sees them; at most five versions are kept per memory. `memory_get` with `history: true` returns them, each verified against its original key. Records gain a signed lifecycle: `status` (active | deprecated | disputed | retracted) with a required `statusReason`, `validUntil` and `reviewAfter`. Recall leaves out retracted and expired memories, ranks deprecated and disputed ones at half weight, and flags `stale` ones past their review date. `memory_list` still shows everything, and `stale: true` narrows it to what needs review. New tools: memory_dispute, memory_deprecate, memory_retract. Retract leaves a content-free tombstone; it is the only status change allowed on a refused record. `memory_forget` stays the hard delete and now removes the history too. `/memories --stale` walks the user through review. Bumps the plugin to 0.6.0. Closes #33 Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #33.
What changes
Saves no longer overwrite blind, and nothing they replace is lost.
Store::save_versioned:expectedVersion(from a recall/get;0= must be new) and it doesn't match, writes nothing and returns a conflict;archive/<type>/<slug>/v<n>and links it assupersedes;put/0.3 expectedVersion) on the version from step 1 — a writer that landed in between wins, and the losing save returnsSaveOutcome::Conflictwith the checked winner (a forged winner is aRefused, not anEntry);History keys are outside every
<type>/prefix, so recall and typed lists never reach them.memory_getwithhistory: truereturns them, each verified against the key it was originally written to.Lifecycle fields, all signed:
status(active|deprecated|disputed|retracted) + requiredstatusReason,validUntil,reviewAfter,supersedes.stalepastreviewAfter.memory_listshows everything with its status;stale: truenarrows to due-for-review/expired.New MCP tools:
memory_dispute,memory_deprecate,memory_retract(each takes a reason and optionalexpectedVersion). Retract leaves a content-free tombstone (content survives only in history) and is the only status change allowed on a refused record — anything else would re-sign content nobody vouches for. A status change is a new version signed by whoever made it; the original author's version is in history.memory_savegainsexpectedVersion,validUntil,reviewAfter; a conflict returnssaved: falsewith the current version and value (fenced).memory_forgetstays the hard delete and now removes history too./memories --stalewalks the user through review;/forgetoffers retract.Skill, commands, README, CLAUDE.md updated. Plugin and crate → 0.6.0.
Acceptance
a_replacing_save_keeps_the_previous_version_in_history,disputed_and_deprecated_memories_are_demoted_not_hidden,expired_memories_drop_out_and_stale_ones_are_flagged).a_save_based_on_a_stale_read_is_rejected_with_the_winner,a_concurrent_save_in_the_read_write_window_is_rejected_by_the_vta; the fake keyspace now implementsexpectedVersionandversionConflictas the VTA does).retracting_leaves_a_tombstone_and_forgetting_removes_everything).Notes
put/0.3expectedVersion/versionConflictas specified (VTI#2115).Testing
cargo fmt,cargo clippy --all-targetsclean; 102 unit + 41 roundtrip + binary/hook/stdio suites pass.