Bug
VectorStoreSemanticStorage.update_feature (packages/server/src/memmachine_server/semantic_memory/storage/vector_store_semantic_storage.py, main at d57f5cb36) updates the feature row and commits it, then rewrites the feature's vector record. Since upsert replaces a whole record and the row holds no embedding, it first reads the stored vector back (_get_existing_vector_record: VectorStoreCollection.get(..., return_vector=True)) and upserts it again with fresh properties. That read-modify-write fails in two ways.
1. Concurrent updates lose an embedding, on every backend. Take two updates of one feature: A with a new embedding, B changing only text or metadata. B reads the old vector, A upserts the new one, and then B upserts the old vector over it. The row keeps A's text, but the feature is searched by its old embedding from then on. Nothing serializes the two updates.
2. An update fails half-applied when its read misses. The row is committed before the read. When get does not return the vector record, update_feature raises ResourceNotFoundError("Vector record not found: ...") with the row updated and the vector record not. Whether get sees a recent write is up to the backend. On Milvus, the store's reads run at the collection's consistency level, Session by default, which waits only for the reading process's own writes. An update served by a process other than the one that created or last updated the feature can therefore miss the record, or read an older vector (failure 1 without any concurrency).
Expected
An update of a feature's text or metadata keeps the last embedding written for it. An update never fails, or applies half, because of a read of the vector store.
Fix
Don't read the vector back. Nothing reads the vector record's properties except feature_id: search maps hits to rows through it, and the rows hold every other value. So the record can carry only its vector and feature_id, and update_feature can write the vector store only when it is given a new embedding, writing the whole record then. #1663 (the port of #1598) removes the read, with a vector_uuid column in place of the property, and removes VectorStoreCollection.get, whose only production caller this is.
Bug
VectorStoreSemanticStorage.update_feature(packages/server/src/memmachine_server/semantic_memory/storage/vector_store_semantic_storage.py,mainatd57f5cb36) updates the feature row and commits it, then rewrites the feature's vector record. Sinceupsertreplaces a whole record and the row holds no embedding, it first reads the stored vector back (_get_existing_vector_record:VectorStoreCollection.get(..., return_vector=True)) and upserts it again with fresh properties. That read-modify-write fails in two ways.1. Concurrent updates lose an embedding, on every backend. Take two updates of one feature: A with a new embedding, B changing only text or metadata. B reads the old vector, A upserts the new one, and then B upserts the old vector over it. The row keeps A's text, but the feature is searched by its old embedding from then on. Nothing serializes the two updates.
2. An update fails half-applied when its read misses. The row is committed before the read. When
getdoes not return the vector record,update_featureraisesResourceNotFoundError("Vector record not found: ...")with the row updated and the vector record not. Whethergetsees a recent write is up to the backend. On Milvus, the store's reads run at the collection's consistency level,Sessionby default, which waits only for the reading process's own writes. An update served by a process other than the one that created or last updated the feature can therefore miss the record, or read an older vector (failure 1 without any concurrency).Expected
An update of a feature's text or metadata keeps the last embedding written for it. An update never fails, or applies half, because of a read of the vector store.
Fix
Don't read the vector back. Nothing reads the vector record's properties except
feature_id: search maps hits to rows through it, and the rows hold every other value. So the record can carry only its vector andfeature_id, andupdate_featurecan write the vector store only when it is given a new embedding, writing the whole record then. #1663 (the port of #1598) removes the read, with avector_uuidcolumn in place of the property, and removesVectorStoreCollection.get, whose only production caller this is.