diff --git a/README.md b/README.md
index 0d787e6b..e41d69da 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
+
diff --git a/reme/agent/memory/personal/personal_halumem_retriever.yaml b/reme/agent/memory/personal/personal_halumem_retriever.yaml
index edd941cc..41395dbb 100644
--- a/reme/agent/memory/personal/personal_halumem_retriever.yaml
+++ b/reme/agent/memory/personal/personal_halumem_retriever.yaml
@@ -44,27 +44,27 @@ user_message: |
## User Question
{context}
-
+
# Core Objective:
Before responding to the user, you must strictly follow the **[Memory Retrieve -> Original Source Tracing -> Broad Search Fallback]** retrieval strategy. It is strictly forbidden to directly opt for an indiscriminate search of massive historical original texts.
-
+
## Retrieval Strategy & Workflow (Strictly Enforced Chain of Thought)
### Phase 1: Intent Decomposition and Primary Retrieval (Summary First)
1. **Analyze Intent**: Analyze the user's current Query, decomposing it into 1-3 core search intents.2. **Summary Priority**: First, retrieve from **high-level memories**.
- **Action**: Call `vector_retrieve_memory` using at least two different `query`.
- **Filter**: (Optional) Set metadata filter {{"timestamp": "YYYY-MM-DD"}}
- **Goal**: Obtain refined conclusions such as entity attributes, task status, user preferences, or environmental information.
-
+
### Phase 2: Memory Evaluation and Deep Tracing (Drill Down)
Check the retrieval results of Phase 1:
- **Case A (Sufficient Information)**: If the summarized memory contains all the details needed for the answer, proceed directly to Phase 4 for the response.
- **Case B (Vague/Complex Information)**: If summarized memory exists (e.g., 'discussed project architecture') but lacks specific details (e.g., 'specific parameter configuration'), use clues from the summary to trace the original text.
- **Action**: Call `read_history` using ref_memory_id from the retrieved memory.
- **Goal**: Obtain the specific conversation context at that time.
-
+
### Phase 3: Fallback Retrieval and Strategy Adjustment (Fallback & Expand)
If no valid information is found in both Phase 1 and Phase 2 (result is empty or similarity is too low): Rewrite the Query based on the context (remove non-keywords, synonym substitution), and search again.
-
+
### Phase 4: Result Compilation and Response
- Combine the retrieved content (summary or original text) with the current conversation context.
- If all retrieved results are irrelevant, **it is strictly forbidden to fabricate memories**; directly inform the user that no relevant information was found.
diff --git a/reme/agent/memory/personal/personal_halumem_summarizer.py b/reme/agent/memory/personal/personal_halumem_summarizer.py
index 22e4265c..4541d9de 100644
--- a/reme/agent/memory/personal/personal_halumem_summarizer.py
+++ b/reme/agent/memory/personal/personal_halumem_summarizer.py
@@ -113,9 +113,8 @@ class PersonalHalumemSummarizer(BaseMemoryAgent):
else:
all_profiles = ""
-
stage = "s2-profile"
- messages_s2 = await self._build_s2_messages(user_profile = all_profiles)
+ messages_s2 = await self._build_s2_messages(user_profile=all_profiles)
for i, message in enumerate(messages_s2):
role = message.name or message.role
logger.info(f"[{self.__class__.__name__} {stage}] role={role} {message.simple_dump(as_dict=False)}")
diff --git a/reme/agent/memory/personal/personal_halumem_summarizer.yaml b/reme/agent/memory/personal/personal_halumem_summarizer.yaml
index 659590f2..6a55a241 100644
--- a/reme/agent/memory/personal/personal_halumem_summarizer.yaml
+++ b/reme/agent/memory/personal/personal_halumem_summarizer.yaml
@@ -21,19 +21,19 @@ user_message_s1: |
## Latest Conversation
Format: round [] :
{context}
-
+
## Task
### Step 1: Create Memory
- At this step, you can call the tool multiple times to store memories, or you can call it once to store multiple memories.
### Step 2: Update Memory Store
- Update the vector store using `update_memory` to keep it well-organized and consolidated.
-
+
## Storage Scope (Biographical & Behavioral ONLY)
- Personal Biography: Significant milestones, past experiences, and life events.
- Behavioral Patterns: How the agent reacts, specific actions taken, and recurring habits.
- **EXCLUSION**: DO NOT record objective world facts, general knowledge, or user-specific health/states.
-
+
Extraction & Formatting Rules
- Fact Filtering: Only extract information that builds the biography of **{memory_target}**.
- Subject Splitting: If a conversation mentions multiple subject (e.g., the User's childhood and their Father's career), create separate memory entries for each subject.
@@ -65,14 +65,14 @@ user_message_s2: |
## Current User Profile:
{user_profile}
-
+
## Task
### Step 1: ADD Profile
- Add new, relevant, and up-to-date information to the user profile using `update_profile` (via `profiles_to_add`).
-
+
### Step 2: DELETE Profile
- Delete outdated, redundant, or resolved states from the user profile using `update_profile` (via `profile_ids_to_delete`).
-
+
## Storage Scope (Current States ONLY)
- **EXCLUSION PRINCIPLE**: DO NOT record any user *actions*, *requests*, *queries*, or *interactions with the system* (e.g., "asked for code", "solved a puzzle", "requested translation"). These are interaction logs, not user states.
- Identity: Geography, job title, work content, income.
@@ -83,13 +83,13 @@ user_message_s2: |
- Subject Splitting (CRITICAL): If the conversation mentions multiple subjects (e.g., the User's job and their Spouse's health), you MUST create separate profile entries for each unique subject.
- Conflict Resolution: Use profile_ids_to_delete to remove outdated, redundant, or resolved states (e.g., if a user is "Recovered," delete the "Illness" entry).
- Each profile entry MUST describe a **persistent or temporary state of the user themselves** (e.g., who they are, what they like, what they’re dealing with), NOT an event they participated in or a request they made.
-
+
## Profile Format
- **ONLY record what the user EXPLICITLY STATES about themselves as a state or preference.**
- The key in the record represents the category of memory, and the value should record the specific content. For example:
{{"message_time": "YYYY-MM-DD HH:MM:SS", "profile_key": "the category of memory", "profile_value": "content" }}
- When there is no information conflict or outdated information, you don't need to delete any of the memory. If there is no information that meets the requirements, it is also acceptable not to add it.
-
+
## Forbidden Case
1.There is no need to record user behavior: {{ "profile_key": "workouts", "profile_content": "confident in new running shoes' suitability for chosen route; they have significantly improved morning jogs"}}
2. There is no need to record the users' plans or requirements: {{ "profile_key": "plans.vacation", "profile_content": "planning to go to a nearby city for a week and ask for a job change"}}
diff --git a/reme/core/vector_store/es_vector_store.py b/reme/core/vector_store/es_vector_store.py
index 1090d4b6..cf9409a5 100644
--- a/reme/core/vector_store/es_vector_store.py
+++ b/reme/core/vector_store/es_vector_store.py
@@ -80,7 +80,7 @@ class ESVectorStore(BaseVectorStore):
async def _get_client(self) -> AsyncElasticsearch:
"""Create or return the existing AsyncElasticsearch client.
-
+
This lazy initialization ensures the client is created in the correct event loop.
"""
if self._client is None:
@@ -93,7 +93,7 @@ class ESVectorStore(BaseVectorStore):
headers=self.headers,
)
logger.info("AsyncElasticsearch client initialized")
-
+
return self._client
async def list_collections(self) -> list[str]:
diff --git a/reme/core/vector_store/qdrant_vector_store.py b/reme/core/vector_store/qdrant_vector_store.py
index 0afb37d7..1ef9828c 100644
--- a/reme/core/vector_store/qdrant_vector_store.py
+++ b/reme/core/vector_store/qdrant_vector_store.py
@@ -109,7 +109,7 @@ class QdrantVectorStore(BaseVectorStore):
async def _get_client(self) -> AsyncQdrantClient:
"""Create or return the existing AsyncQdrantClient.
-
+
This lazy initialization ensures the client is created in the correct event loop.
"""
if self._client is None:
@@ -125,7 +125,7 @@ class QdrantVectorStore(BaseVectorStore):
**self.client_kwargs,
)
logger.info("AsyncQdrantClient initialized")
-
+
return self._client
async def list_collections(self) -> list[str]:
diff --git a/reme/reme.py b/reme/reme.py
index cf5aa4f8..b4f64fe0 100644
--- a/reme/reme.py
+++ b/reme/reme.py
@@ -32,9 +32,8 @@ from .tool.memory import (
UpdateMemoryV2,
AddDraftAndReadAllProfiles,
UpdateProfile,
- UpdateProfileFilterOlder,
- DeleteProfile,
- AddProfile,
+ # DeleteProfile,
+ # AddProfile,
AddHistory,
ReadAllProfiles,
AddMemory,
@@ -110,7 +109,6 @@ class ReMe(Application):
task_name: str | list[str] = "",
tool_name: str | list[str] = "",
enable_thinking_params: bool = True,
- enable_time_filter: bool = True,
version: str = "default",
retrieve_top_k: int = 20,
return_dict: bool = False,
@@ -181,24 +179,18 @@ class ReMe(Application):
top_k=retrieve_top_k,
),
UpdateMemoryV2(
- enable_thinking_params=enable_thinking_params
+ enable_thinking_params=enable_thinking_params,
),
# RetrieveMemory(
# enable_thinking_params=enable_thinking_params,
# top_k=retrieve_top_k,
# enable_time_filter=enable_time_filter,
# ),
-
# 处理userprofile
ReadAllProfiles(
enable_thinking_params=enable_thinking_params,
profile_dir=self.profile_dir,
),
- # UpdateProfileFilterOlder(
- # enable_thinking_params=enable_thinking_params,
- # max_profile_count=50,
- # profile_dir=self.profile_dir,
- # ),
UpdateProfile(
enable_thinking_params=enable_thinking_params,
profile_dir=self.profile_dir,
@@ -328,7 +320,7 @@ class ReMe(Application):
top_k=retrieve_top_k,
enable_thinking_params=enable_thinking_params,
enable_time_filter=enable_time_filter,
- enable_multiple=True
+ enable_multiple=True,
),
ReadHistory(enable_thinking_params=enable_thinking_params),
],
@@ -346,7 +338,7 @@ class ReMe(Application):
enable_time_filter=enable_time_filter,
),
ReadHistory(enable_thinking_params=enable_thinking_params),
- ]
+ ],
)
else:
raise NotImplementedError
diff --git a/reme/tool/memory/__init__.py b/reme/tool/memory/__init__.py
index 16e0d727..fe4e8431 100644
--- a/reme/tool/memory/__init__.py
+++ b/reme/tool/memory/__init__.py
@@ -9,7 +9,6 @@ from .profiles.profile_handler import ProfileHandler
from .profiles.read_all_profiles import ReadAllProfiles
from .profiles.add_profile import AddProfile
from .profiles.update_profile import UpdateProfile
-from .profiles.update_profile_filter_older import UpdateProfileFilterOlder
from .profiles.delete_profile import DeleteProfile
from .vector.add_draft_and_retrieve_similar_memory import AddAndRetrieveSimilarMemory
from .vector.add_memory import AddMemory
@@ -30,10 +29,10 @@ __all__ = [
"ReadHistory",
# Profiles
"AddDraftAndReadAllProfiles",
+ "AddProfile",
"ProfileHandler",
"ReadAllProfiles",
"UpdateProfile",
- "UpdateProfileFilterOlder",
"DeleteProfile",
# Vector
"AddAndRetrieveSimilarMemory",
diff --git a/reme/tool/memory/profiles/add_profile.py b/reme/tool/memory/profiles/add_profile.py
new file mode 100644
index 00000000..880bcf45
--- /dev/null
+++ b/reme/tool/memory/profiles/add_profile.py
@@ -0,0 +1,71 @@
+"""Add user profile tool"""
+
+from loguru import logger
+
+from .profile_handler import ProfileHandler
+from ..base_memory_tool import BaseMemoryTool
+from ....core.schema import ToolCall
+
+
+class AddProfile(BaseMemoryTool):
+ """Tool to add a single profile entry"""
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ def _build_tool_call(self) -> ToolCall:
+ """Build and return the tool call schema"""
+ return ToolCall(
+ **{
+ "description": "Add a new profile entry for the user.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "message_time": {
+ "type": "string",
+ "description": "Message time, e.g. '2020-01-01 00:00:00'",
+ },
+ "profile_key": {
+ "type": "string",
+ "description": "Profile key or category, e.g. 'name'",
+ },
+ "profile_value": {
+ "type": "string",
+ "description": "Profile value or content, e.g. 'John Smith'",
+ },
+ },
+ "required": ["message_time", "profile_key", "profile_value"],
+ },
+ },
+ )
+
+ async def execute(self):
+ profile_handler = ProfileHandler(profile_path=self.profile_path, memory_target=self.memory_target)
+
+ # Get parameters
+ message_time = self.context.get("message_time", "")
+ profile_key = self.context.get("profile_key", "")
+ profile_value = self.context.get("profile_value", "")
+
+ if not profile_key or not profile_value:
+ return "Missing required parameters (profile_key or profile_value), operation cancelled."
+
+ # Build profile dict
+ profile = {
+ "message_time": message_time,
+ "profile_key": profile_key,
+ "profile_value": profile_value,
+ }
+
+ # Add profile using ProfileHandler
+ new_nodes = profile_handler.add_batch(profiles=[profile], ref_memory_id=self.history_id)
+ self.memory_nodes.extend(new_nodes)
+
+ if new_nodes:
+ output = f"Successfully added profile: [{profile_key}] = {profile_value}"
+ logger.info(output)
+ return output
+ else:
+ output = "Failed to add profile."
+ logger.warning(output)
+ return output
diff --git a/reme/tool/memory/profiles/delete_profile.py b/reme/tool/memory/profiles/delete_profile.py
new file mode 100644
index 00000000..ae5bbc05
--- /dev/null
+++ b/reme/tool/memory/profiles/delete_profile.py
@@ -0,0 +1,53 @@
+"""Delete user profile tool"""
+
+from loguru import logger
+
+from .profile_handler import ProfileHandler
+from ..base_memory_tool import BaseMemoryTool
+from ....core.schema import ToolCall
+
+
+class DeleteProfile(BaseMemoryTool):
+ """Tool to delete a single profile entry by ID"""
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ def _build_tool_call(self) -> ToolCall:
+ """Build and return the tool call schema"""
+ return ToolCall(
+ **{
+ "description": "Delete a profile entry by profile ID.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "profile_id": {
+ "type": "string",
+ "description": "The unique ID of the profile to delete.",
+ },
+ },
+ "required": ["profile_id"],
+ },
+ },
+ )
+
+ async def execute(self):
+ profile_handler = ProfileHandler(profile_path=self.profile_path, memory_target=self.memory_target)
+
+ # Get profile_id parameter
+ profile_id = self.context.get("profile_id", "")
+
+ if not profile_id:
+ return "No profile_id provided, operation cancelled."
+
+ # Delete profile using ProfileHandler
+ success = profile_handler.delete(profile_id)
+
+ if success:
+ output = f"Successfully deleted profile with ID: {profile_id}"
+ logger.info(output)
+ return output
+ else:
+ output = f"Profile with ID '{profile_id}' not found."
+ logger.warning(output)
+ return output
diff --git a/reme/tool/memory/profiles/update_profile.py b/reme/tool/memory/profiles/update_profile.py
index 94e653aa..37512714 100644
--- a/reme/tool/memory/profiles/update_profile.py
+++ b/reme/tool/memory/profiles/update_profile.py
@@ -90,6 +90,7 @@ class UpdateProfile(BaseMemoryTool):
if self.enable_memory_target:
# Group profiles by memory_target
from collections import defaultdict
+
profiles_by_target = defaultdict(list)
for profile in profiles_to_add:
target = profile.get("memory_target", self.memory_target)