Skip to content

memory: let a user see and edit what dirge remembers - #3 - #814

Merged
yogthos merged 3 commits into
dirge-code:mainfrom
wayniacal:feat/memory-browse
Aug 24, 2026
Merged

memory: let a user see and edit what dirge remembers - #3#814
yogthos merged 3 commits into
dirge-code:mainfrom
wayniacal:feat/memory-browse

Conversation

@wayniacal

Copy link
Copy Markdown
Contributor

/memory had one subcommand, reload. There was no way to see what dirge had remembered about you — the store is SQLite with an FTS index, so the options were to ask the agent to call the memory tool and hope, or open sqlite3 and risk desyncing the index against the content. Memory is injected verbatim into the system prompt of every session in the project, and under global scope every project, so "you cannot read it" is a real gap.

/memory now lists the store. /memory edit opens it in $EDITOR: reword a line to reword the memory, delete a block to forget it, add a block to record something new.

Each entry is anchored on its id, rendered short ([n7x4bhbp]) and resolved by prefix. That is the load-bearing part. A memory row carries far more than its text — uid lineage, created_at, use_count, confidence, the supersession audit chain, and the procedural success/failure counters that the post-session expectation pass exists to move. Matching edited text back by content, or applying an edit as delete-then-recreate, silently resets all of it. With the id we UPDATE in place through the same path replace_entry already used, so it survives.

replace_entry's body is split into apply_replacement so the substring and by-id paths share one definition of what replacing means, rather than growing a second one that drifts.

Deleting a block tombstones rather than destroys, so restore still works — removing a line in an editor should not be more destructive than the tool's own removal. An unparseable document aborts the whole edit intact; aborting the editor (:cq) changes nothing.

$EDITOR handling is extracted from Input::open_in_external_editor into ui::external_editor and shared: the O_EXCL temp file, the /dev/tty fd juggling and the git-style argv are each easy to get subtly wrong twice. /edit should be retested by hand.

Verified end to end against the built binary. Two bugs found by running it, both now covered by tests: a new entry written as [identity] ... — which the document's own header invites — was read as an unknown id; and because the document always echoes the kind back, every rewording looked like a re-classification and reset the outcome counters. A rewording now preserves uid, use_count, confidence and success_count while changing the text.

`/memory` had one subcommand, `reload`. There was no way to see what dirge
had remembered about you — the store is SQLite with an FTS index, so the
options were to ask the agent to call the `memory` tool and hope, or open
sqlite3 and risk desyncing the index against the content. Memory is injected
verbatim into the system prompt of every session in the project, and under
global scope every project, so "you cannot read it" is a real gap.

`/memory` now lists the store. `/memory edit` opens it in $EDITOR: reword a
line to reword the memory, delete a block to forget it, add a block to record
something new.

Each entry is anchored on its id, rendered short (`[n7x4bhbp]`) and resolved
by prefix. That is the load-bearing part. A memory row carries far more than
its text — uid lineage, created_at, use_count, confidence, the supersession
audit chain, and the procedural success/failure counters that the
post-session expectation pass exists to move. Matching edited text back by
content, or applying an edit as delete-then-recreate, silently resets all of
it. With the id we UPDATE in place through the same path `replace_entry`
already used, so it survives.

`replace_entry`'s body is split into `apply_replacement` so the substring and
by-id paths share one definition of what replacing means, rather than growing
a second one that drifts.

Deleting a block tombstones rather than destroys, so `restore` still works —
removing a line in an editor should not be more destructive than the tool's
own removal. An unparseable document aborts the whole edit intact; aborting
the editor (`:cq`) changes nothing.

$EDITOR handling is extracted from `Input::open_in_external_editor` into
`ui::external_editor` and shared: the O_EXCL temp file, the /dev/tty fd
juggling and the git-style argv are each easy to get subtly wrong twice.
`/edit` should be retested by hand.

Verified end to end against the built binary. Two bugs found by running it,
both now covered by tests: a new entry written as `[identity] ...` — which
the document's own header invites — was read as an unknown id; and because
the document always echoes the kind back, every rewording looked like a
re-classification and reset the outcome counters. A rewording now preserves
uid, use_count, confidence and success_count while changing the text.
@wayniacal
wayniacal marked this pull request as ready for review August 24, 2026 10:57
Yogthos added 2 commits August 24, 2026 10:14
render/parse/apply and the *_by_uid store methods are only reached from
/memory edit, which needs $EDITOR (edit_text is unix-only), so windows
flagged them dead. summarize stays cross-platform — the /memory listing
works everywhere. also drops a needless borrow clippy flagged.
apply() was its only consumer left on windows after the previous commit.
@yogthos
yogthos merged commit f536c99 into dirge-code:main Aug 24, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants