Merge pull request #85 from agentscope-ai/dev_0123

Dev 0123
This commit is contained in:
jinliyl 2026-01-26 17:41:49 +08:00 • committed by GitHub
commit b02d3ab269
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
146 changed files with 1675 additions and 7309 deletions

1
.gitignore vendored
View file

@ -34,6 +34,7 @@ test_compact_storage/*
test_working_memory/*
*.code-workspace
local_vector_store/*
reme_local_memory/*
chroma_vector_store/*
bench_results/*
meta_memory/*

View file

@ -5,7 +5,7 @@ from . import config
from . import core
from . import tool
from . import workflow
from .reme_app import ReMeApp
from .reme import ReMe
__all__ = [
"agent",
@ -13,7 +13,7 @@ __all__ = [
"core",
"tool",
"workflow",
"ReMeApp",
"ReMe",
]
__version__ = "0.3.0.0a1"

View file

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

View file

@ -0,0 +1,75 @@
"""Base memory agent for handling memory operations with tool-based reasoning."""
from abc import ABCMeta
from typing import Literal
from loguru import logger
from ...core.enumeration import MemoryType
from ...core.op import BaseReact
from ...core.schema import MemoryNode
class BaseMemoryAgent(BaseReact, metaclass=ABCMeta):
"""Base class for memory agents that handle memory operations with tool-based reasoning."""
memory_type: MemoryType | None = None
@staticmethod
async def read_meta_memories(meta_memories: list[dict]) -> str:
"""Read and format meta memory information from the provided metadata list."""
from ...tool.memory import ReadMetaMemory
meta_memory_info = ReadMetaMemory().format_memory_metadata(meta_memories)
logger.info(f"meta_memory_info={meta_memory_info}")
return meta_memory_info
async def read_user_profile(self, show_id: Literal["profile", "history"] = "profile") -> str:
"""Read current user profile."""
from ...tool.memory import ReadUserProfile
read_tool = ReadUserProfile(show_id=show_id)
await read_tool.call(memory_target=self.memory_target, service_context=self.service_context)
return str(read_tool.response.answer)
async def add_history_node(self) -> MemoryNode:
"""Add history node"""
from ...tool.memory import AddHistory
add_history_tool = AddHistory()
await add_history_tool.call(
messages=self.messages,
description=self.description,
service_context=self.service_context,
)
return add_history_tool.context.history_node
@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,13 @@
"""Default memory agents for personal and ReMe memory operations."""
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

@ -0,0 +1,61 @@
"""Personal memory retriever agent for retrieving personal memories through vector search."""
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role, MemoryType
from ....core.op import BaseTool
from ....core.schema import Message, MemoryNode
from ....core.utils import format_messages
class PersonalRetriever(BaseMemoryAgent):
"""Retrieve personal memories through vector search and history reading."""
memory_type: MemoryType = MemoryType.PERSONAL
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.retrieved_nodes: list[MemoryNode] = []
async def build_messages(self) -> list[Message]:
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = self.description + "\n" + format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
return [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
prompt_name="system_prompt",
memory_type=self.memory_type.value,
memory_target=self.memory_target,
user_profile=await self.read_user_profile(show_id="history"),
context=context.strip(),
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message"),
),
]
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
"""Execute tool calls with memory context."""
return await super()._acting_step(
assistant_message,
tools,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
retrieved_nodes=self.retrieved_nodes,
**kwargs,
)

View file

@ -0,0 +1,48 @@
system_prompt: |
You are a memory agent managing **{memory_type}** memories about **{memory_target}**.
## User Profile
{user_profile}
## Question
{context}
## Retrieval Strategy
**Tool 1: Vector Search (`retrieve_memory`)**
- Purpose: Search for relevant memories using semantic similarity
- Try at least 3-5 different queries before moving to next tool:
* Direct question reformulation
* Different phrasings and perspectives
* Entity-focused queries (names, places, events)
* Various keyword combinations
- Time range filtering (optional):
* Format: single date '20200101' or range '20200101,20200102'
* Example: '20200101,20200102' for 20200101 <= time <= 20200102
* Single-sided: '0,20200102' (before date) or '20200101,99999999' (after date)
- If no results: retry with different time ranges or remove time constraints
**Tool 2: Read History (`read_history`) - ONLY AFTER Tool 1**
- Purpose: Read full original conversation context
- Use this ONLY after completing multiple retrieve_memory attempts
- Extract history_id from retrieved memory results
- Prioritize most relevant or recent history entries
- Read multiple histories if needed for complete understanding
## Response Requirements
- Answer ONLY based on retrieved memories and user profile - NO hallucination or inference
- Always cite the source: reference specific memories with their timestamps
- If information conflicts, present all versions with their respective times
- Try multiple search angles before concluding no information exists
## Output Format
When answering, structure your response as follows:
- [timestamp][Relevant memory content from search results]
- [timestamp][Relevant user profile information]
If no relevant information found after thorough search (5+ queries), state:
"No relevant information found after thorough search using multiple query strategies."
user_message: |
Answer the question following the retrieval strategy and response requirements above.

View file

@ -0,0 +1,103 @@
"""Personal memory summarizer agent for two-phase personal memory processing."""
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role, MemoryType
from ....core.op import BaseTool
from ....core.schema import Message, MemoryNode
class PersonalSummarizer(BaseMemoryAgent):
"""Two-phase personal memory processor: retrieve/add memories then update profile."""
memory_type: MemoryType = MemoryType.PERSONAL
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.retrieved_nodes: list[MemoryNode] = []
async def _build_phase1_messages(self) -> list[Message]:
"""Build messages for phase 1: retrieve and add memory."""
return [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
prompt_name="system_prompt_phase1",
context=self.context.history_node.content,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message_phase1"),
),
]
async def _build_phase2_messages(self) -> list[Message]:
"""Build messages for phase 2: update user profile."""
return [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
prompt_name="system_prompt_phase2",
context=self.context.history_node.content,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
user_profile=await self.read_user_profile(show_id="profile"),
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message_phase2"),
),
]
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
"""Execute tool calls with memory context."""
return await super()._acting_step(
assistant_message,
tools,
step,
stage=stage,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
history_node=self.history_node,
author=self.author,
retrieved_nodes=self.retrieved_nodes,
**kwargs,
)
async def execute(self):
"""Execute two-phase memory processing: retrieve/add -> update profile."""
tools = self.tools
for i, tool in enumerate(tools):
logger.info(f"[{self.__class__.__name__}] tool_call[{i}]={tool.tool_call.simple_input_dump(as_dict=False)}")
messages_phase1 = await self._build_phase1_messages()
for i, message in enumerate(messages_phase1):
role = message.name or message.role
logger.info(f"[{self.__class__.__name__}-S1] role={role} {message.simple_dump(as_dict=False)}")
tools_phase1, messages_phase1, success_phase1 = await self.react(messages_phase1, tools[:-1], stage="S1")
messages_phase2 = await self._build_phase2_messages()
for i, message in enumerate(messages_phase2):
role = message.name or message.role
logger.info(f"[{self.__class__.__name__}-S2] role={role} {message.simple_dump(as_dict=False)}")
tools_phase2, messages_phase2, success_phase2 = await self.react(messages_phase2, tools[-1:], stage="S2")
return {
"answer": (messages_phase1[-1].content if success_phase1 else "")
+ (messages_phase2[-1].content if success_phase2 else ""),
"success": success_phase1 and success_phase2,
"messages": messages_phase1 + messages_phase2,
"tools": tools_phase1 + tools_phase2,
}

View file

@ -0,0 +1,45 @@
system_prompt_phase1: |
You are a memory agent managing **{memory_type}** memories about **{memory_target}**.
## Latest Conversation:
Message format: `round<index> [<timestamp>] <role/name>: <content>` (timestamp: YYYY-MM-DD HH:MM:SS).
{context}
## Task: Retrieve Similar Memories and Add New Memories
**CRITICAL**: Extract ONLY explicitly stated information. DO NOT infer, assume, or fabricate.
### Step 1: Retrieve Similar Memories
Use `retrieve_memory` to search for existing similar memories about **{memory_target}**.
- Use appropriate queries to find relevant existing memories
- Check if new information already exists in the memory store
### Step 2: Add New Memories
Use `add_memory` to add new memories:
- Extract and summarize important information about **{memory_target}**
- Set `conversation_time` (format: 2020-01-01 00:00:00; use 0000-00-00 00:00:00 if unavailable)
- If the information is completely identical to existing memory, skip adding
user_message_phase1: |
First retrieve similar memories, then extract and add new personal memories from the conversation.
system_prompt_phase2: |
You are a memory agent managing **{memory_type}** memories about **{memory_target}**.
## Latest Conversation:
Message format: `round<index> [<timestamp>] <role/name>: <content>` (timestamp: YYYY-MM-DD HH:MM:SS).
{context}
## Current User Profile:
UserProfile format: `profile_id=<id> conversation_time=<timestamp> <content>`.
{user_profile}
## Task: Update Profile with `UpdateUserProfile`
**CRITICAL**: Extract ONLY explicitly stated information. DO NOT infer, assume, or fabricate.
Synchronize profile/memories with new information from the conversation, including **{memory_target}**' current status:
- `profile_ids_to_delete`: Remove outdated, conflicting, or redundant entries.
- `profiles_to_add`: Add new profiles/memories with `conversation_time`, e.g. `YYYY-MM-DD HH:MM:SS`, {memory_target} did something.
- Maintain profiles that are concise, mutually exclusive, and collectively comprehensive with no information loss.
user_message_phase2: |
Update user profile using `UpdateUserProfile` based on the conversation and current profile.

View file

@ -0,0 +1,80 @@
"""ReMe retriever agent that orchestrates multiple memory agents to retrieve information."""
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role
from ....core.op import BaseTool
from ....core.schema import Message
from ....core.utils import format_messages
class ReMeRetriever(BaseMemoryAgent):
"""Orchestrate multiple memory agents to retrieve information."""
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
async def build_messages(self) -> list[Message]:
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = self.description + "\n" + format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
return [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self.read_meta_memories(self.meta_memories),
context=context.strip(),
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message"),
),
]
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
return await super()._acting_step(
assistant_message,
tools,
step,
description=self.description,
messages=self.messages,
query=self.query,
author=self.author,
**kwargs,
)
async def execute(self):
result = await super().execute()
tools: list[BaseTool] = result["tools"]
hands_off_tool = tools[0]
agents: list[BaseMemoryAgent] = hands_off_tool.response.metadata["agents"]
answer = ""
success = True
messages = []
tools = []
for agent in agents:
answer += "\n" + agent.response.answer
success = success and agent.response.success
messages += agent.response.metadata["messages"]
tools += agent.response.metadata["tools"]
return {
"answer": answer.strip(),
"success": True,
"messages": self.messages,
"tools": tools,
}

View file

@ -0,0 +1,23 @@
system_prompt: |
You are a Memory Orchestrator responsible for routing memory retrieval tasks to specialized agents based on the user query.
# User Query
{context}
## Available Memory Agents
Each line indicates a specialized Memory Agent dedicated to storing and retrieving 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 retrieve information from specialized agents:
1. Analyze the user query and identify which memory dimensions are relevant
2. Specify `memory_type` and `memory_target` for each retrieval task
- 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
3. Multiple tasks can be specified to enable parallel retrieval from specialized agents
Note: If the retrieved information is insufficient to answer the query, respond: "nothing found after thorough search."
user_message: |
Please analyze the user query and retrieve relevant information from the appropriate existing agents.

View file

@ -0,0 +1,76 @@
"""ReMe summarizer agent that orchestrates multiple memory agents to summarize information."""
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role
from ....core.op import BaseTool
from ....core.schema import Message
class ReMeSummarizer(BaseMemoryAgent):
"""Orchestrates multiple memory agents to summarize and store information across different memory types."""
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
async def build_messages(self) -> list[Message]:
self.context.history_node = await self.add_history_node()
messages = [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self.read_meta_memories(self.meta_memories),
context=self.context.history_node.content,
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message"),
),
]
return messages
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
return await super()._acting_step(
assistant_message,
tools,
step,
description=self.description,
messages=self.messages,
history_node=self.history_node,
author=self.author,
**kwargs,
)
async def execute(self):
result = await super().execute()
tools: list[BaseTool] = result["tools"]
hands_off_tool = tools[0]
agents: list[BaseMemoryAgent] = hands_off_tool.response.metadata["agents"]
answer = ""
success = True
messages = []
tools = []
for agent in agents:
answer += "\n" + agent.response.answer
success = success and agent.response.success
messages += agent.response.metadata["messages"]
tools += agent.response.metadata["tools"]
return {
"answer": answer.strip(),
"success": True,
"messages": self.messages,
"tools": tools,
}

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

@ -101,7 +101,6 @@ class PromptHandler(BaseContext):
prompt_file_path = Path(prompt_file_path)
if not prompt_file_path.exists():
logger.warning(f"Prompt file not found: {prompt_file_path}")
return self
suffix = prompt_file_path.suffix.lower()
@ -117,7 +116,6 @@ class PromptHandler(BaseContext):
f"Unsupported file format: {suffix}. " f"Supported formats: .yaml, .yml, .json",
)
logger.info(f"Loaded {len(prompt_dict or {})} prompts from {prompt_file_path}")
self.load_prompt_dict(prompt_dict, overwrite=overwrite)
except (yaml.YAMLError, json.JSONDecodeError) as e:

View file

@ -2,6 +2,7 @@
from .base_op import BaseOp
from .base_ray_op import BaseRayOp
from .base_react import BaseReact
from .base_tool import BaseTool
from .mcp_tool import MCPTool
from .parallel_op import ParallelOp
@ -11,6 +12,7 @@ from ..context import R
__all__ = [
"BaseOp",
"BaseRayOp",
"BaseReact",
"BaseTool",
"MCPTool",
"ParallelOp",

View file

@ -22,6 +22,8 @@ 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):
"""Capture initialization arguments for object cloning."""
instance = super().__new__(cls)
@ -52,7 +54,7 @@ class BaseOp(metaclass=ABCMeta):
**kwargs,
):
"""Initialize operator configurations and internal state."""
self.name = name or camel_to_snake(self.__class__.__name__)
self.name = name or self.__alias_name__ or camel_to_snake(self.__class__.__name__)
self.async_mode = async_mode
self.language = language
self.prompt = self._get_prompt_handler(prompt_name, prompt_path)
@ -92,12 +94,12 @@ class BaseOp(metaclass=ABCMeta):
def _handle_failure(self, e: Exception, attempt: int) -> str | None:
"""Log failures and handle final retry logic."""
message = f"[{self.__class__.__name__}] {self.name} failed (attempt {attempt + 1}): {e}"
message = f"[{self.__class__.__name__}] failed (attempt {attempt + 1}): {e}"
if attempt == self.max_retries - 1:
logger.exception(message)
if self.raise_exception:
raise e
return f"{self.name} failed: {e}"
return f"[{self.__class__.__name__}] failed: {e}"
else:
logger.warning(message)
return None
@ -176,7 +178,7 @@ class BaseOp(metaclass=ABCMeta):
if k == "answer":
self.response.answer = v
elif k == "success":
self.response.success = v.lower() == "true"
self.response.success = v if isinstance(v, bool) else v.lower() == "true"
else:
self.response.metadata[k] = v
else:
@ -260,7 +262,7 @@ class BaseOp(metaclass=ABCMeta):
if isinstance(result, list):
results.extend(result)
else:
result.append(result)
results.append(result)
self._pending_tasks.clear()
return results

164
reme/core/op/base_react.py Normal file
View file

@ -0,0 +1,164 @@
"""Base memory agent for handling memory operations with tool-based reasoning."""
import asyncio
from typing import TYPE_CHECKING
from loguru import logger
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,
):
"""Initialize ReAct agent with tools and execution parameters."""
kwargs["sub_ops"] = tools or []
super().__init__(**kwargs)
# Filter only BaseTool instances from sub_ops
from . import BaseTool
self.sub_ops: list[BaseTool] = [t for t in self.sub_ops if isinstance(t, BaseTool)]
self.tool_call_interval: float = tool_call_interval
self.max_steps: int = max_steps
@property
def tools(self) -> list["BaseTool"]:
"""Return available tools for the agent."""
return self.sub_ops
async def build_messages(self) -> list[Message]:
"""Build initial message list from 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 _reasoning_step(
self,
messages: list[Message],
tools: list["BaseTool"],
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 tools]
# Generate assistant response with potential tool calls
assistant_message: Message = await self.llm.chat(messages=messages, tools=tool_calls, **kwargs)
messages.append(assistant_message)
assistant_content: str = assistant_message.simple_dump(as_dict=False)
logger.info(f"[{self.__class__.__name__} {stage or ''} step{step + 1}] assistant={assistant_content}")
# Determine if tools should be called
should_act = bool(assistant_message.tool_calls)
return assistant_message, should_act
async def _acting_step(
self,
assistant_message: Message,
tools: list["BaseTool"],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list["BaseTool"], list[Message]]:
"""Execute tool calls requested by the assistant and collect results."""
tool_list: list["BaseTool"] = []
tool_messages: list[Message] = []
if not assistant_message.tool_calls:
return tool_list, tool_messages
# Create tool name to tool instance mapping
tool_dict = {t.tool_call.name: t for t in tools}
for j, tool_call in enumerate(assistant_message.tool_calls):
prefix: str = f"[{self.__class__.__name__} {stage or ''} step{step + 1}.{j}]"
if tool_call.name not in tool_dict:
logger.warning(f"{prefix} unknown tool_call={tool_call.name}")
continue
logger.info(f"{prefix} submit tool_calls={tool_call.simple_output_dump(as_dict=False)}")
# Create independent tool copy with unique ID
tool_copy: BaseTool = tool_dict[tool_call.name].copy()
tool_copy.tool_call.id = tool_call.id
tool_list.append(tool_copy)
# Create isolated kwargs for each tool call to avoid parameter conflicts
tool_kwargs = {**kwargs, **tool_call.argument_dict}
self.submit_async_task(tool_copy.call, service_context=self.service_context, **tool_kwargs)
if self.tool_call_interval > 0:
await asyncio.sleep(self.tool_call_interval)
# Wait for all tool executions to complete
await self.join_async_tasks()
# 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,
),
)
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
async def react(self, messages: list[Message], tools: list["BaseTool"], stage: str = ""):
"""Run ReAct loop alternating between reasoning and acting until completion."""
success: bool = False
used_tools: list[BaseTool] = []
for step in range(self.max_steps):
# Reasoning: LLM decides next action
assistant_message, should_act = await self._reasoning_step(messages, tools, step=step, stage=stage)
if not should_act:
# No tools requested, task complete
success = True
break
# Acting: execute tools and collect results
t_tools, tool_messages = await self._acting_step(assistant_message, tools, step=step, stage=stage)
used_tools.extend(t_tools)
messages.extend(tool_messages)
return used_tools, messages, success
async def execute(self):
"""Execute the ReAct agent and return final results."""
# Log available tools
for i, tool in enumerate(self.tools):
logger.info(f"[{self.__class__.__name__}] tool_call[{i}]={tool.tool_call.simple_input_dump(as_dict=False)}")
# Build and log initial messages
messages = await self.build_messages()
for i, message in enumerate(messages):
role = message.name or message.role
logger.info(f"[{self.__class__.__name__}] role={role} {message.simple_dump(as_dict=False)}")
# Run ReAct loop
t_tools, messages, success = await self.react(messages, self.tools)
return {
"answer": messages[-1].content if success else "",
"success": success,
"messages": messages,
"tools": t_tools,
}

View file

@ -1,7 +1,5 @@
"""MCP (Model Context Protocol) tool integration for remote tool execution."""
from typing import List
from mcp.types import CallToolResult, TextContent
from .base_tool import BaseTool
@ -16,9 +14,9 @@ class MCPTool(BaseTool):
self,
mcp_server: str = "",
tool_name: str = "",
parameter_required: List[str] | None = None,
parameter_optional: List[str] | None = None,
parameter_deleted: List[str] | None = None,
parameter_required: list[str] | None = None,
parameter_optional: list[str] | None = None,
parameter_deleted: list[str] | None = None,
max_retries: int = 3,
timeout: float | None = None,
raise_exception: bool = False,
@ -28,9 +26,9 @@ class MCPTool(BaseTool):
self.mcp_server: str = mcp_server
self.tool_name: str = tool_name
self.parameter_required: List[str] | None = parameter_required
self.parameter_optional: List[str] | None = parameter_optional
self.parameter_deleted: List[str] | None = parameter_deleted
self.parameter_required: list[str] | None = parameter_required
self.parameter_optional: list[str] | None = parameter_optional
self.parameter_deleted: list[str] | None = parameter_deleted
self.timeout: float | None = timeout
# Example MCP marketplace: https://bailian.console.aliyun.com/?tab=mcp#/mcp-market

View file

@ -84,7 +84,7 @@ class Message(BaseModel):
add_reasoning: bool = True,
add_time_created: bool = False,
add_metadata: bool = False,
enable_json_dump: bool = False,
as_dict: bool = True,
) -> dict | str:
"""Transforms the message into a simplified dictionary for standard APIs."""
result = {}
@ -109,10 +109,7 @@ class Message(BaseModel):
if add_metadata:
result["metadata"] = self.metadata
if enable_json_dump:
return json.dumps(result, ensure_ascii=False)
else:
return result
return result if as_dict else json.dumps(result, ensure_ascii=False)
def format_message(
self,

View file

@ -1,7 +1,7 @@
"""MCP Tool Schema definitions for recursive JSON Schema representation."""
import json
from typing import Any, Dict, List, Optional, Union
from typing import Any, Union, Optional
from mcp.types import Tool
from pydantic import BaseModel, ConfigDict, Field, model_validator, field_validator
@ -16,10 +16,10 @@ class ToolAttr(BaseModel):
type: str = Field(default=str(JsonSchemaEnum.STRING), description="The data type of the attribute")
description: Optional[str] = Field(default=None, description="Description of the attribute")
required: Optional[List[str]] = Field(default=None, description="Required property names for object types")
properties: Optional[Dict[str, "ToolAttr"]] = Field(default=None, description="Child properties for objects")
items: Optional[Union[Dict[str, Any], "ToolAttr"]] = Field(default=None, description="Schema for array items")
enum: Optional[List[str]] = Field(default=None, description="Allowed values for the attribute")
required: Optional[list[str]] = Field(default=None, description="Required property names for object types")
properties: Optional[dict[str, "ToolAttr"]] = Field(default=None, description="Child properties for objects")
items: Optional[Union[dict[str, Any], "ToolAttr"]] = Field(default=None, description="Schema for array items")
enum: Optional[list[str]] = Field(default=None, description="Allowed values for the attribute")
@field_validator("type")
@classmethod
@ -129,9 +129,13 @@ class ToolCall(BaseModel):
return data
def simple_input_dump(self) -> dict:
"""Returns a standardized tool definition dictionary."""
return {
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.
"""
result = {
"type": self.type,
self.type: {
"name": self.name,
@ -139,10 +143,15 @@ class ToolCall(BaseModel):
"parameters": self.parameters.simple_input_dump(),
},
}
return result if as_dict else json.dumps(result, ensure_ascii=False)
def simple_output_dump(self) -> dict:
"""Convert ToolCall to output format dictionary for API responses."""
return {
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.
"""
result = {
"index": self.index,
"id": self.id,
self.type: {
@ -151,6 +160,7 @@ class ToolCall(BaseModel):
},
"type": self.type,
}
return result if as_dict else json.dumps(result, ensure_ascii=False)
@property
def argument_dict(self) -> dict:

View file

@ -1,6 +1,5 @@
"""Defines the data structure for individual vector embedding nodes within a retrieval system."""
from typing import List, Dict
from uuid import uuid4
from pydantic import BaseModel, Field
@ -11,5 +10,5 @@ class VectorNode(BaseModel):
vector_id: str = Field(default_factory=lambda: uuid4().hex)
content: str = Field(default="")
vector: List[float] | None = Field(default=None)
metadata: Dict[str, str | bool | int | float] = Field(default_factory=dict)
vector: list[float] | None = Field(default=None)
metadata: dict[str, str | bool | int | float] = Field(default_factory=dict)

View file

@ -2,7 +2,6 @@
import json
from collections.abc import AsyncIterator
from typing import Optional
import httpx
from loguru import logger
@ -45,7 +44,7 @@ class HttpClient:
response.raise_for_status()
return response.json()
async def execute_flow(self, flow_name: str, **kwargs) -> Optional[Response]:
async def execute_flow(self, flow_name: str, **kwargs) -> Response | None:
"""Execute a flow with automated retry logic."""
endpoint = f"{self.base_url}/{flow_name}"

View file

@ -91,6 +91,16 @@ class BaseVectorStore(ABC):
async def get(self, vector_ids: str | list[str]) -> VectorNode | list[VectorNode]:
"""Fetch specific vector nodes from the collection by their IDs."""
async def dump(self) -> list[VectorNode]:
"""Dump the vector store to a list of vector nodes."""
return await self.list()
async def load(self, nodes: list[VectorNode]):
"""Load the vector store from a list of vector nodes."""
await self.delete_all()
if nodes:
await self.insert(nodes)
@abstractmethod
async def list(
self,

257
reme/reme.py Normal file
View file

@ -0,0 +1,257 @@
"""ReMe application classes for simplified configuration and execution."""
import asyncio
import sys
from .agent.memory.default import ReMeSummarizer, PersonalSummarizer, PersonalRetriever, ReMeRetriever
from .config import ReMeConfigParser
from .core.context import ServiceContext
from .core.embedding import BaseEmbeddingModel
from .core.flow import BaseFlow
from .core.llm import BaseLLM
from .core.schema import Response, Message
from .core.token_counter import BaseTokenCounter
from .core.utils import execute_stream_task
from .core.vector_store import BaseVectorStore
from .tool.memory import UpdateUserProfile, RetrieveMemory, AddMemory, HandsOff, ReadHistory
class ReMe:
"""ReMe application with config file support and flow execution methods."""
def __init__(
self,
*args,
llm_api_key: str | None = None,
llm_api_base: str | None = None,
embedding_api_key: str | None = None,
embedding_api_base: str | None = None,
enable_logo: bool = True,
llm: dict | None = None,
embedding_model: dict | None = None,
vector_store: dict | None = None,
token_counter: dict | None = None,
**kwargs,
):
self.service_context = ServiceContext(
*args,
llm_api_key=llm_api_key,
llm_api_base=llm_api_base,
embedding_api_key=embedding_api_key,
embedding_api_base=embedding_api_base,
service_config=None,
parser=ReMeConfigParser,
config_path=None,
enable_logo=enable_logo,
llm=llm,
embedding_model=embedding_model,
vector_store=vector_store,
token_counter=token_counter,
**kwargs,
)
async def __aenter__(self):
"""Async context manager entry."""
return self
def __enter__(self):
"""Context manager entry."""
return self
async def close(self):
"""Close the application."""
return await self.service_context.close()
def close_sync(self):
"""Close the application synchronously."""
self.service_context.close_sync()
async def __aexit__(self, exc_type=None, exc_val=None, exc_tb=None):
"""Async context manager exit."""
await self.close()
return False
def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
"""Context manager exit."""
self.close_sync()
return False
@property
def default_llm(self) -> BaseLLM:
"""Return the default LLM instance from the service context."""
return self.service_context.llms["default"]
@property
def default_embedding_model(self) -> BaseEmbeddingModel:
"""Return the default embedding model instance from the service context."""
return self.service_context.embedding_models["default"]
@property
def default_vector_store(self) -> BaseVectorStore:
"""Return the default vector store instance from the service context."""
return self.service_context.vector_stores["default"]
@property
def default_token_counter(self) -> BaseTokenCounter:
"""Return the default token counter instance from the service context."""
return self.service_context.token_counters["default"]
async def summary(
self,
messages: list[Message | dict],
description: str = "",
user_name: str | list[str] = "",
enable_thinking_params: bool = False,
meta_memories: list[dict] = None,
version: str = "default",
**kwargs,
):
"""Summarize messages and store them in memory for the specified user(s)."""
if user_name:
if isinstance(user_name, str):
for message in messages:
if isinstance(message, dict) and not message.get("name"):
message["name"] = user_name
elif isinstance(message, Message) and not message.name:
message.name = user_name
user_name = [user_name]
if not meta_memories:
meta_memories = [
{
"memory_type": "personal",
"memory_target": name,
}
for name in user_name
]
if version == "default":
reme_summarizer = ReMeSummarizer(
meta_memories=meta_memories,
tools=[
HandsOff(
memory_agents=[
PersonalSummarizer(
tools=[
RetrieveMemory(enable_thinking_params=enable_thinking_params),
AddMemory(enable_thinking_params=enable_thinking_params),
UpdateUserProfile(enable_thinking_params=enable_thinking_params),
],
),
],
),
],
)
else:
raise NotImplementedError
return await reme_summarizer.call(
messages=messages,
description=description,
service_context=self.service_context,
**kwargs,
)
else:
raise NotImplementedError
async def retrieve(
self,
query: str = "",
top_k: int = 20,
description: str = "",
messages: list[dict] | None = None,
user_name: str | list[str] = "",
enable_thinking_params: bool = False,
meta_memories: list[dict] = None,
version: str = "default",
**kwargs,
):
"""Retrieve relevant memories for the specified user(s) based on query or messages."""
if user_name:
if isinstance(user_name, str):
if messages:
for message in messages:
if isinstance(message, dict) and not message.get("name"):
message["name"] = user_name
elif isinstance(message, Message) and not message.name:
message.name = user_name
user_name = [user_name]
if not meta_memories:
meta_memories = [
{
"memory_type": "personal",
"memory_target": name,
}
for name in user_name
]
if version == "default":
reme_retriever = ReMeRetriever(
meta_memories=meta_memories,
tools=[
HandsOff(
memory_agents=[
PersonalRetriever(
tools=[
RetrieveMemory(enable_thinking_params=enable_thinking_params, top_k=top_k),
ReadHistory(enable_thinking_params=enable_thinking_params),
],
),
],
),
],
)
else:
raise NotImplementedError
return await reme_retriever.call(
query=query,
messages=messages,
description=description,
service_context=self.service_context,
**kwargs,
)
else:
raise NotImplementedError
async def execute_flow(self, name: str, **kwargs) -> Response:
"""Execute a flow with the given name and parameters."""
assert name in self.service_context.flows, f"Flow {name} not found"
flow: BaseFlow = self.service_context.flows[name]
return await flow.call(**kwargs)
async def execute_stream_flow(self, name: str, **kwargs):
"""Execute a stream flow with the given name and parameters."""
assert name in self.service_context.flows, f"Flow {name} not found"
flow: BaseFlow = self.service_context.flows[name]
assert flow.stream is True, "non-stream flow is not supported in execute_stream_flow!"
stream_queue = asyncio.Queue()
task = asyncio.create_task(flow.call(stream_queue=stream_queue, **kwargs))
async for chunk in execute_stream_task(
stream_queue=stream_queue,
task=task,
task_name=name,
as_bytes=False,
):
yield chunk
def run_service(self):
"""Run the configured service (HTTP, MCP, or CMD)."""
self.service_context.service.run()
def main():
"""Main entry point for running ReMe application from command line."""
with ReMe(*sys.argv[1:]) as app:
app.run_service()
if __name__ == "__main__":
main()

View file

@ -1,90 +0,0 @@
"""ReMe application classes for simplified configuration and execution."""
import asyncio
import sys
from .config import ReMeConfigParser
from .core.context import ServiceContext
from .core.flow import BaseFlow
from .core.schema import Response
from .core.utils import execute_stream_task
class ReMeApp:
"""ReMe application with config file support and flow execution methods."""
def __init__(
self,
*args,
llm_api_key: str | None = None,
llm_api_base: str | None = None,
embedding_api_key: str | None = None,
embedding_api_base: str | None = None,
enable_logo: bool = True,
**kwargs,
):
self.service_context = ServiceContext(
*args,
llm_api_key=llm_api_key,
llm_api_base=llm_api_base,
embedding_api_key=embedding_api_key,
embedding_api_base=embedding_api_base,
service_config=None,
parser=ReMeConfigParser,
config_path=None,
enable_logo=enable_logo,
**kwargs,
)
async def __aenter__(self):
"""Async context manager entry."""
return self
def __enter__(self):
"""Context manager entry."""
return self
async def __aexit__(self, exc_type=None, exc_val=None, exc_tb=None):
"""Async context manager exit."""
await self.service_context.close()
return False
def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
"""Context manager exit."""
self.service_context.close_sync()
return False
async def execute_flow(self, name: str, **kwargs) -> Response:
"""Execute a flow with the given name and parameters."""
assert name in self.service_context.flows, f"Flow {name} not found"
flow: BaseFlow = self.service_context.flows[name]
return await flow.call(**kwargs)
async def execute_stream_flow(self, name: str, **kwargs):
"""Execute a stream flow with the given name and parameters."""
assert name in self.service_context.flows, f"Flow {name} not found"
flow: BaseFlow = self.service_context.flows[name]
assert flow.stream is True, "non-stream flow is not supported in execute_stream_flow!"
stream_queue = asyncio.Queue()
task = asyncio.create_task(flow.call(stream_queue=stream_queue, **kwargs))
async for chunk in execute_stream_task(
stream_queue=stream_queue,
task=task,
task_name=name,
as_bytes=False,
):
yield chunk
def run_service(self):
"""Run the configured service (HTTP, MCP, or CMD)."""
self.service_context.service.run()
def main():
"""Main entry point for running ReMe application from command line."""
with ReMeApp(*sys.argv[1:]) as app:
app.run_service()
if __name__ == "__main__":
main()

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.delete_memory import DeleteMemory
from .vector.retrieve_memory import RetrieveMemory
from .vector.retrieve_recent_memory import RetrieveRecentMemory
from .vector.update_memory import UpdateMemory
from ...core import R
__all__ = [
"BaseMemoryTool",
"HandsOff",
"AddHistory",
"ReadHistory",
"AddIdentity",
@ -21,6 +28,11 @@ __all__ = [
"ReadMetaMemory",
"ReadUserProfile",
"UpdateUserProfile",
"AddMemory",
"DeleteMemory",
"RetrieveMemory",
"RetrieveRecentMemory",
"UpdateMemory",
]
for name in __all__:

View file

@ -0,0 +1,108 @@
"""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, service_context=self.service_context, **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 {
"answer": "\n".join(results),
"agents": agent_list,
}

View file

@ -38,10 +38,11 @@ class AddHistory(BaseMemoryTool):
content=history_content,
author=self.author,
)
logger.info(f"Adding history node: {history_node.model_dump_json(indent=2, exclude={'content'})}")
self.context.history_node = history_node
logger.info(f"Adding history node: {history_node.model_dump_json(indent=2)}")
vector_node = history_node.to_vector_node()
await self.vector_store.delete(vector_node.memory_id)
await self.vector_store.delete(vector_node.vector_id)
await self.vector_store.insert([vector_node])
return f"Successfully added history: {history_node.memory_id}"

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

@ -5,8 +5,8 @@ from typing import Literal
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ....core.schema import ToolCall
from ....core.schema.memory_node import MemoryNode
from ....core.enumeration import MemoryType
from ....core.schema import ToolCall, MemoryNode
class ReadUserProfile(BaseMemoryTool):
@ -30,6 +30,7 @@ class ReadUserProfile(BaseMemoryTool):
)
async def execute(self):
self.context.memory_type = MemoryType.PERSONAL
cached_data = self.local_memory.load(self.memory_cache_key, auto_clean=False)
if not cached_data:

View file

@ -106,4 +106,4 @@ class UpdateUserProfile(BaseMemoryTool):
operations.append(f"added {len(new_nodes)} new profiles.")
operations.append("Operation completed.")
logger.info("\n".join(operations))
return operations
return "\n".join(operations)

View file

@ -0,0 +1,109 @@
"""Add memory to vector store"""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ....core.schema import ToolCall, MemoryNode
class AddMemory(BaseMemoryTool):
"""Tool to add memories to vector store"""
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"""
return ToolCall(
**{
"description": "add multiple memories to vector store for future retrieval.",
"parameters": {
"type": "object",
"properties": {
"memories": {
"type": "array",
"description": "list of memories to store.",
"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.",
},
},
"required": ["conversation_time", "memory_content"],
},
},
},
"required": ["memories"],
},
},
)
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}")
return MemoryNode(
memory_type=self.memory_type,
memory_target=self.memory_target,
content=memory_content,
author=self.author,
metadata=metadata,
)
async def execute(self):
memory_nodes: list[MemoryNode] = []
memories: list[dict] = self.context.get("memories", [])
if not memories:
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:
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]
await self.vector_store.delete(vector_ids=vector_ids)
await self.vector_store.insert(nodes=vector_nodes)
self.memory_nodes = memory_nodes
output = f"Successfully added {len(memory_nodes)} memories to vector_store."
logger.info(output)
return output

View file

@ -0,0 +1,71 @@
"""Delete memory from vector store"""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ....core.schema import ToolCall
class DeleteMemory(BaseMemoryTool):
"""Tool to delete memories from vector store"""
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"""
return ToolCall(
**{
"description": "delete multiple memories from vector store using their unique IDs.",
"parameters": {
"type": "object",
"properties": {
"memory_ids": {
"type": "array",
"description": "list of unique identifiers (memory_ids) of memories to delete.",
"items": {"type": "string"},
},
},
"required": ["memory_ids"],
},
},
)
async def execute(self):
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:
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
output = f"Successfully deleted {len(memory_ids)} memories from vector_store."
logger.info(output)
return output

View file

@ -0,0 +1,137 @@
"""Retrieve memory 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 RetrieveMemory(BaseMemoryTool):
"""Tool to retrieve memories from vector store using similarity search"""
def __init__(self, top_k: int = 20, **kwargs):
super().__init__(**kwargs)
self.top_k: int = top_k
@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"],
}
def _build_tool_call(self) -> ToolCall:
"""Build and return the tool call schema"""
return ToolCall(
**{
"description": "retrieve memories using vector similarity search.",
"parameters": self._build_query_parameters(),
},
)
def _build_multiple_tool_call(self) -> ToolCall:
"""Build and return the multiple tool call schema"""
return ToolCall(
**{
"description": "retrieve memories using multiple queries with vector similarity search.",
"parameters": {
"type": "object",
"properties": {
"query_items": {
"type": "array",
"description": "list of query items for vector similarity search.",
"items": self._build_query_parameters(),
},
},
"required": ["query_items"],
},
},
)
async def _retrieve_by_query(
self,
memory_type: str,
memory_target: str,
query: str,
time_range: str | None = None,
) -> list[MemoryNode]:
"""Retrieve memories by query with filters"""
filter_dict: dict = {
"memory_type": memory_type,
"memory_target": memory_target,
}
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)
return [MemoryNode.from_vector_node(n) for n in nodes]
async def execute(self):
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", [])
else:
query_items: list[dict] = [
{
"query": self.context.get("query", ""),
"time_range": self.context.get("time_range", ""),
},
]
query_items = [item for item in query_items if item.get("query")]
memory_nodes: list[MemoryNode] = []
for item in query_items:
retrieved = await self._retrieve_by_query(
memory_type=memory_type,
memory_target=memory_target,
query=item["query"],
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:
output = "No new memory_nodes found matching the query (duplicates removed)."
else:
outputs = []
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} "
outputs.append(line.strip())
output = "\n".join(outputs)
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 RetrieveRecentMemory(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

@ -0,0 +1,126 @@
"""Update memory in vector store"""
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ....core.schema import ToolCall, MemoryNode
class UpdateMemory(BaseMemoryTool):
"""Tool to update memories in vector store"""
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"""
return ToolCall(
**{
"description": "update multiple memories in vector store by replacing old memories with new content.",
"parameters": {
"type": "object",
"properties": {
"memories": {
"type": "array",
"description": "list of memory update objects.",
"items": {
"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"],
},
},
},
"required": ["memories"],
},
},
)
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}
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=memory_content,
author=self.author,
metadata=metadata,
)
return memory_id, memory_node
async def execute(self):
old_memory_ids: list[str] = []
memory_nodes: list[MemoryNode] = []
memories: list[dict] = self.context.get("memories", [])
if not memories:
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)
if not memory_nodes:
output = "No valid memories provided for update."
logger.info(output)
return output
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 = memory_nodes
output = f"Successfully updated {len(memory_nodes)} memories in vector_store."
logger.info(output)
return output

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 tool’s 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 2–3 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 2–3 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-perception–related 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 person’s 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 Agent’s **{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,52 +0,0 @@
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
class PersonalRetrieverV4(BaseMemoryAgent):
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
if not context:
raise ValueError("input must have either `query` or `messages`")
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
return [
Message(
role=Role.USER,
content=self.prompt_format(
prompt_name="user_message",
memory_type=self.memory_type.value,
memory_target=self.memory_target,
user_profile=user_profile,
context=context,
))
]
async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]:
return await super()._acting_step(
assistant_message,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
**kwargs,
)
async def execute(self):
"""Execute the retriever and determine success based on output markers."""
await super().execute()
# Check for memory found/not found markers in the output
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

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,124 +0,0 @@
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ...core.enumeration import Role, MemoryType
from ...core.schema import Message, MemoryNode
class PersonalSummarizerV4(BaseMemoryAgent):
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 = [
Message(
role=Role.USER,
content=self.prompt_format(
prompt_name="user_message_phase1",
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 = [
Message(
role=Role.USER,
content=self.prompt_format(
prompt_name="user_message_phase2",
context=history_node.content,
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(
assistant_message,
step,
stage=stage,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
history_node=self.history_node,
author=self.author,
**kwargs,
)
async def execute(self):
"""Execute in two phases: 1) AddSummaryMemory, 2) UpdateUserProfile"""
# Log available tools
for i, tool in enumerate(self.tools):
logger.info(
f"[{self.__class__.__name__}] step0.{i} "
f"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)
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)}",
)
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")
# 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
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")
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)}",
)
messages_phase2, success_phase2 = await self.react(messages_phase2, stage="S2")
# Restore original tools
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."

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,117 +0,0 @@
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
class ReMeRetrieverV4(BaseMemoryAgent):
def __init__(self, meta_memories: list[dict] | None = None, **kwargs):
super().__init__(**kwargs)
self.meta_memories: list[dict] = meta_memories or []
self.meta_info_dict: dict[str, str] = {}
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]:
if self.context.get("query"):
user_query = self.context.query
elif self.context.get("messages"):
user_query = format_messages(self.context.messages)
else:
raise ValueError("Input must have either `query` or `messages`")
messages = [
Message(
role=Role.SYSTEM,
content=self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self._read_meta_memories(),
user_query=user_query,
),
),
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]:
import asyncio
from ...mem_tool.v4 import HandsOff
if not assistant_message.tool_calls:
return []
tool_list: list = []
tool_result_messages: list[Message] = []
tool_dict = {t.tool_call.name: t for t in self.tools}
stage_prefix = ""
# Add required context parameters
kwargs["query"] = self.context.get("query", "")
kwargs["messages"] = self.context.get("messages", [])
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 = 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)
# Collect meta_info_dict from HandsOff tool
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")
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)
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 execute(self):
await super().execute()
# Assemble meta_info_dict into output
if self.meta_info_dict:
output_parts = []
for key, value in self.meta_info_dict.items():
output_parts.append(f"## {key}\n{value}")
self.output = "\n\n".join(output_parts)
logger.info(f"Assembled output from meta_info_dict with {len(self.meta_info_dict)} entries")

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,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,144 +0,0 @@
"""Hands-off tool for distributing memory tasks 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
if TYPE_CHECKING:
from ..mem_agent import BaseMemoryAgent
@C.register_op()
class HandsOffTool(BaseMemoryTool):
"""Distribute memory tasks to appropriate agents based on memory_type."""
def __init__(self, memory_agents: list["BaseMemoryAgent"], **kwargs):
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)]
@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_parameters(self) -> dict:
"""Build input schema for single memory task distribution."""
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 task distribution."""
item_properties, required_fields = self._build_item_schema()
return {
"type": "object",
"properties": {
"memory_tasks": {
"type": "array",
"description": self.get_prompt("memory_tasks"),
"items": {
"type": "object",
"properties": item_properties,
"required": required_fields,
},
},
},
"required": ["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 based on enable_multiple flag."""
tasks: list[dict] = []
if self.enable_multiple:
memory_tasks: list[dict] = self.context.get("memory_tasks", [])
for task in memory_tasks:
tasks.append(self._parse_memory_type_target(task))
else:
tasks.append(self._parse_memory_type_target(self.context))
return tasks
async def execute(self):
"""Execute memory tasks by distributing to appropriate agents in parallel."""
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 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)
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 {len(results)} memory tasks:\n{results_str}"

View file

@ -1,19 +0,0 @@
tool: |
Distribute a memory task to the appropriate agent based on memory_type.
Use this tool to hand off memory summarization to specialized agents.
Examples: summarizing user preferences, extracting procedural knowledge, or analyzing tool usage patterns.
tool_multiple: |
Distribute multiple memory tasks to appropriate agents in parallel.
Use this tool to hand off multiple memory summarization tasks efficiently.
Each task will be processed by its corresponding specialized agent based on memory_type.
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,54 +0,0 @@
from loguru import logger
from ..base_memory_tool import BaseMemoryTool
from ...core.context import C
from ...core.schema.memory_node import MemoryNode
@C.register_op()
class ReadLocalMemories(BaseMemoryTool):
def __init__(self, **kwargs):
kwargs["enable_multiple"] = False
super().__init__(**kwargs)
def _build_parameters(self) -> dict:
return {
"type": "object",
"properties": {
"memory_type": {
"type": "string",
"description": self.get_prompt("memory_type"),
},
"memory_target": {
"type": "string",
"description": self.get_prompt("memory_target"),
},
},
"required": ["memory_type", "memory_target"],
}
async def execute(self):
memory_type = self.context.get("memory_type", "")
memory_target = self.context.get("memory_target", "")
if not memory_type or not memory_target:
self.output = "memory_type and memory_target are required."
return
cache_key = f"{memory_type}_{memory_target}"
cached_data = self.meta_memory.load(cache_key, auto_clean=False)
if not cached_data:
self.output = f"Local memory not found: {memory_type}_{memory_target}"
logger.info(self.output)
return
memory_nodes = [MemoryNode(**node_data) for node_data in cached_data]
if not memory_nodes:
self.output = f"No valid memory nodes found in {memory_type}_{memory_target}"
return
self.output = memory_nodes
logger.info(f"Read {len(memory_nodes)} nodes from cache key: {cache_key}")

View file

@ -1,8 +0,0 @@
tool: |
Read memory nodes from local memory files.
memory_type: |
The type of local memory to read.
memory_target: |
The target identifier for the local memory.

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"
)

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