claude-memory-agent 2.1.0 → 2.2.0
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/cli.js +11 -1
- package/bin/lib/banner.js +39 -0
- package/bin/lib/environment.js +166 -0
- package/bin/lib/installer.js +291 -0
- package/bin/lib/models.js +95 -0
- package/bin/lib/steps/advanced.js +101 -0
- package/bin/lib/steps/confirm.js +87 -0
- package/bin/lib/steps/model.js +57 -0
- package/bin/lib/steps/provider.js +65 -0
- package/bin/lib/steps/scope.js +59 -0
- package/bin/lib/steps/server.js +74 -0
- package/bin/lib/ui.js +75 -0
- package/bin/onboarding.js +164 -0
- package/bin/postinstall.js +22 -257
- package/config.py +103 -4
- package/dashboard.html +697 -27
- package/hooks/extract_memories.py +439 -0
- package/hooks/pre_compact_hook.py +76 -0
- package/hooks/session_end_hook.py +149 -0
- package/hooks/stop_hook.py +372 -0
- package/install.py +85 -32
- package/main.py +1636 -892
- package/mcp_server.py +451 -0
- package/package.json +14 -3
- package/requirements.txt +12 -8
- package/services/adaptive_ranker.py +272 -0
- package/services/agent_catalog.json +153 -0
- package/services/agent_registry.py +245 -730
- package/services/claude_md_sync.py +320 -4
- package/services/consolidation.py +417 -0
- package/services/database.py +586 -105
- package/services/embedding_pipeline.py +262 -0
- package/services/embeddings.py +493 -85
- package/services/memory_decay.py +408 -0
- package/services/native_memory_paths.py +86 -0
- package/services/native_memory_sync.py +496 -0
- package/services/response_manager.py +183 -0
- package/services/terminal_ui.py +199 -0
- package/services/tier_manager.py +235 -0
- package/services/websocket.py +26 -6
- package/skills/search.py +136 -61
- package/skills/session_review.py +210 -23
- package/skills/store.py +125 -18
- package/terminal_dashboard.py +474 -0
- package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
- package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
- package/hooks/__pycache__/grounding-hook.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/services/__pycache__/__init__.cpython-312.pyc +0 -0
- package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
- package/services/__pycache__/auth.cpython-312.pyc +0 -0
- package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
- package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
- package/services/__pycache__/confidence.cpython-312.pyc +0 -0
- package/services/__pycache__/curator.cpython-312.pyc +0 -0
- package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
- package/services/__pycache__/database.cpython-312.pyc +0 -0
- package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
- package/services/__pycache__/insights.cpython-312.pyc +0 -0
- package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
- package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
- package/services/__pycache__/timeline.cpython-312.pyc +0 -0
- package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
- package/services/__pycache__/websocket.cpython-312.pyc +0 -0
- package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/__pycache__/admin.cpython-312.pyc +0 -0
- package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
- package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
- package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/skills/__pycache__/confidence_tracker.cpython-312.pyc +0 -0
- package/skills/__pycache__/context.cpython-312.pyc +0 -0
- package/skills/__pycache__/curator.cpython-312.pyc +0 -0
- package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
- package/skills/__pycache__/insights.cpython-312.pyc +0 -0
- package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
- package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
- package/skills/__pycache__/search.cpython-312.pyc +0 -0
- package/skills/__pycache__/session_review.cpython-312.pyc +0 -0
- package/skills/__pycache__/state.cpython-312.pyc +0 -0
- package/skills/__pycache__/store.cpython-312.pyc +0 -0
- package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
- package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
- package/skills/__pycache__/verification.cpython-312.pyc +0 -0
- package/test_automation.py +0 -221
- package/test_complete.py +0 -338
- package/test_full.py +0 -322
- package/verify_db.py +0 -134
package/main.py
CHANGED
|
@@ -39,6 +39,7 @@ from services.database import (
|
|
|
39
39
|
)
|
|
40
40
|
from services.embeddings import EmbeddingService
|
|
41
41
|
from services.auth import get_auth_service, AuthService
|
|
42
|
+
from services.response_manager import fit_response
|
|
42
43
|
|
|
43
44
|
# Original memory skills
|
|
44
45
|
from skills.store import store_memory, store_project, store_pattern
|
|
@@ -110,14 +111,83 @@ from services.claude_md_sync import get_claude_md_sync
|
|
|
110
111
|
# Agent registry for dashboard
|
|
111
112
|
from services.agent_registry import (
|
|
112
113
|
AVAILABLE_AGENTS, AVAILABLE_MCPS, AVAILABLE_HOOKS,
|
|
113
|
-
AGENT_CATEGORIES, get_agents_by_category, get_agent_by_id
|
|
114
|
+
AGENT_CATEGORIES, get_agents_by_category, get_agent_by_id,
|
|
115
|
+
load_configured_hooks, load_configured_mcps
|
|
114
116
|
)
|
|
115
117
|
|
|
116
118
|
load_dotenv()
|
|
117
119
|
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# Simple metrics tracker for search/store operations
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
class OperationMetrics:
|
|
124
|
+
"""Lightweight in-memory metrics for search and store operations.
|
|
125
|
+
|
|
126
|
+
Tracks hit rates, result counts, and operation frequencies to answer:
|
|
127
|
+
"Are stored memories actually being found?"
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(self):
|
|
131
|
+
self.search_total = 0
|
|
132
|
+
self.search_hits = 0 # searches that returned >= 1 result
|
|
133
|
+
self.search_empty = 0 # searches that returned 0 results
|
|
134
|
+
self.search_result_counts: List[int] = [] # rolling window of result counts
|
|
135
|
+
self.store_total = 0
|
|
136
|
+
self.store_merged = 0 # dedup merges
|
|
137
|
+
self.store_without_embedding = 0
|
|
138
|
+
self._max_history = 1000 # keep last 1000 result counts
|
|
139
|
+
|
|
140
|
+
def record_search(self, result_count: int):
|
|
141
|
+
self.search_total += 1
|
|
142
|
+
if result_count > 0:
|
|
143
|
+
self.search_hits += 1
|
|
144
|
+
else:
|
|
145
|
+
self.search_empty += 1
|
|
146
|
+
self.search_result_counts.append(result_count)
|
|
147
|
+
if len(self.search_result_counts) > self._max_history:
|
|
148
|
+
self.search_result_counts = self.search_result_counts[-self._max_history:]
|
|
149
|
+
|
|
150
|
+
def record_store(self, merged: bool = False, has_embedding: bool = True):
|
|
151
|
+
self.store_total += 1
|
|
152
|
+
if merged:
|
|
153
|
+
self.store_merged += 1
|
|
154
|
+
if not has_embedding:
|
|
155
|
+
self.store_without_embedding += 1
|
|
156
|
+
|
|
157
|
+
def to_dict(self) -> dict:
|
|
158
|
+
avg_results = (
|
|
159
|
+
sum(self.search_result_counts) / len(self.search_result_counts)
|
|
160
|
+
if self.search_result_counts else 0
|
|
161
|
+
)
|
|
162
|
+
hit_rate = (
|
|
163
|
+
self.search_hits / self.search_total
|
|
164
|
+
if self.search_total > 0 else 0
|
|
165
|
+
)
|
|
166
|
+
return {
|
|
167
|
+
"search": {
|
|
168
|
+
"total": self.search_total,
|
|
169
|
+
"hits": self.search_hits,
|
|
170
|
+
"empty": self.search_empty,
|
|
171
|
+
"hit_rate": round(hit_rate, 3),
|
|
172
|
+
"avg_result_count": round(avg_results, 1)
|
|
173
|
+
},
|
|
174
|
+
"store": {
|
|
175
|
+
"total": self.store_total,
|
|
176
|
+
"merged": self.store_merged,
|
|
177
|
+
"without_embedding": self.store_without_embedding
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
metrics = OperationMetrics()
|
|
183
|
+
|
|
118
184
|
# Initialize services
|
|
119
185
|
db = DatabaseService()
|
|
120
|
-
|
|
186
|
+
from config import config as _cfg
|
|
187
|
+
embeddings = EmbeddingService(
|
|
188
|
+
provider_type=_cfg.EMBEDDING_PROVIDER,
|
|
189
|
+
model=_cfg.EMBEDDING_MODEL,
|
|
190
|
+
)
|
|
121
191
|
|
|
122
192
|
# Retry queue (imported lazily to avoid circular imports)
|
|
123
193
|
retry_queue = None
|
|
@@ -182,19 +252,84 @@ async def lifespan(app: FastAPI):
|
|
|
182
252
|
run_curator_scheduler(db, embeddings, interval_hours=curator_interval)
|
|
183
253
|
)
|
|
184
254
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
255
|
+
# Initialize embedding pipeline with LRU cache
|
|
256
|
+
from services.embedding_pipeline import get_embedding_pipeline
|
|
257
|
+
pipeline = get_embedding_pipeline(embeddings, db)
|
|
258
|
+
|
|
259
|
+
# Start embedding pre-computation background loop
|
|
260
|
+
from config import config as app_config
|
|
188
261
|
|
|
189
|
-
|
|
262
|
+
async def precompute_loop():
|
|
263
|
+
"""Background: generate embeddings for memories missing them."""
|
|
264
|
+
interval = app_config.EMBEDDING_PRECOMPUTE_INTERVAL
|
|
265
|
+
while True:
|
|
266
|
+
await asyncio.sleep(interval)
|
|
267
|
+
try:
|
|
268
|
+
result = await pipeline.precompute_missing_embeddings()
|
|
269
|
+
if result.get('generated', 0) > 0:
|
|
270
|
+
logger.info(f"Pre-computed {result['generated']} embeddings")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.debug(f"Precompute loop error: {e}")
|
|
273
|
+
|
|
274
|
+
precompute_task = asyncio.create_task(precompute_loop())
|
|
275
|
+
|
|
276
|
+
# Start consolidation background loop
|
|
277
|
+
async def consolidation_loop():
|
|
278
|
+
"""Background: consolidate similar warm-tier memories."""
|
|
279
|
+
interval_hours = app_config.CONSOLIDATION_INTERVAL_HOURS
|
|
280
|
+
while True:
|
|
281
|
+
await asyncio.sleep(interval_hours * 3600)
|
|
282
|
+
try:
|
|
283
|
+
from services.consolidation import ConsolidationService
|
|
284
|
+
consolidator = ConsolidationService(db, embeddings)
|
|
285
|
+
result = await consolidator.run_consolidation()
|
|
286
|
+
if result.get('consolidated', 0) > 0:
|
|
287
|
+
logger.info(
|
|
288
|
+
f"Consolidated {result['consolidated']} groups "
|
|
289
|
+
f"({result['memories_archived']} memories archived)"
|
|
290
|
+
)
|
|
291
|
+
except Exception as e:
|
|
292
|
+
logger.debug(f"Consolidation loop error: {e}")
|
|
293
|
+
|
|
294
|
+
consolidation_task = asyncio.create_task(consolidation_loop())
|
|
295
|
+
|
|
296
|
+
# Collect DB stats for splash
|
|
190
297
|
auth_stats = auth_service.get_stats()
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
298
|
+
db_stats = None
|
|
299
|
+
try:
|
|
300
|
+
db_stats = await db.get_stats()
|
|
301
|
+
except Exception:
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
# Rich terminal splash screen
|
|
305
|
+
try:
|
|
306
|
+
from services.terminal_ui import print_splash, setup_rich_logging
|
|
307
|
+
|
|
308
|
+
print_splash(
|
|
309
|
+
version="2.4.0",
|
|
310
|
+
port=int(os.getenv("PORT", 8102)),
|
|
311
|
+
auth_enabled=auth_stats.get("enabled", False),
|
|
312
|
+
auth_keys=auth_stats.get("active_keys", 0),
|
|
313
|
+
queue_depth=retry_queue.get_queue_depth(),
|
|
314
|
+
curator_interval=curator_interval,
|
|
315
|
+
embedding_cache_size=app_config.EMBEDDING_CACHE_SIZE,
|
|
316
|
+
precompute_interval=app_config.EMBEDDING_PRECOMPUTE_INTERVAL,
|
|
317
|
+
consolidation_threshold=app_config.CONSOLIDATION_THRESHOLD,
|
|
318
|
+
consolidation_interval=app_config.CONSOLIDATION_INTERVAL_HOURS,
|
|
319
|
+
db_stats=db_stats,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Install rich logging handler for prettier output
|
|
323
|
+
rich_handler = setup_rich_logging(app_config.LOG_LEVEL)
|
|
324
|
+
logging.root.handlers = [rich_handler]
|
|
325
|
+
|
|
326
|
+
except ImportError:
|
|
327
|
+
# Fallback to plain output if rich unavailable
|
|
328
|
+
print(f"Memory Agent v2.4.0 (CLaRa) started on port {os.getenv('PORT', 8102)}")
|
|
329
|
+
if auth_stats.get("enabled"):
|
|
330
|
+
print(f"Authentication: ENABLED ({auth_stats.get('active_keys', 0)} active keys)")
|
|
331
|
+
else:
|
|
332
|
+
print("Authentication: DISABLED")
|
|
198
333
|
|
|
199
334
|
yield
|
|
200
335
|
|
|
@@ -202,14 +337,13 @@ async def lifespan(app: FastAPI):
|
|
|
202
337
|
retry_queue.stop_processing()
|
|
203
338
|
queue_task.cancel()
|
|
204
339
|
curator_task.cancel()
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
pass
|
|
340
|
+
precompute_task.cancel()
|
|
341
|
+
consolidation_task.cancel()
|
|
342
|
+
for task in [queue_task, curator_task, precompute_task, consolidation_task]:
|
|
343
|
+
try:
|
|
344
|
+
await task
|
|
345
|
+
except asyncio.CancelledError:
|
|
346
|
+
pass
|
|
213
347
|
retry_queue.close()
|
|
214
348
|
await db.disconnect()
|
|
215
349
|
|
|
@@ -217,7 +351,7 @@ async def lifespan(app: FastAPI):
|
|
|
217
351
|
app = FastAPI(
|
|
218
352
|
title="Claude Memory Agent",
|
|
219
353
|
description="Persistent semantic memory for Claude Code sessions with cross-project support",
|
|
220
|
-
version="2.
|
|
354
|
+
version="2.4.0",
|
|
221
355
|
lifespan=lifespan
|
|
222
356
|
)
|
|
223
357
|
|
|
@@ -358,7 +492,7 @@ async def handle_task_send(request: A2ARequest) -> JSONResponse:
|
|
|
358
492
|
"result": {
|
|
359
493
|
"id": task_id,
|
|
360
494
|
"status": {"state": "completed"},
|
|
361
|
-
"artifacts": [{"parts": [{"type": "text", "text":
|
|
495
|
+
"artifacts": [{"parts": [{"type": "text", "text": fit_response(result)}]}]
|
|
362
496
|
}
|
|
363
497
|
})
|
|
364
498
|
|
|
@@ -394,7 +528,7 @@ async def handle_task_get(request: A2ARequest) -> JSONResponse:
|
|
|
394
528
|
"result": {
|
|
395
529
|
"id": task_id,
|
|
396
530
|
"status": {"state": task["status"]},
|
|
397
|
-
"artifacts": [{"parts": [{"type": "text", "text":
|
|
531
|
+
"artifacts": [{"parts": [{"type": "text", "text": fit_response(task.get("result", {}))}]}] if task.get("result") else []
|
|
398
532
|
}
|
|
399
533
|
})
|
|
400
534
|
|
|
@@ -411,974 +545,1285 @@ async def handle_task_cancel(request: A2ARequest) -> JSONResponse:
|
|
|
411
545
|
})
|
|
412
546
|
|
|
413
547
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
"""Execute the specified skill with enhanced context support."""
|
|
421
|
-
# Debug to file
|
|
422
|
-
with open("c:/Users/moham/Desktop/Claude Memory/memory-agent/debug.log", "a") as f:
|
|
423
|
-
f.write(f"[SKILL DEBUG] execute_skill called with skill_id='{skill_id}'\n")
|
|
424
|
-
f.flush()
|
|
425
|
-
|
|
426
|
-
if skill_id == "store_memory":
|
|
427
|
-
result = await store_memory(
|
|
428
|
-
db=db,
|
|
429
|
-
embeddings=embeddings,
|
|
430
|
-
content=params.get("content", query),
|
|
431
|
-
memory_type=params.get("type", "chunk"),
|
|
432
|
-
metadata=params.get("metadata"),
|
|
433
|
-
session_id=session_id or params.get("session_id"),
|
|
434
|
-
# Project context
|
|
435
|
-
project_path=params.get("project_path"),
|
|
436
|
-
project_name=params.get("project_name"),
|
|
437
|
-
project_type=params.get("project_type"),
|
|
438
|
-
tech_stack=params.get("tech_stack"),
|
|
439
|
-
# Agent context
|
|
440
|
-
agent_type=params.get("agent_type"),
|
|
441
|
-
skill_used=params.get("skill_used"),
|
|
442
|
-
tools_used=params.get("tools_used"),
|
|
443
|
-
# Outcome
|
|
444
|
-
outcome=params.get("outcome"),
|
|
445
|
-
success=params.get("success"),
|
|
446
|
-
# Classification
|
|
447
|
-
tags=params.get("tags"),
|
|
448
|
-
importance=params.get("importance", 5),
|
|
449
|
-
confidence=params.get("confidence", 0.5),
|
|
450
|
-
# Outcome spectrum
|
|
451
|
-
outcome_status=params.get("outcome_status", "pending"),
|
|
452
|
-
fixed=params.get("fixed"),
|
|
453
|
-
did_not_fix=params.get("did_not_fix"),
|
|
454
|
-
caused=params.get("caused")
|
|
455
|
-
)
|
|
456
|
-
# Broadcast real-time update
|
|
457
|
-
print(f"[DEBUG] About to broadcast memory_stored event for memory_id={result.get('memory_id')}")
|
|
458
|
-
try:
|
|
459
|
-
await broadcast_event(
|
|
460
|
-
EventTypes.MEMORY_STORED,
|
|
461
|
-
{"memory_id": result.get("memory_id"), "type": params.get("type", "chunk")},
|
|
462
|
-
params.get("project_path")
|
|
463
|
-
)
|
|
464
|
-
print(f"[DEBUG] Broadcast completed successfully")
|
|
465
|
-
except Exception as e:
|
|
466
|
-
print(f"[DEBUG] Broadcast error: {e}")
|
|
467
|
-
return result
|
|
548
|
+
# ============================================================
|
|
549
|
+
# SKILL HANDLER FUNCTIONS
|
|
550
|
+
# ============================================================
|
|
551
|
+
# Each handler receives (query, params, session_id) and returns a dict.
|
|
552
|
+
# Grouped by category for maintainability.
|
|
553
|
+
# ============================================================
|
|
468
554
|
|
|
469
|
-
elif skill_id == "store_project":
|
|
470
|
-
return await store_project(
|
|
471
|
-
db=db,
|
|
472
|
-
path=params.get("path"),
|
|
473
|
-
name=params.get("name"),
|
|
474
|
-
project_type=params.get("project_type"),
|
|
475
|
-
tech_stack=params.get("tech_stack"),
|
|
476
|
-
conventions=params.get("conventions"),
|
|
477
|
-
preferences=params.get("preferences")
|
|
478
|
-
)
|
|
479
555
|
|
|
480
|
-
|
|
481
|
-
return await store_pattern(
|
|
482
|
-
db=db,
|
|
483
|
-
embeddings=embeddings,
|
|
484
|
-
name=params.get("name"),
|
|
485
|
-
solution=params.get("solution"),
|
|
486
|
-
problem_type=params.get("problem_type"),
|
|
487
|
-
tech_context=params.get("tech_context"),
|
|
488
|
-
metadata=params.get("metadata")
|
|
489
|
-
)
|
|
556
|
+
# --- Core Memory Skills ---
|
|
490
557
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
558
|
+
async def _handle_store_memory(query, params, session_id):
|
|
559
|
+
result = await store_memory(
|
|
560
|
+
db=db,
|
|
561
|
+
embeddings=embeddings,
|
|
562
|
+
content=params.get("content", query),
|
|
563
|
+
memory_type=params.get("type", "chunk"),
|
|
564
|
+
metadata=params.get("metadata"),
|
|
565
|
+
session_id=session_id or params.get("session_id"),
|
|
566
|
+
project_path=params.get("project_path"),
|
|
567
|
+
project_name=params.get("project_name"),
|
|
568
|
+
project_type=params.get("project_type"),
|
|
569
|
+
tech_stack=params.get("tech_stack"),
|
|
570
|
+
agent_type=params.get("agent_type"),
|
|
571
|
+
skill_used=params.get("skill_used"),
|
|
572
|
+
tools_used=params.get("tools_used"),
|
|
573
|
+
outcome=params.get("outcome"),
|
|
574
|
+
success=params.get("success"),
|
|
575
|
+
tags=params.get("tags"),
|
|
576
|
+
importance=params.get("importance", 5),
|
|
577
|
+
confidence=params.get("confidence", 0.5),
|
|
578
|
+
outcome_status=params.get("outcome_status", "pending"),
|
|
579
|
+
fixed=params.get("fixed"),
|
|
580
|
+
did_not_fix=params.get("did_not_fix"),
|
|
581
|
+
caused=params.get("caused")
|
|
582
|
+
)
|
|
583
|
+
metrics.record_store(
|
|
584
|
+
merged=result.get("action") == "merged",
|
|
585
|
+
has_embedding=result.get("has_embedding", True)
|
|
586
|
+
)
|
|
587
|
+
try:
|
|
588
|
+
await broadcast_event(
|
|
589
|
+
EventTypes.MEMORY_STORED,
|
|
590
|
+
{"memory_id": result.get("memory_id"), "type": params.get("type", "chunk")},
|
|
591
|
+
params.get("project_path")
|
|
499
592
|
)
|
|
593
|
+
except Exception as e:
|
|
594
|
+
logger.debug(f"Broadcast error: {e}")
|
|
595
|
+
return result
|
|
500
596
|
|
|
501
|
-
elif skill_id == "semantic_search":
|
|
502
|
-
return await semantic_search(
|
|
503
|
-
db=db,
|
|
504
|
-
embeddings=embeddings,
|
|
505
|
-
query=params.get("query", query),
|
|
506
|
-
limit=params.get("limit", 10),
|
|
507
|
-
memory_type=params.get("type"),
|
|
508
|
-
session_id=session_id or params.get("session_id"),
|
|
509
|
-
project_path=params.get("project_path"),
|
|
510
|
-
agent_type=params.get("agent_type"),
|
|
511
|
-
success_only=params.get("success_only", False),
|
|
512
|
-
threshold=params.get("threshold", 0.5),
|
|
513
|
-
# Outcome spectrum filters
|
|
514
|
-
include_failed=params.get("include_failed", False),
|
|
515
|
-
include_superseded=params.get("include_superseded", False),
|
|
516
|
-
include_unreliable=params.get("include_unreliable", False),
|
|
517
|
-
outcome_status=params.get("outcome_status")
|
|
518
|
-
)
|
|
519
597
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
)
|
|
598
|
+
async def _handle_store_project(query, params, session_id):
|
|
599
|
+
return await store_project(
|
|
600
|
+
db=db,
|
|
601
|
+
path=params.get("path"),
|
|
602
|
+
name=params.get("name"),
|
|
603
|
+
project_type=params.get("project_type"),
|
|
604
|
+
tech_stack=params.get("tech_stack"),
|
|
605
|
+
conventions=params.get("conventions"),
|
|
606
|
+
preferences=params.get("preferences")
|
|
607
|
+
)
|
|
529
608
|
|
|
530
|
-
elif skill_id == "get_project_context":
|
|
531
|
-
return await get_project_context(
|
|
532
|
-
db=db,
|
|
533
|
-
embeddings=embeddings,
|
|
534
|
-
project_path=params.get("project_path"),
|
|
535
|
-
query=params.get("query"),
|
|
536
|
-
limit=params.get("limit", 10)
|
|
537
|
-
)
|
|
538
609
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
)
|
|
610
|
+
async def _handle_store_pattern(query, params, session_id):
|
|
611
|
+
return await store_pattern(
|
|
612
|
+
db=db,
|
|
613
|
+
embeddings=embeddings,
|
|
614
|
+
name=params.get("name"),
|
|
615
|
+
solution=params.get("solution"),
|
|
616
|
+
problem_type=params.get("problem_type"),
|
|
617
|
+
tech_context=params.get("tech_context"),
|
|
618
|
+
metadata=params.get("metadata")
|
|
619
|
+
)
|
|
550
620
|
|
|
551
|
-
elif skill_id == "auto_summarize_session":
|
|
552
|
-
return await auto_summarize_session(
|
|
553
|
-
db=db,
|
|
554
|
-
embeddings=embeddings,
|
|
555
|
-
session_id=session_id or params.get("session_id"),
|
|
556
|
-
project_path=params.get("project_path")
|
|
557
|
-
)
|
|
558
621
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
)
|
|
622
|
+
async def _handle_retrieve_memory(query, params, session_id):
|
|
623
|
+
return await retrieve_memory(
|
|
624
|
+
db=db,
|
|
625
|
+
memory_id=params.get("memory_id"),
|
|
626
|
+
memory_type=params.get("type"),
|
|
627
|
+
session_id=session_id or params.get("session_id"),
|
|
628
|
+
project_path=params.get("project_path"),
|
|
629
|
+
limit=params.get("limit", 10)
|
|
630
|
+
)
|
|
566
631
|
|
|
567
|
-
elif skill_id == "create_diary_entry":
|
|
568
|
-
return await create_diary_entry(
|
|
569
|
-
db=db,
|
|
570
|
-
embeddings=embeddings,
|
|
571
|
-
session_id=session_id or params.get("session_id"),
|
|
572
|
-
project_path=params.get("project_path"),
|
|
573
|
-
user_notes=params.get("user_notes")
|
|
574
|
-
)
|
|
575
632
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
)
|
|
633
|
+
async def _handle_semantic_search(query, params, session_id):
|
|
634
|
+
result = await semantic_search(
|
|
635
|
+
db=db,
|
|
636
|
+
embeddings=embeddings,
|
|
637
|
+
query=params.get("query", query),
|
|
638
|
+
limit=params.get("limit", 10),
|
|
639
|
+
memory_type=params.get("type"),
|
|
640
|
+
session_id=session_id or params.get("session_id"),
|
|
641
|
+
project_path=params.get("project_path"),
|
|
642
|
+
agent_type=params.get("agent_type"),
|
|
643
|
+
success_only=params.get("success_only", False),
|
|
644
|
+
threshold=params.get("threshold", 0.5),
|
|
645
|
+
include_failed=params.get("include_failed", False),
|
|
646
|
+
include_superseded=params.get("include_superseded", False),
|
|
647
|
+
include_unreliable=params.get("include_unreliable", False),
|
|
648
|
+
outcome_status=params.get("outcome_status"),
|
|
649
|
+
include_graph=params.get("include_graph", True),
|
|
650
|
+
temperature=params.get("temperature")
|
|
651
|
+
)
|
|
652
|
+
metrics.record_search(result.get("count", 0))
|
|
653
|
+
return result
|
|
582
654
|
|
|
583
|
-
elif skill_id == "get_stats":
|
|
584
|
-
return await db.get_stats()
|
|
585
655
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
656
|
+
async def _handle_search_patterns(query, params, session_id):
|
|
657
|
+
result = await search_patterns(
|
|
658
|
+
db=db,
|
|
659
|
+
embeddings=embeddings,
|
|
660
|
+
query=params.get("query", query),
|
|
661
|
+
limit=params.get("limit", 5),
|
|
662
|
+
problem_type=params.get("problem_type"),
|
|
663
|
+
threshold=params.get("threshold", 0.5)
|
|
664
|
+
)
|
|
665
|
+
metrics.record_search(result.get("count", 0))
|
|
666
|
+
return result
|
|
589
667
|
|
|
590
|
-
elif skill_id == "timeline_log":
|
|
591
|
-
result = await timeline_log(
|
|
592
|
-
db=db,
|
|
593
|
-
embeddings=embeddings,
|
|
594
|
-
session_id=params.get("session_id") or session_id or str(uuid.uuid4()),
|
|
595
|
-
event_type=params.get("event_type", "observation"),
|
|
596
|
-
summary=params.get("summary", query),
|
|
597
|
-
details=params.get("details"),
|
|
598
|
-
project_path=params.get("project_path"),
|
|
599
|
-
parent_event_id=params.get("parent_event_id"),
|
|
600
|
-
root_event_id=params.get("root_event_id"),
|
|
601
|
-
entities=params.get("entities"),
|
|
602
|
-
status=params.get("status", "completed"),
|
|
603
|
-
outcome=params.get("outcome"),
|
|
604
|
-
confidence=params.get("confidence"),
|
|
605
|
-
is_anchor=params.get("is_anchor", False)
|
|
606
|
-
)
|
|
607
|
-
# Broadcast real-time update
|
|
608
|
-
await broadcast_event(
|
|
609
|
-
EventTypes.TIMELINE_LOGGED,
|
|
610
|
-
{"event_id": result.get("event_id"), "event_type": params.get("event_type", "observation")},
|
|
611
|
-
params.get("project_path")
|
|
612
|
-
)
|
|
613
|
-
return result
|
|
614
668
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
parent_event_id=params.get("parent_event_id"),
|
|
624
|
-
root_event_id=params.get("root_event_id")
|
|
625
|
-
)
|
|
626
|
-
# Broadcast single update for the batch
|
|
627
|
-
if result.get("events_logged", 0) > 0:
|
|
628
|
-
await broadcast_event(
|
|
629
|
-
EventTypes.TIMELINE_LOGGED,
|
|
630
|
-
{
|
|
631
|
-
"event_ids": result.get("event_ids", []),
|
|
632
|
-
"batch_size": result.get("events_logged", 0),
|
|
633
|
-
"event_types": result.get("event_types", {})
|
|
634
|
-
},
|
|
635
|
-
params.get("project_path")
|
|
636
|
-
)
|
|
637
|
-
return result
|
|
669
|
+
async def _handle_get_project_context(query, params, session_id):
|
|
670
|
+
return await get_project_context(
|
|
671
|
+
db=db,
|
|
672
|
+
embeddings=embeddings,
|
|
673
|
+
project_path=params.get("project_path"),
|
|
674
|
+
query=params.get("query"),
|
|
675
|
+
limit=params.get("limit", 10)
|
|
676
|
+
)
|
|
638
677
|
|
|
639
|
-
elif skill_id == "timeline_get":
|
|
640
|
-
return await timeline_get(
|
|
641
|
-
db=db,
|
|
642
|
-
session_id=params.get("session_id") or session_id,
|
|
643
|
-
limit=params.get("limit", 20),
|
|
644
|
-
event_type=params.get("event_type"),
|
|
645
|
-
since_event_id=params.get("since_event_id"),
|
|
646
|
-
anchors_only=params.get("anchors_only", False),
|
|
647
|
-
include_state=params.get("include_state", True),
|
|
648
|
-
include_checkpoint=params.get("include_checkpoint", True)
|
|
649
|
-
)
|
|
650
678
|
|
|
651
|
-
|
|
652
|
-
return await timeline_search(
|
|
653
|
-
db=db,
|
|
654
|
-
embeddings=embeddings,
|
|
655
|
-
query=params.get("query", query),
|
|
656
|
-
session_id=params.get("session_id") or session_id,
|
|
657
|
-
limit=params.get("limit", 10),
|
|
658
|
-
threshold=params.get("threshold", 0.5)
|
|
659
|
-
)
|
|
679
|
+
# --- Session Skills ---
|
|
660
680
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
)
|
|
681
|
+
async def _handle_summarize_session(query, params, session_id):
|
|
682
|
+
return await summarize_session(
|
|
683
|
+
db=db,
|
|
684
|
+
embeddings=embeddings,
|
|
685
|
+
session_id=session_id or params.get("session_id", str(uuid.uuid4())),
|
|
686
|
+
summary=params.get("summary", query),
|
|
687
|
+
key_decisions=params.get("key_decisions"),
|
|
688
|
+
code_patterns=params.get("code_patterns"),
|
|
689
|
+
metadata=params.get("metadata"),
|
|
690
|
+
project_path=params.get("project_path")
|
|
691
|
+
)
|
|
670
692
|
|
|
671
|
-
elif skill_id == "timeline_chain":
|
|
672
|
-
return await timeline_chain(
|
|
673
|
-
db=db,
|
|
674
|
-
session_id=params.get("session_id") or session_id,
|
|
675
|
-
root_event_id=params.get("root_event_id"),
|
|
676
|
-
include_details=params.get("include_details", False)
|
|
677
|
-
)
|
|
678
693
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
694
|
+
async def _handle_auto_summarize_session(query, params, session_id):
|
|
695
|
+
return await auto_summarize_session(
|
|
696
|
+
db=db,
|
|
697
|
+
embeddings=embeddings,
|
|
698
|
+
session_id=session_id or params.get("session_id"),
|
|
699
|
+
project_path=params.get("project_path")
|
|
700
|
+
)
|
|
682
701
|
|
|
683
|
-
elif skill_id == "state_get":
|
|
684
|
-
return await state_get(
|
|
685
|
-
db=db,
|
|
686
|
-
session_id=params.get("session_id") or session_id,
|
|
687
|
-
project_path=params.get("project_path")
|
|
688
|
-
)
|
|
689
702
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
remove_question=params.get("remove_question"),
|
|
698
|
-
register_entity=params.get("register_entity"),
|
|
699
|
-
entity_registry=params.get("entity_registry"),
|
|
700
|
-
add_decision=params.get("add_decision"),
|
|
701
|
-
decisions_summary=params.get("decisions_summary")
|
|
702
|
-
)
|
|
703
|
+
async def _handle_get_session_handoff(query, params, session_id):
|
|
704
|
+
return await get_session_handoff(
|
|
705
|
+
db=db,
|
|
706
|
+
embeddings=embeddings,
|
|
707
|
+
project_path=params.get("project_path"),
|
|
708
|
+
include_last_n_sessions=params.get("include_last_n_sessions", 3)
|
|
709
|
+
)
|
|
703
710
|
|
|
704
|
-
elif skill_id == "state_init_session":
|
|
705
|
-
return await state_init_session(
|
|
706
|
-
db=db,
|
|
707
|
-
embeddings=embeddings,
|
|
708
|
-
project_path=params.get("project_path")
|
|
709
|
-
)
|
|
710
711
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
712
|
+
async def _handle_create_diary_entry(query, params, session_id):
|
|
713
|
+
return await create_diary_entry(
|
|
714
|
+
db=db,
|
|
715
|
+
embeddings=embeddings,
|
|
716
|
+
session_id=session_id or params.get("session_id"),
|
|
717
|
+
project_path=params.get("project_path"),
|
|
718
|
+
user_notes=params.get("user_notes")
|
|
719
|
+
)
|
|
714
720
|
|
|
715
|
-
elif skill_id == "checkpoint_create":
|
|
716
|
-
return await checkpoint_create(
|
|
717
|
-
db=db,
|
|
718
|
-
embeddings=embeddings,
|
|
719
|
-
session_id=params.get("session_id") or session_id,
|
|
720
|
-
summary=params.get("summary"),
|
|
721
|
-
key_facts=params.get("key_facts"),
|
|
722
|
-
include_state=params.get("include_state", True)
|
|
723
|
-
)
|
|
724
721
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
)
|
|
722
|
+
async def _handle_check_session_inactivity(query, params, session_id):
|
|
723
|
+
return await check_session_inactivity(
|
|
724
|
+
db=db,
|
|
725
|
+
session_id=session_id or params.get("session_id"),
|
|
726
|
+
inactivity_threshold_hours=params.get("inactivity_threshold_hours", 4.0)
|
|
727
|
+
)
|
|
732
728
|
|
|
733
|
-
elif skill_id == "checkpoint_list":
|
|
734
|
-
return await checkpoint_list(
|
|
735
|
-
db=db,
|
|
736
|
-
session_id=params.get("session_id") or session_id,
|
|
737
|
-
limit=params.get("limit", 10)
|
|
738
|
-
)
|
|
739
729
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
# ============================================================
|
|
730
|
+
async def _handle_get_stats(query, params, session_id):
|
|
731
|
+
return await db.get_stats()
|
|
743
732
|
|
|
744
|
-
elif skill_id == "context_refresh":
|
|
745
|
-
return await context_refresh(
|
|
746
|
-
db=db,
|
|
747
|
-
embeddings=embeddings,
|
|
748
|
-
session_id=params.get("session_id") or session_id,
|
|
749
|
-
query=params.get("query", query) if query else None,
|
|
750
|
-
include_recent_events=params.get("include_recent_events", 10),
|
|
751
|
-
include_state=params.get("include_state", True),
|
|
752
|
-
include_checkpoint=params.get("include_checkpoint", True),
|
|
753
|
-
include_relevant_memories=params.get("include_relevant_memories", True),
|
|
754
|
-
check_contradictions=params.get("check_contradictions", True)
|
|
755
|
-
)
|
|
756
733
|
|
|
757
|
-
|
|
758
|
-
return await check_contradictions(
|
|
759
|
-
db=db,
|
|
760
|
-
embeddings=embeddings,
|
|
761
|
-
statement=params.get("statement", query),
|
|
762
|
-
session_id=params.get("session_id") or session_id,
|
|
763
|
-
scope=params.get("scope", "session")
|
|
764
|
-
)
|
|
734
|
+
# --- Timeline Skills ---
|
|
765
735
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
)
|
|
736
|
+
async def _handle_timeline_log(query, params, session_id):
|
|
737
|
+
result = await timeline_log(
|
|
738
|
+
db=db,
|
|
739
|
+
embeddings=embeddings,
|
|
740
|
+
session_id=params.get("session_id") or session_id or str(uuid.uuid4()),
|
|
741
|
+
event_type=params.get("event_type", "observation"),
|
|
742
|
+
summary=params.get("summary", query),
|
|
743
|
+
details=params.get("details"),
|
|
744
|
+
project_path=params.get("project_path"),
|
|
745
|
+
parent_event_id=params.get("parent_event_id"),
|
|
746
|
+
root_event_id=params.get("root_event_id"),
|
|
747
|
+
entities=params.get("entities"),
|
|
748
|
+
status=params.get("status", "completed"),
|
|
749
|
+
outcome=params.get("outcome"),
|
|
750
|
+
confidence=params.get("confidence"),
|
|
751
|
+
is_anchor=params.get("is_anchor", False)
|
|
752
|
+
)
|
|
753
|
+
await broadcast_event(
|
|
754
|
+
EventTypes.TIMELINE_LOGGED,
|
|
755
|
+
{"event_id": result.get("event_id"), "event_type": params.get("event_type", "observation")},
|
|
756
|
+
params.get("project_path")
|
|
757
|
+
)
|
|
758
|
+
return result
|
|
773
759
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
|
|
760
|
+
|
|
761
|
+
async def _handle_timeline_log_batch(query, params, session_id):
|
|
762
|
+
result = await timeline_log_batch(
|
|
763
|
+
db=db,
|
|
764
|
+
embeddings=embeddings,
|
|
765
|
+
session_id=params.get("session_id") or session_id or str(uuid.uuid4()),
|
|
766
|
+
events=params.get("events", []),
|
|
767
|
+
project_path=params.get("project_path"),
|
|
768
|
+
parent_event_id=params.get("parent_event_id"),
|
|
769
|
+
root_event_id=params.get("root_event_id")
|
|
770
|
+
)
|
|
771
|
+
if result.get("events_logged", 0) > 0:
|
|
786
772
|
await broadcast_event(
|
|
787
|
-
|
|
788
|
-
{
|
|
773
|
+
EventTypes.TIMELINE_LOGGED,
|
|
774
|
+
{
|
|
775
|
+
"event_ids": result.get("event_ids", []),
|
|
776
|
+
"batch_size": result.get("events_logged", 0),
|
|
777
|
+
"event_types": result.get("event_types", {})
|
|
778
|
+
},
|
|
789
779
|
params.get("project_path")
|
|
790
780
|
)
|
|
791
|
-
|
|
781
|
+
return result
|
|
792
782
|
|
|
793
|
-
elif skill_id == "get_unresolved_conflicts":
|
|
794
|
-
return await get_unresolved_conflicts(
|
|
795
|
-
db=db,
|
|
796
|
-
session_id=params.get("session_id") or session_id,
|
|
797
|
-
project_path=params.get("project_path"),
|
|
798
|
-
limit=params.get("limit", 20)
|
|
799
|
-
)
|
|
800
783
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
)
|
|
784
|
+
async def _handle_timeline_get(query, params, session_id):
|
|
785
|
+
return await timeline_get(
|
|
786
|
+
db=db,
|
|
787
|
+
session_id=params.get("session_id") or session_id,
|
|
788
|
+
limit=params.get("limit", 20),
|
|
789
|
+
event_type=params.get("event_type"),
|
|
790
|
+
since_event_id=params.get("since_event_id"),
|
|
791
|
+
anchors_only=params.get("anchors_only", False),
|
|
792
|
+
include_state=params.get("include_state", True),
|
|
793
|
+
include_checkpoint=params.get("include_checkpoint", True)
|
|
794
|
+
)
|
|
810
795
|
|
|
811
|
-
elif skill_id == "get_anchor_history":
|
|
812
|
-
return await get_anchor_history(
|
|
813
|
-
db=db,
|
|
814
|
-
anchor_id=params.get("anchor_id"),
|
|
815
|
-
session_id=params.get("session_id") or session_id,
|
|
816
|
-
limit=params.get("limit", 50)
|
|
817
|
-
)
|
|
818
796
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
)
|
|
797
|
+
async def _handle_timeline_search(query, params, session_id):
|
|
798
|
+
return await timeline_search(
|
|
799
|
+
db=db,
|
|
800
|
+
embeddings=embeddings,
|
|
801
|
+
query=params.get("query", query),
|
|
802
|
+
session_id=params.get("session_id") or session_id,
|
|
803
|
+
limit=params.get("limit", 10),
|
|
804
|
+
threshold=params.get("threshold", 0.5)
|
|
805
|
+
)
|
|
825
806
|
|
|
826
|
-
# ============================================================
|
|
827
|
-
# SELF-CORRECTING CONFIDENCE SKILLS
|
|
828
|
-
# ============================================================
|
|
829
807
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
)
|
|
838
|
-
|
|
839
|
-
await broadcast_event(
|
|
840
|
-
EventTypes.MEMORY_UPDATED,
|
|
841
|
-
{
|
|
842
|
-
"memory_id": params.get("memory_id"),
|
|
843
|
-
"action": "worked",
|
|
844
|
-
"new_confidence": result.get("new_confidence"),
|
|
845
|
-
"reliability": result.get("reliability")
|
|
846
|
-
}
|
|
847
|
-
)
|
|
848
|
-
return result
|
|
808
|
+
async def _handle_timeline_auto_detect(query, params, session_id):
|
|
809
|
+
return await timeline_auto_detect(
|
|
810
|
+
db=db,
|
|
811
|
+
embeddings=embeddings,
|
|
812
|
+
session_id=params.get("session_id") or session_id or str(uuid.uuid4()),
|
|
813
|
+
response_text=params.get("response_text", query),
|
|
814
|
+
project_path=params.get("project_path"),
|
|
815
|
+
parent_event_id=params.get("parent_event_id")
|
|
816
|
+
)
|
|
849
817
|
|
|
850
|
-
elif skill_id == "memory_failed":
|
|
851
|
-
from skills.confidence_tracker import report_solution_outcome
|
|
852
|
-
result = await report_solution_outcome(
|
|
853
|
-
db=db,
|
|
854
|
-
memory_id=params.get("memory_id"),
|
|
855
|
-
worked=False,
|
|
856
|
-
context=params.get("context")
|
|
857
|
-
)
|
|
858
|
-
if result.get("success"):
|
|
859
|
-
await broadcast_event(
|
|
860
|
-
EventTypes.MEMORY_UPDATED,
|
|
861
|
-
{
|
|
862
|
-
"memory_id": params.get("memory_id"),
|
|
863
|
-
"action": "failed",
|
|
864
|
-
"new_confidence": result.get("new_confidence"),
|
|
865
|
-
"reliability": result.get("reliability"),
|
|
866
|
-
"is_unreliable": result.get("is_unreliable")
|
|
867
|
-
}
|
|
868
|
-
)
|
|
869
|
-
return result
|
|
870
818
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
)
|
|
819
|
+
async def _handle_timeline_chain(query, params, session_id):
|
|
820
|
+
return await timeline_chain(
|
|
821
|
+
db=db,
|
|
822
|
+
session_id=params.get("session_id") or session_id,
|
|
823
|
+
root_event_id=params.get("root_event_id"),
|
|
824
|
+
include_details=params.get("include_details", False)
|
|
825
|
+
)
|
|
877
826
|
|
|
878
|
-
elif skill_id == "get_unreliable_memories":
|
|
879
|
-
from skills.confidence_tracker import get_unreliable_memories
|
|
880
|
-
return await get_unreliable_memories(
|
|
881
|
-
db=db,
|
|
882
|
-
project_path=params.get("project_path"),
|
|
883
|
-
limit=params.get("limit", 50)
|
|
884
|
-
)
|
|
885
827
|
|
|
886
|
-
|
|
887
|
-
from skills.confidence_tracker import reset_memory_reliability
|
|
888
|
-
return await reset_memory_reliability(
|
|
889
|
-
db=db,
|
|
890
|
-
memory_id=params.get("memory_id"),
|
|
891
|
-
new_confidence=params.get("confidence", 0.5)
|
|
892
|
-
)
|
|
828
|
+
# --- State Skills ---
|
|
893
829
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
830
|
+
async def _handle_state_get(query, params, session_id):
|
|
831
|
+
return await state_get(
|
|
832
|
+
db=db,
|
|
833
|
+
session_id=params.get("session_id") or session_id,
|
|
834
|
+
project_path=params.get("project_path")
|
|
835
|
+
)
|
|
897
836
|
|
|
898
|
-
elif skill_id == "claude_md_read":
|
|
899
|
-
return await claude_md_read(
|
|
900
|
-
section=params.get("section")
|
|
901
|
-
)
|
|
902
837
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
)
|
|
838
|
+
async def _handle_state_update(query, params, session_id):
|
|
839
|
+
return await state_update(
|
|
840
|
+
db=db,
|
|
841
|
+
session_id=params.get("session_id") or session_id,
|
|
842
|
+
current_goal=params.get("current_goal"),
|
|
843
|
+
pending_questions=params.get("pending_questions"),
|
|
844
|
+
add_question=params.get("add_question"),
|
|
845
|
+
remove_question=params.get("remove_question"),
|
|
846
|
+
register_entity=params.get("register_entity"),
|
|
847
|
+
entity_registry=params.get("entity_registry"),
|
|
848
|
+
add_decision=params.get("add_decision"),
|
|
849
|
+
decisions_summary=params.get("decisions_summary")
|
|
850
|
+
)
|
|
909
851
|
|
|
910
|
-
elif skill_id == "claude_md_update_section":
|
|
911
|
-
return await claude_md_update_section(
|
|
912
|
-
section_name=params.get("section_name"),
|
|
913
|
-
content=params.get("content", query),
|
|
914
|
-
mode=params.get("mode", "replace")
|
|
915
|
-
)
|
|
916
852
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
853
|
+
async def _handle_state_init_session(query, params, session_id):
|
|
854
|
+
return await state_init_session(
|
|
855
|
+
db=db,
|
|
856
|
+
embeddings=embeddings,
|
|
857
|
+
project_path=params.get("project_path")
|
|
858
|
+
)
|
|
923
859
|
|
|
924
|
-
elif skill_id == "claude_md_list_sections":
|
|
925
|
-
return await claude_md_list_sections()
|
|
926
860
|
|
|
927
|
-
|
|
928
|
-
return await claude_md_suggest_from_session(
|
|
929
|
-
db=db,
|
|
930
|
-
session_id=params.get("session_id") or session_id,
|
|
931
|
-
min_importance=params.get("min_importance", 7)
|
|
932
|
-
)
|
|
861
|
+
# --- Checkpoint Skills ---
|
|
933
862
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
863
|
+
async def _handle_checkpoint_create(query, params, session_id):
|
|
864
|
+
return await checkpoint_create(
|
|
865
|
+
db=db,
|
|
866
|
+
embeddings=embeddings,
|
|
867
|
+
session_id=params.get("session_id") or session_id,
|
|
868
|
+
summary=params.get("summary"),
|
|
869
|
+
key_facts=params.get("key_facts"),
|
|
870
|
+
include_state=params.get("include_state", True)
|
|
871
|
+
)
|
|
937
872
|
|
|
938
|
-
elif skill_id == "best_of_n_verify":
|
|
939
|
-
return await best_of_n_verify(
|
|
940
|
-
query=params.get("query", query),
|
|
941
|
-
n=params.get("n", 3),
|
|
942
|
-
context=params.get("context"),
|
|
943
|
-
threshold=params.get("threshold", 0.7)
|
|
944
|
-
)
|
|
945
873
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
874
|
+
async def _handle_checkpoint_load(query, params, session_id):
|
|
875
|
+
return await checkpoint_load(
|
|
876
|
+
db=db,
|
|
877
|
+
session_id=params.get("session_id") or session_id,
|
|
878
|
+
checkpoint_id=params.get("checkpoint_id"),
|
|
879
|
+
project_path=params.get("project_path")
|
|
880
|
+
)
|
|
953
881
|
|
|
954
|
-
elif skill_id == "require_grounding":
|
|
955
|
-
return await require_grounding(
|
|
956
|
-
db=db,
|
|
957
|
-
session_id=params.get("session_id") or session_id,
|
|
958
|
-
statement=params.get("statement", query),
|
|
959
|
-
source_type=params.get("source_type", "any")
|
|
960
|
-
)
|
|
961
882
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
883
|
+
async def _handle_checkpoint_list(query, params, session_id):
|
|
884
|
+
return await checkpoint_list(
|
|
885
|
+
db=db,
|
|
886
|
+
session_id=params.get("session_id") or session_id,
|
|
887
|
+
limit=params.get("limit", 10)
|
|
888
|
+
)
|
|
965
889
|
|
|
966
|
-
elif skill_id == "run_aggregation":
|
|
967
|
-
return await run_aggregation(
|
|
968
|
-
db=db,
|
|
969
|
-
embeddings=embeddings,
|
|
970
|
-
days_back=params.get("days_back", 30)
|
|
971
|
-
)
|
|
972
890
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
891
|
+
# --- Grounding Skills (Anti-Hallucination) ---
|
|
892
|
+
|
|
893
|
+
async def _handle_context_refresh(query, params, session_id):
|
|
894
|
+
return await context_refresh(
|
|
895
|
+
db=db,
|
|
896
|
+
embeddings=embeddings,
|
|
897
|
+
session_id=params.get("session_id") or session_id,
|
|
898
|
+
query=params.get("query", query) if query else None,
|
|
899
|
+
include_recent_events=params.get("include_recent_events", 10),
|
|
900
|
+
include_state=params.get("include_state", True),
|
|
901
|
+
include_checkpoint=params.get("include_checkpoint", True),
|
|
902
|
+
include_relevant_memories=params.get("include_relevant_memories", True),
|
|
903
|
+
check_contradictions=params.get("check_contradictions", True)
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
async def _handle_check_contradictions(query, params, session_id):
|
|
908
|
+
return await check_contradictions(
|
|
909
|
+
db=db,
|
|
910
|
+
embeddings=embeddings,
|
|
911
|
+
statement=params.get("statement", query),
|
|
912
|
+
session_id=params.get("session_id") or session_id,
|
|
913
|
+
scope=params.get("scope", "session")
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
async def _handle_verify_entity(query, params, session_id):
|
|
918
|
+
return await verify_entity(
|
|
919
|
+
db=db,
|
|
920
|
+
session_id=params.get("session_id") or session_id,
|
|
921
|
+
entity_key=params.get("entity_key"),
|
|
922
|
+
entity_type=params.get("entity_type")
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
async def _handle_mark_anchor(query, params, session_id):
|
|
927
|
+
result = await mark_anchor(
|
|
928
|
+
db=db,
|
|
929
|
+
embeddings=embeddings,
|
|
930
|
+
session_id=params.get("session_id") or session_id,
|
|
931
|
+
fact=params.get("fact", query),
|
|
932
|
+
details=params.get("details"),
|
|
933
|
+
project_path=params.get("project_path"),
|
|
934
|
+
force=params.get("force", False)
|
|
935
|
+
)
|
|
936
|
+
event_type = EventTypes.ANCHOR_CONFLICT if result.get("conflict_detected") else EventTypes.ANCHOR_MARKED
|
|
937
|
+
await broadcast_event(
|
|
938
|
+
event_type,
|
|
939
|
+
{"anchor_id": result.get("anchor_id"), "fact": params.get("fact", query)[:100]},
|
|
940
|
+
params.get("project_path")
|
|
941
|
+
)
|
|
942
|
+
return result
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
async def _handle_get_unresolved_conflicts(query, params, session_id):
|
|
946
|
+
return await get_unresolved_conflicts(
|
|
947
|
+
db=db,
|
|
948
|
+
session_id=params.get("session_id") or session_id,
|
|
949
|
+
project_path=params.get("project_path"),
|
|
950
|
+
limit=params.get("limit", 20)
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
async def _handle_resolve_conflict(query, params, session_id):
|
|
955
|
+
return await resolve_conflict(
|
|
956
|
+
db=db,
|
|
957
|
+
embeddings=embeddings,
|
|
958
|
+
conflict_id=params.get("conflict_id"),
|
|
959
|
+
resolution=params.get("resolution"),
|
|
960
|
+
keep_anchor_id=params.get("keep_anchor_id"),
|
|
961
|
+
resolved_by=params.get("resolved_by", "user")
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
async def _handle_get_anchor_history(query, params, session_id):
|
|
966
|
+
return await get_anchor_history(
|
|
967
|
+
db=db,
|
|
968
|
+
anchor_id=params.get("anchor_id"),
|
|
969
|
+
session_id=params.get("session_id") or session_id,
|
|
970
|
+
limit=params.get("limit", 50)
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
async def _handle_auto_resolve_conflicts(query, params, session_id):
|
|
975
|
+
return await auto_resolve_conflicts(
|
|
976
|
+
db=db,
|
|
977
|
+
embeddings=embeddings,
|
|
978
|
+
session_id=params.get("session_id") or session_id
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
# --- Self-Correcting Confidence Skills ---
|
|
983
|
+
|
|
984
|
+
async def _handle_memory_worked(query, params, session_id):
|
|
985
|
+
from skills.confidence_tracker import report_solution_outcome
|
|
986
|
+
result = await report_solution_outcome(
|
|
987
|
+
db=db,
|
|
988
|
+
memory_id=params.get("memory_id"),
|
|
989
|
+
worked=True,
|
|
990
|
+
context=params.get("context")
|
|
991
|
+
)
|
|
992
|
+
if result.get("success"):
|
|
993
|
+
await broadcast_event(
|
|
994
|
+
EventTypes.MEMORY_UPDATED,
|
|
995
|
+
{
|
|
996
|
+
"memory_id": params.get("memory_id"),
|
|
997
|
+
"action": "worked",
|
|
998
|
+
"new_confidence": result.get("new_confidence"),
|
|
999
|
+
"reliability": result.get("reliability")
|
|
1000
|
+
}
|
|
981
1001
|
)
|
|
1002
|
+
return result
|
|
982
1003
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1004
|
+
|
|
1005
|
+
async def _handle_memory_failed(query, params, session_id):
|
|
1006
|
+
from skills.confidence_tracker import report_solution_outcome
|
|
1007
|
+
result = await report_solution_outcome(
|
|
1008
|
+
db=db,
|
|
1009
|
+
memory_id=params.get("memory_id"),
|
|
1010
|
+
worked=False,
|
|
1011
|
+
context=params.get("context")
|
|
1012
|
+
)
|
|
1013
|
+
if result.get("success"):
|
|
1014
|
+
await broadcast_event(
|
|
1015
|
+
EventTypes.MEMORY_UPDATED,
|
|
1016
|
+
{
|
|
1017
|
+
"memory_id": params.get("memory_id"),
|
|
1018
|
+
"action": "failed",
|
|
1019
|
+
"new_confidence": result.get("new_confidence"),
|
|
1020
|
+
"reliability": result.get("reliability"),
|
|
1021
|
+
"is_unreliable": result.get("is_unreliable")
|
|
1022
|
+
}
|
|
988
1023
|
)
|
|
1024
|
+
return result
|
|
989
1025
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1026
|
+
|
|
1027
|
+
async def _handle_get_reliability_stats(query, params, session_id):
|
|
1028
|
+
from skills.confidence_tracker import get_reliability_stats as _get_reliability_stats
|
|
1029
|
+
return await _get_reliability_stats(
|
|
1030
|
+
db=db,
|
|
1031
|
+
memory_id=params.get("memory_id")
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
async def _handle_get_unreliable_memories(query, params, session_id):
|
|
1036
|
+
from skills.confidence_tracker import get_unreliable_memories as _get_unreliable_memories
|
|
1037
|
+
return await _get_unreliable_memories(
|
|
1038
|
+
db=db,
|
|
1039
|
+
project_path=params.get("project_path"),
|
|
1040
|
+
limit=params.get("limit", 50)
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
async def _handle_reset_memory_reliability(query, params, session_id):
|
|
1045
|
+
from skills.confidence_tracker import reset_memory_reliability as _reset_memory_reliability
|
|
1046
|
+
return await _reset_memory_reliability(
|
|
1047
|
+
db=db,
|
|
1048
|
+
memory_id=params.get("memory_id"),
|
|
1049
|
+
new_confidence=params.get("confidence", 0.5)
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
# --- CLAUDE.MD Management Skills ---
|
|
1054
|
+
|
|
1055
|
+
async def _handle_claude_md_read(query, params, session_id):
|
|
1056
|
+
return await claude_md_read(
|
|
1057
|
+
section=params.get("section")
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
async def _handle_claude_md_add_section(query, params, session_id):
|
|
1062
|
+
return await claude_md_add_section(
|
|
1063
|
+
section_name=params.get("section_name"),
|
|
1064
|
+
content=params.get("content", query),
|
|
1065
|
+
position=params.get("position", "end")
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
async def _handle_claude_md_update_section(query, params, session_id):
|
|
1070
|
+
return await claude_md_update_section(
|
|
1071
|
+
section_name=params.get("section_name"),
|
|
1072
|
+
content=params.get("content", query),
|
|
1073
|
+
mode=params.get("mode", "replace")
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
async def _handle_claude_md_add_instruction(query, params, session_id):
|
|
1078
|
+
return await claude_md_add_instruction(
|
|
1079
|
+
section_name=params.get("section_name"),
|
|
1080
|
+
instruction=params.get("instruction", query),
|
|
1081
|
+
bullet_style=params.get("bullet_style", "-")
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
async def _handle_claude_md_list_sections(query, params, session_id):
|
|
1086
|
+
return await claude_md_list_sections()
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
async def _handle_claude_md_suggest(query, params, session_id):
|
|
1090
|
+
return await claude_md_suggest_from_session(
|
|
1091
|
+
db=db,
|
|
1092
|
+
session_id=params.get("session_id") or session_id,
|
|
1093
|
+
min_importance=params.get("min_importance", 7)
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
# --- Verification Skills ---
|
|
1098
|
+
|
|
1099
|
+
async def _handle_best_of_n_verify(query, params, session_id):
|
|
1100
|
+
return await best_of_n_verify(
|
|
1101
|
+
query=params.get("query", query),
|
|
1102
|
+
n=params.get("n", 3),
|
|
1103
|
+
context=params.get("context"),
|
|
1104
|
+
threshold=params.get("threshold", 0.7)
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
async def _handle_extract_quotes(query, params, session_id):
|
|
1109
|
+
return await extract_quotes(
|
|
1110
|
+
document=params.get("document", ""),
|
|
1111
|
+
query=params.get("query", query),
|
|
1112
|
+
max_quotes=params.get("max_quotes", 5),
|
|
1113
|
+
min_length=params.get("min_length", 20)
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
async def _handle_require_grounding(query, params, session_id):
|
|
1118
|
+
return await require_grounding(
|
|
1119
|
+
db=db,
|
|
1120
|
+
session_id=params.get("session_id") or session_id,
|
|
1121
|
+
statement=params.get("statement", query),
|
|
1122
|
+
source_type=params.get("source_type", "any")
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
# --- Cross-Session Learning Skills ---
|
|
1127
|
+
|
|
1128
|
+
async def _handle_run_aggregation(query, params, session_id):
|
|
1129
|
+
return await run_aggregation(
|
|
1130
|
+
db=db,
|
|
1131
|
+
embeddings=embeddings,
|
|
1132
|
+
days_back=params.get("days_back", 30)
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
async def _handle_get_insights(query, params, session_id):
|
|
1137
|
+
return await get_insights(
|
|
1138
|
+
db=db,
|
|
1139
|
+
embeddings=embeddings,
|
|
1140
|
+
insight_type=params.get("insight_type"),
|
|
1141
|
+
project_path=params.get("project_path"),
|
|
1142
|
+
min_confidence=params.get("min_confidence", 0.5),
|
|
1143
|
+
limit=params.get("limit", 10)
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
async def _handle_suggest_improvements(query, params, session_id):
|
|
1148
|
+
return await suggest_improvements(
|
|
1149
|
+
db=db,
|
|
1150
|
+
embeddings=embeddings,
|
|
1151
|
+
min_confidence=params.get("min_confidence", 0.7)
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
async def _handle_record_insight_feedback(query, params, session_id):
|
|
1156
|
+
return await record_insight_feedback(
|
|
1157
|
+
db=db,
|
|
1158
|
+
embeddings=embeddings,
|
|
1159
|
+
insight_id=params.get("insight_id"),
|
|
1160
|
+
helpful=params.get("helpful", True),
|
|
1161
|
+
session_id=session_id or params.get("session_id"),
|
|
1162
|
+
comment=params.get("comment")
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
async def _handle_mark_insight_applied(query, params, session_id):
|
|
1167
|
+
return await mark_insight_applied(
|
|
1168
|
+
db=db,
|
|
1169
|
+
embeddings=embeddings,
|
|
1170
|
+
insight_id=params.get("insight_id")
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
async def _handle_get_project_insights(query, params, session_id):
|
|
1175
|
+
return await get_project_insights(
|
|
1176
|
+
db=db,
|
|
1177
|
+
embeddings=embeddings,
|
|
1178
|
+
project_path=params.get("project_path"),
|
|
1179
|
+
include_global=params.get("include_global", True),
|
|
1180
|
+
limit=params.get("limit", 10)
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
# --- Memory Cleanup Skills ---
|
|
1185
|
+
|
|
1186
|
+
async def _handle_memory_cleanup(query, params, session_id):
|
|
1187
|
+
result = await memory_cleanup(
|
|
1188
|
+
db=db,
|
|
1189
|
+
embeddings=embeddings,
|
|
1190
|
+
project_path=params.get("project_path"),
|
|
1191
|
+
dry_run=params.get("dry_run", True)
|
|
1192
|
+
)
|
|
1193
|
+
if not params.get("dry_run", True):
|
|
1194
|
+
await broadcast_event(
|
|
1195
|
+
EventTypes.CLEANUP_COMPLETED,
|
|
1196
|
+
{"archived": result.get("total_archived", 0), "deleted": result.get("total_deleted", 0)},
|
|
1197
|
+
params.get("project_path")
|
|
998
1198
|
)
|
|
1199
|
+
return result
|
|
999
1200
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1201
|
+
|
|
1202
|
+
async def _handle_get_archived_memories(query, params, session_id):
|
|
1203
|
+
return await get_archived_memories(
|
|
1204
|
+
db=db,
|
|
1205
|
+
embeddings=embeddings,
|
|
1206
|
+
project_path=params.get("project_path"),
|
|
1207
|
+
reason=params.get("reason"),
|
|
1208
|
+
limit=params.get("limit", 50)
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
async def _handle_restore_memory(query, params, session_id):
|
|
1213
|
+
return await restore_memory(
|
|
1214
|
+
db=db,
|
|
1215
|
+
embeddings=embeddings,
|
|
1216
|
+
archive_id=params.get("archive_id")
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
async def _handle_get_cleanup_config(query, params, session_id):
|
|
1221
|
+
return await get_cleanup_config(
|
|
1222
|
+
db=db,
|
|
1223
|
+
embeddings=embeddings,
|
|
1224
|
+
project_path=params.get("project_path")
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
async def _handle_set_cleanup_config(query, params, session_id):
|
|
1229
|
+
return await set_cleanup_config(
|
|
1230
|
+
db=db,
|
|
1231
|
+
embeddings=embeddings,
|
|
1232
|
+
project_path=params.get("project_path"),
|
|
1233
|
+
retention_days=params.get("retention_days"),
|
|
1234
|
+
min_relevance_score=params.get("min_relevance_score"),
|
|
1235
|
+
keep_high_importance=params.get("keep_high_importance"),
|
|
1236
|
+
importance_threshold=params.get("importance_threshold"),
|
|
1237
|
+
dedup_enabled=params.get("dedup_enabled"),
|
|
1238
|
+
dedup_threshold=params.get("dedup_threshold"),
|
|
1239
|
+
archive_before_delete=params.get("archive_before_delete"),
|
|
1240
|
+
auto_cleanup_enabled=params.get("auto_cleanup_enabled")
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
async def _handle_get_cleanup_stats(query, params, session_id):
|
|
1245
|
+
return await get_cleanup_stats(db=db, embeddings=embeddings)
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
async def _handle_purge_expired_archives(query, params, session_id):
|
|
1249
|
+
return await purge_expired_archives(db=db, embeddings=embeddings)
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
# --- Admin Skills (Embedding Model Management) ---
|
|
1253
|
+
|
|
1254
|
+
async def _handle_get_embedding_status(query, params, session_id):
|
|
1255
|
+
return await get_embedding_status(db=db, embeddings=embeddings)
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
async def _handle_switch_embedding_model(query, params, session_id):
|
|
1259
|
+
return await switch_embedding_model(
|
|
1260
|
+
db=db,
|
|
1261
|
+
embeddings=embeddings,
|
|
1262
|
+
model=params.get("model", "nomic-embed-text"),
|
|
1263
|
+
reindex_existing=params.get("reindex_existing", False)
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
async def _handle_reindex_memories(query, params, session_id):
|
|
1268
|
+
return await reindex_memories(
|
|
1269
|
+
db=db,
|
|
1270
|
+
embeddings=embeddings,
|
|
1271
|
+
model=params.get("model"),
|
|
1272
|
+
project_path=params.get("project_path"),
|
|
1273
|
+
batch_size=params.get("batch_size", 10),
|
|
1274
|
+
dry_run=params.get("dry_run", False)
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
async def _handle_get_reindex_progress(query, params, session_id):
|
|
1279
|
+
return await get_reindex_progress(db=db, embeddings=embeddings)
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
async def _handle_cancel_reindex(query, params, session_id):
|
|
1283
|
+
return await cancel_reindex(db=db, embeddings=embeddings)
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
async def _handle_get_model_info(query, params, session_id):
|
|
1287
|
+
return await get_model_info(
|
|
1288
|
+
db=db,
|
|
1289
|
+
embeddings=embeddings,
|
|
1290
|
+
model=params.get("model")
|
|
1291
|
+
)
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
async def _handle_get_system_stats(query, params, session_id):
|
|
1295
|
+
return await get_system_stats(db=db, embeddings=embeddings)
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
# --- MoltBot-Inspired Skills (Human-Readable Transparency) ---
|
|
1299
|
+
|
|
1300
|
+
async def _handle_daily_log_append(query, params, session_id):
|
|
1301
|
+
from services.daily_log import append_entry
|
|
1302
|
+
return await append_entry(
|
|
1303
|
+
project_path=params.get("project_path"),
|
|
1304
|
+
content=params.get("content", query),
|
|
1305
|
+
entry_type=params.get("entry_type", "note"),
|
|
1306
|
+
session_id=session_id or params.get("session_id")
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
async def _handle_daily_log_append_session(query, params, session_id):
|
|
1311
|
+
from services.daily_log import append_session_summary
|
|
1312
|
+
return await append_session_summary(
|
|
1313
|
+
project_path=params.get("project_path"),
|
|
1314
|
+
session_id=session_id or params.get("session_id"),
|
|
1315
|
+
decisions=params.get("decisions"),
|
|
1316
|
+
accomplishments=params.get("accomplishments"),
|
|
1317
|
+
notes=params.get("notes"),
|
|
1318
|
+
errors_solved=params.get("errors_solved")
|
|
1319
|
+
)
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
async def _handle_daily_log_read(query, params, session_id):
|
|
1323
|
+
from services.daily_log import load_recent_logs
|
|
1324
|
+
return await load_recent_logs(
|
|
1325
|
+
project_path=params.get("project_path"),
|
|
1326
|
+
days=params.get("days", 2),
|
|
1327
|
+
max_chars=params.get("max_chars", 8000)
|
|
1328
|
+
)
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
async def _handle_daily_log_highlights(query, params, session_id):
|
|
1332
|
+
from services.daily_log import get_today_highlights
|
|
1333
|
+
return await get_today_highlights(
|
|
1334
|
+
project_path=params.get("project_path"),
|
|
1335
|
+
max_entries=params.get("max_entries", 10)
|
|
1336
|
+
)
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
async def _handle_daily_log_list(query, params, session_id):
|
|
1340
|
+
from services.daily_log import list_logs
|
|
1341
|
+
return await list_logs(
|
|
1342
|
+
project_path=params.get("project_path"),
|
|
1343
|
+
limit=params.get("limit", 30)
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
async def _handle_sync_memory_md(query, params, session_id):
|
|
1348
|
+
from services.memory_md_sync import sync_to_memory_md
|
|
1349
|
+
return await sync_to_memory_md(
|
|
1350
|
+
db=db,
|
|
1351
|
+
project_path=params.get("project_path"),
|
|
1352
|
+
min_importance=params.get("min_importance", 7),
|
|
1353
|
+
min_pattern_success=params.get("min_pattern_success", 3)
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
async def _handle_read_memory_md(query, params, session_id):
|
|
1358
|
+
from services.memory_md_sync import read_memory_md
|
|
1359
|
+
return await read_memory_md(
|
|
1360
|
+
project_path=params.get("project_path")
|
|
1361
|
+
)
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
async def _handle_get_memory_md_summary(query, params, session_id):
|
|
1365
|
+
from services.memory_md_sync import get_memory_md_summary
|
|
1366
|
+
return await get_memory_md_summary(
|
|
1367
|
+
project_path=params.get("project_path")
|
|
1368
|
+
)
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
async def _handle_add_memory_md_fact(query, params, session_id):
|
|
1372
|
+
from services.memory_md_sync import add_fact
|
|
1373
|
+
return await add_fact(
|
|
1374
|
+
project_path=params.get("project_path"),
|
|
1375
|
+
fact=params.get("fact", query),
|
|
1376
|
+
section=params.get("section", "anchors")
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
async def _handle_check_flush_needed(query, params, session_id):
|
|
1381
|
+
from services.compaction_flush import check_flush_needed as _check_flush_needed
|
|
1382
|
+
return await _check_flush_needed(
|
|
1383
|
+
db=db,
|
|
1384
|
+
session_id=session_id or params.get("session_id"),
|
|
1385
|
+
event_threshold=params.get("event_threshold", 50),
|
|
1386
|
+
time_threshold_minutes=params.get("time_threshold_minutes", 30)
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
async def _handle_pre_compaction_flush(query, params, session_id):
|
|
1391
|
+
from services.compaction_flush import execute_flush
|
|
1392
|
+
return await execute_flush(
|
|
1393
|
+
db=db,
|
|
1394
|
+
project_path=params.get("project_path"),
|
|
1395
|
+
session_id=session_id or params.get("session_id")
|
|
1396
|
+
)
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
async def _handle_list_flushes(query, params, session_id):
|
|
1400
|
+
from services.compaction_flush import list_flushes as _list_flushes
|
|
1401
|
+
return await _list_flushes(
|
|
1402
|
+
project_path=params.get("project_path"),
|
|
1403
|
+
limit=params.get("limit", 20)
|
|
1404
|
+
)
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
async def _handle_read_flush(query, params, session_id):
|
|
1408
|
+
from services.compaction_flush import read_flush as _read_flush
|
|
1409
|
+
return await _read_flush(
|
|
1410
|
+
project_path=params.get("project_path"),
|
|
1411
|
+
filename=params.get("filename")
|
|
1412
|
+
)
|
|
1413
|
+
|
|
1414
|
+
|
|
1415
|
+
# --- Outcome Spectrum Skills ---
|
|
1416
|
+
|
|
1417
|
+
async def _handle_update_memory_outcome(query, params, session_id):
|
|
1418
|
+
result = await db.update_memory_outcome(
|
|
1419
|
+
memory_id=params.get("memory_id"),
|
|
1420
|
+
outcome_status=params.get("outcome_status"),
|
|
1421
|
+
fixed=params.get("fixed"),
|
|
1422
|
+
did_not_fix=params.get("did_not_fix"),
|
|
1423
|
+
caused=params.get("caused"),
|
|
1424
|
+
superseded_by=params.get("superseded_by")
|
|
1425
|
+
)
|
|
1426
|
+
if result.get("success"):
|
|
1427
|
+
await broadcast_event(
|
|
1428
|
+
EventTypes.MEMORY_UPDATED,
|
|
1429
|
+
{
|
|
1430
|
+
"memory_id": params.get("memory_id"),
|
|
1431
|
+
"outcome_status": params.get("outcome_status"),
|
|
1432
|
+
"action": "outcome_updated"
|
|
1433
|
+
}
|
|
1005
1434
|
)
|
|
1435
|
+
return result
|
|
1006
1436
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1437
|
+
|
|
1438
|
+
async def _handle_supersede_memory(query, params, session_id):
|
|
1439
|
+
result = await db.supersede_memory(
|
|
1440
|
+
old_memory_id=params.get("old_memory_id"),
|
|
1441
|
+
new_memory_id=params.get("new_memory_id"),
|
|
1442
|
+
reason=params.get("reason")
|
|
1443
|
+
)
|
|
1444
|
+
if result.get("success"):
|
|
1445
|
+
await broadcast_event(
|
|
1446
|
+
EventTypes.MEMORY_UPDATED,
|
|
1447
|
+
{
|
|
1448
|
+
"old_memory_id": params.get("old_memory_id"),
|
|
1449
|
+
"new_memory_id": params.get("new_memory_id"),
|
|
1450
|
+
"action": "superseded"
|
|
1451
|
+
}
|
|
1014
1452
|
)
|
|
1453
|
+
return result
|
|
1015
1454
|
|
|
1016
|
-
# ============================================================
|
|
1017
|
-
# MEMORY CLEANUP SKILLS
|
|
1018
|
-
# ============================================================
|
|
1019
1455
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1456
|
+
async def _handle_get_superseding_memory(query, params, session_id):
|
|
1457
|
+
superseding = await db.get_superseding_memory(
|
|
1458
|
+
memory_id=params.get("memory_id")
|
|
1459
|
+
)
|
|
1460
|
+
return {
|
|
1461
|
+
"success": True,
|
|
1462
|
+
"superseded": superseding is not None,
|
|
1463
|
+
"superseding_memory": superseding
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
# --- Curator Agent Skills ---
|
|
1468
|
+
|
|
1469
|
+
async def _handle_curator_explore(query, params, session_id):
|
|
1470
|
+
from skills.curator import curator_explore as _curator_explore
|
|
1471
|
+
return await _curator_explore(
|
|
1472
|
+
db=db,
|
|
1473
|
+
embeddings=embeddings,
|
|
1474
|
+
start_node_id=params.get("start_node_id"),
|
|
1475
|
+
max_depth=params.get("max_depth", 3),
|
|
1476
|
+
mode=params.get("mode", "bfs"),
|
|
1477
|
+
relationship_filter=params.get("relationship_filter")
|
|
1478
|
+
)
|
|
1479
|
+
|
|
1035
1480
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
)
|
|
1481
|
+
async def _handle_curator_find_duplicates(query, params, session_id):
|
|
1482
|
+
from skills.curator import curator_find_duplicates as _curator_find_duplicates
|
|
1483
|
+
return await _curator_find_duplicates(
|
|
1484
|
+
db=db,
|
|
1485
|
+
embeddings=embeddings,
|
|
1486
|
+
project_path=params.get("project_path"),
|
|
1487
|
+
similarity_threshold=params.get("similarity_threshold", 0.92),
|
|
1488
|
+
limit=params.get("limit", 50)
|
|
1489
|
+
)
|
|
1044
1490
|
|
|
1045
|
-
elif skill_id == "restore_memory":
|
|
1046
|
-
return await restore_memory(
|
|
1047
|
-
db=db,
|
|
1048
|
-
embeddings=embeddings,
|
|
1049
|
-
archive_id=params.get("archive_id")
|
|
1050
|
-
)
|
|
1051
1491
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
)
|
|
1492
|
+
async def _handle_curator_suggest_links(query, params, session_id):
|
|
1493
|
+
from skills.curator import curator_suggest_links as _curator_suggest_links
|
|
1494
|
+
return await _curator_suggest_links(
|
|
1495
|
+
db=db,
|
|
1496
|
+
embeddings=embeddings,
|
|
1497
|
+
memory_id=params.get("memory_id"),
|
|
1498
|
+
project_path=params.get("project_path"),
|
|
1499
|
+
similarity_threshold=params.get("similarity_threshold", 0.7),
|
|
1500
|
+
limit=params.get("limit", 20)
|
|
1501
|
+
)
|
|
1058
1502
|
|
|
1059
|
-
elif skill_id == "set_cleanup_config":
|
|
1060
|
-
return await set_cleanup_config(
|
|
1061
|
-
db=db,
|
|
1062
|
-
embeddings=embeddings,
|
|
1063
|
-
project_path=params.get("project_path"),
|
|
1064
|
-
retention_days=params.get("retention_days"),
|
|
1065
|
-
min_relevance_score=params.get("min_relevance_score"),
|
|
1066
|
-
keep_high_importance=params.get("keep_high_importance"),
|
|
1067
|
-
importance_threshold=params.get("importance_threshold"),
|
|
1068
|
-
dedup_enabled=params.get("dedup_enabled"),
|
|
1069
|
-
dedup_threshold=params.get("dedup_threshold"),
|
|
1070
|
-
archive_before_delete=params.get("archive_before_delete"),
|
|
1071
|
-
auto_cleanup_enabled=params.get("auto_cleanup_enabled")
|
|
1072
|
-
)
|
|
1073
1503
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1504
|
+
async def _handle_curator_merge(query, params, session_id):
|
|
1505
|
+
from skills.curator import curator_merge as _curator_merge
|
|
1506
|
+
return await _curator_merge(
|
|
1507
|
+
db=db,
|
|
1508
|
+
embeddings=embeddings,
|
|
1509
|
+
keep_id=params.get("keep_id"),
|
|
1510
|
+
remove_ids=params.get("remove_ids", []),
|
|
1511
|
+
merge_content=params.get("merge_content", False)
|
|
1512
|
+
)
|
|
1076
1513
|
|
|
1077
|
-
elif skill_id == "purge_expired_archives":
|
|
1078
|
-
return await purge_expired_archives(db=db, embeddings=embeddings)
|
|
1079
1514
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1515
|
+
async def _handle_curator_get_summary(query, params, session_id):
|
|
1516
|
+
from skills.curator import curator_get_summary as _curator_get_summary
|
|
1517
|
+
return await _curator_get_summary(
|
|
1518
|
+
db=db,
|
|
1519
|
+
embeddings=embeddings,
|
|
1520
|
+
query=params.get("query", query),
|
|
1521
|
+
project_path=params.get("project_path"),
|
|
1522
|
+
max_memories=params.get("max_memories", 10),
|
|
1523
|
+
include_graph=params.get("include_graph", True)
|
|
1524
|
+
)
|
|
1083
1525
|
|
|
1084
|
-
elif skill_id == "get_embedding_status":
|
|
1085
|
-
return await get_embedding_status(db=db, embeddings=embeddings)
|
|
1086
1526
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
)
|
|
1527
|
+
async def _handle_curator_run_maintenance(query, params, session_id):
|
|
1528
|
+
from skills.curator import curator_run_maintenance as _curator_run_maintenance
|
|
1529
|
+
return await _curator_run_maintenance(
|
|
1530
|
+
db=db,
|
|
1531
|
+
embeddings=embeddings,
|
|
1532
|
+
project_path=params.get("project_path"),
|
|
1533
|
+
tasks=params.get("tasks")
|
|
1534
|
+
)
|
|
1094
1535
|
|
|
1095
|
-
elif skill_id == "reindex_memories":
|
|
1096
|
-
return await reindex_memories(
|
|
1097
|
-
db=db,
|
|
1098
|
-
embeddings=embeddings,
|
|
1099
|
-
model=params.get("model"),
|
|
1100
|
-
project_path=params.get("project_path"),
|
|
1101
|
-
batch_size=params.get("batch_size", 10),
|
|
1102
|
-
dry_run=params.get("dry_run", False)
|
|
1103
|
-
)
|
|
1104
1536
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1537
|
+
async def _handle_curator_get_report(query, params, session_id):
|
|
1538
|
+
from skills.curator import curator_get_report as _curator_get_report
|
|
1539
|
+
return await _curator_get_report(
|
|
1540
|
+
db=db,
|
|
1541
|
+
embeddings=embeddings,
|
|
1542
|
+
project_path=params.get("project_path")
|
|
1543
|
+
)
|
|
1107
1544
|
|
|
1108
|
-
elif skill_id == "cancel_reindex":
|
|
1109
|
-
return await cancel_reindex(db=db, embeddings=embeddings)
|
|
1110
1545
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1546
|
+
async def _handle_curator_get_status(query, params, session_id):
|
|
1547
|
+
from skills.curator import curator_get_status as _curator_get_status
|
|
1548
|
+
return await _curator_get_status(
|
|
1549
|
+
db=db,
|
|
1550
|
+
embeddings=embeddings
|
|
1551
|
+
)
|
|
1117
1552
|
|
|
1118
|
-
elif skill_id == "get_system_stats":
|
|
1119
|
-
return await get_system_stats(db=db, embeddings=embeddings)
|
|
1120
1553
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1554
|
+
async def _handle_curator_score_quality(query, params, session_id):
|
|
1555
|
+
from skills.curator import curator_score_quality as _curator_score_quality
|
|
1556
|
+
return await _curator_score_quality(
|
|
1557
|
+
db=db,
|
|
1558
|
+
embeddings=embeddings,
|
|
1559
|
+
memory_id=params.get("memory_id"),
|
|
1560
|
+
project_path=params.get("project_path"),
|
|
1561
|
+
limit=params.get("limit", 100)
|
|
1562
|
+
)
|
|
1124
1563
|
|
|
1125
|
-
elif skill_id == "daily_log_append":
|
|
1126
|
-
from services.daily_log import append_entry
|
|
1127
|
-
return await append_entry(
|
|
1128
|
-
project_path=params.get("project_path"),
|
|
1129
|
-
content=params.get("content", query),
|
|
1130
|
-
entry_type=params.get("entry_type", "note"),
|
|
1131
|
-
session_id=session_id or params.get("session_id")
|
|
1132
|
-
)
|
|
1133
1564
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
errors_solved=params.get("errors_solved")
|
|
1143
|
-
)
|
|
1565
|
+
async def _handle_curator_find_orphans(query, params, session_id):
|
|
1566
|
+
from skills.curator import curator_find_orphans as _curator_find_orphans
|
|
1567
|
+
return await _curator_find_orphans(
|
|
1568
|
+
db=db,
|
|
1569
|
+
embeddings=embeddings,
|
|
1570
|
+
project_path=params.get("project_path"),
|
|
1571
|
+
limit=params.get("limit", 50)
|
|
1572
|
+
)
|
|
1144
1573
|
|
|
1145
|
-
elif skill_id == "daily_log_read":
|
|
1146
|
-
from services.daily_log import load_recent_logs
|
|
1147
|
-
return await load_recent_logs(
|
|
1148
|
-
project_path=params.get("project_path"),
|
|
1149
|
-
days=params.get("days", 2),
|
|
1150
|
-
max_chars=params.get("max_chars", 8000)
|
|
1151
|
-
)
|
|
1152
1574
|
|
|
1153
|
-
|
|
1154
|
-
from services.daily_log import get_today_highlights
|
|
1155
|
-
return await get_today_highlights(
|
|
1156
|
-
project_path=params.get("project_path"),
|
|
1157
|
-
max_entries=params.get("max_entries", 10)
|
|
1158
|
-
)
|
|
1575
|
+
# --- Memory Decay Skills ---
|
|
1159
1576
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1577
|
+
async def _handle_decay_maintenance(query, params, session_id):
|
|
1578
|
+
from services.memory_decay import MemoryDecayService
|
|
1579
|
+
from config import config
|
|
1580
|
+
decay_service = MemoryDecayService(
|
|
1581
|
+
db=db,
|
|
1582
|
+
archive_threshold=config.DECAY_ARCHIVE_THRESHOLD
|
|
1583
|
+
)
|
|
1584
|
+
return await decay_service.apply_decay()
|
|
1166
1585
|
|
|
1167
|
-
elif skill_id == "sync_memory_md":
|
|
1168
|
-
from services.memory_md_sync import sync_to_memory_md
|
|
1169
|
-
return await sync_to_memory_md(
|
|
1170
|
-
db=db,
|
|
1171
|
-
project_path=params.get("project_path"),
|
|
1172
|
-
min_importance=params.get("min_importance", 7),
|
|
1173
|
-
min_pattern_success=params.get("min_pattern_success", 3)
|
|
1174
|
-
)
|
|
1175
1586
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1587
|
+
async def _handle_decay_stats(query, params, session_id):
|
|
1588
|
+
from services.memory_decay import MemoryDecayService
|
|
1589
|
+
from config import config
|
|
1590
|
+
decay_service = MemoryDecayService(
|
|
1591
|
+
db=db,
|
|
1592
|
+
archive_threshold=config.DECAY_ARCHIVE_THRESHOLD
|
|
1593
|
+
)
|
|
1594
|
+
return await decay_service.get_decay_stats()
|
|
1181
1595
|
|
|
1182
|
-
elif skill_id == "get_memory_md_summary":
|
|
1183
|
-
from services.memory_md_sync import get_memory_md_summary
|
|
1184
|
-
return await get_memory_md_summary(
|
|
1185
|
-
project_path=params.get("project_path")
|
|
1186
|
-
)
|
|
1187
1596
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1597
|
+
async def _handle_decay_boost(query, params, session_id):
|
|
1598
|
+
from services.memory_decay import MemoryDecayService
|
|
1599
|
+
from config import config
|
|
1600
|
+
decay_service = MemoryDecayService(
|
|
1601
|
+
db=db,
|
|
1602
|
+
archive_threshold=config.DECAY_ARCHIVE_THRESHOLD
|
|
1603
|
+
)
|
|
1604
|
+
memory_id = params.get("memory_id")
|
|
1605
|
+
if not memory_id:
|
|
1606
|
+
return {"error": "memory_id is required"}
|
|
1607
|
+
return await decay_service.boost_on_access(int(memory_id))
|
|
1195
1608
|
|
|
1196
|
-
elif skill_id == "check_flush_needed":
|
|
1197
|
-
from services.compaction_flush import check_flush_needed
|
|
1198
|
-
return await check_flush_needed(
|
|
1199
|
-
db=db,
|
|
1200
|
-
session_id=session_id or params.get("session_id"),
|
|
1201
|
-
event_threshold=params.get("event_threshold", 50),
|
|
1202
|
-
time_threshold_minutes=params.get("time_threshold_minutes", 30)
|
|
1203
|
-
)
|
|
1204
1609
|
|
|
1205
|
-
|
|
1206
|
-
from services.compaction_flush import execute_flush
|
|
1207
|
-
return await execute_flush(
|
|
1208
|
-
db=db,
|
|
1209
|
-
project_path=params.get("project_path"),
|
|
1210
|
-
session_id=session_id or params.get("session_id")
|
|
1211
|
-
)
|
|
1610
|
+
# --- Tier 1 Auto-Generation Skill ---
|
|
1212
1611
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
)
|
|
1612
|
+
async def _handle_generate_tier1(query, params, session_id):
|
|
1613
|
+
from services.claude_md_sync import get_claude_md_sync
|
|
1614
|
+
sync_service = get_claude_md_sync(db, embeddings)
|
|
1615
|
+
return await sync_service.write_tier1_to_claude_md(
|
|
1616
|
+
project_path=params.get("project_path"),
|
|
1617
|
+
dry_run=params.get("dry_run", False)
|
|
1618
|
+
)
|
|
1219
1619
|
|
|
1220
|
-
elif skill_id == "read_flush":
|
|
1221
|
-
from services.compaction_flush import read_flush
|
|
1222
|
-
return await read_flush(
|
|
1223
|
-
project_path=params.get("project_path"),
|
|
1224
|
-
filename=params.get("filename")
|
|
1225
|
-
)
|
|
1226
1620
|
|
|
1227
|
-
|
|
1228
|
-
# OUTCOME SPECTRUM SKILLS
|
|
1229
|
-
# ============================================================
|
|
1621
|
+
# --- Session Review Skills ---
|
|
1230
1622
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
superseded_by=params.get("superseded_by")
|
|
1239
|
-
)
|
|
1240
|
-
# Broadcast real-time update
|
|
1241
|
-
if result.get("success"):
|
|
1242
|
-
await broadcast_event(
|
|
1243
|
-
EventTypes.MEMORY_UPDATED,
|
|
1244
|
-
{
|
|
1245
|
-
"memory_id": params.get("memory_id"),
|
|
1246
|
-
"outcome_status": params.get("outcome_status"),
|
|
1247
|
-
"action": "outcome_updated"
|
|
1248
|
-
}
|
|
1249
|
-
)
|
|
1250
|
-
return result
|
|
1623
|
+
async def _handle_get_session_memories(query, params, session_id):
|
|
1624
|
+
return await get_session_memories(
|
|
1625
|
+
db=db,
|
|
1626
|
+
session_id=session_id or params.get("session_id"),
|
|
1627
|
+
include_patterns=params.get("include_patterns", False),
|
|
1628
|
+
limit=params.get("limit", 100)
|
|
1629
|
+
)
|
|
1251
1630
|
|
|
1252
|
-
elif skill_id == "supersede_memory":
|
|
1253
|
-
result = await db.supersede_memory(
|
|
1254
|
-
old_memory_id=params.get("old_memory_id"),
|
|
1255
|
-
new_memory_id=params.get("new_memory_id"),
|
|
1256
|
-
reason=params.get("reason")
|
|
1257
|
-
)
|
|
1258
|
-
# Broadcast real-time update
|
|
1259
|
-
if result.get("success"):
|
|
1260
|
-
await broadcast_event(
|
|
1261
|
-
EventTypes.MEMORY_UPDATED,
|
|
1262
|
-
{
|
|
1263
|
-
"old_memory_id": params.get("old_memory_id"),
|
|
1264
|
-
"new_memory_id": params.get("new_memory_id"),
|
|
1265
|
-
"action": "superseded"
|
|
1266
|
-
}
|
|
1267
|
-
)
|
|
1268
|
-
return result
|
|
1269
1631
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
)
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
"superseded": superseding is not None,
|
|
1277
|
-
"superseding_memory": superseding
|
|
1278
|
-
}
|
|
1632
|
+
async def _handle_review_session_memories(query, params, session_id):
|
|
1633
|
+
return await review_session_memories(
|
|
1634
|
+
db=db,
|
|
1635
|
+
session_id=session_id or params.get("session_id"),
|
|
1636
|
+
reviews=params.get("reviews", [])
|
|
1637
|
+
)
|
|
1279
1638
|
|
|
1280
|
-
# ============================================================
|
|
1281
|
-
# CURATOR AGENT SKILLS
|
|
1282
|
-
# ============================================================
|
|
1283
1639
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
max_depth=params.get("max_depth", 3),
|
|
1291
|
-
mode=params.get("mode", "bfs"),
|
|
1292
|
-
relationship_filter=params.get("relationship_filter")
|
|
1293
|
-
)
|
|
1640
|
+
async def _handle_suggest_session_reviews(query, params, session_id):
|
|
1641
|
+
return await suggest_session_reviews(
|
|
1642
|
+
db=db,
|
|
1643
|
+
embeddings=embeddings,
|
|
1644
|
+
session_id=session_id or params.get("session_id")
|
|
1645
|
+
)
|
|
1294
1646
|
|
|
1295
|
-
elif skill_id == "curator_find_duplicates":
|
|
1296
|
-
from skills.curator import curator_find_duplicates
|
|
1297
|
-
return await curator_find_duplicates(
|
|
1298
|
-
db=db,
|
|
1299
|
-
embeddings=embeddings,
|
|
1300
|
-
project_path=params.get("project_path"),
|
|
1301
|
-
similarity_threshold=params.get("similarity_threshold", 0.92),
|
|
1302
|
-
limit=params.get("limit", 50)
|
|
1303
|
-
)
|
|
1304
1647
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
project_path=params.get("project_path"),
|
|
1312
|
-
similarity_threshold=params.get("similarity_threshold", 0.7),
|
|
1313
|
-
limit=params.get("limit", 20)
|
|
1314
|
-
)
|
|
1648
|
+
async def _handle_get_recent_sessions(query, params, session_id):
|
|
1649
|
+
return await get_recent_sessions(
|
|
1650
|
+
db=db,
|
|
1651
|
+
project_path=params.get("project_path"),
|
|
1652
|
+
limit=params.get("limit", 10)
|
|
1653
|
+
)
|
|
1315
1654
|
|
|
1316
|
-
elif skill_id == "curator_merge":
|
|
1317
|
-
from skills.curator import curator_merge
|
|
1318
|
-
return await curator_merge(
|
|
1319
|
-
db=db,
|
|
1320
|
-
embeddings=embeddings,
|
|
1321
|
-
keep_id=params.get("keep_id"),
|
|
1322
|
-
remove_ids=params.get("remove_ids", []),
|
|
1323
|
-
merge_content=params.get("merge_content", False)
|
|
1324
|
-
)
|
|
1325
1655
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
project_path=params.get("project_path"),
|
|
1333
|
-
max_memories=params.get("max_memories", 10),
|
|
1334
|
-
include_graph=params.get("include_graph", True)
|
|
1335
|
-
)
|
|
1656
|
+
async def _handle_bulk_review_by_type(query, params, session_id):
|
|
1657
|
+
return await bulk_review_by_type(
|
|
1658
|
+
db=db,
|
|
1659
|
+
session_id=session_id or params.get("session_id"),
|
|
1660
|
+
type_decisions=params.get("type_decisions", {})
|
|
1661
|
+
)
|
|
1336
1662
|
|
|
1337
|
-
elif skill_id == "curator_run_maintenance":
|
|
1338
|
-
from skills.curator import curator_run_maintenance
|
|
1339
|
-
return await curator_run_maintenance(
|
|
1340
|
-
db=db,
|
|
1341
|
-
embeddings=embeddings,
|
|
1342
|
-
project_path=params.get("project_path"),
|
|
1343
|
-
tasks=params.get("tasks")
|
|
1344
|
-
)
|
|
1345
1663
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
)
|
|
1664
|
+
# ============================================================
|
|
1665
|
+
# SKILL DISPATCH TABLE
|
|
1666
|
+
# ============================================================
|
|
1667
|
+
# Maps skill_id strings to their async handler functions.
|
|
1668
|
+
# All handlers share the signature: (query, params, session_id) -> dict
|
|
1669
|
+
# ============================================================
|
|
1353
1670
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1671
|
+
SKILL_DISPATCH = {
|
|
1672
|
+
# Core Memory
|
|
1673
|
+
"store_memory": _handle_store_memory,
|
|
1674
|
+
"store_project": _handle_store_project,
|
|
1675
|
+
"store_pattern": _handle_store_pattern,
|
|
1676
|
+
"retrieve_memory": _handle_retrieve_memory,
|
|
1677
|
+
"semantic_search": _handle_semantic_search,
|
|
1678
|
+
"search_patterns": _handle_search_patterns,
|
|
1679
|
+
"get_project_context": _handle_get_project_context,
|
|
1680
|
+
|
|
1681
|
+
# Session
|
|
1682
|
+
"summarize_session": _handle_summarize_session,
|
|
1683
|
+
"auto_summarize_session": _handle_auto_summarize_session,
|
|
1684
|
+
"get_session_handoff": _handle_get_session_handoff,
|
|
1685
|
+
"create_diary_entry": _handle_create_diary_entry,
|
|
1686
|
+
"check_session_inactivity": _handle_check_session_inactivity,
|
|
1687
|
+
"get_stats": _handle_get_stats,
|
|
1688
|
+
|
|
1689
|
+
# Timeline
|
|
1690
|
+
"timeline_log": _handle_timeline_log,
|
|
1691
|
+
"timeline_log_batch": _handle_timeline_log_batch,
|
|
1692
|
+
"timeline_get": _handle_timeline_get,
|
|
1693
|
+
"timeline_search": _handle_timeline_search,
|
|
1694
|
+
"timeline_auto_detect": _handle_timeline_auto_detect,
|
|
1695
|
+
"timeline_chain": _handle_timeline_chain,
|
|
1696
|
+
|
|
1697
|
+
# State
|
|
1698
|
+
"state_get": _handle_state_get,
|
|
1699
|
+
"state_update": _handle_state_update,
|
|
1700
|
+
"state_init_session": _handle_state_init_session,
|
|
1701
|
+
|
|
1702
|
+
# Checkpoint
|
|
1703
|
+
"checkpoint_create": _handle_checkpoint_create,
|
|
1704
|
+
"checkpoint_load": _handle_checkpoint_load,
|
|
1705
|
+
"checkpoint_list": _handle_checkpoint_list,
|
|
1706
|
+
|
|
1707
|
+
# Grounding (Anti-Hallucination)
|
|
1708
|
+
"context_refresh": _handle_context_refresh,
|
|
1709
|
+
"check_contradictions": _handle_check_contradictions,
|
|
1710
|
+
"verify_entity": _handle_verify_entity,
|
|
1711
|
+
"mark_anchor": _handle_mark_anchor,
|
|
1712
|
+
"get_unresolved_conflicts": _handle_get_unresolved_conflicts,
|
|
1713
|
+
"resolve_conflict": _handle_resolve_conflict,
|
|
1714
|
+
"get_anchor_history": _handle_get_anchor_history,
|
|
1715
|
+
"auto_resolve_conflicts": _handle_auto_resolve_conflicts,
|
|
1716
|
+
|
|
1717
|
+
# Self-Correcting Confidence
|
|
1718
|
+
"memory_worked": _handle_memory_worked,
|
|
1719
|
+
"memory_failed": _handle_memory_failed,
|
|
1720
|
+
"get_reliability_stats": _handle_get_reliability_stats,
|
|
1721
|
+
"get_unreliable_memories": _handle_get_unreliable_memories,
|
|
1722
|
+
"reset_memory_reliability": _handle_reset_memory_reliability,
|
|
1723
|
+
|
|
1724
|
+
# CLAUDE.MD Management
|
|
1725
|
+
"claude_md_read": _handle_claude_md_read,
|
|
1726
|
+
"claude_md_add_section": _handle_claude_md_add_section,
|
|
1727
|
+
"claude_md_update_section": _handle_claude_md_update_section,
|
|
1728
|
+
"claude_md_add_instruction": _handle_claude_md_add_instruction,
|
|
1729
|
+
"claude_md_list_sections": _handle_claude_md_list_sections,
|
|
1730
|
+
"claude_md_suggest": _handle_claude_md_suggest,
|
|
1731
|
+
|
|
1732
|
+
# Verification
|
|
1733
|
+
"best_of_n_verify": _handle_best_of_n_verify,
|
|
1734
|
+
"extract_quotes": _handle_extract_quotes,
|
|
1735
|
+
"require_grounding": _handle_require_grounding,
|
|
1736
|
+
|
|
1737
|
+
# Cross-Session Learning
|
|
1738
|
+
"run_aggregation": _handle_run_aggregation,
|
|
1739
|
+
"get_insights": _handle_get_insights,
|
|
1740
|
+
"suggest_improvements": _handle_suggest_improvements,
|
|
1741
|
+
"record_insight_feedback": _handle_record_insight_feedback,
|
|
1742
|
+
"mark_insight_applied": _handle_mark_insight_applied,
|
|
1743
|
+
"get_project_insights": _handle_get_project_insights,
|
|
1744
|
+
|
|
1745
|
+
# Memory Cleanup
|
|
1746
|
+
"memory_cleanup": _handle_memory_cleanup,
|
|
1747
|
+
"get_archived_memories": _handle_get_archived_memories,
|
|
1748
|
+
"restore_memory": _handle_restore_memory,
|
|
1749
|
+
"get_cleanup_config": _handle_get_cleanup_config,
|
|
1750
|
+
"set_cleanup_config": _handle_set_cleanup_config,
|
|
1751
|
+
"get_cleanup_stats": _handle_get_cleanup_stats,
|
|
1752
|
+
"purge_expired_archives": _handle_purge_expired_archives,
|
|
1753
|
+
|
|
1754
|
+
# Admin (Embedding Model Management)
|
|
1755
|
+
"get_embedding_status": _handle_get_embedding_status,
|
|
1756
|
+
"switch_embedding_model": _handle_switch_embedding_model,
|
|
1757
|
+
"reindex_memories": _handle_reindex_memories,
|
|
1758
|
+
"get_reindex_progress": _handle_get_reindex_progress,
|
|
1759
|
+
"cancel_reindex": _handle_cancel_reindex,
|
|
1760
|
+
"get_model_info": _handle_get_model_info,
|
|
1761
|
+
"get_system_stats": _handle_get_system_stats,
|
|
1762
|
+
|
|
1763
|
+
# MoltBot-Inspired (Human-Readable Transparency)
|
|
1764
|
+
"daily_log_append": _handle_daily_log_append,
|
|
1765
|
+
"daily_log_append_session": _handle_daily_log_append_session,
|
|
1766
|
+
"daily_log_read": _handle_daily_log_read,
|
|
1767
|
+
"daily_log_highlights": _handle_daily_log_highlights,
|
|
1768
|
+
"daily_log_list": _handle_daily_log_list,
|
|
1769
|
+
"sync_memory_md": _handle_sync_memory_md,
|
|
1770
|
+
"read_memory_md": _handle_read_memory_md,
|
|
1771
|
+
"get_memory_md_summary": _handle_get_memory_md_summary,
|
|
1772
|
+
"add_memory_md_fact": _handle_add_memory_md_fact,
|
|
1773
|
+
"check_flush_needed": _handle_check_flush_needed,
|
|
1774
|
+
"pre_compaction_flush": _handle_pre_compaction_flush,
|
|
1775
|
+
"list_flushes": _handle_list_flushes,
|
|
1776
|
+
"read_flush": _handle_read_flush,
|
|
1777
|
+
|
|
1778
|
+
# Outcome Spectrum
|
|
1779
|
+
"update_memory_outcome": _handle_update_memory_outcome,
|
|
1780
|
+
"supersede_memory": _handle_supersede_memory,
|
|
1781
|
+
"get_superseding_memory": _handle_get_superseding_memory,
|
|
1782
|
+
|
|
1783
|
+
# Curator Agent
|
|
1784
|
+
"curator_explore": _handle_curator_explore,
|
|
1785
|
+
"curator_find_duplicates": _handle_curator_find_duplicates,
|
|
1786
|
+
"curator_suggest_links": _handle_curator_suggest_links,
|
|
1787
|
+
"curator_merge": _handle_curator_merge,
|
|
1788
|
+
"curator_get_summary": _handle_curator_get_summary,
|
|
1789
|
+
"curator_run_maintenance": _handle_curator_run_maintenance,
|
|
1790
|
+
"curator_get_report": _handle_curator_get_report,
|
|
1791
|
+
"curator_get_status": _handle_curator_get_status,
|
|
1792
|
+
"curator_score_quality": _handle_curator_score_quality,
|
|
1793
|
+
"curator_find_orphans": _handle_curator_find_orphans,
|
|
1794
|
+
|
|
1795
|
+
# Memory Decay
|
|
1796
|
+
"decay_maintenance": _handle_decay_maintenance,
|
|
1797
|
+
"decay_stats": _handle_decay_stats,
|
|
1798
|
+
"decay_boost": _handle_decay_boost,
|
|
1799
|
+
|
|
1800
|
+
# Tier 1 Auto-Generation
|
|
1801
|
+
"generate_tier1": _handle_generate_tier1,
|
|
1802
|
+
|
|
1803
|
+
# Session Review
|
|
1804
|
+
"get_session_memories": _handle_get_session_memories,
|
|
1805
|
+
"review_session_memories": _handle_review_session_memories,
|
|
1806
|
+
"suggest_session_reviews": _handle_suggest_session_reviews,
|
|
1807
|
+
"get_recent_sessions": _handle_get_recent_sessions,
|
|
1808
|
+
"bulk_review_by_type": _handle_bulk_review_by_type,
|
|
1809
|
+
}
|
|
1360
1810
|
|
|
1361
|
-
elif skill_id == "curator_score_quality":
|
|
1362
|
-
from skills.curator import curator_score_quality
|
|
1363
|
-
return await curator_score_quality(
|
|
1364
|
-
db=db,
|
|
1365
|
-
embeddings=embeddings,
|
|
1366
|
-
memory_id=params.get("memory_id"),
|
|
1367
|
-
project_path=params.get("project_path"),
|
|
1368
|
-
limit=params.get("limit", 100)
|
|
1369
|
-
)
|
|
1370
1811
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
)
|
|
1812
|
+
async def execute_skill(
|
|
1813
|
+
skill_id: str,
|
|
1814
|
+
query: str,
|
|
1815
|
+
params: Dict[str, Any],
|
|
1816
|
+
session_id: Optional[str] = None
|
|
1817
|
+
) -> Dict[str, Any]:
|
|
1818
|
+
"""Execute the specified skill with enhanced context support.
|
|
1379
1819
|
|
|
1380
|
-
|
|
1820
|
+
Dispatches to the appropriate handler via SKILL_DISPATCH lookup table.
|
|
1821
|
+
Each handler receives (query, params, session_id) and returns a dict.
|
|
1822
|
+
"""
|
|
1823
|
+
handler = SKILL_DISPATCH.get(skill_id)
|
|
1824
|
+
if handler is None:
|
|
1381
1825
|
raise ValueError(f"Unknown skill: {skill_id}")
|
|
1826
|
+
return await handler(query, params, session_id)
|
|
1382
1827
|
|
|
1383
1828
|
|
|
1384
1829
|
# ============= REST API Endpoints =============
|
|
@@ -1416,6 +1861,7 @@ async def api_get_stats():
|
|
|
1416
1861
|
stats["timeline_stats_error"] = f"Unexpected error: {str(e)}"
|
|
1417
1862
|
|
|
1418
1863
|
stats["success"] = True
|
|
1864
|
+
stats["operation_metrics"] = metrics.to_dict()
|
|
1419
1865
|
return stats
|
|
1420
1866
|
|
|
1421
1867
|
|
|
@@ -1538,7 +1984,12 @@ async def api_get_timeline(
|
|
|
1538
1984
|
):
|
|
1539
1985
|
"""Get timeline events with optional filtering."""
|
|
1540
1986
|
try:
|
|
1541
|
-
|
|
1987
|
+
# Exclude the embedding column - it's a large binary blob that makes responses huge
|
|
1988
|
+
query = """SELECT id, session_id, project_path, event_type, sequence_num,
|
|
1989
|
+
summary, details, parent_event_id, root_event_id, entities,
|
|
1990
|
+
status, outcome, confidence, is_anchor, is_reversible,
|
|
1991
|
+
needs_verification, created_at
|
|
1992
|
+
FROM timeline_events WHERE 1=1"""
|
|
1542
1993
|
params = []
|
|
1543
1994
|
|
|
1544
1995
|
if project_path:
|
|
@@ -2168,6 +2619,57 @@ async def liveness_check():
|
|
|
2168
2619
|
return {"alive": True, "timestamp": datetime.now().isoformat()}
|
|
2169
2620
|
|
|
2170
2621
|
|
|
2622
|
+
@app.post("/health/retry")
|
|
2623
|
+
async def retry_connection():
|
|
2624
|
+
"""Force retry connection to Ollama.
|
|
2625
|
+
|
|
2626
|
+
Clears health check cache and attempts to reconnect.
|
|
2627
|
+
Used by dashboard to manually trigger recovery.
|
|
2628
|
+
"""
|
|
2629
|
+
# Force health check with fresh attempt
|
|
2630
|
+
health = await embeddings.check_health(force=True)
|
|
2631
|
+
|
|
2632
|
+
# If still unhealthy, try to ping Ollama directly
|
|
2633
|
+
if not health.get("healthy"):
|
|
2634
|
+
try:
|
|
2635
|
+
import subprocess
|
|
2636
|
+
import platform
|
|
2637
|
+
|
|
2638
|
+
# Try to check if Ollama is running
|
|
2639
|
+
if platform.system() == "Windows":
|
|
2640
|
+
# On Windows, try to start Ollama if not running
|
|
2641
|
+
result = subprocess.run(
|
|
2642
|
+
["tasklist", "/FI", "IMAGENAME eq ollama.exe"],
|
|
2643
|
+
capture_output=True,
|
|
2644
|
+
text=True,
|
|
2645
|
+
timeout=5
|
|
2646
|
+
)
|
|
2647
|
+
ollama_running = "ollama.exe" in result.stdout
|
|
2648
|
+
|
|
2649
|
+
if not ollama_running:
|
|
2650
|
+
# Try to start Ollama
|
|
2651
|
+
subprocess.Popen(
|
|
2652
|
+
["ollama", "serve"],
|
|
2653
|
+
creationflags=subprocess.CREATE_NO_WINDOW,
|
|
2654
|
+
stdout=subprocess.DEVNULL,
|
|
2655
|
+
stderr=subprocess.DEVNULL
|
|
2656
|
+
)
|
|
2657
|
+
# Wait a moment for startup
|
|
2658
|
+
import asyncio
|
|
2659
|
+
await asyncio.sleep(2)
|
|
2660
|
+
# Retry health check
|
|
2661
|
+
health = await embeddings.check_health(force=True)
|
|
2662
|
+
except Exception as e:
|
|
2663
|
+
logger.warning(f"Could not auto-start Ollama: {e}")
|
|
2664
|
+
|
|
2665
|
+
return {
|
|
2666
|
+
"success": health.get("healthy", False),
|
|
2667
|
+
"health": health,
|
|
2668
|
+
"message": "Healthy" if health.get("healthy") else "Still degraded - check if Ollama is running",
|
|
2669
|
+
"timestamp": datetime.now().isoformat()
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
|
|
2171
2673
|
@app.get("/api/index-stats")
|
|
2172
2674
|
async def get_index_stats():
|
|
2173
2675
|
"""Get FAISS vector index statistics.
|
|
@@ -3581,21 +4083,33 @@ async def get_all_agents():
|
|
|
3581
4083
|
|
|
3582
4084
|
@app.get("/api/mcps")
|
|
3583
4085
|
async def get_all_mcps():
|
|
3584
|
-
"""Get all available MCP servers."""
|
|
4086
|
+
"""Get all available MCP servers with live configured status."""
|
|
4087
|
+
mcps = load_configured_mcps()
|
|
4088
|
+
configured_count = sum(1 for m in mcps if m.get("configured"))
|
|
3585
4089
|
return {
|
|
3586
4090
|
"success": True,
|
|
3587
|
-
"mcps":
|
|
3588
|
-
"total": len(
|
|
4091
|
+
"mcps": mcps,
|
|
4092
|
+
"total": len(mcps),
|
|
4093
|
+
"configured": configured_count
|
|
3589
4094
|
}
|
|
3590
4095
|
|
|
3591
4096
|
|
|
3592
4097
|
@app.get("/api/hooks")
|
|
3593
4098
|
async def get_all_hooks():
|
|
3594
|
-
"""Get all available hooks."""
|
|
4099
|
+
"""Get all available hooks with live configured status."""
|
|
4100
|
+
hooks = load_configured_hooks()
|
|
4101
|
+
configured_count = sum(1 for h in hooks if h.get("configured"))
|
|
4102
|
+
# Group by trigger for dashboard convenience
|
|
4103
|
+
by_trigger: dict = {}
|
|
4104
|
+
for h in hooks:
|
|
4105
|
+
trigger = h.get("trigger", "Unknown")
|
|
4106
|
+
by_trigger.setdefault(trigger, []).append(h)
|
|
3595
4107
|
return {
|
|
3596
4108
|
"success": True,
|
|
3597
|
-
"hooks":
|
|
3598
|
-
"total": len(
|
|
4109
|
+
"hooks": hooks,
|
|
4110
|
+
"total": len(hooks),
|
|
4111
|
+
"configured": configured_count,
|
|
4112
|
+
"by_trigger": by_trigger
|
|
3599
4113
|
}
|
|
3600
4114
|
|
|
3601
4115
|
|
|
@@ -3647,7 +4161,8 @@ async def get_project_config(project_path: str):
|
|
|
3647
4161
|
'settings': {}
|
|
3648
4162
|
}
|
|
3649
4163
|
|
|
3650
|
-
# Build MCP status map
|
|
4164
|
+
# Build MCP status map using dynamic loader
|
|
4165
|
+
live_mcps = load_configured_mcps(project_path)
|
|
3651
4166
|
mcp_status = {}
|
|
3652
4167
|
for config in (mcp_configs or []):
|
|
3653
4168
|
mcp_status[config['mcp_id']] = {
|
|
@@ -3655,14 +4170,18 @@ async def get_project_config(project_path: str):
|
|
|
3655
4170
|
'settings': json.loads(config['settings']) if config['settings'] else {}
|
|
3656
4171
|
}
|
|
3657
4172
|
|
|
3658
|
-
for mcp in
|
|
4173
|
+
for mcp in live_mcps:
|
|
3659
4174
|
if mcp['id'] not in mcp_status:
|
|
3660
4175
|
mcp_status[mcp['id']] = {
|
|
3661
4176
|
'enabled': mcp['default_enabled'],
|
|
4177
|
+
'configured': mcp.get('configured', False),
|
|
3662
4178
|
'settings': {}
|
|
3663
4179
|
}
|
|
4180
|
+
else:
|
|
4181
|
+
mcp_status[mcp['id']]['configured'] = mcp.get('configured', False)
|
|
3664
4182
|
|
|
3665
|
-
# Build hook status map
|
|
4183
|
+
# Build hook status map using dynamic loader
|
|
4184
|
+
live_hooks = load_configured_hooks(project_path)
|
|
3666
4185
|
hook_status = {}
|
|
3667
4186
|
for config in (hook_configs or []):
|
|
3668
4187
|
hook_status[config['hook_id']] = {
|
|
@@ -3670,12 +4189,17 @@ async def get_project_config(project_path: str):
|
|
|
3670
4189
|
'settings': json.loads(config['settings']) if config['settings'] else {}
|
|
3671
4190
|
}
|
|
3672
4191
|
|
|
3673
|
-
for hook in
|
|
4192
|
+
for hook in live_hooks:
|
|
3674
4193
|
if hook['id'] not in hook_status:
|
|
3675
4194
|
hook_status[hook['id']] = {
|
|
3676
4195
|
'enabled': hook['default_enabled'],
|
|
4196
|
+
'configured': hook.get('configured', False),
|
|
4197
|
+
'trigger': hook.get('trigger', ''),
|
|
3677
4198
|
'settings': {}
|
|
3678
4199
|
}
|
|
4200
|
+
else:
|
|
4201
|
+
hook_status[hook['id']]['configured'] = hook.get('configured', False)
|
|
4202
|
+
hook_status[hook['id']]['trigger'] = hook.get('trigger', '')
|
|
3679
4203
|
|
|
3680
4204
|
return {
|
|
3681
4205
|
"success": True,
|
|
@@ -3687,10 +4211,12 @@ async def get_project_config(project_path: str):
|
|
|
3687
4211
|
"stats": {
|
|
3688
4212
|
"enabled_agents": sum(1 for a in agent_status.values() if a['enabled']),
|
|
3689
4213
|
"total_agents": len(AVAILABLE_AGENTS),
|
|
3690
|
-
"enabled_mcps": sum(1 for m in mcp_status.values() if m
|
|
3691
|
-
"total_mcps": len(
|
|
3692
|
-
"
|
|
3693
|
-
"
|
|
4214
|
+
"enabled_mcps": sum(1 for m in mcp_status.values() if m.get('enabled')),
|
|
4215
|
+
"total_mcps": len(live_mcps),
|
|
4216
|
+
"configured_mcps": sum(1 for m in mcp_status.values() if m.get('configured')),
|
|
4217
|
+
"enabled_hooks": sum(1 for h in hook_status.values() if h.get('enabled')),
|
|
4218
|
+
"total_hooks": len(live_hooks),
|
|
4219
|
+
"configured_hooks": sum(1 for h in hook_status.values() if h.get('configured'))
|
|
3694
4220
|
}
|
|
3695
4221
|
}
|
|
3696
4222
|
except DatabaseError as e:
|
|
@@ -4029,7 +4555,7 @@ async def get_graph_node(memory_id: int):
|
|
|
4029
4555
|
"""
|
|
4030
4556
|
try:
|
|
4031
4557
|
# Get the memory itself
|
|
4032
|
-
memory = await db.
|
|
4558
|
+
memory = await db.get_memory(memory_id)
|
|
4033
4559
|
if not memory:
|
|
4034
4560
|
return {"success": False, "error": "Memory not found"}
|
|
4035
4561
|
|
|
@@ -4486,6 +5012,224 @@ async def curator_config_update_endpoint(
|
|
|
4486
5012
|
return {"success": False, "error": str(e)}
|
|
4487
5013
|
|
|
4488
5014
|
|
|
5015
|
+
# ============= Memory Decay API Endpoints =============
|
|
5016
|
+
|
|
5017
|
+
@app.post("/api/decay/run")
|
|
5018
|
+
async def decay_run_endpoint():
|
|
5019
|
+
"""Run memory decay maintenance - evaluate all decayable memories and archive expired ones."""
|
|
5020
|
+
try:
|
|
5021
|
+
from services.memory_decay import MemoryDecayService
|
|
5022
|
+
from config import config as app_config
|
|
5023
|
+
decay_service = MemoryDecayService(
|
|
5024
|
+
db=db,
|
|
5025
|
+
archive_threshold=app_config.DECAY_ARCHIVE_THRESHOLD
|
|
5026
|
+
)
|
|
5027
|
+
result = await decay_service.apply_decay()
|
|
5028
|
+
return result
|
|
5029
|
+
except Exception as e:
|
|
5030
|
+
logger.error(f"Decay maintenance failed: {e}")
|
|
5031
|
+
return {"success": False, "error": str(e)}
|
|
5032
|
+
|
|
5033
|
+
|
|
5034
|
+
@app.get("/api/decay/stats")
|
|
5035
|
+
async def decay_stats_endpoint():
|
|
5036
|
+
"""Get memory decay statistics - permanent vs decayable counts, at-risk memories."""
|
|
5037
|
+
try:
|
|
5038
|
+
from services.memory_decay import MemoryDecayService
|
|
5039
|
+
from config import config as app_config
|
|
5040
|
+
decay_service = MemoryDecayService(
|
|
5041
|
+
db=db,
|
|
5042
|
+
archive_threshold=app_config.DECAY_ARCHIVE_THRESHOLD
|
|
5043
|
+
)
|
|
5044
|
+
result = await decay_service.get_decay_stats()
|
|
5045
|
+
return result
|
|
5046
|
+
except Exception as e:
|
|
5047
|
+
logger.error(f"Decay stats failed: {e}")
|
|
5048
|
+
return {"success": False, "error": str(e)}
|
|
5049
|
+
|
|
5050
|
+
|
|
5051
|
+
@app.post("/api/decay/boost/{memory_id}")
|
|
5052
|
+
async def decay_boost_endpoint(memory_id: int):
|
|
5053
|
+
"""Boost a memory's access count to resist decay."""
|
|
5054
|
+
try:
|
|
5055
|
+
from services.memory_decay import MemoryDecayService
|
|
5056
|
+
from config import config as app_config
|
|
5057
|
+
decay_service = MemoryDecayService(
|
|
5058
|
+
db=db,
|
|
5059
|
+
archive_threshold=app_config.DECAY_ARCHIVE_THRESHOLD
|
|
5060
|
+
)
|
|
5061
|
+
result = await decay_service.boost_on_access(memory_id)
|
|
5062
|
+
return result
|
|
5063
|
+
except Exception as e:
|
|
5064
|
+
logger.error(f"Decay boost failed: {e}")
|
|
5065
|
+
return {"success": False, "error": str(e)}
|
|
5066
|
+
|
|
5067
|
+
|
|
5068
|
+
# ============= Tier 1 Auto-Generation API Endpoint =============
|
|
5069
|
+
|
|
5070
|
+
@app.post("/api/tier1/generate")
|
|
5071
|
+
async def tier1_generate_endpoint(request: Request):
|
|
5072
|
+
"""Generate Tier 1 context from top memories and write to CLAUDE.md.
|
|
5073
|
+
|
|
5074
|
+
Auto-generates a ranked summary of the most important memories and
|
|
5075
|
+
writes it into CLAUDE.md between marker comments. All manually-written
|
|
5076
|
+
content in CLAUDE.md is preserved.
|
|
5077
|
+
|
|
5078
|
+
Optional JSON body:
|
|
5079
|
+
project_path: Filter to a specific project
|
|
5080
|
+
dry_run: If true, return preview without writing (default false)
|
|
5081
|
+
"""
|
|
5082
|
+
try:
|
|
5083
|
+
body = {}
|
|
5084
|
+
try:
|
|
5085
|
+
body = await request.json()
|
|
5086
|
+
except Exception:
|
|
5087
|
+
pass # No body is fine, all params are optional
|
|
5088
|
+
|
|
5089
|
+
from services.claude_md_sync import get_claude_md_sync
|
|
5090
|
+
sync_service = get_claude_md_sync(db, embeddings)
|
|
5091
|
+
result = await sync_service.write_tier1_to_claude_md(
|
|
5092
|
+
project_path=body.get("project_path"),
|
|
5093
|
+
dry_run=body.get("dry_run", False)
|
|
5094
|
+
)
|
|
5095
|
+
return result
|
|
5096
|
+
except Exception as e:
|
|
5097
|
+
logger.error(f"Tier 1 generation failed: {e}")
|
|
5098
|
+
return {"success": False, "error": str(e)}
|
|
5099
|
+
|
|
5100
|
+
|
|
5101
|
+
# ============= CLaRa-Inspired Memory Enhancement Endpoints =============
|
|
5102
|
+
|
|
5103
|
+
@app.get("/api/tiers/stats")
|
|
5104
|
+
async def tier_stats_endpoint():
|
|
5105
|
+
"""Get memory distribution across tiers (hot/warm/cold)."""
|
|
5106
|
+
try:
|
|
5107
|
+
from services.tier_manager import TierManager
|
|
5108
|
+
tier_mgr = TierManager(db)
|
|
5109
|
+
return await tier_mgr.get_tier_stats()
|
|
5110
|
+
except Exception as e:
|
|
5111
|
+
logger.error(f"Tier stats failed: {e}")
|
|
5112
|
+
return {"error": str(e)}
|
|
5113
|
+
|
|
5114
|
+
|
|
5115
|
+
@app.post("/api/tiers/maintenance")
|
|
5116
|
+
async def tier_maintenance_endpoint(request: Request):
|
|
5117
|
+
"""Run tier maintenance (evaluate and update all memory tiers)."""
|
|
5118
|
+
try:
|
|
5119
|
+
body = {}
|
|
5120
|
+
try:
|
|
5121
|
+
body = await request.json()
|
|
5122
|
+
except Exception:
|
|
5123
|
+
pass
|
|
5124
|
+
from services.tier_manager import TierManager
|
|
5125
|
+
tier_mgr = TierManager(db)
|
|
5126
|
+
return await tier_mgr.run_tier_maintenance(
|
|
5127
|
+
skip_recent_hours=body.get("skip_recent_hours", 24)
|
|
5128
|
+
)
|
|
5129
|
+
except Exception as e:
|
|
5130
|
+
logger.error(f"Tier maintenance failed: {e}")
|
|
5131
|
+
return {"error": str(e)}
|
|
5132
|
+
|
|
5133
|
+
|
|
5134
|
+
@app.post("/api/consolidation/run")
|
|
5135
|
+
async def consolidation_run_endpoint():
|
|
5136
|
+
"""Manually trigger memory consolidation."""
|
|
5137
|
+
try:
|
|
5138
|
+
from services.consolidation import ConsolidationService
|
|
5139
|
+
consolidator = ConsolidationService(db, embeddings)
|
|
5140
|
+
return await consolidator.run_consolidation()
|
|
5141
|
+
except Exception as e:
|
|
5142
|
+
logger.error(f"Consolidation failed: {e}")
|
|
5143
|
+
return {"error": str(e)}
|
|
5144
|
+
|
|
5145
|
+
|
|
5146
|
+
@app.get("/api/consolidation/candidates")
|
|
5147
|
+
async def consolidation_candidates_endpoint():
|
|
5148
|
+
"""Preview consolidation candidates without executing."""
|
|
5149
|
+
try:
|
|
5150
|
+
from services.consolidation import ConsolidationService
|
|
5151
|
+
consolidator = ConsolidationService(db, embeddings)
|
|
5152
|
+
groups = await consolidator.find_consolidation_candidates()
|
|
5153
|
+
return {
|
|
5154
|
+
"candidate_groups": len(groups),
|
|
5155
|
+
"groups": [
|
|
5156
|
+
{
|
|
5157
|
+
"size": len(g),
|
|
5158
|
+
"types": list(set(m.get('type', 'chunk') for m in g)),
|
|
5159
|
+
"ids": [m['id'] for m in g],
|
|
5160
|
+
"preview": g[0]['content'][:100] if g else ''
|
|
5161
|
+
}
|
|
5162
|
+
for g in groups
|
|
5163
|
+
]
|
|
5164
|
+
}
|
|
5165
|
+
except Exception as e:
|
|
5166
|
+
logger.error(f"Consolidation candidates failed: {e}")
|
|
5167
|
+
return {"error": str(e)}
|
|
5168
|
+
|
|
5169
|
+
|
|
5170
|
+
@app.post("/api/consolidation/{consolidated_id}/restore")
|
|
5171
|
+
async def consolidation_restore_endpoint(consolidated_id: int):
|
|
5172
|
+
"""Deconsolidate: restore original memories from a consolidated memory."""
|
|
5173
|
+
try:
|
|
5174
|
+
from services.consolidation import ConsolidationService
|
|
5175
|
+
consolidator = ConsolidationService(db, embeddings)
|
|
5176
|
+
return await consolidator.deconsolidate(consolidated_id)
|
|
5177
|
+
except Exception as e:
|
|
5178
|
+
logger.error(f"Deconsolidation failed: {e}")
|
|
5179
|
+
return {"error": str(e)}
|
|
5180
|
+
|
|
5181
|
+
|
|
5182
|
+
@app.get("/api/consolidation/stats")
|
|
5183
|
+
async def consolidation_stats_endpoint():
|
|
5184
|
+
"""Get consolidation statistics."""
|
|
5185
|
+
try:
|
|
5186
|
+
from services.consolidation import ConsolidationService
|
|
5187
|
+
consolidator = ConsolidationService(db, embeddings)
|
|
5188
|
+
return await consolidator.get_consolidation_stats()
|
|
5189
|
+
except Exception as e:
|
|
5190
|
+
logger.error(f"Consolidation stats failed: {e}")
|
|
5191
|
+
return {"error": str(e)}
|
|
5192
|
+
|
|
5193
|
+
|
|
5194
|
+
@app.get("/api/embedding-pipeline/stats")
|
|
5195
|
+
async def embedding_pipeline_stats_endpoint():
|
|
5196
|
+
"""Get embedding pipeline statistics (cache hits, batch stats)."""
|
|
5197
|
+
try:
|
|
5198
|
+
from services.embedding_pipeline import get_embedding_pipeline
|
|
5199
|
+
pipeline = get_embedding_pipeline()
|
|
5200
|
+
if pipeline:
|
|
5201
|
+
return pipeline.get_stats()
|
|
5202
|
+
return {"error": "Pipeline not initialized"}
|
|
5203
|
+
except Exception as e:
|
|
5204
|
+
logger.error(f"Embedding pipeline stats failed: {e}")
|
|
5205
|
+
return {"error": str(e)}
|
|
5206
|
+
|
|
5207
|
+
|
|
5208
|
+
@app.post("/api/embedding-pipeline/precompute")
|
|
5209
|
+
async def embedding_precompute_endpoint():
|
|
5210
|
+
"""Manually trigger embedding pre-computation for memories with missing embeddings."""
|
|
5211
|
+
try:
|
|
5212
|
+
from services.embedding_pipeline import get_embedding_pipeline
|
|
5213
|
+
pipeline = get_embedding_pipeline()
|
|
5214
|
+
if pipeline:
|
|
5215
|
+
return await pipeline.precompute_missing_embeddings()
|
|
5216
|
+
return {"error": "Pipeline not initialized"}
|
|
5217
|
+
except Exception as e:
|
|
5218
|
+
logger.error(f"Embedding precompute failed: {e}")
|
|
5219
|
+
return {"error": str(e)}
|
|
5220
|
+
|
|
5221
|
+
|
|
5222
|
+
@app.post("/api/embeddings/migrate-binary")
|
|
5223
|
+
async def embeddings_migrate_binary_endpoint():
|
|
5224
|
+
"""Migrate existing JSON embeddings to binary format for storage savings."""
|
|
5225
|
+
try:
|
|
5226
|
+
result = await db.migrate_embeddings_to_binary()
|
|
5227
|
+
return {"success": True, **result}
|
|
5228
|
+
except Exception as e:
|
|
5229
|
+
logger.error(f"Embedding migration failed: {e}")
|
|
5230
|
+
return {"error": str(e)}
|
|
5231
|
+
|
|
5232
|
+
|
|
4489
5233
|
if __name__ == "__main__":
|
|
4490
5234
|
import uvicorn
|
|
4491
5235
|
uvicorn.run(
|