neural-memory 0.3.0__py3-none-any.whl → 0.5.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.
neural_memory/__init__.py CHANGED
@@ -13,7 +13,7 @@ from neural_memory.core.synapse import Direction, Synapse, SynapseType
13
13
  from neural_memory.engine.encoder import EncodingResult, MemoryEncoder
14
14
  from neural_memory.engine.retrieval import DepthLevel, ReflexPipeline, RetrievalResult
15
15
 
16
- __version__ = "0.3.0"
16
+ __version__ = "0.5.0"
17
17
 
18
18
  __all__ = [
19
19
  "__version__",
neural_memory/cli/main.py CHANGED
@@ -2901,7 +2901,8 @@ def serve(
2901
2901
  raise typer.Exit(1)
2902
2902
 
2903
2903
  typer.echo(f"Starting NeuralMemory API server on http://{host}:{port}")
2904
- typer.echo("API docs: http://{host}:{port}/docs")
2904
+ typer.echo(f" UI: http://{host}:{port}/ui")
2905
+ typer.echo(f" Docs: http://{host}:{port}/docs")
2905
2906
 
2906
2907
  uvicorn.run(
2907
2908
  "neural_memory.server.app:create_app",
@@ -2912,6 +2913,62 @@ def serve(
2912
2913
  )
2913
2914
 
2914
2915
 
2916
+ @app.command()
2917
+ def decay(
2918
+ brain: Annotated[
2919
+ str | None, typer.Option("--brain", "-b", help="Brain to apply decay to")
2920
+ ] = None,
2921
+ dry_run: Annotated[
2922
+ bool, typer.Option("--dry-run", "-n", help="Preview changes without applying")
2923
+ ] = False,
2924
+ prune_threshold: Annotated[
2925
+ float, typer.Option("--prune", "-p", help="Prune below this activation level")
2926
+ ] = 0.01,
2927
+ ) -> None:
2928
+ """Apply memory decay to simulate forgetting.
2929
+
2930
+ Memories that haven't been accessed recently will have their
2931
+ activation levels reduced following the Ebbinghaus forgetting curve.
2932
+
2933
+ Examples:
2934
+ nmem decay # Apply decay to current brain
2935
+ nmem decay -b work # Apply to specific brain
2936
+ nmem decay --dry-run # Preview without changes
2937
+ nmem decay --prune 0.05 # More aggressive pruning
2938
+ """
2939
+ from neural_memory.engine.lifecycle import DecayManager
2940
+ from neural_memory.unified_config import get_config, get_shared_storage
2941
+
2942
+ async def _decay() -> None:
2943
+ config = get_config()
2944
+ brain_name = brain or config.current_brain
2945
+
2946
+ typer.echo(f"Applying decay to brain '{brain_name}'...")
2947
+ if dry_run:
2948
+ typer.echo("(dry run - no changes will be saved)")
2949
+
2950
+ storage = await get_shared_storage(brain_name)
2951
+
2952
+ manager = DecayManager(
2953
+ decay_rate=config.brain.decay_rate,
2954
+ prune_threshold=prune_threshold,
2955
+ )
2956
+
2957
+ report = await manager.apply_decay(storage, dry_run=dry_run)
2958
+
2959
+ typer.echo("")
2960
+ typer.echo(report.summary())
2961
+
2962
+ if report.neurons_pruned > 0 or report.synapses_pruned > 0:
2963
+ typer.echo("")
2964
+ typer.echo(
2965
+ f"Pruned {report.neurons_pruned} neurons and "
2966
+ f"{report.synapses_pruned} synapses below threshold {prune_threshold}"
2967
+ )
2968
+
2969
+ asyncio.run(_decay())
2970
+
2971
+
2915
2972
  @app.command()
2916
2973
  def version() -> None:
2917
2974
  """Show version information."""
@@ -0,0 +1,233 @@
1
+ """Memory lifecycle management - decay, reinforcement, compression.
2
+
3
+ Implements the Ebbinghaus forgetting curve for natural memory decay
4
+ and reinforcement for frequently accessed memories.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timedelta
12
+ from typing import TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from neural_memory.storage.base import NeuralStorage
16
+
17
+
18
+ @dataclass
19
+ class DecayReport:
20
+ """Report of decay operation results."""
21
+
22
+ neurons_processed: int = 0
23
+ neurons_decayed: int = 0
24
+ neurons_pruned: int = 0
25
+ synapses_processed: int = 0
26
+ synapses_decayed: int = 0
27
+ synapses_pruned: int = 0
28
+ duration_ms: float = 0.0
29
+ reference_time: datetime = field(default_factory=datetime.now)
30
+
31
+ def summary(self) -> str:
32
+ """Generate human-readable summary."""
33
+ lines = [
34
+ f"Decay Report ({self.reference_time.strftime('%Y-%m-%d %H:%M')})",
35
+ f" Neurons: {self.neurons_decayed}/{self.neurons_processed} decayed, {self.neurons_pruned} pruned",
36
+ f" Synapses: {self.synapses_decayed}/{self.synapses_processed} decayed, {self.synapses_pruned} pruned",
37
+ f" Duration: {self.duration_ms:.1f}ms",
38
+ ]
39
+ return "\n".join(lines)
40
+
41
+
42
+ class DecayManager:
43
+ """Manage memory decay using Ebbinghaus forgetting curve.
44
+
45
+ Decay formula: retention = e^(-decay_rate * days_since_access)
46
+
47
+ Memories that haven't been accessed recently will have their
48
+ activation levels reduced. Memories below the prune threshold
49
+ can be marked as dormant or removed.
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ decay_rate: float = 0.1,
55
+ prune_threshold: float = 0.01,
56
+ min_age_days: float = 1.0,
57
+ ):
58
+ """Initialize decay manager.
59
+
60
+ Args:
61
+ decay_rate: Rate of decay per day (0.1 = 10% per day)
62
+ prune_threshold: Activation level below which to prune
63
+ min_age_days: Minimum age before applying decay
64
+ """
65
+ self.decay_rate = decay_rate
66
+ self.prune_threshold = prune_threshold
67
+ self.min_age_days = min_age_days
68
+
69
+ async def apply_decay(
70
+ self,
71
+ storage: NeuralStorage,
72
+ reference_time: datetime | None = None,
73
+ dry_run: bool = False,
74
+ ) -> DecayReport:
75
+ """Apply decay to all neurons and synapses in storage.
76
+
77
+ Args:
78
+ storage: Storage instance to apply decay to
79
+ reference_time: Reference time for decay calculation (default: now)
80
+ dry_run: If True, calculate but don't save changes
81
+
82
+ Returns:
83
+ DecayReport with statistics
84
+ """
85
+ import time
86
+
87
+ start_time = time.perf_counter()
88
+ reference_time = reference_time or datetime.now()
89
+ report = DecayReport(reference_time=reference_time)
90
+
91
+ # Get all neuron states
92
+ states = await storage.get_all_neuron_states()
93
+ report.neurons_processed = len(states)
94
+
95
+ for state in states:
96
+ if state.last_activated is None:
97
+ continue
98
+
99
+ # Calculate time since last activation
100
+ time_diff = reference_time - state.last_activated
101
+ days_elapsed = time_diff.total_seconds() / 86400
102
+
103
+ # Skip if too recent
104
+ if days_elapsed < self.min_age_days:
105
+ continue
106
+
107
+ # Calculate decay
108
+ decay_factor = math.exp(-self.decay_rate * days_elapsed)
109
+ new_level = state.activation_level * decay_factor
110
+
111
+ if new_level < state.activation_level:
112
+ report.neurons_decayed += 1
113
+
114
+ if new_level < self.prune_threshold:
115
+ report.neurons_pruned += 1
116
+ new_level = 0.0
117
+
118
+ if not dry_run:
119
+ # Update the neuron state
120
+ updated_state = state.with_activation(new_level)
121
+ updated_state = type(state)(
122
+ neuron_id=state.neuron_id,
123
+ activation_level=new_level,
124
+ access_frequency=state.access_frequency,
125
+ last_activated=state.last_activated,
126
+ decay_rate=state.decay_rate,
127
+ created_at=state.created_at,
128
+ )
129
+ await storage.update_neuron_state(updated_state)
130
+
131
+ # Get all synapses and apply decay
132
+ synapses = await storage.get_all_synapses()
133
+ report.synapses_processed = len(synapses)
134
+
135
+ for synapse in synapses:
136
+ if synapse.last_activated is None:
137
+ continue
138
+
139
+ time_diff = reference_time - synapse.last_activated
140
+ days_elapsed = time_diff.total_seconds() / 86400
141
+
142
+ if days_elapsed < self.min_age_days:
143
+ continue
144
+
145
+ # Decay synapse weight
146
+ decay_factor = math.exp(-self.decay_rate * days_elapsed)
147
+ new_weight = synapse.weight * decay_factor
148
+
149
+ if new_weight < synapse.weight:
150
+ report.synapses_decayed += 1
151
+
152
+ if new_weight < self.prune_threshold:
153
+ report.synapses_pruned += 1
154
+ if not dry_run:
155
+ # Could delete synapse here, but just set weight to 0 for now
156
+ pass
157
+
158
+ if not dry_run and new_weight >= self.prune_threshold:
159
+ decayed_synapse = synapse.decay(decay_factor)
160
+ await storage.update_synapse(decayed_synapse)
161
+
162
+ report.duration_ms = (time.perf_counter() - start_time) * 1000
163
+ return report
164
+
165
+
166
+ class ReinforcementManager:
167
+ """Strengthen frequently accessed memory paths.
168
+
169
+ When memories are accessed, their activation levels and
170
+ synapse weights are increased (reinforced).
171
+ """
172
+
173
+ def __init__(
174
+ self,
175
+ reinforcement_delta: float = 0.05,
176
+ max_activation: float = 1.0,
177
+ max_weight: float = 1.0,
178
+ ):
179
+ """Initialize reinforcement manager.
180
+
181
+ Args:
182
+ reinforcement_delta: Amount to increase on each access
183
+ max_activation: Maximum activation level
184
+ max_weight: Maximum synapse weight
185
+ """
186
+ self.reinforcement_delta = reinforcement_delta
187
+ self.max_activation = max_activation
188
+ self.max_weight = max_weight
189
+
190
+ async def reinforce(
191
+ self,
192
+ storage: NeuralStorage,
193
+ neuron_ids: list[str],
194
+ synapse_ids: list[str] | None = None,
195
+ ) -> int:
196
+ """Reinforce accessed neurons and synapses.
197
+
198
+ Args:
199
+ storage: Storage instance
200
+ neuron_ids: List of accessed neuron IDs
201
+ synapse_ids: Optional list of accessed synapse IDs
202
+
203
+ Returns:
204
+ Number of items reinforced
205
+ """
206
+ reinforced = 0
207
+
208
+ for neuron_id in neuron_ids:
209
+ state = await storage.get_neuron_state(neuron_id)
210
+ if state:
211
+ new_level = min(
212
+ state.activation_level + self.reinforcement_delta,
213
+ self.max_activation,
214
+ )
215
+ activated_state = state.activate(new_level - state.activation_level)
216
+ await storage.update_neuron_state(activated_state)
217
+ reinforced += 1
218
+
219
+ if synapse_ids:
220
+ for synapse_id in synapse_ids:
221
+ synapse = await storage.get_synapse(synapse_id)
222
+ if synapse:
223
+ new_weight = min(
224
+ synapse.weight + self.reinforcement_delta,
225
+ self.max_weight,
226
+ )
227
+ reinforced_synapse = synapse.reinforce(
228
+ new_weight - synapse.weight
229
+ )
230
+ await storage.update_synapse(reinforced_synapse)
231
+ reinforced += 1
232
+
233
+ return reinforced
@@ -603,7 +603,7 @@ async def handle_message(server: MCPServer, message: dict[str, Any]) -> dict[str
603
603
  "id": msg_id,
604
604
  "result": {
605
605
  "protocolVersion": "2024-11-05",
606
- "serverInfo": {"name": "neural-memory", "version": "0.3.0"},
606
+ "serverInfo": {"name": "neural-memory", "version": "0.4.0"},
607
607
  "capabilities": {"tools": {}, "resources": {}},
608
608
  },
609
609
  }
@@ -4,15 +4,21 @@ from __future__ import annotations
4
4
 
5
5
  from collections.abc import AsyncGenerator
6
6
  from contextlib import asynccontextmanager
7
+ from pathlib import Path
7
8
 
8
9
  from fastapi import FastAPI
9
10
  from fastapi.middleware.cors import CORSMiddleware
11
+ from fastapi.responses import FileResponse
12
+ from fastapi.staticfiles import StaticFiles
10
13
 
11
14
  from neural_memory import __version__
12
15
  from neural_memory.server.models import HealthResponse
13
16
  from neural_memory.server.routes import brain_router, memory_router, sync_router
14
17
  from neural_memory.storage.memory_store import InMemoryStorage
15
18
 
19
+ # Static files directory
20
+ STATIC_DIR = Path(__file__).parent / "static"
21
+
16
22
 
17
23
  @asynccontextmanager
18
24
  async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
@@ -90,8 +96,70 @@ def create_app(
90
96
  "version": __version__,
91
97
  "docs": "/docs",
92
98
  "health": "/health",
99
+ "ui": "/ui",
100
+ }
101
+
102
+ # Graph visualization API
103
+ @app.get("/api/graph", tags=["visualization"])
104
+ async def get_graph_data() -> dict:
105
+ """Get graph data for visualization."""
106
+ from neural_memory.unified_config import get_shared_storage
107
+
108
+ storage = await get_shared_storage()
109
+
110
+ # Get all data
111
+ neurons = await storage.find_neurons()
112
+ synapses = await storage.get_all_synapses()
113
+ fibers = await storage.get_fibers(limit=1000)
114
+
115
+ stats = {
116
+ "neuron_count": len(neurons),
117
+ "synapse_count": len(synapses),
118
+ "fiber_count": len(fibers),
93
119
  }
94
120
 
121
+ return {
122
+ "neurons": [
123
+ {
124
+ "id": n.id,
125
+ "type": n.type.value,
126
+ "content": n.content,
127
+ "metadata": n.metadata,
128
+ }
129
+ for n in neurons
130
+ ],
131
+ "synapses": [
132
+ {
133
+ "id": s.id,
134
+ "source_id": s.source_id,
135
+ "target_id": s.target_id,
136
+ "type": s.type.value,
137
+ "weight": s.weight,
138
+ "direction": s.direction.value,
139
+ }
140
+ for s in synapses
141
+ ],
142
+ "fibers": [
143
+ {
144
+ "id": f.id,
145
+ "summary": f.summary,
146
+ "neuron_count": len(f.neuron_ids) if f.neuron_ids else 0,
147
+ }
148
+ for f in fibers
149
+ ],
150
+ "stats": stats,
151
+ }
152
+
153
+ # UI endpoint
154
+ @app.get("/ui", tags=["visualization"])
155
+ async def ui() -> FileResponse:
156
+ """Serve the visualization UI."""
157
+ return FileResponse(STATIC_DIR / "index.html")
158
+
159
+ # Mount static files (for potential future assets)
160
+ if STATIC_DIR.exists():
161
+ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
162
+
95
163
  return app
96
164
 
97
165
 
@@ -0,0 +1,403 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NeuralMemory - Brain Visualization</title>
7
+ <script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
16
+ background: #1a1a2e;
17
+ color: #eee;
18
+ }
19
+ .container {
20
+ display: flex;
21
+ height: 100vh;
22
+ }
23
+ .sidebar {
24
+ width: 300px;
25
+ background: #16213e;
26
+ padding: 20px;
27
+ overflow-y: auto;
28
+ }
29
+ .main {
30
+ flex: 1;
31
+ display: flex;
32
+ flex-direction: column;
33
+ }
34
+ .header {
35
+ background: #0f3460;
36
+ padding: 15px 20px;
37
+ display: flex;
38
+ justify-content: space-between;
39
+ align-items: center;
40
+ }
41
+ .header h1 {
42
+ font-size: 1.5rem;
43
+ color: #e94560;
44
+ }
45
+ .stats {
46
+ display: flex;
47
+ gap: 20px;
48
+ }
49
+ .stat {
50
+ text-align: center;
51
+ }
52
+ .stat-value {
53
+ font-size: 1.5rem;
54
+ font-weight: bold;
55
+ color: #e94560;
56
+ }
57
+ .stat-label {
58
+ font-size: 0.8rem;
59
+ color: #aaa;
60
+ }
61
+ #graph {
62
+ flex: 1;
63
+ background: #1a1a2e;
64
+ }
65
+ .sidebar h2 {
66
+ color: #e94560;
67
+ margin-bottom: 15px;
68
+ font-size: 1.1rem;
69
+ }
70
+ .sidebar h3 {
71
+ color: #aaa;
72
+ margin: 15px 0 10px;
73
+ font-size: 0.9rem;
74
+ text-transform: uppercase;
75
+ }
76
+ .brain-select {
77
+ width: 100%;
78
+ padding: 10px;
79
+ background: #0f3460;
80
+ border: 1px solid #e94560;
81
+ color: #eee;
82
+ border-radius: 5px;
83
+ margin-bottom: 15px;
84
+ }
85
+ .btn {
86
+ width: 100%;
87
+ padding: 10px;
88
+ background: #e94560;
89
+ border: none;
90
+ color: white;
91
+ border-radius: 5px;
92
+ cursor: pointer;
93
+ margin-bottom: 10px;
94
+ font-size: 0.9rem;
95
+ }
96
+ .btn:hover {
97
+ background: #ff6b6b;
98
+ }
99
+ .btn-secondary {
100
+ background: #0f3460;
101
+ border: 1px solid #e94560;
102
+ }
103
+ .btn-secondary:hover {
104
+ background: #16213e;
105
+ }
106
+ .node-info {
107
+ background: #0f3460;
108
+ padding: 15px;
109
+ border-radius: 5px;
110
+ margin-top: 15px;
111
+ }
112
+ .node-info p {
113
+ margin: 5px 0;
114
+ font-size: 0.85rem;
115
+ }
116
+ .node-info .label {
117
+ color: #aaa;
118
+ }
119
+ .node-info .value {
120
+ color: #eee;
121
+ }
122
+ .legend {
123
+ display: flex;
124
+ flex-wrap: wrap;
125
+ gap: 10px;
126
+ margin-top: 15px;
127
+ }
128
+ .legend-item {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 5px;
132
+ font-size: 0.75rem;
133
+ }
134
+ .legend-color {
135
+ width: 12px;
136
+ height: 12px;
137
+ border-radius: 50%;
138
+ }
139
+ .loading {
140
+ display: flex;
141
+ justify-content: center;
142
+ align-items: center;
143
+ height: 100%;
144
+ font-size: 1.2rem;
145
+ color: #aaa;
146
+ }
147
+ .error {
148
+ color: #ff6b6b;
149
+ padding: 10px;
150
+ background: rgba(255,107,107,0.1);
151
+ border-radius: 5px;
152
+ margin-top: 10px;
153
+ }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div class="container">
158
+ <aside class="sidebar">
159
+ <h2>NeuralMemory</h2>
160
+
161
+ <select id="brainSelect" class="brain-select">
162
+ <option value="">Loading brains...</option>
163
+ </select>
164
+
165
+ <button class="btn" onclick="loadGraph()">Refresh Graph</button>
166
+ <button class="btn btn-secondary" onclick="resetView()">Reset View</button>
167
+
168
+ <h3>Statistics</h3>
169
+ <div class="stats" style="flex-direction: column; gap: 10px;">
170
+ <div class="stat" style="text-align: left;">
171
+ <span class="stat-label">Neurons: </span>
172
+ <span class="stat-value" id="neuronCount">0</span>
173
+ </div>
174
+ <div class="stat" style="text-align: left;">
175
+ <span class="stat-label">Synapses: </span>
176
+ <span class="stat-value" id="synapseCount">0</span>
177
+ </div>
178
+ <div class="stat" style="text-align: left;">
179
+ <span class="stat-label">Fibers: </span>
180
+ <span class="stat-value" id="fiberCount">0</span>
181
+ </div>
182
+ </div>
183
+
184
+ <h3>Selected Node</h3>
185
+ <div class="node-info" id="nodeInfo">
186
+ <p><span class="label">Click a node to see details</span></p>
187
+ </div>
188
+
189
+ <h3>Legend</h3>
190
+ <div class="legend">
191
+ <div class="legend-item">
192
+ <div class="legend-color" style="background: #e94560;"></div>
193
+ <span>Concept</span>
194
+ </div>
195
+ <div class="legend-item">
196
+ <div class="legend-color" style="background: #4ecdc4;"></div>
197
+ <span>Entity</span>
198
+ </div>
199
+ <div class="legend-item">
200
+ <div class="legend-color" style="background: #ffe66d;"></div>
201
+ <span>Time</span>
202
+ </div>
203
+ <div class="legend-item">
204
+ <div class="legend-color" style="background: #95e1d3;"></div>
205
+ <span>Action</span>
206
+ </div>
207
+ <div class="legend-item">
208
+ <div class="legend-color" style="background: #f38181;"></div>
209
+ <span>State</span>
210
+ </div>
211
+ <div class="legend-item">
212
+ <div class="legend-color" style="background: #aa96da;"></div>
213
+ <span>Other</span>
214
+ </div>
215
+ </div>
216
+ </aside>
217
+
218
+ <main class="main">
219
+ <header class="header">
220
+ <h1>Brain Visualization</h1>
221
+ <div class="stats">
222
+ <div class="stat">
223
+ <div class="stat-value" id="visibleNodes">0</div>
224
+ <div class="stat-label">Visible Nodes</div>
225
+ </div>
226
+ <div class="stat">
227
+ <div class="stat-value" id="visibleEdges">0</div>
228
+ <div class="stat-label">Visible Edges</div>
229
+ </div>
230
+ </div>
231
+ </header>
232
+ <div id="graph">
233
+ <div class="loading">Loading graph...</div>
234
+ </div>
235
+ </main>
236
+ </div>
237
+
238
+ <script>
239
+ const API_BASE = window.location.origin;
240
+ let network = null;
241
+ let allNodes = [];
242
+ let allEdges = [];
243
+
244
+ const COLORS = {
245
+ concept: '#e94560',
246
+ entity: '#4ecdc4',
247
+ time: '#ffe66d',
248
+ action: '#95e1d3',
249
+ state: '#f38181',
250
+ spatial: '#45b7d1',
251
+ sensory: '#96ceb4',
252
+ intent: '#dda0dd',
253
+ default: '#aa96da'
254
+ };
255
+
256
+ async function loadBrains() {
257
+ try {
258
+ // For now, use default brain from unified config
259
+ const select = document.getElementById('brainSelect');
260
+ select.innerHTML = '<option value="default">default</option>';
261
+ } catch (error) {
262
+ console.error('Error loading brains:', error);
263
+ }
264
+ }
265
+
266
+ async function loadGraph() {
267
+ const graphContainer = document.getElementById('graph');
268
+ graphContainer.innerHTML = '<div class="loading">Loading graph...</div>';
269
+
270
+ try {
271
+ const response = await fetch(`${API_BASE}/api/graph`);
272
+ if (!response.ok) {
273
+ throw new Error(`HTTP ${response.status}`);
274
+ }
275
+ const data = await response.json();
276
+
277
+ // Update stats
278
+ document.getElementById('neuronCount').textContent = data.stats.neuron_count;
279
+ document.getElementById('synapseCount').textContent = data.stats.synapse_count;
280
+ document.getElementById('fiberCount').textContent = data.stats.fiber_count;
281
+
282
+ // Convert to vis.js format
283
+ allNodes = data.neurons.map(n => ({
284
+ id: n.id,
285
+ label: truncate(n.content, 20),
286
+ title: n.content,
287
+ color: COLORS[n.type] || COLORS.default,
288
+ type: n.type,
289
+ content: n.content,
290
+ metadata: n.metadata
291
+ }));
292
+
293
+ allEdges = data.synapses.map(s => ({
294
+ id: s.id,
295
+ from: s.source_id,
296
+ to: s.target_id,
297
+ label: s.type,
298
+ title: `${s.type} (weight: ${s.weight.toFixed(2)})`,
299
+ arrows: s.direction === 'uni' ? 'to' : 'to, from',
300
+ width: Math.max(1, s.weight * 3),
301
+ color: { color: '#555', highlight: '#e94560' }
302
+ }));
303
+
304
+ renderGraph();
305
+
306
+ } catch (error) {
307
+ console.error('Error loading graph:', error);
308
+ graphContainer.innerHTML = `<div class="error">Error loading graph: ${error.message}<br>Make sure the server is running with UI enabled.</div>`;
309
+ }
310
+ }
311
+
312
+ function renderGraph() {
313
+ const container = document.getElementById('graph');
314
+ container.innerHTML = '';
315
+
316
+ const nodes = new vis.DataSet(allNodes);
317
+ const edges = new vis.DataSet(allEdges);
318
+
319
+ document.getElementById('visibleNodes').textContent = allNodes.length;
320
+ document.getElementById('visibleEdges').textContent = allEdges.length;
321
+
322
+ const options = {
323
+ nodes: {
324
+ shape: 'dot',
325
+ size: 16,
326
+ font: {
327
+ size: 12,
328
+ color: '#eee'
329
+ },
330
+ borderWidth: 2,
331
+ shadow: true
332
+ },
333
+ edges: {
334
+ font: {
335
+ size: 10,
336
+ color: '#888',
337
+ strokeWidth: 0
338
+ },
339
+ smooth: {
340
+ type: 'continuous'
341
+ }
342
+ },
343
+ physics: {
344
+ stabilization: {
345
+ iterations: 100
346
+ },
347
+ barnesHut: {
348
+ gravitationalConstant: -2000,
349
+ springLength: 150
350
+ }
351
+ },
352
+ interaction: {
353
+ hover: true,
354
+ tooltipDelay: 200
355
+ }
356
+ };
357
+
358
+ network = new vis.Network(container, { nodes, edges }, options);
359
+
360
+ network.on('selectNode', function(params) {
361
+ if (params.nodes.length > 0) {
362
+ const nodeId = params.nodes[0];
363
+ const node = allNodes.find(n => n.id === nodeId);
364
+ showNodeInfo(node);
365
+ }
366
+ });
367
+
368
+ network.on('deselectNode', function() {
369
+ document.getElementById('nodeInfo').innerHTML =
370
+ '<p><span class="label">Click a node to see details</span></p>';
371
+ });
372
+ }
373
+
374
+ function showNodeInfo(node) {
375
+ if (!node) return;
376
+
377
+ const infoDiv = document.getElementById('nodeInfo');
378
+ infoDiv.innerHTML = `
379
+ <p><span class="label">Type:</span> <span class="value">${node.type}</span></p>
380
+ <p><span class="label">Content:</span> <span class="value">${node.content}</span></p>
381
+ <p><span class="label">ID:</span> <span class="value" style="font-size:0.7rem">${node.id}</span></p>
382
+ `;
383
+ }
384
+
385
+ function resetView() {
386
+ if (network) {
387
+ network.fit();
388
+ }
389
+ }
390
+
391
+ function truncate(str, len) {
392
+ if (!str) return '';
393
+ return str.length > len ? str.substring(0, len) + '...' : str;
394
+ }
395
+
396
+ // Initialize
397
+ document.addEventListener('DOMContentLoaded', () => {
398
+ loadBrains();
399
+ loadGraph();
400
+ });
401
+ </script>
402
+ </body>
403
+ </html>
@@ -396,6 +396,18 @@ class SQLiteStorage(NeuralStorage):
396
396
  created_at=datetime.fromisoformat(row["created_at"]),
397
397
  )
398
398
 
399
+ async def get_all_neuron_states(self) -> list[NeuronState]:
400
+ """Get all neuron states for current brain."""
401
+ conn = self._ensure_conn()
402
+ brain_id = self._get_brain_id()
403
+
404
+ async with conn.execute(
405
+ "SELECT * FROM neuron_states WHERE brain_id = ?",
406
+ (brain_id,),
407
+ ) as cursor:
408
+ rows = await cursor.fetchall()
409
+ return [self._row_to_neuron_state(row) for row in rows]
410
+
399
411
  # ========== Synapse Operations ==========
400
412
 
401
413
  async def add_synapse(self, synapse: Synapse) -> str:
@@ -486,6 +498,10 @@ class SQLiteStorage(NeuralStorage):
486
498
  rows = await cursor.fetchall()
487
499
  return [self._row_to_synapse(row) for row in rows]
488
500
 
501
+ async def get_all_synapses(self) -> list[Synapse]:
502
+ """Get all synapses for current brain."""
503
+ return await self.get_synapses()
504
+
489
505
  async def update_synapse(self, synapse: Synapse) -> None:
490
506
  conn = self._ensure_conn()
491
507
  brain_id = self._get_brain_id()
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neural-memory
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Reflex-based memory system for AI agents - retrieval through activation, not search
5
5
  Project-URL: Homepage, https://github.com/nhadaututtheky/neural-memory
6
- Project-URL: Documentation, https://github.com/nhadaututtheky/neural-memory#readme
6
+ Project-URL: Documentation, https://nhadaututtheky.github.io/neural-memory
7
7
  Project-URL: Repository, https://github.com/nhadaututtheky/neural-memory
8
8
  Project-URL: Issues, https://github.com/nhadaututtheky/neural-memory/issues
9
9
  Author: NeuralMemory Contributors
@@ -43,6 +43,11 @@ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
43
43
  Requires-Dist: pytest-cov>=4.0; extra == 'dev'
44
44
  Requires-Dist: pytest>=7.0; extra == 'dev'
45
45
  Requires-Dist: ruff>=0.1.0; extra == 'dev'
46
+ Provides-Extra: docs
47
+ Requires-Dist: mike>=2.0; extra == 'docs'
48
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
49
+ Requires-Dist: mkdocs>=1.5; extra == 'docs'
50
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
46
51
  Provides-Extra: neo4j
47
52
  Requires-Dist: neo4j>=5.0; extra == 'neo4j'
48
53
  Provides-Extra: nlp
@@ -1,10 +1,10 @@
1
- neural_memory/__init__.py,sha256=98W3ehXo6Pf4-bL0DpVt3W_aTIZPbyDqp4nXnI_uj0U,970
1
+ neural_memory/__init__.py,sha256=2JURAwRU2O0DjWp5mh7vhu0lMNa6ZSok2sZTi306oGM,970
2
2
  neural_memory/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  neural_memory/unified_config.py,sha256=BySYLNJmnBcz9A83kKztJ53ShkScJ5qOVqBNGQls0Rs,10208
4
4
  neural_memory/cli/__init__.py,sha256=4srWgYwiGcs-mG6dm91gRbYCN-om2hfkuxd-fYkgIGI,409
5
5
  neural_memory/cli/__main__.py,sha256=TnhrDVCqsaagmqua7QH1yaP9YXLNRvLxQ0pFrMxDYRw,132
6
6
  neural_memory/cli/config.py,sha256=3kuYeWyTJms_2H_xAgx0qXEN1FbaYwvcU0oKPdiGO2Y,5612
7
- neural_memory/cli/main.py,sha256=rQra4oGAmd18yJ0e3xbw18IKg3--FpQSqvdyZGRO8tM,99873
7
+ neural_memory/cli/main.py,sha256=XdfbuDpYPY6s5Uu8fTW1gWBf4cqjAiLAA75oDcu-xMk,101845
8
8
  neural_memory/cli/storage.py,sha256=xiIk9Nmygw2G-nBDuRaSh6ZY6Ccu3zX_1l_eyeOlIsM,6044
9
9
  neural_memory/cli/tui.py,sha256=3D0uhQD2LpP7caBOns7EeOsx-JsORl4-DyzSsS1HN64,16335
10
10
  neural_memory/core/__init__.py,sha256=j9oPfDRg6n3Jr1OwwjY1OufH1zEPcL5aZol23R8vik0,1181
@@ -18,6 +18,7 @@ neural_memory/core/synapse.py,sha256=esfb0kNKwyYw0Ps68ao7IfFxf9thyML24WJJoLgnQjM
18
18
  neural_memory/engine/__init__.py,sha256=bBuk-gqynBG_ebwB1scGu52PE6LjZw5fNOpL9zTe4wE,472
19
19
  neural_memory/engine/activation.py,sha256=wTecU_j5FBe3LgXAvPNnR6Nji-fsLeLsANezIBW0KaI,11221
20
20
  neural_memory/engine/encoder.py,sha256=uk3NvThLntXszzlbR0nO2GiPFikJ4th4TKkZf4NueyY,12716
21
+ neural_memory/engine/lifecycle.py,sha256=zhNdokjc8uz6YmT6Qm4zsgHhVZBN9TZg7aM3UzG_CIo,7908
21
22
  neural_memory/engine/retrieval.py,sha256=0IjyDVDnUJCdZo6umgjYIBElVbTggV45zGxRzYoQryg,13809
22
23
  neural_memory/extraction/__init__.py,sha256=XTuBnuAv0uWXs6LOUEQMoPBsBxVih93EvBaY1Vb_OCk,854
23
24
  neural_memory/extraction/entities.py,sha256=62To2Z-DbPFidOLn2pjY6tN10LmAxk_7vDyhTtxinso,13430
@@ -27,30 +28,31 @@ neural_memory/extraction/temporal.py,sha256=uESwr-NWTMNiRJHDryKBdSS3swnkp_FBwjUw
27
28
  neural_memory/mcp/__init__.py,sha256=8qo502ZfwryBv5htRaUJWsk3q9tbZX6TBtjmO6H0iTA,332
28
29
  neural_memory/mcp/__main__.py,sha256=BETnISNHCDaqFhvOo51fSV1DcB2yMu9iefu-b6mA0bk,137
29
30
  neural_memory/mcp/prompt.py,sha256=PTTWJ5ZxSPFq42ojU7UH8DKqgJxepg8ZMZmj9xYcaKQ,5003
30
- neural_memory/mcp/server.py,sha256=lQ9kfNOU8nkBd8mza506h9N9b08uyBqNkp8AoUT_6dc,26121
31
+ neural_memory/mcp/server.py,sha256=c4k3jFcmAUDDBZ71kk1y9q0XRgjTaZWtd3n7FWW-fMk,26121
31
32
  neural_memory/safety/__init__.py,sha256=25_pdeXu-UI3dNsPYiCsC3Rvq26ma-tCd3h1ow7Ny3w,679
32
33
  neural_memory/safety/freshness.py,sha256=nwUdybjsDK_8uwa_mJRPflx0XGZXVwDwJCw-LEVyh9c,6748
33
34
  neural_memory/safety/sensitive.py,sha256=3dCv951D1JOMo6V4vu11d0OxUFhxCITZ2w05HhJ3CtA,9359
34
35
  neural_memory/server/__init__.py,sha256=-VB8CldyBAzD10NK0aFBKMhUkHwR4aJHn05g_4X8xYQ,114
35
- neural_memory/server/app.py,sha256=dEc1qKxTt6bGlzkiwlatVOZ_p48ukW4Dd14mClpD4vw,2649
36
+ neural_memory/server/app.py,sha256=nd2fKFeOGs2KuOsARsxkZO4duD2D75Pz1Bw7v6lake4,4791
36
37
  neural_memory/server/dependencies.py,sha256=gcUsxihGs7VrhZjmqzA4VwytTNkQE9IMswQnf5t-fnA,923
37
38
  neural_memory/server/models.py,sha256=HDrqIMamc2c4bf2yrsVgTGDQZcO27ASCjeVJAdOWpso,4259
38
39
  neural_memory/server/routes/__init__.py,sha256=dkZBeETlaGDW1uetueQWhCRgzfUGr64T_ELkDUWFi1c,310
39
40
  neural_memory/server/routes/brain.py,sha256=ej7N5jU8SKTC4jkX-uXEmSojONd3q0EJjgjJwEVKXXc,6389
40
41
  neural_memory/server/routes/memory.py,sha256=A-A8PF-aX7L8RhEN_zn0Gno09oI1IUnYadEZ7B5YcC8,5076
41
42
  neural_memory/server/routes/sync.py,sha256=5nvnuu8EakOQ-iNEBztSehvbwQDpdT5YwMP8zy3RBco,12747
43
+ neural_memory/server/static/index.html,sha256=iOtVm8LgHVij5FxRBKBWwokMLdzbZjs3pvITEtMwWUk,13098
42
44
  neural_memory/storage/__init__.py,sha256=jiVLlyvj_Yn-E24O_edNQmyYhNsUNmT8OMvvrvxll9c,542
43
45
  neural_memory/storage/base.py,sha256=LxoiYseiNP4zOpK__lYW7eGkUiNw7tuLDPX_vH_dZNY,10565
44
46
  neural_memory/storage/factory.py,sha256=kQYTDuPG3LTW8lPrzn556DDci3d8uEHfWUgn0uox3E4,10567
45
47
  neural_memory/storage/memory_store.py,sha256=cY5Gtw1_oDyOzDVuIQTPZ-q0javFb2t2gr1lFC_a_Vo,31669
46
48
  neural_memory/storage/shared_store.py,sha256=wI33TY40c-_7sCIRIn63mLzfSXHgvdhGUnrj4fMK2tg,23372
47
- neural_memory/storage/sqlite_store.py,sha256=TARL9mIvaeY_NwA1DJ8cy25f4C9ZEFa6PyC_xcZe80g,60327
49
+ neural_memory/storage/sqlite_store.py,sha256=5JeCtETIPi0CQPwfCcl7T7vy7g4X2HxkPjsbhY9RZDY,60922
48
50
  neural_memory/sync/__init__.py,sha256=epKpdAsHzwUYXrWawlHPckSEgK5CZYb-B29AAcdTnVk,162
49
51
  neural_memory/sync/client.py,sha256=kiKqRlteXVe9P-IzgayV7bwdT9Ovu2UgV_XJtEUIbxQ,13421
50
52
  neural_memory/utils/__init__.py,sha256=vZM_jKQ_BUUd6jXtz9BGq5A1d-kFRMcMXt96XVhRv2M,135
51
53
  neural_memory/utils/config.py,sha256=jgDaHksEibusC2gub1I_USUBOJyP6gBHcfgS7RkL4dc,3086
52
- neural_memory-0.3.0.dist-info/METADATA,sha256=UarxhykY-BWQ-PhREpptBF9MUDnD5ejnHmpxKNGAmKI,10305
53
- neural_memory-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
54
- neural_memory-0.3.0.dist-info/entry_points.txt,sha256=u0ZGaAD6uU98g0Wheyj1WjrSchm_zlAkyzz1HRbKOnE,121
55
- neural_memory-0.3.0.dist-info/licenses/LICENSE,sha256=uuCGDPgkW8shclBRpQNK5I0T97ZQy9HHolwo9Qr3Bbc,1082
56
- neural_memory-0.3.0.dist-info/RECORD,,
54
+ neural_memory-0.5.0.dist-info/METADATA,sha256=iN0ws7O45v7RhXvxX_LdOUdykppaXOYquGL0uRQeesg,10516
55
+ neural_memory-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
56
+ neural_memory-0.5.0.dist-info/entry_points.txt,sha256=u0ZGaAD6uU98g0Wheyj1WjrSchm_zlAkyzz1HRbKOnE,121
57
+ neural_memory-0.5.0.dist-info/licenses/LICENSE,sha256=uuCGDPgkW8shclBRpQNK5I0T97ZQy9HHolwo9Qr3Bbc,1082
58
+ neural_memory-0.5.0.dist-info/RECORD,,