#2404

Reader, writer, and docs disagree about where chroma.sqlite3 lives — silent empty vault when reader is one level too shallow

OpenBuboTheWiseopened 2mo ago

Summary

Three consumers of the same palace disagree about where the SQLite data file lives, and the mismatch is silent: nothing errors, nothing is logged, and a reader pointed one level too shallow simply reports an empty vault.

The three layouts

SideWhere it looks for chroma.sqlite3
Writer (MCP mempalace_add_drawer + auto_init layout)<palace>/palace/chroma.sqlite3
Reader (MCP server, verbatim)<palace>/chroma.sqlite3
Docs (README per-profile section)a third, workspace-style layout that exists on no observed install

<palace> = the value of --palace / MEMPALACE_PALACE_PATH / config.json:palace_path.

Repro (minimal, no wrapper)

  1. Write N drawers to a fresh parent dir <parent> (this mirrors what the writer-side layout produces):

    mkdir -p <parent>/palace
    # write into <parent>/palace/chroma.sqlite3  -> N rows in `embeddings`
  2. Run the MCP server (or status) with --palace <parent> (i.e. the parent dir, not the leaf):

    Actual: …has no chroma.sqlite3 yet. Run: mempalace mine <dir> — the parent shell is reported as empty, even though <parent>/palace/ holds N real drawers.

    Expected: either (a) the reader resolves to the same dir the writer wrote into, or (b) a loud error pointing at the sibling <parent>/palace/ that does hold data. A silent empty vault is the worst of both.

Code locations (v3.8.0, Linux)

  • mempalace/mcp_server.py:864 — sqlite_path = os.path.join(_config.palace_path, "chroma.sqlite3") (verbatim parent-relative). Related sites that assume the same leaf: mcp_server.py:954, :992, :1340.
  • mempalace/config.py:221 — DEFAULT_PALACE_PATH = ~/.mempalace/palace (leaf is the convention the rest of the codebase follows).
  • mempalace/config.py:383-393 — the resolution chain: explicit override → MEMPALACE_PALACE_PATH → config.json:palace_path → default. No site in this chain normalises a parent to a leaf.
  • mempalace/mcp_server.py:462 — mempalace_add_drawer handler; whatever path it writes to, the reader at :864 is a different dir when --palace is one level too shallow.
  • mempalace README per-profile section — documents a workspace/ layout not seen in any tested install.

Relation to #2366

#2366 reports --palace being silently ignored by init and status (the flag is dropped, everything lands at the default). This issue is a different failure mode: the flag is honoured, but the reader/writer then disagree about which subdirectory is canonical. Both are "silent, plausible-looking output" problems, and both are caught by the same suggested contract below.

Suggested fix (one canonical location + reader fallback + docs alignment)

  1. Pick one canonical layout and say so. Two options:
    • A (preferred): <palace>/chroma.sqlite3 is always the leaf. --palace is documented as "the directory that directly contains chroma.sqlite3."
    • B: <palace>/palace/chroma.sqlite3 is always the leaf. Only one of the two should be true for a given release; today both are silently tolerated.
  2. Reader fallback: when chroma.sqlite3 does not exist at <palace>/ but does exist (with rows) at <palace>/palace/, log a one-line deprecation and serve from the leaf. Symmetric for <palace>/chroma.sqlite3 present at <palace>/palace/…/palace/. This is a two-line check in resolve_palace_file() and would have made the current three-profile install work without any wrapper-level normalisation.
  3. Writer must not create a second file at the parent — today the writer leaves an empty <palace>/chroma.sqlite3 shell (from auto_init touching the dir) and the real file under <palace>/palace/. Pick the leaf.
  4. Docs: the per-profile README section must name the exact leaf it documents.

Environment

  • Python 3.11
  • mempalace 3.8.0 (pip)
  • Linux
  • Observed on three independent agent profiles, each with a writer shell + a leaf with real rows; the leaf is always the one the writer wrote to, and is always one level deeper than what the reader resolves against.

Acceptance

  • A single, documented, canonical leaf dir for chroma.sqlite3.
  • A reader-side fallback that, instead of reporting an empty vault, finds the sibling leaf with data and serves from it (with a log line).
  • README per-profile layout describes the canonical leaf, not a workspace/ tree.

Activity

1
BuboTheWise1mo ago

Re-opening with a sharper, fully code-proven framing (and an OPSEC scrub — all counts now generic).

Key change from the original wording: this is not a docs disagreement — the default, the flag, and the reader all agree the data dir is a leaf (~/.mempalace/palace). The bug is narrower and worse: the reader reads --palace verbatim with no canonicalisation, so pointing it at the parent silently opens the parent's 0-row chroma.sqlite3 instead of the leaf's populated one.

Proven against the live 3.8.0 install:

  • config.py:221 — DEFAULT_PALACE_PATH = os.path.expanduser("~/.mempalace/palace") (canonical location is a leaf)
  • mcp_server.py:864 — sqlite_path = os.path.join(_config.palace_path, "chroma.sqlite3") (verbatim; no fallback), sibling sites :954, :992, :1340
  • config.py:383–393 — resolution chain flag > env > config.json > default, so the flag can be pointed one level shallower than the default and then reads a different file
  • Live repro: same profile, --palace at the parent → 0 drawers; --palace at the leaf → N drawers (populated). Two chroma.sqlite3 files on disk, zero rows vs N rows.

Linked here as the same root-cause family as open #2366 (flag/env and config.json resolving to different paths). A single canonical-path invariant closes both.

Reader-side workaround we ship on our integration side (v2.0.27) cross-referenced in Related for context.