feat: progressive disclosure + <private> tag filter - #1033
Conversation
Two privacy-minded additions to the search and write paths: 1. ``<private>...</private>`` tag handling on write. ``tool_add_drawer`` and ``tool_diary_write`` strip the marked blocks before sanitize + embed. Entries whose entire content is inside a private tag are refused outright so nothing leaks through metadata, drawer IDs, or the embedding itself. 2. Progressive disclosure on read. ``tool_search`` returns a ~30-char summary + ``drawer_id`` per hit by default; callers opt in to the full verbatim drawer text via ``full=true`` or fetch on demand via ``tool_get_drawer`` (now accepts a single ID or an array). Stored content stays verbatim — only the preview is truncated — so this does not violate the "verbatim always" principle. Supporting changes: - ``mempalace/privacy.py`` — ``redact_private`` + ``summarize_for_search`` - ``searcher.py`` — hits now carry ``drawer_id`` from chroma ids so the two-step flow can round-trip reliably - Full test coverage in ``tests/test_privacy.py`` and new progressive disclosure test class in ``tests/test_mcp_server.py`` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the new search/write semantics so LLM callers discover the two-step fetch naturally instead of re-requesting full results. - search.md: describe the default summary + drawer_id response, the ``full=true`` opt-out, and the recommended search→get_drawer flow. - help.md: add a Privacy section covering <private> tag stripping, the summary-only default, and the expanded ``mempalace_get_drawer`` (single id or array) in the write-side tool list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scanned all 233 open upstream PRs today against our open PRs and fork-ahead / planned-work items. Findings merged into README: - P2 (decay) and P3 Tier-0 (LLM rerank): both covered by MemPalace#1032 (@zackchiutw, MERGEABLE, 2026-04-19 — Weibull decay + 4-stage rerank pipeline). Older simpler version at #337. Dropped as fork work; watching MemPalace#1032. - P7 (alternative storage): formally out of scope. RFC 001 MemPalace#743 (@igorls) defines the plugin contract; four backend PRs already in flight (MemPalace#700, #381 Qdrant; MemPalace#574, MemPalace#575 LanceDB). Fork consumes, does not rebuild. - P0 (multi-label tags): still fork/upstream candidate. MemPalace#1033 (@zackchiutw) ships adjacent privacy-tag + progressive disclosure but not the full multi-label scheme. - Merged MemPalace#1023 section acknowledges complementary MemPalace#976 (felipetruman) which adds broader mine_global_lock() + HNSW num_threads pin. Gives future-us a map so we don't re-file MemPalace#1036-style duplicates.
|
Hi, thanks for the contribution. This PR has merge conflicts with Could you rebase onto If this change is no longer relevant, feel free to close the PR. (This message is part of a periodic backlog pass, sent to all open PRs that match this state.) |
|
Thanks for this contribution, and apologies for the slow turnaround.
If you'd rather not pick it back up, no problem at all — just say so and I'll close it out, and thanks either way for taking the time to send it. |
Summary
Two privacy-minded additions to the MCP search and write paths:
1.
<private>...</private>tag handling on writetool_add_drawerandtool_diary_writenow strip<private>…</private>blocks (case-insensitive, multiline, non-greedy) before sanitize + embed. Entries whose entire content sits inside a private tag are refused, so nothing leaks through metadata, drawer IDs, or the embedding itself.2. Progressive disclosure on read
tool_searchreturns a~30-charsummary +drawer_idper hit by default. Callers that need the raw text opt in viafull=true, or fetch on demand viatool_get_drawer(which now accepts a single ID or an array). Stored content stays verbatim — only the preview is truncated — so this respects the "Verbatim always" design principle inCLAUDE.md. The two-step flow also keeps agent context small when search is exploratory.Why
Private tags let users mark thoughts they want remembered locally but never fed back to the model — useful for rough reasoning, personal notes, and conversation drafts. Progressive disclosure is the read-side complement: long drawer text no longer floods the agent response when it only needed to know which memory matched.
What changes
mempalace/privacy.py(new)redact_private(text) -> (cleaned, is_fully_private)andsummarize_for_search(text, n_chars)mempalace/mcp_server.pytool_searchgainsfull=Falsedefault + schema entry;tool_get_draweraccepts str or list[str];tool_add_drawer/tool_diary_writestrip + reject fully-private contentmempalace/searcher.pydrawer_idpulled from chromaidsso the two-step fetch round-trips reliablytests/test_privacy.py(new)tests/test_mcp_server.pyTestProgressiveDisclosureclass (20+ integration tests)mempalace/instructions/search.mdfull=trueopt-out, and the recommendedsearch → get_drawerflowmempalace/instructions/help.mdCompatibility notes
tool_searchno longer returns full drawer text by default. Callers that depended on full text can passfull=trueto restore the old behavior. LLM callers typically benefit from the new default (smaller responses, clear drawer_id for follow-up).tool_get_drawer(id=...)is backwards compatible (single ID still works); new array form is additive.Example — private tag
Example — progressive disclosure
Test plan
pytest tests/test_privacy.py tests/test_searcher.py tests/test_mcp_server.py— 112 passedruff check— cleanruff format --checkon modified files — clean<private>stripping is case-insensitive (<PRIVATE>,<Private>all work)success=False+ reason fieldtool_get_draweraccepts bothid="abc"andid=["abc", "def"]tool_search(full=true)returns the pre-existing full-text response shapeNotes for reviewers
mempalace/mcp_server.py:tool_searchschema but at different call sites; a trivial merge conflict is expected if both land.~30chars backing up to the last word boundary) is hard-coded inprivacy.py. Happy to make that configurable if reviewers prefer.🤖 Generated with Claude Code