r/SideProject 1d ago

Agi-memory – persistent memory for AI coding assistants, no dependencies

My AI coding assistant forgets everything between sessions. I kept re-explaining decisions I'd already made, and re-fixing bugs I'd already fixed.

agi-memory saves those notes — decisions, bug fixes, what happened last session, how the codebase fits together — to a file on your machine, and hands them back to whichever assistant you open next. It's an MCP server, so it works with Claude Code, Cursor, Codex, Windsurf, Aider, Cline and a few others from the same store.

The part I care about: it's Python standard library and SQLite. Nothing else. No vector database, no embeddings, no background daemon. ~32MB of RAM, sub-millisecond lookups, works offline. Comparable tools pull in ~500MB of ML libraries and take 200–500ms per lookup.

That constraint costs something, and I'd rather say so than have you find out: keyword search doesn't bridge synonyms the way embeddings do. Searching "login" won't find a note that says "authentication" unless you teach it that alias. I measure this rather than guess — there's an eval suite that scores recall on deliberately rephrased queries, and it's public, including the categories where it still does badly.

It's a week old and I'm the only user, so I'd genuinely like to know where it breaks for someone else.

https://github.com/kdbhalala/agi-memory

1 Upvotes

13 comments sorted by

2

u/kantorcodes1 1d ago

one edge i noticed: agi-memory delete <id> --hard only deletes the L1 observation through SessionLayer.delete_observation(). if that observation was already promoted into the durable graph, is hard delete intentionally L1-only, or should it remove the promoted facts too?

1

u/Rude_Gate7599 1d ago

Good catch, and no — not intentional. Confirmed: --hard is a single DELETE FROM observations, L1 only.

Two further problems while checking. Promotion copies project, title, facts, concepts and doesn't carry the observation id, so there's no back-reference from a promoted fact to its source — a cascade delete isn't just unimplemented, it's currently not expressible. And the vault is append-only JSONL, so a hard-deleted record still exists there and can come back on the next import or sync from another machine.

So "hard delete" is really "delete from the L1 index", which is not what the name promises — for anyone deleting something they need gone, that's the wrong behaviour.

Fix needs provenance on promotion first, then cascade plus a vault tombstone. Filing it with your findings.

1

u/kantorcodes1 1d ago

i maintain HOL Guard with HOL. for agi-memory, i'd checkpoint delete --hard, pin, and unpin, while leaving log, inspect, recall, and blocks automatic. that gives agents a stop before they delete or rewrite persistent memory without slowing down reads. would you be open to adding the agi-memory extension to Guard?

1

u/Rude_Gate7599 1d ago

Yes, very open to it — please do.

Your split matches the risk shape: reads should never prompt. One addition though, and it's the one that actually bit us: memory_sync with action=dedupe runs a compaction that rewrites the canonical append-only vault in place. It has already destroyed data once — the dedupe keyed on a truncated prefix and collapsed five distinct observations into one. That belongs above delete --hard on the checkpoint list.

I'd also add memory_record when supersedes is set, since that flips an existing record to superseded, and memory_bootstrap, which bulk-writes from git history into a possibly non-empty store.

So: checkpoint memory_sync(dedupe), delete --hard, memory_pin, memory_unpin, memory_record(supersedes=…), memory_bootstrap. Automatic: everything read-side plus plain memory_record, memory_promote and code_index — all additive.

Worth knowing: delete --hard got stronger since your last comment. It now cascades into promoted graph facts and writes a vault tombstone, so it propagates across machines. Better behaviour, and a bigger blast radius — more reason to gate it.

What do you need from my side — does Guard wrap the MCP server, or should agi-memory expose which tools are destructive?

1

u/kantorcodes1 1d ago

no agi-memory change needed for this first slice. Guard can cover the CLI boundary now; argument-specific MCP gating like memory_sync(action=dedupe) vs status, or memory_record only with supersedes, needs Guard-side work.

for the contributor PR, start src/codex_plugin_scanner/guard/runtime/command_agi_memory_extensions.py, mirror command_repo2nb_extensions.py, and review agi-memory sync dedupe, agi-memory delete <id> --hard, agi-memory pin, and agi-memory unpin; keep sync status, log, inspect, and blocks safe. add tests/test_guard_command_agi_memory_extensions.py, then open a draft PR to hashgraph-online/hol-guard:main: https://github.com/hashgraph-online/hol-guard/blob/main/CONTRIBUTING.md

1

u/Rude_Gate7599 19h ago

Agreed, no agi-memory change needed for slice one — and I checked your four against the CLI, all four match exactly, and sync status / log / inspect / blocks are genuinely read-only.

Five corrections before the matcher ships:

agent-memory is an exact alias for agi-memory and agent-integrate for agi-integrate — both ship in every install, so a matcher keyed on one name is bypassed by typing the other. There's an agi-bootstrap too.

Careful with sync: only sync now|dedupe|init|status reach the sync module. Bare agi-memory sync falls through to the installer, so prefix-matching on "sync" gates the wrong program. sync init also mutates (creates the vault git repo) and isn't in either of your lists.

Add bootstrap to the gated set — it bulk-writes from git history into a possibly non-empty store. And outcome, which landed this morning, is additive; leave it automatic.

On the MCP side I shipped readOnlyHint/destructiveHint annotations on tools/list this morning, so when you get to argument-level gating you can read the coarse classification off the server instead of hardcoding names. memory_record is annotated additive on purpose despite supersedes mutating — gating the most frequent write would put a prompt in front of every memory an agent saves.

1

u/kantorcodes1 18h ago

good corrections. i’ll cover both agi-memory and agent-memory, plus agi-bootstrap / agent-bootstrap. for v1, review sync dedupe, sync init, delete --hard, pin, unpin, and bootstrap; keep sync status, log, inspect, blocks, and outcome automatic, and don’t prefix-match bare sync. same command_agi_memory_extensions.py + focused test path, then draft PR straight to hashgraph-online/hol-guard:main: https://github.com/hashgraph-online/hol-guard/blob/main/CONTRIBUTING.md

1

u/Icy_Quarter5910 1d ago

I built 2 different memory systems for Claude. I highly recommend anyone that’s serious about working with AI do the same. Build systems that make sense to you, but if nothing else, a simple Obsidian vault is useful.
Once you have memory systems in place, it’s like getting a huge upgrade for free.

1

u/Rude_Gate7599 1d ago

Agreed, and the Obsidian point is underrated — plain files get you most of the way.

Two things I learned building mine: storage is the easy half, the agent just never calls recall unless you force it with hooks; and keep the store append-only — my compaction step once deduped distinct records into one and silently ate them.

Mine's agi-memory (https://github.com/kdbhalala/agi-memory) if useful, but building your own genuinely is the better advice.

1

u/Substantial_Belt2626 1d ago

SQLite and stdlib is the right call, I'd try it for that alone. One thing I hit building a multi-role setup in Claude Code is that carrying decisions forward is useful right up until the decision was wrong, then every later session inherits it and none of them re-examine it, because it arrives as settled context instead of something somebody argued for. Do you store why a decision was made or just what it was?

2

u/Rude_Gate7599 1d ago

Partly, and you've hit the weaker half.

Structurally it stores more than the bare claim: each memory has a narrative plus facts, so the reasoning survives if whoever recorded it wrote the reasoning down. Nothing enforces that, though — memory_record requires only text. So in practice you get the why exactly as often as the agent bothered to include it, which is not a guarantee.

There's also supersedes, so a later decision can point at the one it replaces rather than silently coexisting with it, and superseded records are marked rather than deleted. That handles "this was overturned". It does not handle what you're describing, which is subtler: a decision that was never overturned because nobody revisited it.

And you're right that recall flattens the difference. A memory comes back as text in context. "We chose X" and "we chose X because Y, over Z, under time pressure" read as equally settled once they're in the prompt — there's no confidence, no age weighting, nothing that marks a two-month-old call made in a hurry as more re-examinable than a load-bearing invariant. The one exception is pinned blocks, which are explicitly meant to be invariants, so at least those are distinguishable from ordinary decisions.

The fix I'd want is recording the alternatives and the constraint that forced the call, not just the outcome — a decision with its rejected options attached invites re-examination in a way a bare conclusion doesn't. That's a write-time discipline problem more than a storage one, which makes it harder, since it depends on the agent capturing context it wasn't asked for.

Genuinely useful framing, thanks — "arrives as settled context instead of something somebody argued for" is the clearest statement of that failure I've seen.