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.
Files changed (65) hide show
  1. memorygraph/__init__.py +50 -0
  2. memorygraph/__main__.py +12 -0
  3. memorygraph/advanced_tools.py +509 -0
  4. memorygraph/analytics/__init__.py +46 -0
  5. memorygraph/analytics/advanced_queries.py +727 -0
  6. memorygraph/backends/__init__.py +21 -0
  7. memorygraph/backends/base.py +179 -0
  8. memorygraph/backends/cloud.py +75 -0
  9. memorygraph/backends/cloud_backend.py +858 -0
  10. memorygraph/backends/factory.py +577 -0
  11. memorygraph/backends/falkordb_backend.py +749 -0
  12. memorygraph/backends/falkordblite_backend.py +746 -0
  13. memorygraph/backends/ladybugdb_backend.py +242 -0
  14. memorygraph/backends/memgraph_backend.py +327 -0
  15. memorygraph/backends/neo4j_backend.py +298 -0
  16. memorygraph/backends/sqlite_fallback.py +463 -0
  17. memorygraph/backends/turso.py +448 -0
  18. memorygraph/cli.py +743 -0
  19. memorygraph/cloud_database.py +297 -0
  20. memorygraph/config.py +295 -0
  21. memorygraph/database.py +933 -0
  22. memorygraph/graph_analytics.py +631 -0
  23. memorygraph/integration/__init__.py +69 -0
  24. memorygraph/integration/context_capture.py +426 -0
  25. memorygraph/integration/project_analysis.py +583 -0
  26. memorygraph/integration/workflow_tracking.py +492 -0
  27. memorygraph/intelligence/__init__.py +59 -0
  28. memorygraph/intelligence/context_retrieval.py +447 -0
  29. memorygraph/intelligence/entity_extraction.py +386 -0
  30. memorygraph/intelligence/pattern_recognition.py +420 -0
  31. memorygraph/intelligence/temporal.py +374 -0
  32. memorygraph/migration/__init__.py +27 -0
  33. memorygraph/migration/manager.py +579 -0
  34. memorygraph/migration/models.py +142 -0
  35. memorygraph/migration/scripts/__init__.py +17 -0
  36. memorygraph/migration/scripts/bitemporal_migration.py +595 -0
  37. memorygraph/migration/scripts/multitenancy_migration.py +452 -0
  38. memorygraph/migration_tools_module.py +146 -0
  39. memorygraph/models.py +684 -0
  40. memorygraph/proactive/__init__.py +46 -0
  41. memorygraph/proactive/outcome_learning.py +444 -0
  42. memorygraph/proactive/predictive.py +410 -0
  43. memorygraph/proactive/session_briefing.py +399 -0
  44. memorygraph/relationships.py +668 -0
  45. memorygraph/server.py +883 -0
  46. memorygraph/sqlite_database.py +1876 -0
  47. memorygraph/tools/__init__.py +59 -0
  48. memorygraph/tools/activity_tools.py +262 -0
  49. memorygraph/tools/memory_tools.py +315 -0
  50. memorygraph/tools/migration_tools.py +181 -0
  51. memorygraph/tools/relationship_tools.py +147 -0
  52. memorygraph/tools/search_tools.py +406 -0
  53. memorygraph/tools/temporal_tools.py +339 -0
  54. memorygraph/utils/__init__.py +10 -0
  55. memorygraph/utils/context_extractor.py +429 -0
  56. memorygraph/utils/error_handling.py +151 -0
  57. memorygraph/utils/export_import.py +425 -0
  58. memorygraph/utils/graph_algorithms.py +200 -0
  59. memorygraph/utils/pagination.py +149 -0
  60. memorygraph/utils/project_detection.py +133 -0
  61. memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
  62. memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
  63. memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
  64. memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
  65. 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