neural-memory 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 (55) hide show
  1. neural_memory/__init__.py +38 -0
  2. neural_memory/cli/__init__.py +15 -0
  3. neural_memory/cli/__main__.py +6 -0
  4. neural_memory/cli/config.py +176 -0
  5. neural_memory/cli/main.py +2702 -0
  6. neural_memory/cli/storage.py +169 -0
  7. neural_memory/cli/tui.py +471 -0
  8. neural_memory/core/__init__.py +52 -0
  9. neural_memory/core/brain.py +301 -0
  10. neural_memory/core/brain_mode.py +273 -0
  11. neural_memory/core/fiber.py +236 -0
  12. neural_memory/core/memory_types.py +331 -0
  13. neural_memory/core/neuron.py +168 -0
  14. neural_memory/core/project.py +257 -0
  15. neural_memory/core/synapse.py +215 -0
  16. neural_memory/engine/__init__.py +15 -0
  17. neural_memory/engine/activation.py +335 -0
  18. neural_memory/engine/encoder.py +391 -0
  19. neural_memory/engine/retrieval.py +440 -0
  20. neural_memory/extraction/__init__.py +42 -0
  21. neural_memory/extraction/entities.py +547 -0
  22. neural_memory/extraction/parser.py +337 -0
  23. neural_memory/extraction/router.py +396 -0
  24. neural_memory/extraction/temporal.py +428 -0
  25. neural_memory/mcp/__init__.py +9 -0
  26. neural_memory/mcp/__main__.py +6 -0
  27. neural_memory/mcp/server.py +621 -0
  28. neural_memory/py.typed +0 -0
  29. neural_memory/safety/__init__.py +31 -0
  30. neural_memory/safety/freshness.py +238 -0
  31. neural_memory/safety/sensitive.py +304 -0
  32. neural_memory/server/__init__.py +5 -0
  33. neural_memory/server/app.py +99 -0
  34. neural_memory/server/dependencies.py +33 -0
  35. neural_memory/server/models.py +138 -0
  36. neural_memory/server/routes/__init__.py +7 -0
  37. neural_memory/server/routes/brain.py +221 -0
  38. neural_memory/server/routes/memory.py +169 -0
  39. neural_memory/server/routes/sync.py +387 -0
  40. neural_memory/storage/__init__.py +17 -0
  41. neural_memory/storage/base.py +441 -0
  42. neural_memory/storage/factory.py +329 -0
  43. neural_memory/storage/memory_store.py +896 -0
  44. neural_memory/storage/shared_store.py +650 -0
  45. neural_memory/storage/sqlite_store.py +1613 -0
  46. neural_memory/sync/__init__.py +5 -0
  47. neural_memory/sync/client.py +435 -0
  48. neural_memory/unified_config.py +315 -0
  49. neural_memory/utils/__init__.py +5 -0
  50. neural_memory/utils/config.py +98 -0
  51. neural_memory-0.1.0.dist-info/METADATA +314 -0
  52. neural_memory-0.1.0.dist-info/RECORD +55 -0
  53. neural_memory-0.1.0.dist-info/WHEEL +4 -0
  54. neural_memory-0.1.0.dist-info/entry_points.txt +4 -0
  55. neural_memory-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,169 @@
1
+ """JSON-based persistent storage for CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from neural_memory.core.brain import Brain, BrainConfig, BrainSnapshot
11
+ from neural_memory.storage.memory_store import InMemoryStorage
12
+
13
+
14
+ class PersistentStorage(InMemoryStorage):
15
+ """InMemoryStorage with JSON file persistence.
16
+
17
+ Wraps InMemoryStorage and adds save/load functionality for CLI use.
18
+ Data is stored in ~/.neural-memory/brains/<brain_name>.json
19
+ """
20
+
21
+ def __init__(self, file_path: Path) -> None:
22
+ """Initialize persistent storage.
23
+
24
+ Args:
25
+ file_path: Path to the JSON file for this brain.
26
+ """
27
+ super().__init__()
28
+ self._file_path = file_path
29
+ self._auto_save = True
30
+
31
+ @classmethod
32
+ async def load(cls, file_path: Path) -> PersistentStorage:
33
+ """Load storage from file, or create new if doesn't exist."""
34
+ storage = cls(file_path)
35
+
36
+ if file_path.exists():
37
+ await storage._load_from_file()
38
+ else:
39
+ # Create default brain
40
+ file_path.parent.mkdir(parents=True, exist_ok=True)
41
+ brain_name = file_path.stem
42
+ brain = Brain.create(name=brain_name)
43
+ await storage.save_brain(brain)
44
+ storage.set_brain(brain.id)
45
+ await storage._save_to_file()
46
+
47
+ return storage
48
+
49
+ async def _load_from_file(self) -> None:
50
+ """Load data from JSON file."""
51
+ with open(self._file_path, encoding="utf-8") as f:
52
+ data = json.load(f)
53
+
54
+ # Reconstruct brain
55
+ brain_data = data.get("brain", {})
56
+ if brain_data:
57
+ config = BrainConfig(**brain_data.get("config", {}))
58
+ brain = Brain(
59
+ id=brain_data["id"],
60
+ name=brain_data["name"],
61
+ config=config,
62
+ owner_id=brain_data.get("owner_id"),
63
+ is_public=brain_data.get("is_public", False),
64
+ created_at=datetime.fromisoformat(brain_data["created_at"]),
65
+ updated_at=datetime.fromisoformat(brain_data["updated_at"]),
66
+ )
67
+ await super().save_brain(brain)
68
+ self.set_brain(brain.id)
69
+
70
+ # Import snapshot data if exists
71
+ if "snapshot" in data:
72
+ snapshot_data = data["snapshot"]
73
+ snapshot = BrainSnapshot(
74
+ brain_id=snapshot_data["brain_id"],
75
+ brain_name=snapshot_data["brain_name"],
76
+ exported_at=datetime.fromisoformat(snapshot_data["exported_at"]),
77
+ version=snapshot_data["version"],
78
+ neurons=snapshot_data["neurons"],
79
+ synapses=snapshot_data["synapses"],
80
+ fibers=snapshot_data["fibers"],
81
+ config=snapshot_data.get("config", {}),
82
+ metadata=snapshot_data.get("metadata", {}),
83
+ )
84
+ await self.import_brain(snapshot, brain.id if brain_data else None)
85
+
86
+ async def _save_to_file(self) -> None:
87
+ """Save data to JSON file."""
88
+ if not self._current_brain_id:
89
+ return
90
+
91
+ brain = await self.get_brain(self._current_brain_id)
92
+ if not brain:
93
+ return
94
+
95
+ # Export as snapshot
96
+ snapshot = await self.export_brain(self._current_brain_id)
97
+
98
+ data = {
99
+ "brain": {
100
+ "id": brain.id,
101
+ "name": brain.name,
102
+ "config": {
103
+ "decay_rate": brain.config.decay_rate,
104
+ "reinforcement_delta": brain.config.reinforcement_delta,
105
+ "activation_threshold": brain.config.activation_threshold,
106
+ "max_spread_hops": brain.config.max_spread_hops,
107
+ "max_context_tokens": brain.config.max_context_tokens,
108
+ },
109
+ "owner_id": brain.owner_id,
110
+ "is_public": brain.is_public,
111
+ "created_at": brain.created_at.isoformat(),
112
+ "updated_at": brain.updated_at.isoformat(),
113
+ },
114
+ "snapshot": {
115
+ "brain_id": snapshot.brain_id,
116
+ "brain_name": snapshot.brain_name,
117
+ "exported_at": snapshot.exported_at.isoformat(),
118
+ "version": snapshot.version,
119
+ "neurons": snapshot.neurons,
120
+ "synapses": snapshot.synapses,
121
+ "fibers": snapshot.fibers,
122
+ "config": snapshot.config,
123
+ "metadata": snapshot.metadata,
124
+ },
125
+ "saved_at": datetime.now().isoformat(),
126
+ }
127
+
128
+ self._file_path.parent.mkdir(parents=True, exist_ok=True)
129
+ with open(self._file_path, "w", encoding="utf-8") as f:
130
+ json.dump(data, f, indent=2, default=str)
131
+
132
+ async def save(self) -> None:
133
+ """Explicitly save to file."""
134
+ await self._save_to_file()
135
+
136
+ # Override methods to auto-save
137
+
138
+ async def add_neuron(self, neuron: Any) -> str:
139
+ """Add neuron and save."""
140
+ result = await super().add_neuron(neuron)
141
+ if self._auto_save:
142
+ await self._save_to_file()
143
+ return result
144
+
145
+ async def add_synapse(self, synapse: Any) -> str:
146
+ """Add synapse and save."""
147
+ result = await super().add_synapse(synapse)
148
+ if self._auto_save:
149
+ await self._save_to_file()
150
+ return result
151
+
152
+ async def add_fiber(self, fiber: Any) -> str:
153
+ """Add fiber and save."""
154
+ result = await super().add_fiber(fiber)
155
+ if self._auto_save:
156
+ await self._save_to_file()
157
+ return result
158
+
159
+ async def batch_save(self) -> None:
160
+ """Save after batch operations (call manually when auto_save is off)."""
161
+ await self._save_to_file()
162
+
163
+ def disable_auto_save(self) -> None:
164
+ """Disable auto-save for batch operations."""
165
+ self._auto_save = False
166
+
167
+ def enable_auto_save(self) -> None:
168
+ """Enable auto-save."""
169
+ self._auto_save = True
@@ -0,0 +1,471 @@
1
+ """Terminal UI components for NeuralMemory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from rich.console import Console
8
+ from rich.layout import Layout
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+ from rich.tree import Tree
13
+
14
+ if TYPE_CHECKING:
15
+ from neural_memory.cli.storage import PersistentStorage
16
+
17
+ console = Console()
18
+
19
+
20
+ # =============================================================================
21
+ # Color Schemes
22
+ # =============================================================================
23
+
24
+ MEMORY_TYPE_COLORS = {
25
+ "fact": "white",
26
+ "decision": "blue",
27
+ "preference": "magenta",
28
+ "todo": "yellow",
29
+ "insight": "green",
30
+ "context": "cyan",
31
+ "instruction": "bright_blue",
32
+ "error": "red",
33
+ "workflow": "bright_cyan",
34
+ "reference": "bright_black",
35
+ }
36
+
37
+ PRIORITY_COLORS = {
38
+ "critical": "bold red",
39
+ "high": "red",
40
+ "normal": "white",
41
+ "low": "bright_black",
42
+ "lowest": "dim",
43
+ }
44
+
45
+ FRESHNESS_COLORS = {
46
+ "fresh": "green",
47
+ "recent": "bright_green",
48
+ "aging": "yellow",
49
+ "stale": "red",
50
+ "ancient": "bright_black",
51
+ }
52
+
53
+
54
+ # =============================================================================
55
+ # Dashboard
56
+ # =============================================================================
57
+
58
+
59
+ async def render_dashboard(storage: PersistentStorage) -> None:
60
+ """Render a rich dashboard with brain stats and recent activity."""
61
+ from neural_memory.safety.freshness import analyze_freshness, evaluate_freshness
62
+
63
+ brain = await storage.get_brain(storage._current_brain_id)
64
+ if not brain:
65
+ console.print("[red]No brain configured[/red]")
66
+ return
67
+
68
+ # Gather data
69
+ stats = await storage.get_stats(brain.id)
70
+ fibers = await storage.get_fibers(limit=100)
71
+ typed_memories = await storage.find_typed_memories(limit=1000)
72
+ expired_memories = await storage.get_expired_memories()
73
+
74
+ # Analyze freshness
75
+ created_dates = [f.created_at for f in fibers]
76
+ freshness_report = analyze_freshness(created_dates)
77
+
78
+ # Count by type and priority
79
+ type_counts: dict[str, int] = {}
80
+ priority_counts: dict[str, int] = {}
81
+ for tm in typed_memories:
82
+ type_name = tm.memory_type.value
83
+ type_counts[type_name] = type_counts.get(type_name, 0) + 1
84
+ pri_name = tm.priority.name.lower()
85
+ priority_counts[pri_name] = priority_counts.get(pri_name, 0) + 1
86
+
87
+ # Create layout
88
+ layout = Layout()
89
+ layout.split_column(
90
+ Layout(name="header", size=3),
91
+ Layout(name="main", ratio=1),
92
+ Layout(name="footer", size=3),
93
+ )
94
+
95
+ layout["main"].split_row(
96
+ Layout(name="left", ratio=1),
97
+ Layout(name="right", ratio=1),
98
+ )
99
+
100
+ layout["left"].split_column(
101
+ Layout(name="stats", ratio=1),
102
+ Layout(name="types", ratio=1),
103
+ )
104
+
105
+ layout["right"].split_column(
106
+ Layout(name="freshness", ratio=1),
107
+ Layout(name="recent", ratio=1),
108
+ )
109
+
110
+ # Header
111
+ header_text = Text()
112
+ header_text.append("[*] ", style="bold")
113
+ header_text.append("NeuralMemory Dashboard", style="bold cyan")
114
+ header_text.append(f" - {brain.name}", style="bright_black")
115
+ layout["header"].update(Panel(header_text, style="cyan"))
116
+
117
+ # Stats panel
118
+ stats_table = Table(show_header=False, box=None, padding=(0, 2))
119
+ stats_table.add_column("Metric", style="bright_black")
120
+ stats_table.add_column("Value", style="bold")
121
+ stats_table.add_row("Neurons", f"[cyan]{stats['neuron_count']:,}[/cyan]")
122
+ stats_table.add_row("Synapses", f"[blue]{stats['synapse_count']:,}[/blue]")
123
+ stats_table.add_row("Fibers", f"[green]{stats['fiber_count']:,}[/green]")
124
+ stats_table.add_row("Typed Memories", f"[yellow]{len(typed_memories):,}[/yellow]")
125
+ if expired_memories:
126
+ stats_table.add_row(
127
+ "Expired", f"[red]{len(expired_memories)}[/red] [dim](run cleanup)[/dim]"
128
+ )
129
+ layout["stats"].update(Panel(stats_table, title="Brain Stats", border_style="cyan"))
130
+
131
+ # Types panel
132
+ types_table = Table(show_header=False, box=None, padding=(0, 1))
133
+ types_table.add_column("Type")
134
+ types_table.add_column("Count", justify="right")
135
+ for mem_type, count in sorted(type_counts.items(), key=lambda x: -x[1])[:8]:
136
+ color = MEMORY_TYPE_COLORS.get(mem_type, "white")
137
+ types_table.add_row(f"[{color}]*[/{color}] {mem_type}", str(count))
138
+ layout["types"].update(Panel(types_table, title="By Type", border_style="blue"))
139
+
140
+ # Freshness panel
141
+ fresh_table = Table(show_header=False, box=None, padding=(0, 1))
142
+ fresh_table.add_column("Age")
143
+ fresh_table.add_column("Count", justify="right")
144
+ fresh_table.add_column("Bar")
145
+
146
+ total = max(freshness_report.total, 1)
147
+ for label, count, color in [
148
+ ("Fresh (<7d)", freshness_report.fresh, "green"),
149
+ ("Recent (7-30d)", freshness_report.recent, "bright_green"),
150
+ ("Aging (30-90d)", freshness_report.aging, "yellow"),
151
+ ("Stale (90-365d)", freshness_report.stale, "red"),
152
+ ("Ancient (>365d)", freshness_report.ancient, "bright_black"),
153
+ ]:
154
+ pct = count / total
155
+ bar = "#" * int(pct * 15) + "-" * (15 - int(pct * 15))
156
+ fresh_table.add_row(f"[{color}]{label}[/{color}]", str(count), f"[{color}]{bar}[/{color}]")
157
+
158
+ layout["freshness"].update(Panel(fresh_table, title="Memory Freshness", border_style="green"))
159
+
160
+ # Recent memories panel
161
+ recent_table = Table(show_header=False, box=None, padding=(0, 1))
162
+ recent_table.add_column("Memory", overflow="ellipsis", max_width=40)
163
+
164
+ recent_typed = sorted(typed_memories, key=lambda x: x.created_at, reverse=True)[:6]
165
+ for tm in recent_typed:
166
+ fiber = await storage.get_fiber(tm.fiber_id)
167
+ content = ""
168
+ if fiber:
169
+ if fiber.summary:
170
+ content = fiber.summary[:35]
171
+ elif fiber.anchor_neuron_id:
172
+ anchor = await storage.get_neuron(fiber.anchor_neuron_id)
173
+ if anchor:
174
+ content = anchor.content[:35]
175
+
176
+ if len(content) > 35:
177
+ content = content[:32] + "..."
178
+
179
+ type_color = MEMORY_TYPE_COLORS.get(tm.memory_type.value, "white")
180
+ freshness = evaluate_freshness(tm.created_at)
181
+ age_color = FRESHNESS_COLORS.get(freshness.level.value, "white")
182
+
183
+ row_text = Text()
184
+ row_text.append(f"[{tm.memory_type.value[:4].upper()}] ", style=type_color)
185
+ row_text.append(content, style="white")
186
+ row_text.append(f" ({freshness.label})", style=age_color)
187
+ recent_table.add_row(row_text)
188
+
189
+ layout["recent"].update(Panel(recent_table, title="Recent Memories", border_style="yellow"))
190
+
191
+ # Footer
192
+ footer_text = Text()
193
+ footer_text.append("Commands: ", style="bright_black")
194
+ footer_text.append("nmem ui", style="cyan")
195
+ footer_text.append(" (browse) ", style="bright_black")
196
+ footer_text.append("nmem graph", style="cyan")
197
+ footer_text.append(" (visualize) ", style="bright_black")
198
+ footer_text.append("nmem recall", style="cyan")
199
+ footer_text.append(" (search)", style="bright_black")
200
+ layout["footer"].update(Panel(footer_text, style="bright_black"))
201
+
202
+ console.print(layout)
203
+
204
+
205
+ # =============================================================================
206
+ # Memory Browser (UI)
207
+ # =============================================================================
208
+
209
+
210
+ async def render_memory_browser(
211
+ storage: PersistentStorage,
212
+ memory_type: str | None = None,
213
+ limit: int = 20,
214
+ search: str | None = None,
215
+ ) -> None:
216
+ """Render an interactive memory browser."""
217
+ from neural_memory.safety.freshness import evaluate_freshness, format_age
218
+
219
+ brain = await storage.get_brain(storage._current_brain_id)
220
+ if not brain:
221
+ console.print("[red]No brain configured[/red]")
222
+ return
223
+
224
+ # Get memories
225
+ mem_type_filter = None
226
+ if memory_type:
227
+ from neural_memory.core.memory_types import MemoryType
228
+
229
+ try:
230
+ mem_type_filter = MemoryType(memory_type.lower())
231
+ except ValueError:
232
+ console.print(f"[red]Invalid memory type: {memory_type}[/red]")
233
+ return
234
+
235
+ typed_memories = await storage.find_typed_memories(
236
+ memory_type=mem_type_filter,
237
+ limit=limit * 2, # Get extra for filtering
238
+ )
239
+
240
+ # Filter by search if provided
241
+ if search:
242
+ search_lower = search.lower()
243
+ filtered = []
244
+ for tm in typed_memories:
245
+ fiber = await storage.get_fiber(tm.fiber_id)
246
+ if fiber:
247
+ content = fiber.summary or ""
248
+ if not content and fiber.anchor_neuron_id:
249
+ anchor = await storage.get_neuron(fiber.anchor_neuron_id)
250
+ if anchor:
251
+ content = anchor.content
252
+ if search_lower in content.lower():
253
+ filtered.append((tm, content))
254
+ typed_memories_with_content = filtered[:limit]
255
+ else:
256
+ typed_memories_with_content = []
257
+ for tm in typed_memories[:limit]:
258
+ fiber = await storage.get_fiber(tm.fiber_id)
259
+ content = ""
260
+ if fiber:
261
+ if fiber.summary:
262
+ content = fiber.summary
263
+ elif fiber.anchor_neuron_id:
264
+ anchor = await storage.get_neuron(fiber.anchor_neuron_id)
265
+ if anchor:
266
+ content = anchor.content
267
+ typed_memories_with_content.append((tm, content))
268
+
269
+ if not typed_memories_with_content:
270
+ console.print("[yellow]No memories found.[/yellow]")
271
+ return
272
+
273
+ # Create table
274
+ table = Table(
275
+ title="Memory Browser",
276
+ show_lines=True,
277
+ title_style="bold cyan",
278
+ border_style="bright_black",
279
+ )
280
+
281
+ table.add_column("#", style="bright_black", width=3)
282
+ table.add_column("Type", style="bold", width=10)
283
+ table.add_column("Priority", width=8)
284
+ table.add_column("Content", overflow="fold")
285
+ table.add_column("Age", width=10)
286
+ table.add_column("Tags", width=15)
287
+
288
+ for idx, (tm, content) in enumerate(typed_memories_with_content, 1):
289
+ type_color = MEMORY_TYPE_COLORS.get(tm.memory_type.value, "white")
290
+ priority_color = PRIORITY_COLORS.get(tm.priority.name.lower(), "white")
291
+ freshness = evaluate_freshness(tm.created_at)
292
+ age_color = FRESHNESS_COLORS.get(freshness.level.value, "white")
293
+
294
+ # Truncate content
295
+ display_content = content[:80] + "..." if len(content) > 80 else content
296
+
297
+ # Format tags
298
+ tags_str = ", ".join(list(tm.tags)[:3]) if tm.tags else "-"
299
+ if tm.tags and len(tm.tags) > 3:
300
+ tags_str += f" +{len(tm.tags) - 3}"
301
+
302
+ table.add_row(
303
+ str(idx),
304
+ f"[{type_color}]{tm.memory_type.value}[/{type_color}]",
305
+ f"[{priority_color}]{tm.priority.name.lower()}[/{priority_color}]",
306
+ display_content,
307
+ f"[{age_color}]{format_age(freshness.age_days)}[/{age_color}]",
308
+ f"[bright_black]{tags_str}[/bright_black]",
309
+ )
310
+
311
+ # Print summary
312
+ filter_info = []
313
+ if memory_type:
314
+ filter_info.append(f"type={memory_type}")
315
+ if search:
316
+ filter_info.append(f"search='{search}'")
317
+
318
+ if filter_info:
319
+ console.print(f"[bright_black]Filters: {', '.join(filter_info)}[/bright_black]")
320
+
321
+ console.print(table)
322
+ console.print(
323
+ f"\n[bright_black]Showing {len(typed_memories_with_content)} memories. "
324
+ f"Use --limit N to show more.[/bright_black]"
325
+ )
326
+
327
+
328
+ # =============================================================================
329
+ # Graph Visualization
330
+ # =============================================================================
331
+
332
+
333
+ async def render_graph(
334
+ storage: PersistentStorage,
335
+ query: str | None = None,
336
+ depth: int = 2,
337
+ ) -> None:
338
+ """Render a text-based graph visualization."""
339
+ from neural_memory.core.synapse import SynapseType
340
+
341
+ brain = await storage.get_brain(storage._current_brain_id)
342
+ if not brain:
343
+ console.print("[red]No brain configured[/red]")
344
+ return
345
+
346
+ # If query provided, find related neurons
347
+ if query:
348
+ from neural_memory.engine.retrieval import DepthLevel, ReflexPipeline
349
+
350
+ pipeline = ReflexPipeline(storage, brain.config)
351
+ result = await pipeline.query(
352
+ query=query,
353
+ depth=DepthLevel(min(depth, 3)),
354
+ max_tokens=1000,
355
+ )
356
+
357
+ if result.confidence < 0.1:
358
+ console.print("[yellow]No relevant memories found for query.[/yellow]")
359
+ return
360
+
361
+ # Get fibers matched
362
+ fibers = []
363
+ for fiber_id in result.fibers_matched[:5]:
364
+ fiber = await storage.get_fiber(fiber_id)
365
+ if fiber:
366
+ fibers.append(fiber)
367
+ else:
368
+ # Get recent fibers
369
+ fibers = await storage.get_fibers(limit=5)
370
+
371
+ if not fibers:
372
+ console.print("[yellow]No memories to visualize.[/yellow]")
373
+ return
374
+
375
+ # Build tree visualization
376
+ tree = Tree(
377
+ "[bold cyan][*] Neural Graph[/bold cyan]",
378
+ guide_style="bright_black",
379
+ )
380
+
381
+ synapse_icons = {
382
+ SynapseType.CAUSED_BY: "<-",
383
+ SynapseType.LEADS_TO: "->",
384
+ SynapseType.CO_OCCURS: "<->",
385
+ SynapseType.RELATED_TO: "~",
386
+ SynapseType.SIMILAR_TO: "~~",
387
+ SynapseType.HAPPENED_AT: "@",
388
+ SynapseType.AT_LOCATION: "#",
389
+ SynapseType.INVOLVES: "&",
390
+ }
391
+
392
+ for fiber in fibers:
393
+ # Get fiber content
394
+ content = fiber.summary or ""
395
+ if not content and fiber.anchor_neuron_id:
396
+ anchor = await storage.get_neuron(fiber.anchor_neuron_id)
397
+ if anchor:
398
+ content = anchor.content
399
+
400
+ display_content = content[:50] + "..." if len(content) > 50 else content
401
+
402
+ fiber_branch = tree.add(
403
+ f"[green]*[/green] {display_content}",
404
+ )
405
+
406
+ # Get connected neurons
407
+ if fiber.anchor_neuron_id:
408
+ neighbors = await storage.get_neighbors(fiber.anchor_neuron_id, direction="both")
409
+
410
+ for neighbor, synapse in neighbors[:5]:
411
+ icon = synapse_icons.get(synapse.type, "─")
412
+ neighbor_content = neighbor.content[:30]
413
+ if len(neighbor.content) > 30:
414
+ neighbor_content += "..."
415
+
416
+ synapse_color = "blue" if synapse.weight > 0.5 else "bright_black"
417
+ fiber_branch.add(
418
+ f"[{synapse_color}]{icon}[/{synapse_color}] "
419
+ f"[bright_black]{synapse.type.value}[/bright_black] "
420
+ f"[white]{neighbor_content}[/white]"
421
+ )
422
+
423
+ console.print()
424
+ console.print(tree)
425
+ console.print()
426
+
427
+ # Legend
428
+ legend = Table(show_header=False, box=None, padding=(0, 2))
429
+ legend.add_column("Symbol")
430
+ legend.add_column("Meaning")
431
+ legend.add_row("[green]*[/green]", "Memory (Fiber)")
432
+ legend.add_row("->", "leads_to")
433
+ legend.add_row("<-", "caused_by")
434
+ legend.add_row("<->", "co_occurs")
435
+ legend.add_row("@", "happened_at")
436
+ legend.add_row("#", "at_location")
437
+
438
+ console.print(Panel(legend, title="Legend", border_style="bright_black"))
439
+
440
+
441
+ # =============================================================================
442
+ # Quick Stats (for prompt injection)
443
+ # =============================================================================
444
+
445
+
446
+ async def render_quick_stats(storage: PersistentStorage) -> str:
447
+ """Render a compact stats string for context injection."""
448
+ brain = await storage.get_brain(storage._current_brain_id)
449
+ if not brain:
450
+ return "No brain configured"
451
+
452
+ stats = await storage.get_stats(brain.id)
453
+ typed_memories = await storage.find_typed_memories(limit=1000)
454
+
455
+ # Count by type
456
+ type_counts: dict[str, int] = {}
457
+ for tm in typed_memories:
458
+ type_name = tm.memory_type.value
459
+ type_counts[type_name] = type_counts.get(type_name, 0) + 1
460
+
461
+ # Format
462
+ parts = [f"Brain: {brain.name}"]
463
+ parts.append(f"Memories: {stats['fiber_count']}")
464
+
465
+ if type_counts:
466
+ type_summary = ", ".join(
467
+ f"{count} {t}" for t, count in sorted(type_counts.items(), key=lambda x: -x[1])[:3]
468
+ )
469
+ parts.append(f"Types: {type_summary}")
470
+
471
+ return " | ".join(parts)
@@ -0,0 +1,52 @@
1
+ """Core data models for NeuralMemory."""
2
+
3
+ from neural_memory.core.brain import Brain, BrainConfig
4
+ from neural_memory.core.brain_mode import (
5
+ BrainMode,
6
+ BrainModeConfig,
7
+ HybridConfig,
8
+ SharedConfig,
9
+ SyncStrategy,
10
+ )
11
+ from neural_memory.core.fiber import Fiber
12
+ from neural_memory.core.memory_types import (
13
+ Confidence,
14
+ MemoryType,
15
+ Priority,
16
+ Provenance,
17
+ TypedMemory,
18
+ suggest_memory_type,
19
+ )
20
+ from neural_memory.core.neuron import Neuron, NeuronState, NeuronType
21
+ from neural_memory.core.project import MemoryScope, Project
22
+ from neural_memory.core.synapse import Direction, Synapse, SynapseType
23
+
24
+ __all__ = [
25
+ # Brain
26
+ "Brain",
27
+ "BrainConfig",
28
+ # Brain mode (local/shared toggle)
29
+ "BrainMode",
30
+ "BrainModeConfig",
31
+ "SharedConfig",
32
+ "HybridConfig",
33
+ "SyncStrategy",
34
+ # Memory structures
35
+ "Fiber",
36
+ "Neuron",
37
+ "NeuronState",
38
+ "NeuronType",
39
+ "Synapse",
40
+ "SynapseType",
41
+ "Direction",
42
+ # Memory types (MemoCore integration)
43
+ "MemoryType",
44
+ "Priority",
45
+ "Confidence",
46
+ "Provenance",
47
+ "TypedMemory",
48
+ "suggest_memory_type",
49
+ # Project scoping
50
+ "Project",
51
+ "MemoryScope",
52
+ ]