From 15f62d1d4e9072be7682bdde88a225fd5e83b7db Mon Sep 17 00:00:00 2001
From: Omer Aplak <omeraplak@gmail.com>
Date: Sun, 15 Mar 2026 09:57:23 -0700
Subject: [PATCH 1/3] fix: delete vectors when clearing messages

---
 .changeset/mean-pets-perform.md               | 21 ++++++++
 packages/core/src/memory/index.ts             | 39 +++++++++++++++
 .../core/src/memory/semantic-search.spec.ts   | 48 +++++++++++++++++++
 3 files changed, 108 insertions(+)
 create mode 100644 .changeset/mean-pets-perform.md

diff --git a/.changeset/mean-pets-perform.md b/.changeset/mean-pets-perform.md
new file mode 100644
index 000000000..6d1e67cda
--- /dev/null
+++ b/.changeset/mean-pets-perform.md
@@ -0,0 +1,21 @@
+---
+"@voltagent/core": patch
+---
+
+Fix stale semantic-search results after `Memory.clearMessages()`.
+
+Previously, `clearMessages()` removed conversation messages from storage but left vector
+embeddings behind when a vector adapter was configured. This meant semantic search could
+still return hits for cleared conversations even though the message history had been removed.
+
+## What Changed
+
+- `Memory.clearMessages(userId, conversationId)` now deletes vector entries for that
+  conversation before clearing storage
+- `Memory.clearMessages(userId)` now also deletes vector entries across all of the user's
+  conversations
+
+## Impact
+
+- Cleared conversations no longer appear in semantic search results
+- Message storage and vector storage stay in sync after cleanup
diff --git a/packages/core/src/memory/index.ts b/packages/core/src/memory/index.ts
index e7a613967..126f4070d 100644
--- a/packages/core/src/memory/index.ts
+++ b/packages/core/src/memory/index.ts
@@ -170,6 +170,20 @@ export class Memory {
     conversationId?: string,
     context?: OperationContext,
   ): Promise<void> {
+    if (this.vector) {
+      try {
+        const vectorIds = await this.getMessageVectorIdsForClear(userId, conversationId);
+        if (vectorIds.length > 0) {
+          await this.vector.deleteBatch(vectorIds);
+        }
+      } catch (error) {
+        console.warn(
+          `Failed to delete vectors while clearing messages for user ${userId}${conversationId ? ` conversation ${conversationId}` : ""}:`,
+          error,
+        );
+      }
+    }
+
     return this.storage.clearMessages(userId, conversationId, context);
   }
 
@@ -376,6 +390,31 @@ export class Memory {
     return ordered;
   }
 
+  private async getMessageVectorIdsForClear(
+    userId: string,
+    conversationId?: string,
+  ): Promise<string[]> {
+    const vectorIds = new Set<string>();
+
+    if (conversationId) {
+      const messages = await this.storage.getMessages(userId, conversationId);
+      for (const message of messages) {
+        vectorIds.add(`msg_${conversationId}_${message.id}`);
+      }
+      return Array.from(vectorIds);
+    }
+
+    const conversations = await this.storage.getConversationsByUserId(userId);
+    for (const conversation of conversations) {
+      const messages = await this.storage.getMessages(userId, conversation.id);
+      for (const message of messages) {
+        vectorIds.add(`msg_${conversation.id}_${message.id}`);
+      }
+    }
+
+    return Array.from(vectorIds);
+  }
+
   /**
    * Merge two arrays of messages, removing duplicates
    */
diff --git a/packages/core/src/memory/semantic-search.spec.ts b/packages/core/src/memory/semantic-search.spec.ts
index fbc0f4788..4a8d8cb57 100644
--- a/packages/core/src/memory/semantic-search.spec.ts
+++ b/packages/core/src/memory/semantic-search.spec.ts
@@ -455,5 +455,53 @@ describe("Memory V2 - Semantic Search", () => {
         expect.arrayContaining([`msg_${conversationId}_msg1`, `msg_${conversationId}_msg2`]),
       );
     });
+
+    it("should delete vectors when conversation messages are cleared", async () => {
+      const userId = "user123";
+      const conversationId = "conv789";
+
+      await memory.createConversation({
+        id: conversationId,
+        userId,
+        resourceId: "agent1",
+        title: "Clear Conversation",
+      });
+
+      const messages: UIMessage[] = [
+        {
+          id: "msg1",
+          role: "user",
+          parts: [{ type: "text", text: "My name is Sujal" }],
+        },
+        {
+          id: "msg2",
+          role: "assistant",
+          parts: [{ type: "text", text: "Your name is Sujal." }],
+        },
+      ];
+
+      await memory.addMessages(messages, userId, conversationId);
+
+      const beforeClear = await memory.searchSimilar("What is my name?", {
+        limit: 5,
+        filter: { userId, conversationId },
+      });
+      const deleteBatchSpy = vi.spyOn(vector, "deleteBatch");
+
+      await memory.clearMessages(userId, conversationId);
+
+      const storedMessages = await memory.getMessages(userId, conversationId);
+      const afterClear = await memory.searchSimilar("What is my name?", {
+        limit: 5,
+        filter: { userId, conversationId },
+      });
+
+      expect(beforeClear.length).toBeGreaterThan(0);
+      expect(deleteBatchSpy).toHaveBeenCalledWith(
+        expect.arrayContaining([`msg_${conversationId}_msg1`, `msg_${conversationId}_msg2`]),
+      );
+      expect(storedMessages).toHaveLength(0);
+      expect(afterClear).toHaveLength(0);
+    });
   });
 });

From 134da7829153b8532a296b38ae951d2388821d50 Mon Sep 17 00:00:00 2001
From: Omer Aplak <omeraplak@gmail.com>
Date: Sun, 15 Mar 2026 10:06:06 -0700
Subject: [PATCH 2/3] fix: paginate user-wide vector cleanup

---
 packages/core/src/memory/index.ts             | 27 ++++++--
 .../core/src/memory/semantic-search.spec.ts   | 67 +++++++++++++++++++
 2 files changed, 89 insertions(+), 5 deletions(-)

diff --git a/packages/core/src/memory/index.ts b/packages/core/src/memory/index.ts
index 126f4070d..c024e22ee 100644
--- a/packages/core/src/memory/index.ts
+++ b/packages/core/src/memory/index.ts
@@ -44,6 +44,8 @@ const isEmbeddingAdapter = (value: EmbeddingAdapterInput): value is EmbeddingAda
 const isEmbeddingAdapterConfig = (value: EmbeddingAdapterInput): value is EmbeddingAdapterConfig =>
   typeof value === "object" && value !== null && "model" in value && !isEmbeddingAdapter(value);
 
+const VECTOR_CLEAR_CONVERSATION_PAGE_SIZE = 200;
+
 const resolveEmbeddingAdapter = (
   embedding?: EmbeddingAdapterInput,
 ): EmbeddingAdapter | undefined => {
@@ -404,12 +406,27 @@ export class Memory {
       return Array.from(vectorIds);
     }
 
-    const conversations = await this.storage.getConversationsByUserId(userId);
-    for (const conversation of conversations) {
-      const messages = await this.storage.getMessages(userId, conversation.id);
-      for (const message of messages) {
-        vectorIds.add(`msg_${conversation.id}_${message.id}`);
+    let offset = 0;
+
+    while (true) {
+      const conversations = await this.storage.queryConversations({
+        userId,
+        limit: VECTOR_CLEAR_CONVERSATION_PAGE_SIZE,
+        offset,
+      });
+
+      for (const conversation of conversations) {
+        const messages = await this.storage.getMessages(userId, conversation.id);
+        for (const message of messages) {
+          vectorIds.add(`msg_${conversation.id}_${message.id}`);
+        }
       }
+
+      if (conversations.length < VECTOR_CLEAR_CONVERSATION_PAGE_SIZE) {
+        break;
+      }
+
+      offset += VECTOR_CLEAR_CONVERSATION_PAGE_SIZE;
     }
 
     return Array.from(vectorIds);
diff --git a/packages/core/src/memory/semantic-search.spec.ts b/packages/core/src/memory/semantic-search.spec.ts
index 4a8d8cb57..d6a0cdc83 100644
--- a/packages/core/src/memory/semantic-search.spec.ts
+++ b/packages/core/src/memory/semantic-search.spec.ts
@@ -8,6 +8,7 @@ import type { EmbeddingAdapter } from "./adapters/embedding/types";
 import { InMemoryStorageAdapter } from "./adapters/storage/in-memory";
 import { InMemoryVectorAdapter } from "./adapters/vector/in-memory";
 import { Memory } from "./index";
+import type { ConversationQueryOptions } from "./types";
 
 // Mock embedding adapter
 class MockEmbeddingAdapter implements EmbeddingAdapter {
@@ -32,6 +33,24 @@ class MockEmbeddingAdapter implements EmbeddingAdapter {
   }
 }
 
+class PagedConversationStorageAdapter extends InMemoryStorageAdapter {
+  constructor(private readonly defaultPageSize: number) {
+    super();
+  }
+
+  override async getConversationsByUserId(
+    userId: string,
+    options?: Omit<ConversationQueryOptions, "userId">,
+  ) {
+    return this.queryConversations({
+      ...options,
+      userId,
+      limit: options?.limit ?? this.defaultPageSize,
+      offset: options?.offset ?? 0,
+    });
+  }
+}
+
 describe("Memory V2 - Semantic Search", () => {
   let memory: Memory;
   let storage: InMemoryStorageAdapter;
@@ -503,5 +522,53 @@ describe("Memory V2 - Semantic Search", () => {
       expect(storedMessages).toHaveLength(0);
       expect(afterClear).toHaveLength(0);
     });
+
+    it("should paginate user-wide vector cleanup across all conversations", async () => {
+      const userId = "paged-user";
+      const pagedStorage = new PagedConversationStorageAdapter(2);
+      const pagedVector = new InMemoryVectorAdapter();
+      const pagedMemory = new Memory({
+        storage: pagedStorage,
+        embedding: new MockEmbeddingAdapter(),
+        vector: pagedVector,
+      });
+
+      for (const conversationId of ["conv-1", "conv-2", "conv-3"]) {
+        await pagedMemory.createConversation({
+          id: conversationId,
+          userId,
+          resourceId: "agent1",
+          title: conversationId,
+        });
+
+        await pagedMemory.addMessage(
+          {
+            id: `msg-${conversationId}`,
+            role: "user",
+            parts: [{ type: "text", text: `Memory from ${conversationId}` }],
+          },
+          userId,
+          conversationId,
+        );
+      }
+
+      const beforeClear = await pagedMemory.searchSimilar("Memory from conv-3", {
+        limit: 5,
+        filter: { userId },
+      });
+
+      await pagedMemory.clearMessages(userId);
+
+      const afterClear = await pagedMemory.searchSimilar("Memory from conv-3", {
+        limit: 5,
+        filter: { userId },
+      });
+
+      expect(beforeClear.length).toBeGreaterThan(0);
+      expect(await pagedMemory.getMessages(userId, "conv-1")).toHaveLength(0);
+      expect(await pagedMemory.getMessages(userId, "conv-2")).toHaveLength(0);
+      expect(await pagedMemory.getMessages(userId, "conv-3")).toHaveLength(0);
+      expect(afterClear).toHaveLength(0);
+    });
   });
 });

From 2acd40a707f9ece7a5be8fea60e67f28cd4f6d31 Mon Sep 17 00:00:00 2001
From: Omer Aplak <omeraplak@gmail.com>
Date: Sun, 15 Mar 2026 10:18:45 -0700
Subject: [PATCH 3/3] fix: handle paged conversation cleanup

---
 packages/core/src/memory/index.ts                |  7 ++++---
 packages/core/src/memory/semantic-search.spec.ts | 14 ++++++--------
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/packages/core/src/memory/index.ts b/packages/core/src/memory/index.ts
index c024e22ee..a63206f2f 100644
--- a/packages/core/src/memory/index.ts
+++ b/packages/core/src/memory/index.ts
@@ -406,9 +406,10 @@ export class Memory {
       return Array.from(vectorIds);
     }
 
+    const totalConversations = await this.storage.countConversations({ userId });
     let offset = 0;
 
-    while (true) {
+    while (offset < totalConversations) {
       const conversations = await this.storage.queryConversations({
         userId,
         limit: VECTOR_CLEAR_CONVERSATION_PAGE_SIZE,
@@ -422,11 +423,11 @@ export class Memory {
         }
       }
 
-      if (conversations.length < VECTOR_CLEAR_CONVERSATION_PAGE_SIZE) {
+      if (conversations.length === 0) {
         break;
       }
 
-      offset += VECTOR_CLEAR_CONVERSATION_PAGE_SIZE;
+      offset += conversations.length;
     }
 
     return Array.from(vectorIds);
diff --git a/packages/core/src/memory/semantic-search.spec.ts b/packages/core/src/memory/semantic-search.spec.ts
index d6a0cdc83..8ba6f03b3 100644
--- a/packages/core/src/memory/semantic-search.spec.ts
+++ b/packages/core/src/memory/semantic-search.spec.ts
@@ -38,15 +38,13 @@ class PagedConversationStorageAdapter extends InMemoryStorageAdapter {
     super();
   }
 
-  override async getConversationsByUserId(
-    userId: string,
-    options?: Omit<ConversationQueryOptions, "userId">,
-  ) {
-    return this.queryConversations({
+  override async queryConversations(options: ConversationQueryOptions) {
+    const limit = Math.min(options.limit ?? Number.POSITIVE_INFINITY, this.defaultPageSize);
+
+    return super.queryConversations({
       ...options,
-      userId,
-      limit: options?.limit ?? this.defaultPageSize,
-      offset: options?.offset ?? 0,
+      limit,
+      offset: options.offset ?? 0,
     });
   }
 }
