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,236 @@
|
|
|
1
|
+
"""Fiber data structures - memory clusters of related neurons."""
|
|
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
|
|
12
|
+
class Fiber:
|
|
13
|
+
"""
|
|
14
|
+
A fiber represents a memory cluster - a subgraph of related neurons.
|
|
15
|
+
|
|
16
|
+
Fibers bundle together neurons and synapses that form a coherent
|
|
17
|
+
memory or concept. They act as retrieval units and can be
|
|
18
|
+
compressed into summaries over time.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
id: Unique identifier
|
|
22
|
+
neuron_ids: Set of neuron IDs in this fiber
|
|
23
|
+
synapse_ids: Set of synapse IDs connecting neurons in this fiber
|
|
24
|
+
anchor_neuron_id: Primary entry point neuron for this fiber
|
|
25
|
+
time_start: Earliest timestamp in this memory
|
|
26
|
+
time_end: Latest timestamp in this memory
|
|
27
|
+
coherence: How tightly connected the neurons are (0.0 - 1.0)
|
|
28
|
+
salience: Importance/relevance score (0.0 - 1.0)
|
|
29
|
+
frequency: Number of times this fiber has been accessed
|
|
30
|
+
summary: Optional compressed text summary
|
|
31
|
+
tags: Optional tags for categorization
|
|
32
|
+
metadata: Additional fiber-specific data
|
|
33
|
+
created_at: When this fiber was created
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
id: str
|
|
37
|
+
neuron_ids: set[str]
|
|
38
|
+
synapse_ids: set[str]
|
|
39
|
+
anchor_neuron_id: str
|
|
40
|
+
time_start: datetime | None = None
|
|
41
|
+
time_end: datetime | None = None
|
|
42
|
+
coherence: float = 0.0
|
|
43
|
+
salience: float = 0.0
|
|
44
|
+
frequency: int = 0
|
|
45
|
+
summary: str | None = None
|
|
46
|
+
tags: set[str] = field(default_factory=set)
|
|
47
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def create(
|
|
52
|
+
cls,
|
|
53
|
+
neuron_ids: set[str],
|
|
54
|
+
synapse_ids: set[str],
|
|
55
|
+
anchor_neuron_id: str,
|
|
56
|
+
time_start: datetime | None = None,
|
|
57
|
+
time_end: datetime | None = None,
|
|
58
|
+
summary: str | None = None,
|
|
59
|
+
tags: set[str] | None = None,
|
|
60
|
+
metadata: dict[str, Any] | None = None,
|
|
61
|
+
fiber_id: str | None = None,
|
|
62
|
+
) -> Fiber:
|
|
63
|
+
"""
|
|
64
|
+
Factory method to create a new Fiber.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
neuron_ids: Set of neuron IDs
|
|
68
|
+
synapse_ids: Set of synapse IDs
|
|
69
|
+
anchor_neuron_id: Primary entry point
|
|
70
|
+
time_start: Optional start time
|
|
71
|
+
time_end: Optional end time
|
|
72
|
+
summary: Optional text summary
|
|
73
|
+
tags: Optional tags
|
|
74
|
+
metadata: Optional metadata
|
|
75
|
+
fiber_id: Optional explicit ID
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
A new Fiber instance
|
|
79
|
+
"""
|
|
80
|
+
if anchor_neuron_id not in neuron_ids:
|
|
81
|
+
raise ValueError(f"Anchor neuron {anchor_neuron_id} must be in neuron_ids")
|
|
82
|
+
|
|
83
|
+
return cls(
|
|
84
|
+
id=fiber_id or str(uuid4()),
|
|
85
|
+
neuron_ids=neuron_ids,
|
|
86
|
+
synapse_ids=synapse_ids,
|
|
87
|
+
anchor_neuron_id=anchor_neuron_id,
|
|
88
|
+
time_start=time_start,
|
|
89
|
+
time_end=time_end,
|
|
90
|
+
summary=summary,
|
|
91
|
+
tags=tags or set(),
|
|
92
|
+
metadata=metadata or {},
|
|
93
|
+
created_at=datetime.utcnow(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def access(self) -> Fiber:
|
|
97
|
+
"""
|
|
98
|
+
Create a new Fiber with incremented access frequency.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
New Fiber with frequency + 1
|
|
102
|
+
"""
|
|
103
|
+
return Fiber(
|
|
104
|
+
id=self.id,
|
|
105
|
+
neuron_ids=self.neuron_ids,
|
|
106
|
+
synapse_ids=self.synapse_ids,
|
|
107
|
+
anchor_neuron_id=self.anchor_neuron_id,
|
|
108
|
+
time_start=self.time_start,
|
|
109
|
+
time_end=self.time_end,
|
|
110
|
+
coherence=self.coherence,
|
|
111
|
+
salience=self.salience,
|
|
112
|
+
frequency=self.frequency + 1,
|
|
113
|
+
summary=self.summary,
|
|
114
|
+
tags=self.tags,
|
|
115
|
+
metadata=self.metadata,
|
|
116
|
+
created_at=self.created_at,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def with_salience(self, salience: float) -> Fiber:
|
|
120
|
+
"""
|
|
121
|
+
Create a new Fiber with updated salience.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
salience: New salience value (clamped to 0.0-1.0)
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
New Fiber with updated salience
|
|
128
|
+
"""
|
|
129
|
+
return Fiber(
|
|
130
|
+
id=self.id,
|
|
131
|
+
neuron_ids=self.neuron_ids,
|
|
132
|
+
synapse_ids=self.synapse_ids,
|
|
133
|
+
anchor_neuron_id=self.anchor_neuron_id,
|
|
134
|
+
time_start=self.time_start,
|
|
135
|
+
time_end=self.time_end,
|
|
136
|
+
coherence=self.coherence,
|
|
137
|
+
salience=max(0.0, min(1.0, salience)),
|
|
138
|
+
frequency=self.frequency,
|
|
139
|
+
summary=self.summary,
|
|
140
|
+
tags=self.tags,
|
|
141
|
+
metadata=self.metadata,
|
|
142
|
+
created_at=self.created_at,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def with_summary(self, summary: str) -> Fiber:
|
|
146
|
+
"""
|
|
147
|
+
Create a new Fiber with a summary.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
summary: The summary text
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
New Fiber with summary
|
|
154
|
+
"""
|
|
155
|
+
return Fiber(
|
|
156
|
+
id=self.id,
|
|
157
|
+
neuron_ids=self.neuron_ids,
|
|
158
|
+
synapse_ids=self.synapse_ids,
|
|
159
|
+
anchor_neuron_id=self.anchor_neuron_id,
|
|
160
|
+
time_start=self.time_start,
|
|
161
|
+
time_end=self.time_end,
|
|
162
|
+
coherence=self.coherence,
|
|
163
|
+
salience=self.salience,
|
|
164
|
+
frequency=self.frequency,
|
|
165
|
+
summary=summary,
|
|
166
|
+
tags=self.tags,
|
|
167
|
+
metadata=self.metadata,
|
|
168
|
+
created_at=self.created_at,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def add_tags(self, *new_tags: str) -> Fiber:
|
|
172
|
+
"""
|
|
173
|
+
Create a new Fiber with additional tags.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
*new_tags: Tags to add
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
New Fiber with merged tags
|
|
180
|
+
"""
|
|
181
|
+
return Fiber(
|
|
182
|
+
id=self.id,
|
|
183
|
+
neuron_ids=self.neuron_ids,
|
|
184
|
+
synapse_ids=self.synapse_ids,
|
|
185
|
+
anchor_neuron_id=self.anchor_neuron_id,
|
|
186
|
+
time_start=self.time_start,
|
|
187
|
+
time_end=self.time_end,
|
|
188
|
+
coherence=self.coherence,
|
|
189
|
+
salience=self.salience,
|
|
190
|
+
frequency=self.frequency,
|
|
191
|
+
summary=self.summary,
|
|
192
|
+
tags=self.tags | set(new_tags),
|
|
193
|
+
metadata=self.metadata,
|
|
194
|
+
created_at=self.created_at,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def neuron_count(self) -> int:
|
|
199
|
+
"""Number of neurons in this fiber."""
|
|
200
|
+
return len(self.neuron_ids)
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def synapse_count(self) -> int:
|
|
204
|
+
"""Number of synapses in this fiber."""
|
|
205
|
+
return len(self.synapse_ids)
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def time_span(self) -> float | None:
|
|
209
|
+
"""
|
|
210
|
+
Duration of this memory in seconds.
|
|
211
|
+
|
|
212
|
+
Returns None if time bounds are not set.
|
|
213
|
+
"""
|
|
214
|
+
if self.time_start and self.time_end:
|
|
215
|
+
return (self.time_end - self.time_start).total_seconds()
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def contains_neuron(self, neuron_id: str) -> bool:
|
|
219
|
+
"""Check if this fiber contains a specific neuron."""
|
|
220
|
+
return neuron_id in self.neuron_ids
|
|
221
|
+
|
|
222
|
+
def overlaps_time(self, start: datetime, end: datetime) -> bool:
|
|
223
|
+
"""
|
|
224
|
+
Check if this fiber's time range overlaps with given range.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
start: Query start time
|
|
228
|
+
end: Query end time
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
True if there is any overlap
|
|
232
|
+
"""
|
|
233
|
+
if self.time_start is None or self.time_end is None:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
return self.time_start <= end and self.time_end >= start
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Memory types and classification for Neural Memory.
|
|
2
|
+
|
|
3
|
+
Integrates MemoCore concepts: typed memories with priority, expiry, and provenance.
|
|
4
|
+
This enables smarter query routing and memory lifecycle management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
from enum import IntEnum, StrEnum
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MemoryType(StrEnum):
|
|
16
|
+
"""Types of memories based on their nature and purpose."""
|
|
17
|
+
|
|
18
|
+
# Core types from MemoCore
|
|
19
|
+
FACT = "fact" # Objective information: "Python 3.11 was released in Oct 2022"
|
|
20
|
+
DECISION = "decision" # Choices made: "We decided to use PostgreSQL"
|
|
21
|
+
PREFERENCE = "preference" # User preferences: "I prefer tabs over spaces"
|
|
22
|
+
TODO = "todo" # Action items: "Need to refactor auth module"
|
|
23
|
+
INSIGHT = "insight" # Learned patterns: "This codebase uses dependency injection"
|
|
24
|
+
CONTEXT = "context" # Situational info: "Working on project X for client Y"
|
|
25
|
+
INSTRUCTION = "instruction" # User guidelines: "Always use type hints"
|
|
26
|
+
|
|
27
|
+
# Additional useful types
|
|
28
|
+
ERROR = "error" # Error patterns: "This API returns 429 on rate limit"
|
|
29
|
+
WORKFLOW = "workflow" # Process patterns: "Deploy flow: test -> stage -> prod"
|
|
30
|
+
REFERENCE = "reference" # External refs: "Docs at https://..."
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Priority(IntEnum):
|
|
34
|
+
"""Memory priority levels (0-10 scale)."""
|
|
35
|
+
|
|
36
|
+
LOWEST = 0
|
|
37
|
+
LOW = 2
|
|
38
|
+
NORMAL = 5
|
|
39
|
+
HIGH = 7
|
|
40
|
+
CRITICAL = 10
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_int(cls, value: int) -> Priority:
|
|
44
|
+
"""Convert integer to nearest Priority level."""
|
|
45
|
+
if value <= 1:
|
|
46
|
+
return cls.LOWEST
|
|
47
|
+
elif value <= 3:
|
|
48
|
+
return cls.LOW
|
|
49
|
+
elif value <= 6:
|
|
50
|
+
return cls.NORMAL
|
|
51
|
+
elif value <= 8:
|
|
52
|
+
return cls.HIGH
|
|
53
|
+
else:
|
|
54
|
+
return cls.CRITICAL
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Confidence(StrEnum):
|
|
58
|
+
"""Confidence level in the memory's accuracy."""
|
|
59
|
+
|
|
60
|
+
VERIFIED = "verified" # Confirmed by user or external source
|
|
61
|
+
HIGH = "high" # Very likely accurate
|
|
62
|
+
MEDIUM = "medium" # Probably accurate
|
|
63
|
+
LOW = "low" # Uncertain, needs verification
|
|
64
|
+
INFERRED = "inferred" # AI-inferred, may be wrong
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class Provenance:
|
|
69
|
+
"""Tracks the origin and reliability of a memory.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
source: Where this memory came from
|
|
73
|
+
confidence: How reliable this memory is
|
|
74
|
+
verified: Whether explicitly verified by user
|
|
75
|
+
verified_at: When verification happened
|
|
76
|
+
created_by: Who/what created this memory
|
|
77
|
+
last_confirmed: When last confirmed as still valid
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
source: str # "user_input", "ai_inference", "import", "observation"
|
|
81
|
+
confidence: Confidence = Confidence.MEDIUM
|
|
82
|
+
verified: bool = False
|
|
83
|
+
verified_at: datetime | None = None
|
|
84
|
+
created_by: str = "user"
|
|
85
|
+
last_confirmed: datetime | None = None
|
|
86
|
+
|
|
87
|
+
def verify(self) -> Provenance:
|
|
88
|
+
"""Create a new Provenance marked as verified."""
|
|
89
|
+
return Provenance(
|
|
90
|
+
source=self.source,
|
|
91
|
+
confidence=Confidence.VERIFIED,
|
|
92
|
+
verified=True,
|
|
93
|
+
verified_at=datetime.utcnow(),
|
|
94
|
+
created_by=self.created_by,
|
|
95
|
+
last_confirmed=datetime.utcnow(),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def confirm(self) -> Provenance:
|
|
99
|
+
"""Create a new Provenance with updated confirmation time."""
|
|
100
|
+
return Provenance(
|
|
101
|
+
source=self.source,
|
|
102
|
+
confidence=self.confidence,
|
|
103
|
+
verified=self.verified,
|
|
104
|
+
verified_at=self.verified_at,
|
|
105
|
+
created_by=self.created_by,
|
|
106
|
+
last_confirmed=datetime.utcnow(),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True)
|
|
111
|
+
class TypedMemory:
|
|
112
|
+
"""A memory with type classification, priority, and lifecycle metadata.
|
|
113
|
+
|
|
114
|
+
This wraps around fibers to add MemoCore-style memory management.
|
|
115
|
+
|
|
116
|
+
Attributes:
|
|
117
|
+
fiber_id: Reference to the underlying Fiber
|
|
118
|
+
memory_type: Classification of this memory
|
|
119
|
+
priority: Importance level (0-10)
|
|
120
|
+
provenance: Origin and reliability info
|
|
121
|
+
expires_at: Optional expiration timestamp
|
|
122
|
+
project_id: Optional project scope
|
|
123
|
+
tags: Additional categorization tags
|
|
124
|
+
metadata: Extra type-specific data
|
|
125
|
+
created_at: Creation timestamp
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
fiber_id: str
|
|
129
|
+
memory_type: MemoryType
|
|
130
|
+
priority: Priority = Priority.NORMAL
|
|
131
|
+
provenance: Provenance = field(default_factory=lambda: Provenance(source="user_input"))
|
|
132
|
+
expires_at: datetime | None = None
|
|
133
|
+
project_id: str | None = None
|
|
134
|
+
tags: frozenset[str] = field(default_factory=frozenset)
|
|
135
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
136
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def create(
|
|
140
|
+
cls,
|
|
141
|
+
fiber_id: str,
|
|
142
|
+
memory_type: MemoryType,
|
|
143
|
+
priority: Priority | int = Priority.NORMAL,
|
|
144
|
+
source: str = "user_input",
|
|
145
|
+
confidence: Confidence = Confidence.MEDIUM,
|
|
146
|
+
expires_in_days: int | None = None,
|
|
147
|
+
project_id: str | None = None,
|
|
148
|
+
tags: set[str] | None = None,
|
|
149
|
+
metadata: dict[str, Any] | None = None,
|
|
150
|
+
) -> TypedMemory:
|
|
151
|
+
"""Factory method to create a TypedMemory.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
fiber_id: The underlying fiber ID
|
|
155
|
+
memory_type: Type of memory
|
|
156
|
+
priority: Priority level (int or Priority enum)
|
|
157
|
+
source: Source of this memory
|
|
158
|
+
confidence: Confidence level
|
|
159
|
+
expires_in_days: Optional days until expiry
|
|
160
|
+
project_id: Optional project scope
|
|
161
|
+
tags: Optional tags
|
|
162
|
+
metadata: Optional metadata
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
A new TypedMemory instance
|
|
166
|
+
"""
|
|
167
|
+
if isinstance(priority, int):
|
|
168
|
+
priority = Priority.from_int(priority)
|
|
169
|
+
|
|
170
|
+
expires_at = None
|
|
171
|
+
if expires_in_days is not None:
|
|
172
|
+
expires_at = datetime.utcnow() + timedelta(days=expires_in_days)
|
|
173
|
+
|
|
174
|
+
return cls(
|
|
175
|
+
fiber_id=fiber_id,
|
|
176
|
+
memory_type=memory_type,
|
|
177
|
+
priority=priority,
|
|
178
|
+
provenance=Provenance(source=source, confidence=confidence),
|
|
179
|
+
expires_at=expires_at,
|
|
180
|
+
project_id=project_id,
|
|
181
|
+
tags=frozenset(tags) if tags else frozenset(),
|
|
182
|
+
metadata=metadata or {},
|
|
183
|
+
created_at=datetime.utcnow(),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def is_expired(self) -> bool:
|
|
188
|
+
"""Check if this memory has expired."""
|
|
189
|
+
if self.expires_at is None:
|
|
190
|
+
return False
|
|
191
|
+
return datetime.utcnow() > self.expires_at
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def days_until_expiry(self) -> int | None:
|
|
195
|
+
"""Days until this memory expires, or None if no expiry."""
|
|
196
|
+
if self.expires_at is None:
|
|
197
|
+
return None
|
|
198
|
+
delta = self.expires_at - datetime.utcnow()
|
|
199
|
+
return max(0, delta.days)
|
|
200
|
+
|
|
201
|
+
def with_priority(self, priority: Priority | int) -> TypedMemory:
|
|
202
|
+
"""Create a new TypedMemory with updated priority."""
|
|
203
|
+
if isinstance(priority, int):
|
|
204
|
+
priority = Priority.from_int(priority)
|
|
205
|
+
return TypedMemory(
|
|
206
|
+
fiber_id=self.fiber_id,
|
|
207
|
+
memory_type=self.memory_type,
|
|
208
|
+
priority=priority,
|
|
209
|
+
provenance=self.provenance,
|
|
210
|
+
expires_at=self.expires_at,
|
|
211
|
+
project_id=self.project_id,
|
|
212
|
+
tags=self.tags,
|
|
213
|
+
metadata=self.metadata,
|
|
214
|
+
created_at=self.created_at,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def verify(self) -> TypedMemory:
|
|
218
|
+
"""Create a new TypedMemory marked as verified."""
|
|
219
|
+
return TypedMemory(
|
|
220
|
+
fiber_id=self.fiber_id,
|
|
221
|
+
memory_type=self.memory_type,
|
|
222
|
+
priority=self.priority,
|
|
223
|
+
provenance=self.provenance.verify(),
|
|
224
|
+
expires_at=self.expires_at,
|
|
225
|
+
project_id=self.project_id,
|
|
226
|
+
tags=self.tags,
|
|
227
|
+
metadata=self.metadata,
|
|
228
|
+
created_at=self.created_at,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def extend_expiry(self, days: int) -> TypedMemory:
|
|
232
|
+
"""Create a new TypedMemory with extended expiry."""
|
|
233
|
+
new_expiry = datetime.utcnow() + timedelta(days=days)
|
|
234
|
+
return TypedMemory(
|
|
235
|
+
fiber_id=self.fiber_id,
|
|
236
|
+
memory_type=self.memory_type,
|
|
237
|
+
priority=self.priority,
|
|
238
|
+
provenance=self.provenance,
|
|
239
|
+
expires_at=new_expiry,
|
|
240
|
+
project_id=self.project_id,
|
|
241
|
+
tags=self.tags,
|
|
242
|
+
metadata=self.metadata,
|
|
243
|
+
created_at=self.created_at,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# Default expiry settings per memory type
|
|
248
|
+
DEFAULT_EXPIRY_DAYS: dict[MemoryType, int | None] = {
|
|
249
|
+
MemoryType.FACT: None, # Facts don't expire by default
|
|
250
|
+
MemoryType.DECISION: 90, # Decisions may become stale
|
|
251
|
+
MemoryType.PREFERENCE: None, # Preferences persist
|
|
252
|
+
MemoryType.TODO: 30, # TODOs should be acted on
|
|
253
|
+
MemoryType.INSIGHT: 180, # Insights may become outdated
|
|
254
|
+
MemoryType.CONTEXT: 7, # Context is usually short-term
|
|
255
|
+
MemoryType.INSTRUCTION: None, # Instructions persist
|
|
256
|
+
MemoryType.ERROR: 30, # Error patterns may get fixed
|
|
257
|
+
MemoryType.WORKFLOW: 365, # Workflows change slowly
|
|
258
|
+
MemoryType.REFERENCE: None, # References persist
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def suggest_memory_type(content: str) -> MemoryType:
|
|
263
|
+
"""Suggest a memory type based on content analysis.
|
|
264
|
+
|
|
265
|
+
This is a simple heuristic. For production, use NLP.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
content: The memory content to analyze
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Suggested MemoryType
|
|
272
|
+
"""
|
|
273
|
+
content_lower = content.lower()
|
|
274
|
+
|
|
275
|
+
# TODO patterns (highest priority - actionable)
|
|
276
|
+
if any(
|
|
277
|
+
kw in content_lower
|
|
278
|
+
for kw in ["todo", "need to", "should", "must", "fix", "implement", "add"]
|
|
279
|
+
):
|
|
280
|
+
return MemoryType.TODO
|
|
281
|
+
|
|
282
|
+
# Decision patterns
|
|
283
|
+
if any(
|
|
284
|
+
kw in content_lower
|
|
285
|
+
for kw in ["decided", "chose", "will use", "going with", "picked", "selected"]
|
|
286
|
+
):
|
|
287
|
+
return MemoryType.DECISION
|
|
288
|
+
|
|
289
|
+
# Instruction patterns (check BEFORE preference - "always use" vs "always")
|
|
290
|
+
if any(
|
|
291
|
+
kw in content_lower
|
|
292
|
+
for kw in [
|
|
293
|
+
"always use",
|
|
294
|
+
"never use",
|
|
295
|
+
"make sure",
|
|
296
|
+
"remember to",
|
|
297
|
+
"don't forget",
|
|
298
|
+
]
|
|
299
|
+
):
|
|
300
|
+
return MemoryType.INSTRUCTION
|
|
301
|
+
|
|
302
|
+
# Preference patterns
|
|
303
|
+
if any(kw in content_lower for kw in ["prefer", "like", "favorite", "hate"]):
|
|
304
|
+
return MemoryType.PREFERENCE
|
|
305
|
+
|
|
306
|
+
# Insight patterns (check BEFORE error - "discovered" vs "issue")
|
|
307
|
+
if any(
|
|
308
|
+
kw in content_lower
|
|
309
|
+
for kw in ["learned", "realized", "discovered", "found that", "turns out"]
|
|
310
|
+
):
|
|
311
|
+
return MemoryType.INSIGHT
|
|
312
|
+
|
|
313
|
+
# Error patterns
|
|
314
|
+
if any(
|
|
315
|
+
kw in content_lower
|
|
316
|
+
for kw in ["error", "bug", "issue", "problem", "fail", "crash", "exception"]
|
|
317
|
+
):
|
|
318
|
+
return MemoryType.ERROR
|
|
319
|
+
|
|
320
|
+
# Workflow patterns
|
|
321
|
+
if any(
|
|
322
|
+
kw in content_lower for kw in ["workflow", "process", "step", "flow", "pipeline", "deploy"]
|
|
323
|
+
):
|
|
324
|
+
return MemoryType.WORKFLOW
|
|
325
|
+
|
|
326
|
+
# Reference patterns
|
|
327
|
+
if any(kw in content_lower for kw in ["http", "https", "docs", "documentation"]):
|
|
328
|
+
return MemoryType.REFERENCE
|
|
329
|
+
|
|
330
|
+
# Default to fact
|
|
331
|
+
return MemoryType.FACT
|