Skip to content

memory: confirm writes before storing them - #2 - #815

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

memory: confirm writes before storing them - #2#815
yogthos merged 2 commits into
dirge-code:mainfrom
wayniacal:feat/memory-review

Conversation

@wayniacal

Copy link
Copy Markdown
Contributor

dirge decides what is worth remembering on its own. The agent writes mid-session, and after an idle session the background review and memory curator write more. All of it lands in the system prompt of every later session in the project — and under global scope, every project. Nobody approves any of it, a wrong or trivial memory persists silently, and something the human knows matters is never recorded unless the model happened to notice it.

memory.confirm_writes (default off) puts a human in that loop. An add is queued instead of stored; /memory review opens the queue in $EDITOR.

The file is the desired final state of the batch, not a diff — so reject (delete the block), reword (edit the text) and add (type a new one) are one operation, and recording what the model missed is a first-class action rather than an afterthought. Accepted entries go in through the normal add_entry path, so a memory you typed is indistinguishable from one the model proposed.

A queued entry rides on status = 'pending'. Every read path already filters status = 'active' — the snapshot, view, and the FTS join in search — so a proposal is inert everywhere with no new filtering and no migration. It also skips hot-tier compaction: a merely proposed memory must not demote an accepted one before anyone agreed to keep it.

Only add is gated. replace/supersede/remove act on entries a human already accepted, and supersede usually fires because the user just corrected the agent.

An aborted edit (:cq) changes nothing, which is why edit_text returns None rather than an empty document — confusing the two would reject the whole queue. An unparseable file aborts the apply intact rather than half-applying.

$EDITOR handling is extracted from Input::open_in_external_editor into ui::external_editor and shared, rather than copied: the O_EXCL temp file, the /dev/tty fd juggling and the git-style argv are all easy to get subtly wrong twice.

Verified end to end against the built binary: writes queue as pending; the agent's own view and search report zero entries; review rewords one entry, rejects another and adds a third, leaving exactly that in the store with an empty queue; an editor exiting non-zero leaves queue and store untouched; and with the gate off writes go straight through as before.

note: this should merge after my previous memory browse does since they both extract ui::external_editor, so it will require a rebase

@wayniacal
wayniacal marked this pull request as ready for review August 24, 2026 10:59
@yogthos

yogthos commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Thanks, that makes sense.

Wayne and others added 2 commits August 24, 2026 10:38
dirge decides what is worth remembering on its own. The agent writes
mid-session, and after an idle session the background review and memory
curator write more. All of it lands in the system prompt of every later
session in the project — and under global scope, every project. Nobody
approves any of it, a wrong or trivial memory persists silently, and
something the human knows matters is never recorded unless the model
happened to notice it.

`memory.confirm_writes` (default off) puts a human in that loop. An `add`
is queued instead of stored; `/memory review` opens the queue in $EDITOR.

The file is the desired final state of the batch, not a diff — so reject
(delete the block), reword (edit the text) and add (type a new one) are one
operation, and recording what the model missed is a first-class action
rather than an afterthought. Accepted entries go in through the normal
add_entry path, so a memory you typed is indistinguishable from one the
model proposed.

A queued entry rides on `status = 'pending'`. Every read path already
filters `status = 'active'` — the snapshot, `view`, and the FTS join in
`search` — so a proposal is inert everywhere with no new filtering and no
migration. It also skips hot-tier compaction: a merely proposed memory must
not demote an accepted one before anyone agreed to keep it.

Only `add` is gated. `replace`/`supersede`/`remove` act on entries a human
already accepted, and `supersede` usually fires because the user just
corrected the agent.

An aborted edit (`:cq`) changes nothing, which is why `edit_text` returns
None rather than an empty document — confusing the two would reject the
whole queue. An unparseable file aborts the apply intact rather than
half-applying.

$EDITOR handling is extracted from `Input::open_in_external_editor` into
`ui::external_editor` and shared, rather than copied: the O_EXCL temp file,
the /dev/tty fd juggling and the git-style argv are all easy to get subtly
wrong twice.

Verified end to end against the built binary: writes queue as pending; the
agent's own `view` and `search` report zero entries; review rewords one
entry, rejects another and adds a third, leaving exactly that in the store
with an empty queue; an editor exiting non-zero leaves queue and store
untouched; and with the gate off writes go straight through as before.
collect/render/parse/apply and PendingEntry/list_pending/clear_pending
are only reached from /memory review, which needs $EDITOR, so windows
flagged them dead. notify_if_queued and add_pending stay cross-platform:
the queue itself is written and counted everywhere. clippy wanted the
indent-continuation if collapsed.
@yogthos
yogthos force-pushed the feat/memory-review branch from 35b315a to 5ff6e9d Compare August 24, 2026 14:40
@yogthos
yogthos merged commit 398d882 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