From 91033fc0e970ea8a9c9427175ced03c3ce06658d Mon Sep 17 00:00:00 2001 From: agumpandey Date: Tue, 5 May 2026 17:54:26 +0530 Subject: [PATCH] Feat: add temporal reasoning cookbook and docs --- docs/api-reference/events/get-event.mdx | 2 + docs/api-reference/memory/add-memories.mdx | 8 + docs/api-reference/memory/get-memories.mdx | 56 ++- docs/api-reference/memory/search-memories.mdx | 70 ++++ docs/changelog/sdk.mdx | 2 +- .../essentials/temporal-memory-assistant.mdx | 390 ++++++++++++++++++ .../memory-operations/search.mdx | 8 +- docs/docs.json | 6 +- docs/llms.txt | 8 +- docs/migration/oss-to-platform.mdx | 16 +- docs/migration/platform-v2-to-v3.mdx | 27 +- docs/openapi.json | 211 ++++++++-- docs/platform/features/async-client.mdx | 18 +- docs/platform/features/direct-import.mdx | 23 +- docs/platform/features/temporal-reasoning.mdx | 274 ++++++++++++ 15 files changed, 1062 insertions(+), 57 deletions(-) create mode 100644 docs/cookbooks/essentials/temporal-memory-assistant.mdx create mode 100644 docs/platform/features/temporal-reasoning.mdx diff --git a/docs/api-reference/events/get-event.mdx b/docs/api-reference/events/get-event.mdx index 7acdb5b33..cee5debae 100644 --- a/docs/api-reference/events/get-event.mdx +++ b/docs/api-reference/events/get-event.mdx @@ -5,3 +5,5 @@ openapi: get /v1/event/{event_id}/ --- Retrieve details about a specific event by passing its `event_id`. This endpoint is particularly helpful for tracking the status, payload, and completion details of asynchronous memory operations. + +For `POST /v3/memories/add/`, the event confirms that the write pipeline completed. If your deployment uses asynchronous temporal enrichment, the event may be `SUCCEEDED` slightly before temporal search fields become visible in subsequent `search` responses. diff --git a/docs/api-reference/memory/add-memories.mdx b/docs/api-reference/memory/add-memories.mdx index 338fa1b1a..82f3341f0 100644 --- a/docs/api-reference/memory/add-memories.mdx +++ b/docs/api-reference/memory/add-memories.mdx @@ -49,7 +49,12 @@ Provide conversation messages for Mem0 to extract memories from. At least one en | `run_id` | string | No* | Associates the memory with a run. | | `app_id` | string | No* | Associates the memory with an app. | | `metadata` | object | Optional | Custom key/value metadata (e.g., `{"topic": "preferences"}`). | +| `timestamp` | integer | Optional | Unix timestamp used to anchor imported or historical memories. | +| `observation_date` | string | Optional | Original conversation date (`YYYY-MM-DD`) for temporal grounding. | +| `observation_datetime` | string | Optional | Original conversation datetime for more precise temporal grounding. | +| `timezone` | string | Optional | IANA timezone used when resolving relative dates. | | `infer` | boolean (default `true`) | Optional | Set to `false` to skip inference and store the provided text as-is. | +| `temporal_reasoning` | boolean | Optional | Overrides the deployment default for temporal enrichment on this write. | > \* At least one entity ID (`user_id`, `agent_id`, `app_id`, or `run_id`) is required. @@ -84,3 +89,6 @@ The request is queued for background processing. The response contains an `event Poll the event status via `GET /v1/event/{event_id}/`. Status will be `SUCCEEDED` or `FAILED` once processing completes. + + Some deployments run temporal enrichment in a follow-up async step. In that mode, the add event can complete successfully before temporal search fields are fully available on later searches. + diff --git a/docs/api-reference/memory/get-memories.mdx b/docs/api-reference/memory/get-memories.mdx index 08c61f16d..3b2f4b011 100644 --- a/docs/api-reference/memory/get-memories.mdx +++ b/docs/api-reference/memory/get-memories.mdx @@ -19,6 +19,31 @@ The `filters` object supports complex logical operations (AND, OR, NOT) and comp Pass `page` and `page_size` as query parameters to paginate through results. +## Temporal fields on `get_all` + +`get_all` returns the **stored** memory record. For temporally enriched memories, that can include top-level fields such as: + +- `event_date` +- `event_start` +- `event_end` +- `time_precision` +- `timezone` +- `polarity` +- `temporal_relation` +- `state_key` +- `plan_status` + +These are stored memory attributes, not search-time interpretation fields. + +`get_all` does **not** add search-only fields such as: + +- `effective_plan_status` +- `temporal_surface` +- `reference_date_used` +- `score_breakdown` + +Those fields belong to `search`, where Mem0 interprets a query in context. + ```python Code memories = client.get_all( @@ -46,14 +71,38 @@ memories = client.get_all( { "id": "f4cbdb08-7062-4f3e-8eb2-9f5c80dfe64c", "memory": "Alex is planning a trip to San Francisco from July 1st to July 10th", + "user_id": "alex", + "metadata": {}, + "categories": [], "created_at": "2024-07-01T12:00:00Z", - "updated_at": "2024-07-01T12:00:00Z" + "updated_at": "2024-07-01T12:00:00Z", + "event_date": "2024-07-01", + "event_start": "2024-07-01", + "event_end": "2024-07-10", + "time_precision": "interval", + "timezone": "UTC", + "polarity": "positive", + "temporal_relation": "during", + "state_key": null, + "plan_status": "pending" }, { "id": "a2b8c3d4-5e6f-7g8h-9i0j-1k2l3m4n5o6p", "memory": "Alex prefers vegetarian restaurants", + "user_id": "alex", + "metadata": {}, + "categories": [], "created_at": "2024-07-05T15:30:00Z", - "updated_at": "2024-07-05T15:30:00Z" + "updated_at": "2024-07-05T15:30:00Z", + "event_date": null, + "event_start": null, + "event_end": null, + "time_precision": null, + "timezone": null, + "polarity": null, + "temporal_relation": null, + "state_key": null, + "plan_status": null } ] } @@ -65,3 +114,6 @@ memories = client.get_all( The response is a paginated envelope with `count`, `next`, `previous`, and `results`. Use `page` and `page_size` query params to step through results. + +`get_all` is best for exports, audits, and raw record inspection. Use Search Memories when you want time-aware ranking or query interpretation. + diff --git a/docs/api-reference/memory/search-memories.mdx b/docs/api-reference/memory/search-memories.mdx index b4d237cb1..c396c3d51 100644 --- a/docs/api-reference/memory/search-memories.mdx +++ b/docs/api-reference/memory/search-memories.mdx @@ -18,6 +18,21 @@ The `filters` object supports complex logical operations (AND, OR, NOT) and comp - `icontains`: Case-insensitive containment check - `*`: Wildcard character that matches everything +## Temporal fields in V3 responses + +When Temporal Reasoning is enabled for a query, search results can include additional **top-level** fields such as: + +- `memory_type` +- `event_date` +- `event_start` +- `event_end` +- `plan_status` +- `effective_plan_status` +- `time_precision` +- `temporal_surface` +- `reference_date_used` +- `score_breakdown.temporal_boost` + ### Search parameter defaults | Parameter | V1/V2 | V3 | @@ -49,10 +64,17 @@ related_memories = client.search( { "id": "ea925981-272f-40dd-b576-be64e4871429", "memory": "Likes to play cricket and plays cricket on weekends.", + "user_id": "alice", "metadata": { "category": "hobbies" }, "score": 0.82, + "score_breakdown": { + "semantic": 0.79, + "bm25": 0.0, + "entity": 0.0, + "temporal_boost": 0.0 + }, "created_at": "2024-07-26T10:29:36.630547-07:00", "updated_at": null, "categories": ["hobbies"] @@ -62,6 +84,54 @@ related_memories = client.search( ``` +### Temporal example + + +```python Platform API Example +results = client.search( + query="what did I do last week?", + filters={"user_id": "alice"}, + reference_date="2025-03-21T00:00:00Z", +) +``` + +```json Output +{ + "results": [ + { + "id": "9b59b0fd-0b31-4a0b-a3ad-7f26bb4f1f8d", + "memory": "Finished the Q1 product review on March 10, 2025.", + "user_id": "alice", + "score": 0.91, + "score_breakdown": { + "semantic": 0.84, + "bm25": 0.0, + "entity": 0.0, + "temporal_boost": 0.07 + }, + "memory_type": "event", + "event_date": "2025-03-10", + "event_start": "2025-03-10", + "event_end": "2025-03-10", + "plan_status": null, + "effective_plan_status": null, + "time_precision": "day", + "temporal_surface": "Mar 10, 2025", + "reference_date_used": "2025-03-21", + "metadata": {}, + "categories": [], + "created_at": "2025-03-10T00:00:00Z", + "updated_at": "2025-03-10T00:00:00Z" + } + ] +} +``` + + + + Temporal fields are most useful on `search`. The paginated `get_all` endpoint remains a broader retrieval surface and does not mirror the full time-aware search result shape. + + ```python Wildcard Example # Using wildcard to match all run_ids for a specific user diff --git a/docs/changelog/sdk.mdx b/docs/changelog/sdk.mdx index d20702f98..1a1a43219 100644 --- a/docs/changelog/sdk.mdx +++ b/docs/changelog/sdk.mdx @@ -41,7 +41,7 @@ mode: "wide" **Breaking Changes:** - **`add()` returns ADD-only events** — No more `"UPDATE"` or `"DELETE"` events. Memories accumulate; nothing is overwritten ([#4805](https://github.com/mem0ai/mem0/pull/4805)) - **`search()` default `threshold` is now `0.1`** — Pass `threshold=0.0` for previous behavior ([#4805](https://github.com/mem0ai/mem0/pull/4805)) -- **`search()` `score` is now a combined multi-signal score** — The top-level `score` fuses semantic similarity, BM25 keyword match, and entity boost into one value. Absolute numbers shift versus the old raw cosine score; retune any hard thresholds against representative queries. Per-signal scores are not exposed on the response ([#4805](https://github.com/mem0ai/mem0/pull/4805), [#4836](https://github.com/mem0ai/mem0/pull/4836)) +- **`search()` `score` is now a combined multi-signal score** — The top-level `score` fuses semantic similarity, BM25 keyword match, entity signals, and compatible temporal boosts into one value. Absolute numbers shift versus the old raw cosine score; retune any hard thresholds against representative queries. Platform V3 responses may also include `score_breakdown` and temporal result fields for compatible queries, so treat those as optional enrichments rather than required keys ([#4805](https://github.com/mem0ai/mem0/pull/4805), [#4836](https://github.com/mem0ai/mem0/pull/4836)) - **`search()` default `rerank` is now `False`** — Pass `rerank=True` for previous behavior ([#4805](https://github.com/mem0ai/mem0/pull/4805)) - **`top_k` default changed 100 → 20** in `Memory.get_all()` and `Memory.search()` (sync + async). Pass `top_k=100` explicitly to restore the old behavior ([#4843](https://github.com/mem0ai/mem0/pull/4843)) - **Entity ID validation:** `user_id` / `agent_id` / `run_id` are trimmed; empty-string and whitespace-only values now raise `ValueError` ([#4843](https://github.com/mem0ai/mem0/pull/4843)) diff --git a/docs/cookbooks/essentials/temporal-memory-assistant.mdx b/docs/cookbooks/essentials/temporal-memory-assistant.mdx new file mode 100644 index 000000000..e2e616992 --- /dev/null +++ b/docs/cookbooks/essentials/temporal-memory-assistant.mdx @@ -0,0 +1,390 @@ +--- +title: Time-Aware Retrieval +description: Use Mem0 Platform v3 Temporal Reasoning to make one memory store answer past, upcoming, and current questions correctly. +--- + +An assistant often stores all of these in the same user profile: + +- "I finished the Q1 product review on March 10, 2025." +- "I have a dentist appointment on April 18, 2025 at 2 PM." +- "I am the product lead at Acme Corp." + +Those memories are all relevant to the same person, but they should not rank the same way for every question. A query like `what did I do last week?` should favor the finished review. A query like `what do I have coming up?` should favor the appointment. A query like `where do I work right now?` should favor the ongoing role. + +This cookbook shows how to get that behavior with Mem0 Platform v3 using the normal `add()` and `search()` flow. + + +**Time to complete:** ~15 minutes · **Languages:** Python, JavaScript + + +## Setup + + +```python Python +from mem0 import MemoryClient + +client = MemoryClient(api_key="your-api-key") +USER_ID = "jordan" +REFERENCE_DATE = "2025-03-21T00:00:00Z" +``` + +```javascript JavaScript +import MemoryClient from "mem0ai"; + +const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY }); +const USER_ID = "jordan"; +const REFERENCE_DATE = "2025-03-21T00:00:00Z"; +``` + + + + These examples target **Mem0 Platform v3**. Writes are asynchronous, so allow a short delay before validating search behavior. + + +## Seed Memories + +Start with one past event, one future plan, and one ongoing state: + + +```python Python +client.add( + [{"role": "user", "content": "I finished the Q1 product review on March 10, 2025."}], + user_id=USER_ID, +) + +client.add( + [{"role": "user", "content": "I have a dentist appointment on April 18, 2025 at 2 PM."}], + user_id=USER_ID, +) + +client.add( + [{"role": "user", "content": "I am the product lead at Acme Corp."}], + user_id=USER_ID, +) +``` + +```javascript JavaScript +await client.add( + [{ role: "user", content: "I finished the Q1 product review on March 10, 2025." }], + { userId: USER_ID } +); + +await client.add( + [{ role: "user", content: "I have a dentist appointment on April 18, 2025 at 2 PM." }], + { userId: USER_ID } +); + +await client.add( + [{ role: "user", content: "I am the product lead at Acme Corp." }], + { userId: USER_ID } +); +``` + + +## Compare Retrieval Modes + +The easiest way to understand Temporal Reasoning is to compare the same query in two modes: + +- **Standard retrieval** with `temporal_reasoning=False` +- **Temporal retrieval** with the default time-aware mode enabled + +### Mode 1: Standard Retrieval + +This is useful for broad semantic lookups, but it does not try to interpret `last week` against a timeline. + + +```python Python +results = client.search( + "what did I do last week?", + filters={"user_id": USER_ID}, + reference_date=REFERENCE_DATE, + temporal_reasoning=False, +) + +for item in results["results"]: + print(item["memory"]) +``` + +```javascript JavaScript +const results = await client.search("what did I do last week?", { + filters: { user_id: USER_ID }, + referenceDate: REFERENCE_DATE, + temporalReasoning: false, +}); + +results.results.forEach((item) => console.log(item.memory)); +``` + + +**Illustrative output:** +```text +I finished the Q1 product review on March 10, 2025. +I have a dentist appointment on April 18, 2025 at 2 PM. +I am the product lead at Acme Corp. +``` + +That result set is semantically reasonable, but it is not yet behaving like a time-aware assistant. + +### Mode 2: Temporal Retrieval + +Now run the same query with temporal mode enabled. This is the default, so you can omit the flag. + + +```python Python +results = client.search( + "what did I do last week?", + filters={"user_id": USER_ID}, + reference_date=REFERENCE_DATE, +) + +for item in results["results"]: + print( + item["memory"], + item.get("memory_type"), + item.get("event_date"), + item.get("temporal_surface"), + ) +``` + +```javascript JavaScript +const results = await client.search("what did I do last week?", { + filters: { user_id: USER_ID }, + referenceDate: REFERENCE_DATE, +}); + +results.results.forEach((item) => { + console.log(item.memory, item.memoryType, item.eventDate, item.temporalSurface); +}); +``` + + +**Illustrative output:** +```text +I finished the Q1 product review on March 10, 2025. event 2025-03-10 Mar 10, 2025 +``` + +The impact is not just better ranking. Mem0 is now using the query’s time intent plus the stored temporal fields to prefer the memory that fits `last week`. + + +Expected behavior: with temporal mode on, the March 10 event should lead for `last week`, while the April 18 appointment and the ongoing job title should not dominate the answer. + + +## See the Three Query Shapes + +The same memory store can now support three different retrieval patterns. + +### 1. Past-oriented retrieval + +Use this for questions about completed activity. + +```python +client.search( + "what did I do last week?", + filters={"user_id": USER_ID}, + reference_date=REFERENCE_DATE, +) +``` + +Expected outcome: the completed review ranks above future plans and ongoing profile facts. + +### 2. Upcoming retrieval + +Use this for scheduled commitments and future-facing assistant flows. + + +```python Python +upcoming = client.search( + "what do I have coming up?", + filters={"user_id": USER_ID}, + reference_date=REFERENCE_DATE, +) + +for item in upcoming["results"]: + print(item["memory"], item.get("memory_type"), item.get("effective_plan_status")) +``` + +```javascript JavaScript +const upcoming = await client.search("what do I have coming up?", { + filters: { user_id: USER_ID }, + referenceDate: REFERENCE_DATE, +}); + +upcoming.results.forEach((item) => { + console.log(item.memory, item.memoryType, item.effectivePlanStatus); +}); +``` + + +**Illustrative output:** +```text +I have a dentist appointment on April 18, 2025 at 2 PM. plan upcoming +``` + + + Upcoming queries work best when the source memory includes a real date or date range. Vague text like "we should schedule something soon" is less reliable for future-oriented retrieval. + + +### 3. Current-state retrieval + +Use this for durable facts that are still true now. + + +```python Python +current = client.search( + "where do I work right now?", + filters={"user_id": USER_ID}, + reference_date=REFERENCE_DATE, +) + +for item in current["results"]: + print(item["memory"], item.get("memory_type"), item.get("temporal_surface")) +``` + +```javascript JavaScript +const current = await client.search("where do I work right now?", { + filters: { user_id: USER_ID }, + referenceDate: REFERENCE_DATE, +}); + +current.results.forEach((item) => { + console.log(item.memory, item.memoryType, item.temporalSurface); +}); +``` + + +**Illustrative output:** +```text +I am the product lead at Acme Corp. state ongoing +``` + +## Use the Right Controls + +There are two public controls that matter most in production: + +- `reference_date` anchors relative phrases like `last week`, `tomorrow`, and `right now` +- `temporal_reasoning` lets you switch between standard retrieval and temporal retrieval for a specific request + +### Deterministic tests and demos + +Use `reference_date` whenever the wording is relative. + + +```python Python +client.search( + "what did I do last week?", + filters={"user_id": USER_ID}, + reference_date="2025-03-21T00:00:00Z", +) +``` + +```javascript JavaScript +await client.search("what did I do last week?", { + filters: { user_id: USER_ID }, + referenceDate: "2025-03-21T00:00:00Z", +}); +``` + + +### Standard retrieval for timeless queries + +Turn temporal mode off when a question is not time-sensitive. + + +```python Python +client.search( + "what is my job title?", + filters={"user_id": USER_ID}, + temporal_reasoning=False, +) +``` + +```javascript JavaScript +await client.search("what is my job title?", { + filters: { user_id: USER_ID }, + temporalReasoning: false, +}); +``` + + +### Historical imports + +If you are backfilling old data, send the original event time instead of relying only on ingestion time. + + +```python Python +from datetime import datetime, timezone + +client.add( + [{"role": "user", "content": "I attended the partner summit on November 15, 2024."}], + user_id=USER_ID, + timestamp=int(datetime(2024, 11, 15, tzinfo=timezone.utc).timestamp()), +) +``` + +```javascript JavaScript +await client.add( + [{ role: "user", content: "I attended the partner summit on November 15, 2024." }], + { + userId: USER_ID, + timestamp: Math.floor(new Date("2024-11-15T00:00:00Z").getTime() / 1000), + } +); +``` + + +### Combine with normal Platform filters + +Temporal retrieval still respects the normal entity filters. + + +```python Python +client.search( + "what did we discuss last week?", + filters={ + "AND": [ + {"user_id": USER_ID}, + {"agent_id": "task-planner"}, + ] + }, + reference_date=REFERENCE_DATE, +) +``` + +```javascript JavaScript +await client.search("what did we discuss last week?", { + filters: { + AND: [ + { user_id: USER_ID }, + { agent_id: "task-planner" }, + ], + }, + referenceDate: REFERENCE_DATE, +}); +``` + + +## What You Built + +- Past questions can favor completed events instead of just semantically related memories. +- Upcoming questions can surface scheduled plans instead of historical notes. +- Current-state questions can keep stable facts available without letting them take over every search. +- You can switch between standard retrieval and temporal retrieval per request with `temporal_reasoning`. + +## Next Steps + +If your application depends on relative time language, keep `reference_date` in your tests and evals, and pass `timestamp` when you backfill older conversations. That keeps time-aware retrieval predictable without adding a separate timeline system to your stack. + + + + + diff --git a/docs/core-concepts/memory-operations/search.mdx b/docs/core-concepts/memory-operations/search.mdx index 62019d96e..fa3583bd7 100644 --- a/docs/core-concepts/memory-operations/search.mdx +++ b/docs/core-concepts/memory-operations/search.mdx @@ -156,6 +156,10 @@ const memories = memory.search("food preferences", { Expect an array of memory documents. Platform responses include vectors, metadata, and timestamps; OSS returns your stored schema. + + On Mem0 Platform v3, time-aware queries can also return top-level temporal fields such as `memory_type`, `event_date`, `event_start`, `event_end`, `effective_plan_status`, `temporal_surface`, and `score_breakdown.temporal_boost`. See Temporal Reasoning. + + ## Filter patterns Filters help narrow down search results. Common use cases: @@ -211,7 +215,7 @@ client.search("preferences", filters={ - **Use natural language**: Mem0 understands intent, so describe what you're looking for naturally - **Scope with user ID**: Always provide `user_id` to scope search to relevant memories - **Platform API**: Use `filters={"user_id": "alice"}` - - **OSS**: Use `user_id="alice"` as parameter + - **OSS**: Use `filters={"user_id": "alice"}` - **Combine filters**: Use AND/OR logic to create precise queries (Platform) - **Consider wildcard filters**: Use wildcard filters (e.g., `run_id: "*"`) for broader matches - **Tune parameters**: Adjust `top_k` for result count, `threshold` for relevance cutoff @@ -251,4 +255,4 @@ For the full list of filter logic, comparison operators, and optional search par icon="rocket" href="/cookbooks/operations/support-inbox" /> - \ No newline at end of file + diff --git a/docs/docs.json b/docs/docs.json index f77d05f26..7e9a209e4 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -72,7 +72,8 @@ "platform/features/entity-scoped-memory", "platform/features/async-client", "platform/features/multimodal-support", - "platform/features/custom-categories" + "platform/features/custom-categories", + "platform/features/temporal-reasoning" ] }, { @@ -329,6 +330,7 @@ "cookbooks/essentials/building-ai-companion", "cookbooks/essentials/entity-partitioning-playbook", "cookbooks/essentials/controlling-memory-ingestion", + "cookbooks/essentials/temporal-memory-assistant", "cookbooks/essentials/tagging-and-organizing-memories", "cookbooks/essentials/exporting-memories" ] @@ -1143,4 +1145,4 @@ "destination": "/introduction" } ] -} \ No newline at end of file +} diff --git a/docs/llms.txt b/docs/llms.txt index 1725f45ab..52f58a2d0 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -38,8 +38,8 @@ client.add( ) # Read -client.search("What does Alice like to do?", user_id="alice") -client.get_all(user_id="alice") +client.search("What does Alice like to do?", filters={"user_id": "alice"}) +client.get_all(filters={"user_id": "alice"}, page=1, page_size=50) client.get(memory_id="") # Update @@ -68,8 +68,8 @@ await client.add( ); // Read -await client.search("What does Alice like to do?", { user_id: "alice" }); -await client.getAll({ user_id: "alice" }); +await client.search("What does Alice like to do?", { filters: { user_id: "alice" } }); +await client.getAll({ filters: { user_id: "alice" }, page: 1, pageSize: 50 }); await client.get(""); // Update diff --git a/docs/migration/oss-to-platform.mdx b/docs/migration/oss-to-platform.mdx index 2af242682..a2f5fefcf 100644 --- a/docs/migration/oss-to-platform.mdx +++ b/docs/migration/oss-to-platform.mdx @@ -72,13 +72,13 @@ client = MemoryClient(api_key="m0-...") ``` - Run `client.get_all(filters={"user_id": "test_connection"})` to verify your API key works. It should return an empty list or valid results. + Run `client.get_all(filters={"user_id": "test_connection"}, page=1, page_size=1)` to verify your API key works. A healthy response is a paginated envelope like `{"count": 0, "next": null, "previous": null, "results": []}` or the same shape with results. ### 3. Update Retrieval Calls (Critical) - **Critical Change**: Platform uses v2 endpoints that require filtering parameters to be nested inside a `filters` dictionary. + **Critical Change**: Platform uses V3 endpoints and requires entity filters to be nested inside a `filters` dictionary. @@ -95,6 +95,10 @@ client = MemoryClient(api_key="m0-...") Note: `add()` and `delete()` methods remain unchanged. The `update()` method is not available in Platform - use delete + add pattern instead. + + Platform search stays compatible with the normal `results[]` shape, but V3 may include additional top-level fields such as `score_breakdown`, `memory_type`, `event_date`, and `effective_plan_status` when the platform has structured context for the result. + + @@ -132,11 +136,11 @@ Note: `add()` and `delete()` methods remain unchanged. The `update()` method is ``` ```python Platform (New) - # Get all memories for a user - memories = client.get_all(filters={"user_id": "alex"}, top_k=10) + # Get the first page of memories for a user + page = client.get_all(filters={"user_id": "alex"}, page=1, page_size=10) - # Get memories with pagination - memories = client.get_all(filters={"user_id": "alex"}, top_k=5, offset=10) + # Get the next page + next_page = client.get_all(filters={"user_id": "alex"}, page=2, page_size=10) ``` diff --git a/docs/migration/platform-v2-to-v3.mdx b/docs/migration/platform-v2-to-v3.mdx index 8ad0c06af..5cb402766 100644 --- a/docs/migration/platform-v2-to-v3.mdx +++ b/docs/migration/platform-v2-to-v3.mdx @@ -42,7 +42,7 @@ Previously, when an agent said something like "I've booked your flight for March ### Retrieval is hybrid now -Search now uses hybrid retrieval, which improves ranking quality — especially for queries involving exact keywords, proper nouns, or entities that appear across multiple memories. The response shape is unchanged: +Search now uses hybrid retrieval, which improves ranking quality — especially for queries involving exact keywords, proper nouns, entities that appear across multiple memories, and time-aware queries. Existing code that reads `results[]` and the top-level `score` continues to work, but V3 can return additional top-level fields when the platform has more structured context available: ```json { @@ -51,14 +51,20 @@ Search now uses hybrid retrieval, which improves ranking quality — especially "id": "mem-uuid", "memory": "User moved to San Francisco in January 2026", "score": 0.82, + "score_breakdown": { + "temporal_boost": 0.18 + }, "metadata": {}, - "categories": ["location"] + "categories": ["location"], + "memory_type": "state", + "event_date": "2026-01-15", + "temporal_surface": "January 2026" } ] } ``` -The top-level `score` remains a `[0, 1]` value. Relative ranking between results stays comparable to v2, but absolute numbers shift since the scoring method changed — retune any hard thresholds in your app against representative queries. +The top-level `score` remains a `[0, 1]` value. Relative ranking between results stays comparable to v2, but absolute numbers shift since the scoring method changed — retune any hard thresholds in your app against representative queries. Search responses may include fields such as `score_breakdown`, `memory_type`, `event_date`, `effective_plan_status`, `temporal_surface`, and `reference_date_used` when relevant. ## API Changes @@ -164,8 +170,15 @@ Poll status via `GET /v1/event/{event_id}/` — status will be `SUCCEEDED` or `F "id": "mem-uuid", "memory": "User moved to San Francisco from New York in January 2026", "score": 0.82, + "score_breakdown": { + "temporal_boost": 0.18 + }, "metadata": {}, "categories": ["location"], + "memory_type": "state", + "event_date": "2026-01-15", + "temporal_surface": "January 2026", + "reference_date_used": "2026-01-20", "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" } @@ -186,6 +199,12 @@ Poll status via `GET /v1/event/{event_id}/` — status will be `SUCCEEDED` or `F "memory": "...", "metadata": {}, "categories": [], + "event_date": "2026-01-15", + "event_start": "2026-01-15", + "event_end": null, + "time_precision": "day", + "timezone": "UTC", + "plan_status": null, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" } @@ -288,7 +307,7 @@ If your application previously read graph relations from the API response (`rela - **V1 and V2 endpoints continue to work.** There is no requirement to migrate to V3 endpoints immediately. - **Existing memories are preserved.** The new algorithm does not modify or re-process previously stored memories. -- **Search response shape is unchanged.** The top-level `score` and `results[]` array are the same; existing code that reads `score` continues to work. What changed is the scoring method behind the number (multi-signal fusion instead of pure cosine), so the absolute values shift even when ranking stays comparable. +- **Search remains backward-compatible at the top level.** Existing code that reads `results[]` and `score` continues to work, but V3 can now include extra top-level result fields such as `score_breakdown`, `memory_type`, and temporal fields when available. - **List response shape changed.** `get_all` now returns a paginated envelope (`{count, next, previous, results}`) instead of a bare `{results: [...]}`. Update code that reads `response["results"]` to continue working, or switch to the client SDKs which handle both shapes. ## Performance Improvements diff --git a/docs/openapi.json b/docs/openapi.json index ae7c80c48..5482024a1 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -417,10 +417,10 @@ "nullable": true, "description": "Additional metadata associated with the event." }, - "results": { - "type": "array", - "description": "Array of results produced by the event." - }, + "results": { + "type": "array", + "description": "Array of results produced by the event. For add events, this confirms the write completed; temporal enrichment may continue asynchronously on some deployments." + }, "created_at": { "type": "string", "format": "date-time", @@ -1593,7 +1593,7 @@ }, { "lang": "JavaScript", - "source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"Your search query here\";\n\nclient.search(query, { user_id: \"\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));" + "source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst query = \"Your search query here\";\n\nclient.search(query, { filters: { user_id: \"\" } })\n .then(result => console.log(result))\n .catch(error => console.error(error));" }, { "lang": "cURL", @@ -1828,17 +1828,30 @@ "type": "string", "description": "The extracted memory fact." }, - "score": { - "type": "number", - "format": "float", - "minimum": 0, - "maximum": 1, - "description": "Combined multi-signal relevance score in [0, 1] (search responses only)." + "user_id": { + "type": "string", + "nullable": true, + "description": "External user identifier scoping this memory, if any." }, - "metadata": { - "type": "object", - "additionalProperties": true, - "description": "User-supplied metadata attached to the memory." + "agent_id": { + "type": "string", + "nullable": true, + "description": "External agent identifier scoping this memory, if any." + }, + "app_id": { + "type": "string", + "nullable": true, + "description": "External app identifier scoping this memory, if any." + }, + "run_id": { + "type": "string", + "nullable": true, + "description": "External run identifier scoping this memory, if any." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "User-supplied metadata attached to the memory." }, "categories": { "type": "array", @@ -1853,8 +1866,56 @@ "updated_at": { "type": "string", "format": "date-time" - } - }, + }, + "plan_status": { + "type": "string", + "nullable": true, + "description": "Stored plan status if this memory is a plan." + }, + "event_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Primary event date when available." + }, + "event_start": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Start date for an event or interval when available." + }, + "event_end": { + "type": "string", + "format": "date", + "nullable": true, + "description": "End date for an event or interval when available." + }, + "time_precision": { + "type": "string", + "nullable": true, + "description": "Temporal precision for the resolved date information." + }, + "timezone": { + "type": "string", + "nullable": true, + "description": "Stored timezone associated with the temporal record, when available." + }, + "polarity": { + "type": "string", + "nullable": true, + "description": "Stored temporal polarity, when available." + }, + "temporal_relation": { + "type": "string", + "nullable": true, + "description": "Stored temporal relation, when available." + }, + "state_key": { + "type": "string", + "nullable": true, + "description": "Stored state key for state-like memories, when available." + } + }, "required": [ "id", "memory", @@ -1875,18 +1936,28 @@ "next": "https://api.mem0.ai/v3/memories/?page=2&page_size=100", "previous": null, "results": [ - { - "id": "mem-uuid", - "memory": "User moved to San Francisco from New York in January 2026", - "metadata": {}, - "categories": [ - "location" - ], - "created_at": "2026-01-15T10:30:00Z", - "updated_at": "2026-01-15T10:30:00Z" - } - ] - } + { + "id": "mem-uuid", + "memory": "User moved to San Francisco from New York in January 2026", + "user_id": "alice", + "metadata": {}, + "categories": [ + "location" + ], + "created_at": "2026-01-15T10:30:00Z", + "updated_at": "2026-01-15T10:30:00Z", + "event_date": "2026-01-15", + "event_start": "2026-01-15", + "event_end": null, + "time_precision": "day", + "timezone": "UTC", + "polarity": "positive", + "temporal_relation": "on", + "state_key": null, + "plan_status": null + } + ] + } } } }, @@ -1968,6 +2039,10 @@ "type": "string", "description": "Scope memories to this agent." }, + "app_id": { + "type": "string", + "description": "Scope memories to this app." + }, "run_id": { "type": "string", "description": "Scope memories to this session / run." @@ -1981,10 +2056,48 @@ "type": "string", "description": "Project-level instructions that guide extraction for this call." }, + "observation_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Original conversation date used for temporal grounding." + }, + "observation_datetime": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Original conversation datetime used for temporal grounding." + }, + "timezone": { + "type": "string", + "nullable": true, + "description": "IANA timezone used to resolve observation dates and relative dates." + }, + "multilingual": { + "type": "boolean", + "nullable": true, + "description": "Extract memories in the same language as the input." + }, + "timestamp": { + "type": "integer", + "nullable": true, + "description": "Unix epoch timestamp for when the conversation happened." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Optional expiration date for stored memories." + }, "infer": { "type": "boolean", "default": true, "description": "When `false`, stores each message verbatim without running the extraction LLM." + }, + "temporal_reasoning": { + "type": "boolean", + "nullable": true, + "description": "Enable V3 temporal reasoning write-path metadata. Defaults to the deployment setting." } } }, @@ -2071,7 +2184,7 @@ "memories" ], "summary": "Search memories (V3)", - "description": "Relevance-ranked search across stored memories. V3 uses hybrid retrieval — the returned `score` is a combined `[0, 1]` value; per-signal component scores are not exposed on the response. Entity IDs **must** be passed inside the `filters` object — top-level `user_id` / `agent_id` / `run_id` are rejected with 400. At least one entity ID is required.", + "description": "Relevance-ranked search across stored memories. V3 uses hybrid retrieval and can also apply temporal reasoning for time-aware queries. Entity IDs **must** be passed inside the `filters` object — top-level `user_id` / `agent_id` / `run_id` are rejected with 400. At least one entity ID is required.", "operationId": "memories_search_v3", "requestBody": { "required": true, @@ -2112,6 +2225,26 @@ "type": "boolean", "default": false, "description": "Apply the managed reranker for better ordering (adds latency)." + }, + "reference_date": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "string" + } + ], + "nullable": true, + "description": "Optional query anchor time for relative temporal interpretation. Accepts Unix epoch, YYYY-MM-DD, or ISO datetime." + }, + "temporal_reasoning": { + "type": "boolean", + "nullable": true, + "description": "Enable V3 temporal intent and temporal scoring. Defaults to the deployment setting." } } }, @@ -2191,7 +2324,23 @@ { "id": "mem-uuid", "memory": "User moved to San Francisco from New York in January 2026", + "user_id": "alice", "score": 0.82, + "score_breakdown": { + "semantic": 0.78, + "bm25": 0.0, + "entity": 0.0, + "temporal_boost": 0.04 + }, + "memory_type": "event", + "event_date": "2026-01-15", + "event_start": "2026-01-15", + "event_end": "2026-01-15", + "plan_status": null, + "effective_plan_status": null, + "time_precision": "day", + "temporal_surface": "Jan 15, 2026", + "reference_date_used": "2026-01-20", "metadata": {}, "categories": [ "location" @@ -6241,4 +6390,4 @@ } }, "x-original-swagger-version": "2.0" -} \ No newline at end of file +} diff --git a/docs/platform/features/async-client.mdx b/docs/platform/features/async-client.mdx index 60049ee5a..69204120d 100644 --- a/docs/platform/features/async-client.mdx +++ b/docs/platform/features/async-client.mdx @@ -73,7 +73,7 @@ await client.search("What is Alice's favorite sport?", { filters: { userId: "ali ### Get All -Retrieve all memories for a user asynchronously. +Retrieve a paginated list of memories for a user asynchronously. `get_all()` now requires filters to be specified. @@ -82,15 +82,27 @@ Retrieve all memories for a user asynchronously. ```python Python -await client.get_all(filters={"AND": [{"user_id": "alice"}]}) +page = await client.get_all( + filters={"AND": [{"user_id": "alice"}]}, + page=1, + page_size=50, +) ``` ```javascript JavaScript -await client.getAll({ filters: {"AND": [{"user_id": "alice"}]} }); +const page = await client.getAll({ + filters: { "AND": [{ user_id: "alice" }] }, + page: 1, + pageSize: 50, +}); ``` + + The async client returns the same paginated envelope as the sync client: `{"count", "next", "previous", "results"}`. + + ### Delete Delete a specific memory asynchronously. diff --git a/docs/platform/features/direct-import.mdx b/docs/platform/features/direct-import.mdx index b28348cf5..4058766f7 100644 --- a/docs/platform/features/direct-import.mdx +++ b/docs/platform/features/direct-import.mdx @@ -23,11 +23,27 @@ client.add(messages, user_id="alice", infer=False) ``` ```markdown Output -[] +{ + "message": "Memories stored successfully", + "status": "SUCCEEDED", + "event_id": "evt_123", + "results": [ + { + "id": "19d6d7aa-2454-4e58-96fc-e74d9e9f8dd1", + "memory": "Alice loves playing badminton", + "event": "ADD" + }, + { + "id": "8557f05d-7b3c-47e5-b409-9886f9e314fc", + "memory": "Alice mostly cooks at home because of her gym plan", + "event": "ADD" + } + ] +} ``` -You can see that the output of the add call is an empty list. +In V3, `infer=False` stores the supplied memory text directly and returns a successful event envelope with the created memories in `results`. Only messages with the role "user" will be used for storage. Messages with roles such as "assistant" or "system" will be ignored during the storage process. @@ -79,6 +95,9 @@ client.get_all(filters={"AND": [{"user_id": "alice"}]}) ```json Output { + "count": 2, + "next": null, + "previous": null, "results": [ { "id": "19d6d7aa-2454-4e58-96fc-e74d9e9f8dd1", diff --git a/docs/platform/features/temporal-reasoning.mdx b/docs/platform/features/temporal-reasoning.mdx new file mode 100644 index 000000000..52149c0cb --- /dev/null +++ b/docs/platform/features/temporal-reasoning.mdx @@ -0,0 +1,274 @@ +--- +title: Temporal Reasoning +description: "Use time-aware memory retrieval in Mem0 Platform v3 so searches like 'last week', 'upcoming', and 'right now' return the right memories." +icon: "clock" +badge: "v3" +--- + +Some memories should matter because of **when** they happened, not just because they sound similar. Temporal Reasoning helps Mem0 Platform v3 classify time-aware memories on write and interpret time-aware queries on search. + + + **You'll use this when…** + - Users ask questions like "what happened last week?" or "what do I have coming up?" + - Your app stores both past events and future plans for the same person + - You want time-aware retrieval without building your own date parsing layer + + + + Temporal Reasoning is a **Mem0 Platform v3** feature. It does not apply to OSS memory stores or older Platform endpoints. + + +## Configure access + +Confirm your `MEM0_API_KEY` is configured and that you are using the v3 Platform client: + +```python +from mem0 import MemoryClient + +client = MemoryClient(api_key="your-api-key") +``` + +## How it works + +Temporal Reasoning has two public-facing parts: + +- **Write-time enrichment**: when Mem0 stores a memory, it can attach temporal structure such as memory type and date bounds when that information is present in the text. +- **Search-time interpretation**: when a query includes time language like `last week`, `upcoming`, or `right now`, Mem0 uses the stored temporal fields to return more appropriate results. + +### Memory types + +Mem0 normalizes temporal memories into a small set of developer-facing types: + +| Type | What it represents | Example | +| --- | --- | --- | +| `event` | A dated occurrence | "I finished the Q1 review on March 10, 2025." | +| `plan` | A future commitment or scheduled item | "I have a dentist appointment on March 18, 2025." | +| `state` | An ongoing fact that can remain true over time | "I am the product lead at Acme Corp." | +| `relationship` | A durable connection between people or entities | "Priya manages Jordan." | +| `preference` | A stable preference or habit | "I prefer morning meetings." | + +On search responses, temporal fields are returned as top-level result attributes when Temporal Reasoning is active. The most useful ones are: + +- `memory_type` +- `event_date` +- `event_start` +- `event_end` +- `plan_status` +- `effective_plan_status` +- `time_precision` +- `temporal_surface` +- `reference_date_used` +- `score_breakdown.temporal_boost` + +## Configure it + +### Default behavior + +Temporal Reasoning is enabled by default for supported v3 searches and writes. + +### Per-request override + +Use `temporal_reasoning=False` when you want a specific call to behave like a normal semantic add or search. + + +```python Python +client.add( + [{"role": "user", "content": "Preferred language is English."}], + user_id="jordan", + temporal_reasoning=False, +) + +results = client.search( + "preferred language", + filters={"user_id": "jordan"}, + temporal_reasoning=False, +) +``` + +```javascript JavaScript +await client.add( + [{ role: "user", content: "Preferred language is English." }], + { userId: "jordan", temporalReasoning: false } +); + +const results = await client.search("preferred language", { + filters: { user_id: "jordan" }, + temporalReasoning: false, +}); +``` + +```bash cURL +curl -X POST "https://api.mem0.ai/v3/memories/search/" \ + -H "Authorization: Token your-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "preferred language", + "filters": { "user_id": "jordan" }, + "temporal_reasoning": false + }' +``` + + +### Anchor imports and tests + +Two fields matter when you want predictable temporal behavior: + +- `timestamp` on `add()` anchors imported memories to the time they actually happened +- `reference_date` on `search()` anchors relative queries like `last week` to a known point in time + + +```python Python +from datetime import datetime, timezone + +client.add( + [{"role": "user", "content": "I finished the Q1 review on March 10, 2025."}], + user_id="jordan", + timestamp=int(datetime(2025, 3, 10, tzinfo=timezone.utc).timestamp()), +) + +results = client.search( + "what did I do last week?", + filters={"user_id": "jordan"}, + reference_date="2025-03-21T00:00:00Z", +) +``` + +```javascript JavaScript +await client.add( + [{ role: "user", content: "I finished the Q1 review on March 10, 2025." }], + { + userId: "jordan", + timestamp: Math.floor(new Date("2025-03-10T00:00:00Z").getTime() / 1000), + } +); + +const results = await client.search("what did I do last week?", { + filters: { user_id: "jordan" }, + referenceDate: "2025-03-21T00:00:00Z", +}); +``` + + + + `reference_date` is especially useful for tests, backfills, and cookbook examples because it makes relative phrases reproducible. + + +## See it in action + +### Add a few dated memories + + +```python Python +client.add( + [{"role": "user", "content": "I finished the Q1 product review on March 10, 2025."}], + user_id="jordan", +) + +client.add( + [{"role": "user", "content": "I have a dentist appointment on April 18, 2025 at 2 PM."}], + user_id="jordan", +) + +client.add( + [{"role": "user", "content": "I am the product lead at Acme Corp."}], + user_id="jordan", +) +``` + +```javascript JavaScript +await client.add( + [{ role: "user", content: "I finished the Q1 product review on March 10, 2025." }], + { userId: "jordan" } +); + +await client.add( + [{ role: "user", content: "I have a dentist appointment on April 18, 2025 at 2 PM." }], + { userId: "jordan" } +); + +await client.add( + [{ role: "user", content: "I am the product lead at Acme Corp." }], + { userId: "jordan" } +); +``` + + + + Platform writes are asynchronous. For quick validation, wait briefly before searching or verify the completed event in the dashboard or CLI. + + +### Search with a temporal query + + +```python Python +results = client.search( + "what did I do last week?", + filters={"user_id": "jordan"}, + reference_date="2025-03-21T00:00:00Z", +) + +for item in results["results"]: + print(item["memory"], item.get("memory_type"), item.get("event_date")) +``` + +```javascript JavaScript +const results = await client.search("what did I do last week?", { + filters: { user_id: "jordan" }, + referenceDate: "2025-03-21T00:00:00Z", +}); + +results.results.forEach((item) => { + console.log(item.memory, item.memoryType, item.eventDate); +}); +``` + + + + Expected behavior: the March 10 review appears as a past `event`, the April 18 appointment does not lead this result set, and the ongoing role at Acme does not crowd out the dated answer. + + +## Supported query patterns + + + + Examples: `last week`, `last month`, `in March 2025`, `on 2025-03-10` + + + Examples: `upcoming`, `next week`, `tomorrow`, `what do I have coming up?` + + + Examples: `right now`, `currently`, `where do I work now?` + + + Examples: `as of March 2025`, `where was I living as of 2024?` + + + Examples: `how long have I lived here?`, `since when have I worked there?` + + + +## Verify the feature is working + +- Run a temporal search and confirm results include top-level fields like `memory_type`, `event_date`, or `effective_plan_status`. +- Repeat the same query with `temporal_reasoning=False` and confirm ranking changes for time-sensitive prompts. +- Use `reference_date` in test queries so relative phrases resolve consistently. +- For backfilled data, confirm imported memories use the expected chronology after you set `timestamp`. + +## Best practices + +- Use explicit dates in source conversations when plans or events matter. +- Pass `timestamp` during historical imports so ingestion time does not become the only time anchor. +- Keep search scoped with `filters` so time-aware ranking runs inside the right entity boundary. +- Use `reference_date` in automated tests and reproducible demos. +- Disable Temporal Reasoning only for clearly timeless lookups such as static profile fields. + + + + Anchor imported memories to when they actually happened. + + + Follow the cookbook for an end-to-end temporal retrieval workflow. + + + +