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.
- neural_memory/__init__.py +38 -0
- neural_memory/cli/__init__.py +15 -0
- neural_memory/cli/__main__.py +6 -0
- neural_memory/cli/config.py +176 -0
- neural_memory/cli/main.py +2702 -0
- neural_memory/cli/storage.py +169 -0
- neural_memory/cli/tui.py +471 -0
- neural_memory/core/__init__.py +52 -0
- neural_memory/core/brain.py +301 -0
- neural_memory/core/brain_mode.py +273 -0
- neural_memory/core/fiber.py +236 -0
- neural_memory/core/memory_types.py +331 -0
- neural_memory/core/neuron.py +168 -0
- neural_memory/core/project.py +257 -0
- neural_memory/core/synapse.py +215 -0
- neural_memory/engine/__init__.py +15 -0
- neural_memory/engine/activation.py +335 -0
- neural_memory/engine/encoder.py +391 -0
- neural_memory/engine/retrieval.py +440 -0
- neural_memory/extraction/__init__.py +42 -0
- neural_memory/extraction/entities.py +547 -0
- neural_memory/extraction/parser.py +337 -0
- neural_memory/extraction/router.py +396 -0
- neural_memory/extraction/temporal.py +428 -0
- neural_memory/mcp/__init__.py +9 -0
- neural_memory/mcp/__main__.py +6 -0
- neural_memory/mcp/server.py +621 -0
- neural_memory/py.typed +0 -0
- neural_memory/safety/__init__.py +31 -0
- neural_memory/safety/freshness.py +238 -0
- neural_memory/safety/sensitive.py +304 -0
- neural_memory/server/__init__.py +5 -0
- neural_memory/server/app.py +99 -0
- neural_memory/server/dependencies.py +33 -0
- neural_memory/server/models.py +138 -0
- neural_memory/server/routes/__init__.py +7 -0
- neural_memory/server/routes/brain.py +221 -0
- neural_memory/server/routes/memory.py +169 -0
- neural_memory/server/routes/sync.py +387 -0
- neural_memory/storage/__init__.py +17 -0
- neural_memory/storage/base.py +441 -0
- neural_memory/storage/factory.py +329 -0
- neural_memory/storage/memory_store.py +896 -0
- neural_memory/storage/shared_store.py +650 -0
- neural_memory/storage/sqlite_store.py +1613 -0
- neural_memory/sync/__init__.py +5 -0
- neural_memory/sync/client.py +435 -0
- neural_memory/unified_config.py +315 -0
- neural_memory/utils/__init__.py +5 -0
- neural_memory/utils/config.py +98 -0
- neural_memory-0.1.0.dist-info/METADATA +314 -0
- neural_memory-0.1.0.dist-info/RECORD +55 -0
- neural_memory-0.1.0.dist-info/WHEEL +4 -0
- neural_memory-0.1.0.dist-info/entry_points.txt +4 -0
- 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)
|