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,329 @@
1
+ """Storage factory for creating storage based on configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from neural_memory.core.brain_mode import BrainMode, BrainModeConfig
8
+ from neural_memory.storage.memory_store import InMemoryStorage
9
+ from neural_memory.storage.shared_store import SharedStorage
10
+ from neural_memory.storage.sqlite_store import SQLiteStorage
11
+
12
+ if TYPE_CHECKING:
13
+ from neural_memory.storage.base import NeuralStorage
14
+
15
+
16
+ async def create_storage(
17
+ config: BrainModeConfig,
18
+ brain_id: str,
19
+ *,
20
+ local_path: str | None = None,
21
+ ) -> NeuralStorage:
22
+ """
23
+ Create a storage instance based on configuration.
24
+
25
+ Args:
26
+ config: Brain mode configuration
27
+ brain_id: ID of the brain to connect to
28
+ local_path: Path for local SQLite storage (used in LOCAL mode)
29
+
30
+ Returns:
31
+ Configured storage instance
32
+
33
+ Examples:
34
+ # Local mode with SQLite
35
+ config = BrainModeConfig.local()
36
+ storage = await create_storage(config, "brain-1", local_path="./brain.db")
37
+
38
+ # Shared mode
39
+ config = BrainModeConfig.shared_mode("http://localhost:8000")
40
+ storage = await create_storage(config, "brain-1")
41
+
42
+ # Hybrid mode
43
+ config = BrainModeConfig.hybrid_mode("./local.db", "http://localhost:8000")
44
+ storage = await create_storage(config, "brain-1")
45
+ """
46
+ if config.mode == BrainMode.LOCAL:
47
+ if local_path:
48
+ storage = SQLiteStorage(local_path)
49
+ await storage.initialize()
50
+ storage.set_brain(brain_id)
51
+ return storage
52
+ else:
53
+ storage = InMemoryStorage()
54
+ storage.set_brain(brain_id)
55
+ return storage
56
+
57
+ elif config.mode == BrainMode.SHARED:
58
+ if not config.shared:
59
+ raise ValueError("SharedConfig required for SHARED mode")
60
+
61
+ storage = SharedStorage(
62
+ server_url=config.shared.server_url,
63
+ brain_id=brain_id,
64
+ timeout=config.shared.timeout,
65
+ api_key=config.shared.api_key,
66
+ )
67
+ await storage.connect()
68
+ return storage
69
+
70
+ elif config.mode == BrainMode.HYBRID:
71
+ if not config.hybrid:
72
+ raise ValueError("HybridConfig required for HYBRID mode")
73
+
74
+ # For hybrid mode, return a HybridStorage that wraps both local and remote
75
+ storage = await HybridStorage.create(
76
+ local_path=config.hybrid.local_path,
77
+ server_url=config.hybrid.server_url,
78
+ brain_id=brain_id,
79
+ api_key=config.hybrid.api_key,
80
+ sync_strategy=config.hybrid.sync_strategy,
81
+ auto_sync_on_encode=config.hybrid.auto_sync_on_encode,
82
+ )
83
+ return storage
84
+
85
+ else:
86
+ raise ValueError(f"Unknown brain mode: {config.mode}")
87
+
88
+
89
+ class HybridStorage:
90
+ """
91
+ Hybrid storage that combines local SQLite with remote sync.
92
+
93
+ Provides offline-first capability with optional sync to server.
94
+ """
95
+
96
+ def __init__(
97
+ self,
98
+ local: SQLiteStorage,
99
+ remote: SharedStorage,
100
+ *,
101
+ auto_sync_on_encode: bool = True,
102
+ ) -> None:
103
+ self._local = local
104
+ self._remote = remote
105
+ self._auto_sync = auto_sync_on_encode
106
+ self._brain_id: str | None = None
107
+
108
+ @classmethod
109
+ async def create(
110
+ cls,
111
+ local_path: str,
112
+ server_url: str,
113
+ brain_id: str,
114
+ *,
115
+ api_key: str | None = None,
116
+ sync_strategy: str = "bidirectional",
117
+ auto_sync_on_encode: bool = True,
118
+ ) -> HybridStorage:
119
+ """Create and initialize hybrid storage."""
120
+ local = SQLiteStorage(local_path)
121
+ await local.initialize()
122
+ local.set_brain(brain_id)
123
+
124
+ remote = SharedStorage(
125
+ server_url=server_url,
126
+ brain_id=brain_id,
127
+ api_key=api_key,
128
+ )
129
+ # Don't connect remote immediately - connect on demand
130
+
131
+ storage = cls(
132
+ local=local,
133
+ remote=remote,
134
+ auto_sync_on_encode=auto_sync_on_encode,
135
+ )
136
+ storage._brain_id = brain_id
137
+ return storage
138
+
139
+ def set_brain(self, brain_id: str) -> None:
140
+ """Set the current brain context."""
141
+ self._brain_id = brain_id
142
+ self._local.set_brain(brain_id)
143
+ self._remote.set_brain(brain_id)
144
+
145
+ # Delegate all NeuralStorage methods to local storage
146
+ # Sync to remote when appropriate
147
+
148
+ async def add_neuron(self, neuron):
149
+ """Add neuron locally, optionally sync."""
150
+ result = await self._local.add_neuron(neuron)
151
+ if self._auto_sync:
152
+ try:
153
+ await self._ensure_connected()
154
+ await self._remote.add_neuron(neuron)
155
+ except Exception:
156
+ pass # Offline, will sync later
157
+ return result
158
+
159
+ async def get_neuron(self, neuron_id):
160
+ """Get neuron from local storage."""
161
+ return await self._local.get_neuron(neuron_id)
162
+
163
+ async def find_neurons(self, **kwargs):
164
+ """Find neurons in local storage."""
165
+ return await self._local.find_neurons(**kwargs)
166
+
167
+ async def update_neuron(self, neuron):
168
+ """Update neuron locally, optionally sync."""
169
+ await self._local.update_neuron(neuron)
170
+ if self._auto_sync:
171
+ try:
172
+ await self._ensure_connected()
173
+ await self._remote.update_neuron(neuron)
174
+ except Exception:
175
+ pass
176
+
177
+ async def delete_neuron(self, neuron_id):
178
+ """Delete neuron locally, optionally sync."""
179
+ result = await self._local.delete_neuron(neuron_id)
180
+ if self._auto_sync:
181
+ try:
182
+ await self._ensure_connected()
183
+ await self._remote.delete_neuron(neuron_id)
184
+ except Exception:
185
+ pass
186
+ return result
187
+
188
+ async def get_neuron_state(self, neuron_id):
189
+ return await self._local.get_neuron_state(neuron_id)
190
+
191
+ async def update_neuron_state(self, state):
192
+ await self._local.update_neuron_state(state)
193
+
194
+ async def add_synapse(self, synapse):
195
+ result = await self._local.add_synapse(synapse)
196
+ if self._auto_sync:
197
+ try:
198
+ await self._ensure_connected()
199
+ await self._remote.add_synapse(synapse)
200
+ except Exception:
201
+ pass
202
+ return result
203
+
204
+ async def get_synapse(self, synapse_id):
205
+ return await self._local.get_synapse(synapse_id)
206
+
207
+ async def get_synapses(self, **kwargs):
208
+ return await self._local.get_synapses(**kwargs)
209
+
210
+ async def update_synapse(self, synapse):
211
+ await self._local.update_synapse(synapse)
212
+ if self._auto_sync:
213
+ try:
214
+ await self._ensure_connected()
215
+ await self._remote.update_synapse(synapse)
216
+ except Exception:
217
+ pass
218
+
219
+ async def delete_synapse(self, synapse_id):
220
+ result = await self._local.delete_synapse(synapse_id)
221
+ if self._auto_sync:
222
+ try:
223
+ await self._ensure_connected()
224
+ await self._remote.delete_synapse(synapse_id)
225
+ except Exception:
226
+ pass
227
+ return result
228
+
229
+ async def get_neighbors(self, neuron_id, **kwargs):
230
+ return await self._local.get_neighbors(neuron_id, **kwargs)
231
+
232
+ async def get_path(self, source_id, target_id, max_hops=4):
233
+ return await self._local.get_path(source_id, target_id, max_hops)
234
+
235
+ async def add_fiber(self, fiber):
236
+ result = await self._local.add_fiber(fiber)
237
+ if self._auto_sync:
238
+ try:
239
+ await self._ensure_connected()
240
+ await self._remote.add_fiber(fiber)
241
+ except Exception:
242
+ pass
243
+ return result
244
+
245
+ async def get_fiber(self, fiber_id):
246
+ return await self._local.get_fiber(fiber_id)
247
+
248
+ async def find_fibers(self, **kwargs):
249
+ return await self._local.find_fibers(**kwargs)
250
+
251
+ async def update_fiber(self, fiber):
252
+ await self._local.update_fiber(fiber)
253
+ if self._auto_sync:
254
+ try:
255
+ await self._ensure_connected()
256
+ await self._remote.update_fiber(fiber)
257
+ except Exception:
258
+ pass
259
+
260
+ async def delete_fiber(self, fiber_id):
261
+ result = await self._local.delete_fiber(fiber_id)
262
+ if self._auto_sync:
263
+ try:
264
+ await self._ensure_connected()
265
+ await self._remote.delete_fiber(fiber_id)
266
+ except Exception:
267
+ pass
268
+ return result
269
+
270
+ async def get_fibers(self, **kwargs):
271
+ return await self._local.get_fibers(**kwargs)
272
+
273
+ async def save_brain(self, brain):
274
+ await self._local.save_brain(brain)
275
+
276
+ async def get_brain(self, brain_id):
277
+ return await self._local.get_brain(brain_id)
278
+
279
+ async def export_brain(self, brain_id):
280
+ return await self._local.export_brain(brain_id)
281
+
282
+ async def import_brain(self, snapshot, target_brain_id=None):
283
+ return await self._local.import_brain(snapshot, target_brain_id)
284
+
285
+ async def get_stats(self, brain_id):
286
+ return await self._local.get_stats(brain_id)
287
+
288
+ async def clear(self, brain_id):
289
+ await self._local.clear(brain_id)
290
+
291
+ # Sync operations
292
+
293
+ async def sync(self) -> dict:
294
+ """
295
+ Manually trigger a full sync with remote server.
296
+
297
+ Returns:
298
+ Sync statistics
299
+ """
300
+ await self._ensure_connected()
301
+
302
+ # Export local brain
303
+ if not self._brain_id:
304
+ raise ValueError("No brain set")
305
+
306
+ local_snapshot = await self._local.export_brain(self._brain_id)
307
+
308
+ # Get remote snapshot
309
+ try:
310
+ _remote_snapshot = await self._remote.export_brain(self._brain_id)
311
+ except Exception:
312
+ # Brain doesn't exist on remote, push our version
313
+ await self._remote.import_brain(local_snapshot, self._brain_id)
314
+ return {"pushed": True, "pulled": False}
315
+
316
+ # TODO: Implement proper merge logic using _remote_snapshot
317
+ # For now, just push local to remote
318
+ await self._remote.import_brain(local_snapshot, self._brain_id)
319
+
320
+ return {"pushed": True, "pulled": False}
321
+
322
+ async def _ensure_connected(self) -> None:
323
+ """Ensure remote storage is connected."""
324
+ if not self._remote.is_connected:
325
+ await self._remote.connect()
326
+
327
+ async def close(self) -> None:
328
+ """Close all connections."""
329
+ await self._remote.disconnect()