Summary
update_memory silently mis-resolves the entities field on a small but consistent fraction of edits, while the text field is always written correctly. The wrong entity is not random noise — it's an existing, valid entity already in the bank, sometimes a completely unrelated real person, so the result reads as a normal, well-formed record. There is no error, no partial-failure indicator, and 200 OK is returned on the corrupted writes exactly as on the correct ones.
Environment
- Package:
hindsight-all 0.9.0 (self-hosted, pip install, embedded Postgres backend)
- Called via the Hindsight MCP server, from Claude Code
- macOS 25.3.0 (Darwin arm64)
Repro
- Have a bank with several hundred existing memories and an entity graph that includes near-duplicate/related names (e.g.
FirstName / FirstName LastName, or multiple people who share a surname).
- Run a bulk
update_memory correction across many records — in this case, 657 calls each replacing a generic placeholder entity (User) with the correct person's real name in a record's entities list (and updating text to match).
- After the batch, re-fetch every edited record (
list_memories or per-id fetch) and diff the actual stored entities array against the entities you submitted for that call.
Observed
text field: 657/657 correct. No mismatches anywhere.
entities field: 25/523 entity-bearing edits (~4.8%) came back different from what was submitted, and none of the 25 are cosmetic — e.g. (names/labels below are placeholders standing in for real values, structure and specificity preserved):
- Submitted
["topic A", "Person B", "Person C", "Person C Full Name"] → stored ["topic A", "Person B", "Person C", "Person D Full Name"] — an existing, unrelated real person (Person D, who shares a surname with the intended target) was swapped in on a high-value financial fact.
- Submitted
["Medication X", "Person C Full Name"] (a medication name + a person) → stored ["Person E", "Person C Full Name"] — the medication name silently became a different existing entity, an unrelated person, even though nothing about this edit touched that entity.
- Submitted
["Person C Full Name", "Account Label 1", "topic"] → stored ["Person C Full Name", "Account Label 2", "topic"] — an untouched account-name entity was swapped for a different existing account entity belonging to the same person.
- An entity not targeted by the edit at all (present unchanged in both old and new lists) came back as a different real contact already in the bank.
- 11 of the 25 simply reverted the
entities field to the pre-edit value despite text updating correctly and the new entities being submitted.
- Deterministic, not a race condition. A single isolated sequential retry (zero concurrency) reproduced the exact same wrong output. A batch of 8 parallel retries reproduced the identical wrong output 8/8 times. Same input → same wrong output, every time.
- Not fixable by resubmission, for 24 of the 25 — retrying with the same or a cleaned-up entity list changed nothing.
- One partial mechanism found: 1 of the 25 had a near-duplicate short/long-form pair in the submitted list (e.g.
"Person C" and "Person C Full Name" together). Dropping the short form and resubmitting the long form alone fixed that one record. This does not explain the other 24 — several of those have no duplicate-form pair anywhere in the submitted list (e.g. the medication and account-label cases above).
- Clusters by source document, not randomly distributed. Grouping the 25 mismatches by the source document the memory was originally extracted from: 17 of 25 trace back to just 4 source documents (5, 5, 4, and 3 mismatches respectively), with 8 further isolated singletons across 8 other documents. Fields failing within the same source document fail in unrelated ways (different entities, different substitution targets) — it isn't one bad value propagating, it looks scoped to something about the document/chunk rather than the specific entity string.
Expected
Either the stored entities field matches what was submitted, or the call fails/reports a conflict — not a silent, confidently-wrong substitution returned as a successful write.
Why this matters more than a normal bug
text was correct on every single edit, so a spot-check of a few records looks completely clean. The only way this surfaced was a deliberate full post-hoc diff (re-fetch all 657 edited records, compare entities arrays field-by-field against what was sent). Without that audit, a sensitive financial fact would have sat silently attached to the wrong real person (an actual family member of the intended person, already an entity in the bank) indefinitely — discoverable only if someone later asked a reflect/graph-scoped question about that wrong person and got an answer partly built from a fact that was never theirs.
What I haven't verified
- Root cause is unconfirmed — the document-clustering pattern suggests something cache- or index-scoped in the entity-resolution path of
update_memory's write, but this is inference from black-box I/O only; I didn't have access to server internals/logs.
- Not tested against the Docker distribution, only the pip (
hindsight-all) self-hosted install with the embedded Postgres backend — unknown whether this is install-specific.
- Not tested against a fresh/small bank — this bank had ~2,000 pre-existing facts and an entity graph with several genuinely similar/overlapping names, which may or may not be a precondition.
Data
I have the full input/output diff for all 25 mismatched records (record id, expected entities, actual entities) and the complete 657-record batch that was submitted. The real values are personal data, so I've kept this report to structurally-accurate placeholders — happy to provide an anonymized version of the diff (ids + expected/actual shape, values replaced with consistent placeholders) if that would help debugging.
Summary
update_memorysilently mis-resolves theentitiesfield on a small but consistent fraction of edits, while thetextfield is always written correctly. The wrong entity is not random noise — it's an existing, valid entity already in the bank, sometimes a completely unrelated real person, so the result reads as a normal, well-formed record. There is no error, no partial-failure indicator, and200 OKis returned on the corrupted writes exactly as on the correct ones.Environment
hindsight-all0.9.0 (self-hosted, pip install, embedded Postgres backend)Repro
FirstName/FirstName LastName, or multiple people who share a surname).update_memorycorrection across many records — in this case, 657 calls each replacing a generic placeholder entity (User) with the correct person's real name in a record'sentitieslist (and updatingtextto match).list_memoriesor per-id fetch) and diff the actual storedentitiesarray against theentitiesyou submitted for that call.Observed
textfield: 657/657 correct. No mismatches anywhere.entitiesfield: 25/523 entity-bearing edits (~4.8%) came back different from what was submitted, and none of the 25 are cosmetic — e.g. (names/labels below are placeholders standing in for real values, structure and specificity preserved):["topic A", "Person B", "Person C", "Person C Full Name"]→ stored["topic A", "Person B", "Person C", "Person D Full Name"]— an existing, unrelated real person (Person D, who shares a surname with the intended target) was swapped in on a high-value financial fact.["Medication X", "Person C Full Name"](a medication name + a person) → stored["Person E", "Person C Full Name"]— the medication name silently became a different existing entity, an unrelated person, even though nothing about this edit touched that entity.["Person C Full Name", "Account Label 1", "topic"]→ stored["Person C Full Name", "Account Label 2", "topic"]— an untouched account-name entity was swapped for a different existing account entity belonging to the same person.entitiesfield to the pre-edit value despitetextupdating correctly and the new entities being submitted."Person C"and"Person C Full Name"together). Dropping the short form and resubmitting the long form alone fixed that one record. This does not explain the other 24 — several of those have no duplicate-form pair anywhere in the submitted list (e.g. the medication and account-label cases above).Expected
Either the stored
entitiesfield matches what was submitted, or the call fails/reports a conflict — not a silent, confidently-wrong substitution returned as a successful write.Why this matters more than a normal bug
textwas correct on every single edit, so a spot-check of a few records looks completely clean. The only way this surfaced was a deliberate full post-hoc diff (re-fetch all 657 edited records, compareentitiesarrays field-by-field against what was sent). Without that audit, a sensitive financial fact would have sat silently attached to the wrong real person (an actual family member of the intended person, already an entity in the bank) indefinitely — discoverable only if someone later asked areflect/graph-scoped question about that wrong person and got an answer partly built from a fact that was never theirs.What I haven't verified
update_memory's write, but this is inference from black-box I/O only; I didn't have access to server internals/logs.hindsight-all) self-hosted install with the embedded Postgres backend — unknown whether this is install-specific.Data
I have the full input/output diff for all 25 mismatched records (record id, expected
entities, actualentities) and the complete 657-record batch that was submitted. The real values are personal data, so I've kept this report to structurally-accurate placeholders — happy to provide an anonymized version of the diff (ids + expected/actual shape, values replaced with consistent placeholders) if that would help debugging.