Skip to content

Commit 72bb631

Browse files
authored
Add support for procedural memory (#2460)
1 parent 2bf9286 commit 72bb631

File tree

10 files changed

+158
-20
lines changed

10 files changed

+158
-20
lines changed

mem0/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
__version__ = importlib.metadata.version("mem0ai")
44

5-
from mem0.client.main import MemoryClient, AsyncMemoryClient # noqa
5+
from mem0.client.main import AsyncMemoryClient, MemoryClient # noqa
66
from mem0.memory.main import Memory # noqa

mem0/configs/enums.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from enum import Enum
2+
3+
4+
class MemoryType(Enum):
5+
SEMANTIC = "semantic_memory"
6+
EPISODIC = "episodic_memory"
7+
PROCEDURAL = "procedural_memory"

mem0/configs/prompts.py

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,91 @@
208208
}
209209
"""
210210

211+
PROCEDURAL_MEMORY_SYSTEM_PROMPT = """
212+
You are a memory summarization system that records and preserves the complete interaction history between a human and an AI agent. You are provided with the agent’s execution history over the past N steps. Your task is to produce a comprehensive summary of the agent's output history that contains every detail necessary for the agent to continue the task without ambiguity. **Every output produced by the agent must be recorded verbatim as part of the summary.**
213+
214+
### Overall Structure:
215+
- **Overview (Global Metadata):**
216+
- **Task Objective**: The overall goal the agent is working to accomplish.
217+
- **Progress Status**: The current completion percentage and summary of specific milestones or steps completed.
218+
219+
- **Sequential Agent Actions (Numbered Steps):**
220+
Each numbered step must be a self-contained entry that includes all of the following elements:
221+
222+
1. **Agent Action**:
223+
- Precisely describe what the agent did (e.g., "Clicked on the 'Blog' link", "Called API to fetch content", "Scraped page data").
224+
- Include all parameters, target elements, or methods involved.
225+
226+
2. **Action Result (Mandatory, Unmodified)**:
227+
- Immediately follow the agent action with its exact, unaltered output.
228+
- Record all returned data, responses, HTML snippets, JSON content, or error messages exactly as received. This is critical for constructing the final output later.
229+
230+
3. **Embedded Metadata**:
231+
For the same numbered step, include additional context such as:
232+
- **Key Findings**: Any important information discovered (e.g., URLs, data points, search results).
233+
- **Navigation History**: For browser agents, detail which pages were visited, including their URLs and relevance.
234+
- **Errors & Challenges**: Document any error messages, exceptions, or challenges encountered along with any attempted recovery or troubleshooting.
235+
- **Current Context**: Describe the state after the action (e.g., "Agent is on the blog detail page" or "JSON data stored for further processing") and what the agent plans to do next.
236+
237+
### Guidelines:
238+
1. **Preserve Every Output**: The exact output of each agent action is essential. Do not paraphrase or summarize the output. It must be stored as is for later use.
239+
2. **Chronological Order**: Number the agent actions sequentially in the order they occurred. Each numbered step is a complete record of that action.
240+
3. **Detail and Precision**:
241+
- Use exact data: Include URLs, element indexes, error messages, JSON responses, and any other concrete values.
242+
- Preserve numeric counts and metrics (e.g., "3 out of 5 items processed").
243+
- For any errors, include the full error message and, if applicable, the stack trace or cause.
244+
4. **Output Only the Summary**: The final output must consist solely of the structured summary with no additional commentary or preamble.
245+
246+
### Example Template:
247+
248+
```
249+
**Task Objective**: Scrape blog post titles and full content from the OpenAI blog.
250+
**Progress Status**: 10% complete — 5 out of 50 blog posts processed.
251+
252+
1. **Agent Action**: Opened URL "https://openai.com"
253+
**Action Result**:
254+
"HTML Content of the homepage including navigation bar with links: 'Blog', 'API', 'ChatGPT', etc."
255+
**Key Findings**: Navigation bar loaded correctly.
256+
**Navigation History**: Visited homepage: "https://openai.com"
257+
**Current Context**: Homepage loaded; ready to click on the 'Blog' link.
258+
259+
2. **Agent Action**: Clicked on the "Blog" link in the navigation bar.
260+
**Action Result**:
261+
"Navigated to 'https://openai.com/blog/' with the blog listing fully rendered."
262+
**Key Findings**: Blog listing shows 10 blog previews.
263+
**Navigation History**: Transitioned from homepage to blog listing page.
264+
**Current Context**: Blog listing page displayed.
265+
266+
3. **Agent Action**: Extracted the first 5 blog post links from the blog listing page.
267+
**Action Result**:
268+
"[ '/blog/chatgpt-updates', '/blog/ai-and-education', '/blog/openai-api-announcement', '/blog/gpt-4-release', '/blog/safety-and-alignment' ]"
269+
**Key Findings**: Identified 5 valid blog post URLs.
270+
**Current Context**: URLs stored in memory for further processing.
271+
272+
4. **Agent Action**: Visited URL "https://openai.com/blog/chatgpt-updates"
273+
**Action Result**:
274+
"HTML content loaded for the blog post including full article text."
275+
**Key Findings**: Extracted blog title "ChatGPT Updates – March 2025" and article content excerpt.
276+
**Current Context**: Blog post content extracted and stored.
277+
278+
5. **Agent Action**: Extracted blog title and full article content from "https://openai.com/blog/chatgpt-updates"
279+
**Action Result**:
280+
"{ 'title': 'ChatGPT Updates – March 2025', 'content': 'We\'re introducing new updates to ChatGPT, including improved browsing capabilities and memory recall... (full content)' }"
281+
**Key Findings**: Full content captured for later summarization.
282+
**Current Context**: Data stored; ready to proceed to next blog post.
283+
284+
... (Additional numbered steps for subsequent actions)
285+
```
286+
"""
287+
211288

212289
def get_update_memory_messages(retrieved_old_memory_dict, response_content, custom_update_memory_prompt=None):
213290
if custom_update_memory_prompt is None:
214291
global DEFAULT_UPDATE_MEMORY_PROMPT
215292
custom_update_memory_prompt = DEFAULT_UPDATE_MEMORY_PROMPT
216293

217294
return f"""{custom_update_memory_prompt}
218-
295+
219296
Below is the current content of my memory which I have collected till now. You have to update it in the following format only:
220297
221298
```
@@ -227,9 +304,9 @@ def get_update_memory_messages(retrieved_old_memory_dict, response_content, cust
227304
```
228305
{response_content}
229306
```
230-
307+
231308
You must return your response in the following JSON structure only:
232-
309+
233310
{{
234311
"memory" : [
235312
{{
@@ -241,7 +318,7 @@ def get_update_memory_messages(retrieved_old_memory_dict, response_content, cust
241318
...
242319
]
243320
}}
244-
321+
245322
Follow the instruction mentioned below:
246323
- Do not return anything from the custom few shot prompts provided above.
247324
- If the current memory is empty, then you have to add the new retrieved facts to the memory.

mem0/configs/vector_stores/azure_ai_search.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Any, Dict, Optional
2+
23
from pydantic import BaseModel, Field, model_validator
34

45

mem0/configs/vector_stores/supabase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from typing import Any, Dict, Optional
21
from enum import Enum
2+
from typing import Any, Dict, Optional
33

44
from pydantic import BaseModel, Field, model_validator
55

mem0/configs/vector_stores/weaviate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Any, ClassVar, Dict, Optional
2+
23
from pydantic import BaseModel, Field, model_validator
34

45

mem0/memory/main.py

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,15 @@
1111
from pydantic import ValidationError
1212

1313
from mem0.configs.base import MemoryConfig, MemoryItem
14-
from mem0.configs.prompts import get_update_memory_messages
14+
from mem0.configs.enums import MemoryType
15+
from mem0.configs.prompts import (PROCEDURAL_MEMORY_SYSTEM_PROMPT,
16+
get_update_memory_messages)
1517
from mem0.memory.base import MemoryBase
1618
from mem0.memory.setup import setup_config
1719
from mem0.memory.storage import SQLiteManager
1820
from mem0.memory.telemetry import capture_event
19-
from mem0.memory.utils import (
20-
get_fact_retrieval_messages,
21-
parse_messages,
22-
parse_vision_messages,
23-
remove_code_blocks,
24-
)
21+
from mem0.memory.utils import (get_fact_retrieval_messages, parse_messages,
22+
parse_vision_messages, remove_code_blocks)
2523
from mem0.utils.factory import EmbedderFactory, LlmFactory, VectorStoreFactory
2624

2725
# Setup user config
@@ -89,6 +87,7 @@ def add(
8987
metadata=None,
9088
filters=None,
9189
infer=True,
90+
memory_type=None,
9291
prompt=None,
9392
):
9493
"""
@@ -102,8 +101,8 @@ def add(
102101
metadata (dict, optional): Metadata to store with the memory. Defaults to None.
103102
filters (dict, optional): Filters to apply to the search. Defaults to None.
104103
infer (bool, optional): Whether to infer the memories. Defaults to True.
105-
prompt (str, optional): Prompt to use for memory deduction. Defaults to None.
106-
104+
memory_type (str, optional): Type of memory to create. Defaults to None. By default, it creates the short term memories and long term (semantic and episodic) memories. Pass "procedural_memory" to create procedural memories.
105+
prompt (str, optional): Prompt to use for the memory creation. Defaults to None.
107106
Returns:
108107
dict: A dictionary containing the result of the memory addition operation.
109108
result: dict of affected events with each dict has the following key:
@@ -131,9 +130,18 @@ def add(
131130
if not any(key in filters for key in ("user_id", "agent_id", "run_id")):
132131
raise ValueError("One of the filters: user_id, agent_id or run_id is required!")
133132

133+
if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:
134+
raise ValueError(
135+
f"Invalid 'memory_type'. Please pass {MemoryType.PROCEDURAL.value} to create procedural memories."
136+
)
137+
134138
if isinstance(messages, str):
135139
messages = [{"role": "user", "content": messages}]
136140

141+
if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
142+
results = self._create_procedural_memory(messages, metadata, prompt)
143+
return results
144+
137145
if self.config.llm.config.get("enable_vision"):
138146
messages = parse_vision_messages(messages, self.llm, self.config.llm.config.get("vision_details"))
139147
else:
@@ -595,11 +603,11 @@ def history(self, memory_id):
595603
return self.db.get_history(memory_id)
596604

597605
def _create_memory(self, data, existing_embeddings, metadata=None):
598-
logging.info(f"Creating memory with {data=}")
606+
logging.debug(f"Creating memory with {data=}")
599607
if data in existing_embeddings:
600608
embeddings = existing_embeddings[data]
601609
else:
602-
embeddings = self.embedding_model.embed(data, "add")
610+
embeddings = self.embedding_model.embed(data, memory_action="add")
603611
memory_id = str(uuid.uuid4())
604612
metadata = metadata or {}
605613
metadata["data"] = data
@@ -615,6 +623,50 @@ def _create_memory(self, data, existing_embeddings, metadata=None):
615623
capture_event("mem0._create_memory", self, {"memory_id": memory_id})
616624
return memory_id
617625

626+
def _create_procedural_memory(self, messages, metadata, llm=None, prompt=None):
627+
"""
628+
Create a procedural memory
629+
"""
630+
try:
631+
from langchain_core.messages.utils import convert_to_messages # type: ignore
632+
except Exception:
633+
logger.error("Import error while loading langchain-core. Please install 'langchain-core' to use procedural memory.")
634+
raise
635+
636+
logger.info("Creating procedural memory")
637+
638+
parsed_messages = [
639+
{"role": "system", "content": prompt or PROCEDURAL_MEMORY_SYSTEM_PROMPT},
640+
*messages,
641+
{"role": "user", "content": "Create procedural memory of the above conversation."},
642+
]
643+
644+
try:
645+
if llm is not None:
646+
parsed_messages = convert_to_messages(parsed_messages)
647+
response = llm.invoke(messages=parsed_messages)
648+
procedural_memory = response.content
649+
else:
650+
procedural_memory = self.llm.generate_response(messages=parsed_messages)
651+
except Exception as e:
652+
logger.error(f"Error generating procedural memory summary: {e}")
653+
raise
654+
655+
if metadata is None:
656+
raise ValueError("Metadata cannot be done for procedural memory.")
657+
658+
metadata["memory_type"] = MemoryType.PROCEDURAL.value
659+
# Generate embeddings for the summary
660+
embeddings = self.embedding_model.embed(procedural_memory, memory_action="add")
661+
# Create the memory
662+
memory_id = self._create_memory(procedural_memory, {procedural_memory: embeddings}, metadata=metadata)
663+
capture_event("mem0._create_procedural_memory", self, {"memory_id": memory_id})
664+
665+
# Return results in the same format as add()
666+
result = {"results": [{"id": memory_id, "memory": procedural_memory, "event": "ADD"}]}
667+
668+
return result
669+
618670
def _update_memory(self, memory_id, data, existing_embeddings, metadata=None):
619671
logger.info(f"Updating memory with {data=}")
620672

mem0/memory/storage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sqlite3
2-
import uuid
32
import threading
3+
import uuid
44

55

66
class SQLiteManager:

mem0/vector_stores/supabase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
except ImportError:
1010
raise ImportError("The 'vecs' library is required. Please install it using 'pip install vecs'.")
1111

12+
from mem0.configs.vector_stores.supabase import IndexMeasure, IndexMethod
1213
from mem0.vector_stores.base import VectorStoreBase
13-
from mem0.configs.vector_stores.supabase import IndexMethod, IndexMeasure
1414

1515
logger = logging.getLogger(__name__)
1616

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "mem0ai"
3-
version = "0.1.79"
3+
version = "0.1.80"
44
description = "Long-term memory for AI Agents"
55
authors = ["Mem0 <[email protected]>"]
66
exclude = [

0 commit comments

Comments
 (0)