MemoryOS 0.2.1__py3-none-any.whl → 1.0.0__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 (92) hide show
  1. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/METADATA +7 -1
  2. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/RECORD +87 -64
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +158 -69
  5. memos/api/context/context.py +147 -0
  6. memos/api/context/dependencies.py +101 -0
  7. memos/api/product_models.py +5 -1
  8. memos/api/routers/product_router.py +54 -26
  9. memos/configs/graph_db.py +49 -1
  10. memos/configs/internet_retriever.py +19 -0
  11. memos/configs/mem_os.py +5 -0
  12. memos/configs/mem_reader.py +9 -0
  13. memos/configs/mem_scheduler.py +54 -18
  14. memos/configs/mem_user.py +58 -0
  15. memos/graph_dbs/base.py +38 -3
  16. memos/graph_dbs/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +1612 -0
  18. memos/graph_dbs/neo4j.py +18 -9
  19. memos/log.py +6 -1
  20. memos/mem_cube/utils.py +13 -6
  21. memos/mem_os/core.py +157 -37
  22. memos/mem_os/main.py +2 -2
  23. memos/mem_os/product.py +252 -201
  24. memos/mem_os/utils/default_config.py +1 -1
  25. memos/mem_os/utils/format_utils.py +281 -70
  26. memos/mem_os/utils/reference_utils.py +133 -0
  27. memos/mem_reader/simple_struct.py +13 -5
  28. memos/mem_scheduler/base_scheduler.py +239 -266
  29. memos/mem_scheduler/{modules → general_modules}/base.py +4 -5
  30. memos/mem_scheduler/{modules → general_modules}/dispatcher.py +57 -21
  31. memos/mem_scheduler/general_modules/misc.py +104 -0
  32. memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +12 -10
  33. memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
  34. memos/mem_scheduler/general_modules/retriever.py +199 -0
  35. memos/mem_scheduler/general_modules/scheduler_logger.py +261 -0
  36. memos/mem_scheduler/general_scheduler.py +243 -80
  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} +106 -57
  40. memos/mem_scheduler/mos_for_test_scheduler.py +23 -20
  41. memos/mem_scheduler/schemas/__init__.py +0 -0
  42. memos/mem_scheduler/schemas/general_schemas.py +44 -0
  43. memos/mem_scheduler/schemas/message_schemas.py +149 -0
  44. memos/mem_scheduler/schemas/monitor_schemas.py +337 -0
  45. memos/mem_scheduler/utils/__init__.py +0 -0
  46. memos/mem_scheduler/utils/filter_utils.py +176 -0
  47. memos/mem_scheduler/utils/misc_utils.py +102 -0
  48. memos/mem_user/factory.py +94 -0
  49. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  50. memos/mem_user/mysql_user_manager.py +500 -0
  51. memos/mem_user/persistent_factory.py +96 -0
  52. memos/mem_user/user_manager.py +4 -4
  53. memos/memories/activation/item.py +5 -1
  54. memos/memories/activation/kv.py +20 -8
  55. memos/memories/textual/base.py +2 -2
  56. memos/memories/textual/general.py +36 -92
  57. memos/memories/textual/item.py +5 -33
  58. memos/memories/textual/tree.py +13 -7
  59. memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +34 -50
  60. memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
  61. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +49 -43
  62. memos/memories/textual/tree_text_memory/organize/reorganizer.py +107 -142
  63. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +229 -0
  64. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  65. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +11 -0
  66. memos/memories/textual/tree_text_memory/retrieve/recall.py +15 -8
  67. memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
  68. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  69. memos/memories/textual/tree_text_memory/retrieve/searcher.py +191 -116
  70. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +47 -15
  71. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  72. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  73. memos/memos_tools/dinding_report_bot.py +422 -0
  74. memos/memos_tools/lockfree_dict.py +120 -0
  75. memos/memos_tools/notification_service.py +44 -0
  76. memos/memos_tools/notification_utils.py +96 -0
  77. memos/memos_tools/thread_safe_dict.py +288 -0
  78. memos/settings.py +3 -1
  79. memos/templates/mem_reader_prompts.py +4 -1
  80. memos/templates/mem_scheduler_prompts.py +62 -15
  81. memos/templates/mos_prompts.py +116 -0
  82. memos/templates/tree_reorganize_prompts.py +24 -17
  83. memos/utils.py +19 -0
  84. memos/mem_scheduler/modules/misc.py +0 -39
  85. memos/mem_scheduler/modules/retriever.py +0 -268
  86. memos/mem_scheduler/modules/schemas.py +0 -328
  87. memos/mem_scheduler/utils.py +0 -75
  88. memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
  89. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/LICENSE +0 -0
  90. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/WHEEL +0 -0
  91. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/entry_points.txt +0 -0
  92. /memos/mem_scheduler/{modules → general_modules}/__init__.py +0 -0
@@ -9,30 +9,30 @@ from memos.configs.mem_scheduler import AuthConfig, BaseSchedulerConfig
9
9
  from memos.llms.base import BaseLLM
10
10
  from memos.log import get_logger
11
11
  from memos.mem_cube.general import GeneralMemCube
12
- from memos.mem_scheduler.modules.dispatcher import SchedulerDispatcher
13
- from memos.mem_scheduler.modules.misc import AutoDroppingQueue as Queue
14
- from memos.mem_scheduler.modules.monitor import SchedulerMonitor
15
- from memos.mem_scheduler.modules.rabbitmq_service import RabbitMQSchedulerModule
16
- from memos.mem_scheduler.modules.redis_service import RedisSchedulerModule
17
- from memos.mem_scheduler.modules.retriever import SchedulerRetriever
18
- from memos.mem_scheduler.modules.schemas import (
19
- ACTIVATION_MEMORY_TYPE,
20
- ADD_LABEL,
12
+ from memos.mem_scheduler.general_modules.dispatcher import SchedulerDispatcher
13
+ from memos.mem_scheduler.general_modules.misc import AutoDroppingQueue as Queue
14
+ from memos.mem_scheduler.general_modules.rabbitmq_service import RabbitMQSchedulerModule
15
+ from memos.mem_scheduler.general_modules.redis_service import RedisSchedulerModule
16
+ from memos.mem_scheduler.general_modules.retriever import SchedulerRetriever
17
+ from memos.mem_scheduler.general_modules.scheduler_logger import SchedulerLoggerModule
18
+ from memos.mem_scheduler.monitors.dispatcher_monitor import SchedulerDispatcherMonitor
19
+ from memos.mem_scheduler.monitors.general_monitor import SchedulerGeneralMonitor
20
+ from memos.mem_scheduler.schemas.general_schemas import (
21
21
  DEFAULT_ACT_MEM_DUMP_PATH,
22
22
  DEFAULT_CONSUME_INTERVAL_SECONDS,
23
23
  DEFAULT_THREAD__POOL_MAX_WORKERS,
24
- LONG_TERM_MEMORY_TYPE,
25
- NOT_INITIALIZED,
26
- PARAMETER_MEMORY_TYPE,
27
- QUERY_LABEL,
28
- TEXT_MEMORY_TYPE,
29
- USER_INPUT_TYPE,
30
- WORKING_MEMORY_TYPE,
24
+ MemCubeID,
25
+ TreeTextMemory_SEARCH_METHOD,
26
+ UserID,
27
+ )
28
+ from memos.mem_scheduler.schemas.message_schemas import (
31
29
  ScheduleLogForWebItem,
32
30
  ScheduleMessageItem,
33
- TreeTextMemory_SEARCH_METHOD,
34
31
  )
35
- from memos.mem_scheduler.utils import transform_name_to_key
32
+ from memos.mem_scheduler.schemas.monitor_schemas import MemoryMonitorItem
33
+ from memos.mem_scheduler.utils.filter_utils import (
34
+ transform_name_to_key,
35
+ )
36
36
  from memos.memories.activation.kv import KVCacheMemory
37
37
  from memos.memories.activation.vllmkv import VLLMKVCacheItem, VLLMKVCacheMemory
38
38
  from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
@@ -42,7 +42,7 @@ from memos.templates.mem_scheduler_prompts import MEMORY_ASSEMBLY_TEMPLATE
42
42
  logger = get_logger(__name__)
43
43
 
44
44
 
45
- class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
45
+ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLoggerModule):
46
46
  """Base class for all mem_scheduler."""
47
47
 
48
48
  def __init__(self, config: BaseSchedulerConfig):
@@ -51,22 +51,22 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
51
51
  self.config = config
52
52
 
53
53
  # hyper-parameters
54
- self.top_k = self.config.get("top_k", 5)
54
+ self.top_k = self.config.get("top_k", 10)
55
55
  self.context_window_size = self.config.get("context_window_size", 5)
56
- self.enable_act_memory_update = self.config.get("enable_act_memory_update", False)
56
+ self.enable_activation_memory = self.config.get("enable_activation_memory", False)
57
57
  self.act_mem_dump_path = self.config.get("act_mem_dump_path", DEFAULT_ACT_MEM_DUMP_PATH)
58
58
  self.search_method = TreeTextMemory_SEARCH_METHOD
59
-
60
59
  self.enable_parallel_dispatch = self.config.get("enable_parallel_dispatch", False)
61
- self.max_workers = self.config.get(
60
+ self.thread_pool_max_workers = self.config.get(
62
61
  "thread_pool_max_workers", DEFAULT_THREAD__POOL_MAX_WORKERS
63
62
  )
64
63
 
65
64
  self.retriever: SchedulerRetriever | None = None
66
- self.monitor: SchedulerMonitor | None = None
67
-
65
+ self.monitor: SchedulerGeneralMonitor | None = None
66
+ self.dispatcher_monitor: SchedulerDispatcherMonitor | None = None
68
67
  self.dispatcher = SchedulerDispatcher(
69
- max_workers=self.max_workers, enable_parallel_dispatch=self.enable_parallel_dispatch
68
+ max_workers=self.thread_pool_max_workers,
69
+ enable_parallel_dispatch=self.enable_parallel_dispatch,
70
70
  )
71
71
 
72
72
  # internal message queue
@@ -85,7 +85,9 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
85
85
 
86
86
  # other attributes
87
87
  self._context_lock = threading.Lock()
88
- self._current_user_id: str | None = None
88
+ self.current_user_id: UserID | str | None = None
89
+ self.current_mem_cube_id: MemCubeID | str | None = None
90
+ self.current_mem_cube: GeneralMemCube | None = None
89
91
  self.auth_config_path: str | Path | None = self.config.get("auth_config_path", None)
90
92
  self.auth_config = None
91
93
  self.rabbitmq_config = None
@@ -97,15 +99,19 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
97
99
  # initialize submodules
98
100
  self.chat_llm = chat_llm
99
101
  self.process_llm = process_llm
100
- self.monitor = SchedulerMonitor(process_llm=self.process_llm, config=self.config)
102
+ self.monitor = SchedulerGeneralMonitor(process_llm=self.process_llm, config=self.config)
103
+ self.dispatcher_monitor = SchedulerDispatcherMonitor(config=self.config)
101
104
  self.retriever = SchedulerRetriever(process_llm=self.process_llm, config=self.config)
102
- self.retriever.log_working_memory_replacement = self.log_working_memory_replacement
105
+
106
+ if self.enable_parallel_dispatch:
107
+ self.dispatcher_monitor.initialize(dispatcher=self.dispatcher)
108
+ self.dispatcher_monitor.start()
103
109
 
104
110
  # initialize with auth_cofig
105
111
  if self.auth_config_path is not None and Path(self.auth_config_path).exists():
106
- self.auth_config = AuthConfig.from_local_yaml(config_path=self.auth_config_path)
112
+ self.auth_config = AuthConfig.from_local_config(config_path=self.auth_config_path)
107
113
  elif AuthConfig.default_config_exists():
108
- self.auth_config = AuthConfig.from_local_yaml()
114
+ self.auth_config = AuthConfig.from_local_config()
109
115
  else:
110
116
  self.auth_config = None
111
117
 
@@ -118,58 +124,145 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
118
124
  @property
119
125
  def mem_cube(self) -> GeneralMemCube:
120
126
  """The memory cube associated with this MemChat."""
121
- return self._current_mem_cube
127
+ return self.current_mem_cube
122
128
 
123
129
  @mem_cube.setter
124
130
  def mem_cube(self, value: GeneralMemCube) -> None:
125
131
  """The memory cube associated with this MemChat."""
126
- self._current_mem_cube = value
132
+ self.current_mem_cube = value
127
133
  self.retriever.mem_cube = value
128
134
 
129
135
  def _set_current_context_from_message(self, msg: ScheduleMessageItem) -> None:
130
136
  """Update current user/cube context from the incoming message (thread-safe)."""
131
137
  with self._context_lock:
132
- self._current_user_id = msg.user_id
133
- self._current_mem_cube_id = msg.mem_cube_id
134
- self._current_mem_cube = msg.mem_cube
138
+ self.current_user_id = msg.user_id
139
+ self.current_mem_cube_id = msg.mem_cube_id
140
+ self.current_mem_cube = msg.mem_cube
135
141
 
136
- def _validate_messages(self, messages: list[ScheduleMessageItem], label: str):
137
- """Validate if all messages match the expected label.
142
+ def transform_working_memories_to_monitors(
143
+ self, query_keywords, memories: list[TextualMemoryItem]
144
+ ) -> list[MemoryMonitorItem]:
145
+ """
146
+ Convert a list of TextualMemoryItem objects into MemoryMonitorItem objects
147
+ with importance scores based on keyword matching.
138
148
 
139
149
  Args:
140
- messages: List of message items to validate.
141
- label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
150
+ memories: List of TextualMemoryItem objects to be transformed.
142
151
 
143
152
  Returns:
144
- bool: True if all messages passed validation, False if any failed.
153
+ List of MemoryMonitorItem objects with computed importance scores.
145
154
  """
146
- for message in messages:
147
- if not self._validate_message(message, label):
148
- return False
149
- logger.error("Message batch contains invalid labels, aborting processing")
150
- return True
151
155
 
152
- def _validate_message(self, message: ScheduleMessageItem, label: str):
153
- """Validate if the message matches the expected label.
156
+ result = []
157
+ mem_length = len(memories)
158
+ for idx, mem in enumerate(memories):
159
+ text_mem = mem.memory
160
+ mem_key = transform_name_to_key(name=text_mem)
161
+
162
+ # Calculate importance score based on keyword matches
163
+ keywords_score = 0
164
+ if query_keywords and text_mem:
165
+ for keyword, count in query_keywords.items():
166
+ keyword_count = text_mem.count(keyword)
167
+ if keyword_count > 0:
168
+ keywords_score += keyword_count * count
169
+ logger.debug(
170
+ f"Matched keyword '{keyword}' {keyword_count} times, added {keywords_score} to keywords_score"
171
+ )
172
+
173
+ # rank score
174
+ sorting_score = mem_length - idx
175
+
176
+ mem_monitor = MemoryMonitorItem(
177
+ memory_text=text_mem,
178
+ tree_memory_item=mem,
179
+ tree_memory_item_mapping_key=mem_key,
180
+ sorting_score=sorting_score,
181
+ keywords_score=keywords_score,
182
+ recording_count=1,
183
+ )
184
+ result.append(mem_monitor)
154
185
 
155
- Args:
156
- message: Incoming message item to validate.
157
- label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
186
+ logger.info(f"Transformed {len(result)} memories to monitors")
187
+ return result
158
188
 
159
- Returns:
160
- bool: True if validation passed, False otherwise.
161
- """
162
- if message.label != label:
163
- logger.error(f"Handler validation failed: expected={label}, actual={message.label}")
164
- return False
165
- return True
189
+ def replace_working_memory(
190
+ self,
191
+ user_id: UserID | str,
192
+ mem_cube_id: MemCubeID | str,
193
+ mem_cube: GeneralMemCube,
194
+ original_memory: list[TextualMemoryItem],
195
+ new_memory: list[TextualMemoryItem],
196
+ ) -> None | list[TextualMemoryItem]:
197
+ """Replace working memory with new memories after reranking."""
198
+ text_mem_base = mem_cube.text_mem
199
+ if isinstance(text_mem_base, TreeTextMemory):
200
+ text_mem_base: TreeTextMemory = text_mem_base
201
+
202
+ # process rerank memories with llm
203
+ query_monitor = self.monitor.query_monitors[user_id][mem_cube_id]
204
+ query_history = query_monitor.get_queries_with_timesort()
205
+ memories_with_new_order, rerank_success_flag = (
206
+ self.retriever.process_and_rerank_memories(
207
+ queries=query_history,
208
+ original_memory=original_memory,
209
+ new_memory=new_memory,
210
+ top_k=self.top_k,
211
+ )
212
+ )
213
+
214
+ # update working memory monitors
215
+ query_keywords = query_monitor.get_keywords_collections()
216
+ logger.info(
217
+ f"Processing {len(memories_with_new_order)} memories with {len(query_keywords)} query keywords"
218
+ )
219
+ new_working_memory_monitors = self.transform_working_memories_to_monitors(
220
+ query_keywords=query_keywords,
221
+ memories=memories_with_new_order,
222
+ )
223
+
224
+ if not rerank_success_flag:
225
+ for one in new_working_memory_monitors:
226
+ one.sorting_score = 0
227
+
228
+ logger.info(f"update {len(new_working_memory_monitors)} working_memory_monitors")
229
+ self.monitor.update_working_memory_monitors(
230
+ new_working_memory_monitors=new_working_memory_monitors,
231
+ user_id=user_id,
232
+ mem_cube_id=mem_cube_id,
233
+ mem_cube=mem_cube,
234
+ )
235
+
236
+ mem_monitors: list[MemoryMonitorItem] = self.monitor.working_memory_monitors[user_id][
237
+ mem_cube_id
238
+ ].get_sorted_mem_monitors(reverse=True)
239
+ new_working_memories = [mem_monitor.tree_memory_item for mem_monitor in mem_monitors]
240
+
241
+ text_mem_base.replace_working_memory(memories=new_working_memories)
242
+
243
+ logger.info(
244
+ f"The working memory has been replaced with {len(memories_with_new_order)} new memories."
245
+ )
246
+ self.log_working_memory_replacement(
247
+ original_memory=original_memory,
248
+ new_memory=new_working_memories,
249
+ user_id=user_id,
250
+ mem_cube_id=mem_cube_id,
251
+ mem_cube=mem_cube,
252
+ log_func_callback=self._submit_web_logs,
253
+ )
254
+ else:
255
+ logger.error("memory_base is not supported")
256
+ memories_with_new_order = new_memory
257
+
258
+ return memories_with_new_order
166
259
 
167
260
  def update_activation_memory(
168
261
  self,
169
262
  new_memories: list[str | TextualMemoryItem],
170
263
  label: str,
171
- user_id: str,
172
- mem_cube_id: str,
264
+ user_id: UserID | str,
265
+ mem_cube_id: MemCubeID | str,
173
266
  mem_cube: GeneralMemCube,
174
267
  ) -> None:
175
268
  """
@@ -195,7 +288,7 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
195
288
  logger.error("Not Implemented.")
196
289
  return
197
290
 
198
- text_memory = MEMORY_ASSEMBLY_TEMPLATE.format(
291
+ new_text_memory = MEMORY_ASSEMBLY_TEMPLATE.format(
199
292
  memory_text="".join(
200
293
  [
201
294
  f"{i + 1}. {sentence.strip()}\n"
@@ -211,10 +304,20 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
211
304
  if len(original_cache_items) > 0:
212
305
  pre_cache_item: VLLMKVCacheItem = original_cache_items[-1]
213
306
  original_text_memories = pre_cache_item.records.text_memories
307
+ original_composed_text_memory = pre_cache_item.records.composed_text_memory
308
+ if original_composed_text_memory == new_text_memory:
309
+ logger.warning(
310
+ "Skipping memory update - new composition matches existing cache: %s",
311
+ new_text_memory[:50] + "..."
312
+ if len(new_text_memory) > 50
313
+ else new_text_memory,
314
+ )
315
+ return
214
316
  act_mem.delete_all()
215
317
 
216
- cache_item = act_mem.extract(text_memory)
318
+ cache_item = act_mem.extract(new_text_memory)
217
319
  cache_item.records.text_memories = new_text_memories
320
+ cache_item.records.timestamp = datetime.utcnow()
218
321
 
219
322
  act_mem.add([cache_item])
220
323
  act_mem.dump(self.act_mem_dump_path)
@@ -226,6 +329,7 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
226
329
  user_id=user_id,
227
330
  mem_cube_id=mem_cube_id,
228
331
  mem_cube=mem_cube,
332
+ log_func_callback=self._submit_web_logs,
229
333
  )
230
334
 
231
335
  except Exception as e:
@@ -235,50 +339,72 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
235
339
  self,
236
340
  interval_seconds: int,
237
341
  label: str,
238
- user_id: str,
239
- mem_cube_id: str,
342
+ user_id: UserID | str,
343
+ mem_cube_id: MemCubeID | str,
240
344
  mem_cube: GeneralMemCube,
241
345
  ):
242
- new_activation_memories = []
243
-
244
- if self.monitor.timed_trigger(
245
- last_time=self.monitor._last_activation_mem_update_time,
246
- interval_seconds=interval_seconds,
247
- ):
248
- logger.info(f"Updating activation memory for user {user_id} and mem_cube {mem_cube_id}")
346
+ try:
347
+ if (
348
+ self.monitor.last_activation_mem_update_time == datetime.min
349
+ or self.monitor.timed_trigger(
350
+ last_time=self.monitor.last_activation_mem_update_time,
351
+ interval_seconds=interval_seconds,
352
+ )
353
+ ):
354
+ logger.info(
355
+ f"Updating activation memory for user {user_id} and mem_cube {mem_cube_id}"
356
+ )
249
357
 
250
- self.monitor.update_memory_monitors(
251
- user_id=user_id, mem_cube_id=mem_cube_id, mem_cube=mem_cube
252
- )
358
+ if (
359
+ user_id not in self.monitor.working_memory_monitors
360
+ or mem_cube_id not in self.monitor.working_memory_monitors[user_id]
361
+ or len(self.monitor.working_memory_monitors[user_id][mem_cube_id].memories) == 0
362
+ ):
363
+ logger.warning(
364
+ "No memories found in working_memory_monitors, activation memory update is skipped"
365
+ )
366
+ return
367
+
368
+ self.monitor.update_activation_memory_monitors(
369
+ user_id=user_id, mem_cube_id=mem_cube_id, mem_cube=mem_cube
370
+ )
253
371
 
254
- new_activation_memories = [
255
- m.memory_text
256
- for m in self.monitor.activation_memory_monitors[user_id][mem_cube_id].memories
257
- ]
372
+ new_activation_memories = [
373
+ m.memory_text
374
+ for m in self.monitor.activation_memory_monitors[user_id][mem_cube_id].memories
375
+ ]
258
376
 
259
- logger.info(
260
- f"Collected {len(new_activation_memories)} new memory entries for processing"
261
- )
377
+ logger.info(
378
+ f"Collected {len(new_activation_memories)} new memory entries for processing"
379
+ )
380
+ # Print the content of each new activation memory
381
+ for i, memory in enumerate(new_activation_memories[:5], 1):
382
+ logger.info(
383
+ f"Part of New Activation Memorires | {i}/{len(new_activation_memories)}: {memory[:20]}"
384
+ )
385
+
386
+ self.update_activation_memory(
387
+ new_memories=new_activation_memories,
388
+ label=label,
389
+ user_id=user_id,
390
+ mem_cube_id=mem_cube_id,
391
+ mem_cube=mem_cube,
392
+ )
262
393
 
263
- self.update_activation_memory(
264
- new_memories=new_activation_memories,
265
- label=label,
266
- user_id=user_id,
267
- mem_cube_id=mem_cube_id,
268
- mem_cube=mem_cube,
269
- )
394
+ self.monitor.last_activation_mem_update_time = datetime.utcnow()
270
395
 
271
- self.monitor._last_activation_mem_update_time = datetime.now()
396
+ logger.debug(
397
+ f"Activation memory update completed at {self.monitor.last_activation_mem_update_time}"
398
+ )
272
399
 
273
- logger.debug(
274
- f"Activation memory update completed at {self.monitor._last_activation_mem_update_time}"
275
- )
276
- else:
277
- logger.info(
278
- f"Skipping update - {interval_seconds} second interval not yet reached. "
279
- f"Last update time is {self.monitor._last_activation_mem_update_time} and now is"
280
- f"{datetime.now()}"
281
- )
400
+ else:
401
+ logger.info(
402
+ f"Skipping update - {interval_seconds} second interval not yet reached. "
403
+ f"Last update time is {self.monitor.last_activation_mem_update_time} and now is"
404
+ f"{datetime.utcnow()}"
405
+ )
406
+ except Exception as e:
407
+ logger.error(f"Error in update_activation_memory_periodically: {e}", exc_info=True)
282
408
 
283
409
  def submit_messages(self, messages: ScheduleMessageItem | list[ScheduleMessageItem]):
284
410
  """Submit multiple messages to the message queue."""
@@ -289,7 +415,9 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
289
415
  self.memos_message_queue.put(message)
290
416
  logger.info(f"Submitted message: {message.label} - {message.content}")
291
417
 
292
- def _submit_web_logs(self, messages: ScheduleLogForWebItem | list[ScheduleLogForWebItem]):
418
+ def _submit_web_logs(
419
+ self, messages: ScheduleLogForWebItem | list[ScheduleLogForWebItem]
420
+ ) -> None:
293
421
  """Submit log messages to the web log queue and optionally to RabbitMQ.
294
422
 
295
423
  Args:
@@ -300,176 +428,14 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
300
428
 
301
429
  for message in messages:
302
430
  self._web_log_message_queue.put(message)
303
- logger.info(f"Submitted Scheduling log for web: {message.log_content}")
431
+ message_info = message.debug_info()
432
+ logger.debug(f"Submitted Scheduling log for web: {message_info}")
304
433
 
305
434
  if self.is_rabbitmq_connected():
306
- logger.info("Submitted Scheduling log to rabbitmq")
435
+ logger.info(f"Submitted Scheduling log to rabbitmq: {message_info}")
307
436
  self.rabbitmq_publish_message(message=message.to_dict())
308
437
  logger.debug(f"{len(messages)} submitted. {self._web_log_message_queue.qsize()} in queue.")
309
438
 
310
- def log_activation_memory_update(
311
- self,
312
- original_text_memories: list[str],
313
- new_text_memories: list[str],
314
- label: str,
315
- user_id: str,
316
- mem_cube_id: str,
317
- mem_cube: GeneralMemCube,
318
- ):
319
- """Log changes when activation memory is updated.
320
-
321
- Args:
322
- original_text_memories: List of original memory texts
323
- new_text_memories: List of new memory texts
324
- """
325
- original_set = set(original_text_memories)
326
- new_set = set(new_text_memories)
327
-
328
- # Identify changes
329
- added_memories = list(new_set - original_set) # Present in new but not original
330
-
331
- # recording messages
332
- for mem in added_memories:
333
- log_message_a = self.create_autofilled_log_item(
334
- log_content=mem,
335
- label=label,
336
- from_memory_type=TEXT_MEMORY_TYPE,
337
- to_memory_type=ACTIVATION_MEMORY_TYPE,
338
- user_id=user_id,
339
- mem_cube_id=mem_cube_id,
340
- mem_cube=mem_cube,
341
- )
342
- log_message_b = self.create_autofilled_log_item(
343
- log_content=mem,
344
- label=label,
345
- from_memory_type=ACTIVATION_MEMORY_TYPE,
346
- to_memory_type=PARAMETER_MEMORY_TYPE,
347
- user_id=user_id,
348
- mem_cube_id=mem_cube_id,
349
- mem_cube=mem_cube,
350
- )
351
- self._submit_web_logs(messages=[log_message_a, log_message_b])
352
- logger.info(
353
- f"{len(added_memories)} {LONG_TERM_MEMORY_TYPE} memorie(s) "
354
- f"transformed to {WORKING_MEMORY_TYPE} memories."
355
- )
356
-
357
- def log_working_memory_replacement(
358
- self,
359
- original_memory: list[TextualMemoryItem],
360
- new_memory: list[TextualMemoryItem],
361
- user_id: str,
362
- mem_cube_id: str,
363
- mem_cube: GeneralMemCube,
364
- ):
365
- """Log changes when working memory is replaced."""
366
- memory_type_map = {
367
- transform_name_to_key(name=m.memory): m.metadata.memory_type
368
- for m in original_memory + new_memory
369
- }
370
-
371
- original_text_memories = [m.memory for m in original_memory]
372
- new_text_memories = [m.memory for m in new_memory]
373
-
374
- # Convert to sets for efficient difference operations
375
- original_set = set(original_text_memories)
376
- new_set = set(new_text_memories)
377
-
378
- # Identify changes
379
- added_memories = list(new_set - original_set) # Present in new but not original
380
-
381
- # recording messages
382
- for mem in added_memories:
383
- normalized_mem = transform_name_to_key(name=mem)
384
- if normalized_mem not in memory_type_map:
385
- logger.error(f"Memory text not found in type mapping: {mem[:50]}...")
386
- # Get the memory type from the map, default to LONG_TERM_MEMORY_TYPE if not found
387
- mem_type = memory_type_map.get(normalized_mem, LONG_TERM_MEMORY_TYPE)
388
-
389
- if mem_type == WORKING_MEMORY_TYPE:
390
- logger.warning(f"Memory already in working memory: {mem[:50]}...")
391
- continue
392
-
393
- log_message = self.create_autofilled_log_item(
394
- log_content=mem,
395
- label=QUERY_LABEL,
396
- from_memory_type=mem_type,
397
- to_memory_type=WORKING_MEMORY_TYPE,
398
- user_id=user_id,
399
- mem_cube_id=mem_cube_id,
400
- mem_cube=mem_cube,
401
- )
402
- self._submit_web_logs(messages=log_message)
403
- logger.info(
404
- f"{len(added_memories)} {LONG_TERM_MEMORY_TYPE} memorie(s) "
405
- f"transformed to {WORKING_MEMORY_TYPE} memories."
406
- )
407
-
408
- def log_adding_user_inputs(
409
- self,
410
- user_inputs: list[str],
411
- user_id: str,
412
- mem_cube_id: str,
413
- mem_cube: GeneralMemCube,
414
- ):
415
- """Log changes when working memory is replaced."""
416
-
417
- # recording messages
418
- for input_str in user_inputs:
419
- log_message = self.create_autofilled_log_item(
420
- log_content=input_str,
421
- label=ADD_LABEL,
422
- from_memory_type=USER_INPUT_TYPE,
423
- to_memory_type=TEXT_MEMORY_TYPE,
424
- user_id=user_id,
425
- mem_cube_id=mem_cube_id,
426
- mem_cube=mem_cube,
427
- )
428
- self._submit_web_logs(messages=log_message)
429
- logger.info(
430
- f"{len(user_inputs)} {USER_INPUT_TYPE} memorie(s) "
431
- f"transformed to {TEXT_MEMORY_TYPE} memories."
432
- )
433
-
434
- def create_autofilled_log_item(
435
- self,
436
- log_content: str,
437
- label: str,
438
- from_memory_type: str,
439
- to_memory_type: str,
440
- user_id: str,
441
- mem_cube_id: str,
442
- mem_cube: GeneralMemCube,
443
- ) -> ScheduleLogForWebItem:
444
- text_mem_base: TreeTextMemory = mem_cube.text_mem
445
- current_memory_sizes = text_mem_base.get_current_memory_size()
446
- current_memory_sizes = {
447
- "long_term_memory_size": current_memory_sizes["LongTermMemory"],
448
- "user_memory_size": current_memory_sizes["UserMemory"],
449
- "working_memory_size": current_memory_sizes["WorkingMemory"],
450
- "transformed_act_memory_size": NOT_INITIALIZED,
451
- "parameter_memory_size": NOT_INITIALIZED,
452
- }
453
- memory_capacities = {
454
- "long_term_memory_capacity": text_mem_base.memory_manager.memory_size["LongTermMemory"],
455
- "user_memory_capacity": text_mem_base.memory_manager.memory_size["UserMemory"],
456
- "working_memory_capacity": text_mem_base.memory_manager.memory_size["WorkingMemory"],
457
- "transformed_act_memory_capacity": NOT_INITIALIZED,
458
- "parameter_memory_capacity": NOT_INITIALIZED,
459
- }
460
-
461
- log_message = ScheduleLogForWebItem(
462
- user_id=user_id,
463
- mem_cube_id=mem_cube_id,
464
- label=label,
465
- from_memory_type=from_memory_type,
466
- to_memory_type=to_memory_type,
467
- log_content=log_content,
468
- current_memory_sizes=current_memory_sizes,
469
- memory_capacities=memory_capacities,
470
- )
471
- return log_message
472
-
473
439
  def get_web_log_messages(self) -> list[dict]:
474
440
  """
475
441
  Retrieves all web log messages from the queue and returns them as a list of JSON-serializable dictionaries.
@@ -536,7 +502,9 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
536
502
 
537
503
  # Initialize dispatcher resources
538
504
  if self.enable_parallel_dispatch:
539
- logger.info(f"Initializing dispatcher thread pool with {self.max_workers} workers")
505
+ logger.info(
506
+ f"Initializing dispatcher thread pool with {self.thread_pool_max_workers} workers"
507
+ )
540
508
 
541
509
  # Start consumer thread
542
510
  self._running = True
@@ -571,10 +539,15 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
571
539
  logger.info("Consumer thread stopped")
572
540
 
573
541
  # Shutdown dispatcher
574
- if hasattr(self, "dispatcher") and self.dispatcher:
542
+ if self.dispatcher:
575
543
  logger.info("Shutting down dispatcher...")
576
544
  self.dispatcher.shutdown()
577
545
 
546
+ # Shutdown dispatcher_monitor
547
+ if self.dispatcher_monitor:
548
+ logger.info("Shutting down monitor...")
549
+ self.dispatcher_monitor.stop()
550
+
578
551
  # Clean up queues
579
552
  self._cleanup_queues()
580
553
  logger.info("Memory Scheduler stopped completely")