Skip to content

fix(mcp): resolve chunked diary entries by their entry_id (#2185) - #2186

Merged
igorls merged 1 commit into
developfrom
fix/2185-diary-chunk-logical-id
Aug 8, 2026
Merged

fix(mcp): resolve chunked diary entries by their entry_id (#2185)#2186
igorls merged 1 commit into
developfrom
fix/2185-diary-chunk-logical-id

Conversation

@igorls

@igorls igorls commented Aug 8, 2026

Copy link
Copy Markdown
Member

Fixes #2185.

Problem

mempalace_diary_write returns an entry_id for every diary entry, but for entries large enough to be chunked that id is unusable — mempalace_get_drawer, mempalace_update_drawer and mempalace_delete_drawer all answer Drawer not found, and it's the only id the diary tools ever surface to MCP clients. Single-chunk entries are unaffected (their physical id equals the entry_id).

Reproduced on develop: a 5000-char entry at chunk_size=800 returns an entry_id, then get_drawer(entry_id){"error": "Drawer not found: diary_wing_..."}.

Root cause

Two metadata conventions that never meet. The diary chunking path stamps parent_entry_id on each chunk, while the logical-id read paths added in #1782 query only parent_drawer_id. Both keys mean the same thing — "physical chunk of this logical drawer" — so chunk groups written by diary_write are invisible to logical-id resolution.

This is the same bug class as #1763, which #1782 fixed for add_drawer drawers only.

Fix

Read paths now resolve either key, centralized in _PARENT_ID_KEYS so the two conventions are reconciled in one place:

Site Symptom fixed
_logical_chunk_group() — matches both keys via $or get / update / delete by entry_id
_collapse_drawer_rows() — groups on either key list_drawers returned a chunked entry as N chunk rows instead of one logical drawer
searcher._result_drawer_id() — resolves either key a search hit on a chunked entry reported the physical chunk id, so fetching it returned one chunk instead of the whole entry

The last two are beyond what the issue reports, but they're the same convention split and the $or alone does not cover them — fixing only _logical_chunk_group would leave list and search still fragmenting.

New diary writes additionally stamp parent_drawer_id alongside parent_entry_id, so the conventions converge going forward and the $or becomes pure legacy compatibility.

No migration needed. The read paths still accept the parent_entry_id-only shape, so palaces written before this fix are repaired on read. A regression test writes chunks in exactly the 3.6.0 shape to lock that in.

Notes on blast radius

  • $or is explicitly supported by all four backends (chroma, qdrant, pgvector, milvus), so the where-clause is portable.
  • A chunk carrying both keys matches both $or branches but is returned once — Chroma dedupes by physical id. The tests assert the joined content equals the original entry verbatim, which would fail on a duplicated chunk.
  • Diary chunks are written without source_file, so neighbor expansion (searcher: _expand_with_neighbors stitches unrelated chunks across MCP drawers sharing empty source_file #1580) returns early on them and is unaffected by the added key. Comment in test_closets.py updated, since it cited the parent-id key as the reason rather than the missing source_file.
  • mempalace_diary_read never used the chunk/parent logic — it renders rows directly, which is why it always looked fine. Unchanged here.
  • Two stale comments dropped: one told callers to iterate chunk_ids (documenting the bug as intended behavior), and one claimed search rejoins chunks via parent_entry_id — no search code read that key.

Tests

Five new regression tests, mirroring the add_drawer contract locked in by #1782:

  • chunked diary_writeentry_id fetches verbatim, lists as one logical drawer, deletes every chunk
  • chunked diary_write → update by entry_id rewrites the group and leaves no stale chunks
  • legacy parent_entry_id-only chunks resolve for get / list / delete (no-migration proof)
  • _result_drawer_id unit coverage for both keys, plus the plain and missing-metadata fallbacks

Full suite green: 3857 passed, 31 skipped. ruff check and ruff format --check clean.

`mempalace_diary_write` returns an `entry_id` for every diary entry, but
for entries large enough to be chunked that id was unusable: get_drawer,
update_drawer and delete_drawer all answered "Drawer not found", and
list_drawers showed the entry as N unrelated chunk rows.

Two metadata conventions never met. The diary chunking path stamped
`parent_entry_id` on each chunk, while the logical-id read paths added in
#1782 query only `parent_drawer_id`. Both keys mean the same thing --
"physical chunk of this logical drawer" -- so chunk groups written by
diary_write were invisible to logical-id resolution. Same bug class as
#1763, which #1782 fixed for `add_drawer` drawers only.

Read paths now resolve either key via `_PARENT_ID_KEYS`:

- `_logical_chunk_group()` matches both with an `$or` (fixes get /
  update / delete). All four backends support `$or`.
- `_collapse_drawer_rows()` groups on either (fixes list_drawers, which
  the `$or` alone does not cover).
- `searcher._result_drawer_id()` resolves either, so a hit on a chunked
  diary entry reports the id that fetches the whole entry rather than
  the single chunk that matched.

New diary writes also stamp `parent_drawer_id` alongside
`parent_entry_id` so the two conventions converge going forward. Because
the read paths still accept the `parent_entry_id`-only shape, palaces
written before this fix are repaired with no data migration.

Diary chunks are written without `source_file`, so neighbor expansion
(#1580) returns early on them and is unaffected by the added key.

Also drops the comment telling callers to iterate `chunk_ids` (it
documented the bug as intended behavior) and a stale claim that search
rejoins chunks via `parent_entry_id` -- no search code read that key.
@igorls
igorls requested a review from milla-jovovich as a code owner August 8, 2026 12:07
Copilot AI lite review requested due to automatic review settings August 8, 2026 12:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 011e63e5de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mempalace/mcp_server.py
branches of the ``$or`` but is still returned once -- Chroma dedupes by
physical id -- so the joined content never repeats a chunk.
"""
return {"$or": [{key: drawer_id} for key in _PARENT_ID_KEYS]}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve pushdown for parent-ID lookup

When using the Qdrant or pgvector backends, this $or makes every logical get/update/delete scan the entire collection: Qdrant's _requires_local_filter() classifies $or as local-only and _rows() consequently calls _scroll_all() without a filter, while pgvector likewise disables SQL pushdown for $or. This regresses existing chunked parent_drawer_id lookups from a server-side equality filter and can make fetching a single drawer transfer all palace rows; issue two equality-filtered queries and merge their results instead.

AGENTS.md reference: AGENTS.md:L26-L26

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes logical-id resolution for chunked diary entries so the entry_id returned by mempalace_diary_write can be used with get/update/delete/list (and search results round-trip through mempalace_get_drawer) by reconciling the parent_drawer_id vs parent_entry_id metadata conventions.

Changes:

  • Centralizes “logical parent id” resolution in mcp_server and updates chunk-group lookup and list collapsing to accept either parent-id key.
  • Updates diary chunk writes to stamp parent_drawer_id alongside legacy parent_entry_id for forward consistency.
  • Updates search hit drawer_id reporting to resolve via either parent-id key and adds regression/unit tests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
mempalace/mcp_server.py Adds shared parent-id-key helpers; resolves chunk groups and collapses list rows using either parent_drawer_id or parent_entry_id; stamps both keys on new diary chunks.
mempalace/searcher.py Makes _result_drawer_id resolve diary chunks via parent_entry_id as well as parent_drawer_id.
tests/test_mcp_server.py Adds regression tests ensuring chunked diary entry_id supports get/list/update/delete and legacy parent_entry_id-only palaces still work.
tests/test_searcher.py Adds unit coverage for _result_drawer_id behavior across both parent-id keys and fallbacks.
tests/test_closets.py Updates an explanatory comment about why diary chunks don’t enter the neighbor-expansion path.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread mempalace/mcp_server.py
Comment on lines 2667 to 2672
def _logical_chunk_group(col, drawer_id: str):
try:
result = col.get(
where={"parent_drawer_id": drawer_id},
where=_logical_parent_where(drawer_id),
include=["documents", "metadatas"],
)
Comment thread tests/test_mcp_server.py
Comment on lines +3153 to +3155
_client2, col = _get_collection(palace_path)
del _client2
assert "".join(col.get()["documents"]) == "B" * 2600, "stale chunks must not survive"
@igorls
igorls merged commit 96bd4b4 into develop Aug 8, 2026
9 checks passed
@igorls
igorls deleted the fix/2185-diary-chunk-logical-id branch August 8, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chunked diary entries cannot be fetched/updated/deleted by their entry_id — logical-id resolution only checks parent_drawer_id, not parent_entry_id

2 participants