MemoryOS 0.2.2__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MemoryOS might be problematic. Click here for more details.

Files changed (82) hide show
  1. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/METADATA +7 -1
  2. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/RECORD +81 -66
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +31 -8
  5. memos/api/context/context.py +1 -1
  6. memos/api/context/context_thread.py +96 -0
  7. memos/api/middleware/request_context.py +94 -0
  8. memos/api/product_api.py +5 -1
  9. memos/api/product_models.py +16 -0
  10. memos/api/routers/product_router.py +39 -3
  11. memos/api/start_api.py +3 -0
  12. memos/configs/internet_retriever.py +13 -0
  13. memos/configs/mem_scheduler.py +38 -16
  14. memos/configs/memory.py +13 -0
  15. memos/configs/reranker.py +18 -0
  16. memos/graph_dbs/base.py +33 -4
  17. memos/graph_dbs/nebular.py +631 -236
  18. memos/graph_dbs/neo4j.py +18 -7
  19. memos/graph_dbs/neo4j_community.py +6 -3
  20. memos/llms/vllm.py +2 -0
  21. memos/log.py +125 -8
  22. memos/mem_os/core.py +49 -11
  23. memos/mem_os/main.py +1 -1
  24. memos/mem_os/product.py +392 -215
  25. memos/mem_os/utils/default_config.py +1 -1
  26. memos/mem_os/utils/format_utils.py +11 -47
  27. memos/mem_os/utils/reference_utils.py +153 -0
  28. memos/mem_reader/simple_struct.py +112 -43
  29. memos/mem_scheduler/base_scheduler.py +58 -55
  30. memos/mem_scheduler/{modules → general_modules}/base.py +1 -2
  31. memos/mem_scheduler/{modules → general_modules}/dispatcher.py +54 -15
  32. memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +4 -4
  33. memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
  34. memos/mem_scheduler/{modules → general_modules}/retriever.py +19 -5
  35. memos/mem_scheduler/{modules → general_modules}/scheduler_logger.py +10 -4
  36. memos/mem_scheduler/general_scheduler.py +110 -67
  37. memos/mem_scheduler/monitors/__init__.py +0 -0
  38. memos/mem_scheduler/monitors/dispatcher_monitor.py +305 -0
  39. memos/mem_scheduler/{modules/monitor.py → monitors/general_monitor.py} +57 -19
  40. memos/mem_scheduler/mos_for_test_scheduler.py +7 -1
  41. memos/mem_scheduler/schemas/general_schemas.py +3 -2
  42. memos/mem_scheduler/schemas/message_schemas.py +2 -1
  43. memos/mem_scheduler/schemas/monitor_schemas.py +10 -2
  44. memos/mem_scheduler/utils/misc_utils.py +43 -2
  45. memos/mem_user/mysql_user_manager.py +4 -2
  46. memos/memories/activation/item.py +1 -1
  47. memos/memories/activation/kv.py +20 -8
  48. memos/memories/textual/base.py +1 -1
  49. memos/memories/textual/general.py +1 -1
  50. memos/memories/textual/item.py +1 -1
  51. memos/memories/textual/tree.py +31 -1
  52. memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +30 -48
  53. memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
  54. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +2 -0
  55. memos/memories/textual/tree_text_memory/organize/reorganizer.py +102 -140
  56. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +231 -0
  57. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +9 -0
  58. memos/memories/textual/tree_text_memory/retrieve/recall.py +67 -10
  59. memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
  60. memos/memories/textual/tree_text_memory/retrieve/searcher.py +246 -134
  61. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +7 -2
  62. memos/memories/textual/tree_text_memory/retrieve/utils.py +7 -5
  63. memos/memos_tools/lockfree_dict.py +120 -0
  64. memos/memos_tools/notification_utils.py +46 -0
  65. memos/memos_tools/thread_safe_dict.py +288 -0
  66. memos/reranker/__init__.py +4 -0
  67. memos/reranker/base.py +24 -0
  68. memos/reranker/cosine_local.py +95 -0
  69. memos/reranker/factory.py +43 -0
  70. memos/reranker/http_bge.py +99 -0
  71. memos/reranker/noop.py +16 -0
  72. memos/templates/mem_reader_prompts.py +290 -39
  73. memos/templates/mem_scheduler_prompts.py +23 -10
  74. memos/templates/mos_prompts.py +133 -31
  75. memos/templates/tree_reorganize_prompts.py +24 -17
  76. memos/utils.py +19 -0
  77. memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
  78. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/LICENSE +0 -0
  79. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/WHEEL +0 -0
  80. {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/entry_points.txt +0 -0
  81. /memos/mem_scheduler/{modules → general_modules}/__init__.py +0 -0
  82. /memos/mem_scheduler/{modules → general_modules}/misc.py +0 -0
memos/graph_dbs/neo4j.py CHANGED
@@ -323,7 +323,7 @@ class Neo4jGraphDB(BaseGraphDB):
323
323
  return result.single() is not None
324
324
 
325
325
  # Graph Query & Reasoning
326
- def get_node(self, id: str) -> dict[str, Any] | None:
326
+ def get_node(self, id: str, **kwargs) -> dict[str, Any] | None:
327
327
  """
328
328
  Retrieve the metadata and memory of a node.
329
329
  Args:
@@ -331,6 +331,7 @@ class Neo4jGraphDB(BaseGraphDB):
331
331
  Returns:
332
332
  Dictionary of node fields, or None if not found.
333
333
  """
334
+
334
335
  where_user = ""
335
336
  params = {"id": id}
336
337
  if not self.config.use_multi_db and self.config.user_name:
@@ -343,7 +344,7 @@ class Neo4jGraphDB(BaseGraphDB):
343
344
  record = session.run(query, params).single()
344
345
  return self._parse_node(dict(record["n"])) if record else None
345
346
 
346
- def get_nodes(self, ids: list[str]) -> list[dict[str, Any]]:
347
+ def get_nodes(self, ids: list[str], **kwargs) -> list[dict[str, Any]]:
347
348
  """
348
349
  Retrieve the metadata and memory of a list of nodes.
349
350
  Args:
@@ -355,6 +356,7 @@ class Neo4jGraphDB(BaseGraphDB):
355
356
  - Assumes all provided IDs are valid and exist.
356
357
  - Returns empty list if input is empty.
357
358
  """
359
+
358
360
  if not ids:
359
361
  return []
360
362
 
@@ -363,7 +365,10 @@ class Neo4jGraphDB(BaseGraphDB):
363
365
 
364
366
  if not self.config.use_multi_db and self.config.user_name:
365
367
  where_user = " AND n.user_name = $user_name"
366
- params["user_name"] = self.config.user_name
368
+ if kwargs.get("cube_name"):
369
+ params["user_name"] = kwargs["cube_name"]
370
+ else:
371
+ params["user_name"] = self.config.user_name
367
372
 
368
373
  query = f"MATCH (n:Memory) WHERE n.id IN $ids{where_user} RETURN n"
369
374
 
@@ -601,6 +606,7 @@ class Neo4jGraphDB(BaseGraphDB):
601
606
  scope: str | None = None,
602
607
  status: str | None = None,
603
608
  threshold: float | None = None,
609
+ **kwargs,
604
610
  ) -> list[dict]:
605
611
  """
606
612
  Retrieve node IDs based on vector similarity.
@@ -650,7 +656,10 @@ class Neo4jGraphDB(BaseGraphDB):
650
656
  if status:
651
657
  parameters["status"] = status
652
658
  if not self.config.use_multi_db and self.config.user_name:
653
- parameters["user_name"] = self.config.user_name
659
+ if kwargs.get("cube_name"):
660
+ parameters["user_name"] = kwargs["cube_name"]
661
+ else:
662
+ parameters["user_name"] = self.config.user_name
654
663
 
655
664
  with self.driver.session(database=self.db_name) as session:
656
665
  result = session.run(query, parameters)
@@ -829,7 +838,7 @@ class Neo4jGraphDB(BaseGraphDB):
829
838
  logger.error(f"[ERROR] Failed to clear database '{self.db_name}': {e}")
830
839
  raise
831
840
 
832
- def export_graph(self) -> dict[str, Any]:
841
+ def export_graph(self, **kwargs) -> dict[str, Any]:
833
842
  """
834
843
  Export all graph nodes and edges in a structured form.
835
844
 
@@ -910,12 +919,13 @@ class Neo4jGraphDB(BaseGraphDB):
910
919
  target_id=edge["target"],
911
920
  )
912
921
 
913
- def get_all_memory_items(self, scope: str) -> list[dict]:
922
+ def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]:
914
923
  """
915
924
  Retrieve all memory items of a specific memory_type.
916
925
 
917
926
  Args:
918
927
  scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'.
928
+ Returns:
919
929
 
920
930
  Returns:
921
931
  list[dict]: Full list of memory items under this scope.
@@ -940,12 +950,13 @@ class Neo4jGraphDB(BaseGraphDB):
940
950
  results = session.run(query, params)
941
951
  return [self._parse_node(dict(record["n"])) for record in results]
942
952
 
943
- def get_structure_optimization_candidates(self, scope: str) -> list[dict]:
953
+ def get_structure_optimization_candidates(self, scope: str, **kwargs) -> list[dict]:
944
954
  """
945
955
  Find nodes that are likely candidates for structure optimization:
946
956
  - Isolated nodes, nodes with empty background, or nodes with exactly one child.
947
957
  - Plus: the child of any parent node that has exactly one child.
948
958
  """
959
+
949
960
  where_clause = """
950
961
  WHERE n.memory_type = $scope
951
962
  AND n.status = 'activated'
@@ -129,6 +129,7 @@ class Neo4jCommunityGraphDB(Neo4jGraphDB):
129
129
  scope: str | None = None,
130
130
  status: str | None = None,
131
131
  threshold: float | None = None,
132
+ **kwargs,
132
133
  ) -> list[dict]:
133
134
  """
134
135
  Retrieve node IDs based on vector similarity using external vector DB.
@@ -157,7 +158,10 @@ class Neo4jCommunityGraphDB(Neo4jGraphDB):
157
158
  if status:
158
159
  vec_filter["status"] = status
159
160
  vec_filter["vector_sync"] = "success"
160
- vec_filter["user_name"] = self.config.user_name
161
+ if kwargs.get("cube_name"):
162
+ vec_filter["user_name"] = kwargs["cube_name"]
163
+ else:
164
+ vec_filter["user_name"] = self.config.user_name
161
165
 
162
166
  # Perform vector search
163
167
  results = self.vec_db.search(query_vector=vector, top_k=top_k, filter=vec_filter)
@@ -169,13 +173,12 @@ class Neo4jCommunityGraphDB(Neo4jGraphDB):
169
173
  # Return consistent format
170
174
  return [{"id": r.id, "score": r.score} for r in results]
171
175
 
172
- def get_all_memory_items(self, scope: str) -> list[dict]:
176
+ def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]:
173
177
  """
174
178
  Retrieve all memory items of a specific memory_type.
175
179
 
176
180
  Args:
177
181
  scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'.
178
-
179
182
  Returns:
180
183
  list[dict]: Full list of memory items under this scope.
181
184
  """
memos/llms/vllm.py CHANGED
@@ -105,6 +105,7 @@ class VLLMLLM(BaseLLM):
105
105
  "temperature": float(getattr(self.config, "temperature", 0.8)),
106
106
  "max_tokens": int(getattr(self.config, "max_tokens", 1024)),
107
107
  "top_p": float(getattr(self.config, "top_p", 0.9)),
108
+ "extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
108
109
  }
109
110
 
110
111
  response = self.client.chat.completions.create(**completion_kwargs)
@@ -142,6 +143,7 @@ class VLLMLLM(BaseLLM):
142
143
  "max_tokens": int(getattr(self.config, "max_tokens", 1024)),
143
144
  "top_p": float(getattr(self.config, "top_p", 0.9)),
144
145
  "stream": True, # Enable streaming
146
+ "extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
145
147
  }
146
148
 
147
149
  stream = self.client.chat.completions.create(**completion_kwargs)
memos/log.py CHANGED
@@ -1,11 +1,23 @@
1
+ import atexit
1
2
  import logging
3
+ import os
4
+ import threading
2
5
 
3
6
  from logging.config import dictConfig
4
7
  from pathlib import Path
5
8
  from sys import stdout
6
9
 
10
+ import requests
11
+
12
+ from dotenv import load_dotenv
13
+
7
14
  from memos import settings
15
+ from memos.api.context.context import get_current_trace_id
16
+ from memos.api.context.context_thread import ContextThreadPoolExecutor
17
+
8
18
 
19
+ # Load environment variables
20
+ load_dotenv()
9
21
 
10
22
  selected_log_level = logging.DEBUG if settings.DEBUG else logging.WARNING
11
23
 
@@ -21,27 +33,126 @@ def _setup_logfile() -> Path:
21
33
  return logfile
22
34
 
23
35
 
36
+ class TraceIDFilter(logging.Filter):
37
+ """add trace_id to the log record"""
38
+
39
+ def filter(self, record):
40
+ try:
41
+ trace_id = get_current_trace_id()
42
+ record.trace_id = trace_id if trace_id else "no-trace-id"
43
+ except Exception:
44
+ record.trace_id = "no-trace-id"
45
+ return True
46
+
47
+
48
+ class CustomLoggerRequestHandler(logging.Handler):
49
+ _instance = None
50
+ _lock = threading.Lock()
51
+
52
+ def __new__(cls):
53
+ if cls._instance is None:
54
+ with cls._lock:
55
+ if cls._instance is None:
56
+ cls._instance = super().__new__(cls)
57
+ cls._instance._initialized = False
58
+ cls._instance._executor = None
59
+ cls._instance._session = None
60
+ cls._instance._is_shutting_down = None
61
+ return cls._instance
62
+
63
+ def __init__(self):
64
+ """Initialize handler with minimal setup"""
65
+ if not self._initialized:
66
+ super().__init__()
67
+ workers = int(os.getenv("CUSTOM_LOGGER_WORKERS", "2"))
68
+ self._executor = ContextThreadPoolExecutor(
69
+ max_workers=workers, thread_name_prefix="log_sender"
70
+ )
71
+ self._is_shutting_down = threading.Event()
72
+ self._session = requests.Session()
73
+ self._initialized = True
74
+ atexit.register(self._cleanup)
75
+
76
+ def emit(self, record):
77
+ """Process log records of INFO or ERROR level (non-blocking)"""
78
+ if os.getenv("CUSTOM_LOGGER_URL") is None or self._is_shutting_down.is_set():
79
+ return
80
+
81
+ try:
82
+ trace_id = get_current_trace_id() or "no-trace-id"
83
+ self._executor.submit(self._send_log_sync, record.getMessage(), trace_id)
84
+ except Exception as e:
85
+ if not self._is_shutting_down.is_set():
86
+ print(f"Error sending log: {e}")
87
+
88
+ def _send_log_sync(self, message, trace_id):
89
+ """Send log message synchronously in a separate thread"""
90
+ try:
91
+ logger_url = os.getenv("CUSTOM_LOGGER_URL")
92
+ token = os.getenv("CUSTOM_LOGGER_TOKEN")
93
+
94
+ headers = {"Content-Type": "application/json"}
95
+ post_content = {"message": message, "trace_id": trace_id}
96
+
97
+ # Add auth token if exists
98
+ if token:
99
+ headers["Authorization"] = f"Bearer {token}"
100
+
101
+ # Add traceId to headers for consistency
102
+ headers["traceId"] = trace_id
103
+
104
+ # Add custom attributes from env
105
+ for key, value in os.environ.items():
106
+ if key.startswith("CUSTOM_LOGGER_ATTRIBUTE_"):
107
+ attribute_key = key[len("CUSTOM_LOGGER_ATTRIBUTE_") :].lower()
108
+ post_content[attribute_key] = value
109
+
110
+ self._session.post(logger_url, headers=headers, json=post_content, timeout=5)
111
+ except Exception:
112
+ # Silently ignore errors to avoid affecting main application
113
+ pass
114
+
115
+ def _cleanup(self):
116
+ """Clean up resources during program exit"""
117
+ if not self._initialized:
118
+ return
119
+
120
+ self._is_shutting_down.set()
121
+ try:
122
+ self._executor.shutdown(wait=False)
123
+ self._session.close()
124
+ except Exception as e:
125
+ print(f"Error during cleanup: {e}")
126
+
127
+ def close(self):
128
+ """Override close to prevent premature shutdown"""
129
+
130
+
24
131
  LOGGING_CONFIG = {
25
132
  "version": 1,
26
133
  "disable_existing_loggers": False,
27
134
  "formatters": {
28
135
  "standard": {
29
- "format": "%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
136
+ "format": "%(asctime)s [%(trace_id)s] - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
30
137
  },
31
138
  "no_datetime": {
32
- "format": "%(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
139
+ "format": "[%(trace_id)s] - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
140
+ },
141
+ "simplified": {
142
+ "format": "%(asctime)s | %(trace_id)s | %(levelname)s | %(filename)s | %(message)s"
33
143
  },
34
144
  },
35
145
  "filters": {
36
- "package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX}
146
+ "package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX},
147
+ "trace_id_filter": {"()": "memos.log.TraceIDFilter"},
37
148
  },
38
149
  "handlers": {
39
150
  "console": {
40
151
  "level": selected_log_level,
41
152
  "class": "logging.StreamHandler",
42
153
  "stream": stdout,
43
- "formatter": "no_datetime",
44
- "filters": ["package_tree_filter"],
154
+ "formatter": "simplified",
155
+ "filters": ["package_tree_filter", "trace_id_filter"],
45
156
  },
46
157
  "file": {
47
158
  "level": "DEBUG",
@@ -49,12 +160,18 @@ LOGGING_CONFIG = {
49
160
  "filename": _setup_logfile(),
50
161
  "maxBytes": 1024**2 * 10,
51
162
  "backupCount": 10,
52
- "formatter": "standard",
163
+ "formatter": "simplified",
164
+ "filters": ["trace_id_filter"],
165
+ },
166
+ "custom_logger": {
167
+ "level": selected_log_level,
168
+ "class": "memos.log.CustomLoggerRequestHandler",
169
+ "formatter": "simplified",
53
170
  },
54
171
  },
55
172
  "root": { # Root logger handles all logs
56
- "level": logging.DEBUG if settings.DEBUG else logging.INFO,
57
- "handlers": ["console", "file"],
173
+ "level": selected_log_level,
174
+ "handlers": ["console", "file", "custom_logger"],
58
175
  },
59
176
  "loggers": {
60
177
  "memos": {
memos/mem_os/core.py CHANGED
@@ -24,6 +24,7 @@ from memos.mem_user.user_manager import UserManager, UserRole
24
24
  from memos.memories.activation.item import ActivationMemoryItem
25
25
  from memos.memories.parametric.item import ParametricMemoryItem
26
26
  from memos.memories.textual.item import TextualMemoryItem, TextualMemoryMetadata
27
+ from memos.memos_tools.thread_safe_dict import ThreadSafeDict
27
28
  from memos.templates.mos_prompts import QUERY_REWRITING_PROMPT
28
29
  from memos.types import ChatHistory, MessageList, MOSSearchResult
29
30
 
@@ -42,10 +43,13 @@ class MOSCore:
42
43
  self.config = config
43
44
  self.user_id = config.user_id
44
45
  self.session_id = config.session_id
45
- self.mem_cubes: dict[str, GeneralMemCube] = {}
46
46
  self.chat_llm = LLMFactory.from_config(config.chat_model)
47
47
  self.mem_reader = MemReaderFactory.from_config(config.mem_reader)
48
48
  self.chat_history_manager: dict[str, ChatHistory] = {}
49
+ # use thread safe dict for multi-user product-server scenario
50
+ self.mem_cubes: ThreadSafeDict[str, GeneralMemCube] = (
51
+ ThreadSafeDict() if user_manager is not None else {}
52
+ )
49
53
  self._register_chat_history()
50
54
 
51
55
  # Use provided user_manager or create a new one
@@ -124,7 +128,7 @@ class MOSCore:
124
128
  chat_llm=self.chat_llm, process_llm=self.chat_llm
125
129
  )
126
130
  else:
127
- # Configure scheduler modules
131
+ # Configure scheduler general_modules
128
132
  self._mem_scheduler.initialize_modules(
129
133
  chat_llm=self.chat_llm, process_llm=self.mem_reader.llm
130
134
  )
@@ -185,7 +189,7 @@ class MOSCore:
185
189
  self.chat_history_manager[user_id] = ChatHistory(
186
190
  user_id=user_id,
187
191
  session_id=self.session_id,
188
- created_at=datetime.now(),
192
+ created_at=datetime.utcnow(),
189
193
  total_messages=0,
190
194
  chat_history=[],
191
195
  )
@@ -279,7 +283,7 @@ class MOSCore:
279
283
  mem_cube=mem_cube,
280
284
  label=QUERY_LABEL,
281
285
  content=query,
282
- timestamp=datetime.now(),
286
+ timestamp=datetime.utcnow(),
283
287
  )
284
288
  self.mem_scheduler.submit_messages(messages=[message_item])
285
289
 
@@ -338,7 +342,7 @@ class MOSCore:
338
342
  mem_cube=mem_cube,
339
343
  label=ANSWER_LABEL,
340
344
  content=response,
341
- timestamp=datetime.now(),
345
+ timestamp=datetime.utcnow(),
342
346
  )
343
347
  self.mem_scheduler.submit_messages(messages=[message_item])
344
348
 
@@ -348,6 +352,7 @@ class MOSCore:
348
352
  self,
349
353
  memories: list[TextualMemoryItem] | list[str] | None = None,
350
354
  base_prompt: str | None = None,
355
+ **kwargs,
351
356
  ) -> str:
352
357
  """Build system prompt with optional memories context."""
353
358
  if base_prompt is None:
@@ -541,6 +546,8 @@ class MOSCore:
541
546
  top_k: int | None = None,
542
547
  mode: Literal["fast", "fine"] = "fast",
543
548
  internet_search: bool = False,
549
+ moscube: bool = False,
550
+ **kwargs,
544
551
  ) -> MOSSearchResult:
545
552
  """
546
553
  Search for textual memories across all registered MemCubes.
@@ -575,7 +582,13 @@ class MOSCore:
575
582
  }
576
583
  if install_cube_ids is None:
577
584
  install_cube_ids = user_cube_ids
578
- for mem_cube_id, mem_cube in self.mem_cubes.items():
585
+ # create exist dict in mem_cubes and avoid one search slow
586
+ tmp_mem_cubes = {}
587
+ for mem_cube_id in install_cube_ids:
588
+ if mem_cube_id in self.mem_cubes:
589
+ tmp_mem_cubes[mem_cube_id] = self.mem_cubes.get(mem_cube_id)
590
+
591
+ for mem_cube_id, mem_cube in tmp_mem_cubes.items():
579
592
  if (
580
593
  (mem_cube_id in install_cube_ids)
581
594
  and (mem_cube.text_mem is not None)
@@ -592,6 +605,7 @@ class MOSCore:
592
605
  "session_id": self.session_id,
593
606
  "chat_history": chat_history.chat_history,
594
607
  },
608
+ moscube=moscube,
595
609
  )
596
610
  result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
597
611
  logger.info(
@@ -649,7 +663,7 @@ class MOSCore:
649
663
  if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
650
664
  add_memory = []
651
665
  metadata = TextualMemoryMetadata(
652
- user_id=self.user_id, session_id=self.session_id, source="conversation"
666
+ user_id=target_user_id, session_id=self.session_id, source="conversation"
653
667
  )
654
668
  for message in messages:
655
669
  add_memory.append(
@@ -681,7 +695,7 @@ class MOSCore:
681
695
  mem_cube=mem_cube,
682
696
  label=ADD_LABEL,
683
697
  content=json.dumps(mem_ids),
684
- timestamp=datetime.now(),
698
+ timestamp=datetime.utcnow(),
685
699
  )
686
700
  self.mem_scheduler.submit_messages(messages=[message_item])
687
701
 
@@ -725,7 +739,7 @@ class MOSCore:
725
739
  mem_cube=mem_cube,
726
740
  label=ADD_LABEL,
727
741
  content=json.dumps(mem_ids),
728
- timestamp=datetime.now(),
742
+ timestamp=datetime.utcnow(),
729
743
  )
730
744
  self.mem_scheduler.submit_messages(messages=[message_item])
731
745
 
@@ -756,7 +770,7 @@ class MOSCore:
756
770
  mem_cube=mem_cube,
757
771
  label=ADD_LABEL,
758
772
  content=json.dumps(mem_ids),
759
- timestamp=datetime.now(),
773
+ timestamp=datetime.utcnow(),
760
774
  )
761
775
  self.mem_scheduler.submit_messages(messages=[message_item])
762
776
 
@@ -946,6 +960,30 @@ class MOSCore:
946
960
  self.mem_cubes[mem_cube_id].dump(dump_dir)
947
961
  logger.info(f"MemCube {mem_cube_id} dumped to {dump_dir}")
948
962
 
963
+ def load(
964
+ self,
965
+ load_dir: str,
966
+ user_id: str | None = None,
967
+ mem_cube_id: str | None = None,
968
+ memory_types: list[Literal["text_mem", "act_mem", "para_mem"]] | None = None,
969
+ ) -> None:
970
+ """Dump the MemCube to a dictionary.
971
+ Args:
972
+ load_dir (str): The directory to load the MemCube from.
973
+ user_id (str, optional): The identifier of the user to load the MemCube from.
974
+ If None, the default user is used.
975
+ mem_cube_id (str, optional): The identifier of the MemCube to load.
976
+ If None, the default MemCube for the user is used.
977
+ """
978
+ target_user_id = user_id if user_id is not None else self.user_id
979
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
980
+ if not mem_cube_id:
981
+ mem_cube_id = accessible_cubes[0].cube_id
982
+ if mem_cube_id not in self.mem_cubes:
983
+ raise ValueError(f"MemCube with ID {mem_cube_id} does not exist. please regiester")
984
+ self.mem_cubes[mem_cube_id].load(load_dir, memory_types=memory_types)
985
+ logger.info(f"MemCube {mem_cube_id} loaded from {load_dir}")
986
+
949
987
  def get_user_info(self) -> dict[str, Any]:
950
988
  """Get current user information including accessible cubes.
951
989
 
@@ -961,7 +999,7 @@ class MOSCore:
961
999
  return {
962
1000
  "user_id": user.user_id,
963
1001
  "user_name": user.user_name,
964
- "role": user.role.value,
1002
+ "role": user.role.value if hasattr(user.role, "value") else user.role,
965
1003
  "created_at": user.created_at.isoformat(),
966
1004
  "accessible_cubes": [
967
1005
  {
memos/mem_os/main.py CHANGED
@@ -219,7 +219,7 @@ class MOS(MOSCore):
219
219
  mem_cube=mem_cube,
220
220
  label=ANSWER_LABEL,
221
221
  content=enhanced_response,
222
- timestamp=datetime.now(),
222
+ timestamp=datetime.now().isoformat(),
223
223
  )
224
224
  self.mem_scheduler.submit_messages(messages=[message_item])
225
225