get-claudia 1.28.2 → 1.28.4
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.
- package/bin/index.js +1 -1
- package/memory-daemon/claudia_memory/database.py +36 -11
- package/memory-daemon/claudia_memory/mcp/server.py +99 -25
- package/memory-daemon/claudia_memory/services/remember.py +8 -6
- package/memory-daemon/tests/test_batch_parallel.py +348 -0
- package/memory-daemon/tests/test_llm_coercion.py +258 -0
- package/package.json +1 -1
- package/template-v2/.claude/agents/document-processor.md +67 -0
- package/template-v2/.claude/hooks/pre-compact.py +27 -0
- package/template-v2/.claude/hooks/session-health-check.py +116 -0
- package/template-v2/.claude/hooks/session-health-check.sh +16 -0
- package/template-v2/.claude/settings.local.json +2 -2
- package/template-v2/.claude/skills/capture-meeting/SKILL.md +27 -1
- package/template-v2/.claude/skills/diagnose/SKILL.md +70 -3
- package/template-v2/.claude/skills/memory-manager.md +28 -2
- package/template-v2/.mcp.json.example +2 -1
package/bin/index.js
CHANGED
|
@@ -415,7 +415,7 @@ async function main() {
|
|
|
415
415
|
|
|
416
416
|
mcpConfig.mcpServers['claudia-memory'] = {
|
|
417
417
|
command: pythonCmd,
|
|
418
|
-
args: ['-m', 'claudia_memory
|
|
418
|
+
args: ['-m', 'claudia_memory', '--project-dir', '${workspaceFolder}'],
|
|
419
419
|
_description: 'Claudia memory system with vector search'
|
|
420
420
|
};
|
|
421
421
|
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2));
|
|
@@ -13,6 +13,7 @@ import json
|
|
|
13
13
|
import logging
|
|
14
14
|
import os
|
|
15
15
|
import sqlite3
|
|
16
|
+
import sys
|
|
16
17
|
import threading
|
|
17
18
|
from contextlib import contextmanager
|
|
18
19
|
from datetime import datetime
|
|
@@ -62,18 +63,34 @@ class Database:
|
|
|
62
63
|
except ImportError:
|
|
63
64
|
logger.debug("sqlite_vec package not installed")
|
|
64
65
|
except Exception as e:
|
|
65
|
-
logger.
|
|
66
|
+
logger.warning(f"sqlite_vec package installed but load() failed: {e}")
|
|
66
67
|
|
|
67
68
|
# Method 2: Try native extension loading (for systems with pre-installed sqlite-vec)
|
|
68
69
|
if not loaded:
|
|
69
70
|
try:
|
|
70
71
|
conn.enable_load_extension(True)
|
|
71
|
-
sqlite_vec_paths = [
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
sqlite_vec_paths = ["vec0"] # System-wide
|
|
73
|
+
|
|
74
|
+
if sys.platform == "win32":
|
|
75
|
+
# Try to find vec0.dll in the sqlite-vec package directory
|
|
76
|
+
try:
|
|
77
|
+
import sqlite_vec as _sv
|
|
78
|
+
pkg_dir = Path(_sv.__file__).parent
|
|
79
|
+
for dll in pkg_dir.rglob("vec0*"):
|
|
80
|
+
if dll.suffix in (".dll", ".so"):
|
|
81
|
+
sqlite_vec_paths.append(str(dll.with_suffix("")))
|
|
82
|
+
except ImportError:
|
|
83
|
+
pass
|
|
84
|
+
sqlite_vec_paths.extend([
|
|
85
|
+
str(Path(sys.executable).parent / "DLLs" / "vec0"),
|
|
86
|
+
str(Path.home() / ".local" / "lib" / "sqlite-vec" / "vec0"),
|
|
87
|
+
])
|
|
88
|
+
else:
|
|
89
|
+
sqlite_vec_paths.extend([
|
|
90
|
+
"/usr/local/lib/sqlite-vec/vec0",
|
|
91
|
+
"/opt/homebrew/lib/sqlite-vec/vec0",
|
|
92
|
+
str(Path.home() / ".local" / "lib" / "sqlite-vec" / "vec0"),
|
|
93
|
+
])
|
|
77
94
|
|
|
78
95
|
for path in sqlite_vec_paths:
|
|
79
96
|
try:
|
|
@@ -92,10 +109,18 @@ class Database:
|
|
|
92
109
|
logger.debug(f"Extension loading failed: {e}")
|
|
93
110
|
|
|
94
111
|
if not loaded:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
if sys.platform == "win32":
|
|
113
|
+
logger.warning(
|
|
114
|
+
"sqlite-vec not available. Vector search will be disabled. "
|
|
115
|
+
"Install with: pip install sqlite-vec "
|
|
116
|
+
"If already installed but failing, ensure your Python and "
|
|
117
|
+
"sqlite-vec architectures match (both 64-bit or both 32-bit)."
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
logger.warning(
|
|
121
|
+
"sqlite-vec not available. Vector search will be disabled. "
|
|
122
|
+
"Install with: pip install sqlite-vec"
|
|
123
|
+
)
|
|
99
124
|
|
|
100
125
|
self._local.connection = conn
|
|
101
126
|
|
|
@@ -66,9 +66,33 @@ from ..services.remember import (
|
|
|
66
66
|
remember_fact,
|
|
67
67
|
remember_message,
|
|
68
68
|
)
|
|
69
|
+
from ..embeddings import get_embedding_service
|
|
69
70
|
|
|
70
71
|
logger = logging.getLogger(__name__)
|
|
71
72
|
|
|
73
|
+
|
|
74
|
+
def _coerce_arg(arguments: Dict[str, Any], key: str, expected_type: type = list) -> None:
|
|
75
|
+
"""Coerce a tool argument from JSON string to expected type in-place.
|
|
76
|
+
|
|
77
|
+
LLMs sometimes serialize array parameters as JSON strings instead of
|
|
78
|
+
native arrays. This transparently parses them back so handler code
|
|
79
|
+
can assume native types.
|
|
80
|
+
"""
|
|
81
|
+
value = arguments.get(key)
|
|
82
|
+
if isinstance(value, str):
|
|
83
|
+
try:
|
|
84
|
+
parsed = json.loads(value)
|
|
85
|
+
if isinstance(parsed, expected_type):
|
|
86
|
+
arguments[key] = parsed
|
|
87
|
+
else:
|
|
88
|
+
logger.warning(
|
|
89
|
+
f"Coercion: '{key}' parsed to {type(parsed).__name__}, "
|
|
90
|
+
f"expected {expected_type.__name__}"
|
|
91
|
+
)
|
|
92
|
+
except (json.JSONDecodeError, TypeError):
|
|
93
|
+
logger.warning(f"Could not parse '{key}' as JSON: {value[:100]}")
|
|
94
|
+
|
|
95
|
+
|
|
72
96
|
# Initialize the MCP server
|
|
73
97
|
server = Server("claudia-memory")
|
|
74
98
|
|
|
@@ -94,7 +118,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
94
118
|
"default": "fact",
|
|
95
119
|
},
|
|
96
120
|
"about": {
|
|
97
|
-
"type": "array",
|
|
121
|
+
"type": ["array", "string"],
|
|
98
122
|
"items": {"type": "string"},
|
|
99
123
|
"description": "Entity names this memory relates to (people, projects, etc.)",
|
|
100
124
|
},
|
|
@@ -139,7 +163,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
139
163
|
"default": 10,
|
|
140
164
|
},
|
|
141
165
|
"types": {
|
|
142
|
-
"type": "array",
|
|
166
|
+
"type": ["array", "string"],
|
|
143
167
|
"items": {"type": "string"},
|
|
144
168
|
"description": "Filter by memory types (fact, preference, observation, learning, commitment)",
|
|
145
169
|
},
|
|
@@ -153,7 +177,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
153
177
|
"default": False,
|
|
154
178
|
},
|
|
155
179
|
"ids": {
|
|
156
|
-
"type": "array",
|
|
180
|
+
"type": ["array", "string"],
|
|
157
181
|
"items": {"type": "integer"},
|
|
158
182
|
"description": "Fetch specific memories by ID (skips search). Use after a compact search to get full content.",
|
|
159
183
|
},
|
|
@@ -232,7 +256,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
232
256
|
"default": 5,
|
|
233
257
|
},
|
|
234
258
|
"types": {
|
|
235
|
-
"type": "array",
|
|
259
|
+
"type": ["array", "string"],
|
|
236
260
|
"items": {"type": "string"},
|
|
237
261
|
"description": "Filter by type (reminder, suggestion, warning, insight)",
|
|
238
262
|
},
|
|
@@ -268,7 +292,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
268
292
|
"description": "Description of the entity",
|
|
269
293
|
},
|
|
270
294
|
"aliases": {
|
|
271
|
-
"type": "array",
|
|
295
|
+
"type": ["array", "string"],
|
|
272
296
|
"items": {"type": "string"},
|
|
273
297
|
"description": "Alternative names or spellings",
|
|
274
298
|
},
|
|
@@ -287,7 +311,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
287
311
|
"description": "Search query",
|
|
288
312
|
},
|
|
289
313
|
"types": {
|
|
290
|
-
"type": "array",
|
|
314
|
+
"type": ["array", "string"],
|
|
291
315
|
"items": {"type": "string"},
|
|
292
316
|
"description": "Filter by entity types",
|
|
293
317
|
},
|
|
@@ -354,7 +378,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
354
378
|
),
|
|
355
379
|
},
|
|
356
380
|
"facts": {
|
|
357
|
-
"type": "array",
|
|
381
|
+
"type": ["array", "string"],
|
|
358
382
|
"items": {
|
|
359
383
|
"type": "object",
|
|
360
384
|
"properties": {
|
|
@@ -388,7 +412,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
388
412
|
"description": "Structured facts, preferences, observations, learnings extracted from the session",
|
|
389
413
|
},
|
|
390
414
|
"commitments": {
|
|
391
|
-
"type": "array",
|
|
415
|
+
"type": ["array", "string"],
|
|
392
416
|
"items": {
|
|
393
417
|
"type": "object",
|
|
394
418
|
"properties": {
|
|
@@ -416,7 +440,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
416
440
|
"description": "Commitments or promises made during the session",
|
|
417
441
|
},
|
|
418
442
|
"entities": {
|
|
419
|
-
"type": "array",
|
|
443
|
+
"type": ["array", "string"],
|
|
420
444
|
"items": {
|
|
421
445
|
"type": "object",
|
|
422
446
|
"properties": {
|
|
@@ -437,7 +461,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
437
461
|
"description": "New or updated entities mentioned during the session",
|
|
438
462
|
},
|
|
439
463
|
"relationships": {
|
|
440
|
-
"type": "array",
|
|
464
|
+
"type": ["array", "string"],
|
|
441
465
|
"items": {
|
|
442
466
|
"type": "object",
|
|
443
467
|
"properties": {
|
|
@@ -451,12 +475,12 @@ async def list_tools() -> ListToolsResult:
|
|
|
451
475
|
"description": "Relationships between entities observed during the session",
|
|
452
476
|
},
|
|
453
477
|
"key_topics": {
|
|
454
|
-
"type": "array",
|
|
478
|
+
"type": ["array", "string"],
|
|
455
479
|
"items": {"type": "string"},
|
|
456
480
|
"description": "Main topics discussed in the session",
|
|
457
481
|
},
|
|
458
482
|
"reflections": {
|
|
459
|
-
"type": "array",
|
|
483
|
+
"type": ["array", "string"],
|
|
460
484
|
"items": {
|
|
461
485
|
"type": "object",
|
|
462
486
|
"properties": {
|
|
@@ -492,7 +516,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
492
516
|
),
|
|
493
517
|
},
|
|
494
518
|
},
|
|
495
|
-
"required": ["
|
|
519
|
+
"required": ["narrative"],
|
|
496
520
|
},
|
|
497
521
|
),
|
|
498
522
|
Tool(
|
|
@@ -524,7 +548,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
524
548
|
"description": "Semantic search query (optional). If omitted, returns recent high-importance reflections.",
|
|
525
549
|
},
|
|
526
550
|
"types": {
|
|
527
|
-
"type": "array",
|
|
551
|
+
"type": ["array", "string"],
|
|
528
552
|
"items": {
|
|
529
553
|
"type": "string",
|
|
530
554
|
"enum": ["observation", "pattern", "learning", "question"],
|
|
@@ -570,7 +594,7 @@ async def list_tools() -> ListToolsResult:
|
|
|
570
594
|
"type": "object",
|
|
571
595
|
"properties": {
|
|
572
596
|
"operations": {
|
|
573
|
-
"type": "array",
|
|
597
|
+
"type": ["array", "string"],
|
|
574
598
|
"description": "Array of operations to execute in order",
|
|
575
599
|
"items": {
|
|
576
600
|
"type": "object",
|
|
@@ -775,12 +799,12 @@ async def list_tools() -> ListToolsResult:
|
|
|
775
799
|
"description": "Brief summary of the document",
|
|
776
800
|
},
|
|
777
801
|
"about": {
|
|
778
|
-
"type": "array",
|
|
802
|
+
"type": ["array", "string"],
|
|
779
803
|
"items": {"type": "string"},
|
|
780
804
|
"description": "Entity names this document relates to",
|
|
781
805
|
},
|
|
782
806
|
"memory_ids": {
|
|
783
|
-
"type": "array",
|
|
807
|
+
"type": ["array", "string"],
|
|
784
808
|
"items": {"type": "integer"},
|
|
785
809
|
"description": "Memory IDs to link as sourced from this document",
|
|
786
810
|
},
|
|
@@ -1158,6 +1182,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1158
1182
|
"""Handle tool calls"""
|
|
1159
1183
|
try:
|
|
1160
1184
|
if name == "memory.remember":
|
|
1185
|
+
_coerce_arg(arguments, "about")
|
|
1161
1186
|
memory_id = remember_fact(
|
|
1162
1187
|
content=arguments["content"],
|
|
1163
1188
|
memory_type=arguments.get("type", "fact"),
|
|
@@ -1187,6 +1212,8 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1187
1212
|
)
|
|
1188
1213
|
|
|
1189
1214
|
elif name == "memory.recall":
|
|
1215
|
+
_coerce_arg(arguments, "types")
|
|
1216
|
+
_coerce_arg(arguments, "ids")
|
|
1190
1217
|
# Direct fetch by IDs (skip search)
|
|
1191
1218
|
if "ids" in arguments and arguments["ids"]:
|
|
1192
1219
|
results = fetch_by_ids(arguments["ids"])
|
|
@@ -1339,6 +1366,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1339
1366
|
)
|
|
1340
1367
|
|
|
1341
1368
|
elif name == "memory.predictions":
|
|
1369
|
+
_coerce_arg(arguments, "types")
|
|
1342
1370
|
predictions = get_predictions(
|
|
1343
1371
|
limit=arguments.get("limit", 5),
|
|
1344
1372
|
prediction_types=arguments.get("types"),
|
|
@@ -1364,6 +1392,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1364
1392
|
)
|
|
1365
1393
|
|
|
1366
1394
|
elif name == "memory.entity":
|
|
1395
|
+
_coerce_arg(arguments, "aliases")
|
|
1367
1396
|
entity_id = remember_entity(
|
|
1368
1397
|
name=arguments["name"],
|
|
1369
1398
|
entity_type=arguments.get("type", "person"),
|
|
@@ -1380,6 +1409,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1380
1409
|
)
|
|
1381
1410
|
|
|
1382
1411
|
elif name == "memory.search_entities":
|
|
1412
|
+
_coerce_arg(arguments, "types")
|
|
1383
1413
|
results = search_entities(
|
|
1384
1414
|
query=arguments["query"],
|
|
1385
1415
|
entity_types=arguments.get("types"),
|
|
@@ -1426,19 +1456,30 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1426
1456
|
)
|
|
1427
1457
|
|
|
1428
1458
|
elif name == "memory.end_session":
|
|
1429
|
-
|
|
1459
|
+
# Coerce all array fields (LLMs may send JSON strings)
|
|
1460
|
+
for field in ("facts", "commitments", "entities", "relationships", "key_topics", "reflections"):
|
|
1461
|
+
_coerce_arg(arguments, field)
|
|
1430
1462
|
|
|
1431
|
-
#
|
|
1463
|
+
# Handle missing or invalid episode_id: auto-create
|
|
1464
|
+
episode_id = arguments.get("episode_id")
|
|
1432
1465
|
svc = get_remember_service()
|
|
1433
|
-
|
|
1434
|
-
if not episode:
|
|
1466
|
+
if episode_id is None:
|
|
1435
1467
|
from datetime import datetime
|
|
1436
|
-
|
|
1468
|
+
episode_id = svc.db.insert("episodes", {
|
|
1437
1469
|
"started_at": datetime.utcnow().isoformat(),
|
|
1438
|
-
"source":
|
|
1470
|
+
"source": "claude_code",
|
|
1439
1471
|
})
|
|
1440
|
-
logger.info(f"Auto-created episode {
|
|
1441
|
-
|
|
1472
|
+
logger.info(f"Auto-created episode {episode_id} (no episode_id provided)")
|
|
1473
|
+
else:
|
|
1474
|
+
episode = svc.db.get_one("episodes", where="id = ?", where_params=(episode_id,))
|
|
1475
|
+
if not episode:
|
|
1476
|
+
from datetime import datetime
|
|
1477
|
+
new_id = svc.db.insert("episodes", {
|
|
1478
|
+
"started_at": datetime.utcnow().isoformat(),
|
|
1479
|
+
"source": arguments.get("source", "claude_code"),
|
|
1480
|
+
})
|
|
1481
|
+
logger.info(f"Auto-created episode {new_id} (requested {episode_id} did not exist)")
|
|
1482
|
+
episode_id = new_id
|
|
1442
1483
|
|
|
1443
1484
|
result = end_session(
|
|
1444
1485
|
episode_id=episode_id,
|
|
@@ -1489,6 +1530,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1489
1530
|
)
|
|
1490
1531
|
|
|
1491
1532
|
elif name == "memory.reflections":
|
|
1533
|
+
_coerce_arg(arguments, "types")
|
|
1492
1534
|
action = arguments.get("action", "get")
|
|
1493
1535
|
limit = arguments.get("limit", 10)
|
|
1494
1536
|
types = arguments.get("types")
|
|
@@ -1578,7 +1620,35 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1578
1620
|
)
|
|
1579
1621
|
|
|
1580
1622
|
elif name == "memory.batch":
|
|
1623
|
+
_coerce_arg(arguments, "operations")
|
|
1581
1624
|
operations = arguments.get("operations", [])
|
|
1625
|
+
|
|
1626
|
+
# --- Pass 1: Collect all texts that need embeddings ---
|
|
1627
|
+
embed_tasks = [] # list of (index, text) for parallel embedding
|
|
1628
|
+
for i, op in enumerate(operations):
|
|
1629
|
+
op_type = op.get("op")
|
|
1630
|
+
if op_type == "remember":
|
|
1631
|
+
embed_tasks.append((i, op["content"]))
|
|
1632
|
+
elif op_type == "entity":
|
|
1633
|
+
# Only new entities need embeddings; collect optimistically
|
|
1634
|
+
embed_text = f"{op['name']}. {op.get('description') or ''}"
|
|
1635
|
+
embed_tasks.append((i, embed_text))
|
|
1636
|
+
|
|
1637
|
+
# --- Parallel embedding pass ---
|
|
1638
|
+
embeddings_map = {} # index -> embedding
|
|
1639
|
+
if embed_tasks:
|
|
1640
|
+
try:
|
|
1641
|
+
emb_svc = get_embedding_service()
|
|
1642
|
+
texts = [text for _, text in embed_tasks]
|
|
1643
|
+
all_embeddings = await emb_svc.embed_batch(texts)
|
|
1644
|
+
for (idx, _), emb in zip(embed_tasks, all_embeddings):
|
|
1645
|
+
if emb is not None:
|
|
1646
|
+
embeddings_map[idx] = emb
|
|
1647
|
+
except Exception as e:
|
|
1648
|
+
logger.warning(f"Batch parallel embedding failed, falling back to per-op: {e}")
|
|
1649
|
+
# embeddings_map stays empty; remember_fact/entity will embed individually
|
|
1650
|
+
|
|
1651
|
+
# --- Pass 2: Execute operations with pre-computed embeddings ---
|
|
1582
1652
|
results = []
|
|
1583
1653
|
for i, op in enumerate(operations):
|
|
1584
1654
|
op_type = op.get("op")
|
|
@@ -1590,6 +1660,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1590
1660
|
entity_type=op.get("type", "person"),
|
|
1591
1661
|
description=op.get("description"),
|
|
1592
1662
|
aliases=op.get("aliases"),
|
|
1663
|
+
_precomputed_embedding=embeddings_map.get(i),
|
|
1593
1664
|
)
|
|
1594
1665
|
op_result["success"] = True
|
|
1595
1666
|
op_result["entity_id"] = entity_id
|
|
@@ -1601,6 +1672,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1601
1672
|
importance=op.get("importance", 1.0),
|
|
1602
1673
|
source=op.get("source"),
|
|
1603
1674
|
source_context=op.get("source_context"),
|
|
1675
|
+
_precomputed_embedding=embeddings_map.get(i),
|
|
1604
1676
|
)
|
|
1605
1677
|
op_result["success"] = True
|
|
1606
1678
|
op_result["memory_id"] = memory_id
|
|
@@ -1716,6 +1788,8 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1716
1788
|
)
|
|
1717
1789
|
|
|
1718
1790
|
elif name == "memory.file":
|
|
1791
|
+
_coerce_arg(arguments, "about")
|
|
1792
|
+
_coerce_arg(arguments, "memory_ids")
|
|
1719
1793
|
doc_svc = get_document_service()
|
|
1720
1794
|
result = doc_svc.file_document_from_text(
|
|
1721
1795
|
content=arguments["content"],
|
|
@@ -141,6 +141,7 @@ class RememberService:
|
|
|
141
141
|
source_context: Optional[str] = None,
|
|
142
142
|
metadata: Optional[Dict] = None,
|
|
143
143
|
origin_type: Optional[str] = None,
|
|
144
|
+
_precomputed_embedding: Optional[List[float]] = None,
|
|
144
145
|
) -> Optional[int]:
|
|
145
146
|
"""
|
|
146
147
|
Store a discrete fact/memory.
|
|
@@ -217,8 +218,8 @@ class RememberService:
|
|
|
217
218
|
|
|
218
219
|
memory_id = self.db.insert("memories", insert_data)
|
|
219
220
|
|
|
220
|
-
#
|
|
221
|
-
embedding = embed_sync(content)
|
|
221
|
+
# Store embedding (use precomputed if available, otherwise generate)
|
|
222
|
+
embedding = _precomputed_embedding or embed_sync(content)
|
|
222
223
|
if embedding:
|
|
223
224
|
try:
|
|
224
225
|
self.db.execute(
|
|
@@ -263,6 +264,7 @@ class RememberService:
|
|
|
263
264
|
description: Optional[str] = None,
|
|
264
265
|
aliases: Optional[List[str]] = None,
|
|
265
266
|
metadata: Optional[Dict] = None,
|
|
267
|
+
_precomputed_embedding: Optional[List[float]] = None,
|
|
266
268
|
) -> int:
|
|
267
269
|
"""
|
|
268
270
|
Create or update an entity.
|
|
@@ -326,9 +328,9 @@ class RememberService:
|
|
|
326
328
|
},
|
|
327
329
|
)
|
|
328
330
|
|
|
329
|
-
#
|
|
331
|
+
# Store embedding (use precomputed if available, otherwise generate)
|
|
330
332
|
embed_text = f"{name}. {description or ''}"
|
|
331
|
-
embedding = embed_sync(embed_text)
|
|
333
|
+
embedding = _precomputed_embedding or embed_sync(embed_text)
|
|
332
334
|
if embedding:
|
|
333
335
|
try:
|
|
334
336
|
self.db.execute(
|
|
@@ -1512,12 +1514,12 @@ def remember_message(content: str, role: str = "user", **kwargs) -> Dict[str, An
|
|
|
1512
1514
|
|
|
1513
1515
|
|
|
1514
1516
|
def remember_fact(content: str, **kwargs) -> Optional[int]:
|
|
1515
|
-
"""Store a discrete fact"""
|
|
1517
|
+
"""Store a discrete fact. Pass _precomputed_embedding to skip Ollama call."""
|
|
1516
1518
|
return get_remember_service().remember_fact(content, **kwargs)
|
|
1517
1519
|
|
|
1518
1520
|
|
|
1519
1521
|
def remember_entity(name: str, **kwargs) -> int:
|
|
1520
|
-
"""Create or update an entity"""
|
|
1522
|
+
"""Create or update an entity. Pass _precomputed_embedding to skip Ollama call."""
|
|
1521
1523
|
return get_remember_service().remember_entity(name, **kwargs)
|
|
1522
1524
|
|
|
1523
1525
|
|