feat(memory): restructure memory agent modules and enhance vector store operations

This commit is contained in:
jinli.yl 2026-01-23 23:53:54 +08:00
parent 9f8ad20c53
commit 035703029f
122 changed files with 630 additions and 6401 deletions

View file

@ -0,0 +1,9 @@
"""memory agent"""
from . import default
from .base_memory_agent import BaseMemoryAgent
__all__ = [
"default",
"BaseMemoryAgent",
]

View file

@ -4,7 +4,38 @@ from abc import ABCMeta
from ...core.enumeration import MemoryType
from ...core.op import BaseReact
from ...core.schema import MemoryNode
class BaseMemoryAgent(BaseReact, metaclass=ABCMeta):
memory_type: MemoryType | None = None
@property
def memory_target(self) -> str:
"""memory_target"""
return self.context.get("memory_target", "")
@property
def query(self) -> str:
"""query"""
return self.context.get("query", "")
@property
def messages(self) -> list:
"""messages"""
return self.context.get("messages", [])
@property
def description(self) -> str:
"""description"""
return self.context.get("description", "")
@property
def history_node(self) -> MemoryNode:
"""Returns the history node."""
return self.context.history_node
@property
def author(self) -> str:
"""Returns the LLM model name as the author identifier."""
return self.llm.model_name

View file

@ -0,0 +1,11 @@
from .personal_retriever import PersonalRetriever
from .personal_summarizer import PersonalSummarizer
from .reme_retriever import ReMeRetriever
from .reme_summarizer import ReMeSummarizer
__all__ = [
"PersonalRetriever",
"PersonalSummarizer",
"ReMeRetriever",
"ReMeSummarizer",
]

View file

@ -1,18 +1,27 @@
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message
from ...core.utils import format_messages
from ...mem_tool.v4 import ReadUserProfile
from ....core.enumeration import Role, MemoryType
from ....core.schema import Message
from ....core.utils import format_messages
class PersonalRetrieverV4(BaseMemoryAgent):
class PersonalRetriever(BaseMemoryAgent):
"""Retrieve personal memories through vector search and history reading."""
memory_type: MemoryType = MemoryType.PERSONAL
async def build_messages(self) -> list[Message]:
context = self.context.query if self.context.get("query") else format_messages(self.context.messages) if self.context.get("messages") else None
from ....tool.memory.vector import ReadUserProfile
# Get context from query or messages
context = (
self.context.query
if self.context.get("query")
else format_messages(self.context.messages) if self.context.get("messages") else None
)
if not context:
raise ValueError("input must have either `query` or `messages`")
# Read user profile with history IDs
read_profile_tool = ReadUserProfile(show_ids="history")
await read_profile_tool.call(memory_type=self.memory_type.value, memory_target=self.memory_target)
self.context.user_profile = user_profile = read_profile_tool.output
@ -26,7 +35,8 @@ class PersonalRetrieverV4(BaseMemoryAgent):
memory_target=self.memory_target,
user_profile=user_profile,
context=context,
))
),
),
]
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
@ -39,14 +49,14 @@ class PersonalRetrieverV4(BaseMemoryAgent):
)
async def execute(self):
"""Execute the retriever and determine success based on output markers."""
"""Execute retriever and check for memory found markers."""
await super().execute()
# Check for memory found/not found markers in the output
# Check output markers
if self.output:
if "<MEMORY_FOUND>" in self.output:
self.success = True
elif "<MEMORY_NOT_FOUND>" in self.output:
self.success = False
self.meta_info = self.context.user_profile + "\n" + self.meta_info
self.meta_info = self.context.user_profile + "\n" + self.meta_info

View file

@ -0,0 +1,16 @@
tool: |
Retrieve personal memories via vector search and history reading to answer user questions.
user_message: |
You manage **{memory_type}** memories about **{memory_target}**.
## Profile
{user_profile}
## Question
{context}
## Task
1. Vector search (`retrieve_memory`): Try 3-5 queries with different phrasings, entities, keywords, and time ranges [start, end] in YYYYMMDD
2. Read context (`read_history`): Use history_id from results to get full conversations
3. Respond: `<MEMORY_FOUND>` if found, `<MEMORY_NOT_FOUND>` if not found after thorough search

View file

@ -1,17 +1,19 @@
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, MemoryNode
from ....core.enumeration import Role, MemoryType
from ....core.schema import Message
class PersonalSummarizerV4(BaseMemoryAgent):
class PersonalSummarizer(BaseMemoryAgent):
"""Extract and update personal memories in two phases: add summaries then update profile."""
memory_type: MemoryType = MemoryType.PERSONAL
async def build_messages_phase1(self) -> list[Message]:
"""Build messages for phase 1: AddSummaryMemory"""
history_node: MemoryNode = self.context.history_node
messages = [
"""Phase 1: AddSummaryMemory"""
history_node = self.context.history_node
return [
Message(
role=Role.USER,
content=self.prompt_format(
@ -19,14 +21,14 @@ class PersonalSummarizerV4(BaseMemoryAgent):
context=history_node.content,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
)),
),
),
]
return messages
async def build_messages_phase2(self, user_profile: str) -> list[Message]:
"""Build messages for phase 2: UpdateUserProfile"""
history_node: MemoryNode = self.context.history_node
messages = [
"""Phase 2: UpdateUserProfile"""
history_node = self.context.history_node
return [
Message(
role=Role.USER,
content=self.prompt_format(
@ -35,9 +37,9 @@ class PersonalSummarizerV4(BaseMemoryAgent):
memory_type=self.memory_type.value,
memory_target=self.memory_target,
user_profile=user_profile,
)),
),
),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, stage: str = "", **kwargs) -> list[Message]:
return await super()._acting_step(
@ -52,73 +54,61 @@ class PersonalSummarizerV4(BaseMemoryAgent):
)
async def execute(self):
"""Execute in two phases: 1) AddSummaryMemory, 2) UpdateUserProfile"""
# Log available tools
"""Execute two phases: AddSummaryMemory -> UpdateUserProfile"""
from ....tool.memory.vector import ReadUserProfile
# Log tools
for i, tool in enumerate(self.tools):
logger.info(
f"[{self.__class__.__name__}] step0.{i} "
f"tool_call={tool.tool_call.name}",
)
logger.info(f"[{self.__class__.__name__}] step0.{i} tool_call={tool.tool_call.name}")
# Phase 1: AddSummaryMemory
logger.info(f"[{self.__class__.__name__}-S1] Starting Phase 1: AddSummaryMemory")
# Filter tools for phase 1 (only AddSummaryMemory)
logger.info(f"[{self.__class__.__name__}-S1] Phase 1: AddSummaryMemory")
original_tools = self.tools.copy()
self.tools = [t for t in self.tools if t.tool_call.name == "add_summary_memory"]
messages_phase1 = await self.build_messages_phase1()
for i, message in enumerate(messages_phase1):
logger.info(
f"[{self.__class__.__name__}-S1] phase1.step0.{i} {message.role} "
f"{message.simple_dump(enable_json_dump=True)}",
f"[{self.__class__.__name__}-S1] phase1.step0.{i} {message.role} {message.simple_dump(enable_json_dump=True)}"
)
messages_phase1, success_phase1 = await self.react(messages_phase1, stage="S1")
if not success_phase1:
logger.warning(f"[{self.__class__.__name__}-S1] Phase 1 did not complete successfully")
logger.warning(f"[{self.__class__.__name__}-S1] Phase 1 incomplete")
# Phase 2: Read user profile and UpdateUserProfile
logger.info(f"[{self.__class__.__name__}-S2] Starting Phase 2: UpdateUserProfile")
# Restore original tools and get ReadUserProfile tool
# Phase 2: UpdateUserProfile
logger.info(f"[{self.__class__.__name__}-S2] Phase 2: UpdateUserProfile")
self.tools = original_tools
read_profile_tool = next((t for t in self.tools if t.tool_call.name == "read_user_profile"), None)
user_profile = ""
if read_profile_tool:
# Call ReadUserProfile to load current profile (only show profile_id, not history_id)
logger.info(f"[{self.__class__.__name__}-S2] Loading user profile with ReadUserProfile")
logger.info(f"[{self.__class__.__name__}-S2] Loading user profile")
await read_profile_tool.call(
memory_type=self.memory_type.value,
memory_target=self.memory_target,
show_ids="profile",
)
user_profile = str(read_profile_tool.output)
logger.info(f"[{self.__class__.__name__}-S2] User profile loaded: {user_profile}...")
else:
logger.warning(f"[{self.__class__.__name__}-S2] ReadUserProfile tool not found")
# Filter tools for phase 2 (only UpdateUserProfile)
self.tools = [t for t in self.tools if t.tool_call.name == "update_user_profile"]
messages_phase2 = await self.build_messages_phase2(user_profile)
for i, message in enumerate(messages_phase2):
logger.info(
f"[{self.__class__.__name__}-S2] phase2.step0.{i} {message.role} "
f"{message.simple_dump(enable_json_dump=True)}",
f"[{self.__class__.__name__}-S2] phase2.step0.{i} {message.role} {message.simple_dump(enable_json_dump=True)}"
)
messages_phase2, success_phase2 = await self.react(messages_phase2, stage="S2")
# Restore original tools
# Restore tools and set output
self.tools = original_tools
# Set final output and messages
self.messages = messages_phase1 + messages_phase2
self.success = success_phase1 and success_phase2
if self.success and messages_phase2:
self.output = messages_phase2[-1].content
else:
self.output = "Memory processing completed with issues."
self.output = (
messages_phase2[-1].content
if self.success and messages_phase2
else "Memory processing completed with issues."
)

View file

@ -0,0 +1,26 @@
user_message_phase1: |
You manage **{memory_type}** memories about **{memory_target}**.
## Conversation
{context}
Format: `round<index> [<timestamp>] <role/name>: <content>` (timestamp: YYYY-MM-DD HH:MM:SS).
## Task
Extract memories with `add_memory`. Set `conversation_time` (YYYY-MM-DD HH:MM:SS, use 0000-00-00 00:00:00 if unavailable). Extract ONLY explicit information, no inference.
user_message_phase2: |
You manage **{memory_type}** memories about **{memory_target}**.
## Conversation
{context}
## Profile
{user_profile}
## Task
Update profile with `UpdateUserProfile`:
- `profile_ids_to_delete`: Remove conflicting/redundant entries
- `profiles_to_add`: Add new entries with `conversation_time` and `profile_content` (complete, self-contained, mutually exclusive)
Extract ONLY explicit information, no inference.

View file

@ -1,12 +1,13 @@
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role
from ...core.schema import Message
from ...core.utils import format_messages
from ....core.enumeration import Role
from ....core.schema import Message
from ....core.utils import format_messages
class ReMeRetrieverV4(BaseMemoryAgent):
class ReMeRetriever(BaseMemoryAgent):
"""Orchestrate multiple memory agents to retrieve information."""
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
super().__init__(**kwargs)
@ -14,20 +15,22 @@ class ReMeRetrieverV4(BaseMemoryAgent):
self.meta_info_dict: dict[str, str] = {}
async def _read_meta_memories(self) -> str:
from ...mem_tool import ReadMetaMemory
from ....tool.memory import ReadMetaMemory
meta_memory_info = ReadMetaMemory().format_memory_metadata(self.meta_memories)
logger.info(f"meta_memory_info={meta_memory_info}")
return meta_memory_info
async def build_messages(self) -> list[Message]:
if self.context.get("query"):
user_query = self.context.query
elif self.context.get("messages"):
user_query = format_messages(self.context.messages)
else:
user_query = (
self.context.query
if self.context.get("query")
else format_messages(self.context.messages) if self.context.get("messages") else None
)
if not user_query:
raise ValueError("Input must have either `query` or `messages`")
messages = [
return [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
@ -42,11 +45,9 @@ class ReMeRetrieverV4(BaseMemoryAgent):
),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
import asyncio
from ...mem_tool.v4 import HandsOff
from ....tool.memory import HandsOff
if not assistant_message.tool_calls:
return []
@ -56,7 +57,7 @@ class ReMeRetrieverV4(BaseMemoryAgent):
tool_dict = {t.tool_call.name: t for t in self.tools}
stage_prefix = ""
# Add required context parameters
# Add context parameters
kwargs["query"] = self.context.get("query", "")
kwargs["messages"] = self.context.get("messages", [])
@ -66,8 +67,7 @@ class ReMeRetrieverV4(BaseMemoryAgent):
continue
logger.info(
f"[{self.__class__.__name__}{stage_prefix}] step{step + 1}.{j} "
f"submit tool_calls={tool_call.name} argument={tool_call.arguments}",
f"[{self.__class__.__name__}{stage_prefix}] step{step + 1}.{j} submit tool_calls={tool_call.name} argument={tool_call.arguments}"
)
tool_copy = tool_dict[tool_call.name].copy()
tool_copy.tool_call.id = tool_call.id
@ -86,7 +86,7 @@ class ReMeRetrieverV4(BaseMemoryAgent):
if hasattr(op, "messages") and op.messages:
self.tool_messages.extend(op.messages)
# Collect meta_info_dict from HandsOff tool
# Collect meta_info_dict from HandsOff
if isinstance(op, HandsOff) and hasattr(op, "meta_info_dict"):
self.meta_info_dict.update(op.meta_info_dict)
logger.info(f"Collected meta_info_dict from HandsOff: {len(op.meta_info_dict)} entries")
@ -98,20 +98,19 @@ class ReMeRetrieverV4(BaseMemoryAgent):
tool_call_id=op.tool_call.id,
)
tool_result_messages.append(tool_message)
self.meta_info += tool_result + "\n"
logger.info(f"[{self.__class__.__name__}{stage_prefix}] step{step + 1}.{j} join tool_result={tool_result[:2000]}...\n\n")
logger.info(
f"[{self.__class__.__name__}{stage_prefix}] step{step + 1}.{j} join tool_result={tool_result[:2000]}...\n\n"
)
return tool_result_messages
async def execute(self):
"""Execute and assemble meta_info_dict into output."""
await super().execute()
# Assemble meta_info_dict into output
# Assemble meta_info_dict
if self.meta_info_dict:
output_parts = []
for key, value in self.meta_info_dict.items():
output_parts.append(f"## {key}\n{value}")
output_parts = [f"## {key}\n{value}" for key, value in self.meta_info_dict.items()]
self.output = "\n\n".join(output_parts)
logger.info(f"Assembled output from meta_info_dict with {len(self.meta_info_dict)} entries")
logger.info(f"Assembled output from meta_info_dict with {len(self.meta_info_dict)} entries")

View file

@ -0,0 +1,20 @@
tool: |
Retrieve information from specialized memory agents.
system_prompt: |
You orchestrate memory agents to answer user queries.
# Query
{user_query}
## Agents
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Task
1. Use `hands_off` to query agents (memory_type and memory_target must exactly match existing agents)
2. Answer based on results
3. If insufficient: "nothing found after thorough search."
user_message: |
Retrieve information from agents and answer based on results.

View file

@ -1,9 +1,9 @@
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role, MemoryType
from ....core.schema import Message, MemoryNode
from ....core.utils import format_messages
from ....core.enumeration import Role
from ....core.op import BaseTool
from ....core.schema import Message
class ReMeSummarizer(BaseMemoryAgent):
@ -14,26 +14,17 @@ class ReMeSummarizer(BaseMemoryAgent):
async def _read_meta_memories(self) -> str:
from ....tool.memory import ReadMetaMemory
meta_memory_info = ReadMetaMemory().format_memory_metadata(self.meta_memories)
logger.info(f"meta_memory_info={meta_memory_info}")
return meta_memory_info
async def build_messages(self) -> list[Message]:
self.context.messages = [Message(**m) if isinstance(m, dict) else m for m in self.context.messages]
history_content = self.description + "\n" + format_messages(self.context.messages)
self.context.history_node = history_node = MemoryNode(
memory_type=MemoryType.HISTORY,
memory_target="",
when_to_use=history_content[:100],
content=history_content,
ref_memory_id="",
author=self.author,
metadata={},
)
from ....tool.memory import AddHistory
logger.info(f"Adding summary node: {history_node.model_dump_json(indent=2, exclude_none=True)}")
await self.vector_store.delete(history_node.memory_id)
await self.vector_store.insert([history_node.to_vector_node()])
add_history_tool = AddHistory()
await add_history_tool.call()
self.context.history_node = add_history_tool.context.add_history
messages = [
Message(
@ -41,7 +32,7 @@ class ReMeSummarizer(BaseMemoryAgent):
content=self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self._read_meta_memories(),
context=history_node.content,
context=self.context.history_node.content,
),
),
Message(
@ -52,10 +43,17 @@ class ReMeSummarizer(BaseMemoryAgent):
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
async def _acting_step(
self,
assistant_message: Message,
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
return await super()._acting_step(
assistant_message,
step,
description=self.description,
messages=self.context.messages,
history_node=self.context.history_node,
author=self.author,

View file

@ -1,6 +1,3 @@
tool: |
Orchestrate memory updates across specialized memory agents.
system_prompt: |
You are a Memory Orchestrator responsible for routing memory tasks to specialized agents based on the context.
@ -8,7 +5,7 @@ system_prompt: |
{context}
## Available Memory Agents
Each line indicates a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension (memory_type + memory_target).
Each line indicates a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension <memory_type>(<memory_target>).
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
@ -17,7 +14,7 @@ system_prompt: |
1. Analyze the context and identify which memory dimensions require updates
2. Specify `memory_type` and `memory_target` for each task
- The `memory_type` and `memory_target` must **exactly match** existing entries in the "Available Memory Agents" listed above
- Do NOT create new agents or use memory_type/memory_target combinations that don't exist above
- Do NOT create new agents or use <memory_type>(<memory_target>) combinations that don't exist above
3. Multiple tasks can be specified to enable parallel processing by specialized agents
Note: If the context contains no memorable information (e.g., simple greetings), return `<NO_MEMORY_NEEDED>`.

View file

@ -21,6 +21,7 @@ from ..vector_store import BaseVectorStore
class BaseOp(metaclass=ABCMeta):
"""Base operator class for LLM workflow execution and composition."""
__alias_name__: str = ""
def __new__(cls, *args, **kwargs):

View file

@ -1,24 +1,27 @@
"""Base memory agent for handling memory operations with tool-based reasoning."""
import asyncio
from typing import TYPE_CHECKING
from loguru import logger
from . import BaseTool
from ..enumeration import Role
from ..op import BaseOp
from ..schema import Message
if TYPE_CHECKING:
from . import BaseTool
class BaseReact(BaseOp):
"""ReAct agent that performs reasoning and acting cycles with tools."""
def __init__(
self,
tools: list[BaseTool],
tool_call_interval: float = 0,
max_steps: int = 10,
**kwargs,
self,
tools: list[BaseTool],
tool_call_interval: float = 0,
max_steps: int = 10,
**kwargs,
):
"""Initialize ReAct agent with tools and execution parameters."""
kwargs["sub_ops"] = tools or []
@ -44,11 +47,12 @@ class BaseReact(BaseOp):
return messages
async def _reasoning_step(
self,
messages: list[Message],
step: int,
stage: str = "",
**kwargs) -> tuple[Message, bool]:
self,
messages: list[Message],
step: int,
stage: str = "",
**kwargs,
) -> tuple[Message, bool]:
"""Execute one reasoning step where LLM decides whether to use tools."""
# Get tool definitions for LLM
tool_calls = [t.tool_call for t in self.tools]
@ -62,11 +66,11 @@ class BaseReact(BaseOp):
return assistant_message, should_act
async def _acting_step(
self,
assistant_message: Message,
step: int,
stage: str = "",
**kwargs
self,
assistant_message: Message,
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
"""Execute tool calls requested by the assistant and collect results."""
tool_list: list[BaseTool] = []
@ -101,11 +105,13 @@ class BaseReact(BaseOp):
# Collect tool results as messages
for j, tool in enumerate(tool_list):
tool_messages.append(Message(
role=Role.TOOL,
content=tool.response.answer,
tool_call_id=tool.tool_call.id,
))
tool_messages.append(
Message(
role=Role.TOOL,
content=tool.response.answer,
tool_call_id=tool.tool_call.id,
),
)
prefix: str = f"[{self.__class__.__name__} {stage or ''} step{step + 1}.{j}]"
logger.info(f"{prefix} join tool={tool.name} result={tool.response.answer}")
return tool_list, tool_messages

View file

@ -131,7 +131,7 @@ class ToolCall(BaseModel):
def simple_input_dump(self, as_dict: bool = True) -> dict | str:
"""Returns a standardized tool definition dictionary or JSON string.
Args:
as_dict: If True, returns dict; if False, returns JSON string.
"""
@ -147,7 +147,7 @@ class ToolCall(BaseModel):
def simple_output_dump(self, as_dict: bool = True) -> dict | str:
"""Convert ToolCall to output format dictionary or JSON string for API responses.
Args:
as_dict: If True, returns dict; if False, returns JSON string.
"""

View file

@ -1,6 +1,7 @@
"""memory tools"""
from .base_memory_tool import BaseMemoryTool
from .hands_off.hands_off import HandsOff
from .history.add_history import AddHistory
from .history.read_history import ReadHistory
from .identity.add_identity import AddIdentity
@ -9,10 +10,16 @@ from .meta.add_meta_memory import AddMetaMemory
from .meta.read_meta_memory import ReadMetaMemory
from .user_profile.read_user_profile import ReadUserProfile
from .user_profile.update_user_profile import UpdateUserProfile
from .vector.add_memory import AddMemory
from .vector.update_memory import UpdateMemory
from .vector.retrieve_memory import VectorRetrieveMemory
from .vector.retrieve_recent_memory import VectorRetrieveRecentMemory
from .vector.delete_memory import DeleteMemory
from ...core import R
__all__ = [
"BaseMemoryTool",
"HandsOff",
"AddHistory",
"ReadHistory",
"AddIdentity",
@ -21,6 +28,11 @@ __all__ = [
"ReadMetaMemory",
"ReadUserProfile",
"UpdateUserProfile",
"AddMemory",
"UpdateMemory",
"VectorRetrieveMemory",
"VectorRetrieveRecentMemory",
"DeleteMemory",
]
for name in __all__:

View file

@ -0,0 +1,105 @@
"""Hands-off tool to delegate memory tasks to specific agents"""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ....agent.memory import BaseMemoryAgent
from ....core.enumeration import MemoryType
from ....core.schema import ToolCall
class HandsOff(BaseMemoryTool):
"""Tool to delegate memory tasks to appropriate memory agents"""
def __init__(self, memory_agents: list[BaseMemoryAgent] = None, **kwargs):
kwargs["enable_multiple"] = True
kwargs["sub_ops"] = memory_agents or []
super().__init__(**kwargs)
self.sub_ops: list[BaseMemoryAgent] = [
a for a in self.sub_ops if isinstance(a, BaseMemoryAgent) and a.memory_type is not None
]
@property
def memory_agent_dict(self) -> dict[MemoryType, BaseMemoryAgent]:
"""Map memory types to their corresponding agents"""
return {a.memory_type: a for a in self.sub_ops}
def _build_multiple_tool_call(self) -> ToolCall:
"""Build and return the multiple tool call schema"""
return ToolCall(
**{
"description": "Delegate memory tasks to appropriate agents.",
"parameters": {
"type": "object",
"properties": {
"memory_tasks": {
"type": "array",
"description": "Memory tasks to delegate to specific agents",
"items": {
"type": "object",
"properties": {
"memory_type": {
"type": "string",
"description": "Memory type to handle",
"enum": [k.value for k in self.memory_agent_dict if k],
},
"memory_target": {
"type": "string",
"description": "Target or context for the memory operation",
},
},
"required": ["memory_type", "memory_target"],
},
},
},
"required": ["memory_tasks"],
},
},
)
async def execute(self):
# Deduplicate and validate tasks
tasks = []
seen = set()
for task in self.context.get("memory_tasks", []):
memory_type = MemoryType(task.get("memory_type", ""))
memory_target = task.get("memory_target", "")
task_key = (memory_type, memory_target)
if task_key in seen:
logger.info(f"Skip duplicate: {memory_type.value} - {memory_target}")
continue
seen.add(task_key)
tasks.append({"memory_type": memory_type, "memory_target": memory_target})
if not tasks:
return "No valid memory tasks to execute."
# Submit tasks to agents
agent_list: list[BaseMemoryAgent] = []
for i, task in enumerate(tasks):
memory_type: MemoryType = task["memory_type"]
memory_target: str = task["memory_target"]
agent = self.memory_agent_dict[memory_type].copy()
agent_list.append(agent)
logger.info(f"Task {i}: {memory_type.value} agent for {memory_target}")
task_kwargs = {"memory_type": memory_type, "memory_target": memory_target}
for k in ["query", "messages", "description", "history_node"]:
if k in self.context:
task_kwargs[k] = self.context[k]
self.submit_async_task(agent.call, **task_kwargs)
await self.join_async_tasks()
# Collect results
results = []
for agent in agent_list:
memory_type = agent.memory_type
memory_target = agent.memory_target
results.append(f"{memory_type.value}({memory_target}): {agent.response.answer}")
logger.info(f"Completed {len(results)} task(s)")
return "\n".join(results)

View file

@ -38,6 +38,7 @@ class AddHistory(BaseMemoryTool):
content=history_content,
author=self.author,
)
self.context.history_node = history_node
logger.info(f"Adding history node: {history_node.model_dump_json(indent=2, exclude={'content'})}")
vector_node = history_node.to_vector_node()

View file

@ -33,6 +33,20 @@ class ReadMetaMemory(BaseMemoryTool):
},
)
def format_memory_metadata(self, memories: list[dict[str, str]]) -> str:
"""Format memory metadata into a readable string."""
if not memories:
return ""
lines = []
for memory in memories:
memory_type = memory["memory_type"]
memory_target = memory["memory_target"]
description = self.TYPE_DESC_DICT[memory_type]
lines.append(f"- {memory_type}({memory_target}): {description}")
return "\n".join(lines)
async def execute(self):
# Load and filter meta memories
result = self.local_memory.load("meta_memories")
@ -51,13 +65,8 @@ class ReadMetaMemory(BaseMemoryTool):
)
# Format output
if memories:
lines = [
f"- {m['memory_type']}({m['memory_target']}): {self.TYPE_DESC_DICT.get(m['memory_type'], '')}"
for m in memories
]
output = "\n".join(lines)
output = self.format_memory_metadata(memories)
if output:
logger.info(f"Retrieved {len(memories)} meta memory entries")
else:
output = "No memory metadata found."

View file

@ -9,9 +9,27 @@ from ....core.schema import ToolCall, MemoryNode
class AddMemory(BaseMemoryTool):
"""Tool to add memories to vector store"""
def __init__(self, **kwargs):
kwargs['enable_multiple'] = True
super().__init__(**kwargs)
def _build_tool_call(self) -> ToolCall:
"""Build and return the single tool call schema"""
return ToolCall(
**{
"description": "add a memory to vector store for future retrieval.",
"parameters": {
"type": "object",
"properties": {
"conversation_time": {
"type": "string",
"description": "conversation time, e.g. '2020-01-01 00:00:00'",
},
"memory_content": {
"type": "string",
"description": "content of the memory.",
},
},
"required": ["conversation_time", "memory_content"],
},
},
)
def _build_multiple_tool_call(self) -> ToolCall:
"""Build and return the multiple tool call schema"""
@ -27,16 +45,16 @@ class AddMemory(BaseMemoryTool):
"items": {
"type": "object",
"properties": {
"conversation_time": {
"type": "string",
"description": "conversation time, e.g. '2020-01-01 00:00:00'",
},
"memory_content": {
"type": "string",
"description": "content of the memory.",
},
"metadata": {
"type": "object",
"description": "metadata for the memory.",
}
},
"required": ["memory_content"],
"required": ["conversation_time", "memory_content"],
},
},
},
@ -45,42 +63,39 @@ class AddMemory(BaseMemoryTool):
},
)
def _extract_memory_data(self, mem_dict: dict) -> tuple[str, dict]:
"""Extract memory content and metadata from dict"""
memory_content = mem_dict.get("memory_content", "")
raw_metadata = mem_dict.get("metadata", {})
metadata = {key: str(value).strip() for key, value in raw_metadata.items() if value}
return memory_content, metadata
def _create_memory_node(self, data: dict) -> MemoryNode:
"""Create a MemoryNode from a dictionary."""
memory_content = data.get("memory_content", "")
conversation_time = data.get("conversation_time", "")
metadata: dict = {"conversation_time": conversation_time}
try:
metadata["time_int"] = int(conversation_time.split(" ")[0].replace("-", ""))
except Exception:
logger.warning(f"Invalid conversation time format. {conversation_time}")
def _build_memory_node(self, content: str, metadata: dict = None) -> MemoryNode:
"""Build a memory node"""
return MemoryNode(
memory_type=self.memory_type,
memory_target=self.memory_target,
content=content,
content=memory_content,
author=self.author,
metadata=metadata or {},
metadata=metadata,
)
async def execute(self):
memory_nodes: list[MemoryNode] = []
memories: list[dict] = self.context.get("memories", [])
if not memories:
self.output = "No memories provided for addition."
return
for mem in memories:
memory_content, metadata = self._extract_memory_data(mem)
if not memory_content:
logger.warning("Skipping memory with empty content")
continue
memory_nodes.append(self._build_memory_node(memory_content, metadata=metadata))
memory_nodes.append(self._create_memory_node(self.context))
else:
for mem in memories:
memory_nodes.append(self._create_memory_node(mem))
if not memory_nodes:
self.output = "No valid memories provided for addition."
return
output = "No valid memories provided for addition."
logger.info(output)
return output
vector_nodes = [node.to_vector_node() for node in memory_nodes]
vector_ids: list[str] = [node.vector_id for node in vector_nodes]
@ -89,5 +104,6 @@ class AddMemory(BaseMemoryTool):
await self.vector_store.insert(nodes=vector_nodes)
self.memory_nodes = memory_nodes
self.output = f"Successfully added {len(memory_nodes)} memories to vector_store."
logger.info(self.output)
output = f"Successfully added {len(memory_nodes)} memories to vector_store."
logger.info(output)
return output

View file

@ -9,9 +9,23 @@ from ....core.schema import ToolCall
class DeleteMemory(BaseMemoryTool):
"""Tool to delete memories from vector store"""
def __init__(self, **kwargs):
kwargs['enable_multiple'] = True
super().__init__(**kwargs)
def _build_tool_call(self) -> ToolCall:
"""Build and return the single tool call schema"""
return ToolCall(
**{
"description": "delete a memory from vector store using its unique ID.",
"parameters": {
"type": "object",
"properties": {
"memory_id": {
"type": "string",
"description": "unique identifier (memory_id) of the memory to delete.",
},
},
"required": ["memory_id"],
},
},
)
def _build_multiple_tool_call(self) -> ToolCall:
"""Build and return the multiple tool call schema"""
@ -33,13 +47,25 @@ class DeleteMemory(BaseMemoryTool):
)
async def execute(self):
memory_ids = [m for m in self.context.get("memory_ids", []) if m]
memory_ids: list[str] = []
# Handle multiple memories (array format)
ids_from_array = self.context.get("memory_ids", [])
if ids_from_array:
memory_ids = [m for m in ids_from_array if m]
else:
memory_id = self.context.get("memory_id", "")
if memory_id:
memory_ids = [memory_id]
if not memory_ids:
self.output = "No valid memory IDs provided for deletion."
return
output = "No valid memory IDs provided for deletion."
logger.info(output)
return output
await self.vector_store.delete(vector_ids=memory_ids)
self.memory_nodes = memory_ids
self.output = f"Successfully deleted {len(memory_ids)} memories from vector_store."
logger.info(self.output)
output = f"Successfully deleted {len(memory_ids)} memories from vector_store."
logger.info(output)
return output

View file

@ -11,65 +11,40 @@ from ....core.utils import deduplicate_memories
class VectorRetrieveMemory(BaseMemoryTool):
"""Tool to retrieve memories from vector store using similarity search"""
def __init__(
self,
add_memory_type_target: bool = False,
top_k: int = 20,
enable_metadata: bool = True,
**kwargs,
):
def __init__(self, top_k: int = 20, **kwargs):
super().__init__(**kwargs)
self.add_memory_type_target: bool = add_memory_type_target
self.top_k: int = top_k
self.enable_metadata: bool = enable_metadata
def _build_query_schema(self) -> tuple[dict, list[str]]:
"""Build query schema for single/multiple retrieval"""
properties = {}
required = []
if self.add_memory_type_target:
properties["memory_type"] = {
"type": "string",
"description": "type of memory to search for.",
}
properties["memory_target"] = {
"type": "string",
"description": "target of memory to search within.",
}
required.extend(["memory_type", "memory_target"])
properties["query"] = {
"type": "string",
"description": "query text for vector similarity search.",
@staticmethod
def _build_query_parameters() -> dict:
"""Build query parameters schema for retrieval"""
return {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "query text for vector similarity search.",
},
"time_range": {
"type": "string",
"description": "optional time range filter. "
"Format: single date '20200101' or range '20200101,20200102'",
},
},
"required": ["query"],
}
required.append("query")
if self.enable_metadata:
properties["metadata"] = {
"type": "object",
"description": "optional metadata filters for narrowing search results.",
}
return properties, required
def _build_tool_call(self) -> ToolCall:
"""Build and return the tool call schema"""
properties, required = self._build_query_schema()
return ToolCall(
**{
"description": "retrieve memories using vector similarity search.",
"parameters": {
"type": "object",
"properties": properties,
"required": required,
},
"parameters": self._build_query_parameters(),
},
)
def _build_multiple_tool_call(self) -> ToolCall:
"""Build and return the multiple tool call schema"""
item_properties, item_required = self._build_query_schema()
return ToolCall(
**{
"description": "retrieve memories using multiple queries with vector similarity search.",
@ -79,11 +54,7 @@ class VectorRetrieveMemory(BaseMemoryTool):
"query_items": {
"type": "array",
"description": "list of query items for vector similarity search.",
"items": {
"type": "object",
"properties": item_properties,
"required": item_required,
},
"items": self._build_query_parameters(),
},
},
"required": ["query_items"],
@ -96,90 +67,63 @@ class VectorRetrieveMemory(BaseMemoryTool):
memory_type: str,
memory_target: str,
query: str,
metadata: dict | None = None,
time_range: str | None = None,
) -> list[MemoryNode]:
"""Retrieve memories by query with filters"""
filter_dict = {
"memory_type": [memory_type],
"memory_target": [memory_target],
filter_dict: dict = {
"memory_type": memory_type,
"memory_target": memory_target,
}
if metadata:
for key, value in metadata.items():
if value:
value = str(value).strip()
filter_dict[key] = [value] if not isinstance(value, list) else value
if time_range:
time_range = time_range.strip()
if "," in time_range:
parts = time_range.split(",")
start_time = int(parts[0].strip())
end_time = int(parts[1].strip())
filter_dict["time_int"] = [start_time, end_time]
else:
single_time = int(time_range)
filter_dict["time_int"] = [single_time, single_time]
nodes: list[VectorNode] = await self.vector_store.search(query=query, limit=self.top_k, filters=filter_dict)
memory_nodes: list[MemoryNode] = [MemoryNode.from_vector_node(n) for n in nodes]
filtered_memory_nodes = [
m for m in memory_nodes if not (m.memory_type == MemoryType.TOOL and m.when_to_use != query)
]
return filtered_memory_nodes
return [MemoryNode.from_vector_node(n) for n in nodes]
async def execute(self):
default_memory_type: str = self.context.get("memory_type", "")
default_memory_target: str = self.context.get("memory_target", "")
memory_type: str = self.memory_type.value
memory_target: str = self.memory_target
if self.enable_multiple:
query_items: list[dict] = self.context.get("query_items", [])
if not query_items:
self.output = "No query items provided for retrieval."
return
else:
query = self.context.get("query", "")
if not query:
self.output = "No query provided for retrieval."
return
query_items = [
query_items: list[dict] = [
{
"memory_type": default_memory_type,
"memory_target": default_memory_target,
"query": query,
"query": self.context.get("query", ""),
"time_range": self.context.get("time_range", ""),
},
]
query_items = [item for item in query_items if item.get("query")]
if not query_items:
self.output = "No valid query texts provided for retrieval."
return
memory_nodes: list[MemoryNode] = []
for item in query_items:
memory_type = item.get("memory_type") or default_memory_type
memory_target = item.get("memory_target") or default_memory_target
metadata = item.get("metadata", {}) if self.enable_metadata else None
if not memory_type or not memory_target:
logger.warning(f"Skipping query with missing memory_type or memory_target: {item}")
continue
retrieved = await self._retrieve_by_query(
memory_type=memory_type,
memory_target=memory_target,
query=item["query"],
metadata=metadata,
time_range=item.get("time_range", ""),
)
memory_nodes.extend(retrieved)
memory_nodes = deduplicate_memories(memory_nodes)
retrieved_memory_ids = {node.memory_id for node in self.retrieved_nodes if node.memory_id}
new_memory_nodes = [node for node in memory_nodes if node.memory_id not in retrieved_memory_ids]
self.retrieved_nodes.extend(new_memory_nodes)
self.memory_nodes = new_memory_nodes
if not new_memory_nodes:
self.output = "No new memory_nodes found matching the query (duplicates removed)."
output = "No new memory_nodes found matching the query (duplicates removed)."
else:
self.output = "\n".join([m.format_memory() for m in new_memory_nodes])
output = "\n".join([m.format_memory() for m in new_memory_nodes])
logger.info(f"Retrieved {len(memory_nodes)} memory_nodes, {len(new_memory_nodes)} new after deduplication")
return output

View file

@ -0,0 +1,62 @@
"""Retrieve most recent memories from vector store"""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ....core.schema import ToolCall, MemoryNode, VectorNode
from ....core.utils import deduplicate_memories
class VectorRetrieveRecentMemory(BaseMemoryTool):
"""Tool to retrieve most recent memories sorted by conversation time"""
def __init__(self, top_k: int = 20, **kwargs):
kwargs["enable_multiple"] = False
super().__init__(**kwargs)
self.top_k: int = top_k
def _build_tool_call(self) -> ToolCall:
"""Build and return the tool call schema"""
return ToolCall(
**{
"description": "retrieve the most recent memories sorted by conversation time (newest first).",
"parameters": {
"type": "object",
"properties": {},
"required": [],
},
},
)
async def _retrieve_recent(self) -> list[MemoryNode]:
"""Retrieve recent memories sorted by conversation_time descending"""
filter_dict = {
"memory_type": self.memory_type.value,
"memory_target": self.memory_target,
}
nodes: list[VectorNode] = await self.vector_store.list(
filters=filter_dict,
limit=self.top_k,
sort_key="conversation_time",
reverse=True,
)
return [MemoryNode.from_vector_node(n) for n in nodes]
async def execute(self):
memory_nodes: list[MemoryNode] = await self._retrieve_recent()
memory_nodes = deduplicate_memories(memory_nodes)
retrieved_memory_ids = {node.memory_id for node in self.retrieved_nodes if node.memory_id}
new_memory_nodes = [node for node in memory_nodes if node.memory_id not in retrieved_memory_ids]
self.retrieved_nodes.extend(new_memory_nodes)
self.memory_nodes = new_memory_nodes
if not new_memory_nodes:
output = "No new memory_nodes found (duplicates removed)."
else:
output = "\n".join([m.format_memory() for m in new_memory_nodes])
logger.info(f"Retrieved {len(memory_nodes)} memory_nodes, {len(new_memory_nodes)} new after deduplication")
return output

View file

@ -9,9 +9,31 @@ from ....core.schema import ToolCall, MemoryNode
class UpdateMemory(BaseMemoryTool):
"""Tool to update memories in vector store"""
def __init__(self, **kwargs):
kwargs['enable_multiple'] = True
super().__init__(**kwargs)
def _build_tool_call(self) -> ToolCall:
"""Build and return the single tool call schema"""
return ToolCall(
**{
"description": "update a memory in vector store by replacing old memory with new content.",
"parameters": {
"type": "object",
"properties": {
"memory_id": {
"type": "string",
"description": "unique identifier of memory to update.",
},
"conversation_time": {
"type": "string",
"description": "conversation time, e.g. '2020-01-01 00:00:00'",
},
"memory_content": {
"type": "string",
"description": "new content of the memory.",
},
},
"required": ["memory_id", "conversation_time", "memory_content"],
},
},
)
def _build_multiple_tool_call(self) -> ToolCall:
"""Build and return the multiple tool call schema"""
@ -31,16 +53,16 @@ class UpdateMemory(BaseMemoryTool):
"type": "string",
"description": "unique identifier of memory to update.",
},
"conversation_time": {
"type": "string",
"description": "conversation time, e.g. '2020-01-01 00:00:00'",
},
"memory_content": {
"type": "string",
"description": "new content of the memory.",
},
"metadata": {
"type": "object",
"description": "metadata for the memory.",
}
},
"required": ["memory_id", "memory_content", "metadata"],
"required": ["memory_id", "conversation_time", "memory_content"],
},
},
},
@ -49,52 +71,56 @@ class UpdateMemory(BaseMemoryTool):
},
)
def _extract_memory_data(self, mem_dict: dict) -> tuple[str, str, dict]:
"""Extract memory id, content and metadata from dict"""
memory_id = mem_dict.get("memory_id", "")
memory_content = mem_dict.get("memory_content", "")
raw_metadata = mem_dict.get("metadata", {})
metadata = {key: str(value).strip() for key, value in raw_metadata.items() if value}
return memory_id, memory_content, metadata
def _create_memory_node(self, data: dict) -> tuple[str, MemoryNode]:
"""Create a MemoryNode from a dictionary."""
memory_id = data.get("memory_id", "")
memory_content = data.get("memory_content", "")
conversation_time = data.get("conversation_time", "")
metadata: dict = {"conversation_time": conversation_time}
def _build_memory_node(self, content: str, metadata: dict = None) -> MemoryNode:
"""Build a memory node"""
return MemoryNode(
try:
metadata["time_int"] = int(conversation_time.split(" ")[0].replace("-", ""))
except Exception:
logger.warning(f"Invalid conversation time format. {conversation_time}")
memory_node = MemoryNode(
memory_type=self.memory_type,
memory_target=self.memory_target,
content=content,
content=memory_content,
author=self.author,
metadata=metadata or {},
metadata=metadata,
)
return memory_id, memory_node
async def execute(self):
old_memory_ids: list[str] = []
new_memory_nodes: list[MemoryNode] = []
memory_nodes: list[MemoryNode] = []
memories: list[dict] = self.context.get("memories", [])
if not memories:
self.output = "No memories provided for update."
return
old_id, node = self._create_memory_node(self.context)
old_memory_ids.append(old_id)
memory_nodes.append(node)
else:
for mem in memories:
old_id, node = self._create_memory_node(mem)
old_memory_ids.append(old_id)
memory_nodes.append(node)
for mem in memories:
memory_id, memory_content, metadata = self._extract_memory_data(mem)
if not memory_id or not memory_content:
logger.warning(f"Skipping memory with missing id or content: {mem}")
continue
old_memory_ids.append(memory_id)
new_memory_nodes.append(self._build_memory_node(memory_content, metadata=metadata))
if not memory_nodes:
output = "No valid memories provided for update."
logger.info(output)
return output
if not old_memory_ids or not new_memory_nodes:
self.output = "No valid memories provided for update."
return
vector_nodes = [node.to_vector_node() for node in new_memory_nodes]
new_vector_ids = [node.vector_id for node in vector_nodes]
vector_nodes = [node.to_vector_node() for node in memory_nodes]
new_vector_ids: list[str] = [node.vector_id for node in vector_nodes]
all_ids_to_delete = list(set(old_memory_ids + new_vector_ids))
await self.vector_store.delete(vector_ids=all_ids_to_delete)
await self.vector_store.insert(nodes=vector_nodes)
self.memory_nodes = new_memory_nodes
self.memory_nodes = memory_nodes
self.output = f"Successfully updated {len(new_memory_nodes)} memories in vector_store."
logger.info(self.output)
output = f"Successfully updated {len(memory_nodes)} memories in vector_store."
logger.info(output)
return output

View file

@ -43,4 +43,4 @@
# examples
# bench 里的llm ,辛苦改成 app = ReMeApp() app.default_llm
# clear && pre-commit run --all-files
# clear && pre-commit run --all-files

View file

@ -1,13 +0,0 @@
"""memory agent"""
from . import chat
from . import retriever
from . import summarizer
from .base_memory_agent import BaseMemoryAgent
__all__ = [
"chat",
"retriever",
"summarizer",
"BaseMemoryAgent",
]

View file

@ -1,226 +0,0 @@
"""Base memory agent for handling memory operations with tool-based reasoning."""
import asyncio
import json
from abc import ABCMeta
from loguru import logger
from ..core.enumeration import Role, MemoryType
from ..core.op import BaseOp
from ..core.schema import Message, ToolCall, MemoryNode
from ..mem_tool import BaseMemoryTool, ThinkTool
class BaseMemoryAgent(BaseOp, metaclass=ABCMeta):
"""Base class for memory agents that perform reasoning and acting with memory tools."""
memory_type: MemoryType | None = None
def __init__(
self,
tools: list[BaseMemoryTool],
add_think_tool: bool = False, # only for instruct model
tool_call_interval: float = 0,
max_steps: int = 8,
**kwargs,
):
tools = tools or []
if add_think_tool:
tools.append(ThinkTool())
kwargs["sub_ops"] = tools
super().__init__(**kwargs)
self.sub_ops: list[BaseMemoryTool] = [t for t in self.sub_ops if isinstance(t, BaseMemoryTool)]
self.tool_call_interval: float = tool_call_interval
self.max_steps: int = max_steps
self.messages: list[Message] = []
self.tool_messages: list[Message] = []
self.success: bool = True
self.retrieved_nodes: list[MemoryNode] = []
self.memory_nodes: list[MemoryNode | str] = []
self.meta_info: str = ""
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "query",
},
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": [],
},
},
)
@property
def tools(self) -> list[BaseMemoryTool]:
"""Returns the list of memory tools available to this agent."""
return self.sub_ops
@tools.setter
def tools(self, tools: list[BaseMemoryTool]):
self.sub_ops = tools
def get_messages(self) -> list[Message] | str:
"""Extracts and returns messages from the context query or messages."""
if self.context.get("query"):
messages = [Message(role=Role.USER, content=self.context.query)]
elif self.context.get("messages"):
messages = [Message(**m) if isinstance(m, dict) else m for m in self.context.messages]
else:
raise ValueError("input must have either `query` or `messages`")
return messages
async def build_messages(self) -> list[Message]:
"""Builds and returns the initial messages for the agent."""
return self.get_messages()
async def _reasoning_step(self, messages: list[Message], step: int, stage: str = "", **kwargs) -> tuple[Message, bool]:
assistant_message: Message = await self.llm.chat(
messages=messages,
tools=[t.tool_call for t in self.tools],
**kwargs,
)
messages.append(assistant_message)
stage_prefix = f"-{stage}" if stage else ""
logger.info(
f"[{self.__class__.__name__}{stage_prefix}] "
f"step{step + 1}.assistant={assistant_message.simple_dump(enable_json_dump=True)}",
)
should_act = bool(assistant_message.tool_calls)
return assistant_message, should_act
async def _acting_step(self, assistant_message: Message, step: int, stage: str = "", **kwargs) -> list[Message]:
if not assistant_message.tool_calls:
return []
tool_list: list[BaseMemoryTool] = []
tool_result_messages: list[Message] = []
tool_dict = {t.tool_call.name: t for t in self.tools}
stage_prefix = f"-{stage}" if stage else ""
for j, tool_call in enumerate(assistant_message.tool_calls):
if tool_call.name not in tool_dict:
logger.warning(f"[{self.__class__.__name__}{stage_prefix}] unknown tool_call.name={tool_call.name}")
continue
logger.info(
f"[{self.__class__.__name__}{stage_prefix}] step{step + 1}.{j} "
f"submit tool_calls={tool_call.name} argument={tool_call.arguments}",
)
tool_copy: BaseMemoryTool = tool_dict[tool_call.name].copy()
tool_copy.tool_call.id = tool_call.id
tool_list.append(tool_copy)
kwargs.update(tool_call.argument_dict)
self.submit_async_task(tool_copy.call, retrieved_nodes=self.retrieved_nodes, **kwargs)
if self.tool_call_interval > 0:
await asyncio.sleep(self.tool_call_interval)
await self.join_async_tasks()
for j, op in enumerate(tool_list):
if op.memory_nodes:
self.memory_nodes.extend(op.memory_nodes)
if hasattr(op, "messages") and op.messages:
self.tool_messages.extend(op.messages)
tool_result = str(op.output)
tool_message = Message(
role=Role.TOOL,
content=tool_result,
tool_call_id=op.tool_call.id,
)
tool_result_messages.append(tool_message)
# # Collect tool call information to meta_info
# tool_info = f"\n## Tool Call {step + 1}.{j + 1}: {op.tool_call.name}\n"
# tool_info += f"Arguments: {json.dumps(assistant_message.tool_calls[j].argument_dict, ensure_ascii=False)}\n"
# tool_info += f"Result: {tool_result}\n"
self.meta_info += tool_result + "\n"
logger.info(f"[{self.__class__.__name__}{stage_prefix}] step{step + 1}.{j} join tool_result={tool_result[:2000]}...\n\n")
return tool_result_messages
async def react(self, messages: list[Message], stage: str = ""):
"""Performs reasoning and acting steps until completion or max steps reached."""
success: bool = False
for step in range(self.max_steps):
assistant_message, should_act = await self._reasoning_step(messages, step, stage=stage)
if not should_act:
success = True
break
tool_result_messages = await self._acting_step(assistant_message, step, stage=stage)
messages.extend(tool_result_messages)
return messages, success
async def execute(self):
for i, tool in enumerate(self.tools):
logger.info(
f"[{self.__class__.__name__}] step0.{i} "
f"tool_call={json.dumps(tool.tool_call.simple_input_dump(), ensure_ascii=False)}",
)
messages = await self.build_messages()
for i, message in enumerate(messages):
logger.info(
f"[{self.__class__.__name__}] step0.{i} {message.role} {message.name or ''} "
f"{message.simple_dump(enable_json_dump=True)}",
)
self.messages, self.success = await self.react(messages)
if self.success and self.messages:
self.output = self.messages[-1].content
else:
self.output = "No relevant memories found."
@property
def memory_target(self) -> str:
"""Returns the target memory identifier from context."""
return self.context.get("memory_target", "")
@property
def description(self) -> str:
"""Returns the description of the messages."""
return self.context.get("description", "")
@property
def ref_memory_id(self) -> str:
"""Returns the reference memory ID from context."""
return self.context.get("ref_memory_id", "")
@property
def author(self) -> str:
"""Returns the LLM model name as the author identifier."""
return self.llm.model_name
@property
def history_node(self):
"""Returns the history node."""
return self.context.get("history_node", None)

View file

@ -1,7 +0,0 @@
"""memory retriever"""
from .reme_retriever import ReMeRetriever
__all__ = [
"ReMeRetriever",
]

View file

@ -1,72 +0,0 @@
"""ReMe retriever that builds messages with meta memories."""
from typing import List
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role
from ...core.schema import Message
from ...core.utils import get_now_time, format_messages
@C.register_op()
class ReMeRetriever(BaseMemoryAgent):
"""Memory agent that retrieves and builds messages with meta memory context."""
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
# super().__init__(prompt_name="", **kwargs)
super().__init__(prompt_name="reme_retriever2", **kwargs)
self.meta_memories: list[dict] = meta_memories or []
async def _read_meta_memories(self) -> str:
"""Fetch all meta-memory entries that define specialized memory agents."""
from ...mem_tool import ReadMetaMemory
op = ReadMetaMemory(enable_identity_memory=False)
if self.meta_memories:
return op.format_memory_metadata(self.meta_memories)
else:
await op.call()
return str(op.output)
# async def build_messages1(self) -> List[Message]:
# """Build messages with system prompt and user message."""
# meta_memory_info = await self._read_meta_memories()
# system_prompt = self.prompt_format(
# prompt_name="system_prompt",
# now_time=get_now_time(),
# meta_memory_info=meta_memory_info,
# )
# messages = [Message(role=Role.SYSTEM, content=system_prompt)]
# if self.context.get("query"):
# messages.append(Message(role=Role.USER, content=self.context.query))
# elif self.context.get("messages"):
# messages.extend([Message(**m) for m in self.context.messages])
# else:
# raise ValueError("input must have either `query` or `messages`")
# return messages
async def build_messages(self) -> List[Message]:
"""Build messages with system prompt and user message."""
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
now_time=get_now_time(),
meta_memory_info=await self._read_meta_memories(),
context=context,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages

View file

@ -1,47 +0,0 @@
tool: |
Retrieve relevant memories to assist in answering questions.
Use this tool when you need to search for historical information, user preferences,
procedural knowledge, or any other stored memories that may help answer the current query.
The agent will analyze the context, determine what information is needed, and perform
semantic searches across different memory types to find the most relevant memories.
system_prompt: |
You are a memory agent. Please analyze the context, retrieve relevant memories when needed, and directly answer the user's question based on the retrieved information.
**CRITICAL**: You must ONLY answer based on the retrieved memories. DO NOT fabricate, infer, or add any information that is not explicitly present in the retrieved memories. If the retrieved memories do not contain enough information to answer the question, you must acknowledge this limitation.
## Current Time
{now_time}
## Available Meta Memories
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Tasks
1. **Analyze** the context to determine whether retrieval is necessary:
- If the question can be directly answered using the existing context, output `<NO_RETRIEVAL_NEEDED>` and stop.
- If additional information is required, proceed to retrieval.
- Consider which types of meta memory from the "Available Meta Memories" list are most relevant.
2. **Retrieve** relevant memories using `vector_retrieve_memory`:
- Select the appropriate `memory_type` and `memory_target` from the "Available Meta Memories" list.
- Clearly define the needed information and construct suitable queries.
- Design queries flexibly based on actual needs:
* Generate different queries for different `memory_type`/`memory_target` combinations.
* For the same combination, create multiple queries using different phrasings or perspectives.
* Choose the optimal combination strategy based on the retrieval scenario.
- **Important**: When retrieving tool-related memories (`memory_type` is "tool"), the query must use the tools exact name (not a description or paraphrase of the problem).
- If retrieval results include a `ref_memory_id` and more details are needed—or if vector retrieval proves insufficient—use `read_history_memory` with the `ref_memory_id` as the `memory_id` parameter.
- **Important**: When using `read_history_memory` with multiple `ref_memory_ids`, ensure all IDs are unique and do not provide duplicate IDs.
3. **Iterate if necessary**:
- If the initial retrieval fails, try alternative phrasings or perspectives.
- If multiple memory types exist, attempt retrievals across different types.
- Before concluding that no relevant memory exists, perform at least 23 retrieval attempts using varied phrasings or viewpoints.
- If repeated vector retrievals still fail to yield sufficient information, use `read_history_memory` to fetch the original message content.
4. **Output** the result:
- If no retrieval is needed, output `<NO_RETRIEVAL_NEEDED>`.
- If relevant memories are found and you can answer the user's question, provide a concise, direct answer **strictly based on the retrieved memories only**. DO NOT add any information, inference, or speculation beyond what is explicitly stated in the retrieved memories.
- If after multiple retrieval attempts from various angles you still cannot find relevant information, output `<NO_RELEVANT_MEMORY>`.

View file

@ -1,49 +0,0 @@
tool: |
Retrieve relevant memories from the memory bank to assist in answering questions.
Use this tool when you need to search for historical information, user preferences,
procedural knowledge, or any other stored memories that may help answer the current query.
The agent will analyze the context, determine what information is needed, and perform
semantic searches across different memory types to find the most relevant memories.
system_prompt: |
You are a memory retrieval agent. Please analyze the context, retrieve relevant information from the memory bank when needed, and return a summary of the retrieved memories to assist in answering the user's question.
## Context
{context}
## Current Time
{now_time}
## Available Meta-Memories
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Tasks
1. **Analyze** the conversation context to determine whether retrieval is necessary:
- If the question can be answered directly from the existing context, output `<NO_RETRIEVAL_NEEDED>` and stop.
- If additional information is required, proceed with retrieval.
- Consider which types of meta-memories from the "Available Meta-Memories" list are most relevant.
2. **Retrieve** relevant memories using `vector_retrieve_memory`:
- Select `memory_type` and `memory_target` from the "Available Meta-Memories" list.
- Clearly identify the needed information and construct appropriate queries.
- Design queries flexibly based on actual needs:
* Generate different queries for different `memory_type`/`memory_target` combinations.
* For the same combination, generate multiple queries with varied phrasings or angles if needed.
* Use the combination strategy that best fits the retrieval scenario.
- **Important**: When retrieving tool memories (`memory_type` is "tool"), use the actual tool name as the query (not a description or question).
- If retrieval results include a `ref_memory_id` and more detail is needed—or if vector retrieval proves insufficient—use `read_history_memory` with the `ref_memory_id` as the `memory_id` parameter.
3. **Iterate if necessary**:
- If the initial retrieval yields no matches, try alternative phrasings or perspectives.
- If multiple memory types exist, attempt retrieval across different types.
- Before concluding that no relevant memory exists, perform at least 23 additional retrieval attempts using varied phrasings or angles.
- If repeated vector retrievals still fail to provide adequate information, use `read_history_memory` to fetch the original message content.
4. **Output** the result:
- If no retrieval is needed, output `<NO_RETRIEVAL_NEEDED>`.
- If relevant memories are found, clearly summarize the retrieved information.
- If multiple attempts still yield no relevant memories, output `<NO_RELEVANT_MEMORY>`.
user_message: |
Please analyze the context, retrieve relevant information from the memory bank when needed, and return a summary of the retrieved memories to assist in answering the user's question.

View file

@ -1,5 +0,0 @@
from .reme_retriever_v2 import ReMeRetrieverV2
__all__ = [
"ReMeRetrieverV2",
]

View file

@ -1,67 +0,0 @@
"""ReMe retriever v2 that autonomously retrieves memories from multiple angles."""
from typing import List
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role
from ...core.schema import Message
from ...core.utils import format_messages
@C.register_op()
class ReMeRetrieverV2(BaseMemoryAgent):
"""Memory agent that autonomously retrieves memories from multiple angles.
This retriever:
- Directly queries memories based on user questions without time constraints
- Tries multiple retrieval strategies: direct vector search, metadata filtering, partial filtering
- Attempts at least 3 vector retrievals from different perspectives
- Falls back to read_history if vector retrieval doesn't find sufficient information
"""
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
# Check if ReadHistory tool is available in the tools list
tools = kwargs.get('tools', [])
has_read_history = any(tool.__class__.__name__ == 'ReadHistory' for tool in tools)
# Use simple prompt if ReadHistory is not available
if not has_read_history:
super().__init__(prompt_name="reme_retriever_v2_simple", **kwargs)
else:
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
async def _read_meta_memories(self) -> str:
"""Fetch all meta-memory entries that define specialized memory agents."""
from ...mem_tool import ReadMetaMemory
op = ReadMetaMemory(enable_identity_memory=False)
if self.meta_memories:
return op.format_memory_metadata(self.meta_memories)
else:
await op.call()
return str(op.output)
async def build_messages(self) -> List[Message]:
"""Build messages with system prompt and user message."""
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self._read_meta_memories(),
context=context,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages

View file

@ -1,125 +0,0 @@
tool: |
Autonomously retrieve relevant memories from multiple angles to answer user questions.
This retriever will:
- Try multiple vector search strategies (direct, metadata-filtered, partial)
- Attempt at least 3 different retrieval approaches before giving up
- Fall back to reading original conversation history if vector search is insufficient
- Clearly state "I don't know" if information cannot be found after exhaustive searching
- NEVER hallucinate or fabricate information not present in retrieved memories
Use this when you need comprehensive memory retrieval with persistent searching.
system_prompt: |
You are an autonomous memory retrieval agent. Your task is to persistently search for relevant memories from multiple angles to answer the user's question.
## Available Meta Memories
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## User Context
{context}
## Your Retrieval Strategy
You MUST use the `retrieve_memories` tool to search for relevant information. This is a MANDATORY step - do not skip it.
1. **Multi-Angle Vector Retrieval** (REQUIRED - at least 3 attempts):
You must try AT LEAST 3 different retrieval approaches using `retrieve_memories`:
a) **Direct Vector Search**: Use the user's question directly or with minimal reformulation
- Query the most relevant memory_type and memory_target
- Use straightforward query phrasing
b) **Alternative Phrasing**: Reformulate the query from a different angle
- Use synonyms or different expressions
- Break down complex questions into simpler components
- Try more specific or more general queries
c) **Metadata-Filtered Search**: Add metadata filters to narrow down results
- **Time-based filtering**: Use year/month/day metadata fields to filter by time periods
* Example: {{"year": 2024}} for memories from 2024
* Example: {{"year": 2024, "month": 5}} for memories from May 2024
* Example: {{"year": 2024, "month": 5, "day": 15}} for memories from a specific date
- Combine vector search with metadata constraints
- Try partial metadata filtering if full filtering yields nothing (e.g., only year, or year+month)
d) **Cross-Memory-Type Search**: If applicable, search across different memory types
- Try different memory_type and memory_target combinations
- Some information might be stored in unexpected memory categories
e) **Keyword Extraction**: Extract key entities/concepts and search for them
- Identify important names, places, concepts
- Search for each key element separately
2. **Evaluate Retrieval Results** (After each attempt):
- Review what memories were returned
- Assess if they contain sufficient information to answer the question
- If insufficient, identify what's missing and adjust your next query accordingly
- Track which retrieval strategies you've already tried
3. **Persist Through Failures**:
- DO NOT give up after 1-2 failed attempts
- If a retrieval returns no results or irrelevant results, try a different approach
- Consider that the information might be phrased differently than expected
- Be creative with query reformulation
4. **Fallback to History Reading** (Only after 3+ vector retrieval attempts):
- If after at least 3 different vector retrieval attempts you still lack sufficient information:
* If any retrieved memories contain `ref_memory_id`, use `read_history` to read the original conversation
* Use `read_history` with the `ref_memory_id` to get complete context
* This can reveal details that weren't captured in the memory summaries
5. **Answer the Question**:
- Once you have sufficient information, provide a direct answer based ONLY on retrieved memories
- DO NOT fabricate, guess, or infer information not present in the memories
- **CRITICAL**: If after 3+ retrieval attempts you still cannot find relevant information:
* Simply state: "I don't know. After searching from multiple angles, I could not find relevant information to answer this question."
* DO NOT make up answers or hallucinate information
* DO NOT provide speculative or guessed responses
* It is better to say "I don't know" than to provide incorrect information
## Important Guidelines
- **Be Persistent**: Always try at least 3 different retrieval strategies before concluding no information exists
- **Be Creative**: If one query approach fails, think of alternative ways to phrase or decompose the question
- **Use Tools**: You MUST use `retrieve_memories` for vector search. Use `read_history` if you have `ref_memory_id` and need more details
- **No Hallucination**: NEVER fabricate, guess, or hallucinate information. Only answer based on what you actually retrieved from memories
- **Admit When You Don't Know**: If after 3+ attempts you cannot find relevant information, clearly say "I don't know" rather than making up an answer
- **Track Your Attempts**: Keep count of how many different retrieval strategies you've tried
- **Metadata Awareness**: Utilize metadata filters when they might help narrow down results
* Memories store time information in metadata as year/month/day fields
* Use time-based filters when the question involves specific time periods or dates
* Try progressive filtering: start with year, then add month, then day if needed
## Example Retrieval Flow
**Example 1: Simple Query**
Attempt 1: Direct query "user's favorite food"
→ Result: No relevant memories found
Attempt 2: Reformulated query "what does user like to eat"
→ Result: Some memories about meals, but not specific preferences
Attempt 3: Keyword search "food preferences" with metadata filter
→ Result: Found relevant memory with ref_memory_id
Attempt 4: Use read_history with ref_memory_id to get full context
→ Result: Found detailed conversation about favorite foods
Answer: [Provide answer based on retrieved information]
**Example 2: Time-based Query**
Question: "What did the user do last summer?"
Attempt 1: Direct query "user activities summer" with metadata {{"year": 2025, "month": [6, 7, 8]}}
→ Result: Found some vacation memories
Attempt 2: Broader query "user summer vacation travel" with metadata {{"year": 2025}}
→ Result: Found additional travel-related memories
Attempt 3: Use read_history for memories with ref_memory_id to get detailed context
→ Result: Complete picture of summer activities
Answer: [Provide answer based on retrieved information]
user_message: |
Please retrieve relevant memories and answer the question. Remember to try multiple retrieval approaches before giving up.

View file

@ -1,115 +0,0 @@
tool: |
Autonomously retrieve relevant memories from multiple angles to answer user questions.
This retriever will:
- Try multiple vector search strategies (direct, metadata-filtered, partial)
- Attempt at least 3 different retrieval approaches before giving up
- Clearly state "I don't know" if information cannot be found after exhaustive searching
- NEVER hallucinate or fabricate information not present in retrieved memories
Use this when you need comprehensive memory retrieval with persistent searching.
system_prompt: |
You are an autonomous memory retrieval agent. Your task is to persistently search for relevant memories from multiple angles to answer the user's question.
## Available Meta Memories
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## User Context
{context}
## Your Retrieval Strategy
You MUST use the `retrieve_memories` tool to search for relevant information. This is a MANDATORY step - do not skip it.
1. **Multi-Angle Vector Retrieval** (REQUIRED - at least 3 attempts):
You must try AT LEAST 3 different retrieval approaches using `retrieve_memories`:
a) **Direct Vector Search**: Use the user's question directly or with minimal reformulation
- Query the most relevant memory_type and memory_target
- Use straightforward query phrasing
b) **Alternative Phrasing**: Reformulate the query from a different angle
- Use synonyms or different expressions
- Break down complex questions into simpler components
- Try more specific or more general queries
c) **Metadata-Filtered Search**: Add metadata filters to narrow down results
- **Time-based filtering**: Use year/month/day metadata fields to filter by time periods
* Example: {{"year": 2024}} for memories from 2024
* Example: {{"year": 2024, "month": 5}} for memories from May 2024
* Example: {{"year": 2024, "month": 5, "day": 15}} for memories from a specific date
- Combine vector search with metadata constraints
- Try partial metadata filtering if full filtering yields nothing (e.g., only year, or year+month)
d) **Cross-Memory-Type Search**: If applicable, search across different memory types
- Try different memory_type and memory_target combinations
- Some information might be stored in unexpected memory categories
e) **Keyword Extraction**: Extract key entities/concepts and search for them
- Identify important names, places, concepts
- Search for each key element separately
2. **Evaluate Retrieval Results** (After each attempt):
- Review what memories were returned
- Assess if they contain sufficient information to answer the question
- If insufficient, identify what's missing and adjust your next query accordingly
- Track which retrieval strategies you've already tried
3. **Persist Through Failures**:
- DO NOT give up after 1-2 failed attempts
- If a retrieval returns no results or irrelevant results, try a different approach
- Consider that the information might be phrased differently than expected
- Be creative with query reformulation
4. **Answer the Question**:
- Once you have sufficient information, provide a direct answer based ONLY on retrieved memories
- DO NOT fabricate, guess, or infer information not present in the memories
- **CRITICAL**: If after 3+ retrieval attempts you still cannot find relevant information:
* Simply state: "I don't know. After searching from multiple angles, I could not find relevant information to answer this question."
* DO NOT make up answers or hallucinate information
* DO NOT provide speculative or guessed responses
* It is better to say "I don't know" than to provide incorrect information
## Important Guidelines
- **Be Persistent**: Always try at least 3 different retrieval strategies before concluding no information exists
- **Be Creative**: If one query approach fails, think of alternative ways to phrase or decompose the question
- **Use Tools**: You MUST use `retrieve_memories` for vector search
- **No Hallucination**: NEVER fabricate, guess, or hallucinate information. Only answer based on what you actually retrieved from memories
- **Admit When You Don't Know**: If after 3+ attempts you cannot find relevant information, clearly say "I don't know" rather than making up an answer
- **Track Your Attempts**: Keep count of how many different retrieval strategies you've tried
- **Metadata Awareness**: Utilize metadata filters when they might help narrow down results
* Memories store time information in metadata as year/month/day fields
* Use time-based filters when the question involves specific time periods or dates
* Try progressive filtering: start with year, then add month, then day if needed
## Example Retrieval Flow
**Example 1: Simple Query**
Attempt 1: Direct query "user's favorite food"
→ Result: No relevant memories found
Attempt 2: Reformulated query "what does user like to eat"
→ Result: Some memories about meals, but not specific preferences
Attempt 3: Keyword search "food preferences" with metadata filter
→ Result: Found relevant memory
Answer: [Provide answer based on retrieved information]
**Example 2: Time-based Query**
Question: "What did the user do last summer?"
Attempt 1: Direct query "user activities summer" with metadata {{"year": 2025, "month": [6, 7, 8]}}
→ Result: Found some vacation memories
Attempt 2: Broader query "user summer vacation travel" with metadata {{"year": 2025}}
→ Result: Found additional travel-related memories
Attempt 3: More specific queries about specific activities
→ Result: Complete picture of summer activities
Answer: [Provide answer based on retrieved information]
user_message: |
Please retrieve relevant memories and answer the question. Remember to try multiple retrieval approaches before giving up.

View file

@ -1,15 +0,0 @@
"""memory summarizer"""
from .identity_summarizer import IdentitySummarizer
from .personal_summarizer import PersonalSummarizer
from .procedural_summarizer import ProceduralSummarizer
from .reme_summarizer import ReMeSummarizer
from .tool_summarizer import ToolSummarizer
__all__ = [
"IdentitySummarizer",
"PersonalSummarizer",
"ProceduralSummarizer",
"ReMeSummarizer",
"ToolSummarizer",
]

View file

@ -1,33 +0,0 @@
"""Specialized agent for extracting and updating agent self-cognition memories."""
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message
from ...core.utils import get_now_time, format_messages
@C.register_op()
class IdentitySummarizer(BaseMemoryAgent):
"""Analyzes conversations to extract and update agent's self-perception."""
memory_type: MemoryType = MemoryType.IDENTITY
async def build_messages(self) -> list[Message]:
"""Construct system and user messages with formatted context and timestamp."""
system_prompt = self.prompt_format(
prompt_name="system_prompt",
now_time=get_now_time(),
context=format_messages(self.get_messages()),
memory_type=self.memory_type.value,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with workspace_id and author context."""
return await super()._acting_step(assistant_message, step, author=self.author, **kwargs)

View file

@ -1,28 +0,0 @@
tool: |
Update agent self-cognition based on conversation context.
First read existing self-cognition using `read_identity_memory`, then analyze the context to determine if updates are needed, and use `update_identity_memory` to update when necessary.
system_prompt: |
You are a specialized memory agent in the domain of self-awareness. Your task is to update the main agent's self-perception based on the provided context.
## Context:
{context}
## Current Time:
{now_time}
## Your Responsibilities:
1. **Read the main agent's current self-perception**: Retrieve it using `read_identity_memory`.
2. **Analyze the context** to determine whether an update to self-perception is needed:
- Extract any self-perceptionrelated information from the dialogue (e.g., self-awareness, personality traits, current state, etc.).
- If no relevant self-perception information is found, output `<NO_MEMORY_NEEDED>` and halt further processing.
3. **Update if necessary**: Use `update_identity_memory` to perform the update:
- Compare the extracted information with the existing self-perception.
- If an update is required (due to new information, corrections, or additions), invoke `update_identity_memory`.
- If no update is needed, output `<NO_MEMORY_NEEDED>`.
user_message: |
Please analyze the context and update the main agent's self-perception if necessary.

View file

@ -1,87 +0,0 @@
"""Specialized agent for extracting and managing personal memories about specific individuals."""
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, ToolCall
from ...core.utils import get_now_time, format_messages
@C.register_op()
class PersonalSummarizer(BaseMemoryAgent):
"""Extracts and stores personal information about individuals from conversations."""
def __init__(self, recent_top_k: int = 20, **kwargs):
super().__init__(**kwargs)
self.recent_top_k: int = recent_top_k
memory_type: MemoryType = MemoryType.PERSONAL
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def _retrieve_recent_memories(self) -> str:
"""Retrieve recent memories sorted by time_modified."""
from ...mem_tool import RetrieveRecentMemory
op = RetrieveRecentMemory(top_k=self.recent_top_k)
await op.call(memory_type="personal", memory_target=self.memory_target, retrieved_nodes=self.retrieved_nodes)
return op.output
async def build_messages(self) -> list[Message]:
"""Construct messages with context, memory_target, and memory_type information."""
await self._retrieve_recent_memories()
system_prompt = self.prompt_format(
prompt_name="system_prompt",
now_time=get_now_time(),
recent_memories="\n".join([n.format_memory() for n in self.retrieved_nodes]),
context=self.description + "\n" + format_messages(self.get_messages()),
memory_type=self.memory_type.value,
memory_target=self.memory_target,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with memory_target, memory_type, and author context."""
return await super()._acting_step(
assistant_message,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
ref_memory_id=self.ref_memory_id,
author=self.author,
**kwargs,
)

View file

@ -1,82 +0,0 @@
tool: |
Extract and store personal memories from conversation context.
Use this tool to analyze dialogues and extract important personal information about users,
such as preferences, habits, personal background, relationships, and significant facts.
The agent will determine whether the information is worth remembering, check for duplicates
or conflicts with existing memories, and perform add, update, or delete operations as needed.
system_prompt: |
You are a professional memory agent. Your task is to update the main agent's {memory_type} memory regarding {memory_target} based on the context.
**CRITICAL**: You must extract and store information STRICTLY based on what is explicitly stated in the context. DO NOT infer, assume, fabricate, or add any information that is not directly present in the dialogue. Only extract facts that are clearly and explicitly mentioned.
## Context:
{context}
## Current Time:
{now_time}
## Recent Memories:
{recent_memories}
## Memory Objective:
You are managing **{memory_type}** memories about **{memory_target}** for the main agent. Focus on extracting and storing information directly related to this persons preferences, habits, personal background, and significant facts.
## Your Tasks:
1. **Analyze and Extract** potential memories from the dialogue context:
- Determine whether the conversation contains important, memorable information, including but not limited to: user preferences, habits, or personal details; key facts, decisions, or conclusions; relationships or contextual background related to people or topics.
- If the dialogue is casual chatter or contains no valuable information, output `<NO_MEMORY_NEEDED>` and stop.
- Extract key information using clear and concise phrasing **strictly based on what is explicitly stated in the context**.
- **Important**: DO NOT infer, assume, or add any information beyond what is directly mentioned in the conversation.
- Each memory entry must be self-contained and understandable without additional context.
- Avoid storing trivial or temporary information.
- **CRITICAL**: After extraction, immediately deduplicate within the extracted memories themselves - if multiple extracted items convey the same core information (even with slightly different wording), keep ONLY the most complete and accurate one.
- Before proceeding, list all deduplicated extracted memories in your response.
2. **Retrieve similar historical memories** using `vector_retrieve_memory`:
- For EACH extracted memory, perform a semantic similarity search to find existing, potentially relevant memories (e.g., for "Person A was born on date X", search for "Person A birth date age").
- Retrieve all related memories for thorough comparison to prevent any duplication.
3. **Compare and Decide** on memory operations with STRICT deduplication:
- Compare the newly extracted memories with **both Recent Memories and historical memories** retrieved in the previous step.
- **CRITICAL DEDUPLICATION CHECK**: Before adding ANY new memory:
- Check if the SAME INFORMATION already exists in Recent Memories or retrieved historical memories
- Consider memories as duplicates even if wording differs, as long as they convey the SAME core fact
- Examples of duplicate information:
* "Person A was born on date X. He/She is N years old." vs "Person A is a gender born on date X. He/She is currently N years old." → DUPLICATES
* "Lives in city" vs "Person A lives in city" → DUPLICATES
* "Holds a Bachelor's degree in field" vs "Person A holds a Bachelor's degree in field" → DUPLICATES
- **Use `update_memory` and `delete_memory` to actively deduplicate and resolve conflicts:**
- If multiple existing memories contain duplicate or overlapping information: use `delete_memory` to remove redundant ones, then use `update_memory` to consolidate all information into a single, comprehensive memory.
- If memories conflict (contradictory information): use `delete_memory` to remove outdated/incorrect ones, then use `update_memory` or `add_memory` to store the correct version.
- Choose the appropriate operation based on the situation:
- **If the information already exists in Recent Memories or historical memories and is consistent: SKIP—no action needed. Do NOT add duplicate memories.**
- If existing memory (recent or historical) needs supplementation with NEW details: use `update_memory` to enhance and consolidate it.
- If existing memory (recent or historical) is outdated or contradicted by new information: use `delete_memory` to remove it, then `add_memory` for the corrected version.
- If multiple memories contain similar/overlapping information: use `delete_memory` to remove duplicates, then `update_memory` to merge into one.
- If the information is entirely new and not present in either Recent Memories or historical memories: use `add_memory` to add it to the memory store.
- **When in doubt, prefer updating or consolidating existing memories over adding new ones to avoid redundancy.**
4. **Output** the result:
- If no memory operation is required, output `<NO_MEMORY_NEEDED>`.
- If memories were added, updated, or deleted, summarize the operations performed.
## Guidelines:
- Be selective: store only truly important information.
- Stay concise: each memory should be clear and atomic.
- **Be strictly accurate**: ensure extracted content faithfully reflects ONLY what is explicitly stated in the original context. DO NOT infer, extrapolate, or fabricate any details.
- **AVOID REDUNDANCY AT ALL COSTS**: This is your TOP PRIORITY. Always perform thorough deduplication:
* First, deduplicate within newly extracted memories
* Then, check against Recent Memories (provided above)
* Finally, use `vector_retrieve_memory` to check against historical memories
* **Actively use `delete_memory` to remove duplicate or conflicting memories**
* **Use `update_memory` to consolidate and integrate information from multiple memories into one**
* If information semantically matches existing memories, DO NOT add it again
* When uncertain, prefer to skip or update existing memories rather than create duplicates
- Include relevant metadata (e.g., timestamps) when appropriate.
- **No assumptions**: Only store information that is directly and clearly stated in the conversation.
- **Quality over quantity**: It's better to have fewer, well-maintained memories than many duplicate ones.
user_message: |
Please analyze the context to determine whether important information should be extracted and stored as memory, and perform memory addition, deletion, or update operations when necessary.

View file

@ -1,42 +0,0 @@
"""Specialized agent for extracting and managing procedural knowledge and workflows."""
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message
from ...core.utils import get_now_time, format_messages
@C.register_op()
class ProceduralSummarizer(BaseMemoryAgent):
"""Extracts step-by-step procedures, best practices, and task-completion strategies."""
memory_type: MemoryType = MemoryType.PROCEDURAL
async def build_messages(self) -> list[Message]:
"""Construct messages with context, memory_target, and memory_type information."""
system_prompt = self.prompt_format(
prompt_name="system_prompt",
now_time=get_now_time(),
context=format_messages(self.get_messages()),
memory_type=self.memory_type.value,
memory_target=self.memory_target,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with ref_memory_id, memory_target, memory_type, and author context."""
return await super()._acting_step(
assistant_message,
step,
ref_memory_id=self.ref_memory_id,
memory_target=self.memory_target,
memory_type=self.memory_type.value,
author=self.author,
**kwargs,
)

View file

@ -1,70 +0,0 @@
tool: |
Extract and store procedural memories from conversation context.
Use this tool to analyze dialogues and extract important procedural knowledge,
such as step-by-step workflows, how-to guides, best practices, problem-solving methods,
debugging techniques, and task completion strategies.
The agent will also reflect on task outcomes - extracting lessons from failures
and successful strategies from successes to improve future performance.
system_prompt: |
You are a professional memory Agent specializing. Your task is to update the main agent's {memory_type} memory regarding {memory_target} based on the context.
## Context:
{context}
## Current Time:
{now_time}
## Memory Objective:
You are managing **{memory_type}** memories about **{memory_target}** for the main Agent. Focus on extracting and storing procedural knowledge, such as:
- Step-by-step procedures and workflows
- Operational guides and instructions
- Best practices and methodologies
- Established routines and processes
- Problem-solving techniques and troubleshooting tips
- Task-completion strategies
## Your Tasks:
1. **Analyze and Extract** potential memories from the conversation context:
- Determine whether the dialogue contains procedural knowledge worth remembering, including but not limited to:
- Multi-step procedures or workflows
- Instructions for completing specific tasks
- Best practices or recommended approaches
- Problem-solving methods or debugging tips
- Configuration or setup processes
- If the context includes task outcome information:
- **Successful tasks**: Extract and reflect on successful experiences; summarize key success factors, effective methods, and reusable strategies.
- **Failed tasks**: Extract and reflect on lessons learned; analyze root causes of failure, pitfalls to avoid, and improvement suggestions.
- **Both success and failure**: Conduct comparative reflection; identify critical differences and distill key decision factors and best practices.
- If the conversation is casual chat or contains no valuable information, output `<NO_MEMORY_NEEDED>` and stop.
- Express extracted information clearly and concisely.
- Each memory entry should be self-contained and understandable without additional context.
- Avoid storing trivial or transient information.
- Before proceeding, list all extracted memories in your response.
2. **Retrieve similar historical memories** using `vector_retrieve_memory`:
- Perform a semantic similarity search based on the extracted memories.
- Retrieve potentially relevant existing memories for comparison to check for duplicates or associations.
3. **Compare and Decide** on memory operations:
- Compare extracted memories against historical ones to ensure no duplicates or conflicts exist in the final memory repository.
- Choose the appropriate operation based on the situation:
- If the information already exists and is consistent: skip (no action needed).
- If existing memory needs supplementation or correction: use `update_memory` to revise it.
- If existing memory is outdated or incorrect: use `delete_memory` to remove it.
- If the information is entirely new: use `add_memory` to add it to the memory repository.
4. **Output** the result:
- If no memory operation is needed, output `<NO_MEMORY_NEEDED>`.
- If memories were added, updated, or deleted, summarize the performed operations.
## Guidelines:
- **Be selective**: Store only truly important information.
- **Stay concise**: Each memory should be clear and atomic.
- **Be precise and accurate**: Ensure extracted content faithfully reflects the original context.
- **Avoid redundancy**: Always check for similar existing memories before adding new ones.
- **Include relevant metadata when appropriate** (e.g., timestamp, preconditions, expected outcomes).
user_message: |
Please analyze the context to determine whether important procedural knowledge should be extracted and stored as memory, and perform memory addition, deletion, or update operations when necessary.

View file

@ -1,145 +0,0 @@
"""Orchestrator for complete memory summarization workflow across all memory types."""
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, MemoryNode, ToolCall
from ...core.utils import get_now_time, format_messages
@C.register_op()
class ReMeSummarizer(BaseMemoryAgent):
"""Coordinates memory updates by delegating to specialized memory agents."""
def __init__(self, meta_memories: list[dict] | None = None, enable_identity_memory: bool = False, **kwargs):
"""Initialize with flags to enable/disable identity memory processing."""
super().__init__(**kwargs)
self.enable_identity_memory = enable_identity_memory
self.meta_memories: list[dict] = meta_memories or []
# Check if AddMetaMemory is in tools
self.enable_add_meta_memory = self._check_add_meta_memory_in_tools()
def _check_add_meta_memory_in_tools(self) -> bool:
"""Check if AddMetaMemory tool is present in the tools list."""
from ...mem_tool import AddMetaMemory
for tool in self.tools:
if isinstance(tool, AddMetaMemory):
return True
return False
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.prompt_format("tool", enable_add_meta_memory=self.enable_add_meta_memory),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def _read_identity_memory(self) -> str:
"""Retrieve agent's self-perception memory."""
if self.enable_identity_memory:
from ...mem_tool import ReadIdentityMemory
op = ReadIdentityMemory()
await op.call()
return op.output
else:
return ""
async def _read_meta_memories(self) -> str:
"""Fetch all meta-memory entries that define specialized memory agents."""
from ...mem_tool import ReadMetaMemory
op = ReadMetaMemory(enable_identity_memory=self.enable_identity_memory)
if self.meta_memories:
return op.format_memory_metadata(self.meta_memories)
else:
await op.call()
return str(op.output)
async def build_messages(self) -> list[Message]:
"""Construct initial messages with context, identity, and meta-memory information."""
messages = [Message(**m) if isinstance(m, dict) else m for m in self.context.messages]
self.context["messages_formated"] = self.description + "\n" + format_messages(messages)
self.context["ref_memory_id"] = MemoryNode(
memory_type=MemoryType.HISTORY,
content=self.context["messages_formated"],
).memory_id
now_time = get_now_time()
identity_memory = await self._read_identity_memory()
meta_memory_info = await self._read_meta_memories()
logger.info(f"now_time={now_time} identity_memory={identity_memory} meta_memory_info={meta_memory_info}")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
now_time=now_time,
identity_memory=identity_memory,
meta_memory_info=meta_memory_info,
context=self.context["messages_formated"],
enable_add_meta_memory=self.enable_add_meta_memory,
)
user_message = self.get_prompt("user_message")
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=user_message),
]
return messages
async def _reasoning_step(self, messages: list[Message], step: int, **kwargs) -> tuple[Message, bool]:
"""Refresh meta-memory info in system prompt before each reasoning step."""
system_messages = [message for message in messages if message.role is Role.SYSTEM]
if system_messages:
system_message = system_messages[0]
system_message.content = self.prompt_format(
prompt_name="system_prompt",
now_time=get_now_time(),
identity_memory=await self._read_identity_memory(),
meta_memory_info=await self._read_meta_memories(),
context=self.context["messages_formated"],
enable_add_meta_memory=self.enable_add_meta_memory,
)
return await super()._reasoning_step(messages, step, **kwargs)
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with ref_memory_id and author context."""
return await super()._acting_step(
assistant_message,
step,
messages=self.context.get("messages", []),
description=self.context.get("description"),
ref_memory_id=self.context["ref_memory_id"],
messages_formated=self.context["messages_formated"],
author=self.author,
**kwargs,
)

View file

@ -1,53 +0,0 @@
tool: |
Orchestrate the complete memory summarization workflow for the agent.
This tool receives conversation context and performs necessary memory updates including:
[enable_add_meta_memory]- Creating new meta-memory entries if needed
- Adding summary memory for quick future recall
- Delegating to specialized memory agents for detailed memory extraction and update
system_prompt: |
You are a Memory Agent responsible for performing necessary updates and summaries of the main Agent's memories based on the **context**.
# Context
{context}
## Current Time
{now_time}
## Main Agent's Self-Perception
{identity_memory}
## Main Agent's Meta Memory
Each line of meta memory indicates the existence of a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension (memory_type + memory_target).
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Tasks
### 1. Add Summary Memory
- Use `add_summary_memory` to store a concise summary.
- The summary should capture key points, decisions, or important facts to aid later recollection of the original conversation.
[enable_add_meta_memory]### 2. Create New Meta Memory (if needed)
[enable_add_meta_memory]When the context contains significant new valuable information, first check if the Main Agent's Meta Memory already contains a corresponding `<memory_type>(<memory_target>)` entry:
[enable_add_meta_memory]- If the required `<memory_type>(<memory_target>)` does NOT exist in the Meta Memory, use `add_meta_memory` to create a new meta memory entry.
[enable_add_meta_memory]- For personal memories: specify `memory_type="personal"` and `memory_target=<person's name>`.
[enable_add_meta_memory]- For procedural memories: specify `memory_type="procedural"` and `memory_target=<topic or domain>`.
[enable_add_meta_memory]- Each meta memory entry will instantiate a dedicated specialized Memory Agent for that dimension.
[enable_add_meta_memory]- Only create new meta memory entries when necessary; avoid duplicating existing ones.
[enable_add_meta_memory]
### 3. Delegate to Specialized Memory Agents
You do not need to summarize or update memories yourself. Instead, analyze the context, identify which memory dimensions (memory_type + memory_target) from the existing meta memory require updates, and delegate using `hands_off`:
- The parameters of `hands_off` (`memory_type` and `memory_target`) must exactly match an existing entry in the "Main Agent's Meta Memory" listed above.
- You may delegate concurrently to multiple specialized agents to enable parallel memory processing.
- Each specialized agent will perform detailed memory extraction, addition, updating, or deletion within its assigned dimension.
## Output Requirements
- If the context contains no memorable information (e.g., simple greetings), output `<NO_MEMORY_NEEDED>`.
- If any memory operations were performed, briefly summarize what was done.
user_message: |
Please perform your task based on the context.

View file

@ -1,42 +0,0 @@
"""Specialized agent for extracting and managing tool usage guidelines and best practices."""
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message
from ...core.utils import get_now_time, format_messages
@C.register_op()
class ToolSummarizer(BaseMemoryAgent):
"""Analyzes tool executions to extract effective usage patterns and optimization tips."""
memory_type: MemoryType = MemoryType.TOOL
async def build_messages(self) -> list[Message]:
"""Construct messages with context, memory_target, and memory_type information."""
system_prompt = self.prompt_format(
prompt_name="system_prompt",
now_time=get_now_time(),
context=format_messages(self.get_messages()),
memory_type=self.memory_type.value,
memory_target=self.memory_target,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with ref_memory_id, memory_target, memory_type, and author context."""
return await super()._acting_step(
assistant_message,
step,
ref_memory_id=self.ref_memory_id,
memory_target=self.memory_target,
memory_type=self.memory_type.value,
author=self.author,
**kwargs,
)

View file

@ -1,55 +0,0 @@
tool: |
Extract and store tool usage guidelines from tool call execution context.
Use this tool to analyze tool calls and their results, extracting valuable insights
about how to use tools more effectively, including best practices, common patterns,
error handling strategies, and optimization tips.
The agent will determine whether the information is worth remembering, check for duplicates
or conflicts with existing tool guidelines, and perform add, update, or delete operations as needed.
system_prompt: |
You are a professional memory Agent specializing in the domain of **{memory_target}**. Please analyze the tool execution context, and your task is to update the main Agent's **{memory_type}** memory regarding **{memory_target}** based on this context.
## Context:
{context}
## Current Time:
{now_time}
## Memory Target:
You are managing the main Agents **{memory_type}** memory about **{memory_target}**. Focus on extracting and storing guidelines, best practices, and insights on how to effectively use this tool.
## Your Tasks:
1. **Analyze and Extract** tool usage guidelines from the execution context:
- Determine whether the tool invocation and its results contain valuable insights worth remembering, including but not limited to: successful usage patterns and best practices; common errors and how to avoid them; effective parameter combinations; performance optimization tips; edge cases and special handling requirements.
- If the tool execution represents a routine operation with no new insights, output `<NO_MEMORY_NEEDED>` and stop.
- Extract key guidelines in a clear and actionable manner.
- Each guideline should be self-contained and directly applicable.
- Avoid storing trivial or obvious information.
- Before proceeding, list all extracted guidelines in your response.
2. **Retrieve historical guidelines** for this tool by calling `vector_retrieve_memory`, using the `tool_name` as the query parameter to fetch any existing guidelines.
3. **Compare and Decide** on the appropriate memory operation:
- Compare the newly extracted guidelines with the historical ones to ensure the final memory store contains no duplicates or contradictions.
- Normally, `vector_retrieve_memory` should return at most one guideline per tool. If multiple guidelines exist for the same tool, use `delete_memory` to remove the redundant entries and merge all useful information into a single, comprehensive guideline.
- Choose the appropriate action based on the situation:
- If the guideline already exists and is consistent: skip—no action needed.
- If the existing guideline needs supplementation or refinement: use `update_memory` to enhance it.
- If the existing guideline is outdated or incorrect: use `update_memory` to replace it with the correct version.
- If multiple guidelines exist for the same tool: use `delete_memory` to remove duplicates, then use `update_memory` on the remaining entry to consolidate all useful information.
- If the guideline is entirely new: use `add_memory` to add it to the memory store.
4. **Output** the result:
- If no memory operation is required, output `<NO_MEMORY_NEEDED>`.
- If you added, updated, or deleted any guidelines, summarize the operations performed.
## Guidelines:
- **Be selective**: Only retain insights that genuinely improve tool usage efficiency.
- **Keep it actionable**: Each guideline should offer clear, practical advice.
- **Ensure accuracy**: Verify that extracted guidelines are supported by actual tool execution results.
- **Avoid redundancy**: Always check for similar existing guidelines before adding new ones.
- **Include relevant context when appropriate** (e.g., parameter values, error messages).
user_message: |
Please analyze the tool execution context to determine whether important usage guidelines should be extracted and stored as memory, and perform memory addition, deletion, or update operations when necessary.

View file

@ -1,6 +0,0 @@
"""Simplified V2 summarizers for memory management."""
from .reme_summarizer_v2 import ReMeSummarizerV2
from .personal_summarizer_v2 import PersonalSummarizerV2
__all__ = ["ReMeSummarizerV2", "PersonalSummarizerV2"]

View file

@ -1,107 +0,0 @@
"""Simplified personal memory summarizer using v2 memory tools."""
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, ToolCall
from ...core.utils import format_messages
@C.register_op()
class PersonalSummarizerV2(BaseMemoryAgent):
memory_type: MemoryType = MemoryType.PERSONAL
"""Simplified personal memory summarizer that uses v2 memory tools.
This summarizer follows a three-step workflow:
1. AddMemoryDrafts: Generate initial memory drafts from context
2. RetrieveRecentAndSimilarMemories: Retrieve similar and recent memories
3. UpdateMemories: Delete outdated memories and add new ones
"""
def _build_tool_call(self) -> ToolCall:
"""Build tool call schema for the agent."""
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def build_messages(self) -> list[Message]:
"""Construct messages with context, memory_target, and memory_type information."""
system_prompt = self.prompt_format(
prompt_name="system_prompt",
context=self.description + "\n" + format_messages(self.get_messages()),
memory_type=self.memory_type.value,
memory_target=self.memory_target,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages
async def _reasoning_step(self, messages: list[Message], step: int, **kwargs) -> tuple[Message, bool]:
return await super()._reasoning_step(messages, step, **kwargs)
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with memory_target, memory_type, and author context."""
messages: list[Message] = await super()._acting_step(
assistant_message,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
ref_memory_id=self.ref_memory_id,
author=self.author,
**kwargs,
)
# # Check if AddMemoryDrafts tool was executed
# exist_memory_drafts = False
# if assistant_message.tool_calls:
# for tool_call in assistant_message.tool_calls:
# if tool_call.name == "add_memory_drafts":
# exist_memory_drafts = True
# break
#
# # If memory drafts were added, regenerate system prompt with simplified context
# if exist_memory_drafts:
# simplified_context = "The conversation context has been summarized in memory drafts."
# new_system_prompt = self.prompt_format(
# prompt_name="system_prompt",
# context=simplified_context,
# memory_type=self.memory_type.value,
# memory_target=self.memory_target,
# )
#
# # Update the system message in the message history
# for i, msg in enumerate(self.messages):
# if msg.role == Role.SYSTEM:
# self.messages[i] = Message(role=Role.SYSTEM, content=new_system_prompt)
# break
return messages

View file

@ -1,43 +0,0 @@
tool: |
Extract and store personal memories from conversation context using a three-step workflow.
Use this tool to analyze dialogues and extract important personal information about users,
such as preferences, habits, personal background, relationships, and significant facts.
# - **Memory granularity**: Each memory should record ONE complete piece of information - don't pack multiple facts into one memory, and don't split a single fact into multiple memories.
# - **Self-contained**: Each memory entry must be self-contained and understandable without additional context.
system_prompt: |
You are a professional memory agent managing **{memory_type}** memories about **{memory_target}** for the main agent.
## Latest Conversation:
The context below contains the most recent conversation. Each message is formatted as: `round<index> [<timestamp>] <role/name>: <content>` where timestamp is `YYYY-MM-DD HH:MM:SS`.
{context}
**CRITICAL**: Extract information ONLY from what is explicitly stated. DO NOT infer, assume, or fabricate any information.
## Your Tasks
### Step 1: Generate Memory Drafts
Use `AddMemoryDrafts` to extract key facts from the latest conversation.
- Extract important information: preferences, habits, currentstatus, personal details, key facts, decisions, or conclusions.
- Use clear, concise phrasing based strictly on explicit statements.
- Record the timestamp of the source message for each memory including the year, month, and day.
### Step 2: Retrieve Similar and Recent Memories
Use `RetrieveRecentAndSimilarMemories` to query historical memories for each draft.
- Search for semantically similar memories and recent memories.
- This ensures Step 3 avoids duplicates and properly updates existing memories.
### Step 3: Update Memories
Use `UpdateMemories` to update the memory store by combining drafts with historical memories.
- **Delete conflicts**: Remove old memories that contradict the new drafts (keep most recent/accurate).
- **Add new**: Add drafts that represent completely new information.
- **Skip duplicates**: Do not add drafts that duplicate existing memories.
- **Preserve others**: Keep unrelated historical memories unchanged.
- Write concise memories using minimum words needed. Ensure no information loss.
user_message: |
Please analyze the context and update the memory store following the three-step workflow:
1. First use `AddMemoryDrafts` to generate initial memory drafts
2. Then use `RetrieveRecentAndSimilarMemories` to find related existing memories
3. Finally use `UpdateMemories` to remove outdated memories and add new consolidated memories

View file

@ -1,53 +0,0 @@
tool: |
Extract and store personal memories from conversation context using a three-step workflow.
Use this tool to analyze dialogues and extract important personal information about users,
such as preferences, habits, personal background, relationships, and significant facts.
system_prompt: |
You are a professional memory agent. Your task is to update the main agent's {memory_type} memory regarding {memory_target} based on the context.
**CRITICAL**: You must extract and store information STRICTLY based on what is explicitly stated in the context. DO NOT infer, assume, fabricate, or add any information that is not directly present in the dialogue. Only extract facts that are clearly and explicitly mentioned.
## Context:
{context}
**Context Format Explanation**:
The context contains formatted conversation messages in the following structure:
- Each message is formatted as: `round{index} [{timestamp}] {role/name}: {content}`
- The timestamp is in format: `YYYY-MM-DD HH:MM:SS`
- Content may include reasoning, tool calls
- **Time metadata handling**: When extracting memories with time information, store year/month/day in the metadata. For relative time references (e.g., "last year", "two months ago"), calculate the actual date based on the message's timestamp and store the calculated year/month/day in metadata
## Memory Objective:
You are managing **{memory_type}** memories about **{memory_target}** for the main agent. Focus on extracting and storing information directly related to this person's preferences, habits, personal background, and significant facts.
## Your Tasks - Three-Step Workflow:
### Step 1: Generate Memory Drafts
Use the `AddMemoryDrafts` tool to produce a set of non-redundant, self-contained memory drafts that capture all important information explicitly stated in the context. Each draft should record ONE complete fact with accurate time metadata (year, month, day) when time references are mentioned. If no valuable information exists, output `<NO_MEMORY_NEEDED>` and stop.
### Step 2: Retrieve Similar and Recent Memories
Use the `RetrieveRecentAndSimilarMemories` tool to obtain all existing memories that are semantically related to each memory draft, ensuring comprehensive coverage for deduplication and conflict detection.
### Step 3: Update Memories
Use the `UpdateMemories` tool to produce a final, non-redundant memory set where:
- `memory_ids_to_delete` contains IDs of memories that are duplicates, outdated, or being consolidated
- `memories_to_add` contains new or updated memories that preserve all information without redundancy or conflicts
## Guidelines:
- **Be selective**: Store only truly important information.
- **Stay concise**: Each memory should be clear and atomic, recording ONE complete piece of information.
- **Be strictly accurate**: Ensure extracted content faithfully reflects ONLY what is explicitly stated in the original context. DO NOT infer, extrapolate, or fabricate any details.
- **AVOID REDUNDANCY AT ALL COSTS**: This is your TOP PRIORITY. Always perform thorough deduplication:
* First, deduplicate within newly extracted memory drafts
* Then, check against retrieved memories from Step 2
* Actively use `memory_ids_to_delete` to remove duplicate or conflicting memories
* Use `memories_to_add` to consolidate and integrate information from multiple memories into one
* If information semantically matches existing memories, DO NOT add it again
* When uncertain, prefer to skip or update existing memories rather than create duplicates
- **Include relevant metadata**: Include time-related metadata (year, month, day) when appropriate, especially when time references are mentioned.
- **No assumptions**: Only store information that is directly and clearly stated in the conversation.
- **Quality over quantity**: It's better to have fewer, well-maintained memories than many duplicate ones.
user_message: |
Please update the memory store following the three-step workflow. If there is no valuable information to remember, output `<NO_MEMORY_NEEDED>` without calling any tools.

View file

@ -1,94 +0,0 @@
"""Simplified orchestrator for memory summarization workflow - V2."""
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.context import C
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, MemoryNode, ToolCall
from ...core.utils import format_messages
@C.register_op()
class ReMeSummarizerV2(BaseMemoryAgent):
"""Simplified version that coordinates memory updates using only summary_and_hands_off tool."""
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
"""Initialize with meta memories list."""
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def _read_meta_memories(self) -> str:
"""Fetch meta-memory entries using format_memory_metadata."""
from ...mem_tool import ReadMetaMemory
return ReadMetaMemory().format_memory_metadata(self.meta_memories)
async def build_messages(self) -> list[Message]:
"""Construct initial messages with context and meta-memory information."""
messages = [Message(**m) if isinstance(m, dict) else m for m in self.context.messages]
self.context["messages_formated"] = self.description + "\n" + format_messages(messages)
self.context["ref_memory_id"] = MemoryNode(
memory_type=MemoryType.HISTORY,
content=self.context["messages_formated"],
).memory_id
meta_memory_info = await self._read_meta_memories()
logger.info(f"meta_memory_info={meta_memory_info}")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=meta_memory_info,
context=self.context["messages_formated"],
)
user_message = self.get_prompt("user_message")
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=user_message),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with ref_memory_id and author context."""
return await super()._acting_step(
assistant_message,
step,
messages=self.context.get("messages", []),
description=self.context.get("description"),
ref_memory_id=self.context["ref_memory_id"],
messages_formated=self.context["messages_formated"],
author=self.author,
**kwargs,
)

View file

@ -1,25 +0,0 @@
tool: |
Orchestrate the complete memory summarization for the agent.
system_prompt: |
You are a Memory Agent responsible for performing necessary updates and summaries of the main Agent's memories based on the **context**.
# Context
{context}
## Main Agent's Meta Memory
Each line of meta memory indicates the existence of a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension (memory_type + memory_target).
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Task
Use `summary_and_hands_off` tool to:
1. Create a concise summary in `summary_content` that captures key points, decisions, or important facts from the context.
2. Identify which memory dimensions need updates and specify them in `memory_tasks` (each with `memory_type` and `memory_target`).
- The `memory_type` and `memory_target` must exactly match existing entries in the "Main Agent's Meta Memory" listed above.
- Multiple tasks can be specified to enable parallel processing by specialized agents.
Note: If the context contains no memorable information (e.g., simple greetings), output `<NO_MEMORY_NEEDED>`.
user_message: |
Please perform your task based on the context.

View file

@ -1,9 +0,0 @@
from .personal_summarizer_v3 import PersonalSummarizerV3
from .reme_retriever_v3 import ReMeRetrieverV3
from .reme_summarizer_v3 import ReMeSummarizerV3
__all__ = [
"PersonalSummarizerV3",
"ReMeRetrieverV3",
"ReMeSummarizerV3",
]

View file

@ -1,69 +0,0 @@
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, ToolCall
from ...core.utils import format_messages
class PersonalSummarizerV3(BaseMemoryAgent):
memory_type: MemoryType = MemoryType.PERSONAL
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def build_messages(self) -> list[Message]:
"""Construct messages with context, memory_target, and memory_type information."""
system_prompt = self.prompt_format(
prompt_name="system_prompt",
context=self.description + "\n" + format_messages(self.get_messages()),
memory_type=self.memory_type.value,
memory_target=self.memory_target,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages
async def _reasoning_step(self, messages: list[Message], step: int, **kwargs) -> tuple[Message, bool]:
return await super()._reasoning_step(messages, step, **kwargs)
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with memory_target, memory_type, and author context."""
messages: list[Message] = await super()._acting_step(
assistant_message,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
ref_memory_id=self.ref_memory_id,
author=self.author,
**kwargs,
)
return messages

View file

@ -1,42 +0,0 @@
tool: |
Extract and update personal memories about the user from conversation context.
Analyze dialogues to identify preferences, habits, background, relationships, and key facts.
system_prompt: |
You are a memory agent managing **{memory_type}** memories about **{memory_target}**.
## Latest Conversation:
{context}
Each message format: `round<index> [<timestamp>] <role/name>: <content>` (timestamp: YYYY-MM-DD HH:MM:SS).
**CRITICAL**: Extract ONLY explicitly stated information. DO NOT infer, assume, or fabricate.
## Three-Step Workflow
### Step 1: Extract Conversation Memories
Use `AddMemory` to extract key personal facts from the conversation.
- Extract: preferences, habits, status, personal details, decisions, conclusions
- **Format**: Use third-person perspective to record what **{memory_target}** said, did, or expressed at specific times
- **Consolidation**: Merge related information under the same topic into ONE memory entry
- Group similar facts (e.g., multiple food preferences → one food preference entry)
- Avoid creating separate entries for closely related information
- Keep entries concise and distinct (no duplicates, no omissions)
- Record `conversation_time` for each memory (format: 2020-01-01 00:00:00; use 0000-00-00 00:00:00 if unavailable)
### Step 2: Read User Profile
Use `ReadUserProfile` to retrieve the current user profile.
- Review existing memories to identify conflicts and duplicates
### Step 3: Update User Profile
Use `UpdateUserProfile` to synchronize the profile with new information.
- `profile_ids_to_delete`: Remove outdated or conflicting profiles
- `profiles_to_add`: Add new profiles that are not duplicates
- Use `timestamp` from conversation_time (format: 2020-01-01 00:00:00)
- Keep final profiles concise with no information loss
user_message: |
Execute the three-step workflow:
1. Use `AddMemory` to extract personal memories from the conversation
2. Use `ReadUserProfile` to read existing user profile
3. Use `UpdateUserProfile` to remove outdated entries and add new profiles

View file

@ -1,44 +0,0 @@
"""ReMe retriever v2 that autonomously retrieves memories from multiple angles."""
from typing import List
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role
from ...core.schema import Message
from ...core.utils import format_messages
class ReMeRetrieverV3(BaseMemoryAgent):
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
async def _read_meta_memories(self) -> str:
"""Fetch all meta-memory entries that define specialized memory agents."""
from ...mem_tool import ReadMetaMemory
op = ReadMetaMemory(enable_identity_memory=False)
return op.format_memory_metadata(self.meta_memories)
async def build_messages(self) -> List[Message]:
"""Build messages with system prompt and user message."""
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self._read_meta_memories(),
context=context,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages

View file

@ -1,50 +0,0 @@
tool: |
Autonomously retrieve relevant memories through a three-step strategy to answer user questions.
Steps: read user profile → vector search with multiple angles → read original conversations.
State "I don't know" if information cannot be found after exhaustive searching.
NEVER hallucinate or fabricate information not present in retrieved memories.
system_prompt: |
You are a memory agent. Search for relevant memories to answer the user's question following this strategy:
## Available Meta Memories
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## User's Question
{context}
## Three-Step Retrieval Strategy
**STEP 1: Read User Profile (REQUIRED FIRST)**
- Use `read_user_profile` with memory_type and memory_target from available meta memories
- Check if the user profile directly answers the question
**STEP 2: Vector Search (If Step 1 insufficient)**
- Use `retrieve_memory` with memory_type, memory_target, and query
- Try multiple retrieval angles (at least 3 different attempts):
* Direct query with user's question
* Reformulated queries with different phrasing/keywords
* Queries focused on specific entities or concepts
- **Time Range Filtering** (when applicable):
* Format: [start_date, end_date] in YYYYMMDD format
* Example: [20200101, 20200102] means 20200101 < time < 20200102
* Single-sided: [0, 20200102] for before, [20200101, 99999999] for after
* If no results, try broader time ranges or remove time constraints
- If no results after multiple attempts, try different memory_type/memory_target combinations
**STEP 3: Read Original Conversations (If Step 2 insufficient)**
- Use `read_history` with history_id from retrieved memories
- Prioritize reading:
* Most recent memories with history_id
* Most relevant memories from Step 2 with history_id
- Try multiple history_id entries if needed
## Response Rules
- If nothing found after all three steps: State clearly "I don't know. "
- Be persistent: try multiple angles in each step before moving to the next
user_message: |
Answer the question using the three-step strategy.

View file

@ -1,88 +0,0 @@
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, MemoryNode, ToolCall
from ...core.utils import format_messages
class ReMeSummarizerV3(BaseMemoryAgent):
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
"""Initialize with meta memories list."""
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def _read_meta_memories(self) -> str:
from ...mem_tool import ReadMetaMemory
return ReadMetaMemory().format_memory_metadata(self.meta_memories)
async def build_messages(self) -> list[Message]:
"""Construct initial messages with context and meta-memory information."""
messages = [Message(**m) if isinstance(m, dict) else m for m in self.context.messages]
self.context["messages_formated"] = self.description + "\n" + format_messages(messages)
self.context["ref_memory_id"] = MemoryNode(
memory_type=MemoryType.HISTORY,
content=self.context["messages_formated"],
).memory_id
meta_memory_info = await self._read_meta_memories()
logger.info(f"meta_memory_info={meta_memory_info}")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=meta_memory_info,
context=self.context["messages_formated"],
)
user_message = self.get_prompt("user_message")
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=user_message),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with ref_memory_id and author context."""
return await super()._acting_step(
assistant_message,
step,
messages=self.context.get("messages", []),
description=self.context.get("description"),
ref_memory_id=self.context["ref_memory_id"],
messages_formated=self.context["messages_formated"],
author=self.author,
**kwargs,
)

View file

@ -1,25 +0,0 @@
tool: |
Orchestrate the complete memory summarization for the agent.
system_prompt: |
You are a Memory Agent responsible for performing necessary updates and summaries of the main Agent's memories based on the **context**.
# Context
{context}
## Main Agent's Meta Memory
Each line of meta memory indicates the existence of a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension (memory_type + memory_target).
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Task
Use `summary_and_hands_off` tool to:
1. Create a concise summary in `summary_content` that captures key points, decisions, or important facts from the context.
2. Identify which memory dimensions need updates and specify them in `memory_tasks` (each with `memory_type` and `memory_target`).
- The `memory_type` and `memory_target` must exactly match existing entries in the "Main Agent's Meta Memory" listed above.
- Multiple tasks can be specified to enable parallel processing by specialized agents.
Note: If the context contains no memorable information (e.g., simple greetings), output `<NO_MEMORY_NEEDED>`.
user_message: |
Please perform your task based on the context.

View file

@ -1,11 +0,0 @@
from .reme_summarizer_v4 import ReMeSummarizerV4
from .reme_retriever_v4 import ReMeRetrieverV4
from .personal_summarizer_v4 import PersonalSummarizerV4
from .personal_retriever_v4 import PersonalRetrieverV4
__all__ = [
"ReMeSummarizerV4",
"ReMeRetrieverV4",
"PersonalSummarizerV4",
"PersonalRetrieverV4",
]

View file

@ -1,32 +0,0 @@
tool: |
Retrieve relevant personal memories to answer user questions through vector search and history reading.
user_message: |
You are a memory agent managing **{memory_type}** memories about **{memory_target}**.
## User Profile
{user_profile}
## Question
{context}
## Task
Search for relevant memories to answer the question above.
**Tool 1: Vector Search (`retrieve_memory`)**
- Try at least 3-5 different queries:
* Direct question
* Reformulated phrasings
* Entity-focused queries
* Different keyword combinations
- If no results: retry with different time ranges [start, end] in YYYYMMDD format
* Example: [20200101, 20200102] for 20200101 <= time <= 20200102
* Single-sided: [0, 20200102] or [20200101, 99999999]
**Tool 2: Read Context (`read_history`) - ONLY AFTER Tool 1**
- Use history_id from retrieved memories to read original conversations
- Read multiple if needed for complete context
**Response**
- If found relevant memories: respond exactly `<MEMORY_FOUND>`
- If no memory found after thorough search: respond exactly `<MEMORY_NOT_FOUND>`

View file

@ -1,49 +0,0 @@
tool: |
Extract and update personal memories about the user from conversation context.
Identify preferences, habits, background, relationships, and key facts.
user_message_phase1: |
You are a memory agent managing **{memory_type}** memories about **{memory_target}**.
## Latest Conversation:
{context}
Message format: `round<index> [<timestamp>] <role/name>: <content>` (timestamp: YYYY-MM-DD HH:MM:SS).
**CRITICAL**: Extract ONLY explicitly stated information. DO NOT infer, assume, or fabricate.
## Task: Extract Memories with `AddSummaryMemory`
Summarize all important information about **{memory_target}**
- Set `conversation_time` (format: 2020-01-01 00:00:00; use 0000-00-00 00:00:00 if unavailable)
Extract personal memories from the conversation using `AddSummaryMemory`.
# capturing complete contexts with preconditions, causes, and consequences
user_message_phase2: |
You are a memory agent managing **{memory_type}** memories about **{memory_target}**.
## Latest Conversation:
{context}
Message format: `round<index> [<timestamp>] <role/name>: <content>` (timestamp: YYYY-MM-DD HH:MM:SS).
**CRITICAL**: Extract ONLY explicitly stated information. DO NOT infer, assume, or fabricate.
## Current User Profile:
{user_profile}
## Task: Update Profile with `UpdateUserProfile`
Synchronize profile with new information from the conversation:
- `profile_ids_to_delete`: Remove conflicting, or redundant entries (array of profile IDs).
- `profiles_to_add`:
- `conversation_time`: Time of conversation (format: `YYYY-MM-DD HH:MM:SS`, e.g., `2024-01-15 14:30:00`)
- `profile_content`: Complete, self-contained profile description with full context
**Profile Requirements**:
- One user profile entry records one dimension of the user portrait, and MUST be complete and self-contained with all necessary context (preconditions, causes, and consequences)
- All profiles MUST be mutually exclusive (non-overlapping) and non-conflicting
- Profiles should collectively be comprehensive with no information loss
Update user profile using `UpdateUserProfile` based on the conversation and current profile.

View file

@ -1,25 +0,0 @@
tool: |
Retrieve information from specialized memory agents to answer user queries.
system_prompt: |
You are a Memory Retrieval Orchestrator responsible for querying specialized agents to answer user questions.
# User Query
{user_query}
## Available Memory Agents
Each line indicates a specialized Memory Agent that stores and retrieves memories within a specific dimension (memory_type + memory_target).
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Task
1. Use the `hands_off` tool to retrieve information from relevant agents
- Specify `memory_type` and `memory_target` for each query
- The `memory_type` and `memory_target` must **exactly match** existing entries in the "Available Memory Agents" listed above
- Do NOT query agents that don't exist above
- You can query multiple agents if needed
2. Answer the user query STRICTLY based on the `hands_off` results
3. If the retrieved information is insufficient to answer the query, respond: "nothing found after thorough search."
user_message: |
Please retrieve relevant information from the existing agents and provide an answer based on the results.

View file

@ -1,63 +0,0 @@
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, MemoryNode
from ...core.utils import format_messages
class ReMeSummarizerV4(BaseMemoryAgent):
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
async def _read_meta_memories(self) -> str:
from ...mem_tool import ReadMetaMemory
meta_memory_info = ReadMetaMemory().format_memory_metadata(self.meta_memories)
logger.info(f"meta_memory_info={meta_memory_info}")
return meta_memory_info
async def build_messages(self) -> list[Message]:
self.context.messages = [Message(**m) if isinstance(m, dict) else m for m in self.context.messages]
history_content = self.description + "\n" + format_messages(self.context.messages)
self.context.history_node = history_node = MemoryNode(
memory_type=MemoryType.HISTORY,
memory_target="",
when_to_use=history_content[:100],
content=history_content,
ref_memory_id="",
author=self.author,
metadata={},
)
logger.info(f"Adding summary node: {history_node.model_dump_json(indent=2, exclude_none=True)}")
await self.vector_store.delete(history_node.memory_id)
await self.vector_store.insert([history_node.to_vector_node()])
messages = [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self._read_meta_memories(),
context=history_node.content,
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message"),
),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
return await super()._acting_step(
assistant_message,
step,
messages=self.context.messages,
history_node=self.context.history_node,
author=self.author,
**kwargs,
)

View file

@ -1,26 +0,0 @@
tool: |
Orchestrate memory updates across specialized memory agents.
system_prompt: |
You are a Memory Orchestrator responsible for routing memory tasks to specialized agents based on the context.
# Context
{context}
## Available Memory Agents
Each line indicates a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension (memory_type + memory_target).
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Task
Use the `hands_off` tool to distribute memory tasks to specialized agents:
1. Analyze the context and identify which memory dimensions require updates
2. Specify `memory_type` and `memory_target` for each task
- The `memory_type` and `memory_target` must **exactly match** existing entries in the "Available Memory Agents" listed above
- Do NOT create new agents or use memory_type/memory_target combinations that don't exist above
3. Multiple tasks can be specified to enable parallel processing by specialized agents
Note: If the context contains no memorable information (e.g., simple greetings), return `<NO_MEMORY_NEEDED>`.
user_message: |
Please analyze the context and route memory tasks to the appropriate existing agents.

View file

@ -1,9 +0,0 @@
from .personal_summarizer_wk import PersonalSummarizerWk
from .reme_retriever_wk import ReMeRetrieverV2
from .reme_summarizer_wk import ReMeSummarizerWk
__all__ = [
"PersonalSummarizerWk",
"ReMeRetrieverV2",
"ReMeSummarizerWk",
]

View file

@ -1,69 +0,0 @@
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, ToolCall
from ...core.utils import format_messages
class PersonalSummarizerWk(BaseMemoryAgent):
memory_type: MemoryType = MemoryType.PERSONAL
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def build_messages(self) -> list[Message]:
"""Construct messages with context, memory_target, and memory_type information."""
system_prompt = self.prompt_format(
prompt_name="system_prompt",
context=self.description + "\n" + format_messages(self.get_messages()),
memory_type=self.memory_type.value,
memory_target=self.memory_target,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages
async def _reasoning_step(self, messages: list[Message], step: int, **kwargs) -> tuple[Message, bool]:
return await super()._reasoning_step(messages, step, **kwargs)
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with memory_target, memory_type, and author context."""
messages: list[Message] = await super()._acting_step(
assistant_message,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
ref_memory_id=self.ref_memory_id,
author=self.author,
**kwargs,
)
return messages

View file

@ -1,40 +0,0 @@
tool: |
Extract and store personal memories from conversation context using a three-step workflow.
Use this tool to analyze dialogues and extract important personal information about users,
such as preferences, habits, personal background, relationships, and significant facts.
system_prompt: |
You are a professional memory agent managing **{memory_type}** memories about **{memory_target}** for the main agent.
## Latest Conversation:
The context below contains the most recent conversation. Each message is formatted as: `round<index> [<timestamp>] <role/name>: <content>` where timestamp is `YYYY-MM-DD HH:MM:SS`.
{context}
**CRITICAL**: Extract information ONLY from what is explicitly stated. DO NOT infer, assume, or fabricate any information.
## Your Tasks
### Step 1: Generate Memory Drafts
Use `AddMemoryDrafts` to extract key facts from the latest conversation.
- Extract important information: preferences, habits, currentstatus, personal details, key facts, decisions, or conclusions.
- Use clear, concise phrasing based strictly on explicit statements.
- Record the timestamp of the source message for each memory including the year, month, and day.
### Step 2: Retrieve Similar and Recent Memories
Use `RetrieveRecentAndSimilarMemories` to query historical memories for each draft.
- Search for semantically similar memories and recent memories.
- This ensures Step 3 avoids duplicates and properly updates existing memories.
### Step 3: Update Memories
Use `UpdateMemories` to update the memory store by combining drafts with historical memories.
- **Delete conflicts**: Remove old memories that contradict the new drafts (keep most recent/accurate).
- **Add new**: Add drafts that represent completely new information.
- **Skip duplicates**: Do not add drafts that duplicate existing memories.
- **Preserve others**: Keep unrelated historical memories unchanged.
- Write concise memories using minimum words needed. Ensure no information loss.
user_message: |
Please analyze the context and update the memory store following the three-step workflow:
1. First use `AddMemoryDrafts` to generate initial memory drafts
2. Then use `RetrieveRecentAndSimilarMemories` to find related existing memories
3. Finally use `UpdateMemories` to remove outdated memories and add new consolidated memories

View file

@ -1,44 +0,0 @@
"""ReMe retriever v2 that autonomously retrieves memories from multiple angles."""
from typing import List
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role
from ...core.schema import Message
from ...core.utils import format_messages
class ReMeRetrieverV2(BaseMemoryAgent):
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
async def _read_meta_memories(self) -> str:
"""Fetch all meta-memory entries that define specialized memory agents."""
from ...mem_tool import ReadMetaMemory
op = ReadMetaMemory(enable_identity_memory=False)
return op.format_memory_metadata(self.meta_memories)
async def build_messages(self) -> List[Message]:
"""Build messages with system prompt and user message."""
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self._read_meta_memories(),
context=context,
)
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=self.get_prompt("user_message")),
]
return messages

View file

@ -1,125 +0,0 @@
tool: |
Autonomously retrieve relevant memories from multiple angles to answer user questions.
This retriever will:
- Try multiple vector search strategies (direct, metadata-filtered, partial)
- Attempt at least 3 different retrieval approaches before giving up
- Fall back to reading original conversation history if vector search is insufficient
- Clearly state "I don't know" if information cannot be found after exhaustive searching
- NEVER hallucinate or fabricate information not present in retrieved memories
Use this when you need comprehensive memory retrieval with persistent searching.
system_prompt: |
You are an autonomous memory retrieval agent. Your task is to persistently search for relevant memories from multiple angles to answer the user's question.
## Available Meta Memories
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## User Context
{context}
## Your Retrieval Strategy
You MUST use the `retrieve_memories` tool to search for relevant information. This is a MANDATORY step - do not skip it.
1. **Multi-Angle Vector Retrieval** (REQUIRED - at least 3 attempts):
You must try AT LEAST 3 different retrieval approaches using `retrieve_memories`:
a) **Direct Vector Search**: Use the user's question directly or with minimal reformulation
- Query the most relevant memory_type and memory_target
- Use straightforward query phrasing
b) **Alternative Phrasing**: Reformulate the query from a different angle
- Use synonyms or different expressions
- Break down complex questions into simpler components
- Try more specific or more general queries
c) **Metadata-Filtered Search**: Add metadata filters to narrow down results
- **Time-based filtering**: Use year/month/day metadata fields to filter by time periods
* Example: {{"year": 2024}} for memories from 2024
* Example: {{"year": 2024, "month": 5}} for memories from May 2024
* Example: {{"year": 2024, "month": 5, "day": 15}} for memories from a specific date
- Combine vector search with metadata constraints
- Try partial metadata filtering if full filtering yields nothing (e.g., only year, or year+month)
d) **Cross-Memory-Type Search**: If applicable, search across different memory types
- Try different memory_type and memory_target combinations
- Some information might be stored in unexpected memory categories
e) **Keyword Extraction**: Extract key entities/concepts and search for them
- Identify important names, places, concepts
- Search for each key element separately
2. **Evaluate Retrieval Results** (After each attempt):
- Review what memories were returned
- Assess if they contain sufficient information to answer the question
- If insufficient, identify what's missing and adjust your next query accordingly
- Track which retrieval strategies you've already tried
3. **Persist Through Failures**:
- DO NOT give up after 1-2 failed attempts
- If a retrieval returns no results or irrelevant results, try a different approach
- Consider that the information might be phrased differently than expected
- Be creative with query reformulation
4. **Fallback to History Reading** (Only after 3+ vector retrieval attempts):
- If after at least 3 different vector retrieval attempts you still lack sufficient information:
* If any retrieved memories contain `ref_memory_id`, use `read_history` to read the original conversation
* Use `read_history` with the `ref_memory_id` to get complete context
* This can reveal details that weren't captured in the memory summaries
5. **Answer the Question**:
- Once you have sufficient information, provide a direct answer based ONLY on retrieved memories
- DO NOT fabricate, guess, or infer information not present in the memories
- **CRITICAL**: If after 3+ retrieval attempts you still cannot find relevant information:
* Simply state: "I don't know. After searching from multiple angles, I could not find relevant information to answer this question."
* DO NOT make up answers or hallucinate information
* DO NOT provide speculative or guessed responses
* It is better to say "I don't know" than to provide incorrect information
## Important Guidelines
- **Be Persistent**: Always try at least 3 different retrieval strategies before concluding no information exists
- **Be Creative**: If one query approach fails, think of alternative ways to phrase or decompose the question
- **Use Tools**: You MUST use `retrieve_memories` for vector search. Use `read_history` if you have `ref_memory_id` and need more details
- **No Hallucination**: NEVER fabricate, guess, or hallucinate information. Only answer based on what you actually retrieved from memories
- **Admit When You Don't Know**: If after 3+ attempts you cannot find relevant information, clearly say "I don't know" rather than making up an answer
- **Track Your Attempts**: Keep count of how many different retrieval strategies you've tried
- **Metadata Awareness**: Utilize metadata filters when they might help narrow down results
* Memories store time information in metadata as year/month/day fields
* Use time-based filters when the question involves specific time periods or dates
* Try progressive filtering: start with year, then add month, then day if needed
## Example Retrieval Flow
**Example 1: Simple Query**
Attempt 1: Direct query "user's favorite food"
→ Result: No relevant memories found
Attempt 2: Reformulated query "what does user like to eat"
→ Result: Some memories about meals, but not specific preferences
Attempt 3: Keyword search "food preferences" with metadata filter
→ Result: Found relevant memory with ref_memory_id
Attempt 4: Use read_history with ref_memory_id to get full context
→ Result: Found detailed conversation about favorite foods
Answer: [Provide answer based on retrieved information]
**Example 2: Time-based Query**
Question: "What did the user do last summer?"
Attempt 1: Direct query "user activities summer" with metadata {{"year": 2025, "month": [6, 7, 8]}}
→ Result: Found some vacation memories
Attempt 2: Broader query "user summer vacation travel" with metadata {{"year": 2025}}
→ Result: Found additional travel-related memories
Attempt 3: Use read_history for memories with ref_memory_id to get detailed context
→ Result: Complete picture of summer activities
Answer: [Provide answer based on retrieved information]
user_message: |
Please retrieve relevant memories and answer the question. Remember to try multiple retrieval approaches before giving up.

View file

@ -1,88 +0,0 @@
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, MemoryNode, ToolCall
from ...core.utils import format_messages
class ReMeSummarizerWk(BaseMemoryAgent):
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
"""Initialize with meta memories list."""
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": self.get_prompt("tool"),
"parameters": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "role",
},
"content": {
"type": "string",
"description": "content",
},
},
"required": ["role", "content"],
},
},
},
"required": ["messages"],
},
},
)
async def _read_meta_memories(self) -> str:
from ...mem_tool import ReadMetaMemory
return ReadMetaMemory().format_memory_metadata(self.meta_memories)
async def build_messages(self) -> list[Message]:
"""Construct initial messages with context and meta-memory information."""
messages = [Message(**m) if isinstance(m, dict) else m for m in self.context.messages]
self.context["messages_formated"] = self.description + "\n" + format_messages(messages)
self.context["ref_memory_id"] = MemoryNode(
memory_type=MemoryType.HISTORY,
content=self.context["messages_formated"],
).memory_id
meta_memory_info = await self._read_meta_memories()
logger.info(f"meta_memory_info={meta_memory_info}")
system_prompt = self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=meta_memory_info,
context=self.context["messages_formated"],
)
user_message = self.get_prompt("user_message")
messages = [
Message(role=Role.SYSTEM, content=system_prompt),
Message(role=Role.USER, content=user_message),
]
return messages
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
"""Execute tool calls with ref_memory_id and author context."""
return await super()._acting_step(
assistant_message,
step,
messages=self.context.get("messages", []),
description=self.context.get("description"),
ref_memory_id=self.context["ref_memory_id"],
messages_formated=self.context["messages_formated"],
author=self.author,
**kwargs,
)

View file

@ -1,25 +0,0 @@
tool: |
Orchestrate the complete memory summarization for the agent.
system_prompt: |
You are a Memory Agent responsible for performing necessary updates and summaries of the main Agent's memories based on the **context**.
# Context
{context}
## Main Agent's Meta Memory
Each line of meta memory indicates the existence of a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension (memory_type + memory_target).
Format: "- <memory_type>(<memory_target>): <description>"
{meta_memory_info}
## Your Task
Use `summary_and_hands_off` tool to:
1. Create a concise summary in `summary_content` that captures key points, decisions, or important facts from the context.
2. Identify which memory dimensions need updates and specify them in `memory_tasks` (each with `memory_type` and `memory_target`).
- The `memory_type` and `memory_target` must exactly match existing entries in the "Main Agent's Meta Memory" listed above.
- Multiple tasks can be specified to enable parallel processing by specialized agents.
Note: If the context contains no memorable information (e.g., simple greetings), output `<NO_MEMORY_NEEDED>`.
user_message: |
Please perform your task based on the context.

View file

@ -1,35 +0,0 @@
"""Memory tool operations."""
from .base_memory_tool import BaseMemoryTool
from .hands_off_tool import HandsOffTool
from .history.add_history_memory import AddHistoryMemory
from .history.read_history_memory import ReadHistoryMemory
from .identity.read_identity_memory import ReadIdentityMemory
from .identity.update_identity_memory import UpdateIdentityMemory
from .meta.add_meta_memory import AddMetaMemory
from .meta.read_meta_memory import ReadMetaMemory
from .think_tool import ThinkTool
from .vector_store.add_memory import AddMemory
from .vector_store.add_summary_memory import AddSummaryMemory
from .vector_store.delete_memory import DeleteMemory
from .vector_store.retrieve_recent_memory import RetrieveRecentMemory
from .vector_store.update_memory import UpdateMemory
from .vector_store.vector_retrieve_memory import VectorRetrieveMemory
__all__ = [
"BaseMemoryTool",
"HandsOffTool",
"AddHistoryMemory",
"ReadHistoryMemory",
"ReadIdentityMemory",
"UpdateIdentityMemory",
"AddMetaMemory",
"ReadMetaMemory",
"ThinkTool",
"AddMemory",
"AddSummaryMemory",
"DeleteMemory",
"RetrieveRecentMemory",
"UpdateMemory",
"VectorRetrieveMemory",
]

View file

@ -1,17 +0,0 @@
"""Version 2 memory tools with enhanced functionality."""
from .add_memory_drafts import AddMemoryDrafts
from .read_history import ReadHistory
from .retrieve_memories import RetrieveMemories
from .retrieve_recent_and_similar_memories import RetrieveRecentAndSimilarMemories
from .summary_and_hands_off import SummaryAndHandsOff
from .update_memories import UpdateMemories
__all__ = [
"AddMemoryDrafts",
"ReadHistory",
"RetrieveMemories",
"RetrieveRecentAndSimilarMemories",
"SummaryAndHandsOff",
"UpdateMemories",
]

View file

@ -1,106 +0,0 @@
"""Add memory drafts operation for vector store."""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
@C.register_op()
class AddMemoryDrafts(BaseMemoryTool):
"""Add memory drafts without persisting them to the database.
This tool is useful for creating draft memories that can be reviewed and modified
before final submission. Drafts are not persisted to the vector store.
Metadata fields can be customized via `metadata_desc` parameter.
"""
def __init__(self, add_when_to_use: bool = False, metadata_desc: dict[str, str] | None = None, **kwargs):
"""Initialize AddMemoryDrafts.
Args:
add_when_to_use: Include when_to_use field for better retrieval. Defaults to True.
metadata_desc: Dictionary defining metadata fields and their descriptions.
**kwargs: Additional arguments for BaseMemoryTool.
"""
kwargs["enable_multiple"] = True
super().__init__(**kwargs)
self.add_when_to_use: bool = add_when_to_use
self.metadata_desc: dict[str, str] = metadata_desc or {}
def _build_item_schema(self) -> tuple[dict, list[str]]:
"""Build shared schema properties and required fields for memory items to add.
Returns:
Tuple of (properties dict, required fields list).
"""
properties = {}
required = []
if self.add_when_to_use:
properties["when_to_use"] = {
"type": "string",
"description": self.get_prompt("when_to_use"),
}
required.append("when_to_use")
properties["memory_content"] = {
"type": "string",
"description": self.get_prompt("memory_content"),
}
required.append("memory_content")
# Add metadata field if metadata_desc is provided and not empty
if self.metadata_desc:
metadata_properties = {
key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items()
}
properties["metadata"] = {
"type": "object",
"description": "metadata",
"properties": metadata_properties,
}
required.append("metadata")
return properties, required
def _build_multiple_parameters(self) -> dict:
"""Build input schema for add drafts operation.
Only supports batch mode for adding draft memories.
"""
item_properties, required_fields = self._build_item_schema()
return {
"type": "object",
"properties": {
"memory_drafts": {
"type": "array",
"description": self.get_prompt("memory_drafts"),
"items": {
"type": "object",
"properties": item_properties,
"required": required_fields,
},
},
},
"required": ["memory_drafts"],
}
def _extract_memory_data(self, mem_dict: dict) -> tuple[str, str, dict]:
"""Extract memory data from a dictionary with proper defaults.
Args:
mem_dict: Dictionary containing memory fields.
Returns:
Tuple of (memory_content, when_to_use, metadata).
"""
memory_content = mem_dict.get("memory_content", "")
when_to_use = mem_dict.get("when_to_use", "") if self.add_when_to_use else ""
metadata = mem_dict.get("metadata", {}) if self.metadata_desc else {}
return memory_content, when_to_use, metadata
async def execute(self):
"""Execute add drafts operation: create memory drafts without persisting to vector store."""
self.output = f"Successfully created memory draft(s). These drafts are not yet persisted to the vector store."
logger.info(self.output)

View file

@ -1,18 +0,0 @@
tool_multiple: |
Create draft memories for initial recording of information.
Use this tool to quickly capture information as drafts that can be reviewed or modified later.
**CRITICAL**: Only add memories based on explicitly stated facts. DO NOT store inferred, assumed, or fabricated information.
memory_drafts: |
A list of draft memory objects to create.
Each draft represents a piece of information to be recorded initially.
when_to_use: |
When to retrieve this memory.
This field is used for vector embedding to improve retrieval accuracy by providing contextual information.
memory_content: |
The content of the memory draft to record.
Should be a clear, concise statement that captures the information to remember.
Keep it focused on a single piece of information for better retrieval accuracy.
**Must be strictly accurate and based only on explicitly stated facts - no inference or fabrication.**

View file

@ -1,57 +0,0 @@
"""Read history memory operation."""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
from ...core.schema import MemoryNode
@C.register_op()
class ReadHistory(BaseMemoryTool):
"""Read original history dialogue by reference memory ID.
Only supports single memory read (enable_multiple=False).
"""
def __init__(self, **kwargs):
"""Initialize ReadHistory.
Args:
**kwargs: Additional args for BaseMemoryTool.
"""
# Force disable multiple mode
kwargs["enable_multiple"] = False
super().__init__(**kwargs)
def _build_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"ref_memory_id": {
"type": "string",
"description": self.get_prompt("ref_memory_id"),
},
},
"required": ["ref_memory_id"],
}
async def execute(self):
ref_memory_id = self.context.get("ref_memory_id", "")
if not ref_memory_id:
self.output = "No valid reference memory ID provided."
logger.warning(self.output)
return
# Query history dialogue by ref_memory_id
nodes = await self.vector_store.get(vector_ids=[ref_memory_id])
if not nodes:
self.output = f"No history memory found with ID: {ref_memory_id}"
logger.warning(self.output)
return
memory = MemoryNode.from_vector_node(nodes[0])
self.output = memory.content
logger.info(f"Successfully read history memory: {ref_memory_id}")

View file

@ -1,5 +0,0 @@
tool: |
Read original history dialogue by reference memory ID.
ref_memory_id: |
Reference memory ID to query the original history dialogue.

View file

@ -1,188 +0,0 @@
"""Retrieve memories using vector similarity search with multiple queries."""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
from ...core.schema import MemoryNode, VectorNode
from ...core.utils import deduplicate_memories
@C.register_op()
class RetrieveMemories(BaseMemoryTool):
"""Retrieve memories using vector similarity search with multiple queries.
Always requires memory_type/memory_target in the schema.
Only supports multiple query mode (enable_multiple=True).
Metadata filters can be customized via `metadata_desc` parameter for pre-retrieval filtering.
"""
def __init__(self, metadata_desc: dict[str, str] | None = None, top_k: int = 20, **kwargs):
"""Initialize RetrieveMemories.
Args:
metadata_desc: Dictionary defining metadata filter fields and their descriptions.
These fields will be used as filters in vector search before similarity matching.
top_k: Max memories to retrieve per query.
**kwargs: Additional args for BaseMemoryTool.
"""
kwargs["enable_multiple"] = True
super().__init__(**kwargs)
self.metadata_desc: dict[str, str] = metadata_desc or {}
self.top_k: int = top_k
def _build_query_schema(self) -> tuple[dict, list[str]]:
"""Build schema properties and required fields for query items.
Returns:
Tuple of (properties dict, required fields list).
"""
properties = {
"memory_type": {
"type": "string",
"description": self.get_prompt("memory_type"),
},
"memory_target": {
"type": "string",
"description": self.get_prompt("memory_target"),
},
"query": {
"type": "string",
"description": self.get_prompt("query"),
},
}
required = ["memory_type", "memory_target", "query"]
# Add metadata filter fields if metadata_desc is provided and not empty
if self.metadata_desc:
metadata_properties = {
key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items()
}
# Generate dynamic description based on metadata_desc fields
field_descriptions = "\n".join([f" - {key}: {desc}" for key, desc in self.metadata_desc.items()])
metadata_description = (
f"Optional metadata filters for narrowing search results. Available fields:\n{field_descriptions}"
)
properties["metadata_filters"] = {
"type": "object",
"description": metadata_description,
"properties": metadata_properties,
}
return properties, required
def _build_multiple_parameters(self) -> dict:
"""Build input schema for multiple query mode.
Returns:
Schema with query_items array. Each item has memory_type/memory_target/query.
"""
item_properties, item_required = self._build_query_schema()
return {
"type": "object",
"properties": {
"query_items": {
"type": "array",
"description": self.get_prompt("query_items"),
"items": {
"type": "object",
"properties": item_properties,
"required": item_required,
},
},
},
"required": ["query_items"],
}
async def _retrieve_by_query(
self,
memory_type: str,
memory_target: str,
query: str,
metadata_filters: dict | None = None,
) -> list[MemoryNode]:
"""Retrieve memories by query using vector similarity search.
Args:
memory_type: Memory type to search.
memory_target: Memory target to search.
query: Query string for similarity search.
metadata_filters: Optional metadata filters to narrow search results.
Returns:
List of matching memories.
"""
filter_dict = {
"memory_type": [memory_type],
"memory_target": [memory_target],
}
# Add metadata filters if provided
if metadata_filters:
for key, value in metadata_filters.items():
if value: # Only add non-empty filter values
value = str(value).strip()
filter_dict[key] = [value] if not isinstance(value, list) else value
nodes: list[VectorNode] = await self.vector_store.search(query=query, limit=self.top_k, filters=filter_dict)
memory_nodes: list[MemoryNode] = [MemoryNode.from_vector_node(n) for n in nodes]
return memory_nodes
async def execute(self):
"""Execute memory retrieval based on multiple query items.
Outputs formatted results or error message.
"""
query_items: list[dict] = self.context.get("query_items", [])
if not query_items:
self.output = "No query items provided for retrieval."
return
# Filter out items without query text
query_items = [item for item in query_items if item.get("query")]
if not query_items:
self.output = "No valid query texts provided for retrieval."
return
# Retrieve memory_nodes for all queries
memory_nodes: list[MemoryNode] = []
for item in query_items:
memory_type = item.get("memory_type")
memory_target = item.get("memory_target")
metadata_filters = item.get("metadata_filters", {}) if self.metadata_desc else {}
if not memory_type or not memory_target:
logger.warning(f"Skipping query with missing memory_type or memory_target: {item}")
continue
retrieved = await self._retrieve_by_query(
memory_type=memory_type,
memory_target=memory_target,
query=item["query"],
metadata_filters=metadata_filters,
)
memory_nodes.extend(retrieved)
# Deduplicate and format output
memory_nodes = deduplicate_memories(memory_nodes)
# Build set of historical memory_ids for fast lookup
retrieved_memory_ids = {node.memory_id for node in self.retrieved_nodes if node.memory_id}
# Filter out already retrieved memories by memory_id
new_memory_nodes = [node for node in memory_nodes if node.memory_id not in retrieved_memory_ids]
# Update retrieved_nodes in context with new memories
self.retrieved_nodes.extend(new_memory_nodes)
# Set output to new memories only (after deduplication)
self.memory_nodes = new_memory_nodes
if not new_memory_nodes:
self.output = "No new memories found matching the queries (duplicates removed)."
else:
self.output = "\n".join([m.format_memory() for m in new_memory_nodes])
logger.info(f"Retrieved {len(memory_nodes)} memories, {len(new_memory_nodes)} new after deduplication")

View file

@ -1,24 +0,0 @@
tool_multiple: |
Retrieve memories from the memory store using multiple queries with vector similarity search.
Use this tool to find relevant memories based on semantic similarity to multiple queries.
This is useful when you need to search for different types of information in a single operation.
The search returns the most relevant memories ranked by similarity score for each query.
Note: Within the same session, this tool automatically deduplicates results across multiple calls.
If you call this tool multiple times, only new memories (not previously retrieved) will be returned.
This prevents redundant information in subsequent retrievals.
memory_type: |
The type of memory to search for.
You MUST select one of the memory_type values that are explicitly provided in the Available Meta-Memories.
memory_target: |
The target of the memory to search within.
You MUST select one of the memory_type values that are explicitly provided in the Available Meta-Memories.
query: |
The query text for vector similarity search.
Use descriptive queries that capture the semantic meaning of what you're looking for.
query_items: |
A list of query items for vector similarity search.

View file

@ -1,173 +0,0 @@
"""Combined memory retrieval: recent + vector similarity search."""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
from ...core.schema import MemoryNode, VectorNode
from ...core.utils import deduplicate_memories
@C.register_op()
class RetrieveRecentAndSimilarMemories(BaseMemoryTool):
"""Retrieve memories using both time-based and vector similarity search.
First retrieves recent_top_k memories sorted by modification time,
then retrieves similar_top_k memories using vector similarity search.
Uses memory_type and memory_target from context (self.memory_type, self.memory_target).
"""
def __init__(
self,
recent_top_k: int = 20,
similar_top_k: int = 20,
**kwargs,
):
"""Initialize RetrieveRecentAndSimilarMemories.
Args:
recent_top_k: Max recent memories to retrieve by time.
similar_top_k: Max similar memories to retrieve by vector search.
**kwargs: Additional args for BaseMemoryTool.
"""
kwargs["enable_multiple"] = True
super().__init__(**kwargs)
self.recent_top_k: int = recent_top_k
self.similar_top_k: int = similar_top_k
def _build_tool_description(self) -> str:
"""Build tool description."""
return self.prompt_format("tool_multiple",
recent_top_k=self.recent_top_k,
similar_top_k=self.similar_top_k)
def _build_multiple_parameters(self) -> dict:
"""Build input schema for multiple query mode.
Returns:
Schema with query_items array.
"""
return {
"type": "object",
"properties": {
"query_items": {
"type": "array",
"description": self.get_prompt("query_items"),
"items": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": self.get_prompt("query"),
},
},
"required": ["query"],
},
},
},
"required": ["query_items"],
}
async def _retrieve_recent(self) -> list[MemoryNode]:
"""Retrieve recent memories sorted by time_modified.
Returns:
List of recent memories sorted by modification time (newest first).
"""
filter_dict = {
"memory_type": [self.memory_type.value],
"memory_target": [self.memory_target],
}
# Use list() with sort_key="time_modified", reverse=True (descending), and limit
nodes: list[VectorNode] = await self.vector_store.list(
filters=filter_dict,
limit=self.recent_top_k,
sort_key="time_modified",
reverse=True, # Most recent first (descending order)
)
memory_nodes: list[MemoryNode] = [MemoryNode.from_vector_node(n) for n in nodes]
return memory_nodes
async def _retrieve_by_query(
self,
query: str,
) -> list[MemoryNode]:
"""Retrieve memories by query using vector similarity search.
Args:
query: Query string for similarity search.
Returns:
List of matching memories.
"""
filter_dict = {
"memory_type": [self.memory_type.value],
"memory_target": [self.memory_target],
}
nodes: list[VectorNode] = await self.vector_store.search(
query=query, limit=self.similar_top_k, filters=filter_dict
)
memory_nodes: list[MemoryNode] = [MemoryNode.from_vector_node(n) for n in nodes]
return memory_nodes
async def execute(self):
"""Execute combined memory retrieval (recent + similar).
First retrieves recent_top_k memories by time, then retrieves similar_top_k
memories by vector similarity for each query in query_items.
Uses memory_type and memory_target from context. Outputs formatted results or error message.
"""
if not self.memory_type or not self.memory_target:
raise RuntimeError("memory_type and memory_target are required for retrieval.")
# Get query items
query_items: list[dict] = self.context.get("query_items", [])
if not query_items:
self.output = "No query items provided for retrieval."
return
# Filter out items without query text
query_items = [item for item in query_items if item.get("query")]
if not query_items:
self.output = "No valid query texts provided for retrieval."
return
# Step 1: Retrieve recent memories (once, shared across all queries)
recent_memory_nodes: list[MemoryNode] = await self._retrieve_recent()
logger.info(f"Retrieved {len(recent_memory_nodes)} recent memories")
# Step 2: Retrieve similar memories by vector search for all queries
similar_memory_nodes: list[MemoryNode] = []
for item in query_items:
retrieved = await self._retrieve_by_query(query=item["query"])
similar_memory_nodes.extend(retrieved)
# Combine and deduplicate all memories
all_memory_nodes = recent_memory_nodes + similar_memory_nodes
all_memory_nodes = deduplicate_memories(all_memory_nodes)
# Build set of historical memory_ids for fast lookup
retrieved_memory_ids = {node.memory_id for node in self.retrieved_nodes if node.memory_id}
# Filter out already retrieved memories by memory_id
new_memory_nodes = [node for node in all_memory_nodes if node.memory_id not in retrieved_memory_ids]
# Update retrieved_nodes in context with new memories
self.retrieved_nodes.extend(new_memory_nodes)
if not new_memory_nodes:
self.output = "No new memory_nodes found (duplicates removed)."
else:
self.output = "\n".join([m.format_memory() for m in new_memory_nodes])
logger.info(
f"Retrieved {len(all_memory_nodes)} total memories "
f"({len(recent_memory_nodes)} recent + {len(similar_memory_nodes)} similar), "
f"{len(new_memory_nodes)} new after deduplication"
)

View file

@ -1,25 +0,0 @@
tool_multiple: |
Retrieve memories using both time-based and multiple vector similarity searches.
This tool combines two retrieval strategies:
1. First retrieves the most recent memories based on modification time (recent top {recent_top_k})
2. Then retrieves semantically similar memories for each of your queries (similar top {similar_top_k} per query)
This is useful when you need to search for different types of information in a single operation,
while also considering recent context.
The results are automatically deduplicated, so you get a combined set of both recent
and relevant memories without duplicates.
Note: Within the same session, this tool automatically deduplicates results across multiple calls.
If you call this tool multiple times, only new memories (not previously retrieved) will be returned.
This prevents redundant information in subsequent retrievals.
query: |
The query text for vector similarity search.
Use descriptive queries that capture the semantic meaning of what you're looking for.
query_items: |
A list of query items for vector similarity search.
Each query will be used to find semantically similar memories, which will be combined
with the recent memories retrieved based on modification time.

View file

@ -1,162 +0,0 @@
"""Summary and hands-off tool for distributing summarized memory to appropriate agents."""
import json
from typing import TYPE_CHECKING
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
from ...core.enumeration import MemoryType
from ...core.schema import MemoryNode, Message
if TYPE_CHECKING:
from ...mem_agent import BaseMemoryAgent
@C.register_op()
class SummaryAndHandsOff(BaseMemoryTool):
"""Distribute summarized memory task to appropriate agent based on memory_type."""
def __init__(self, memory_agents: list["BaseMemoryAgent"], **kwargs):
# Force enable_multiple to True since this tool only supports multiple tasks
kwargs["enable_multiple"] = True
kwargs["sub_ops"] = memory_agents or []
super().__init__(**kwargs)
from ...mem_agent import BaseMemoryAgent
self.sub_ops: list[BaseMemoryAgent] = [a for a in self.sub_ops if isinstance(a, BaseMemoryAgent)]
self.messages: list[Message] = []
@property
def memory_agent_dict(self) -> dict[MemoryType, "BaseMemoryAgent"]:
"""Returns a dictionary mapping memory types to their corresponding agents."""
return {a.memory_type: a for a in self.sub_ops}
def _build_item_schema(self) -> tuple[dict, list[str]]:
"""Build shared schema properties and required fields for memory tasks."""
properties = {
"memory_type": {
"type": "string",
"description": self.get_prompt("memory_type"),
"enum": [k.value for k in self.memory_agent_dict],
},
"memory_target": {
"type": "string",
"description": self.get_prompt("memory_target"),
},
}
required = ["memory_type", "memory_target"]
return properties, required
def _build_multiple_parameters(self) -> dict:
"""Build input schema for multiple summary and hands-off tasks."""
item_properties, required_fields = self._build_item_schema()
return {
"type": "object",
"properties": {
"summary_content": {
"type": "string",
"description": self.get_prompt("summary_content"),
},
"memory_tasks": {
"type": "array",
"description": self.get_prompt("memory_tasks"),
"items": {
"type": "object",
"properties": item_properties,
"required": required_fields,
},
},
},
"required": ["summary_content", "memory_tasks"],
}
@staticmethod
def _parse_memory_type_target(task: dict):
memory_type = task.get("memory_type", "")
memory_target = task.get("memory_target", "")
return {"memory_type": MemoryType(memory_type), "memory_target": memory_target}
def _collect_tasks(self) -> list[dict]:
"""Collect memory tasks from context."""
tasks: list[dict] = []
memory_tasks: list[dict] = self.context.get("memory_tasks", [])
for task in memory_tasks:
tasks.append(self._parse_memory_type_target(task))
return tasks
async def execute(self):
"""Execute memory tasks by distributing to appropriate agents in parallel."""
summary_content = self.context.get("summary_content", "")
assert summary_content, "No summary content provided."
# Build and store summary node
summary_node = MemoryNode(
memory_type=MemoryType.HISTORY,
memory_target="",
when_to_use=summary_content,
content=self.messages_formated,
ref_memory_id="",
author=self.author,
metadata={},
)
logger.info(f"Adding summary node: {summary_node.model_dump_json(indent=2, exclude_none=True)}")
self.memory_nodes.append(summary_node)
vector_node = summary_node.to_vector_node()
await self.vector_store.delete(vector_ids=[vector_node.vector_id])
await self.vector_store.insert([vector_node])
# Collect tasks
tasks = self._collect_tasks()
if not tasks:
self.output = "No valid memory tasks to execute."
return
# Submit tasks to corresponding agents
agent_list = []
for i, task in enumerate(tasks):
memory_type: MemoryType = task["memory_type"]
memory_target: str = task["memory_target"]
if memory_type not in self.memory_agent_dict:
logger.warning(f"No agent found for memory_type={memory_type}")
continue
agent = self.memory_agent_dict[memory_type].copy()
agent_list.append([agent, memory_type, memory_target])
logger.info(f"Task {i}: Submitting {memory_type.value} agent with summary for target={memory_target}")
self.submit_async_task(
agent.call,
query=self.context.get("query", ""),
messages=self.context.get("messages", []),
memory_type=memory_type,
memory_target=memory_target,
description=self.context.get("description"),
ref_memory_id=self.context.get("ref_memory_id", ""),
)
await self.join_async_tasks()
# Collect results
results = []
for i, (agent, memory_type, memory_target) in enumerate(agent_list):
result_str = str(agent.output)
if agent.memory_nodes:
self.memory_nodes.extend(agent.memory_nodes)
if agent.messages:
self.messages.extend(agent.messages)
results.append(
{
"memory_type": memory_type.value,
"memory_target": memory_target,
"result": result_str[:200] + ("..." if len(result_str) > 200 else ""),
}
)
logger.info(f"Task {i}: Completed {memory_type.value} agent for target={memory_target}")
results_str = json.dumps(results, ensure_ascii=False, indent=2)
self.output = f"Successfully executed summary and {len(results)} hands-off task(s):\n{results_str}"

View file

@ -1,18 +0,0 @@
tool_multiple: |
Summarize and distribute memory tasks to appropriate agents in parallel.
Use this tool when you have already summarized the content and need to hand it off to specialized agents.
Each task will be processed by its corresponding memory agent based on memory_type.
summary_content: |
The summarized content to be stored as memory.
Should be a clear, concise summary that captures the key information.
memory_type: |
The type of memory to process. Determines which specialized agent handles the task.
memory_target: |
The target entity for this memory.
This helps the agent focus on the specific subject of the memory task.
memory_tasks: |
A list of memory tasks to distribute, each with memory_type and memory_target.

View file

@ -1,171 +0,0 @@
"""Update memories operation for vector store."""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
from ...core.schema import MemoryNode
@C.register_op()
class UpdateMemories(BaseMemoryTool):
"""Update memories by removing old ones and adding new ones in a single atomic operation.
This tool is useful for updating memories when you need to remove outdated information
and add updated information at the same time. Only supports batch mode (multiple operations).
Metadata fields can be customized via `metadata_desc` parameter.
"""
def __init__(self, add_when_to_use: bool = False, metadata_desc: dict[str, str] | None = None, **kwargs):
"""Initialize UpdateMemories.
Args:
add_when_to_use: Include when_to_use field for better retrieval. Defaults to True.
metadata_desc: Dictionary defining metadata fields and their descriptions.
**kwargs: Additional arguments for BaseMemoryTool.
"""
kwargs["enable_multiple"] = True
super().__init__(**kwargs)
self.add_when_to_use: bool = add_when_to_use
self.metadata_desc: dict[str, str] = metadata_desc or {}
def _build_item_schema(self) -> tuple[dict, list[str]]:
"""Build shared schema properties and required fields for memory items to add.
Returns:
Tuple of (properties dict, required fields list).
"""
properties = {}
required = []
if self.add_when_to_use:
properties["when_to_use"] = {
"type": "string",
"description": self.get_prompt("when_to_use"),
}
required.append("when_to_use")
properties["memory_content"] = {
"type": "string",
"description": self.get_prompt("memory_content"),
}
required.append("memory_content")
# Add metadata field if metadata_desc is provided and not empty
if self.metadata_desc:
metadata_properties = {
key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items()
}
properties["metadata"] = {
"type": "object",
"description": "metadata",
"properties": metadata_properties,
}
required.append("metadata")
return properties, required
def _build_multiple_parameters(self) -> dict:
"""Build input schema for update operation.
Only supports batch mode with both removal and addition.
"""
item_properties, required_fields = self._build_item_schema()
return {
"type": "object",
"properties": {
"memory_ids_to_delete": {
"type": "array",
"description": self.get_prompt("memory_ids_to_delete"),
"items": {"type": "string"},
},
"memories_to_add": {
"type": "array",
"description": self.get_prompt("memories_to_add"),
"items": {
"type": "object",
"properties": item_properties,
"required": required_fields,
},
},
},
"required": ["memory_ids_to_delete", "memories_to_add"],
}
def _extract_memory_data(self, mem_dict: dict) -> tuple[str, str, dict]:
"""Extract memory data from a dictionary with proper defaults.
Args:
mem_dict: Dictionary containing memory fields.
Returns:
Tuple of (memory_content, when_to_use, metadata).
"""
memory_content = mem_dict.get("memory_content", "")
when_to_use = mem_dict.get("when_to_use", "") if self.add_when_to_use else ""
metadata = mem_dict.get("metadata", {}) if self.metadata_desc else {}
return memory_content, when_to_use, metadata
async def execute(self):
"""Execute update operation: first remove old memories by IDs, then add new updated memories."""
# Get removal IDs
memory_ids_to_delete = self.context.get("memory_ids_to_delete", [])
memory_ids_to_delete = [m for m in memory_ids_to_delete if m]
# Deduplicate memory IDs to avoid redundant deletions
memory_ids_to_delete = list(dict.fromkeys(memory_ids_to_delete))
# Get memories to add
memories_to_add = self.context.get("memories_to_add", [])
# Validate input
if not memory_ids_to_delete and not memories_to_add:
self.output = "No memories to remove or add. Operation has been done."
return
removed_count = 0
added_count = 0
# Step 1: Remove old memories
if memory_ids_to_delete:
await self.vector_store.delete(vector_ids=memory_ids_to_delete)
self.memory_nodes.extend(memory_ids_to_delete)
removed_count = len(memory_ids_to_delete)
logger.info(f"Removed {removed_count} memories from vector_store.")
# Step 2: Add new updated memories
if memories_to_add:
memory_nodes: list[MemoryNode] = []
for mem in memories_to_add:
memory_content, when_to_use, metadata = self._extract_memory_data(mem)
if not memory_content:
logger.warning("Skipping memory with empty content")
continue
memory_nodes.append(self._build_memory_node(memory_content, when_to_use=when_to_use, metadata=metadata))
if memory_nodes:
# Convert to VectorNodes and collect IDs
vector_nodes = [node.to_vector_node() for node in memory_nodes]
vector_ids: list[str] = [node.vector_id for node in vector_nodes]
# Delete existing IDs (upsert behavior), then insert
await self.vector_store.delete(vector_ids=vector_ids)
await self.vector_store.insert(nodes=vector_nodes)
added_count = len(memory_nodes)
logger.info(f"Added {added_count} new memories to vector_store.")
self.memory_nodes.extend(memory_nodes)
# Generate output message
operations = []
if removed_count > 0:
operations.append(f"removed {removed_count} old memories")
if added_count > 0:
operations.append(f"added {added_count} new memories")
if operations:
self.output = f"Successfully {' and '.join(operations)} in vector_store."
else:
self.output = "Operation has been done."
logger.info(self.output)

View file

@ -1,28 +0,0 @@
tool_multiple: |
Update memories by removing outdated ones and adding new ones in a single atomic operation.
Use this tool when you need to update the memory store by:
- Removing outdated or incorrect memories
- Adding new, updated information to replace the removed memories
- Performing a batch update where old memories are replaced with new, accurate information
Memory IDs for removal can be obtained from previous memory retrieval results.
**CRITICAL**: Only add memories based on explicitly stated facts. DO NOT store inferred, assumed, or fabricated information.
memory_ids_to_delete: |
A list of unique identifiers (memory_ids) of the memories to remove.
Each ID should be a valid memory_id obtained from previous memory retrieval or addition operations.
These memories will be removed before adding the new updated memories.
**IMPORTANT**: Do NOT add duplicate memory_ids. Each memory_id should appear only once in the list.
memories_to_add: |
A list of new memory objects to add after removal.
These memories typically contain the updated information that replaces the removed memories.
when_to_use: |
When to retrieve this memory.
This field is used for vector embedding to improve retrieval accuracy by providing contextual information.
memory_content: |
The content of the memory to store.
Should be a clear, concise statement that captures the information to remember.
Keep it focused on a single piece of information for better retrieval accuracy.
**Must be strictly accurate and based only on explicitly stated facts - no inference or fabrication.**

View file

@ -1,15 +0,0 @@
from .add_memory import AddMemory
from .read_history import ReadHistory
from .read_user_profile import ReadUserProfile
from .retrieve_memory import RetrieveMemory
from .summary_and_hands_off import SummaryAndHandsOff
from .update_user_profile import UpdateUserProfile
__all__ = [
"AddMemory",
"ReadHistory",
"ReadUserProfile",
"RetrieveMemory",
"SummaryAndHandsOff",
"UpdateUserProfile",
]

View file

@ -1,67 +0,0 @@
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.schema import MemoryNode
class AddMemory(BaseMemoryTool):
def __init__(self, **kwargs):
kwargs['enable_multiple'] = True
super().__init__(**kwargs)
def _build_tool_description(self) -> str:
return "Add multiple memories to the vector store for future retrieval."
def _build_multiple_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"memories": {
"type": "array",
"description": "A list of memory objects to store.",
"items": {
"type": "object",
"properties": {
"memory_content": {
"type": "string",
"description": "memory content",
},
"conversation_time": {
"type": "string",
"description": "conversation time, e.g. '2020-01-01 00:00:00'",
}
},
"required": ["memory_content", "conversation_time"],
},
},
},
"required": ["memories"],
}
async def execute(self):
memories: list[dict] = self.context.get("memories", [])
if not memories:
self.output = "No memories provided for addition."
return
memory_nodes: list[MemoryNode] = []
for mem in memories:
memory_content = mem.get("memory_content", "")
conversation_time = mem.get("conversation_time", "")
metadata: dict = {"conversation_time": conversation_time}
try:
metadata["time_int"] = int(conversation_time.split(" ")[0].replace("-", ""))
except Exception:
...
memory_nodes.append(self._build_memory_node(memory_content, metadata=metadata))
vector_nodes = [node.to_vector_node() for node in memory_nodes]
vector_ids: list[str] = [node.vector_id for node in vector_nodes]
await self.vector_store.delete(vector_ids=vector_ids)
await self.vector_store.insert(nodes=vector_nodes)
self.memory_nodes = memory_nodes
self.output = f"Successfully added {len(memory_nodes)} memories to vector_store."
logger.info(self.output)

View file

@ -1,38 +0,0 @@
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.schema import MemoryNode
class ReadHistory(BaseMemoryTool):
def __init__(self, **kwargs):
kwargs["enable_multiple"] = False
super().__init__(**kwargs)
def _build_tool_description(self) -> str:
return "Read original history dialogue."
def _build_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"history_id": {
"type": "string",
"description": "history_id",
},
},
"required": ["history_id"],
}
async def execute(self):
history_id = self.context.get("history_id", "")
nodes = await self.vector_store.get(vector_ids=[history_id])
if not nodes:
self.output = f"No history: {history_id}"
logger.warning(self.output)
return
memory = MemoryNode.from_vector_node(nodes[0])
self.output = memory.content
logger.info(f"Successfully read history memory: {history_id}")

View file

@ -1,65 +0,0 @@
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.schema.memory_node import MemoryNode
class ReadUserProfile(BaseMemoryTool):
def __init__(self, add_memory_type_target: bool = True, **kwargs):
kwargs["enable_multiple"] = False
self.add_memory_type_target = add_memory_type_target
super().__init__(**kwargs)
def _build_tool_description(self) -> str:
return "Read personal memory profile for the current user."
def _build_parameters(self) -> dict:
if self.add_memory_type_target:
return {
"type": "object",
"properties": {
"memory_type": {
"type": "string",
"description": "memory_type",
},
"memory_target": {
"type": "string",
"description": "memory_target",
},
},
"required": ["memory_type", "memory_target"],
}
else:
return {
"type": "object",
"properties": {},
"required": [],
}
async def execute(self):
cache_key = f"{self.memory_type}_{self.memory_target}"
cached_data = self.meta_memory.load(cache_key, auto_clean=False)
if not cached_data:
self.output = f"Local memory not found: {self.memory_type}_{self.memory_target}"
logger.info(self.output)
return
# Convert to MemoryNode objects and sort by conversation_time (oldest first)
memory_nodes = [MemoryNode(**node_data) for node_data in cached_data]
memory_nodes.sort(
key=lambda n: n.metadata.get("conversation_time", "")
)
memory_formated = []
for node in memory_nodes:
node_formated = f"profile_id={node.memory_id} profile_content={node.content}"
if "conversation_time" in node.metadata:
node_formated += f" conversation_time={node.metadata['conversation_time']}"
if node.ref_memory_id:
node_formated += f" history_id={node.ref_memory_id}"
memory_formated.append(node_formated.strip())
self.output = "\n".join(memory_formated)
logger.info(f"Read {len(memory_formated)} nodes from cache key: {cache_key}")

View file

@ -1,84 +0,0 @@
import json
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.schema import MemoryNode
from ...core.utils import deduplicate_memories
class RetrieveMemory(BaseMemoryTool):
def __init__(self, top_k: int = 20, **kwargs):
super().__init__(**kwargs)
self.top_k: int = top_k
def _build_tool_description(self) -> str:
return "Retrieve memories using vector similarity search."
def _build_multiple_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"query_items": {
"type": "array",
"description": "query_items",
"items": {
"type": "object",
"properties": {
"memory_type": {
"type": "string",
"description": "memory_type",
},
"memory_target": {
"type": "string",
"description": "memory_target",
},
"query": {
"type": "string",
"description": "query",
},
"time_range": {
"type": "string",
"description": "time_range(optional), e.g. [20200101, 20200101]",
},
},
"required": ["memory_type", "memory_target", "query"],
},
},
},
"required": ["query_items"],
}
async def execute(self):
query_items: list[dict] = self.context.get("query_items", [])
memory_nodes: list[MemoryNode] = []
for query_item in query_items:
memory_type = query_item.get("memory_type")
memory_target = query_item.get("memory_target")
query = query_item.get("query")
time_range = query_item.get("time_range", "")
filter_dict = {
"memory_type": memory_type,
"memory_target": memory_target,
}
if time_range:
time_range = json.loads(time_range)
filter_dict["time_range"] = [int(time_range[0]), int(time_range[1])]
nodes = await self.vector_store.search(query=query, limit=self.top_k, filters=filter_dict)
memory_nodes.extend([MemoryNode.from_vector_node(n) for n in nodes])
memory_nodes = deduplicate_memories(memory_nodes)
retrieved_memory_ids = {node.memory_id for node in self.retrieved_nodes if node.memory_id}
new_memory_nodes = [node for node in memory_nodes if node.memory_id not in retrieved_memory_ids]
self.retrieved_nodes.extend(new_memory_nodes)
self.memory_nodes = new_memory_nodes
if not new_memory_nodes:
self.output = "No new memory_nodes found matching the query (duplicates removed)."
else:
self.output = "\n".join([f"{m.metadata['conversation_time']} {m.content}" for m in new_memory_nodes])
logger.info(f"Retrieved {len(memory_nodes)} memory_nodes, {len(new_memory_nodes)} new after deduplication")

View file

@ -1,140 +0,0 @@
import json
from typing import TYPE_CHECKING
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.enumeration import MemoryType
from ...core.schema import MemoryNode, Message
if TYPE_CHECKING:
from ...mem_agent import BaseMemoryAgent
class SummaryAndHandsOff(BaseMemoryTool):
def __init__(self, memory_agents: list["BaseMemoryAgent"], **kwargs):
kwargs["enable_multiple"] = True
kwargs["sub_ops"] = memory_agents or []
super().__init__(**kwargs)
from ...mem_agent import BaseMemoryAgent
self.sub_ops: list[BaseMemoryAgent] = [a for a in self.sub_ops if isinstance(a, BaseMemoryAgent)]
self.messages: list[Message] = []
@property
def memory_agent_dict(self) -> dict[MemoryType, "BaseMemoryAgent"]:
return {a.memory_type: a for a in self.sub_ops}
def _build_tool_description(self) -> str:
return "Summarize and distribute memory tasks to appropriate agents."
def _build_multiple_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"summary_content": {
"type": "string",
"description": "summary content",
},
"memory_tasks": {
"type": "array",
"description": "memory_tasks",
"items": {
"type": "object",
"properties": {
"memory_type": {
"type": "string",
"description": "memory_type",
"enum": [k.value for k in self.memory_agent_dict],
},
"memory_target": {
"type": "string",
"description": "memory_target",
},
},
"required": ["memory_type", "memory_target"],
},
},
},
"required": ["summary_content", "memory_tasks"],
}
@staticmethod
def _parse_memory_type_target(task: dict):
return {
"memory_type": MemoryType(task.get("memory_type", "")),
"memory_target": task.get("memory_target", ""),
}
def _collect_tasks(self) -> list[dict]:
tasks = []
for task in self.context.get("memory_tasks", []):
tasks.append(self._parse_memory_type_target(task))
return tasks
async def execute(self):
summary_content = self.context.get("summary_content", "")
assert summary_content, "No summary content provided."
summary_node = MemoryNode(
memory_type=MemoryType.HISTORY,
memory_target="",
when_to_use=summary_content,
content=self.messages_formated,
ref_memory_id="",
author=self.author,
metadata={},
)
logger.info(f"Adding summary node: {summary_node.model_dump_json(indent=2, exclude_none=True)}")
self.memory_nodes.append(summary_node)
vector_node = summary_node.to_vector_node()
await self.vector_store.delete(vector_ids=[vector_node.vector_id])
await self.vector_store.insert([vector_node])
tasks = self._collect_tasks()
if not tasks:
self.output = "No valid memory tasks to execute."
return
agent_list = []
for i, task in enumerate(tasks):
memory_type: MemoryType = task["memory_type"]
memory_target: str = task["memory_target"]
if memory_type not in self.memory_agent_dict:
logger.warning(f"No agent found for memory_type={memory_type}")
continue
agent = self.memory_agent_dict[memory_type].copy()
agent_list.append([agent, memory_type, memory_target])
logger.info(f"Task {i}: Submitting {memory_type.value} agent for target={memory_target}")
self.submit_async_task(
agent.call,
query=self.context.get("query", ""),
messages=self.context.get("messages", []),
memory_type=memory_type,
memory_target=memory_target,
description=self.context.get("description"),
ref_memory_id=self.context.get("ref_memory_id", ""),
)
await self.join_async_tasks()
results = []
for i, (agent, memory_type, memory_target) in enumerate(agent_list):
result_str = str(agent.output)
if agent.memory_nodes:
self.memory_nodes.extend(agent.memory_nodes)
if agent.messages:
self.messages.extend(agent.messages)
results.append({
"memory_type": memory_type.value,
"memory_target": memory_target,
"result": result_str[:100] + ("..." if len(result_str) > 100 else ""),
})
logger.info(f"Task {i}: Completed {memory_type.value} agent for target={memory_target}")
results_str = json.dumps(results, ensure_ascii=False, indent=2)
self.output = f"Successfully executed summary and {len(results)} hands-off task(s):\n{results_str}"

View file

@ -1,99 +0,0 @@
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.schema.memory_node import MemoryNode
class UpdateUserProfile(BaseMemoryTool):
def __init__(self, **kwargs):
kwargs["enable_multiple"] = True
super().__init__(**kwargs)
def _build_tool_description(self) -> str:
return "Update user profile."
def _build_multiple_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"profile_ids_to_delete": {
"type": "array",
"description": "profile_ids_to_delete",
"items": {"type": "string"},
},
"profiles_to_add": {
"type": "array",
"description": "profiles_to_add",
"items": {
"type": "object",
"properties": {
"profile_content": {
"type": "string",
"description": "profile_content",
},
"conversation_time": {
"type": "string",
"description": "conversation_time, e.g. '2020-01-01 00:00:00'",
},
},
"required": ["profile_content", "conversation_time"],
},
},
},
"required": ["profile_ids_to_delete", "profiles_to_add"],
}
async def execute(self):
profile_ids_to_delete = self.context.get("profile_ids_to_delete", [])
profile_ids_to_delete = [m for m in profile_ids_to_delete if m]
profile_ids_to_delete = list(dict.fromkeys(profile_ids_to_delete))
profiles_to_add = self.context.get("profiles_to_add", [])
if not profile_ids_to_delete and not profiles_to_add:
self.output = "No memories to remove or add. Operation has been done."
return
cache_key = f"{self.memory_type}_{self.memory_target}"
cached_data = self.meta_memory.load(cache_key, auto_clean=False)
existing_memory_nodes = []
if cached_data:
existing_memory_nodes = [MemoryNode(**node_data) for node_data in cached_data]
removed_count = 0
added_count = 0
if profile_ids_to_delete:
profile_ids_set = set(profile_ids_to_delete)
existing_memory_nodes = [node for node in existing_memory_nodes if node.memory_id not in profile_ids_set]
removed_count = len(profile_ids_to_delete)
logger.info(f"Removed {removed_count} memories from user profile.")
new_memory_nodes = []
if profiles_to_add:
for mem in profiles_to_add:
profile_content = mem.get("profile_content", "")
conversation_time = mem.get("conversation_time", "")
new_memory_nodes.append(self._build_memory_node(
memory_content=profile_content,
when_to_use="",
metadata={"conversation_time": conversation_time}
))
logger.info(f"Added {len(new_memory_nodes)} new memories to user profile.")
updated_memory_nodes = existing_memory_nodes + new_memory_nodes
nodes_data = [node.model_dump(exclude_none=True) for node in updated_memory_nodes]
self.meta_memory.save(cache_key, nodes_data)
operations = []
if removed_count > 0:
operations.append(f"removed {removed_count} old memories")
if added_count > 0:
operations.append(f"added {added_count} new memories")
if operations:
self.output = f"Successfully {' and '.join(operations)} in user profile."
else:
self.output = "Operation has been done."
logger.info(self.output)

View file

@ -1,15 +0,0 @@
from .add_summary_memory import AddSummaryMemory
from .hands_off import HandsOff
from .read_history import ReadHistory
from .read_user_profile import ReadUserProfile
from .retrieve_memory import RetrieveMemory
from .update_user_profile import UpdateUserProfile
__all__ = [
"AddSummaryMemory",
"HandsOff",
"ReadHistory",
"ReadUserProfile",
"RetrieveMemory",
"UpdateUserProfile",
]

View file

@ -1,63 +0,0 @@
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.schema import MemoryNode
class AddSummaryMemory(BaseMemoryTool):
def __init__(self, **kwargs):
kwargs["enable_multiple"] = False
super().__init__(**kwargs)
def _build_tool_description(self) -> str:
return "Add a summary memory to the vector store for future retrieval."
@staticmethod
def _build_item_schema() -> tuple[dict, list[str]]:
properties = {
"conversation_time": {"type": "string", "description": "conversation time, e.g. '2020-01-01 00:00:00'"},
"summary_memory": {"type": "string", "description": "summary_memory"},
}
return properties, ["conversation_time", "summary_memory"]
def _build_parameters(self) -> dict:
properties, required = self._build_item_schema()
return {
"type": "object",
"properties": properties,
"required": required,
}
async def execute(self):
summary_memory = self.context.get("summary_memory", "")
conversation_time = self.context.get("conversation_time", "")
if not summary_memory:
self.output = "No summary_memory provided for addition."
return
metadata: dict = {"conversation_time": conversation_time}
try:
metadata["time_int"] = int(conversation_time.split(" ")[0].replace("-", ""))
except Exception:
pass
memory_node = MemoryNode(
memory_type=self.memory_type,
memory_target=self.memory_target,
when_to_use="",
content=summary_memory,
ref_memory_id=self.history_node.memory_id,
author=self.author,
metadata=metadata,
)
vector_node = memory_node.to_vector_node()
vector_id = vector_node.vector_id
await self.vector_store.delete(vector_ids=[vector_id])
await self.vector_store.insert(nodes=[vector_node])
self.memory_nodes.append(memory_node)
self.output = f"Successfully added summary memory to vector_store."
logger.info(self.output)

View file

@ -1,115 +0,0 @@
from typing import TYPE_CHECKING
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.enumeration import MemoryType
from ...core.schema import Message
if TYPE_CHECKING:
from ...mem_agent import BaseMemoryAgent
class HandsOff(BaseMemoryTool):
def __init__(self, memory_agents: list["BaseMemoryAgent"], **kwargs):
kwargs["enable_multiple"] = True
kwargs["sub_ops"] = memory_agents or []
super().__init__(**kwargs)
from ...mem_agent import BaseMemoryAgent
self.sub_ops: list[BaseMemoryAgent] = [a for a in self.sub_ops if isinstance(a, BaseMemoryAgent)]
self.messages: list[Message] = []
self.meta_info_dict: dict[str, str] = {}
@property
def memory_agent_dict(self) -> dict[MemoryType, "BaseMemoryAgent"]:
return {a.memory_type: a for a in self.sub_ops}
def _build_tool_description(self) -> str:
return "Distribute memory tasks to appropriate memory agents."
def _build_multiple_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"memory_tasks": {
"type": "array",
"description": "List of memory tasks to distribute to specific agents",
"items": {
"type": "object",
"properties": {
"memory_type": {
"type": "string",
"description": "Type of memory to handle",
"enum": [k.value for k in self.memory_agent_dict],
},
"memory_target": {
"type": "string",
"description": "Target or context for the memory operation",
},
},
"required": ["memory_type", "memory_target"],
},
},
},
"required": ["memory_tasks"],
}
async def execute(self):
tasks = []
seen = set()
for task in self.context.get("memory_tasks", []):
memory_type = MemoryType(task.get("memory_type", ""))
memory_target = task.get("memory_target", "")
# Deduplicate tasks with same memory_type and memory_target
task_key = (memory_type, memory_target)
if task_key in seen:
logger.info(f"Skipping duplicate task: memory_type={memory_type.value}, memory_target={memory_target}")
continue
seen.add(task_key)
tasks.append({
"memory_type": memory_type,
"memory_target": memory_target,
})
if not tasks:
self.output = "No valid memory tasks to execute."
return
agent_list = []
for i, task in enumerate(tasks):
memory_type: MemoryType = task["memory_type"]
memory_target: str = task["memory_target"]
agent = self.memory_agent_dict[memory_type].copy()
agent_list.append([agent, memory_type, memory_target])
logger.info(f"Task {i}: Submitting {memory_type.value} agent for target={memory_target}")
self.submit_async_task(
agent.call,
memory_type=memory_type,
memory_target=memory_target,
query=self.context.get("query", ""),
messages=self.context.get("messages", []),
description=self.context.get("description"),
history_node=self.context.get("history_node"),
)
await self.join_async_tasks()
results = []
for i, (agent, memory_type, memory_target) in enumerate(agent_list):
if agent.memory_nodes:
self.memory_nodes.extend(agent.memory_nodes)
if agent.messages:
self.messages.extend(agent.messages)
if agent.meta_info:
self.meta_info_dict[f"{memory_type.value} {memory_target}"] = agent.meta_info
results.append(f"{memory_type.value} {memory_target} agent result: {agent.output}")
self.output = "\n".join(results)
logger.info(f"Completed {len(results)} hands-off task(s):\n{self.output}")

View file

@ -1,103 +0,0 @@
import json
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.schema import MemoryNode
from ...core.utils import deduplicate_memories
class RetrieveMemory(BaseMemoryTool):
def __init__(self, top_k: int = 20, **kwargs):
super().__init__(**kwargs)
self.top_k: int = top_k
def _build_tool_description(self) -> str:
return "Retrieve memories using vector similarity search."
def _build_multiple_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"query_items": {
"type": "array",
"description": "query_items",
"items": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "query",
},
"time_range": {
"type": "string",
"description": "time_range(optional), e.g. [20200101, 20200101]",
},
},
"required": ["query"],
},
},
},
"required": ["query_items"],
}
async def execute(self):
query_items: list[dict] = self.context.get("query_items", [])
memory_nodes: list[MemoryNode] = []
for query_item in query_items:
query = query_item.get("query")
time_range = query_item.get("time_range", "")
filter_dict: dict = {
"memory_type": self.memory_type.value,
"memory_target": self.memory_target,
}
if time_range:
# Handle different time_range formats
if isinstance(time_range, str):
try:
time_range = json.loads(time_range)
except json.JSONDecodeError:
# If it's a plain string like "20250907", treat it as a single date
time_range = time_range
# Convert to list format [start, end]
if isinstance(time_range, (list, tuple)):
if len(time_range) == 1:
# Single element list, use it for both start and end
filter_dict["time_int"] = [int(time_range[0]), int(time_range[0])]
else:
# Two element list/tuple
filter_dict["time_int"] = [int(time_range[0]), int(time_range[1])]
else:
# Single value (int or string), use it for both start and end
filter_dict["time_int"] = [int(time_range), int(time_range)]
logger.info(f"memory_type={self.memory_type} memory_target={self.memory_target} query={query} "
f"filter_dict={filter_dict}")
nodes = await self.vector_store.search(query=query, limit=self.top_k, filters=filter_dict)
memory_nodes.extend([MemoryNode.from_vector_node(n) for n in nodes])
memory_nodes = deduplicate_memories(memory_nodes)
retrieved_memory_ids = {node.memory_id for node in self.retrieved_nodes if node.memory_id}
new_memory_nodes = [node for node in memory_nodes if node.memory_id not in retrieved_memory_ids]
self.retrieved_nodes.extend(new_memory_nodes)
self.memory_nodes = new_memory_nodes
if not new_memory_nodes:
self.output = "No new memory_nodes found matching the query (duplicates removed)."
else:
output = []
for node in new_memory_nodes:
line = ""
if "conversation_time" in node.metadata and node.metadata["conversation_time"]:
line += f"conversation_time={node.metadata['conversation_time']} "
line += node.content.strip() + " "
if node.ref_memory_id:
line += f"history_id={node.ref_memory_id} "
output.append(line.strip())
self.output = "### Extracted Memories\n" + "\n".join(output)
logger.info(f"Retrieved {len(memory_nodes)} memory_nodes, {len(new_memory_nodes)} new after deduplication")

View file

@ -1,165 +0,0 @@
"""Add memory operation for vector store."""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
from ...core.schema import MemoryNode
@C.register_op()
class AddMemory(BaseMemoryTool):
"""Add memories to vector store with optional when_to_use and custom metadata fields.
Supports single/multiple addition modes via `enable_multiple` parameter.
Metadata fields can be customized via `metadata_desc` parameter.
"""
def __init__(self, add_when_to_use: bool = False, metadata_desc: dict[str, str] | None = None, **kwargs):
"""Initialize AddMemory.
Args:
add_when_to_use: Include when_to_use field for better retrieval.
metadata_desc: Dictionary defining metadata fields and their descriptions.
Example:
{
"year": "The `year` information associated with the memory(Optional)",
"month": "The `month` information associated with the memory(Optional)",
"day": "The `day` information associated with the memory(Optional)",
"hour": "The `hour` information associated with the memory(Optional)",
}
If None or empty dict, metadata field will not be included.
**kwargs: Additional arguments for BaseMemoryTool.
"""
super().__init__(**kwargs)
self.add_when_to_use: bool = add_when_to_use
self.metadata_desc: dict[str, str] = metadata_desc or {}
def _build_item_schema(self) -> tuple[dict, list[str]]:
"""Build shared schema properties and required fields for memory items.
Returns:
Tuple of (properties dict, required fields list).
"""
properties = {}
required = []
if self.add_when_to_use:
properties["when_to_use"] = {
"type": "string",
"description": self.get_prompt("when_to_use"),
}
required.append("when_to_use")
properties["memory_content"] = {
"type": "string",
"description": self.get_prompt("memory_content"),
}
required.append("memory_content")
# Add metadata field if metadata_desc is provided and not empty
if self.metadata_desc:
metadata_properties = {
key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items()
}
# Generate dynamic description based on metadata_desc fields
field_descriptions = "\n".join([f" - {key}: {desc}" for key, desc in self.metadata_desc.items()])
metadata_description = f"Optional metadata for the memory. Available fields:\n{field_descriptions}"
properties["metadata"] = {
"type": "object",
"description": metadata_description,
"properties": metadata_properties,
}
required.append("metadata")
return properties, required
def _build_parameters(self) -> dict:
"""Build input schema for single memory addition."""
properties, required = self._build_item_schema()
return {
"type": "object",
"properties": properties,
"required": required,
}
def _build_multiple_parameters(self) -> dict:
"""Build input schema for multiple memory addition."""
item_properties, required_fields = self._build_item_schema()
return {
"type": "object",
"properties": {
"memories": {
"type": "array",
"description": self.get_prompt("memories"),
"items": {
"type": "object",
"properties": item_properties,
"required": required_fields,
},
},
},
"required": ["memories"],
}
def _extract_memory_data(self, mem_dict: dict) -> tuple[str, str, dict]:
"""Extract memory data from a dictionary with proper defaults.
Args:
mem_dict: Dictionary containing memory fields.
Returns:
Tuple of (memory_content, when_to_use, metadata).
"""
memory_content = mem_dict.get("memory_content", "")
when_to_use = mem_dict.get("when_to_use", "") if self.add_when_to_use else ""
# Only extract metadata if metadata_desc is configured
# Convert all metadata values to strings
metadata = {}
if self.metadata_desc:
raw_metadata = mem_dict.get("metadata", {})
metadata = {key: str(value).strip() for key, value in raw_metadata.items() if value}
return memory_content, when_to_use, metadata
async def execute(self):
"""Execute addition: delete existing IDs (upsert), then insert new memories."""
memory_nodes: list[MemoryNode] = []
if self.enable_multiple:
memories: list[dict] = self.context.get("memories", [])
if not memories:
self.output = "No memories provided for addition."
return
for mem in memories:
memory_content, when_to_use, metadata = self._extract_memory_data(mem)
if not memory_content:
logger.warning("Skipping memory with empty content")
continue
memory_nodes.append(self._build_memory_node(memory_content, when_to_use=when_to_use, metadata=metadata))
else:
memory_content, when_to_use, metadata = self._extract_memory_data(self.context)
if not memory_content:
self.output = "No memory content provided for addition."
return
memory_nodes.append(self._build_memory_node(memory_content, when_to_use=when_to_use, metadata=metadata))
if not memory_nodes:
self.output = "No valid memories provided for addition."
return
# Convert to VectorNodes and collect IDs
vector_nodes = [node.to_vector_node() for node in memory_nodes]
vector_ids: list[str] = [node.vector_id for node in vector_nodes]
# Delete existing IDs (upsert behavior), then insert
await self.vector_store.delete(vector_ids=vector_ids)
await self.vector_store.insert(nodes=vector_nodes)
self.memory_nodes = memory_nodes
self.output = f"Successfully added {len(memory_nodes)} memories to vector_store."
logger.info(self.output)

View file

@ -1,29 +0,0 @@
tool: |
Add a memory to the vector store for future retrieval.
Use this tool to store important information that should be remembered, such as:
- Meta information: "I am very happy"
- Personal preferences: "John prefers dark mode", "Alice works in PST timezone"
- Procedural knowledge: "To deploy, run build then push", "Always validate input before processing"
**CRITICAL**: Only add memories based on explicitly stated facts. DO NOT store inferred, assumed, or fabricated information.
tool_multiple: |
Add multiple memories to the vector store for future retrieval.
Use this tool to store multiple pieces of important information in a single operation.
Each memory can include when_to_use conditions and metadata for better organization and retrieval.
Examples: storing multiple user preferences, multiple procedural steps, or multiple tool usage tips.
**CRITICAL**: Only add memories based on explicitly stated facts. DO NOT store inferred, assumed, or fabricated information in any of the memory entries.
when_to_use: |
Optional condition description for when to retrieve this memory.
This field is used for vector embedding to improve retrieval accuracy by providing contextual information.
memory_content: |
The content of the memory to store.
Should be a clear, concise statement that captures the information to remember.
Keep it focused on a single piece of information for better retrieval accuracy.
**Must be strictly accurate and based only on explicitly stated facts - no inference or fabrication.**
memories: |
A list of memory objects to store.

View file

@ -1,100 +0,0 @@
"""Add summary memory operation for vector store."""
from loguru import logger
from .add_memory import AddMemory
from ...core.context import C
from ...core.enumeration import MemoryType
from ...core.schema import MemoryNode
@C.register_op()
class AddSummaryMemory(AddMemory):
"""Add LLM-summarized memories to vector store.
Differences from AddMemory:
- Single memory mode only (enable_multiple=False)
- Uses 'summary_memory' parameter instead of 'memory_content'
- No when_to_use field (add_when_to_use=False)
- Metadata fields can be customized via `metadata_desc` parameter
"""
def __init__(self, metadata_desc: dict[str, str] | None = None, **kwargs):
"""Initialize AddSummaryMemory.
Args:
metadata_desc: Dictionary defining metadata fields and their descriptions.
**kwargs: Additional arguments for AddMemory.
"""
# Force single mode and disable when_to_use
kwargs["enable_multiple"] = False
kwargs["add_when_to_use"] = False
super().__init__(metadata_desc=metadata_desc, **kwargs)
def _build_parameters(self) -> dict:
"""Build input schema for summary memory addition."""
properties = {
"summary_memory": {
"type": "string",
"description": self.get_prompt("summary_memory"),
},
}
required = ["summary_memory"]
# Add metadata field if metadata_desc is provided and not empty
if self.metadata_desc:
metadata_properties = {
key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items()
}
# Generate dynamic description based on metadata_desc fields
field_descriptions = "\n".join([f" - {key}: {desc}" for key, desc in self.metadata_desc.items()])
metadata_description = f"Optional metadata for the memory. Available fields:\n{field_descriptions}"
properties["metadata"] = {
"type": "object",
"description": metadata_description,
"properties": metadata_properties,
}
required.append("metadata")
return {
"type": "object",
"properties": properties,
"required": required,
}
def _build_memory_node(
self,
memory_content: str,
memory_type: MemoryType | None = None,
memory_target: str = "",
ref_memory_id: str = "",
when_to_use: str = "",
author: str = "",
metadata: dict | None = None,
) -> MemoryNode:
"""Build MemoryNode from content, when_to_use, and metadata."""
node = MemoryNode(
memory_type=MemoryType.HISTORY,
memory_target="",
when_to_use=memory_content,
content=self.messages_formated,
ref_memory_id="",
author=self.author,
metadata=metadata or {},
)
logger.info(f"Adding summary memory: {node.model_dump_json(indent=2, exclude_none=True)}")
return node
async def execute(self):
"""Execute addition: map summary_memory to memory_content and call parent."""
# Map summary_memory to memory_content
summary_memory = self.context.get("summary_memory", "")
if not summary_memory:
self.output = "No summary memory content provided for addition."
logger.warning(self.output)
return
self.context["memory_content"] = summary_memory
await super().execute()

Some files were not shown because too many files have changed in this diff Show more