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,301 @@
1
+ """Brain container - the top-level memory structure."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from typing import Any
8
+ from uuid import uuid4
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class BrainConfig:
13
+ """
14
+ Configuration for brain behavior.
15
+
16
+ Attributes:
17
+ decay_rate: Rate at which neuron activation decays (per day)
18
+ reinforcement_delta: Amount to increase synapse weight on access
19
+ activation_threshold: Minimum activation level to consider active
20
+ max_spread_hops: Maximum hops in spreading activation
21
+ max_context_tokens: Maximum tokens to include in context injection
22
+ default_synapse_weight: Default weight for new synapses
23
+ """
24
+
25
+ decay_rate: float = 0.1
26
+ reinforcement_delta: float = 0.05
27
+ activation_threshold: float = 0.2
28
+ max_spread_hops: int = 4
29
+ max_context_tokens: int = 1500
30
+ default_synapse_weight: float = 0.5
31
+
32
+ def with_updates(self, **kwargs: Any) -> BrainConfig:
33
+ """Create a new config with updated values."""
34
+ return BrainConfig(
35
+ decay_rate=kwargs.get("decay_rate", self.decay_rate),
36
+ reinforcement_delta=kwargs.get("reinforcement_delta", self.reinforcement_delta),
37
+ activation_threshold=kwargs.get("activation_threshold", self.activation_threshold),
38
+ max_spread_hops=kwargs.get("max_spread_hops", self.max_spread_hops),
39
+ max_context_tokens=kwargs.get("max_context_tokens", self.max_context_tokens),
40
+ default_synapse_weight=kwargs.get(
41
+ "default_synapse_weight", self.default_synapse_weight
42
+ ),
43
+ )
44
+
45
+
46
+ @dataclass
47
+ class Brain:
48
+ """
49
+ A Brain is the top-level container for a memory system.
50
+
51
+ It holds configuration, ownership, and statistics for a
52
+ collection of neurons, synapses, and fibers.
53
+
54
+ Attributes:
55
+ id: Unique identifier
56
+ name: Human-readable name
57
+ config: Brain configuration settings
58
+ owner_id: Optional owner identifier
59
+ is_public: Whether this brain can be read by anyone
60
+ shared_with: List of user IDs with access
61
+ neuron_count: Number of neurons (computed)
62
+ synapse_count: Number of synapses (computed)
63
+ fiber_count: Number of fibers (computed)
64
+ metadata: Additional brain-specific data
65
+ created_at: When this brain was created
66
+ updated_at: When this brain was last modified
67
+ """
68
+
69
+ id: str
70
+ name: str
71
+ config: BrainConfig = field(default_factory=BrainConfig)
72
+ owner_id: str | None = None
73
+ is_public: bool = False
74
+ shared_with: list[str] = field(default_factory=list)
75
+ neuron_count: int = 0
76
+ synapse_count: int = 0
77
+ fiber_count: int = 0
78
+ metadata: dict[str, Any] = field(default_factory=dict)
79
+ created_at: datetime = field(default_factory=datetime.utcnow)
80
+ updated_at: datetime = field(default_factory=datetime.utcnow)
81
+
82
+ @classmethod
83
+ def create(
84
+ cls,
85
+ name: str,
86
+ config: BrainConfig | None = None,
87
+ owner_id: str | None = None,
88
+ is_public: bool = False,
89
+ brain_id: str | None = None,
90
+ metadata: dict[str, Any] | None = None,
91
+ ) -> Brain:
92
+ """
93
+ Factory method to create a new Brain.
94
+
95
+ Args:
96
+ name: Human-readable name
97
+ config: Optional configuration (uses defaults if None)
98
+ owner_id: Optional owner identifier
99
+ is_public: Whether publicly accessible
100
+ brain_id: Optional explicit ID
101
+ metadata: Optional metadata
102
+
103
+ Returns:
104
+ A new Brain instance
105
+ """
106
+ return cls(
107
+ id=brain_id or str(uuid4()),
108
+ name=name,
109
+ config=config or BrainConfig(),
110
+ owner_id=owner_id,
111
+ is_public=is_public,
112
+ metadata=metadata or {},
113
+ created_at=datetime.utcnow(),
114
+ updated_at=datetime.utcnow(),
115
+ )
116
+
117
+ def share_with(self, user_id: str) -> Brain:
118
+ """
119
+ Create a new Brain shared with an additional user.
120
+
121
+ Args:
122
+ user_id: User ID to share with
123
+
124
+ Returns:
125
+ New Brain with updated shared_with list
126
+ """
127
+ if user_id in self.shared_with:
128
+ return self
129
+
130
+ return Brain(
131
+ id=self.id,
132
+ name=self.name,
133
+ config=self.config,
134
+ owner_id=self.owner_id,
135
+ is_public=self.is_public,
136
+ shared_with=[*self.shared_with, user_id],
137
+ neuron_count=self.neuron_count,
138
+ synapse_count=self.synapse_count,
139
+ fiber_count=self.fiber_count,
140
+ metadata=self.metadata,
141
+ created_at=self.created_at,
142
+ updated_at=datetime.utcnow(),
143
+ )
144
+
145
+ def unshare_with(self, user_id: str) -> Brain:
146
+ """
147
+ Create a new Brain with a user removed from sharing.
148
+
149
+ Args:
150
+ user_id: User ID to remove
151
+
152
+ Returns:
153
+ New Brain with updated shared_with list
154
+ """
155
+ return Brain(
156
+ id=self.id,
157
+ name=self.name,
158
+ config=self.config,
159
+ owner_id=self.owner_id,
160
+ is_public=self.is_public,
161
+ shared_with=[uid for uid in self.shared_with if uid != user_id],
162
+ neuron_count=self.neuron_count,
163
+ synapse_count=self.synapse_count,
164
+ fiber_count=self.fiber_count,
165
+ metadata=self.metadata,
166
+ created_at=self.created_at,
167
+ updated_at=datetime.utcnow(),
168
+ )
169
+
170
+ def make_public(self) -> Brain:
171
+ """Create a new Brain that is publicly accessible."""
172
+ return Brain(
173
+ id=self.id,
174
+ name=self.name,
175
+ config=self.config,
176
+ owner_id=self.owner_id,
177
+ is_public=True,
178
+ shared_with=self.shared_with,
179
+ neuron_count=self.neuron_count,
180
+ synapse_count=self.synapse_count,
181
+ fiber_count=self.fiber_count,
182
+ metadata=self.metadata,
183
+ created_at=self.created_at,
184
+ updated_at=datetime.utcnow(),
185
+ )
186
+
187
+ def make_private(self) -> Brain:
188
+ """Create a new Brain that is private."""
189
+ return Brain(
190
+ id=self.id,
191
+ name=self.name,
192
+ config=self.config,
193
+ owner_id=self.owner_id,
194
+ is_public=False,
195
+ shared_with=self.shared_with,
196
+ neuron_count=self.neuron_count,
197
+ synapse_count=self.synapse_count,
198
+ fiber_count=self.fiber_count,
199
+ metadata=self.metadata,
200
+ created_at=self.created_at,
201
+ updated_at=datetime.utcnow(),
202
+ )
203
+
204
+ def with_config(self, config: BrainConfig) -> Brain:
205
+ """Create a new Brain with updated configuration."""
206
+ return Brain(
207
+ id=self.id,
208
+ name=self.name,
209
+ config=config,
210
+ owner_id=self.owner_id,
211
+ is_public=self.is_public,
212
+ shared_with=self.shared_with,
213
+ neuron_count=self.neuron_count,
214
+ synapse_count=self.synapse_count,
215
+ fiber_count=self.fiber_count,
216
+ metadata=self.metadata,
217
+ created_at=self.created_at,
218
+ updated_at=datetime.utcnow(),
219
+ )
220
+
221
+ def with_stats(
222
+ self,
223
+ neuron_count: int | None = None,
224
+ synapse_count: int | None = None,
225
+ fiber_count: int | None = None,
226
+ ) -> Brain:
227
+ """Create a new Brain with updated statistics."""
228
+ return Brain(
229
+ id=self.id,
230
+ name=self.name,
231
+ config=self.config,
232
+ owner_id=self.owner_id,
233
+ is_public=self.is_public,
234
+ shared_with=self.shared_with,
235
+ neuron_count=neuron_count if neuron_count is not None else self.neuron_count,
236
+ synapse_count=synapse_count if synapse_count is not None else self.synapse_count,
237
+ fiber_count=fiber_count if fiber_count is not None else self.fiber_count,
238
+ metadata=self.metadata,
239
+ created_at=self.created_at,
240
+ updated_at=datetime.utcnow(),
241
+ )
242
+
243
+ def can_access(self, user_id: str | None) -> bool:
244
+ """
245
+ Check if a user can access this brain.
246
+
247
+ Args:
248
+ user_id: User ID to check (None for anonymous)
249
+
250
+ Returns:
251
+ True if user has access
252
+ """
253
+ if self.is_public:
254
+ return True
255
+ if user_id is None:
256
+ return False
257
+ if self.owner_id == user_id:
258
+ return True
259
+ return user_id in self.shared_with
260
+
261
+ def can_write(self, user_id: str | None) -> bool:
262
+ """
263
+ Check if a user can write to this brain.
264
+
265
+ Args:
266
+ user_id: User ID to check (None for anonymous)
267
+
268
+ Returns:
269
+ True if user has write access
270
+ """
271
+ if user_id is None:
272
+ return False
273
+ return self.owner_id == user_id
274
+
275
+
276
+ @dataclass
277
+ class BrainSnapshot:
278
+ """
279
+ A serializable snapshot of a brain for export/import.
280
+
281
+ Attributes:
282
+ brain_id: ID of the original brain
283
+ brain_name: Name of the brain
284
+ exported_at: When this snapshot was created
285
+ version: Schema version for compatibility
286
+ neurons: List of serialized neurons
287
+ synapses: List of serialized synapses
288
+ fibers: List of serialized fibers
289
+ config: Brain configuration
290
+ metadata: Additional export metadata
291
+ """
292
+
293
+ brain_id: str
294
+ brain_name: str
295
+ exported_at: datetime
296
+ version: str
297
+ neurons: list[dict[str, Any]]
298
+ synapses: list[dict[str, Any]]
299
+ fibers: list[dict[str, Any]]
300
+ config: dict[str, Any]
301
+ metadata: dict[str, Any] = field(default_factory=dict)
@@ -0,0 +1,273 @@
1
+ """Brain mode configuration for local/shared storage toggle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import StrEnum
7
+ from typing import Any
8
+
9
+
10
+ class BrainMode(StrEnum):
11
+ """Brain storage mode."""
12
+
13
+ LOCAL = "local"
14
+ """Store memories locally only (SQLite or in-memory)."""
15
+
16
+ SHARED = "shared"
17
+ """Connect to remote NeuralMemory server for real-time sharing."""
18
+
19
+ HYBRID = "hybrid"
20
+ """Write locally, sync to server periodically (offline-first)."""
21
+
22
+
23
+ class SyncStrategy(StrEnum):
24
+ """Synchronization strategy for hybrid mode."""
25
+
26
+ PUSH_ONLY = "push_only"
27
+ """Only push local changes to server."""
28
+
29
+ PULL_ONLY = "pull_only"
30
+ """Only pull changes from server."""
31
+
32
+ BIDIRECTIONAL = "bidirectional"
33
+ """Full two-way sync."""
34
+
35
+ ON_DEMAND = "on_demand"
36
+ """Manual sync only."""
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class SharedConfig:
41
+ """Configuration for shared storage mode."""
42
+
43
+ server_url: str
44
+ """URL of NeuralMemory server (e.g., http://localhost:8000)."""
45
+
46
+ api_key: str | None = None
47
+ """Optional API key for authentication."""
48
+
49
+ timeout: float = 30.0
50
+ """Request timeout in seconds."""
51
+
52
+ retry_count: int = 3
53
+ """Number of retries on connection failure."""
54
+
55
+ retry_delay: float = 1.0
56
+ """Delay between retries in seconds."""
57
+
58
+ def with_server_url(self, url: str) -> SharedConfig:
59
+ """Create new config with different server URL."""
60
+ return SharedConfig(
61
+ server_url=url,
62
+ api_key=self.api_key,
63
+ timeout=self.timeout,
64
+ retry_count=self.retry_count,
65
+ retry_delay=self.retry_delay,
66
+ )
67
+
68
+ def with_api_key(self, key: str | None) -> SharedConfig:
69
+ """Create new config with different API key."""
70
+ return SharedConfig(
71
+ server_url=self.server_url,
72
+ api_key=key,
73
+ timeout=self.timeout,
74
+ retry_count=self.retry_count,
75
+ retry_delay=self.retry_delay,
76
+ )
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class HybridConfig:
81
+ """Configuration for hybrid mode (offline-first with sync)."""
82
+
83
+ local_path: str
84
+ """Path to local SQLite database."""
85
+
86
+ server_url: str
87
+ """URL of NeuralMemory server for sync."""
88
+
89
+ api_key: str | None = None
90
+ """Optional API key for authentication."""
91
+
92
+ sync_strategy: SyncStrategy = SyncStrategy.BIDIRECTIONAL
93
+ """How to synchronize with server."""
94
+
95
+ sync_interval_seconds: int = 60
96
+ """How often to sync (0 = only manual)."""
97
+
98
+ auto_sync_on_encode: bool = True
99
+ """Automatically push new memories to server."""
100
+
101
+ auto_sync_on_query: bool = False
102
+ """Pull from server before queries."""
103
+
104
+ conflict_resolution: str = "prefer_local"
105
+ """How to resolve conflicts: 'prefer_local', 'prefer_remote', 'prefer_recent'."""
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class BrainModeConfig:
110
+ """
111
+ Configuration for brain storage mode.
112
+
113
+ Determines how memories are stored and shared.
114
+
115
+ Examples:
116
+ # Local-only mode (default)
117
+ config = BrainModeConfig(mode=BrainMode.LOCAL)
118
+
119
+ # Shared mode - connect to server
120
+ config = BrainModeConfig(
121
+ mode=BrainMode.SHARED,
122
+ shared=SharedConfig(server_url="http://localhost:8000"),
123
+ )
124
+
125
+ # Hybrid mode - offline-first with sync
126
+ config = BrainModeConfig(
127
+ mode=BrainMode.HYBRID,
128
+ hybrid=HybridConfig(
129
+ local_path="./brain.db",
130
+ server_url="http://localhost:8000",
131
+ sync_interval_seconds=300,
132
+ ),
133
+ )
134
+ """
135
+
136
+ mode: BrainMode = BrainMode.LOCAL
137
+ """Current storage mode."""
138
+
139
+ shared: SharedConfig | None = None
140
+ """Configuration for shared mode."""
141
+
142
+ hybrid: HybridConfig | None = None
143
+ """Configuration for hybrid mode."""
144
+
145
+ @classmethod
146
+ def local(cls) -> BrainModeConfig:
147
+ """Create local-only configuration."""
148
+ return cls(mode=BrainMode.LOCAL)
149
+
150
+ @classmethod
151
+ def shared_mode(
152
+ cls,
153
+ server_url: str,
154
+ api_key: str | None = None,
155
+ timeout: float = 30.0,
156
+ ) -> BrainModeConfig:
157
+ """
158
+ Create shared configuration.
159
+
160
+ Args:
161
+ server_url: URL of NeuralMemory server
162
+ api_key: Optional API key
163
+ timeout: Request timeout in seconds
164
+ """
165
+ return cls(
166
+ mode=BrainMode.SHARED,
167
+ shared=SharedConfig(
168
+ server_url=server_url,
169
+ api_key=api_key,
170
+ timeout=timeout,
171
+ ),
172
+ )
173
+
174
+ @classmethod
175
+ def hybrid_mode(
176
+ cls,
177
+ local_path: str,
178
+ server_url: str,
179
+ api_key: str | None = None,
180
+ sync_interval: int = 60,
181
+ strategy: SyncStrategy = SyncStrategy.BIDIRECTIONAL,
182
+ ) -> BrainModeConfig:
183
+ """
184
+ Create hybrid (offline-first) configuration.
185
+
186
+ Args:
187
+ local_path: Path to local SQLite database
188
+ server_url: URL of NeuralMemory server
189
+ api_key: Optional API key
190
+ sync_interval: Sync interval in seconds (0 = manual only)
191
+ strategy: Synchronization strategy
192
+ """
193
+ return cls(
194
+ mode=BrainMode.HYBRID,
195
+ hybrid=HybridConfig(
196
+ local_path=local_path,
197
+ server_url=server_url,
198
+ api_key=api_key,
199
+ sync_strategy=strategy,
200
+ sync_interval_seconds=sync_interval,
201
+ ),
202
+ )
203
+
204
+ def is_local(self) -> bool:
205
+ """Check if using local-only mode."""
206
+ return self.mode == BrainMode.LOCAL
207
+
208
+ def is_shared(self) -> bool:
209
+ """Check if using shared (remote) mode."""
210
+ return self.mode == BrainMode.SHARED
211
+
212
+ def is_hybrid(self) -> bool:
213
+ """Check if using hybrid (offline-first) mode."""
214
+ return self.mode == BrainMode.HYBRID
215
+
216
+ def to_dict(self) -> dict[str, Any]:
217
+ """Convert to dictionary for serialization."""
218
+ result: dict[str, Any] = {"mode": self.mode.value}
219
+
220
+ if self.shared:
221
+ result["shared"] = {
222
+ "server_url": self.shared.server_url,
223
+ "api_key": self.shared.api_key,
224
+ "timeout": self.shared.timeout,
225
+ "retry_count": self.shared.retry_count,
226
+ "retry_delay": self.shared.retry_delay,
227
+ }
228
+
229
+ if self.hybrid:
230
+ result["hybrid"] = {
231
+ "local_path": self.hybrid.local_path,
232
+ "server_url": self.hybrid.server_url,
233
+ "api_key": self.hybrid.api_key,
234
+ "sync_strategy": self.hybrid.sync_strategy.value,
235
+ "sync_interval_seconds": self.hybrid.sync_interval_seconds,
236
+ "auto_sync_on_encode": self.hybrid.auto_sync_on_encode,
237
+ "auto_sync_on_query": self.hybrid.auto_sync_on_query,
238
+ "conflict_resolution": self.hybrid.conflict_resolution,
239
+ }
240
+
241
+ return result
242
+
243
+ @classmethod
244
+ def from_dict(cls, data: dict[str, Any]) -> BrainModeConfig:
245
+ """Create from dictionary."""
246
+ mode = BrainMode(data.get("mode", "local"))
247
+
248
+ shared = None
249
+ if "shared" in data:
250
+ s = data["shared"]
251
+ shared = SharedConfig(
252
+ server_url=s["server_url"],
253
+ api_key=s.get("api_key"),
254
+ timeout=s.get("timeout", 30.0),
255
+ retry_count=s.get("retry_count", 3),
256
+ retry_delay=s.get("retry_delay", 1.0),
257
+ )
258
+
259
+ hybrid = None
260
+ if "hybrid" in data:
261
+ h = data["hybrid"]
262
+ hybrid = HybridConfig(
263
+ local_path=h["local_path"],
264
+ server_url=h["server_url"],
265
+ api_key=h.get("api_key"),
266
+ sync_strategy=SyncStrategy(h.get("sync_strategy", "bidirectional")),
267
+ sync_interval_seconds=h.get("sync_interval_seconds", 60),
268
+ auto_sync_on_encode=h.get("auto_sync_on_encode", True),
269
+ auto_sync_on_query=h.get("auto_sync_on_query", False),
270
+ conflict_resolution=h.get("conflict_resolution", "prefer_local"),
271
+ )
272
+
273
+ return cls(mode=mode, shared=shared, hybrid=hybrid)