diff --git a/docs/core-concepts/memory-operations/add.mdx b/docs/core-concepts/memory-operations/add.mdx
index aa92dbeb7..23d9f772d 100644
--- a/docs/core-concepts/memory-operations/add.mdx
+++ b/docs/core-concepts/memory-operations/add.mdx
@@ -15,6 +15,7 @@ Adding memory is how Mem0 captures useful details from a conversation so your ag
- **Infer**: Controls whether Mem0 extracts structured memories (`infer=True`, default) or stores raw messages.
- **Metadata**: Optional filters (e.g., `{"category": "movie_recommendations"}`) that improve retrieval later.
- **User / Session identifiers**: `user_id`, `agent_id`, `app_id`, or `run_id` that scope the memory for future searches.
+- **expiration_date**: Optional `YYYY-MM-DD` date after which the memory is treated as expired. Use `expirationDate` in the JavaScript SDKs. Expired memories are hidden from `search` and `get_all` unless you pass `show_expired` (`showExpired` in JavaScript); fetching by ID still returns them.
## How does it work?
@@ -105,6 +106,9 @@ result = m.add(messages, user_id="alice", metadata={"category": "movie_recommend
# Optionally store raw messages without inference
result = m.add(messages, user_id="alice", metadata={"category": "movie_recommendations"}, infer=False)
+
+# Optionally set an expiration date (YYYY-MM-DD)
+result = m.add(messages, user_id="alice", expiration_date="2030-01-31")
```
```javascript JavaScript
@@ -123,6 +127,12 @@ const result = memory.add(messages, {
userId: "alice",
metadata: { category: "preferences" }
});
+
+// Optionally set an expiration date (YYYY-MM-DD)
+const expiring = memory.add(messages, {
+ userId: "alice",
+ expirationDate: "2030-01-31",
+});
```
diff --git a/docs/core-concepts/memory-operations/update.mdx b/docs/core-concepts/memory-operations/update.mdx
index d8340c26a..7c5084caf 100644
--- a/docs/core-concepts/memory-operations/update.mdx
+++ b/docs/core-concepts/memory-operations/update.mdx
@@ -12,7 +12,7 @@ Mem0’s update operation lets you fix or enrich an existing memory without dele
## Key terms
- **memory_id**: Unique identifier returned by `add` or `search` results.
-- **text** / **data**: New content that replaces the stored memory value.
+- **text**: New content that replaces the stored memory value. In the Python OSS SDK, `data` is a deprecated alias for `text`.
- **metadata**: Optional key-value pairs you update alongside the text.
- **timestamp**: Unix epoch (int/float) or ISO 8601 string to override the memory's timestamp.
- **batch_update**: Platform API that edits multiple memories in a single request.
@@ -110,17 +110,47 @@ from mem0 import Memory
memory = Memory()
+# Replace the content
memory.update(
memory_id="mem_123",
- data="Alex now prefers decaf coffee",
+ text="Alex now prefers decaf coffee",
+)
+
+# Update content plus metadata and an expiration date (None clears it)
+memory.update(
+ memory_id="mem_123",
+ text="Alex now prefers decaf coffee",
+ metadata={"category": "preferences"},
+ expiration_date="2030-01-31",
)
```
-```
+
+```javascript JavaScript
+import { Memory } from "mem0ai/oss";
+
+const memory = new Memory();
+
+// Replace the content
+await memory.update("mem_123", { text: "Alex now prefers decaf coffee" });
+
+// Update content plus metadata and an expiration date (null clears it)
+await memory.update("mem_123", {
+ text: "Alex now prefers decaf coffee",
+ metadata: { category: "preferences" },
+ expirationDate: "2030-01-31",
+});
+
+// Update metadata only, leaving the stored text untouched
+await memory.update("mem_123", { metadata: { category: "preferences" } });
```
- OSS JavaScript SDK does not expose `update` yet: use the REST API or Python SDK when self-hosting.
+ In both OSS SDKs the content is optional — pass only `metadata` and/or an expiration date to update those while keeping the existing content. At least one of the three must be provided, otherwise the call raises.
+
+
+
+ `data` is a deprecated alias for `text` in both OSS SDKs (`data=` in Python, `{ data: ... }` in JavaScript). It still works but logs a warning; prefer `text`. In JavaScript, passing a bare string is shorthand for `{ text }`, so `update(memoryId, "new text")` also still works.
## Tips
@@ -138,7 +168,7 @@ memory.update(
| Capability | Mem0 Platform | Mem0 OSS |
| --- | --- | --- |
-| Update call | `client.update(memory_id, {...})` | `memory.update(memory_id, data=...)` |
+| Update call | `client.update(memory_id, {...})` | `memory.update(memory_id, text=...)` |
| Batch updates | `client.batch_update` (up to 1000 memories) | Script your own loop or bulk job |
| Dashboard visibility | Inspect updates in the UI | Inspect via logs or custom tooling |
| Immutable handling | Returns descriptive error | Raises exception: delete and re-add |
diff --git a/docs/llms.txt b/docs/llms.txt
index 9adece671..304eb60fc 100644
--- a/docs/llms.txt
+++ b/docs/llms.txt
@@ -61,7 +61,7 @@ client.get_all(user_id="alice")
client.get(memory_id="")
# Update
-client.update(memory_id="", data="Alice loves mountain hiking")
+client.update(memory_id="", text="Alice loves mountain hiking")
# Delete
client.delete(memory_id="")
@@ -118,7 +118,7 @@ m.get_all(user_id="alice")
m.get(memory_id="")
# Update
-m.update(memory_id="", data="Alice loves mountain hiking")
+m.update(memory_id="", text="Alice loves mountain hiking")
# Delete
m.delete(memory_id="")
diff --git a/docs/open-source/features/async-memory.mdx b/docs/open-source/features/async-memory.mdx
index 333ab1895..b0bfface6 100644
--- a/docs/open-source/features/async-memory.mdx
+++ b/docs/open-source/features/async-memory.mdx
@@ -36,7 +36,7 @@ icon: "bolt"
| Search memories | `await memory.search(...)` | Returns dict with `results`, identical shape. |
| List memories | `await memory.get_all(...)` | Filter by `user_id`, `agent_id`, `run_id`. |
| Retrieve memory | `await memory.get(memory_id=...)` | Raises `ValueError` if ID is invalid. |
- | Update memory | `await memory.update(memory_id=..., data=...)` | Accepts partial updates. |
+ | Update memory | `await memory.update(memory_id=..., text=...)` | Accepts partial updates. |
| Delete memory | `await memory.delete(memory_id=...)` | Returns confirmation payload. |
| Delete in bulk | `await memory.delete_all(...)` | Requires at least one scope filter. |
| History | `await memory.history(memory_id=...)` | Fetches change log for auditing. |
@@ -185,7 +185,7 @@ specific_memory = await memory.get(memory_id="memory-id-here")
# Update a memory
updated_memory = await memory.update(
memory_id="memory-id-here",
- data="I'm travelling to Seattle"
+ text="I'm travelling to Seattle"
)
# Delete a memory
diff --git a/docs/platform/advanced-memory-operations.mdx b/docs/platform/advanced-memory-operations.mdx
index c1cb8a689..dc43a55c3 100644
--- a/docs/platform/advanced-memory-operations.mdx
+++ b/docs/platform/advanced-memory-operations.mdx
@@ -129,7 +129,7 @@ matches = await memory.search(
```python
await memory.update(
memory_id=matches["results"][0]["id"],
- data="Morgan avoids shellfish and prefers boutique hotels in central Tokyo.",
+ text="Morgan avoids shellfish and prefers boutique hotels in central Tokyo.",
)
```
diff --git a/mem0-ts/src/oss/README.md b/mem0-ts/src/oss/README.md
index d30b1bb57..3c9ab6742 100644
--- a/mem0-ts/src/oss/README.md
+++ b/mem0-ts/src/oss/README.md
@@ -110,14 +110,42 @@ You only need to provide API keys - all other settings are optional.
### Methods
- `add(messages: string | Message[], userId?: string, ...): Promise`
+ - Options include `metadata`, `infer`, and `expirationDate` (a `YYYY-MM-DD` date after which the memory is treated as expired).
- `search(query: string, userId?: string, ...): Promise`
+ - Expired memories are omitted unless you pass `showExpired: true`.
- `get(memoryId: string): Promise`
-- `update(memoryId: string, data: string): Promise<{ message: string }>`
+ - Fetching by ID returns the memory even if it has expired.
+- `getAll(options): Promise`
+ - Expired memories are omitted unless you pass `showExpired: true`.
+- `update(memoryId: string, config: string | UpdateMemoryOptions): Promise<{ message: string }>`
+ - `UpdateMemoryOptions` is `{ text?, data?, metadata?, expirationDate? }`. At least one must be
+ provided; omitted fields are left untouched, and `expirationDate: null` clears an existing
+ expiry. `data` is a deprecated alias for `text`.
+ - Passing a bare string is shorthand for `{ text }`, so `update(memoryId, "new text")` still works.
- `delete(memoryId: string): Promise<{ message: string }>`
- `deleteAll(userId?: string, ...): Promise<{ message: string }>`
- `history(memoryId: string): Promise`
- `reset(): Promise`
+```typescript
+// Replace the content
+await memory.update(memoryId, { text: "Alex now prefers decaf coffee" });
+
+// Update metadata only, leaving the stored text untouched
+await memory.update(memoryId, { metadata: { category: "preferences" } });
+
+// Expire the memory on a given day, or clear an existing expiry
+await memory.update(memoryId, { expirationDate: "2030-01-31" });
+await memory.update(memoryId, { expirationDate: null });
+
+// Include expired memories in reads
+await memory.getAll({ filters: { user_id: "alice" }, showExpired: true });
+await memory.search("coffee", {
+ filters: { user_id: "alice" },
+ showExpired: true,
+});
+```
+
### Try the Example
We provide a comprehensive example in `examples/basic.ts` that demonstrates all the features including:
diff --git a/mem0-ts/src/oss/examples/basic.ts b/mem0-ts/src/oss/examples/basic.ts
index 28de83834..a43d95e27 100644
--- a/mem0-ts/src/oss/examples/basic.ts
+++ b/mem0-ts/src/oss/examples/basic.ts
@@ -73,10 +73,9 @@ async function runTests(memory: Memory) {
}
// Updating this memory
- const result4 = await memory.update(
- result1.results[0].id,
- "I love India, it is my favorite country.",
- );
+ const result4 = await memory.update(result1.results[0].id, {
+ text: "I love India, it is my favorite country.",
+ });
console.log("Updated memory:", result4);
// Get all memories
diff --git a/mem0-ts/src/oss/examples/utils/test-utils.ts b/mem0-ts/src/oss/examples/utils/test-utils.ts
index 83c6d0913..65fafd40e 100644
--- a/mem0-ts/src/oss/examples/utils/test-utils.ts
+++ b/mem0-ts/src/oss/examples/utils/test-utils.ts
@@ -55,10 +55,9 @@ export async function runTests(memory: Memory) {
}
// Updating this memory
- const result4 = await memory.update(
- result1.results[0].id,
- "I love India, it is my favorite country.",
- );
+ const result4 = await memory.update(result1.results[0].id, {
+ text: "I love India, it is my favorite country.",
+ });
console.log("Updated memory:", result4);
// Get all memories
diff --git a/mem0-ts/src/oss/src/memory/index.ts b/mem0-ts/src/oss/src/memory/index.ts
index b571e3912..5d8aea211 100644
--- a/mem0-ts/src/oss/src/memory/index.ts
+++ b/mem0-ts/src/oss/src/memory/index.ts
@@ -36,6 +36,7 @@ import {
SearchMemoryOptions,
DeleteAllMemoryOptions,
GetAllMemoryOptions,
+ UpdateMemoryOptions,
UpdateProjectOptions,
} from "./memory.types";
import { parse_vision_messages } from "../utils/memory";
@@ -72,6 +73,8 @@ import {
ScoredResult,
} from "../utils/scoring";
import { getDefaultVectorStoreDbPath } from "../utils/sqlite";
+import { logger } from "../utils/logger";
+import { normalizeExpirationDate, payloadIsExpired } from "../utils/expiration";
import { getOrCreateMem0UserId } from "../../../client/config";
// Entity params that must be passed via filters - check both snake_case and camelCase
@@ -713,6 +716,11 @@ export class Memory {
if (agentId) filters.agent_id = metadata.agent_id = agentId;
if (runId) filters.run_id = metadata.run_id = runId;
+ // Normalize expiration date into the stored metadata (round-trips via get()).
+ if (config.expirationDate != null) {
+ metadata.expiration_date = normalizeExpirationDate(config.expirationDate);
+ }
+
if (!filters.user_id && !filters.agent_id && !filters.run_id) {
throw new Error(
"One of the filters: userId, agentId or runId is required!",
@@ -1307,7 +1315,12 @@ export class Memory {
: {};
await this._ensureInitialized();
- const { topK = 20, threshold = 0.1, explain = false } = config;
+ const {
+ topK = 20,
+ threshold = 0.1,
+ explain = false,
+ showExpired = false,
+ } = config;
await this._captureEvent("search", {
query_length: query.length,
@@ -1475,11 +1488,13 @@ export class Memory {
}
// Step 7: Build candidate set from semantic results
- const candidates = semanticResults.map((mem) => ({
- id: String(mem.id),
- score: mem.score ?? 0,
- payload: mem.payload || {},
- }));
+ const candidates = semanticResults
+ .filter((mem) => showExpired || !payloadIsExpired(mem.payload))
+ .map((mem) => ({
+ id: String(mem.id),
+ score: mem.score ?? 0,
+ payload: mem.payload || {},
+ }));
// Step 8: Score and rank
const scoredResults = scoreAndRank(
@@ -1563,11 +1578,49 @@ export class Memory {
return result;
}
- async update(memoryId: string, data: string): Promise<{ message: string }> {
+ async update(
+ memoryId: string,
+ config: string | UpdateMemoryOptions,
+ ): Promise<{ message: string }> {
await this._ensureInitialized();
await this._captureEvent("update", { memory_id: memoryId });
- const embedding = await this.embedder.embed(data);
- await this.updateMemory(memoryId, data, { [data]: embedding });
+
+ const options: UpdateMemoryOptions =
+ typeof config === "string" ? { text: config } : config;
+
+ const { data, metadata, expirationDate } = options;
+ let text = options.text;
+
+ if (data != null) {
+ logger.warn(
+ "The `data` option of update() is deprecated and will be removed in " +
+ "the next major release. Use `text` instead.",
+ );
+ if (text == null) {
+ text = data;
+ }
+ }
+
+ if (text == null && metadata == null && expirationDate === undefined) {
+ throw new Error(
+ "At least one of text, metadata, or expirationDate must be provided.",
+ );
+ }
+
+ const updateMetadata: Record = { ...metadata };
+ if (expirationDate !== undefined) {
+ updateMetadata.expiration_date =
+ expirationDate === null
+ ? null
+ : normalizeExpirationDate(expirationDate);
+ }
+
+ const existingEmbeddings: Record = {};
+ if (text != null) {
+ existingEmbeddings[text] = await this.embedder.embed(text);
+ }
+
+ await this.updateMemory(memoryId, text, existingEmbeddings, updateMetadata);
const result = { message: "Memory updated successfully!" };
await this._displayFirstRunNotice("update");
return result;
@@ -1704,7 +1757,7 @@ export class Memory {
await this._ensureInitialized();
- const { topK = 20 } = config;
+ const { topK = 20, showExpired = false } = config;
// Validate and trim entity IDs in filters. Drop keys that resolve to
// undefined so downstream vector stores don't receive
@@ -1733,7 +1786,13 @@ export class Memory {
);
}
- const [memories] = await this.vectorStore.list(filters, topK);
+ // Over-fetch so expired memories dropped below still leave topK survivors.
+ const fetchLimit = showExpired ? topK : Math.max(topK * 4, 60);
+ const [memories] = await this.vectorStore.list(filters, fetchLimit);
+
+ const visibleMemories = showExpired
+ ? memories
+ : memories.filter((mem) => !payloadIsExpired(mem.payload));
const excludedKeys = new Set([
"user_id",
@@ -1746,7 +1805,7 @@ export class Memory {
"textLemmatized",
"attributedTo",
]);
- const results = memories.map((mem) => ({
+ const results = visibleMemories.slice(0, topK).map((mem) => ({
id: mem.id,
memory: mem.payload.data,
hash: mem.payload.hash,
@@ -1807,7 +1866,7 @@ export class Memory {
private async updateMemory(
memoryId: string,
- data: string,
+ data: string | undefined,
existingEmbeddings: Record,
metadata: Record = {},
): Promise {
@@ -1817,15 +1876,24 @@ export class Memory {
}
const prevValue = existingMemory.payload.data;
+ // Metadata-only update: fall back to the stored text so we can re-index it.
+ const newData = data ?? prevValue;
+ if (typeof newData !== "string") {
+ throw new Error(
+ `Memory with ID ${memoryId} does not have text content to update`,
+ );
+ }
+ const textChanged = newData !== prevValue;
+
const embedding =
- existingEmbeddings[data] || (await this.embedder.embed(data));
+ existingEmbeddings[newData] || (await this.embedder.embed(newData));
const newMetadata = {
...existingMemory.payload,
...metadata,
- data,
- hash: createHash("md5").update(data).digest("hex"),
- textLemmatized: lemmatizeForBm25(data),
+ data: newData,
+ hash: createHash("md5").update(newData).digest("hex"),
+ textLemmatized: lemmatizeForBm25(newData),
createdAt: existingMemory.payload.createdAt,
updatedAt: new Date().toISOString(),
};
@@ -1834,20 +1902,22 @@ export class Memory {
await this.db.addHistory(
memoryId,
prevValue,
- data,
+ newData,
"UPDATE",
newMetadata.createdAt,
newMetadata.updatedAt,
);
- // Entity-store cleanup: strip this memory's id from old-text entities,
- // then re-extract entities from the new text and link them back.
- try {
- const sessionFilters = this._sessionFiltersFromPayload(newMetadata);
- await this._removeMemoryFromEntityStore(memoryId, sessionFilters);
- await this._linkEntitiesForMemory(memoryId, data, sessionFilters);
- } catch (e) {
- console.warn(`Entity store cleanup/link failed during update: ${e}`);
+ // Entity-store cleanup only when the text changed: strip this memory's id
+ // from old-text entities, then re-extract from the new text and link back.
+ if (textChanged) {
+ try {
+ const sessionFilters = this._sessionFiltersFromPayload(newMetadata);
+ await this._removeMemoryFromEntityStore(memoryId, sessionFilters);
+ await this._linkEntitiesForMemory(memoryId, newData, sessionFilters);
+ } catch (e) {
+ console.warn(`Entity store cleanup/link failed during update: ${e}`);
+ }
}
return memoryId;
diff --git a/mem0-ts/src/oss/src/memory/memory.types.ts b/mem0-ts/src/oss/src/memory/memory.types.ts
index c125b61b3..861db25f7 100644
--- a/mem0-ts/src/oss/src/memory/memory.types.ts
+++ b/mem0-ts/src/oss/src/memory/memory.types.ts
@@ -12,6 +12,22 @@ export interface AddMemoryOptions extends Entity {
filters?: SearchFilters;
infer?: boolean;
timestamp?: number | string | Date | null;
+ /** Date (YYYY-MM-DD) after which the memory is considered expired. */
+ expirationDate?: string | null;
+}
+
+export interface UpdateMemoryOptions {
+ /** New content to update the memory with. */
+ text?: string;
+ /**
+ * New content to update the memory with.
+ * @deprecated Use `text` instead. Will be removed in the next major release.
+ */
+ data?: string;
+ /** Metadata merged into the memory's existing metadata. */
+ metadata?: Record;
+ /** Date (YYYY-MM-DD) after which the memory expires, or `null` to clear it. */
+ expirationDate?: string | null;
}
export interface SearchMemoryOptions {
@@ -20,11 +36,15 @@ export interface SearchMemoryOptions {
threshold?: number;
explain?: boolean;
referenceDate?: number | string | Date | null;
+ /** Include expired memories in the results. Defaults to false. */
+ showExpired?: boolean;
}
export interface GetAllMemoryOptions {
topK?: number;
filters?: SearchFilters;
+ /** Include expired memories in the results. Defaults to false. */
+ showExpired?: boolean;
}
export interface DeleteAllMemoryOptions extends Entity {}
diff --git a/mem0-ts/src/oss/src/utils/expiration.ts b/mem0-ts/src/oss/src/utils/expiration.ts
new file mode 100644
index 000000000..ad4710ced
--- /dev/null
+++ b/mem0-ts/src/oss/src/utils/expiration.ts
@@ -0,0 +1,52 @@
+/**
+ * Expiration date handling for memories.
+ *
+ * Memories may carry an `expiration_date` (YYYY-MM-DD) after which they are
+ * hidden from `getAll()` and `search()` unless `showExpired` is set.
+ */
+
+const EXPIRATION_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
+
+function todayUtc(): string {
+ return new Date().toISOString().slice(0, 10);
+}
+
+/**
+ * Normalize a user-supplied expiration date to a YYYY-MM-DD string.
+ *
+ * Deliberately stricter than `new Date(value)`, which accepts formats the
+ * Python SDK rejects ("12/31/2099", "2099") and resolves them against the
+ * local timezone, shifting the calendar day. It also silently rolls invalid
+ * dates over — `new Date("2099-02-30T00:00:00Z")` yields March 2nd.
+ */
+export function normalizeExpirationDate(value: string): string {
+ const match = EXPIRATION_DATE_PATTERN.exec(value);
+ if (match) {
+ const [, year, month, day] = match;
+ const parsed = new Date(`${value}T00:00:00Z`);
+ if (
+ !Number.isNaN(parsed.getTime()) &&
+ parsed.getUTCFullYear() === Number(year) &&
+ parsed.getUTCMonth() === Number(month) - 1 &&
+ parsed.getUTCDate() === Number(day)
+ ) {
+ return value;
+ }
+ }
+ throw new Error("expirationDate must be a valid date in YYYY-MM-DD format.");
+}
+
+/** True when the payload carries an expiration date strictly before today (UTC). */
+export function payloadIsExpired(
+ payload: Record | null | undefined,
+) {
+ const raw = payload?.expiration_date;
+ if (!raw) return false;
+ try {
+ // YYYY-MM-DD sorts lexicographically the same way it sorts chronologically.
+ return normalizeExpirationDate(String(raw)) < todayUtc();
+ } catch {
+ // Unparseable stored value: treat as non-expiring rather than hiding data.
+ return false;
+ }
+}
diff --git a/mem0-ts/src/oss/tests/memory.crud.test.ts b/mem0-ts/src/oss/tests/memory.crud.test.ts
index 55db5d5f4..de2565c16 100644
--- a/mem0-ts/src/oss/tests/memory.crud.test.ts
+++ b/mem0-ts/src/oss/tests/memory.crud.test.ts
@@ -5,6 +5,7 @@
///
import { Memory } from "../src/memory";
import type { MemoryItem, SearchResult } from "../src/types";
+import { logger } from "../src/utils/logger";
jest.setTimeout(30000);
@@ -150,7 +151,7 @@ describe("Memory - update()", () => {
infer: false,
});
const id = addResult.results[0].id;
- const result = await memory.update(id, "Updated");
+ const result = await memory.update(id, { text: "Updated" });
expect(result.message).toBe("Memory updated successfully!");
});
@@ -160,7 +161,7 @@ describe("Memory - update()", () => {
infer: false,
});
const id = addResult.results[0].id;
- await memory.update(id, "After update");
+ await memory.update(id, { text: "After update" });
const item: MemoryItem | null = await memory.get(id);
expect(item!.memory).toBe("After update");
});
@@ -174,7 +175,7 @@ describe("Memory - update()", () => {
const before: MemoryItem | null = await memory.get(id);
const originalCreatedAt = before!.createdAt;
- await memory.update(id, "New text");
+ await memory.update(id, { text: "New text" });
const after: MemoryItem | null = await memory.get(id);
expect(after!.createdAt).toBe(originalCreatedAt);
expect(after!.updatedAt).toBeDefined();
@@ -187,7 +188,7 @@ describe("Memory - update()", () => {
});
const id = addResult.results[0].id;
const before: MemoryItem | null = await memory.get(id);
- await memory.update(id, "Completely different text");
+ await memory.update(id, { text: "Completely different text" });
const after: MemoryItem | null = await memory.get(id);
expect(after!.hash).not.toBe(before!.hash);
});
@@ -199,7 +200,7 @@ describe("Memory - update()", () => {
infer: false,
});
const id = addResult.results[0].id;
- await memory.update(id, "Updated text");
+ await memory.update(id, { text: "Updated text" });
const after: MemoryItem | null = await memory.get(id);
expect(after!.memory).toBe("Updated text");
expect(after!.metadata).toEqual(
@@ -208,6 +209,309 @@ describe("Memory - update()", () => {
});
});
+// ─── update() options: text / data / metadata / expirationDate ───
+
+describe("Memory - update() options", () => {
+ let memory: Memory;
+ let warnSpy: jest.SpyInstance;
+ const userId = `update_options_${Date.now()}`;
+
+ beforeAll(async () => {
+ memory = createMemory();
+ });
+
+ beforeEach(() => {
+ warnSpy = jest.spyOn(logger, "warn").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ warnSpy.mockRestore();
+ });
+
+ afterAll(async () => {
+ await memory.reset();
+ });
+
+ async function seed(text: string): Promise {
+ const addResult: SearchResult = await memory.add(text, {
+ userId,
+ infer: false,
+ });
+ return addResult.results[0].id;
+ }
+
+ test("accepts an options object with text, without warning", async () => {
+ const id = await seed("Options before");
+ await memory.update(id, { text: "Options after" });
+ const after: MemoryItem | null = await memory.get(id);
+ expect(after!.memory).toBe("Options after");
+ expect(warnSpy).not.toHaveBeenCalled();
+ });
+
+ test("accepts a bare text string, as on main", async () => {
+ const id = await seed("Bare before");
+ await memory.update(id, "Bare after");
+ const after: MemoryItem | null = await memory.get(id);
+ expect(after!.memory).toBe("Bare after");
+ });
+
+ test("accepts the deprecated data alias and warns", async () => {
+ const id = await seed("Alias before");
+ await memory.update(id, { data: "Alias after" });
+ const after: MemoryItem | null = await memory.get(id);
+ expect(after!.memory).toBe("Alias after");
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("deprecated"));
+ });
+
+ // An empty `text` is content, so it must beat `data`. Guards against `||=`,
+ // which would treat "" as absent and store the `data` value instead.
+ test("text wins over data, even when text is empty", async () => {
+ const id = await seed("Both before");
+ await memory.update(id, { text: "", data: "From data" });
+ const after: MemoryItem | null = await memory.get(id);
+ expect(after!.memory).toBe("");
+ });
+
+ // Same trap on the guard: "" must not read as "no field provided".
+ test("an empty bare string is content, not a missing argument", async () => {
+ const id = await seed("Empty bare");
+ await memory.update(id, "");
+ const after: MemoryItem | null = await memory.get(id);
+ expect(after!.memory).toBe("");
+ });
+
+ test("updates metadata without touching the stored text", async () => {
+ const id = await seed("Metadata only");
+ await memory.update(id, { metadata: { category: "solo" } });
+ const after: MemoryItem | null = await memory.get(id);
+ expect(after!.memory).toBe("Metadata only");
+ expect(after!.metadata).toEqual(
+ expect.objectContaining({ category: "solo" }),
+ );
+ });
+
+ test("sets an expiration date without touching the stored text", async () => {
+ const id = await seed("Expiry only");
+ await memory.update(id, { expirationDate: "2099-12-31" });
+ const after: MemoryItem | null = await memory.get(id);
+ expect(after!.memory).toBe("Expiry only");
+ expect(after!.metadata).toEqual(
+ expect.objectContaining({ expiration_date: "2099-12-31" }),
+ );
+ });
+
+ // Python raises on `data=None` / `metadata=None`, so loose `== null` is the
+ // right check for both. `{}` is a real value and must not raise.
+ test.each([{}, { data: null }, { metadata: null }])(
+ "throws when %p provides nothing updatable",
+ async (options) => {
+ const id = await seed("Nothing to update");
+ await expect(memory.update(id, options as any)).rejects.toThrow(
+ "At least one of text, metadata, or expirationDate must be provided.",
+ );
+ expect(warnSpy).not.toHaveBeenCalled();
+ },
+ );
+
+ test("accepts empty metadata as an updatable field", async () => {
+ const id = await seed("Empty metadata");
+ await expect(memory.update(id, { metadata: {} })).resolves.toEqual({
+ message: "Memory updated successfully!",
+ });
+ });
+});
+
+// ─── expiration date parsing ─────────────────────────────
+
+describe("Memory - expiration date parsing", () => {
+ let memory: Memory;
+ const userId = `expiry_parse_test_${Date.now()}`;
+
+ beforeAll(async () => {
+ memory = createMemory();
+ });
+
+ afterAll(async () => {
+ await memory.reset();
+ });
+
+ // `new Date(...)` accepts all of these; Python's date.fromisoformat rejects
+ // them. The first two die to the format regex, the last two only to the
+ // UTC component round-trip: they match YYYY-MM-DD but are not real days.
+ const rejected = [
+ "12/31/2099", // also shifts a day west of UTC
+ "2099-12-31T23:00:00",
+ "2099-02-30", // rolls over to 2099-03-02
+ "2100-02-29", // 2100 is not a leap year
+ ];
+
+ test.each(rejected)("add() rejects %p", async (value) => {
+ await expect(
+ memory.add("Bad expiry", { userId, infer: false, expirationDate: value }),
+ ).rejects.toThrow("YYYY-MM-DD");
+ });
+
+ // add() and update() share normalizeExpirationDate(); this checks the wiring.
+ test("update() rejects a malformed expiration date", async () => {
+ const addResult: SearchResult = await memory.add("Good", {
+ userId,
+ infer: false,
+ });
+ await expect(
+ memory.update(addResult.results[0].id, { expirationDate: "12/31/2099" }),
+ ).rejects.toThrow("YYYY-MM-DD");
+ });
+
+ // "2096-02-29" is a real leap day: it must survive the component check.
+ test.each(["2099-12-31", "2096-02-29"])(
+ "stores %p verbatim",
+ async (value) => {
+ const addResult: SearchResult = await memory.add("Good expiry", {
+ userId,
+ infer: false,
+ expirationDate: value,
+ });
+ const item: MemoryItem | null = await memory.get(addResult.results[0].id);
+ expect(item!.metadata!.expiration_date).toBe(value);
+ },
+ );
+});
+
+// ─── expired memories are hidden on read ─────────────────
+
+describe("Memory - expired memories", () => {
+ let memory: Memory;
+ const userId = `expired_test_${Date.now()}`;
+ const today = new Date().toISOString().slice(0, 10);
+
+ beforeAll(async () => {
+ memory = createMemory();
+ });
+
+ afterAll(async () => {
+ await memory.reset();
+ });
+
+ async function seed(text: string, expirationDate?: string): Promise {
+ const addResult: SearchResult = await memory.add(text, {
+ userId,
+ infer: false,
+ ...(expirationDate ? { expirationDate } : {}),
+ });
+ return addResult.results[0].id;
+ }
+
+ async function seedLiveAndDead(scopedUser: string): Promise {
+ await memory.add("Live memory", { userId: scopedUser, infer: false });
+ await memory.add("Dead memory", {
+ userId: scopedUser,
+ infer: false,
+ expirationDate: "2020-01-01",
+ });
+ }
+
+ test("getAll() hides expired memories unless showExpired", async () => {
+ const scopedUser = `${userId}_getall`;
+ await seedLiveAndDead(scopedUser);
+ const filters = { user_id: scopedUser };
+
+ const hidden: SearchResult = await memory.getAll({ filters });
+ expect(hidden.results.map((r) => r.memory)).toEqual(["Live memory"]);
+
+ const shown: SearchResult = await memory.getAll({
+ filters,
+ showExpired: true,
+ });
+ expect(shown.results.map((r) => r.memory).sort()).toEqual([
+ "Dead memory",
+ "Live memory",
+ ]);
+ });
+
+ test("search() hides expired memories unless showExpired", async () => {
+ const scopedUser = `${userId}_search`;
+ await seedLiveAndDead(scopedUser);
+ const filters = { user_id: scopedUser };
+
+ const hidden: SearchResult = await memory.search("memory", { filters });
+ expect(hidden.results.map((r) => r.memory)).toEqual(["Live memory"]);
+
+ const shown: SearchResult = await memory.search("memory", {
+ filters,
+ showExpired: true,
+ });
+ expect(shown.results.map((r) => r.memory).sort()).toEqual([
+ "Dead memory",
+ "Live memory",
+ ]);
+ });
+
+ test("a memory expiring today is not yet expired", async () => {
+ const scopedUser = `${userId}_today`;
+ await memory.add("Expires today", {
+ userId: scopedUser,
+ infer: false,
+ expirationDate: today,
+ });
+ const result: SearchResult = await memory.getAll({
+ filters: { user_id: scopedUser },
+ });
+ expect(result.results).toHaveLength(1);
+ });
+
+ test("get() still returns an expired memory by ID", async () => {
+ const id = await seed("Fetch by id", "2020-01-01");
+ const item: MemoryItem | null = await memory.get(id);
+ expect(item).not.toBeNull();
+ expect(item!.memory).toBe("Fetch by id");
+ });
+
+ test("clearing the expiration date makes a memory visible again", async () => {
+ const scopedUser = `${userId}_revive`;
+ const addResult: SearchResult = await memory.add("Revived", {
+ userId: scopedUser,
+ infer: false,
+ expirationDate: "2020-01-01",
+ });
+ const id = addResult.results[0].id;
+
+ const before: SearchResult = await memory.getAll({
+ filters: { user_id: scopedUser },
+ });
+ expect(before.results).toHaveLength(0);
+
+ await memory.update(id, { expirationDate: null });
+
+ const after: SearchResult = await memory.getAll({
+ filters: { user_id: scopedUser },
+ });
+ expect(after.results.map((r) => r.memory)).toEqual(["Revived"]);
+ });
+
+ test("getAll() still fills topK when expired memories are present", async () => {
+ const scopedUser = `${userId}_topk`;
+ for (let i = 0; i < 3; i++) {
+ await memory.add(`Dead ${i}`, {
+ userId: scopedUser,
+ infer: false,
+ expirationDate: "2020-01-01",
+ });
+ }
+ for (let i = 0; i < 3; i++) {
+ await memory.add(`Live ${i}`, { userId: scopedUser, infer: false });
+ }
+
+ const result: SearchResult = await memory.getAll({
+ filters: { user_id: scopedUser },
+ topK: 3,
+ });
+ expect(result.results).toHaveLength(3);
+ expect(result.results.every((r) => r.memory!.startsWith("Live"))).toBe(
+ true,
+ );
+ });
+});
+
// ─── delete() ────────────────────────────────────────────
describe("Memory - delete()", () => {
@@ -428,7 +732,7 @@ describe("Memory - history()", () => {
userId,
});
const id = addResult.results[0].id;
- await memory.update(id, "After");
+ await memory.update(id, { text: "After" });
const history = await memory.history(id);
expect(history.length).toBeGreaterThanOrEqual(2);
});
diff --git a/mem0-ts/src/oss/tests/vector-stores-compat.test.ts b/mem0-ts/src/oss/tests/vector-stores-compat.test.ts
index 39a259856..b923760a7 100644
--- a/mem0-ts/src/oss/tests/vector-stores-compat.test.ts
+++ b/mem0-ts/src/oss/tests/vector-stores-compat.test.ts
@@ -2154,12 +2154,13 @@ describe("Memory class – backward compat with all providers", () => {
});
it("all public methods work after initialization", async () => {
+ const memoryId = "3f0d5b6a-9c1e-4a2b-8d7f-1e2c3a4b5c6d";
const mockVStore = createMockVectorStore();
mockVStore.search.mockResolvedValue([
- { id: "id-1", payload: { memory: "test", hash: "h" }, score: 0.9 },
+ { id: memoryId, payload: { memory: "test", hash: "h" }, score: 0.9 },
]);
mockVStore.get.mockResolvedValue({
- id: "id-1",
+ id: memoryId,
payload: {
memory: "test",
hash: "h",
@@ -2170,7 +2171,7 @@ describe("Memory class – backward compat with all providers", () => {
mockVStore.list.mockResolvedValue([
[
{
- id: "id-1",
+ id: memoryId,
payload: {
memory: "test",
hash: "h",
@@ -2204,15 +2205,15 @@ describe("Memory class – backward compat with all providers", () => {
expect(searchResult).toBeDefined();
// get
- const item = await mem.get("id-1");
+ const item = await mem.get(memoryId);
expect(item).toBeDefined();
// update
- const updateResult = await mem.update("id-1", "new data");
+ const updateResult = await mem.update(memoryId, { text: "new data" });
expect(updateResult.message).toBe("Memory updated successfully!");
// delete
- const deleteResult = await mem.delete("id-1");
+ const deleteResult = await mem.delete(memoryId);
expect(deleteResult.message).toBe("Memory deleted successfully!");
// deleteAll
@@ -2220,7 +2221,7 @@ describe("Memory class – backward compat with all providers", () => {
expect(deleteAllResult.message).toBe("Memories deleted successfully!");
// history
- const history = await mem.history("id-1");
+ const history = await mem.history(memoryId);
expect(Array.isArray(history)).toBe(true);
});
diff --git a/mem0/memory/main.py b/mem0/memory/main.py
index 8cac28ca0..fa3829e3d 100644
--- a/mem0/memory/main.py
+++ b/mem0/memory/main.py
@@ -25,15 +25,12 @@ from mem0.configs.prompts import (
from mem0.exceptions import LLMError
from mem0.exceptions import ValidationError as Mem0ValidationError
from mem0.memory.base import MemoryBase
-from mem0.memory.setup import mem0_dir, setup_config
-from mem0.memory.storage import SQLiteManager
-from mem0.memory.telemetry import MEM0_TELEMETRY, capture_event
from mem0.memory.notices import (
PERFORMANCE_SLOW_QUERY_THRESHOLD_SECONDS,
- detect_scale_threshold_from_add_result,
- detect_scale_threshold_from_top_k,
detect_decay_usage_from_delete,
detect_decay_usage_from_delete_all,
+ detect_scale_threshold_from_add_result,
+ detect_scale_threshold_from_top_k,
detect_temporal_usage_from_metadata,
detect_temporal_usage_from_search,
display_decay_usage_notice,
@@ -51,6 +48,9 @@ from mem0.memory.notices import (
get_temporal_feature_error_message,
get_temporal_feature_error_message_async,
)
+from mem0.memory.setup import mem0_dir, setup_config
+from mem0.memory.storage import SQLiteManager
+from mem0.memory.telemetry import MEM0_TELEMETRY, capture_event
from mem0.memory.utils import (
extract_json,
parse_messages,
@@ -1767,30 +1767,41 @@ class Memory(MemoryBase):
def update(
self,
memory_id,
- data: Optional[str] = None,
+ text: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
expiration_date: Any = _UNSET,
+ data: Optional[str] = None,
):
"""
Update a memory by ID.
Args:
memory_id (str): ID of the memory to update.
- data (str, optional): New content to update the memory with.
+ text (str, optional): New content to update the memory with.
metadata (dict, optional): Metadata to update with the memory. Defaults to None.
expiration_date (Any, optional): Date in YYYY-MM-DD format, or None to clear it.
+ data (str, optional): Deprecated alias for ``text``. Will be removed in the next
+ major release; use ``text`` instead.
Returns:
dict: Success message indicating the memory was updated.
Example:
- >>> m.update(memory_id="mem_123", data="Likes to play tennis on weekends")
+ >>> m.update(memory_id="mem_123", text="Likes to play tennis on weekends")
{'message': 'Memory updated successfully!'}
"""
capture_event("mem0.update", self, {"memory_id": memory_id, "sync_type": "sync"})
- if data is None and metadata is None and expiration_date is _UNSET:
- raise ValueError("At least one of data, metadata, or expiration_date must be provided.")
+ if data is not None:
+ logger.warning(
+ "The `data` argument to update() is deprecated and will be removed in the "
+ "next major release. Use `text` instead."
+ )
+ if text is None:
+ text = data
+
+ if text is None and metadata is None and expiration_date is _UNSET:
+ raise ValueError("At least one of text, metadata, or expiration_date must be provided.")
update_metadata = deepcopy(metadata) if metadata is not None else None
if expiration_date is not _UNSET:
@@ -1798,10 +1809,10 @@ class Memory(MemoryBase):
update_metadata["expiration_date"] = _normalize_expiration_date(expiration_date)
existing_embeddings = {}
- if data is not None:
- existing_embeddings[data] = self.embedding_model.embed(data, "update")
+ if text is not None:
+ existing_embeddings[text] = self.embedding_model.embed(text, "update")
- self._update_memory(memory_id, data, existing_embeddings, update_metadata)
+ self._update_memory(memory_id, text, existing_embeddings, update_metadata)
display_first_run_notice(self, "sync", "update")
return {"message": "Memory updated successfully!"}
@@ -3387,30 +3398,41 @@ class AsyncMemory(MemoryBase):
async def update(
self,
memory_id,
- data: Optional[str] = None,
+ text: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
expiration_date: Any = _UNSET,
+ data: Optional[str] = None,
):
"""
Update a memory by ID asynchronously.
Args:
memory_id (str): ID of the memory to update.
- data (str, optional): New content to update the memory with.
+ text (str, optional): New content to update the memory with.
metadata (dict, optional): Metadata to update with the memory. Defaults to None.
expiration_date (Any, optional): Date in YYYY-MM-DD format, or None to clear it.
+ data (str, optional): Deprecated alias for ``text``. Will be removed in the next
+ major release; use ``text`` instead.
Returns:
dict: Success message indicating the memory was updated.
Example:
- >>> await m.update(memory_id="mem_123", data="Likes to play tennis on weekends")
+ >>> await m.update(memory_id="mem_123", text="Likes to play tennis on weekends")
{'message': 'Memory updated successfully!'}
"""
capture_event("mem0.update", self, {"memory_id": memory_id, "sync_type": "async"})
- if data is None and metadata is None and expiration_date is _UNSET:
- raise ValueError("At least one of data, metadata, or expiration_date must be provided.")
+ if data is not None:
+ logger.warning(
+ "The `data` argument to update() is deprecated and will be removed in the "
+ "next major release. Use `text` instead."
+ )
+ if text is None:
+ text = data
+
+ if text is None and metadata is None and expiration_date is _UNSET:
+ raise ValueError("At least one of text, metadata, or expiration_date must be provided.")
update_metadata = deepcopy(metadata) if metadata is not None else None
if expiration_date is not _UNSET:
@@ -3418,11 +3440,11 @@ class AsyncMemory(MemoryBase):
update_metadata["expiration_date"] = _normalize_expiration_date(expiration_date)
existing_embeddings = {}
- if data is not None:
- embeddings = await asyncio.to_thread(self.embedding_model.embed, data, "update")
- existing_embeddings[data] = embeddings
+ if text is not None:
+ embeddings = await asyncio.to_thread(self.embedding_model.embed, text, "update")
+ existing_embeddings[text] = embeddings
- await self._update_memory(memory_id, data, existing_embeddings, update_metadata)
+ await self._update_memory(memory_id, text, existing_embeddings, update_metadata)
await display_first_run_notice_async(self, "async", "update")
return {"message": "Memory updated successfully!"}
diff --git a/skills/mem0-oss-to-platform/references/api-mapping.md b/skills/mem0-oss-to-platform/references/api-mapping.md
index 9c9a04b66..3e16f7e8b 100644
--- a/skills/mem0-oss-to-platform/references/api-mapping.md
+++ b/skills/mem0-oss-to-platform/references/api-mapping.md
@@ -49,7 +49,7 @@ Notes:
| get_all | `memory.get_all(user_id="u")` or `…, filters={"user_id":"u"}` | `memory.get_all(filters={"user_id": "u"}, page=1, page_size=N)` — entity IDs in `filters`; paginated with `page`/`page_size` (**not** `top_k`) |
| delete_all | `memory.delete_all(user_id="u")` | `memory.delete_all(user_id="u")` — unchanged |
| get | `memory.get(memory_id)` | `memory.get(memory_id)` |
-| update | `memory.update(memory_id, data=...)` | `memory.update(memory_id, text=...)` — confirm param name against installed sig |
+| update | `memory.update(memory_id, text=...)` *(`data=` is a deprecated alias)* | `memory.update(memory_id, text=...)` — unchanged |
| delete | `memory.delete(memory_id)` | `memory.delete(memory_id)` |
| reset | `memory.reset()` (wipes the local store) | **No global reset.** Use `memory.delete_all(filters=...)` scoped to the relevant entity. Flag this. |
diff --git a/tests/memory/test_main.py b/tests/memory/test_main.py
index 86acb8138..fd4d68852 100644
--- a/tests/memory/test_main.py
+++ b/tests/memory/test_main.py
@@ -190,6 +190,28 @@ class TestAsyncUpdate:
"test_id", "Updated memory", {"Updated memory": [0.1, 0.2, 0.3]}, {}
)
+ @pytest.mark.asyncio
+ async def test_async_update_data_is_deprecated_alias_for_text(self, mock_async_memory, mocker, caplog):
+ mock_async_memory.embedding_model = Mock()
+ mock_async_memory.embedding_model.embed = Mock(return_value=[0.1, 0.2, 0.3])
+ mock_async_memory._update_memory = mocker.AsyncMock()
+
+ # `data=` still works but emits a deprecation warning
+ with caplog.at_level(logging.WARNING):
+ await mock_async_memory.update("test_id", data="via data")
+
+ assert any("deprecated" in record.message for record in caplog.records)
+ mock_async_memory._update_memory.assert_called_once_with(
+ "test_id", "via data", {"via data": [0.1, 0.2, 0.3]}, None
+ )
+
+ # `text` takes precedence when both are passed
+ mock_async_memory._update_memory.reset_mock()
+ await mock_async_memory.update("test_id", text="preferred", data="ignored")
+ mock_async_memory._update_memory.assert_called_once_with(
+ "test_id", "preferred", {"preferred": [0.1, 0.2, 0.3]}, None
+ )
+
@pytest.mark.asyncio
async def test_async_update_can_change_expiration_date_without_changing_text(self, mock_async_memory, mocker):
mock_async_memory.embedding_model.embed = Mock(return_value=[0.1, 0.2, 0.3])
diff --git a/tests/test_main.py b/tests/test_main.py
index 4146c471d..6b0638355 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,3 +1,4 @@
+import logging
import os
from unittest.mock import Mock, patch
@@ -212,6 +213,24 @@ def test_update_with_empty_metadata(memory_instance):
)
+def test_update_data_is_deprecated_alias_for_text(memory_instance, caplog):
+ memory_instance.embedding_model = Mock()
+ memory_instance.embedding_model.embed = Mock(return_value=[0.1, 0.2, 0.3])
+ memory_instance._update_memory = Mock()
+
+ # `data=` still works but emits a deprecation warning
+ with caplog.at_level(logging.WARNING):
+ memory_instance.update("test_id", data="via data")
+
+ assert any("deprecated" in record.message for record in caplog.records)
+ memory_instance._update_memory.assert_called_once_with("test_id", "via data", {"via data": [0.1, 0.2, 0.3]}, None)
+
+ # `text` takes precedence when both are passed
+ memory_instance._update_memory.reset_mock()
+ memory_instance.update("test_id", text="preferred", data="ignored")
+ memory_instance._update_memory.assert_called_once_with("test_id", "preferred", {"preferred": [0.1, 0.2, 0.3]}, None)
+
+
@pytest.mark.parametrize(
("expiration_date", "expected_expiration_date"),
[