Compare commits

...

3 Commits

Author SHA1 Message Date
chaithanyak42 6b1e46c23a chore(release): bump mem0ai to 2.0.4 (py) and 3.0.5 (ts) for delete_linked
Patch release so the new delete_linked option is publishable to PyPI/npm.
Adds Python v2.0.4 and TypeScript v3.0.5 changelog entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 22:05:27 +05:30
chaithanyak42 a54e91705c docs(sdk): convert deleteLinked comment to concise JSDoc
Addresses review: use JSDoc instead of a verbose // block on the type field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:47:52 +05:30
chaithanyak42 46e4b91a06 feat(sdk): add delete_linked option to MemoryClient.delete (Python + TS)
Plumbs the platform v3 `delete_linked` flag through the hosted MemoryClient in
both SDKs. When true, DELETE /v1/memories/{id}/ also removes the older memories
the target superseded (the linked_memory_ids chain) — the delete-side counterpart
of `latest_only`, so a superseded memory doesn't resurface after the current one
is deleted.

- Python: MemoryClient.delete / AsyncMemoryClient.delete gain `delete_linked: bool = False`
  forwarded as a query param (omitted when False — default byte-identical).
- TypeScript: client.delete accepts `{ deleteLinked?: boolean }`, serialized to
  `delete_linked` via the existing camelToSnakeKeys path (matches latestOnly).
- Unit tests for both: default omits the param; opt-in forwards it.

Client-side passthrough only; no changes to the OSS memory engine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:33:58 +05:30
8 changed files with 113 additions and 14 deletions
+14
View File
@@ -7,6 +7,13 @@ mode: "wide"
<Tabs>
<Tab title="Python">
<Update label="2026-05-27" description="v2.0.4">
**New Features:**
- **Client:** `delete()` and async `delete()` accept `delete_linked` (default `False`). When `True`, deleting a memory also removes the older memories it superseded (the v3 `linked_memory_ids` chain), transitively — the delete-side counterpart of `latest_only`, so a superseded memory does not resurface after the current one is deleted ([#5270](https://github.com/mem0ai/mem0/pull/5270))
</Update>
<Update label="2026-05-26" description="v2.0.3">
**Bug Fixes:**
@@ -932,6 +939,13 @@ See the [OSS v1 to v2 migration guide](https://docs.mem0.ai/migration/oss-v1-to-
</Tab>
<Tab title="TypeScript">
<Update label="2026-05-27" description="v3.0.5">
**New Features:**
- **Client:** `delete()` accepts an options object with `deleteLinked` (serialized as `delete_linked`, default `false`). When `true`, deleting a memory also removes the older memories it superseded (the v3 linked chain), transitively — the delete-side counterpart of `latestOnly`, so a superseded memory does not resurface after the current one is deleted ([#5270](https://github.com/mem0ai/mem0/pull/5270))
</Update>
<Update label="2026-05-26" description="v3.0.4">
**Bug Fixes:**
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "3.0.4",
"version": "3.0.5",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
+10 -3
View File
@@ -9,6 +9,7 @@ import {
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
DeleteMemoryOptions,
MemoryUpdateBody,
ProjectResponse,
PromptUpdatePayload,
@@ -377,11 +378,17 @@ export default class MemoryClient {
return response;
}
async delete(memoryId: string): Promise<{ message: string }> {
async delete(
memoryId: string,
options: DeleteMemoryOptions = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
this._captureEvent("delete", []);
this._captureEvent("delete", [Object.keys(options || {})]);
const snakeOptions = camelToSnakeKeys(this._prepareParams(options));
// @ts-ignore
const query = new URLSearchParams(snakeOptions).toString();
return this._fetchWithErrorHandling(
`${this.host}/v1/memories/${memoryId}/`,
`${this.host}/v1/memories/${memoryId}/${query ? `?${query}` : ""}`,
{
method: "DELETE",
headers: this.headers,
+9
View File
@@ -39,6 +39,15 @@ export interface GetAllMemoryOptions {
export interface DeleteAllMemoryOptions extends EntityOptions {}
export interface DeleteMemoryOptions {
/**
* When `true`, also delete the older memories this one superseded (the v3
* linked chain), transitively — the delete-side counterpart of `latestOnly`.
* Off by default. Serialized as `delete_linked`.
*/
deleteLinked?: boolean;
}
// ─── Project Options ────────────────────────────────────────
export interface ProjectOptions {
fields?: string[];
@@ -200,9 +200,26 @@ describe("MemoryClient - delete()", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.delete("mem_123");
expect(
findFetchCall(mock, "/v1/memories/mem_123/", "DELETE"),
).toBeDefined();
const call = findFetchCall(mock, "/v1/memories/mem_123/", "DELETE");
expect(call).toBeDefined();
// Default: no cascade query param, URL byte-identical to before.
expect(call![0]).not.toContain("delete_linked");
});
test("serializes deleteLinked as delete_linked query param", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/mem_123/", {
status: 200,
body: { message: "Memory deleted successfully", cascade_count: 1 },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.delete("mem_123", { deleteLinked: true });
const call = findFetchCall(mock, "/v1/memories/mem_123/", "DELETE");
expect(call).toBeDefined();
expect(call![0]).toContain("delete_linked=true");
});
});
+20 -6
View File
@@ -360,11 +360,16 @@ class MemoryClient:
return response.json()
@api_error_handler
def delete(self, memory_id: str) -> Dict[str, Any]:
def delete(self, memory_id: str, delete_linked: bool = False) -> Dict[str, Any]:
"""Delete a specific memory by ID.
Args:
memory_id: The ID of the memory to delete.
delete_linked: When True, also delete the older memories this one
superseded (the v3 ``linked_memory_ids`` chain), transitively.
This is the delete-side counterpart of ``latest_only`` — it
stops a superseded memory from resurfacing after you delete the
current one. Defaults to False (only the given memory is deleted).
Returns:
A dictionary containing the API response.
@@ -377,10 +382,12 @@ class MemoryClient:
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params()
params = self._prepare_params({"delete_linked": delete_linked or None})
response = self.client.delete(f"/v1/memories/{memory_id}/", params=params)
response.raise_for_status()
capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "sync"})
capture_client_event(
"client.delete", self, {"memory_id": memory_id, "delete_linked": delete_linked, "sync_type": "sync"}
)
return response.json()
@api_error_handler
@@ -1268,11 +1275,16 @@ class AsyncMemoryClient:
return response.json()
@api_error_handler
async def delete(self, memory_id: str) -> Dict[str, Any]:
async def delete(self, memory_id: str, delete_linked: bool = False) -> Dict[str, Any]:
"""Delete a specific memory by ID.
Args:
memory_id: The ID of the memory to delete.
delete_linked: When True, also delete the older memories this one
superseded (the v3 ``linked_memory_ids`` chain), transitively.
This is the delete-side counterpart of ``latest_only`` — it
stops a superseded memory from resurfacing after you delete the
current one. Defaults to False (only the given memory is deleted).
Returns:
A dictionary containing the API response.
@@ -1285,10 +1297,12 @@ class AsyncMemoryClient:
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params()
params = self._prepare_params({"delete_linked": delete_linked or None})
response = await self.async_client.delete(f"/v1/memories/{memory_id}/", params=params)
response.raise_for_status()
capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "async"})
capture_client_event(
"client.delete", self, {"memory_id": memory_id, "delete_linked": delete_linked, "sync_type": "async"}
)
return response.json()
@api_error_handler
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mem0ai"
version = "2.0.3"
version = "2.0.4"
description = "Long-term memory for AI Agents"
authors = [
{ name = "Mem0", email = "support@mem0.ai" }
+38
View File
@@ -147,3 +147,41 @@ class TestFilterOperatorPassthrough:
call_args = mock_memory_client.client.post.call_args
payload = call_args.kwargs.get("json", call_args.args[1] if len(call_args.args) > 1 else {})
assert payload["filters"] == complex_filter
class TestDeleteLinked:
"""delete() should forward the opt-in delete_linked flag as a query param."""
def _setup_delete(self, client):
client.client.delete.return_value = MagicMock(
json=lambda: {"message": "Memory deleted successfully!"},
raise_for_status=lambda: None,
)
def test_delete_default_omits_delete_linked(self, mock_memory_client):
"""Default delete sends no delete_linked param — byte-identical to before."""
self._setup_delete(mock_memory_client)
mock_memory_client.delete("mem_123")
call_args = mock_memory_client.client.delete.call_args
assert call_args.args[0] == "/v1/memories/mem_123/"
assert "delete_linked" not in call_args.kwargs.get("params", {})
def test_delete_linked_true_sets_param(self, mock_memory_client):
"""delete_linked=True forwards delete_linked into the request params."""
self._setup_delete(mock_memory_client)
mock_memory_client.delete("mem_123", delete_linked=True)
call_args = mock_memory_client.client.delete.call_args
assert call_args.kwargs.get("params", {}).get("delete_linked") is True
def test_delete_linked_false_omits_param(self, mock_memory_client):
"""delete_linked=False is stripped, so the default path is untouched."""
self._setup_delete(mock_memory_client)
mock_memory_client.delete("mem_123", delete_linked=False)
call_args = mock_memory_client.client.delete.call_args
assert "delete_linked" not in call_args.kwargs.get("params", {})