diff --git a/src/memos/__init__.py b/src/memos/__init__.py index 489c30f02..8e61e5885 100644 --- a/src/memos/__init__.py +++ b/src/memos/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.0.14" +__version__ = "2.0.15" from memos.configs.mem_cube import GeneralMemCubeConfig from memos.configs.mem_os import MOSConfig diff --git a/src/memos/api/handlers/search_handler.py b/src/memos/api/handlers/search_handler.py index ba1c50b07..15eb7c38e 100644 --- a/src/memos/api/handlers/search_handler.py +++ b/src/memos/api/handlers/search_handler.py @@ -20,6 +20,7 @@ from memos.multi_mem_cube.composite_cube import CompositeCubeView from memos.multi_mem_cube.single_cube import SingleCubeView from memos.multi_mem_cube.views import MemCubeView +from memos.plugins.hooks import hookable logger = get_logger(__name__) @@ -44,6 +45,7 @@ def __init__(self, dependencies: HandlerDependencies): "naive_mem_cube", "mem_scheduler", "searcher", "deepsearch_agent" ) + @hookable("search") def handle_search_memories(self, search_req: APISearchRequest) -> SearchResponse: """ Main handler for search memories endpoint. diff --git a/src/memos/api/product_models.py b/src/memos/api/product_models.py index 78dcfc797..7a90b2091 100644 --- a/src/memos/api/product_models.py +++ b/src/memos/api/product_models.py @@ -438,6 +438,14 @@ class APISearchRequest(BaseRequest): ) # ==== Context ==== + reference_time: str | None = Field( + None, + description=( + "Optional reference time for time-sensitive search parsing. " + "If omitted, search uses the current server time." + ), + ) + chat_history: MessageList | None = Field( None, description=( diff --git a/src/memos/mem_reader/multi_modal_struct.py b/src/memos/mem_reader/multi_modal_struct.py index 26287ff4f..867345ac9 100644 --- a/src/memos/mem_reader/multi_modal_struct.py +++ b/src/memos/mem_reader/multi_modal_struct.py @@ -17,7 +17,7 @@ from memos.mem_reader.utils import parse_json_result from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata from memos.plugins.hook_defs import H -from memos.plugins.hooks import trigger_single_hook +from memos.plugins.hooks import trigger_hook, trigger_single_hook from memos.templates.mem_reader_prompts import MEMORY_MERGE_PROMPT_EN, MEMORY_MERGE_PROMPT_ZH from memos.templates.tool_mem_prompts import TOOL_TRAJECTORY_PROMPT_EN, TOOL_TRAJECTORY_PROMPT_ZH from memos.types import MessagesType @@ -785,6 +785,14 @@ def _process_one_item( ) rawfile_node.metadata.summary_ids = [mem_node.id for mem_node in fine_items] fine_items.append(rawfile_node) + enriched_items = trigger_hook( + H.MEMORY_ITEMS_AFTER_FINE_EXTRACT, + items=fine_items, + user_context=kwargs.get("user_context"), + mem_reader=self, + extract_mode="fine", + ) + fine_items = enriched_items if enriched_items is not None else fine_items return fine_items fine_memory_items: list[TextualMemoryItem] = [] diff --git a/src/memos/memories/textual/item.py b/src/memos/memories/textual/item.py index f34cf1efd..b7004c84c 100644 --- a/src/memos/memories/textual/item.py +++ b/src/memos/memories/textual/item.py @@ -81,6 +81,14 @@ class ArchivedTextualMemory(BaseModel): default_factory=lambda: datetime.now().isoformat(), description="The time the memory was created.", ) + timespec: dict[str, Any] | None = Field( + default=None, + description="Compact temporal index snapshot for this archived version, used by retrieval-side version selection.", + ) + memory_form: Literal["state", "event"] | None = Field( + default=None, + description="Internal memory form snapshot for this archived version, used by retrieval-side routing.", + ) class TextualMemoryMetadata(BaseModel): diff --git a/src/memos/plugins/hook_defs.py b/src/memos/plugins/hook_defs.py index 030d5292f..dbc28effb 100644 --- a/src/memos/plugins/hook_defs.py +++ b/src/memos/plugins/hook_defs.py @@ -77,6 +77,7 @@ class H: # mem_reader — generic extension point before LLM extraction MEM_READER_PRE_EXTRACT = "mem_reader.pre_extract" + MEMORY_ITEMS_AFTER_FINE_EXTRACT = "memory_items.after_fine_extract" # memory version — single-provider business hooks MEMORY_VERSION_PREPARE_UPDATES = "memory_version.prepare_updates" @@ -102,6 +103,13 @@ class H: pipe_key="prompt", ) +define_hook( + H.MEMORY_ITEMS_AFTER_FINE_EXTRACT, + description="Post-process memory items after mem_reader fine extraction completes", + params=["items", "user_context", "mem_reader", "extract_mode"], + pipe_key="items", +) + define_hook( H.MEMORY_VERSION_PREPARE_UPDATES, description=(