mnemosyne-memory 1.9.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.
@@ -0,0 +1,445 @@
1
+ """Mnemosyne Memory Provider for Hermes.
2
+
3
+ Deploy to Hermes via:
4
+ ln -s /path/to/mnemosyne/hermes_memory_provider ~/.hermes/plugins/mnemosyne
5
+
6
+ Then set in ~/.hermes/config.yaml:
7
+ memory:
8
+ provider: mnemosyne
9
+
10
+ This gives Mnemosyne first-class MemoryProvider integration (system prompt
11
+ injection, pre-turn prefetch, post-turn sync, tool dispatch) while remaining
12
+ a standalone plugin deployed through the plugin system.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ import os
20
+ import sys
21
+ from pathlib import Path
22
+ from typing import Any, Dict, List, Optional
23
+
24
+ # Ensure mnemosyne core is importable from this directory
25
+ _mnemosyne_root = Path(__file__).resolve().parent.parent
26
+ if str(_mnemosyne_root) not in sys.path:
27
+ sys.path.insert(0, str(_mnemosyne_root))
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Lazy imports — fail gracefully if mnemosyne core is missing
33
+ # ---------------------------------------------------------------------------
34
+
35
+ def _get_beam_class():
36
+ from mnemosyne.core.beam import BeamMemory
37
+ return BeamMemory
38
+
39
+
40
+ def _get_triple_module():
41
+ from mnemosyne.core.triples import add_triple, query_triples
42
+ return add_triple, query_triples
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Tool schemas
47
+ # ---------------------------------------------------------------------------
48
+
49
+ REMEMBER_SCHEMA = {
50
+ "name": "mnemosyne_remember",
51
+ "description": (
52
+ "Store a durable memory in Mnemosyne. Use for ANY fact, preference, "
53
+ "insight, or context that should persist across sessions. Higher importance "
54
+ "(0.0-1.0) surfaces the memory more often. Use scope='global' for user-level "
55
+ "facts; scope='session' for conversation-specific context. Use valid_until "
56
+ "(ISO date YYYY-MM-DD) for time-bound facts."
57
+ ),
58
+ "parameters": {
59
+ "type": "object",
60
+ "properties": {
61
+ "content": {"type": "string", "description": "The memory content to store."},
62
+ "importance": {"type": "number", "description": "Importance 0.0-1.0. Default 0.5.", "default": 0.5},
63
+ "source": {"type": "string", "description": "Source tag: preference, fact, insight, task, etc.", "default": "user"},
64
+ "scope": {"type": "string", "description": "'session' (default) or 'global'.", "default": "session"},
65
+ "valid_until": {"type": "string", "description": "Optional expiry date YYYY-MM-DD.", "default": ""},
66
+ },
67
+ "required": ["content"],
68
+ },
69
+ }
70
+
71
+ RECALL_SCHEMA = {
72
+ "name": "mnemosyne_recall",
73
+ "description": (
74
+ "Search Mnemosyne for relevant memories. Uses hybrid ranking: 50% vector "
75
+ "similarity + 30% FTS5 text rank + 20% importance. Returns ranked results."
76
+ ),
77
+ "parameters": {
78
+ "type": "object",
79
+ "properties": {
80
+ "query": {"type": "string", "description": "Natural language query."},
81
+ "limit": {"type": "integer", "description": "Max results. Default 5.", "default": 5},
82
+ },
83
+ "required": ["query"],
84
+ },
85
+ }
86
+
87
+ SLEEP_SCHEMA = {
88
+ "name": "mnemosyne_sleep",
89
+ "description": (
90
+ "Run the Mnemosyne consolidation cycle. Compresses old working memories "
91
+ "into episodic summaries. Call after long sessions or when memory feels stale."
92
+ ),
93
+ "parameters": {"type": "object", "properties": {}},
94
+ }
95
+
96
+ STATS_SCHEMA = {
97
+ "name": "mnemosyne_stats",
98
+ "description": "Return Mnemosyne memory statistics: working count, episodic count, BEAM tiers.",
99
+ "parameters": {"type": "object", "properties": {}},
100
+ }
101
+
102
+ INVALIDATE_SCHEMA = {
103
+ "name": "mnemosyne_invalidate",
104
+ "description": (
105
+ "Mark a memory as expired or superseded. Provide memory_id from recall results. "
106
+ "Optionally provide replacement_id to chain old → new."
107
+ ),
108
+ "parameters": {
109
+ "type": "object",
110
+ "properties": {
111
+ "memory_id": {"type": "string", "description": "ID of memory to invalidate."},
112
+ "replacement_id": {"type": "string", "description": "Optional new memory that replaces this one.", "default": ""},
113
+ },
114
+ "required": ["memory_id"],
115
+ },
116
+ }
117
+
118
+ TRIPLE_ADD_SCHEMA = {
119
+ "name": "mnemosyne_triple_add",
120
+ "description": (
121
+ "Add a temporal fact triple (subject, predicate, object) to the knowledge graph. "
122
+ "Example: ('user', 'prefers', 'neovim'). Use for structured relationships."
123
+ ),
124
+ "parameters": {
125
+ "type": "object",
126
+ "properties": {
127
+ "subject": {"type": "string"},
128
+ "predicate": {"type": "string"},
129
+ "object": {"type": "string"},
130
+ "valid_from": {"type": "string", "description": "ISO date YYYY-MM-DD", "default": ""},
131
+ },
132
+ "required": ["subject", "predicate", "object"],
133
+ },
134
+ }
135
+
136
+ TRIPLE_QUERY_SCHEMA = {
137
+ "name": "mnemosyne_triple_query",
138
+ "description": "Query the temporal knowledge graph for facts matching subject/predicate/object patterns.",
139
+ "parameters": {
140
+ "type": "object",
141
+ "properties": {
142
+ "subject": {"type": "string", "default": ""},
143
+ "predicate": {"type": "string", "default": ""},
144
+ "object": {"type": "string", "default": ""},
145
+ },
146
+ },
147
+ }
148
+
149
+ ALL_TOOL_SCHEMAS = [
150
+ REMEMBER_SCHEMA, RECALL_SCHEMA, SLEEP_SCHEMA, STATS_SCHEMA,
151
+ INVALIDATE_SCHEMA, TRIPLE_ADD_SCHEMA, TRIPLE_QUERY_SCHEMA,
152
+ ]
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # MemoryProvider implementation
157
+ # ---------------------------------------------------------------------------
158
+
159
+ try:
160
+ from agent.memory_provider import MemoryProvider
161
+ except ImportError:
162
+ # Graceful fallback if ABC not available (shouldn't happen in practice)
163
+ MemoryProvider = object # type: ignore
164
+
165
+
166
+ class MnemosyneMemoryProvider(MemoryProvider):
167
+ """Mnemosyne native memory — local SQLite with vector + FTS5 hybrid search."""
168
+
169
+ def __init__(self):
170
+ self._beam: Optional[Any] = None
171
+ self._session_id = "hermes_default"
172
+ self._hermes_home = ""
173
+ self._platform = "cli"
174
+ self._agent_context = "primary"
175
+ self._turn_count = 0
176
+ self._auto_sleep_threshold = 50
177
+ self._auto_sleep_enabled = True
178
+
179
+ @property
180
+ def name(self) -> str:
181
+ return "mnemosyne"
182
+
183
+ def is_available(self) -> bool:
184
+ """Check if Mnemosyne core is importable. No network calls."""
185
+ try:
186
+ _get_beam_class()
187
+ return True
188
+ except Exception:
189
+ return False
190
+
191
+ def get_config_schema(self) -> List[Dict[str, Any]]:
192
+ return [
193
+ {"key": "auto_sleep", "description": "Auto-run sleep() when working memory exceeds threshold", "default": True},
194
+ {"key": "sleep_threshold", "description": "Working memory count before auto-sleep triggers", "default": 50},
195
+ {"key": "vector_type", "description": "Vector storage type", "choices": ["float32", "int8", "bit"], "default": "float32"},
196
+ ]
197
+
198
+ def save_config(self, values: Dict[str, Any], hermes_home: str) -> None:
199
+ pass
200
+
201
+ def initialize(self, session_id: str, **kwargs) -> None:
202
+ """Initialize Mnemosyne beam for this session."""
203
+ self._agent_context = kwargs.get("agent_context", "primary")
204
+ self._platform = kwargs.get("platform", "cli")
205
+ self._hermes_home = kwargs.get("hermes_home", "")
206
+
207
+ if self._agent_context in ("cron", "flush", "subagent"):
208
+ logger.debug("Mnemosyne skipped: non-primary context=%s", self._agent_context)
209
+ return
210
+
211
+ self._session_id = f"hermes_{session_id}"
212
+
213
+ # Read config
214
+ try:
215
+ from hermes_cli.config import load_config
216
+ cfg = load_config()
217
+ mn_cfg = cfg.get("memory", {}).get("mnemosyne", {})
218
+ self._auto_sleep_enabled = mn_cfg.get("auto_sleep", True)
219
+ self._auto_sleep_threshold = mn_cfg.get("sleep_threshold", 50)
220
+ vec_type = mn_cfg.get("vector_type", "float32")
221
+ if vec_type:
222
+ os.environ.setdefault("MNEMOSYNE_VEC_TYPE", vec_type)
223
+ except Exception:
224
+ pass
225
+
226
+ try:
227
+ BeamMemory = _get_beam_class()
228
+ self._beam = BeamMemory(session_id=self._session_id)
229
+ logger.info("Mnemosyne initialized: session=%s", self._session_id)
230
+ except Exception as e:
231
+ logger.warning("Mnemosyne init failed: %s", e)
232
+ self._beam = None
233
+
234
+ def system_prompt_block(self) -> str:
235
+ if not self._beam:
236
+ return ""
237
+ return (
238
+ "# Mnemosyne Memory\n"
239
+ "Active (native local memory). Use mnemosyne_remember to store ANY "
240
+ "durable fact, preference, or insight. Use mnemosyne_recall to search. "
241
+ "The legacy memory tool is deprecated for durable storage — Mnemosyne is primary."
242
+ )
243
+
244
+ def prefetch(self, query: str, *, session_id: str = "") -> str:
245
+ """Recall relevant context via Mnemosyne hybrid search."""
246
+ if not self._beam or self._agent_context in ("cron", "flush", "subagent"):
247
+ return ""
248
+ try:
249
+ results = self._beam.recall(query, top_k=8)
250
+ if not results:
251
+ return ""
252
+ lines = ["## Mnemosyne Context"]
253
+ for r in results:
254
+ content = r.get("content", "")[:200]
255
+ if len(r.get("content", "")) > 200:
256
+ content += "..."
257
+ ts = r.get("timestamp", "")[:16] if r.get("timestamp") else ""
258
+ imp = r.get("importance", 0.0)
259
+ lines.append(f" [{ts}] (importance {imp:.2f}) {content}")
260
+ return "\n".join(lines)
261
+ except Exception as e:
262
+ logger.debug("Mnemosyne prefetch failed: %s", e)
263
+ return ""
264
+
265
+ def queue_prefetch(self, query: str, *, session_id: str = "") -> None:
266
+ pass
267
+
268
+ def sync_turn(self, user_content: str, assistant_content: str, *, session_id: str = "") -> None:
269
+ """Persist the turn to Mnemosyne episodic memory."""
270
+ if not self._beam or self._agent_context in ("cron", "flush", "subagent"):
271
+ return
272
+ try:
273
+ if user_content and len(user_content) > 5:
274
+ self._beam.remember(
275
+ content=f"[USER] {user_content[:500]}",
276
+ source="conversation",
277
+ importance=0.3,
278
+ )
279
+ if assistant_content and len(assistant_content) > 10:
280
+ self._beam.remember(
281
+ content=f"[ASSISTANT] {assistant_content[:800]}",
282
+ source="conversation",
283
+ importance=0.2,
284
+ )
285
+ self._turn_count += 1
286
+ if self._auto_sleep_enabled and self._turn_count % 10 == 0:
287
+ self._maybe_auto_sleep()
288
+ except Exception as e:
289
+ logger.debug("Mnemosyne sync_turn failed: %s", e)
290
+
291
+ def _maybe_auto_sleep(self) -> None:
292
+ try:
293
+ stats = self._beam.get_working_stats()
294
+ working = stats.get("count", 0)
295
+ if working > self._auto_sleep_threshold:
296
+ logger.info("Mnemosyne auto-sleep: working=%d > threshold=%d", working, self._auto_sleep_threshold)
297
+ self._beam.sleep()
298
+ except Exception:
299
+ pass
300
+
301
+ def get_tool_schemas(self) -> List[Dict[str, Any]]:
302
+ """Return tool schemas — static, do not depend on initialization state."""
303
+ return list(ALL_TOOL_SCHEMAS)
304
+
305
+ def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
306
+ if not self._beam:
307
+ return json.dumps({"error": "Mnemosyne not initialized"})
308
+ try:
309
+ if tool_name == "mnemosyne_remember":
310
+ return self._handle_remember(args)
311
+ elif tool_name == "mnemosyne_recall":
312
+ return self._handle_recall(args)
313
+ elif tool_name == "mnemosyne_sleep":
314
+ return self._handle_sleep(args)
315
+ elif tool_name == "mnemosyne_stats":
316
+ return self._handle_stats(args)
317
+ elif tool_name == "mnemosyne_invalidate":
318
+ return self._handle_invalidate(args)
319
+ elif tool_name == "mnemosyne_triple_add":
320
+ return self._handle_triple_add(args)
321
+ elif tool_name == "mnemosyne_triple_query":
322
+ return self._handle_triple_query(args)
323
+ else:
324
+ return json.dumps({"error": f"Unknown Mnemosyne tool: {tool_name}"})
325
+ except Exception as e:
326
+ logger.error("Mnemosyne tool %s failed: %s", tool_name, e)
327
+ return json.dumps({"error": f"Mnemosyne tool '{tool_name}' failed: {e}"})
328
+
329
+ def _handle_remember(self, args: Dict[str, Any]) -> str:
330
+ content = args.get("content", "")
331
+ importance = float(args.get("importance", 0.5))
332
+ source = args.get("source", "user")
333
+ scope = args.get("scope", "session")
334
+ valid_until = args.get("valid_until", None) or None
335
+ if not content:
336
+ return json.dumps({"error": "content is required"})
337
+ memory_id = self._beam.remember(
338
+ content=content,
339
+ importance=importance,
340
+ source=source,
341
+ scope=scope,
342
+ valid_until=valid_until,
343
+ )
344
+ return json.dumps({"status": "stored", "memory_id": memory_id, "content_preview": content[:100]})
345
+
346
+ def _handle_recall(self, args: Dict[str, Any]) -> str:
347
+ query = args.get("query", "")
348
+ top_k = int(args.get("limit", 5))
349
+ if not query:
350
+ return json.dumps({"error": "query is required"})
351
+ results = self._beam.recall(query, top_k=top_k)
352
+ return json.dumps({"query": query, "count": len(results), "results": results})
353
+
354
+ def _handle_sleep(self, args: Dict[str, Any]) -> str:
355
+ self._beam.sleep()
356
+ working = self._beam.get_working_stats()
357
+ episodic = self._beam.get_episodic_stats()
358
+ return json.dumps({"status": "consolidated", "working": working, "episodic": episodic})
359
+
360
+ def _handle_stats(self, args: Dict[str, Any]) -> str:
361
+ working = self._beam.get_working_stats()
362
+ episodic = self._beam.get_episodic_stats()
363
+ return json.dumps({"provider": "mnemosyne", "session_id": self._session_id, "working": working, "episodic": episodic})
364
+
365
+ def _handle_invalidate(self, args: Dict[str, Any]) -> str:
366
+ memory_id = args.get("memory_id", "")
367
+ replacement_id = args.get("replacement_id", None) or None
368
+ if not memory_id:
369
+ return json.dumps({"error": "memory_id is required"})
370
+ self._beam.invalidate(memory_id, replacement_id=replacement_id if replacement_id else None)
371
+ return json.dumps({"status": "invalidated", "memory_id": memory_id})
372
+
373
+ def _handle_triple_add(self, args: Dict[str, Any]) -> str:
374
+ subject = args.get("subject", "")
375
+ predicate = args.get("predicate", "")
376
+ obj = args.get("object", "")
377
+ valid_from = args.get("valid_from", None) or None
378
+ if not all([subject, predicate, obj]):
379
+ return json.dumps({"error": "subject, predicate, and object are required"})
380
+ add_triple, _ = _get_triple_module()
381
+ triple_id = add_triple(subject, predicate, obj, valid_from=valid_from)
382
+ return json.dumps({"status": "stored", "triple_id": triple_id})
383
+
384
+ def _handle_triple_query(self, args: Dict[str, Any]) -> str:
385
+ subject = args.get("subject", "") or None
386
+ predicate = args.get("predicate", "") or None
387
+ obj = args.get("object", "") or None
388
+ _, query_triples = _get_triple_module()
389
+ results = query_triples(subject=subject, predicate=predicate, object=obj)
390
+ return json.dumps({"count": len(results), "results": results})
391
+
392
+ def on_turn_start(self, turn_number: int, message: str, **kwargs) -> None:
393
+ self._turn_count = turn_number
394
+
395
+ def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
396
+ if not self._beam:
397
+ return
398
+ try:
399
+ logger.info("Mnemosyne session end — running consolidation")
400
+ self._beam.sleep()
401
+ except Exception as e:
402
+ logger.debug("Mnemosyne session-end sleep failed: %s", e)
403
+
404
+ def on_memory_write(self, action: str, target: str, content: str) -> None:
405
+ if not self._beam or action not in ("add", "replace"):
406
+ return
407
+ try:
408
+ scope = "global" if target == "user" else "session"
409
+ self._beam.remember(
410
+ content=content,
411
+ source=f"builtin_memory_{target}",
412
+ importance=0.7 if target == "user" else 0.5,
413
+ scope=scope,
414
+ )
415
+ except Exception as e:
416
+ logger.debug("Mnemosyne mirror write failed: %s", e)
417
+
418
+ def shutdown(self) -> None:
419
+ self._beam = None
420
+
421
+
422
+ # ---------------------------------------------------------------------------
423
+ # Plugin registration (used when loaded via plugins.memory discovery)
424
+ # ---------------------------------------------------------------------------
425
+
426
+ def register_memory_provider(ctx):
427
+ """Called by Hermes memory provider discovery system."""
428
+ provider = MnemosyneMemoryProvider()
429
+ ctx.register_memory_provider(provider)
430
+
431
+
432
+ # ---------------------------------------------------------------------------
433
+ # Plugin registration (used when loaded via Hermes plugin system)
434
+ # ---------------------------------------------------------------------------
435
+
436
+ def register(ctx):
437
+ """Called by Hermes plugin loader to register CLI commands and tools."""
438
+ from .cli import register_cli, mnemosyne_command
439
+ ctx.register_cli_command(
440
+ name="mnemosyne",
441
+ help="Manage Mnemosyne local memory",
442
+ description="Inspect, consolidate, and manage Mnemosyne native memory.",
443
+ setup_fn=register_cli,
444
+ handler_fn=mnemosyne_command,
445
+ )
@@ -0,0 +1,128 @@
1
+ """CLI commands for Mnemosyne memory provider.
2
+
3
+ Available via: hermes mnemosyne <subcommand>
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ _mnemosyne_root = Path(__file__).resolve().parent.parent
13
+ if str(_mnemosyne_root) not in sys.path:
14
+ sys.path.insert(0, str(_mnemosyne_root))
15
+
16
+
17
+ def register_cli(subparser):
18
+ """Register CLI subcommands for ``hermes mnemosyne``."""
19
+ mn_cmds = subparser.add_subparsers(dest="mnemosyne_cmd")
20
+
21
+ mn_cmds.add_parser("stats", help="Show memory statistics")
22
+ mn_cmds.add_parser("sleep", help="Run consolidation cycle")
23
+ mn_cmds.add_parser("version", help="Show Mnemosyne version")
24
+
25
+ inspect_cmd = mn_cmds.add_parser("inspect", help="Search memories")
26
+ inspect_cmd.add_argument("query", nargs="?", default="", help="Search query")
27
+ inspect_cmd.add_argument("--limit", type=int, default=10, help="Max results")
28
+
29
+ mn_cmds.add_parser("clear", help="Clear scratchpad")
30
+
31
+ export_cmd = mn_cmds.add_parser("export", help="Export all memories to a JSON file")
32
+ export_cmd.add_argument("--output", "-o", type=str, required=True, help="Output JSON file path")
33
+
34
+ import_cmd = mn_cmds.add_parser("import", help="Import memories from a JSON file")
35
+ import_cmd.add_argument("--input", "-i", type=str, required=True, help="Input JSON file path")
36
+ import_cmd.add_argument("--force", action="store_true", help="Overwrite existing records")
37
+
38
+ subparser.set_defaults(func=mnemosyne_command)
39
+
40
+
41
+ def mnemosyne_command(args):
42
+ """Dispatch ``hermes mnemosyne <subcommand>``."""
43
+ cmd = getattr(args, "mnemosyne_cmd", None)
44
+ if not cmd:
45
+ print("Usage: hermes mnemosyne {stats|sleep|inspect|clear}")
46
+ return 1
47
+
48
+ try:
49
+ from mnemosyne.core.beam import BeamMemory
50
+ beam = BeamMemory(session_id="hermes_default")
51
+ except Exception as e:
52
+ print(f"Error: Mnemosyne not available: {e}")
53
+ return 1
54
+
55
+ if cmd == "stats":
56
+ working = beam.get_working_stats()
57
+ episodic = beam.get_episodic_stats()
58
+ print(json.dumps({"working": working, "episodic": episodic}, indent=2))
59
+
60
+ elif cmd == "version":
61
+ from mnemosyne import __version__, __author__
62
+ print(f"Mnemosyne {__version__} by {__author__}")
63
+
64
+ elif cmd == "sleep":
65
+ beam.sleep()
66
+ working = beam.get_working_stats()
67
+ episodic = beam.get_episodic_stats()
68
+ print(f"Consolidation complete. Working: {working.get('count', 0)}, Episodic: {episodic.get('count', 0)}")
69
+
70
+ elif cmd == "inspect":
71
+ query = getattr(args, "query", "") or ""
72
+ limit = getattr(args, "limit", 10)
73
+ if not query:
74
+ query = input("Search query: ")
75
+ results = beam.recall(query, top_k=limit)
76
+ print(f"Results for '{query}': {len(results)}")
77
+ for i, r in enumerate(results, 1):
78
+ content = r.get("content", "")[:120]
79
+ imp = r.get("importance", 0.0)
80
+ print(f" {i}. [{imp:.2f}] {content}")
81
+
82
+ elif cmd == "clear":
83
+ confirm = input("Clear scratchpad? This cannot be undone. [y/N]: ")
84
+ if confirm.lower() in ("y", "yes"):
85
+ beam.scratchpad_clear()
86
+ print("Scratchpad cleared.")
87
+ else:
88
+ print("Cancelled.")
89
+
90
+ elif cmd == "export":
91
+ output_path = getattr(args, "output", None)
92
+ if not output_path:
93
+ print("Usage: hermes mnemosyne export --output <path>")
94
+ return 1
95
+ try:
96
+ from mnemosyne.core.memory import Mnemosyne
97
+ mem = Mnemosyne(session_id="hermes_default")
98
+ result = mem.export_to_file(output_path)
99
+ print(f"Exported {result['working_memory_count']} working, {result['episodic_memory_count']} episodic, {result['legacy_memories_count']} legacy, {result['triples_count']} triples to {output_path}")
100
+ except Exception as e:
101
+ print(f"Export failed: {e}")
102
+ return 1
103
+
104
+ elif cmd == "import":
105
+ input_path = getattr(args, "input", None)
106
+ force = getattr(args, "force", False)
107
+ if not input_path:
108
+ print("Usage: hermes mnemosyne import --input <path> [--force]")
109
+ return 1
110
+ try:
111
+ from mnemosyne.core.memory import Mnemosyne
112
+ mem = Mnemosyne(session_id="hermes_default")
113
+ stats = mem.import_from_file(input_path, force=force)
114
+ beam_stats = stats.get("beam", {})
115
+ legacy_stats = stats.get("legacy", {})
116
+ triples_stats = stats.get("triples", {})
117
+ print(f"Import complete:")
118
+ print(f" Working: +{beam_stats.get('working_memory', {}).get('inserted', 0)}")
119
+ print(f" Episodic: +{beam_stats.get('episodic_memory', {}).get('inserted', 0)}")
120
+ print(f" Legacy: +{legacy_stats.get('inserted', 0)}")
121
+ print(f" Triples: +{triples_stats.get('inserted', 0)}")
122
+ if force:
123
+ print(f" (force mode: overwrites applied)")
124
+ except Exception as e:
125
+ print(f"Import failed: {e}")
126
+ return 1
127
+
128
+ return 0