memorygraphMCP 0.11.7__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.
- memorygraph/__init__.py +50 -0
- memorygraph/__main__.py +12 -0
- memorygraph/advanced_tools.py +509 -0
- memorygraph/analytics/__init__.py +46 -0
- memorygraph/analytics/advanced_queries.py +727 -0
- memorygraph/backends/__init__.py +21 -0
- memorygraph/backends/base.py +179 -0
- memorygraph/backends/cloud.py +75 -0
- memorygraph/backends/cloud_backend.py +858 -0
- memorygraph/backends/factory.py +577 -0
- memorygraph/backends/falkordb_backend.py +749 -0
- memorygraph/backends/falkordblite_backend.py +746 -0
- memorygraph/backends/ladybugdb_backend.py +242 -0
- memorygraph/backends/memgraph_backend.py +327 -0
- memorygraph/backends/neo4j_backend.py +298 -0
- memorygraph/backends/sqlite_fallback.py +463 -0
- memorygraph/backends/turso.py +448 -0
- memorygraph/cli.py +743 -0
- memorygraph/cloud_database.py +297 -0
- memorygraph/config.py +295 -0
- memorygraph/database.py +933 -0
- memorygraph/graph_analytics.py +631 -0
- memorygraph/integration/__init__.py +69 -0
- memorygraph/integration/context_capture.py +426 -0
- memorygraph/integration/project_analysis.py +583 -0
- memorygraph/integration/workflow_tracking.py +492 -0
- memorygraph/intelligence/__init__.py +59 -0
- memorygraph/intelligence/context_retrieval.py +447 -0
- memorygraph/intelligence/entity_extraction.py +386 -0
- memorygraph/intelligence/pattern_recognition.py +420 -0
- memorygraph/intelligence/temporal.py +374 -0
- memorygraph/migration/__init__.py +27 -0
- memorygraph/migration/manager.py +579 -0
- memorygraph/migration/models.py +142 -0
- memorygraph/migration/scripts/__init__.py +17 -0
- memorygraph/migration/scripts/bitemporal_migration.py +595 -0
- memorygraph/migration/scripts/multitenancy_migration.py +452 -0
- memorygraph/migration_tools_module.py +146 -0
- memorygraph/models.py +684 -0
- memorygraph/proactive/__init__.py +46 -0
- memorygraph/proactive/outcome_learning.py +444 -0
- memorygraph/proactive/predictive.py +410 -0
- memorygraph/proactive/session_briefing.py +399 -0
- memorygraph/relationships.py +668 -0
- memorygraph/server.py +883 -0
- memorygraph/sqlite_database.py +1876 -0
- memorygraph/tools/__init__.py +59 -0
- memorygraph/tools/activity_tools.py +262 -0
- memorygraph/tools/memory_tools.py +315 -0
- memorygraph/tools/migration_tools.py +181 -0
- memorygraph/tools/relationship_tools.py +147 -0
- memorygraph/tools/search_tools.py +406 -0
- memorygraph/tools/temporal_tools.py +339 -0
- memorygraph/utils/__init__.py +10 -0
- memorygraph/utils/context_extractor.py +429 -0
- memorygraph/utils/error_handling.py +151 -0
- memorygraph/utils/export_import.py +425 -0
- memorygraph/utils/graph_algorithms.py +200 -0
- memorygraph/utils/pagination.py +149 -0
- memorygraph/utils/project_detection.py +133 -0
- memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
- memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
- memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
- memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
- memorygraphmcp-0.11.7.dist-info/licenses/LICENSE +21 -0
memorygraph/models.py
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models and schemas for Claude Code Memory Server.
|
|
3
|
+
|
|
4
|
+
This module defines the core data structures used throughout the memory system,
|
|
5
|
+
including memory types, relationships, and validation schemas.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Dict, List, Optional, Any, Union
|
|
11
|
+
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MemoryType(str, Enum):
|
|
15
|
+
"""Types of memories that can be stored in the system."""
|
|
16
|
+
|
|
17
|
+
TASK = "task"
|
|
18
|
+
CODE_PATTERN = "code_pattern"
|
|
19
|
+
PROBLEM = "problem"
|
|
20
|
+
SOLUTION = "solution"
|
|
21
|
+
PROJECT = "project"
|
|
22
|
+
TECHNOLOGY = "technology"
|
|
23
|
+
ERROR = "error"
|
|
24
|
+
FIX = "fix"
|
|
25
|
+
COMMAND = "command"
|
|
26
|
+
FILE_CONTEXT = "file_context"
|
|
27
|
+
WORKFLOW = "workflow"
|
|
28
|
+
GENERAL = "general"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RelationshipType(str, Enum):
|
|
32
|
+
"""Types of relationships between memories."""
|
|
33
|
+
|
|
34
|
+
# Causal relationships
|
|
35
|
+
CAUSES = "CAUSES"
|
|
36
|
+
TRIGGERS = "TRIGGERS"
|
|
37
|
+
LEADS_TO = "LEADS_TO"
|
|
38
|
+
PREVENTS = "PREVENTS"
|
|
39
|
+
BREAKS = "BREAKS"
|
|
40
|
+
|
|
41
|
+
# Solution relationships
|
|
42
|
+
SOLVES = "SOLVES"
|
|
43
|
+
ADDRESSES = "ADDRESSES"
|
|
44
|
+
ALTERNATIVE_TO = "ALTERNATIVE_TO"
|
|
45
|
+
IMPROVES = "IMPROVES"
|
|
46
|
+
REPLACES = "REPLACES"
|
|
47
|
+
|
|
48
|
+
# Context relationships
|
|
49
|
+
OCCURS_IN = "OCCURS_IN"
|
|
50
|
+
APPLIES_TO = "APPLIES_TO"
|
|
51
|
+
WORKS_WITH = "WORKS_WITH"
|
|
52
|
+
REQUIRES = "REQUIRES"
|
|
53
|
+
USED_IN = "USED_IN"
|
|
54
|
+
|
|
55
|
+
# Learning relationships
|
|
56
|
+
BUILDS_ON = "BUILDS_ON"
|
|
57
|
+
CONTRADICTS = "CONTRADICTS"
|
|
58
|
+
CONFIRMS = "CONFIRMS"
|
|
59
|
+
GENERALIZES = "GENERALIZES"
|
|
60
|
+
SPECIALIZES = "SPECIALIZES"
|
|
61
|
+
|
|
62
|
+
# Similarity relationships
|
|
63
|
+
SIMILAR_TO = "SIMILAR_TO"
|
|
64
|
+
VARIANT_OF = "VARIANT_OF"
|
|
65
|
+
RELATED_TO = "RELATED_TO"
|
|
66
|
+
ANALOGY_TO = "ANALOGY_TO"
|
|
67
|
+
OPPOSITE_OF = "OPPOSITE_OF"
|
|
68
|
+
|
|
69
|
+
# Workflow relationships
|
|
70
|
+
FOLLOWS = "FOLLOWS"
|
|
71
|
+
DEPENDS_ON = "DEPENDS_ON"
|
|
72
|
+
ENABLES = "ENABLES"
|
|
73
|
+
BLOCKS = "BLOCKS"
|
|
74
|
+
PARALLEL_TO = "PARALLEL_TO"
|
|
75
|
+
|
|
76
|
+
# Quality relationships
|
|
77
|
+
EFFECTIVE_FOR = "EFFECTIVE_FOR"
|
|
78
|
+
INEFFECTIVE_FOR = "INEFFECTIVE_FOR"
|
|
79
|
+
PREFERRED_OVER = "PREFERRED_OVER"
|
|
80
|
+
DEPRECATED_BY = "DEPRECATED_BY"
|
|
81
|
+
VALIDATED_BY = "VALIDATED_BY"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MemoryContext(BaseModel):
|
|
85
|
+
"""Context information for a memory.
|
|
86
|
+
|
|
87
|
+
This model supports both single-tenant (default) and multi-tenant deployments.
|
|
88
|
+
Multi-tenancy fields are optional and only validated/used when MEMORY_MULTI_TENANT_MODE=true.
|
|
89
|
+
|
|
90
|
+
Single-Tenant Mode (default):
|
|
91
|
+
- project_path provides memory scoping
|
|
92
|
+
- tenant_id, team_id, created_by are ignored
|
|
93
|
+
- visibility defaults to "project"
|
|
94
|
+
|
|
95
|
+
Multi-Tenant Mode (MEMORY_MULTI_TENANT_MODE=true):
|
|
96
|
+
- tenant_id is required for memory isolation
|
|
97
|
+
- team_id provides team-level scoping
|
|
98
|
+
- visibility controls access levels: private | project | team | public
|
|
99
|
+
- created_by tracks memory ownership for access control
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
Single-tenant context:
|
|
103
|
+
>>> context = MemoryContext(project_path="/my/project")
|
|
104
|
+
>>> context.tenant_id is None
|
|
105
|
+
True
|
|
106
|
+
|
|
107
|
+
Multi-tenant team context:
|
|
108
|
+
>>> context = MemoryContext(
|
|
109
|
+
... tenant_id="acme-corp",
|
|
110
|
+
... team_id="backend",
|
|
111
|
+
... visibility="team",
|
|
112
|
+
... created_by="alice"
|
|
113
|
+
... )
|
|
114
|
+
>>> context.visibility
|
|
115
|
+
'team'
|
|
116
|
+
|
|
117
|
+
Private memory:
|
|
118
|
+
>>> context = MemoryContext(
|
|
119
|
+
... tenant_id="acme-corp",
|
|
120
|
+
... visibility="private",
|
|
121
|
+
... created_by="bob"
|
|
122
|
+
... )
|
|
123
|
+
>>> context.visibility
|
|
124
|
+
'private'
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
model_config = ConfigDict(
|
|
128
|
+
json_schema_extra={
|
|
129
|
+
"examples": [
|
|
130
|
+
{
|
|
131
|
+
"project_path": "/my/project",
|
|
132
|
+
"session_id": "session-123",
|
|
133
|
+
"files_involved": ["main.py", "utils.py"],
|
|
134
|
+
"languages": ["python"],
|
|
135
|
+
"frameworks": ["fastapi"]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"tenant_id": "acme-corp",
|
|
139
|
+
"team_id": "backend-team",
|
|
140
|
+
"visibility": "team",
|
|
141
|
+
"created_by": "alice",
|
|
142
|
+
"project_path": "/apps/api",
|
|
143
|
+
"git_branch": "main"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"tenant_id": "acme-corp",
|
|
147
|
+
"visibility": "private",
|
|
148
|
+
"created_by": "bob",
|
|
149
|
+
"user_id": "bob",
|
|
150
|
+
"project_path": "/personal/notes"
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# === Existing fields (unchanged for backward compatibility) ===
|
|
157
|
+
project_path: Optional[str] = None
|
|
158
|
+
files_involved: List[str] = Field(default_factory=list)
|
|
159
|
+
languages: List[str] = Field(default_factory=list)
|
|
160
|
+
frameworks: List[str] = Field(default_factory=list)
|
|
161
|
+
technologies: List[str] = Field(default_factory=list)
|
|
162
|
+
git_commit: Optional[str] = None
|
|
163
|
+
git_branch: Optional[str] = None
|
|
164
|
+
working_directory: Optional[str] = None
|
|
165
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
166
|
+
session_id: Optional[str] = None
|
|
167
|
+
user_id: Optional[str] = None
|
|
168
|
+
additional_metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
169
|
+
|
|
170
|
+
# === New optional multi-tenancy fields (Phase 1) ===
|
|
171
|
+
tenant_id: Optional[str] = Field(
|
|
172
|
+
None,
|
|
173
|
+
description="Tenant/organization identifier. Required in multi-tenant mode."
|
|
174
|
+
)
|
|
175
|
+
team_id: Optional[str] = Field(
|
|
176
|
+
None,
|
|
177
|
+
description="Team identifier within tenant"
|
|
178
|
+
)
|
|
179
|
+
visibility: str = Field(
|
|
180
|
+
"project",
|
|
181
|
+
description="Memory visibility level: private | project | team | public"
|
|
182
|
+
)
|
|
183
|
+
created_by: Optional[str] = Field(
|
|
184
|
+
None,
|
|
185
|
+
description="User ID who created this memory (for audit/access control)"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
@field_validator('visibility')
|
|
189
|
+
@classmethod
|
|
190
|
+
def validate_visibility(cls, v: str) -> str:
|
|
191
|
+
"""Ensure visibility is one of the allowed values.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
v: Visibility value to validate
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Validated visibility value
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
ValueError: If visibility is not one of: private, project, team, public
|
|
201
|
+
"""
|
|
202
|
+
valid_values = ["private", "project", "team", "public"]
|
|
203
|
+
if v not in valid_values:
|
|
204
|
+
raise ValueError(f"visibility must be one of {valid_values}, got '{v}'")
|
|
205
|
+
return v
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class Memory(BaseModel):
|
|
209
|
+
"""Core memory data structure.
|
|
210
|
+
|
|
211
|
+
Represents a single memory entry in the system with metadata,
|
|
212
|
+
content, relationships, and tracking information.
|
|
213
|
+
|
|
214
|
+
Attributes:
|
|
215
|
+
id: Unique identifier for the memory (auto-generated if not provided)
|
|
216
|
+
type: Type of memory (task, solution, problem, etc.)
|
|
217
|
+
title: Short descriptive title (1-200 characters)
|
|
218
|
+
content: Main content of the memory (required)
|
|
219
|
+
summary: Optional brief summary (max 500 characters)
|
|
220
|
+
tags: List of lowercase tags for categorization
|
|
221
|
+
context: Optional context information (project, files, etc.)
|
|
222
|
+
importance: Importance score from 0.0 to 1.0 (default 0.5)
|
|
223
|
+
confidence: Confidence score from 0.0 to 1.0 (default 0.8)
|
|
224
|
+
effectiveness: Optional effectiveness rating (0.0 to 1.0)
|
|
225
|
+
usage_count: Number of times this memory has been accessed
|
|
226
|
+
created_at: Timestamp when memory was created
|
|
227
|
+
updated_at: Timestamp when memory was last updated
|
|
228
|
+
last_accessed: Timestamp when memory was last accessed (optional)
|
|
229
|
+
version: Version number for optimistic concurrency control (Phase 1)
|
|
230
|
+
updated_by: User ID who last updated this memory (Phase 1)
|
|
231
|
+
relationships: Enriched field with related memory IDs by type
|
|
232
|
+
match_info: Enriched field with search match information
|
|
233
|
+
context_summary: Enriched field with relationship context
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
id: Optional[str] = None
|
|
237
|
+
type: MemoryType
|
|
238
|
+
title: str = Field(..., min_length=1, max_length=200)
|
|
239
|
+
content: str = Field(..., min_length=1)
|
|
240
|
+
summary: Optional[str] = Field(None, max_length=500)
|
|
241
|
+
tags: List[str] = Field(default_factory=list)
|
|
242
|
+
context: Optional[MemoryContext] = None
|
|
243
|
+
importance: float = Field(default=0.5, ge=0.0, le=1.0)
|
|
244
|
+
confidence: float = Field(default=0.8, ge=0.0, le=1.0)
|
|
245
|
+
effectiveness: Optional[float] = Field(None, ge=0.0, le=1.0)
|
|
246
|
+
usage_count: int = Field(default=0, ge=0)
|
|
247
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
248
|
+
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
249
|
+
last_accessed: Optional[datetime] = None
|
|
250
|
+
|
|
251
|
+
# Concurrency control fields (Phase 1 - Multi-tenancy support)
|
|
252
|
+
version: int = Field(
|
|
253
|
+
default=1,
|
|
254
|
+
ge=1,
|
|
255
|
+
description="Version number for optimistic concurrency control"
|
|
256
|
+
)
|
|
257
|
+
updated_by: Optional[str] = Field(
|
|
258
|
+
None,
|
|
259
|
+
description="User ID who last updated this memory"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Enriched search result fields (populated by search operations)
|
|
263
|
+
relationships: Optional[Dict[str, List[str]]] = None
|
|
264
|
+
match_info: Optional[Dict[str, Any]] = None
|
|
265
|
+
context_summary: Optional[str] = None
|
|
266
|
+
|
|
267
|
+
@field_validator('tags')
|
|
268
|
+
@classmethod
|
|
269
|
+
def validate_tags(cls, v: List[str]) -> List[str]:
|
|
270
|
+
"""Ensure tags are lowercase and non-empty.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
v: List of tag strings to validate
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
List of cleaned, lowercase, non-empty tags
|
|
277
|
+
"""
|
|
278
|
+
return [tag.lower().strip() for tag in v if tag.strip()]
|
|
279
|
+
|
|
280
|
+
@field_validator('title', 'content')
|
|
281
|
+
@classmethod
|
|
282
|
+
def validate_text_fields(cls, v: str) -> str:
|
|
283
|
+
"""Ensure text fields are properly formatted.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
v: Text field value to validate
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Stripped text field value
|
|
290
|
+
"""
|
|
291
|
+
return v.strip()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class RelationshipProperties(BaseModel):
|
|
295
|
+
"""Properties for relationships between memories.
|
|
296
|
+
|
|
297
|
+
Stores metadata about the relationship including strength,
|
|
298
|
+
confidence, validation history, evidence tracking, and bi-temporal fields.
|
|
299
|
+
|
|
300
|
+
Attributes:
|
|
301
|
+
strength: Relationship strength from 0.0 to 1.0 (default 0.5)
|
|
302
|
+
confidence: Confidence in relationship from 0.0 to 1.0 (default 0.8)
|
|
303
|
+
context: Optional descriptive context for the relationship
|
|
304
|
+
evidence_count: Number of times this relationship has been validated
|
|
305
|
+
success_rate: Optional success rate (0.0 to 1.0)
|
|
306
|
+
created_at: Timestamp when relationship was created
|
|
307
|
+
last_validated: Timestamp when relationship was last validated
|
|
308
|
+
validation_count: Number of times this relationship has been validated
|
|
309
|
+
counter_evidence_count: Number of times counter-evidence was found
|
|
310
|
+
valid_from: When the fact became true (validity time)
|
|
311
|
+
valid_until: When the fact stopped being true (None = still valid)
|
|
312
|
+
recorded_at: When we learned this fact (transaction time)
|
|
313
|
+
invalidated_by: ID of relationship that superseded this one
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
strength: float = Field(default=0.5, ge=0.0, le=1.0)
|
|
317
|
+
confidence: float = Field(default=0.8, ge=0.0, le=1.0)
|
|
318
|
+
context: Optional[str] = None
|
|
319
|
+
evidence_count: int = Field(default=1, ge=0)
|
|
320
|
+
success_rate: Optional[float] = Field(None, ge=0.0, le=1.0)
|
|
321
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
322
|
+
last_validated: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
323
|
+
validation_count: int = Field(default=0, ge=0)
|
|
324
|
+
counter_evidence_count: int = Field(default=0, ge=0)
|
|
325
|
+
|
|
326
|
+
# Bi-temporal tracking fields (Phase 2.2)
|
|
327
|
+
valid_from: datetime = Field(
|
|
328
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
329
|
+
description="When the fact became true (validity time)"
|
|
330
|
+
)
|
|
331
|
+
valid_until: Optional[datetime] = Field(
|
|
332
|
+
None,
|
|
333
|
+
description="When the fact stopped being true (None = still valid)"
|
|
334
|
+
)
|
|
335
|
+
recorded_at: datetime = Field(
|
|
336
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
337
|
+
description="When we learned this fact (transaction time)"
|
|
338
|
+
)
|
|
339
|
+
invalidated_by: Optional[str] = Field(
|
|
340
|
+
None,
|
|
341
|
+
description="ID of relationship that superseded this one"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class Relationship(BaseModel):
|
|
346
|
+
"""Relationship between two memories.
|
|
347
|
+
|
|
348
|
+
Represents a directed relationship from one memory to another,
|
|
349
|
+
with a specific type and associated properties.
|
|
350
|
+
|
|
351
|
+
Attributes:
|
|
352
|
+
id: Unique identifier for the relationship (auto-generated)
|
|
353
|
+
from_memory_id: ID of the source memory
|
|
354
|
+
to_memory_id: ID of the target memory
|
|
355
|
+
type: Type of relationship (SOLVES, CAUSES, RELATES_TO, etc.)
|
|
356
|
+
properties: Relationship metadata and tracking information
|
|
357
|
+
description: Optional human-readable description
|
|
358
|
+
bidirectional: Whether the relationship is bidirectional (default False)
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
id: Optional[str] = None
|
|
362
|
+
from_memory_id: str
|
|
363
|
+
to_memory_id: str
|
|
364
|
+
type: RelationshipType
|
|
365
|
+
properties: RelationshipProperties = Field(default_factory=RelationshipProperties)
|
|
366
|
+
description: Optional[str] = None
|
|
367
|
+
bidirectional: bool = Field(default=False)
|
|
368
|
+
|
|
369
|
+
@field_validator('from_memory_id', 'to_memory_id')
|
|
370
|
+
@classmethod
|
|
371
|
+
def validate_memory_ids(cls, v: str) -> str:
|
|
372
|
+
"""Ensure memory IDs are non-empty.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
v: Memory ID to validate
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Stripped memory ID
|
|
379
|
+
|
|
380
|
+
Raises:
|
|
381
|
+
ValueError: If memory ID is empty or whitespace
|
|
382
|
+
"""
|
|
383
|
+
if not v or not v.strip():
|
|
384
|
+
raise ValueError('Memory ID cannot be empty')
|
|
385
|
+
return v.strip()
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class MemoryNode(BaseModel):
|
|
389
|
+
"""Neo4j node representation of a memory."""
|
|
390
|
+
|
|
391
|
+
memory: Memory
|
|
392
|
+
node_id: Optional[int] = None # Neo4j internal node ID
|
|
393
|
+
labels: List[str] = Field(default_factory=list)
|
|
394
|
+
|
|
395
|
+
def to_neo4j_properties(self) -> Dict[str, Any]:
|
|
396
|
+
"""Convert memory to Neo4j node properties.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Dictionary of property names to values suitable for Neo4j storage
|
|
400
|
+
"""
|
|
401
|
+
props = {
|
|
402
|
+
'id': self.memory.id,
|
|
403
|
+
'type': self.memory.type.value,
|
|
404
|
+
'title': self.memory.title,
|
|
405
|
+
'content': self.memory.content,
|
|
406
|
+
'tags': self.memory.tags,
|
|
407
|
+
'importance': self.memory.importance,
|
|
408
|
+
'confidence': self.memory.confidence,
|
|
409
|
+
'usage_count': self.memory.usage_count,
|
|
410
|
+
'created_at': self.memory.created_at.isoformat(),
|
|
411
|
+
'updated_at': self.memory.updated_at.isoformat(),
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
# Add optional fields if present
|
|
415
|
+
if self.memory.summary:
|
|
416
|
+
props['summary'] = self.memory.summary
|
|
417
|
+
if self.memory.effectiveness is not None:
|
|
418
|
+
props['effectiveness'] = self.memory.effectiveness
|
|
419
|
+
if self.memory.last_accessed:
|
|
420
|
+
props['last_accessed'] = self.memory.last_accessed.isoformat()
|
|
421
|
+
|
|
422
|
+
# Add context information if present
|
|
423
|
+
if self.memory.context:
|
|
424
|
+
import json
|
|
425
|
+
context_data = self.memory.context.model_dump()
|
|
426
|
+
for key, value in context_data.items():
|
|
427
|
+
if value is not None:
|
|
428
|
+
if isinstance(value, datetime):
|
|
429
|
+
props[f'context_{key}'] = value.isoformat()
|
|
430
|
+
elif isinstance(value, list):
|
|
431
|
+
# Store lists as native Neo4j arrays (only if simple types)
|
|
432
|
+
if value and all(isinstance(v, (str, int, float, bool)) for v in value):
|
|
433
|
+
props[f'context_{key}'] = value
|
|
434
|
+
else:
|
|
435
|
+
# Complex nested structures: use JSON serialization
|
|
436
|
+
props[f'context_{key}'] = json.dumps(value)
|
|
437
|
+
elif isinstance(value, dict):
|
|
438
|
+
# Dicts must be serialized as JSON strings for Neo4j compatibility
|
|
439
|
+
props[f'context_{key}'] = json.dumps(value)
|
|
440
|
+
else:
|
|
441
|
+
props[f'context_{key}'] = value
|
|
442
|
+
|
|
443
|
+
return props
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class SearchQuery(BaseModel):
|
|
447
|
+
"""Search query parameters for memory retrieval.
|
|
448
|
+
|
|
449
|
+
Supports various filtering options including text search, type filtering,
|
|
450
|
+
metadata filters, and search tolerance modes.
|
|
451
|
+
|
|
452
|
+
Attributes:
|
|
453
|
+
query: Text query for content search (optional)
|
|
454
|
+
terms: Multiple search terms for multi-term queries
|
|
455
|
+
memory_types: Filter by specific memory types
|
|
456
|
+
tags: Filter by tags
|
|
457
|
+
project_path: Filter by project path
|
|
458
|
+
languages: Filter by programming languages
|
|
459
|
+
frameworks: Filter by frameworks
|
|
460
|
+
min_importance: Minimum importance threshold (0.0-1.0)
|
|
461
|
+
min_confidence: Minimum confidence threshold (0.0-1.0)
|
|
462
|
+
min_effectiveness: Minimum effectiveness threshold (0.0-1.0)
|
|
463
|
+
created_after: Only include memories created after this datetime
|
|
464
|
+
created_before: Only include memories created before this datetime
|
|
465
|
+
limit: Maximum number of results (1-1000, default 50)
|
|
466
|
+
offset: Number of results to skip for pagination (default 0)
|
|
467
|
+
include_relationships: Include relationship information in results
|
|
468
|
+
search_tolerance: Search mode - 'strict', 'normal', or 'fuzzy'
|
|
469
|
+
match_mode: Term matching mode - 'any' (OR) or 'all' (AND)
|
|
470
|
+
relationship_filter: Filter results by relationship types
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
query: Optional[str] = None
|
|
474
|
+
terms: List[str] = Field(default_factory=list, description="Multiple search terms for multi-term queries")
|
|
475
|
+
memory_types: List[MemoryType] = Field(default_factory=list)
|
|
476
|
+
tags: List[str] = Field(default_factory=list)
|
|
477
|
+
project_path: Optional[str] = None
|
|
478
|
+
languages: List[str] = Field(default_factory=list)
|
|
479
|
+
frameworks: List[str] = Field(default_factory=list)
|
|
480
|
+
min_importance: Optional[float] = Field(None, ge=0.0, le=1.0)
|
|
481
|
+
min_confidence: Optional[float] = Field(None, ge=0.0, le=1.0)
|
|
482
|
+
min_effectiveness: Optional[float] = Field(None, ge=0.0, le=1.0)
|
|
483
|
+
created_after: Optional[datetime] = None
|
|
484
|
+
created_before: Optional[datetime] = None
|
|
485
|
+
limit: int = Field(default=50, ge=1, le=1000)
|
|
486
|
+
offset: int = Field(default=0, ge=0)
|
|
487
|
+
include_relationships: bool = Field(default=True)
|
|
488
|
+
search_tolerance: Optional[str] = Field(default="normal")
|
|
489
|
+
match_mode: Optional[str] = Field(default="any", description="Match mode for terms: 'any' (OR) or 'all' (AND)")
|
|
490
|
+
relationship_filter: Optional[List[str]] = Field(default=None, description="Filter results by relationship types")
|
|
491
|
+
|
|
492
|
+
@field_validator('search_tolerance')
|
|
493
|
+
@classmethod
|
|
494
|
+
def validate_search_tolerance(cls, v: Optional[str]) -> str:
|
|
495
|
+
"""Validate search_tolerance parameter.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
v: Search tolerance value to validate
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
Validated search tolerance value
|
|
502
|
+
|
|
503
|
+
Raises:
|
|
504
|
+
ValueError: If value is not 'strict', 'normal', or 'fuzzy'
|
|
505
|
+
"""
|
|
506
|
+
if v is None:
|
|
507
|
+
return "normal"
|
|
508
|
+
valid_values = ["strict", "normal", "fuzzy"]
|
|
509
|
+
if v not in valid_values:
|
|
510
|
+
raise ValueError(f"search_tolerance must be one of {valid_values}, got '{v}'")
|
|
511
|
+
return v
|
|
512
|
+
|
|
513
|
+
@field_validator('match_mode')
|
|
514
|
+
@classmethod
|
|
515
|
+
def validate_match_mode(cls, v: Optional[str]) -> str:
|
|
516
|
+
"""Validate match_mode parameter.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
v: Match mode value to validate
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
Validated match mode value
|
|
523
|
+
|
|
524
|
+
Raises:
|
|
525
|
+
ValueError: If value is not 'any' or 'all'
|
|
526
|
+
"""
|
|
527
|
+
if v is None:
|
|
528
|
+
return "any"
|
|
529
|
+
valid_values = ["any", "all"]
|
|
530
|
+
if v not in valid_values:
|
|
531
|
+
raise ValueError(f"match_mode must be one of {valid_values}, got '{v}'")
|
|
532
|
+
return v
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class PaginatedResult(BaseModel):
|
|
536
|
+
"""Paginated result wrapper for memory search operations.
|
|
537
|
+
|
|
538
|
+
Provides pagination metadata to help clients navigate large result sets.
|
|
539
|
+
|
|
540
|
+
Attributes:
|
|
541
|
+
results: List of memories in this page
|
|
542
|
+
total_count: Total number of memories matching the query
|
|
543
|
+
limit: Maximum number of results per page
|
|
544
|
+
offset: Number of results skipped
|
|
545
|
+
has_more: True if more results are available
|
|
546
|
+
next_offset: Offset for the next page (None if no more pages)
|
|
547
|
+
"""
|
|
548
|
+
|
|
549
|
+
results: List[Memory]
|
|
550
|
+
total_count: int = Field(ge=0)
|
|
551
|
+
limit: int = Field(ge=1, le=1000)
|
|
552
|
+
offset: int = Field(ge=0)
|
|
553
|
+
has_more: bool
|
|
554
|
+
next_offset: Optional[int] = None
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class MemoryGraph(BaseModel):
|
|
558
|
+
"""Graph representation of memories and their relationships."""
|
|
559
|
+
|
|
560
|
+
memories: List[Memory]
|
|
561
|
+
relationships: List[Relationship]
|
|
562
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
563
|
+
|
|
564
|
+
def get_memory_by_id(self, memory_id: str) -> Optional[Memory]:
|
|
565
|
+
"""Get a memory by its ID.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
memory_id: Unique identifier of the memory to retrieve
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Memory object if found, None otherwise
|
|
572
|
+
"""
|
|
573
|
+
return next((m for m in self.memories if m.id == memory_id), None)
|
|
574
|
+
|
|
575
|
+
def get_relationships_for_memory(self, memory_id: str) -> List[Relationship]:
|
|
576
|
+
"""Get all relationships involving a specific memory.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
memory_id: ID of the memory to find relationships for
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
List of relationships where memory is source or target
|
|
583
|
+
"""
|
|
584
|
+
return [
|
|
585
|
+
r for r in self.relationships
|
|
586
|
+
if r.from_memory_id == memory_id or r.to_memory_id == memory_id
|
|
587
|
+
]
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class AnalysisResult(BaseModel):
|
|
591
|
+
"""Result of memory or relationship analysis."""
|
|
592
|
+
|
|
593
|
+
analysis_type: str
|
|
594
|
+
results: Dict[str, Any]
|
|
595
|
+
confidence: float = Field(ge=0.0, le=1.0)
|
|
596
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
597
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
# Custom Exception Hierarchy
|
|
601
|
+
|
|
602
|
+
class MemoryError(Exception):
|
|
603
|
+
"""Base exception for all memory-related errors.
|
|
604
|
+
|
|
605
|
+
All custom exceptions in the memory system inherit from this base class,
|
|
606
|
+
allowing for consistent error handling and categorization.
|
|
607
|
+
|
|
608
|
+
Attributes:
|
|
609
|
+
message: Human-readable error message
|
|
610
|
+
details: Optional dictionary with additional error context
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
614
|
+
"""Initialize memory error.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
message: Human-readable error message
|
|
618
|
+
details: Optional dictionary with additional error context
|
|
619
|
+
"""
|
|
620
|
+
self.message = message
|
|
621
|
+
self.details = details or {}
|
|
622
|
+
super().__init__(self.message)
|
|
623
|
+
|
|
624
|
+
def __str__(self) -> str:
|
|
625
|
+
"""Return string representation of error."""
|
|
626
|
+
if self.details:
|
|
627
|
+
return f"{self.message} (Details: {self.details})"
|
|
628
|
+
return self.message
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
class MemoryNotFoundError(MemoryError):
|
|
632
|
+
"""Raised when a requested memory does not exist.
|
|
633
|
+
|
|
634
|
+
Attributes:
|
|
635
|
+
memory_id: ID of the memory that was not found
|
|
636
|
+
message: Error message with memory ID
|
|
637
|
+
details: Optional additional context
|
|
638
|
+
"""
|
|
639
|
+
|
|
640
|
+
def __init__(self, memory_id: str, details: Optional[Dict[str, Any]] = None):
|
|
641
|
+
"""Initialize memory not found error.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
memory_id: ID of the memory that was not found
|
|
645
|
+
details: Optional additional context
|
|
646
|
+
"""
|
|
647
|
+
self.memory_id = memory_id
|
|
648
|
+
message = f"Memory not found: {memory_id}"
|
|
649
|
+
super().__init__(message, details)
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
class RelationshipError(MemoryError):
|
|
653
|
+
"""Raised when there's an issue with relationship operations."""
|
|
654
|
+
pass
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
class ValidationError(MemoryError):
|
|
658
|
+
"""Raised when data validation fails."""
|
|
659
|
+
pass
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
class DatabaseConnectionError(MemoryError):
|
|
663
|
+
"""Raised when there's a database connection issue."""
|
|
664
|
+
pass
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
class SchemaError(MemoryError):
|
|
668
|
+
"""Raised when there's a schema-related issue."""
|
|
669
|
+
pass
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
class NotFoundError(MemoryError):
|
|
673
|
+
"""Alias for MemoryNotFoundError for consistency with workplan naming."""
|
|
674
|
+
pass
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
class BackendError(MemoryError):
|
|
678
|
+
"""Raised when there's a backend operation issue."""
|
|
679
|
+
pass
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
class ConfigurationError(MemoryError):
|
|
683
|
+
"""Raised when there's a configuration issue."""
|
|
684
|
+
pass
|