
MemoryCustodian
Repo-native memory for coding agents
194 followers
Repo-native memory for coding agents
194 followers
MemoryCustodian gives Codex, Claude Code, Gemini, and other coding agents durable project memory—without a hosted service or bloating every prompt. Decisions, constraints, rejected approaches, and project context live as plain Markdown in your repo, where they can be reviewed, versioned, shared, and deleted like code. A manifest loads only the memory relevant to each task. Open source, local-first, and cross-agent.






MemoryCustodian
Hey Product Hunt 👋
I built MemoryCustodian because coding agents are increasingly capable, but every new session still tends to forget why a project looks the way it does.
Important decisions, constraints, rejected approaches, and repeated corrections often live only in old conversations—or get copied into increasingly large instruction files.
MemoryCustodian takes a different approach: durable project memory lives as plain Markdown inside the repository.
A small manifest tells the agent which memory files are relevant to the current task, so the memory can grow without forcing every session to load the entire project history.
What makes it different:
Repo-native and plain text—no opaque memory database
Reviewable, versioned, and shareable through normal Git workflows
Selective loading instead of injecting all memory into every prompt
Explicit support for updating, correcting, and safely forgetting memory
Works with Codex, Claude Code, Gemini, and generic coding-agent workflows
Free, open source, and local-first
The repository also includes a reproducible NightNotes demo showing a new agent session recovering an existing architectural decision and avoiding an already-rejected implementation path.
I’d especially love feedback on the memory structure, cross-agent workflow, and whether the installation experience feels simple enough for real projects.
Thanks for checking it out!
the "manifest loads only the memory relevant to each task" line is the part I'd poke at. relevance selection is its own hard problem - if it's simple keyword or path matching against the task description that's predictable, but if the agent itself is judging which memory files matter for the current task, you can end up with the same failure mode you're trying to avoid: it just won't load the one constraint file it doesn't realize is relevant, and you get no error, just quietly wrong behavior. how does the manifest decide relevance today, and is there any way to see what got left out for a given task
MemoryCustodian
@galdayan
That’s a fair concern. Today, the manifest is not doing semantic retrieval, keyword search, or relevance scoring.
The agent first classifies the task into a small category such as planning, implementation, artifact work, or maintenance. The manifest then deterministically maps that category to a fixed set of files. For example, planning and implementation load decisions.md, constraints.md, and do-not-use.md by default, so the agent does not judge individual constraints one by one.
There is still an agent-judgment boundary for selecting the task category and matching optional rules/, profiles/, or areas/. A wrong classification can therefore omit scoped memory, and we consider that a real limitation.
You can inspect the selected context with:
It shows what was loaded, while the full output also reports missing files and entries omitted by context budgets. What v0.9.1 does not yet provide is a complete trace listing every excluded module and why.
A more precise description would be: the manifest routes a bounded context pack through explicit, project-defined task categories.
Repo-native is the right instinct — memory that lives outside the repo drifts away from the code it describes almost immediately, and then you're maintaining two sources of truth about the same system.
The question that follows is what happens on a branch. If memory is committed alongside code, then a note written on a feature branch is invisible on main until merge, which is arguably correct but might surprise people. And a rebase or a squash would rewrite the memory's history along with everything else. Is memory versioned with the code by design, or does it sit in the repo but outside normal git flow?
MemoryCustodian
@ark_y_k Yes, shared project memory is versioned with the code by design.
A memory change on a feature branch stays on that branch until merge, which keeps branch-specific decisions from affecting main too early. Rebases and squash merges rewrite memory history along with code history, while the final memory state remains visible in the diff.
Only personal preferences and machine-specific context sit outside Git. Shared decisions, constraints, and rejected approaches stay in the repo.
So there is no separate hidden shared-memory timeline.
@waittim Branch-scoped by design is the right answer, and it resolves the thing I was uneasy about — a decision made while exploring an approach shouldn't be shaping agent behavior on main before the approach itself has been accepted.
It also means memory inherits review. If a branch gets abandoned, whatever context it accumulated goes with it rather than lingering as advice about a direction nobody took. That's a property you'd have to build deliberately if memory lived anywhere else, and here you get it for free.
Thanks for the detail — the git-native framing makes a lot more sense to me now than it did from the tagline alone.
MemoryCustodian
@ark_y_k Exactly, that is the property we wanted from making shared memory part of the normal Git history rather than a parallel system.
Branch-specific context stays with the branch, accepted memory merges with accepted code, and abandoned directions disappear together.
Thanks for articulating it so clearly.
@waittim "Rather than a parallel system" is the part I'd underline. A separate memory store is a second thing to keep honest, and nobody ever does — it drifts, and then you're reading advice about a version of the code that stopped existing months ago. Putting it in git history means it can't drift without someone noticing, because drifting would mean a conflict.
Accepted memory merging with accepted code is a clean invariant to build on. Review already decides what's true about the codebase; now it decides what's true about the reasoning too, in the same step.
That's my question fully answered. Thanks for the detail — good luck with the launch.
Different failure mode than the branch/rebase question above: two agent sessions on separate branches each append a note to constraints.md, no line-level conflict so git merges both cleanly, but the notes contradict each other (one says 'never use library X', the other says 'X is now required for Y'). Nothing breaks at merge time, the file just quietly holds two decisions that can't both be true. Is there any detection for that, or does it rely on someone eventually reading the file top to bottom and noticing?
MemoryCustodian
@omri_ben_shoham1 Good catch. Today, Git can merge those entries cleanly, and v0.9.1 does not automatically detect that they contradict each other.
Keeping memory files short makes conflicts easier for humans and agents to notice, while timestamps and evidence help explain where each entry came from. But neither is a conflict-resolution rule, and we would not simply let the newest entry win.
The planned releases add stable entry IDs, explicit supersession, and conflict auditing. Hard-memory entries will also reference a stable canonical subject, scope, and controlled facet. If two active entries claim the same structural identity, check or audit will fail until one supersedes the other or an explicit scoped exception is recorded.
That still is not general semantic contradiction detection. If two branches describe the same underlying issue using different structural identities, a merge-aware audit will flag the concurrent hard-memory changes for reconciliation rather than silently treating the merge as safe.
Repo-native is the right call, and the branch question above is the obvious follow-up — so I'll ask the one underneath it. What happens when the project isn't one repo?
Mine is three: the app, the infrastructure, and the marketing site. The decisions that bite me are almost always made in one and violated in another — a constraint I set while working on infra that the agent working in the app repo has never seen. Repo-native is exactly right for "why is this code shaped this way." It also makes the repo the trust boundary, and my project boundary is wider than that.
Is sharing a memory module across repos something you'd support — a submodule, a symlink, a shared manifest entry — or is one-repo-one-memory a deliberate scope call?
MemoryCustodian
@rickwise That’s a real boundary in the current release. Today, one initialized repository has one shared memory root, so the app repo will not automatically see a constraint recorded in the infrastructure repo.
I don’t think one-repo-one-memory should be a permanent product boundary. The cleaner extension would be an explicit workspace layer: each repository keeps its repo-specific memory, while several repositories can opt into a separate, versioned workspace memory root for decisions and constraints that apply across the whole project.
Both layers would need explicit identity, precedence, revision, and conflict behavior. I would avoid treating raw symlinks as the protocol; a dedicated repository or submodule could be one way to distribute workspace memory, but the relationship should remain visible and auditable.
So today it is one shared memory root per repo by design, but multi-repo project memory is a valid layer above that model.
Repo-native plain Markdown is the right call, I hand-maintain something similar and versioning memory alongside the code is exactly what keeps it from rotting. The part I keep hitting isn't retrieval, it's write discipline: what actually triggers a new entry? If the agent decides when a decision is "worth remembering," how do you stop constraints.md from filling up with conversation-local noise that was never really a durable decision?
MemoryCustodian
@lennoxbeflying That’s the harder problem. A note should become durable only when it is confirmed, project-level, likely to affect future work, and not already captured.
In the current design, the main triggers are meaningful decisions, repeated corrections, and rejected approaches. Hard constraints go into constraints.md, conversation-local observations and uncertain ideas should stay out or wait in the inbox for review.
the deleted like code phrasing is doing more work than it looks. deleting a memory file takes it out of the working tree, but if it was ever committed it stays in history, in every clone, and in every fork. so forgetting is reversible for anyone holding the repo.
matters because rejected-approach notes are exactly where the sensitive material collects. we cannot use X because of the contract with Y, or this is throttled because vendor Z caps us at N. fine in a private repo among employees, less fine once a contractor clones it or the project goes open source.
not an argument against repo-native, I still think the tradeoff lands your way. just that safe forgetting probably wants a line in the docs about what it does and does not cover...
MemoryCustodian
@alex_watson2110 That’s an important distinction, and you’re right.
Today, hard forget and purge remove content from the managed memory files—and purge also covers the memory archive, but they do not rewrite Git history or revoke copies already present in clones, forks, or backups.
So “forgetting” should be understood as preventing content from remaining in, or returning to, active agent memory, not guaranteed erasure from every copy of the repository.
We should make that boundary explicit in the docs, along with stronger guidance not to store raw secrets, contract details, or sensitive vendor information in repo memory in the first place. Thanks for calling this out.