noesium 0.1.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.
Files changed (86) hide show
  1. noesium/core/__init__.py +4 -0
  2. noesium/core/agent/__init__.py +14 -0
  3. noesium/core/agent/base.py +227 -0
  4. noesium/core/consts.py +6 -0
  5. noesium/core/goalith/conflict/conflict.py +104 -0
  6. noesium/core/goalith/conflict/detector.py +53 -0
  7. noesium/core/goalith/decomposer/__init__.py +6 -0
  8. noesium/core/goalith/decomposer/base.py +46 -0
  9. noesium/core/goalith/decomposer/callable_decomposer.py +65 -0
  10. noesium/core/goalith/decomposer/llm_decomposer.py +326 -0
  11. noesium/core/goalith/decomposer/prompts.py +140 -0
  12. noesium/core/goalith/decomposer/simple_decomposer.py +61 -0
  13. noesium/core/goalith/errors.py +22 -0
  14. noesium/core/goalith/goalgraph/graph.py +526 -0
  15. noesium/core/goalith/goalgraph/node.py +179 -0
  16. noesium/core/goalith/replanner/base.py +31 -0
  17. noesium/core/goalith/replanner/replanner.py +36 -0
  18. noesium/core/goalith/service.py +26 -0
  19. noesium/core/llm/__init__.py +154 -0
  20. noesium/core/llm/base.py +152 -0
  21. noesium/core/llm/litellm.py +528 -0
  22. noesium/core/llm/llamacpp.py +487 -0
  23. noesium/core/llm/message.py +184 -0
  24. noesium/core/llm/ollama.py +459 -0
  25. noesium/core/llm/openai.py +520 -0
  26. noesium/core/llm/openrouter.py +89 -0
  27. noesium/core/llm/prompt.py +551 -0
  28. noesium/core/memory/__init__.py +11 -0
  29. noesium/core/memory/base.py +464 -0
  30. noesium/core/memory/memu/__init__.py +24 -0
  31. noesium/core/memory/memu/config/__init__.py +26 -0
  32. noesium/core/memory/memu/config/activity/config.py +46 -0
  33. noesium/core/memory/memu/config/event/config.py +46 -0
  34. noesium/core/memory/memu/config/markdown_config.py +241 -0
  35. noesium/core/memory/memu/config/profile/config.py +48 -0
  36. noesium/core/memory/memu/llm_adapter.py +129 -0
  37. noesium/core/memory/memu/memory/__init__.py +31 -0
  38. noesium/core/memory/memu/memory/actions/__init__.py +40 -0
  39. noesium/core/memory/memu/memory/actions/add_activity_memory.py +299 -0
  40. noesium/core/memory/memu/memory/actions/base_action.py +342 -0
  41. noesium/core/memory/memu/memory/actions/cluster_memories.py +262 -0
  42. noesium/core/memory/memu/memory/actions/generate_suggestions.py +198 -0
  43. noesium/core/memory/memu/memory/actions/get_available_categories.py +66 -0
  44. noesium/core/memory/memu/memory/actions/link_related_memories.py +515 -0
  45. noesium/core/memory/memu/memory/actions/run_theory_of_mind.py +254 -0
  46. noesium/core/memory/memu/memory/actions/update_memory_with_suggestions.py +514 -0
  47. noesium/core/memory/memu/memory/embeddings.py +130 -0
  48. noesium/core/memory/memu/memory/file_manager.py +306 -0
  49. noesium/core/memory/memu/memory/memory_agent.py +578 -0
  50. noesium/core/memory/memu/memory/recall_agent.py +376 -0
  51. noesium/core/memory/memu/memory_store.py +628 -0
  52. noesium/core/memory/models.py +149 -0
  53. noesium/core/msgbus/__init__.py +12 -0
  54. noesium/core/msgbus/base.py +395 -0
  55. noesium/core/orchestrix/__init__.py +0 -0
  56. noesium/core/py.typed +0 -0
  57. noesium/core/routing/__init__.py +20 -0
  58. noesium/core/routing/base.py +66 -0
  59. noesium/core/routing/router.py +241 -0
  60. noesium/core/routing/strategies/__init__.py +9 -0
  61. noesium/core/routing/strategies/dynamic_complexity.py +361 -0
  62. noesium/core/routing/strategies/self_assessment.py +147 -0
  63. noesium/core/routing/types.py +38 -0
  64. noesium/core/toolify/__init__.py +39 -0
  65. noesium/core/toolify/base.py +360 -0
  66. noesium/core/toolify/config.py +138 -0
  67. noesium/core/toolify/mcp_integration.py +275 -0
  68. noesium/core/toolify/registry.py +214 -0
  69. noesium/core/toolify/toolkits/__init__.py +1 -0
  70. noesium/core/tracing/__init__.py +37 -0
  71. noesium/core/tracing/langgraph_hooks.py +308 -0
  72. noesium/core/tracing/opik_tracing.py +144 -0
  73. noesium/core/tracing/token_tracker.py +166 -0
  74. noesium/core/utils/__init__.py +10 -0
  75. noesium/core/utils/logging.py +172 -0
  76. noesium/core/utils/statistics.py +12 -0
  77. noesium/core/utils/typing.py +17 -0
  78. noesium/core/vector_store/__init__.py +79 -0
  79. noesium/core/vector_store/base.py +94 -0
  80. noesium/core/vector_store/pgvector.py +304 -0
  81. noesium/core/vector_store/weaviate.py +383 -0
  82. noesium-0.1.0.dist-info/METADATA +525 -0
  83. noesium-0.1.0.dist-info/RECORD +86 -0
  84. noesium-0.1.0.dist-info/WHEEL +5 -0
  85. noesium-0.1.0.dist-info/licenses/LICENSE +21 -0
  86. noesium-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,578 @@
1
+ """
2
+ MemU Memory Agent - Action-Based Architecture
3
+
4
+ Modern memory management system with function calling interface.
5
+ Each operation is implemented as a separate action module for modularity and maintainability.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import threading
11
+ import time
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import Any, Callable, Dict, List, Optional
15
+
16
+ from ..config.markdown_config import get_config_manager
17
+ from ..llm_adapter import BaseLLMClient
18
+ from .actions import ACTION_REGISTRY
19
+ from .embeddings import create_embedding_client
20
+ from .file_manager import MemoryFileManager
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class MemoryCore:
26
+ """
27
+ Core memory functionality shared across all actions
28
+
29
+ Provides the shared resources and utilities that actions need:
30
+ - LLM client
31
+ - Storage manager
32
+ - Embedding client
33
+ - Configuration
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ llm_client: BaseLLMClient = None,
39
+ memory_dir: str = "memu/server/memory",
40
+ enable_embeddings: bool = True,
41
+ agent_id: str = "",
42
+ user_id: str = "",
43
+ ):
44
+ self.llm_client = llm_client
45
+ self.memory_dir = Path(memory_dir)
46
+ self._stop_flag = threading.Event()
47
+
48
+ # Initialize config manager and processing order (basic only)
49
+ self.config_manager = get_config_manager()
50
+ self.processing_order = self.config_manager.get_processing_order()
51
+
52
+ # Initialize file-based storage manager with context (shared by actions)
53
+ self.storage_manager = MemoryFileManager(memory_dir, agent_id=agent_id, user_id=user_id)
54
+ # Initialize memory types from storage manager (includes cluster for this context)
55
+ self.memory_types = self.storage_manager.memory_types
56
+
57
+ # Initialize embedding client using the LLM client
58
+ self.enable_embeddings = enable_embeddings
59
+ if enable_embeddings and llm_client:
60
+ try:
61
+ self.embedding_client = create_embedding_client(llm_client)
62
+ self.embeddings_enabled = True
63
+ logger.info("Embeddings enabled for semantic retrieval using LLM client")
64
+ except Exception as e:
65
+ logger.warning(f"Failed to initialize embedding client with LLM client: {e}. Embeddings disabled.")
66
+ self.embedding_client = None
67
+ self.embeddings_enabled = False
68
+ else:
69
+ self.embedding_client = None
70
+ self.embeddings_enabled = False
71
+ if enable_embeddings and not llm_client:
72
+ logger.warning("Embeddings requested but no LLM client provided. Embeddings disabled.")
73
+
74
+ # Create storage directories
75
+ self.embeddings_dir = self.memory_dir / "embeddings"
76
+ self.embeddings_dir.mkdir(exist_ok=True)
77
+
78
+ logger.info(
79
+ f"Memory Core initialized: {len(self.memory_types)} memory types, embeddings: {self.embeddings_enabled}"
80
+ )
81
+
82
+
83
+ class MemoryAgent:
84
+ """
85
+ Modern Memory Agent with Action-Based Architecture
86
+
87
+ Uses independent action modules for each memory operation:
88
+ - add_activity_memory: Add new activity memory content with strict formatting
89
+ - get_available_categories: Get available categories (excluding activity)
90
+ - link_related_memories: Find and link related memories using embedding search
91
+ - generate_memory_suggestions: Generate suggestions for memory categories
92
+ - update_memory_with_suggestions: Update memory categories based on suggestions
93
+
94
+
95
+ Each action is implemented as a separate module in the actions/ directory.
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ *,
101
+ llm_client: Optional[BaseLLMClient] = None,
102
+ agent_id: str = "default_agent",
103
+ user_id: str = "default_user",
104
+ memory_dir: str = "default_memory",
105
+ enable_embeddings: bool = True,
106
+ ):
107
+ """
108
+ Initialize Memory Agent
109
+
110
+ Args:
111
+ memory_dir: Directory to store memory files
112
+ enable_embeddings: Whether to generate embeddings for semantic search
113
+ """
114
+ # Initialize LLM client if not provided
115
+ if llm_client is None:
116
+ try:
117
+ from ..llm_adapter import _get_llm_client_memu_compatible
118
+
119
+ llm_client = _get_llm_client_memu_compatible()
120
+ except Exception as e:
121
+ logger.warning(f"Failed to initialize default LLM client: {e}")
122
+
123
+ self.llm_client = llm_client
124
+
125
+ # Initialize memory core
126
+ self.memory_core = MemoryCore(
127
+ llm_client=llm_client,
128
+ memory_dir=memory_dir,
129
+ enable_embeddings=enable_embeddings,
130
+ agent_id=agent_id,
131
+ user_id=user_id,
132
+ )
133
+
134
+ # Initialize actions
135
+ self.actions = {}
136
+ self._load_actions()
137
+
138
+ # Build function registry for compatibility
139
+ self.function_registry = self._build_function_registry()
140
+
141
+ logger.info(f"Memory Agent initialized: {len(self.actions)} actions available")
142
+
143
+ def _load_actions(self):
144
+ """Load all available actions from the registry"""
145
+ for action_name, action_class in ACTION_REGISTRY.items():
146
+ try:
147
+ action_instance = action_class(self.memory_core)
148
+ self.actions[action_name] = action_instance
149
+ logger.debug(f"Loaded action: {action_name}")
150
+ except Exception as e:
151
+ logger.error(f"Failed to load action {action_name}: {e}")
152
+
153
+ def _build_function_registry(self) -> Dict[str, Callable]:
154
+ """Build registry of callable functions from actions"""
155
+ registry = {}
156
+ for action_name, action in self.actions.items():
157
+ registry[action_name] = action.execute
158
+ return registry
159
+
160
+ # ================================
161
+ # Smart Conversation Processing
162
+ # ================================
163
+
164
+ def run(
165
+ self,
166
+ conversation: List[Dict[str, str]],
167
+ character_name: str,
168
+ max_iterations: int = 20,
169
+ session_date: str = None,
170
+ ) -> Dict[str, Any]:
171
+ """
172
+ Intelligent conversation processing using iterative function calling
173
+
174
+ This function allows the LLM to autonomously decide which memory operations to perform
175
+ through function calling, iterating until the LLM decides it's complete or max iterations reached.
176
+
177
+ Args:
178
+ conversation: List of conversation messages
179
+ character_name: Name of the character to store memories for
180
+ max_iterations: Maximum number of function calling iterations (default: 20)
181
+
182
+ Returns:
183
+ Dict containing processing results and file paths
184
+ """
185
+ try:
186
+ if not conversation or not isinstance(conversation, list):
187
+ return {
188
+ "success": False,
189
+ "error": "Invalid conversation format. Expected list of message dictionaries.",
190
+ }
191
+
192
+ if not character_name:
193
+ return {"success": False, "error": "Character name is required."}
194
+
195
+ try:
196
+ session_date = datetime.fromisoformat(session_date).strftime("%Y-%m-%d")
197
+ except Exception:
198
+ logger.info(f"session date unavaiable, use system datetime")
199
+ session_date = datetime.now().strftime("%Y-%m-%d")
200
+
201
+ logger.info(f"🚀 Starting iterative conversation processing for {character_name}")
202
+
203
+ # Convert conversation to text for processing
204
+ conversation_text = self._convert_conversation_to_text(conversation)
205
+
206
+ # Initialize results tracking
207
+ results = {
208
+ "success": True,
209
+ "character_name": character_name,
210
+ "session_date": session_date,
211
+ "conversation_length": len(conversation),
212
+ "iterations": 0,
213
+ "function_calls": [],
214
+ # "files_generated": [],
215
+ "processing_log": [],
216
+ }
217
+
218
+ # Get function schemas for LLM
219
+ function_schemas = self.get_functions_schema()
220
+
221
+ # Build initial system message
222
+ system_message = f"""You are a memory processing agent. Follow this structured process to analyze and store conversation information for "{character_name}":
223
+
224
+ CONVERSATION TO PROCESS:
225
+ {conversation_text}
226
+
227
+ CHARACTER: {character_name}
228
+ SESSION DATE: {session_date}
229
+
230
+ PROCESSING WORKFLOW:
231
+ 1. STORE TO ACTIVITY: Call add_activity_memory with the COMPLETE RAW CONVERSATION TEXT as the 'content' parameter. This will automatically append to existing activity memories. DO NOT extract, modify, or summarize the conversation - pass the entire original conversation text exactly as shown above.
232
+
233
+ 2. THEORY OF MIND: Call run_theory_of_mind to analyze the subtle information behind the conversation and extract the theory of mind of the characters.
234
+
235
+ 3. GENERATE SUGGESTIONS: Call generate_memory_suggestions with the available memory items to get suggestions for what should be added to each category.
236
+
237
+ 4. UPDATE CATEGORIES: For each category that should be updated (based on suggestions), call update_memory_with_suggestions to update that category with the new memory items and suggestions. This will return structured modifications.
238
+
239
+ 5. LINK MEMORIES: For each category that was modified, call link_related_memories with link_all_items=true and write_to_memory=true to add relevant links between ALL memories in that category.
240
+
241
+ 6. CLUSTER MEMORIES: Call cluster_memories to cluster the memories into different categories.
242
+
243
+ IMPORTANT GUIDELINES:
244
+ - Step 1: CRITICAL: For add_activity_memory, the 'content' parameter MUST be the complete original conversation text exactly as shown above. Do NOT modify, extract, or summarize it.
245
+ - Step 2: Use both the original conversation and the extracted activity memoryitems from step 1 for the theory of mind analysis
246
+ - Step 3: Use BOTH the extracted memory items from step 1 and theory-of-mind items from step 2 for generating suggestions. You can simply concatenate the two lists of memory items and pass them to the subsequent function.
247
+ - Step 4: Use the memory suggestions from step 3 to update EVERY memory categories in suggestions.
248
+ - Step 5-6: Use the new memory items returned from step 4 for linking and clustering memories. DO NOT include the memory items returned from step 1 and 2.
249
+ - Each memory item should have its own memory_id and focused content
250
+ - Follow the suggestions when updating categories
251
+ - The update_memory_with_suggestions function will return structured format with memory_id and content
252
+ - Always link related memories after updating categories by setting link_all_items=true and write_to_memory=true
253
+
254
+ Start with step 1 and work through the process systematically. When you complete all steps, respond with "PROCESSING_COMPLETE"."""
255
+
256
+ # Start iterative function calling
257
+ messages = [{"role": "system", "content": system_message}]
258
+
259
+ for iteration in range(max_iterations):
260
+ results["iterations"] = iteration + 1
261
+ logger.info(f"🔄 Iteration {iteration + 1}/{max_iterations}")
262
+
263
+ try:
264
+ # Call LLM with function calling enabled
265
+ response = self.memory_core.llm_client.chat_completion(
266
+ messages=messages,
267
+ tools=[{"type": "function", "function": schema} for schema in function_schemas],
268
+ tool_choice="auto",
269
+ temperature=0.3,
270
+ )
271
+
272
+ if not response.success:
273
+ logger.error(f"LLM call failed: {response.error}")
274
+ break
275
+
276
+ # Add assistant response to conversation
277
+ assistant_message = {
278
+ "role": "assistant",
279
+ "content": response.content or "",
280
+ }
281
+
282
+ # Check if processing is complete
283
+ if response.content and "PROCESSING_COMPLETE" in response.content:
284
+ logger.info("✅ LLM indicated processing is complete")
285
+ results["processing_log"].append(f"Iteration {iteration + 1}: Processing completed")
286
+ break
287
+
288
+ # Handle tool calls if present
289
+ if response.tool_calls:
290
+ assistant_message["tool_calls"] = response.tool_calls
291
+ messages.append(assistant_message)
292
+
293
+ # Execute each tool call
294
+ for tool_call in response.tool_calls:
295
+ function_name = tool_call.function.name
296
+
297
+ try:
298
+ arguments = json.loads(tool_call.function.arguments)
299
+ except json.JSONDecodeError as e:
300
+ logger.error(f"Failed to parse function arguments: {e}")
301
+ logger.error(f"Function name: {function_name}")
302
+ logger.error(f"Arguments raw: {repr(tool_call.function.arguments)}")
303
+ continue
304
+
305
+ logger.info(f"🔧 Calling function: {function_name}")
306
+
307
+ # Execute the function call
308
+ time_start = time.time()
309
+ function_result = self.call_function(function_name, arguments)
310
+ time_end = time.time()
311
+
312
+ logger.info(f" Function time used: {time_end - time_start:.2f} seconds")
313
+
314
+ # Track function call
315
+ call_record = {
316
+ "iteration": iteration + 1,
317
+ "function_name": function_name,
318
+ "arguments": arguments,
319
+ "result": function_result,
320
+ }
321
+ results["function_calls"].append(call_record)
322
+
323
+ # Add tool result to conversation
324
+ tool_message = {
325
+ "role": "tool",
326
+ "tool_call_id": getattr(tool_call, "id", f"call_{iteration}_{function_name}"),
327
+ "content": json.dumps(function_result, ensure_ascii=False),
328
+ }
329
+ messages.append(tool_message)
330
+
331
+ results["processing_log"].append(
332
+ f"Iteration {iteration + 1}: Called {function_name} - "
333
+ + (
334
+ "Success"
335
+ if function_result.get("success")
336
+ else f"Failed: {function_result.get('error', 'Unknown error')}"
337
+ )
338
+ )
339
+ else:
340
+ # No tool calls, add response and continue
341
+ messages.append(assistant_message)
342
+ if response.content:
343
+ results["processing_log"].append(f"Iteration {iteration + 1}: {response.content[:100]}...")
344
+
345
+ except Exception as e:
346
+ logger.error(f"Error in iteration {iteration + 1}: {e}")
347
+ results["processing_log"].append(f"Iteration {iteration + 1}: Error - {str(e)}")
348
+ break
349
+
350
+ # Finalize results
351
+ if results["iterations"] >= max_iterations:
352
+ logger.warning(f"⚠️ Reached maximum iterations ({max_iterations})")
353
+ results["processing_log"].append(f"Reached maximum iterations ({max_iterations})")
354
+
355
+ logger.info(f"🎉 Conversation processing completed after {results['iterations']} iterations")
356
+ # logger.info(f"📁 Generated {len(results['files_generated'])} files")
357
+ logger.info(f"🔧 Made {len(results['function_calls'])} function calls")
358
+
359
+ return results
360
+
361
+ except Exception as e:
362
+ logger.error(f"Error in conversation processing: {e}")
363
+ return {
364
+ "success": False,
365
+ "error": str(e),
366
+ "character_name": character_name,
367
+ "timestamp": datetime.now().isoformat(),
368
+ }
369
+
370
+ def _convert_conversation_to_text(self, conversation: List[Dict]) -> str:
371
+ """Convert conversation list to text format for LLM processing"""
372
+ if not conversation or not isinstance(conversation, list):
373
+ return ""
374
+
375
+ text_parts = []
376
+ for message in conversation:
377
+ role = message.get("role", "unknown")
378
+ content = message.get("content", "")
379
+ text_parts.append(f"{role.upper()}: {content.strip()}")
380
+
381
+ return "\n".join(text_parts)
382
+
383
+ # ================================
384
+ # Function Calling Interface
385
+ # ================================
386
+
387
+ def get_functions_schema(self) -> List[Dict[str, Any]]:
388
+ """
389
+ Get OpenAI-compatible function schemas for all memory functions
390
+
391
+ Returns:
392
+ List of function schemas that can be used with OpenAI function calling
393
+ """
394
+ schemas = []
395
+ for action in self.actions.values():
396
+ try:
397
+ schema = action.get_schema()
398
+ schemas.append(schema)
399
+ except Exception as e:
400
+ logger.error(f"Failed to get schema for action {action.action_name}: {e}")
401
+ return schemas
402
+
403
+ def call_function(self, function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
404
+ """
405
+ Call a memory function with the provided arguments
406
+
407
+ Args:
408
+ function_name: Name of the function to call
409
+ arguments: Arguments to pass to the function
410
+
411
+ Returns:
412
+ Dict containing the function result
413
+ """
414
+ try:
415
+ if function_name not in self.actions:
416
+ return {
417
+ "success": False,
418
+ "error": f"Unknown function: {function_name}",
419
+ "available_functions": list(self.actions.keys()),
420
+ }
421
+
422
+ # Get the action instance
423
+ action = self.actions[function_name]
424
+
425
+ # Execute the action with arguments
426
+ result = action.execute(**arguments)
427
+
428
+ logger.debug(f"Function call successful: {function_name}")
429
+ return result
430
+
431
+ except Exception as e:
432
+ error_result = {
433
+ "success": False,
434
+ "error": str(e),
435
+ "function_name": function_name,
436
+ "timestamp": datetime.now().isoformat(),
437
+ }
438
+ logger.error(f"Function call failed: {function_name} - {repr(e)}")
439
+ import traceback
440
+
441
+ traceback.print_exc()
442
+ return error_result
443
+
444
+ def validate_function_call(self, function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
445
+ """
446
+ Validate a function call before execution
447
+
448
+ Args:
449
+ function_name: Name of the function
450
+ arguments: Arguments for the function
451
+
452
+ Returns:
453
+ Dict with validation result
454
+ """
455
+ try:
456
+ if function_name not in self.actions:
457
+ return {
458
+ "valid": False,
459
+ "error": f"Unknown function: {function_name}",
460
+ "available_functions": list(self.actions.keys()),
461
+ }
462
+
463
+ # Use the action's validation method
464
+ action = self.actions[function_name]
465
+ return action.validate_arguments(arguments)
466
+
467
+ except Exception as e:
468
+ return {"valid": False, "error": f"Validation error: {str(e)}"}
469
+
470
+ # ================================
471
+ # Direct Method Access (Compatibility)
472
+ # ================================
473
+
474
+ def get_available_categories(self) -> Dict[str, Any]:
475
+ """Get available memory categories"""
476
+ return self.actions["get_available_categories"].execute()
477
+
478
+ def link_related_memories(
479
+ self,
480
+ character_name: str,
481
+ category: str,
482
+ memory_id: str = None,
483
+ top_k: int = 5,
484
+ min_similarity: float = 0.3,
485
+ search_categories: List[str] = None,
486
+ link_all_items: bool = False,
487
+ write_to_memory: bool = False,
488
+ ) -> Dict[str, Any]:
489
+ """Find and link related memories using embedding search"""
490
+ return self.actions["link_related_memories"].execute(
491
+ character_name=character_name,
492
+ category=category,
493
+ memory_id=memory_id,
494
+ top_k=top_k,
495
+ min_similarity=min_similarity,
496
+ search_categories=search_categories,
497
+ link_all_items=link_all_items,
498
+ write_to_memory=write_to_memory,
499
+ )
500
+
501
+ # ================================
502
+ # Utility Methods
503
+ # ================================
504
+
505
+ def get_function_list(self) -> List[str]:
506
+ """Get list of available function names"""
507
+ return list(self.actions.keys())
508
+
509
+ def get_function_description(self, function_name: str) -> str:
510
+ """Get description for a specific function"""
511
+ if function_name in self.actions:
512
+ try:
513
+ schema = self.actions[function_name].get_schema()
514
+ return schema.get("description", "No description available")
515
+ except Exception:
516
+ return "Description not available"
517
+ return "Function not found"
518
+
519
+ def get_action_instance(self, action_name: str):
520
+ """Get a specific action instance (for advanced usage)"""
521
+ return self.actions.get(action_name)
522
+
523
+ def stop_action(self) -> Dict[str, Any]:
524
+ """
525
+ Stop current operations
526
+
527
+ Returns:
528
+ Dict containing stop result
529
+ """
530
+ try:
531
+ self.memory_core._stop_flag.set()
532
+ logger.info("Memory Agent: Stop flag set")
533
+
534
+ return {
535
+ "success": True,
536
+ "message": "Stop signal sent to Memory Agent operations",
537
+ "timestamp": datetime.now().isoformat(),
538
+ }
539
+
540
+ except Exception as e:
541
+ logger.error(f"Error stopping operations: {e}")
542
+ return {"success": False, "error": str(e)}
543
+
544
+ def reset_stop_flag(self):
545
+ """Reset the stop flag to allow new operations"""
546
+ self.memory_core._stop_flag.clear()
547
+ logger.debug("Memory Agent: Stop flag reset")
548
+
549
+ def get_status(self) -> Dict[str, Any]:
550
+ """Get status information about the memory agent"""
551
+ return {
552
+ "agent_name": "memory_agent",
553
+ "architecture": "action_based",
554
+ "memory_types": list(self.memory_core.memory_types.keys()),
555
+ "processing_order": self.memory_core.processing_order,
556
+ "storage_type": "file_system",
557
+ "memory_dir": str(self.memory_core.memory_dir),
558
+ "config_source": "memory_cat_config.yaml (system + custom)",
559
+ "total_actions": len(self.actions),
560
+ "available_actions": list(self.actions.keys()),
561
+ "total_functions": len(self.function_registry),
562
+ "available_functions": list(self.function_registry.keys()),
563
+ "function_calling_enabled": True,
564
+ "stop_flag_set": self.memory_core._stop_flag.is_set(),
565
+ "embedding_capabilities": {
566
+ "embeddings_enabled": self.memory_core.embeddings_enabled,
567
+ "embedding_client": (
568
+ str(type(self.memory_core.embedding_client)) if self.memory_core.embedding_client else None
569
+ ),
570
+ "embeddings_directory": str(self.memory_core.embeddings_dir),
571
+ },
572
+ "config_details": {
573
+ "total_file_types": len(self.memory_core.memory_types),
574
+ "categories_from_config": True,
575
+ "config_structure": "Dynamic folder configuration",
576
+ },
577
+ "last_updated": datetime.now().isoformat(),
578
+ }