Skip to content

feat:Add local personal memory - #808

Draft
alecuba16 wants to merge 9 commits into
DeusData:mainfrom
alecuba16:personal-memory
Draft

feat:Add local personal memory#808
alecuba16 wants to merge 9 commits into
DeusData:mainfrom
alecuba16:personal-memory

Conversation

@alecuba16

Copy link
Copy Markdown

This pull request adds a local personal memory system for codebase notes, decisions, and learnings. It is intentionally separated from the fork release workflow work so this PR only contains the personal-memory feature.

This replaces the closed broad PR #761 with the workflow option removed, and addresses the security review note from #761 (comment).

Personal memory

The main addition is a per-repo personal memory that lives outside the repository, in the user's local data directory. The goal is to let an agent or developer store notes, decisions, and learnings about a codebase without writing files into the source repository or pushing that context upstream.

A new MCP tool called manage_memory is exposed with these modes:

  • get
  • update
  • sections
  • settings
  • bootstrap
  • delete
  • list
  • promote
  • sync

manage_adr also gains an optional scope parameter so it can target either project-scoped ADRs, which remains the default for compatibility, or personal memory.

The memory directory, default scope, and enablement are configurable. A new platform helper, cbm_resolve_memory_dir, resolves the correct local user-data path for each operating system.

Security and local-only behavior

After the review comment on #761, I added a focused security hardening pass in commit 5f0f4b6:

  • Personal memory storage directories are created with private 0700 permissions on POSIX systems.
  • Existing memory directories are chmod-corrected to 0700 when opened for writes.
  • manage_memory(settings) redacts local filesystem paths by default.
  • Local paths can only be shown explicitly with reveal_paths=true.
  • MCP responses redact repo IDs and storage keys because they are derived from local canonical repo paths.
  • delete is now idempotent, returns not_found when appropriate, and no longer creates a DB just to delete from it.
  • promote and sync are local copy operations only. Responses now explicitly report local_only and network_sync=disabled.
  • Tests cover path redaction, private directory permissions, local-only metadata, and delete semantics.

Nothing in this feature uploads personal memory to the repository, remotes, package registries, or external services. Storage is local SQLite under the resolved personal memory directory.

Other changes

  • README and embedded skill documentation explain the personal memory workflow.
  • The smoke invariants script covers the new memory paths.
  • Tests cover manage_memory and personal scope in manage_adr.
  • A cross-platform cbm_setenv / cbm_unsetenv helper was added for Windows-compatible tests.

Validation

  • Rebased on current upstream main at 09148ab.
  • make -f Makefile.cbm test passes locally: 5766 passed.

@alecuba16
alecuba16 requested a review from DeusData as a code owner July 3, 2026 17:35
@DeusData DeusData added enhancement New feature or request editor/integration Editor compatibility and CLI integration security Security vulnerabilities, hardening priority/backlog Valuable contribution, lower scheduling urgency; review when maintainer capacity opens. labels Jul 4, 2026
@DeusData

DeusData commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Thanks for splitting the local personal memory work out from #761. Triage: high-caution local-memory enhancement.

This is a better review shape than the combined workflow PR, but it still needs a security/privacy pass: local-only storage boundaries, delete semantics, sync/promote behavior, no external transmission, no sensitive-data logging, and clear docs on where data lives. Please keep the PR scoped to the memory feature only.

@alecuba16

alecuba16 commented Jul 4, 2026

Copy link
Copy Markdown
Author

Thanks for the review. I updated the PR with a security/privacy pass and kept it scoped to the memory feature.

What changed:

  • manage_memory now rejects memory storage paths that are relative or inside the source repo.
  • Responses now state local_only, external_transmission=false, network_sync=disabled, repo_upload=disabled, and sensitive_data_logging=disabled.
  • mode="sync" is now disabled and returns sync_disabled. There is no network sync.
  • mode="promote" is documented and returned as a local copy from branch memory to base branch memory only.
  • mode="delete" is documented and returned as deleting only the selected repo/branch/doc row, not the DB file or other memory.
  • Docs now explain where memory.db lives, how CBM_MEMORY_DIR works, path redaction, and reset/delete limits.
  • Tests were added for repo-local storage rejection, sync disabled, privacy fields, delete semantics, and path redaction.

I also adjusted the default behavior to avoid changing existing installs:

  • memory_enabled=false by default.
  • memory_default_scope=project by default.
  • Local personal memory is opt-in.
  • Added a test that default-disabled memory does not create memory.db.

Validation:

  • lint-format
  • lint-no-suppress
  • git diff --check
  • full test suite: 5768 passed

I squashed it back to one commit: cfa69f3.

@alecuba16
alecuba16 force-pushed the personal-memory branch 2 times, most recently from cfa69f3 to 86dcd5f Compare July 4, 2026 07:23
@alecuba16 alecuba16 changed the title Add local personal memory feat:Add local personal memory Jul 4, 2026
@alecuba16
alecuba16 force-pushed the personal-memory branch 2 times, most recently from 0253e9d to 51f63b9 Compare July 5, 2026 07:42
@DeusData DeusData added this to the 0.9.2-rc milestone Jul 8, 2026
@DeusData

Copy link
Copy Markdown
Owner

Status update after a full review pass — being straight with you about where this stands.

Review outcome: security-clean (no network/exec surface, storage-path validation works as described, env access through the safe helpers), the Makefile/lint wiring is correct, and the privacy rework you did after #761 addressed the concerns from that thread. Two mechanical items would still be needed: a rebase (main has moved substantially — the PR currently conflicts) and the three raw remove() calls on memory/config DB paths should be cbm_unlink (raw ANSI file ops break on non-ASCII Windows paths — same class we just fixed in #996).

Decision: deferring this past the v0.9.1 release. The honest reason is direction, not quality: a personal-memory subsystem is a significant permanent surface (a 9-mode MCP tool, a new storage location, sync/promote semantics across scopes), and whether cbm should grow a notes store alongside its knowledge-graph core — versus leaving that to the agents' native memory systems — is a product question we want to settle deliberately, not under release pressure.

No action needed from you right now; specifically NOT asking for the rebase yet, so you don't invest in churn while the concept decision is pending. We'll come back to this PR after 0.9.1 ships with a clear yes/no/reduced-scope answer. Sorry for the long runway on this one — the split from #761 and the privacy pass were genuinely good work, and this deferral is not a soft rejection.

@alecuba16

Copy link
Copy Markdown
Author

No problem, thanks for taking a look anyway

@DeusData

Copy link
Copy Markdown
Owner

Reviewed in full. The engineering here is careful and one design decision is genuinely better than what we do elsewhere — but there is a direction question for the maintainer and three things bundled in that need to come out regardless.

The privacy engineering is a higher bar than most of our own tools started at. Directories created 0700 with chmod-correction, redaction-by-default with reveal_paths as an explicit opt-in, boundary rejection that refuses to even create the DB, idempotent delete, disabled-by-default — and tests that assert the temp path never leaks into a response. That last one is the kind of test people skip.

The strongest point in its favour, and I want to be specific: storing memory in a separate memory.db in the user-data directory rather than in the project index was exactly the right call. The indexing pipeline never opens it, so reindex — full or incremental — cannot destroy it. We currently have a live bug where incremental reindex silently destroys stored ADRs in the project DB; your design is immune to that failure mode by construction. It is, in effect, the fix-shaped alternative.

Also worth noting: no graph schema change at all — zero new node labels, zero new edge types. The code graph is untouched.

Three things need to come out of this PR, independent of the direction call.

  1. .github/workflows/fork-binary-release.yml is in the diff. Your description says the workflow work was intentionally separated out, but a 106-line CI workflow is here — its commit messages say it builds binaries on the personal-memory branch, i.e. it is your fork's distribution tooling. Every workflow change gets flagged in this repo, and one that contradicts the PR body needs removing.
  2. The auto_update config key and update-check gating is a second, unrelated feature. It is arguably a nice one — an off-switch for the existing GitHub update check — but it belongs in its own PR.
  3. Two pieces of dead surface. memory_default_scope is stored, printed and returned in settings JSON but never read by any code path that changes behaviour — a knob that does nothing. And sync is a mode whose only function is to return sync_disabled as an error. Both would be permanent API surface with no purpose.

The direction question, which is the maintainer's. This is not a code-knowledge-graph feature — it stores user- and agent-authored notes rather than extracted structure, and adds no graph capability. It is an adjacent product riding in the same process. It is thematically close to the project's name and to the existing manage_adr, and the agent-session-memory use case is real and well executed. But manage_memory would be a permanent 16th MCP tool with nine modes, plus four config keys, an env var, and a new on-disk format. That is a one-way door and a product-scope call, so I have put it upstream rather than deciding it in review.

Two smaller things for whenever it resumes:

  • The repo-boundary check is lexical — no realpath canonicalisation — so a symlinked memory_dir pointing into the repo would bypass it. The input comes only from local config or env, so an MCP caller cannot reach it, but it is cheap to harden.
  • Your branch's scripts/smoke-invariants.sh edit sets EXPECTED_TOOL_COUNT=15 and omits check_index_coverage, while your own server exposes 16 tools — so that invariant fails on your own branch, before any conflict with main.

Please hold off reworking until there is a direction answer; between the rebase and the unbundling, that is real work and I would rather it be spent once.

@DeusData

Copy link
Copy Markdown
Owner

I owe you a real answer rather than a third deferral, so here it is: this needs a Discussion before it can be a PR decision, and I'd like you to open it.

Not because the work is weak — the opposite. The security review came back clean, the out-of-repo storage design is genuinely better than what we do today, and you've already absorbed two rounds of narrowing without complaint. The problem is that I can't responsibly decide this from inside a PR thread.

What you're proposing isn't a bug fix or an extension of something existing. It's a 16th first-class MCP tool and, with it, a second axis for the product: cbm today answers questions about code it has indexed, and manage_memory would make it a place where people store things it never indexed. That may well be right — but every MCP tool is a one-way door. Once agents in the wild call manage_memory, its nine modes and its storage format are a contract we maintain forever, and removing it later breaks people. A decision with that shape deserves more input than one contributor and one maintainer talking past a release deadline, which is what's happened twice now.

So: please open a Discussion (Discussions are enabled on the repo), and I'll engage there properly and point others to it. What I'd most want it to surface:

  • Who wants this, and for what. Is the demand "my agent forgets project context between sessions", or "I want notes that survive a reindex", or "I want notes that follow me across machines"? Those pull toward different designs, and only the second is really about cbm.
  • Why the agent's own memory isn't the answer. Most agent runtimes now have some memory mechanism. The strongest case for putting it in cbm is that cbm knows the repo identity and branch — worth arguing explicitly rather than assuming.
  • Nine modes, or fewer? get/update/sections/settings/bootstrap/delete/list/promote/sync is a lot of contract to commit to at once. If the same value lands with three, that's a much easier yes.
  • The relationship to manage_adr. We already store ADR prose. Is personal memory a scope on that, a sibling tool, or a replacement? You've implemented both a scope=personal parameter and a separate tool, which is a fair way to hedge, but the end state should be one of them.

One thing from your PR I want on the record in that Discussion, because it's the most valuable thing to come out of this and it's yours: storing notes outside the project database is architecturally better than what we do now. I went and confirmed it while reviewing something else — ADRs survive a reindex only because there is bespoke capture-and-restore machinery for them (capture_existing_adr, a saved_adr field, and a regression test pinning the behaviour), since the pipeline publishes by renaming a staging database over the live one. Anything written into that database without such machinery is destroyed on the next background reindex. You designed around a fragility that we papered over. That belongs in the Discussion whatever happens to the tool.

Three things need to come out regardless of which way it goes, so they don't confuse the conversation:

  • the bundled .github/workflows/fork-binary-release.yml — unrelated CI, and the PR body says workflow changes were split out, so this looks like a rebase accident;
  • the auto_update config flag — a separate feature about our release-check network call;
  • the dead surface: memory_default_scope is never consulted, and sync only ever returns sync_disabled. Shipping a mode that cannot succeed is worse than not shipping it.

Also minor, for whenever: the storage-boundary check is a string prefix rather than a canonicalised path, so a symlinked memory_dir could point back inside the repo — low severity since it comes from local config and never from an MCP argument, but worth a realpath before it's load-bearing. And the branch's smoke-invariants script still expects 15 tools.

I'm leaving this open and linked to the Discussion. Thank you for your patience — being deferred twice and asked to narrow twice is genuinely frustrating, and you've handled it with more grace than the situation earned.

@DeusData
DeusData marked this pull request as draft August 28, 2026 14:29
@DeusData

Copy link
Copy Markdown
Owner

@alecuba16 — status change and a path forward, so this stops sitting in limbo.

The PR is converted to draft, and the direction question now has the community forum it needed: Discussion #1878. The Discussion the 20 August comment asked for never materialised, so we opened it ourselves rather than let the idea rot inside a stale branch — it references this PR as the concrete proposal, credits your privacy engineering explicitly (default-off, 0700 dirs, redaction-by-default with leak tests, no network — that work is genuinely good and is said so there), and lays out the real alternatives, including the external-companion shape from Discussion #1835.

To be equally plain about this branch: whatever the Discussion concludes, the bundled fork-release workflow and the unrelated auto_update feature cannot ride along — those were flagged earlier and remain the reason this specific head could not merge even under a yes. If the community lands on yes, the path is a clean resubmission scoped to the store itself, designed against whatever consensus #1878 produces.

Please weigh in on the Discussion — it is your idea, and the strongest case for it is yours to make. The draft state means "parked pending direction", not "rejected".

alecuba16 added a commit to alecuba16/codebase-memory-mcp that referenced this pull request Aug 28, 2026
… tests

Raw ANSI file operations break on non-ASCII Windows paths. The three
remove() calls on memory_db and config_db paths in the manage_memory
tests are replaced with cbm_unlink, and rmdir() with cbm_rmdir, matching
the pattern used everywhere else in the test suite.

Addresses reviewer feedback on PR DeusData#808.
@alecuba16

Copy link
Copy Markdown
Author

Update: the branch is now rebased onto current main and shows as MERGEABLE. The three raw remove() calls the reviewer flagged are replaced with cbm_unlink (commit 01af220).

I have also posted a response on Discussion #1878 answering the three questions raised there. The PR will stay in draft until that discussion converges, as requested.

alecuba16 added a commit to alecuba16/codebase-memory-mcp that referenced this pull request Aug 28, 2026
… tests

Raw ANSI file operations break on non-ASCII Windows paths. The three
remove() calls on memory_db and config_db paths in the manage_memory
tests are replaced with cbm_unlink, and rmdir() with cbm_rmdir, matching
the pattern used everywhere else in the test suite.

Addresses reviewer feedback on PR DeusData#808.

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
After rebasing onto main, main added check_index_coverage as the 15th
tool. Our manage_memory makes 16 total. Update all tool count references,
add manage_memory to TOOL_ANNOTATIONS array, and fix test expectations.

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
… tests

Raw ANSI file operations break on non-ASCII Windows paths. The three
remove() calls on memory_db and config_db paths in the manage_memory
tests are replaced with cbm_unlink, and rmdir() with cbm_rmdir, matching
the pattern used everywhere else in the test suite.

Addresses reviewer feedback on PR DeusData#808.

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
This file was a local working note that was accidentally staged.
It is not part of the personal memory feature.

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
Per reviewer feedback on PR DeusData#808, remove three pieces of dead/unrelated
surface that were flagged as required regardless of the discussion
outcome on Discussion DeusData#1878:

1. auto_update config flag — unrelated feature about the release-check
   network call, belongs in its own PR if pursued.

2. memory_default_scope config key — stored, printed, and returned in
   settings JSON but never read by any code path that changes behavior.
   A knob that does nothing.

3. sync mode — a manage_memory mode whose only function is to return
   sync_disabled as an error. Shipping a mode that cannot succeed is
   worse than not shipping it.

Also fixes tool count references from 16 to 17 (upstream added two tools
since the last rebase: get_file_outline and compare_graphs, bringing
the upstream total to 16; our manage_memory makes 17).

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
@alecuba16

Copy link
Copy Markdown
Author

Following up on the three items flagged as required regardless of the Discussion #1878 outcome. All three are now removed from this branch, and the branch is rebased onto current main (mergeable).

1. .github/workflows/fork-binary-release.yml — removed. The three workflow commits (cc0bf08a, fb411d9c, afc04891) and the accidental WORKFLOW_STATE.md are gone from the branch history. The diff is now scoped to the memory feature only.

2. auto_update config flag — removed. The CBM_CONFIG_AUTO_UPDATE define, its default-value entry, and its config-key table row are all gone. This was an unrelated feature about the release-check network call.

3. Dead surface — removed.

  • memory_default_scope: the config key, its default-value entry, its config-key table row, and its default_scope field in the settings JSON response are all gone. It was never consulted by any code path.
  • sync mode: the mode handler, the "sync" entry in the mode enum, the sync_disabled/sync_semantics/network_sync JSON fields, and the corresponding test are all gone. Shipping a mode that cannot succeed is worse than not shipping it.

Tool count updated to 17. Upstream added two tools since the last rebase (get_file_outline, compare_graphs), bringing the upstream total to 16. Our manage_memory makes 17. All references in README, docs, CLI skill text, and smoke-invariants are updated.

The branch is build-clean and the test suite passes (7783 passed, 1 pre-existing failure on main, 7 skipped). The PR stays in draft pending Discussion #1878.

The real binary exposes 18 tools via tools/list. Upstream main has 17
(get_file_outline and compare_graphs were added since the last rebase),
plus our manage_memory makes 18. Updated all references in README,
docs/index.html, docs/llms.txt, CLI skill text, tests, and
smoke-invariants.

Verified via MCP stdio: tools/list returns 18 tools, smoke-invariants
passes the tools-list check with 18 tools.

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
@alecuba16

Copy link
Copy Markdown
Author

Correction to the tool count in the previous comment: the real binary exposes 18 tools via tools/list (upstream has 17 after adding get_file_outline and compare_graphs, plus our manage_memory = 18). All references in README, docs, CLI skill text, tests, and smoke-invariants are now updated to 18.

Verified end-to-end via the MCP stdio protocol against an indexed project with memory_enabled=true:

  • tools/list returns 18 tools, manage_memory modes enum has no sync
  • manage_memory(mode="settings") returns local_only, external_transmission, repo_upload, sensitive_data_logging, delete_semantics, promote_semantics — no network_sync, no default_scope, no sync_semantics
  • manage_memory(mode="update") stores content and returns "status":"updated"
  • manage_memory(mode="get") retrieves the stored content correctly
  • manage_memory(mode="sync") no longer returns sync_disabled — falls through as an unrecognized mode
  • scripts/smoke-invariants.sh passes the tools-list check with 18 tools present

The reviewer on PR DeusData#808 flagged twice that the repo-boundary check was
lexical (string prefix) with no realpath canonicalization, so a
symlinked memory_dir pointing into the repo would bypass it. Low
severity since the input comes from local config or env, not from MCP
arguments, but cheap to harden.

Use cbm_canonical_path (realpath on POSIX, GetFinalPathNameByHandleW on
Windows) to resolve both the repo root and memory_dir before the prefix
comparison. Falls back to the lexical check when canonicalization fails
(e.g. path does not exist yet).

Verified via MCP stdio: a symlinked CBM_MEMORY_DIR pointing into the
repo is now correctly rejected with storage_boundary=memory_dir_inside_repo.

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
@alecuba16

Copy link
Copy Markdown
Author

One more item addressed from the review: the storage-boundary check is now hardened with realpath canonicalization (cbm_canonical_path), addressing the "minor, for whenever" note about the lexical prefix check. A symlinked CBM_MEMORY_DIR pointing into the repo is now correctly rejected with storage_boundary=memory_dir_inside_repo. Falls back to the lexical check when canonicalization fails (e.g. path does not exist yet).

Verified via MCP stdio against a test repo with a symlinked memory_dir: the boundary check catches the symlink and rejects storage, where the old lexical check would have let it through.

On macOS, /tmp is a symlink to /private/tmp. When the memory dir (e.g.
/tmp/foo/.cbm-memory) does not exist yet, realpath fails and the boundary
check fell back to the lexical path, which used /tmp/... while the repo
root was canonicalized to /private/tmp/... The prefix comparison failed,
allowing memory storage inside the repo.

Fix: resolve the parent directory (which exists) and append the tail,
so both paths use the same canonical prefix.

Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
Signed-off-by: Alejandro Blanco-M <alecuba16@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

editor/integration Editor compatibility and CLI integration enhancement New feature or request priority/backlog Valuable contribution, lower scheduling urgency; review when maintainer capacity opens. security Security vulnerabilities, hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants