claude-memory-agent 2.0.0 → 2.1.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/README.md +206 -200
- package/agent_card.py +186 -0
- package/bin/cli.js +317 -181
- package/bin/postinstall.js +270 -216
- package/dashboard.html +4232 -2689
- package/hooks/__pycache__/grounding-hook.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
- package/hooks/grounding-hook.py +422 -348
- package/hooks/session_end.py +293 -192
- package/hooks/session_start.py +227 -227
- package/install.py +919 -887
- package/main.py +4496 -2859
- package/package.json +47 -55
- package/services/__init__.py +50 -50
- package/services/__pycache__/__init__.cpython-312.pyc +0 -0
- package/services/__pycache__/curator.cpython-312.pyc +0 -0
- package/services/__pycache__/database.cpython-312.pyc +0 -0
- package/services/curator.py +1606 -0
- package/services/database.py +3637 -2485
- package/skills/__init__.py +21 -1
- package/skills/__pycache__/__init__.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__/search.cpython-312.pyc +0 -0
- package/skills/__pycache__/session_review.cpython-312.pyc +0 -0
- package/skills/__pycache__/store.cpython-312.pyc +0 -0
- package/skills/confidence_tracker.py +441 -0
- package/skills/context.py +675 -0
- package/skills/curator.py +348 -0
- package/skills/search.py +369 -213
- package/skills/session_review.py +418 -0
- package/skills/store.py +377 -179
- package/update_system.py +829 -817
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"""Session review skill for end-of-session memory verification.
|
|
2
|
+
|
|
3
|
+
Allows users to review memories created during a session and mark them as:
|
|
4
|
+
- keep: Verified as useful, increases confidence
|
|
5
|
+
- discard: Not useful, decreases confidence significantly
|
|
6
|
+
- partial: Partially useful, sets confidence to middle value
|
|
7
|
+
"""
|
|
8
|
+
from typing import Dict, Any, Optional, List
|
|
9
|
+
from services.database import DatabaseService
|
|
10
|
+
from services.embeddings import EmbeddingService
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def get_session_memories(
|
|
14
|
+
db: DatabaseService,
|
|
15
|
+
session_id: str,
|
|
16
|
+
include_patterns: bool = False,
|
|
17
|
+
limit: int = 100
|
|
18
|
+
) -> Dict[str, Any]:
|
|
19
|
+
"""
|
|
20
|
+
Get all memories created in a specific session for review.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
db: Database service instance
|
|
24
|
+
session_id: Session identifier to filter memories
|
|
25
|
+
include_patterns: Whether to include patterns created during session
|
|
26
|
+
limit: Maximum number of memories to return
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Dict with memories list and metadata
|
|
30
|
+
"""
|
|
31
|
+
if not session_id:
|
|
32
|
+
return {
|
|
33
|
+
"success": False,
|
|
34
|
+
"error": "session_id is required"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Get memories for this session
|
|
38
|
+
memories_query = """
|
|
39
|
+
SELECT
|
|
40
|
+
id, type, content, project_path, project_name,
|
|
41
|
+
outcome, success, outcome_status, confidence,
|
|
42
|
+
importance, tags, created_at
|
|
43
|
+
FROM memories
|
|
44
|
+
WHERE session_id = ?
|
|
45
|
+
ORDER BY created_at DESC
|
|
46
|
+
LIMIT ?
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
memories = await db.execute_query(memories_query, [session_id, limit])
|
|
50
|
+
|
|
51
|
+
result = {
|
|
52
|
+
"success": True,
|
|
53
|
+
"session_id": session_id,
|
|
54
|
+
"memories": memories or [],
|
|
55
|
+
"memory_count": len(memories) if memories else 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Optionally include patterns
|
|
59
|
+
if include_patterns:
|
|
60
|
+
patterns_query = """
|
|
61
|
+
SELECT
|
|
62
|
+
id, name, problem_type, solution,
|
|
63
|
+
success_count, failure_count, created_at
|
|
64
|
+
FROM patterns
|
|
65
|
+
WHERE created_at >= (
|
|
66
|
+
SELECT MIN(created_at) FROM memories WHERE session_id = ?
|
|
67
|
+
)
|
|
68
|
+
AND created_at <= (
|
|
69
|
+
SELECT MAX(created_at) FROM memories WHERE session_id = ?
|
|
70
|
+
)
|
|
71
|
+
ORDER BY created_at DESC
|
|
72
|
+
LIMIT ?
|
|
73
|
+
"""
|
|
74
|
+
patterns = await db.execute_query(patterns_query, [session_id, session_id, limit])
|
|
75
|
+
result["patterns"] = patterns or []
|
|
76
|
+
result["pattern_count"] = len(patterns) if patterns else 0
|
|
77
|
+
|
|
78
|
+
# Generate summary
|
|
79
|
+
type_counts = {}
|
|
80
|
+
for m in (memories or []):
|
|
81
|
+
mtype = m.get("type", "unknown")
|
|
82
|
+
type_counts[mtype] = type_counts.get(mtype, 0) + 1
|
|
83
|
+
|
|
84
|
+
result["summary"] = {
|
|
85
|
+
"by_type": type_counts,
|
|
86
|
+
"total_memories": result["memory_count"],
|
|
87
|
+
"avg_importance": (
|
|
88
|
+
sum(m.get("importance", 5) for m in (memories or [])) / len(memories)
|
|
89
|
+
if memories else 0
|
|
90
|
+
),
|
|
91
|
+
"avg_confidence": (
|
|
92
|
+
sum(m.get("confidence", 0.5) for m in (memories or [])) / len(memories)
|
|
93
|
+
if memories else 0
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def review_session_memories(
|
|
101
|
+
db: DatabaseService,
|
|
102
|
+
session_id: str,
|
|
103
|
+
reviews: List[Dict[str, Any]]
|
|
104
|
+
) -> Dict[str, Any]:
|
|
105
|
+
"""
|
|
106
|
+
Process user review decisions for session memories.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
db: Database service instance
|
|
110
|
+
session_id: Session identifier
|
|
111
|
+
reviews: List of review decisions, each containing:
|
|
112
|
+
- memory_id: ID of the memory
|
|
113
|
+
- decision: 'keep', 'discard', or 'partial'
|
|
114
|
+
- feedback: Optional user feedback
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Dict with processing results
|
|
118
|
+
"""
|
|
119
|
+
if not session_id:
|
|
120
|
+
return {
|
|
121
|
+
"success": False,
|
|
122
|
+
"error": "session_id is required"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if not reviews:
|
|
126
|
+
return {
|
|
127
|
+
"success": False,
|
|
128
|
+
"error": "reviews list is required"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
results = {
|
|
132
|
+
"success": True,
|
|
133
|
+
"session_id": session_id,
|
|
134
|
+
"processed": 0,
|
|
135
|
+
"kept": 0,
|
|
136
|
+
"discarded": 0,
|
|
137
|
+
"partial": 0,
|
|
138
|
+
"errors": []
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Confidence mappings for each decision
|
|
142
|
+
confidence_map = {
|
|
143
|
+
"keep": 0.9, # High confidence - verified useful
|
|
144
|
+
"partial": 0.5, # Medium confidence - partially useful
|
|
145
|
+
"discard": 0.1 # Low confidence - not useful
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
outcome_status_map = {
|
|
149
|
+
"keep": "success",
|
|
150
|
+
"partial": "partial",
|
|
151
|
+
"discard": "failed"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for review in reviews:
|
|
155
|
+
memory_id = review.get("memory_id")
|
|
156
|
+
decision = review.get("decision", "keep").lower()
|
|
157
|
+
feedback = review.get("feedback")
|
|
158
|
+
|
|
159
|
+
if not memory_id:
|
|
160
|
+
results["errors"].append({
|
|
161
|
+
"error": "memory_id is required",
|
|
162
|
+
"review": review
|
|
163
|
+
})
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
if decision not in confidence_map:
|
|
167
|
+
results["errors"].append({
|
|
168
|
+
"memory_id": memory_id,
|
|
169
|
+
"error": f"Invalid decision: {decision}. Must be 'keep', 'discard', or 'partial'"
|
|
170
|
+
})
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
# Update confidence
|
|
175
|
+
new_confidence = confidence_map[decision]
|
|
176
|
+
await db.update_memory_confidence(memory_id, new_confidence)
|
|
177
|
+
|
|
178
|
+
# Update outcome status
|
|
179
|
+
new_outcome_status = outcome_status_map[decision]
|
|
180
|
+
await db.update_memory_outcome(
|
|
181
|
+
memory_id=memory_id,
|
|
182
|
+
outcome_status=new_outcome_status
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Store feedback if provided
|
|
186
|
+
if feedback:
|
|
187
|
+
await db.execute_query(
|
|
188
|
+
"UPDATE memories SET user_feedback = ? WHERE id = ?",
|
|
189
|
+
[feedback, memory_id]
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
results["processed"] += 1
|
|
193
|
+
|
|
194
|
+
if decision == "keep":
|
|
195
|
+
results["kept"] += 1
|
|
196
|
+
elif decision == "discard":
|
|
197
|
+
results["discarded"] += 1
|
|
198
|
+
elif decision == "partial":
|
|
199
|
+
results["partial"] += 1
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
results["errors"].append({
|
|
203
|
+
"memory_id": memory_id,
|
|
204
|
+
"error": str(e)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
# Calculate success rate
|
|
208
|
+
total = results["kept"] + results["discarded"] + results["partial"]
|
|
209
|
+
if total > 0:
|
|
210
|
+
results["keep_rate"] = round(results["kept"] / total * 100, 1)
|
|
211
|
+
else:
|
|
212
|
+
results["keep_rate"] = 0
|
|
213
|
+
|
|
214
|
+
return results
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
async def suggest_session_reviews(
|
|
218
|
+
db: DatabaseService,
|
|
219
|
+
embeddings: EmbeddingService,
|
|
220
|
+
session_id: str
|
|
221
|
+
) -> Dict[str, Any]:
|
|
222
|
+
"""
|
|
223
|
+
Analyze session memories and suggest which to keep/discard.
|
|
224
|
+
|
|
225
|
+
Suggestions are based on:
|
|
226
|
+
- Memory type (decisions and patterns more likely to keep)
|
|
227
|
+
- Importance score
|
|
228
|
+
- Outcome status if already set
|
|
229
|
+
- Similarity to existing successful memories
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
db: Database service instance
|
|
233
|
+
embeddings: Embedding service instance
|
|
234
|
+
session_id: Session identifier
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Dict with suggested reviews
|
|
238
|
+
"""
|
|
239
|
+
if not session_id:
|
|
240
|
+
return {
|
|
241
|
+
"success": False,
|
|
242
|
+
"error": "session_id is required"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Get session memories
|
|
246
|
+
memories_result = await get_session_memories(db, session_id)
|
|
247
|
+
if not memories_result.get("success"):
|
|
248
|
+
return memories_result
|
|
249
|
+
|
|
250
|
+
memories = memories_result.get("memories", [])
|
|
251
|
+
suggestions = []
|
|
252
|
+
|
|
253
|
+
# Types that are more likely to be valuable
|
|
254
|
+
high_value_types = {"decision", "error", "pattern", "preference"}
|
|
255
|
+
|
|
256
|
+
for memory in memories:
|
|
257
|
+
memory_id = memory.get("id")
|
|
258
|
+
memory_type = memory.get("type", "chunk")
|
|
259
|
+
importance = memory.get("importance", 5)
|
|
260
|
+
outcome_status = memory.get("outcome_status", "pending")
|
|
261
|
+
confidence = memory.get("confidence", 0.5)
|
|
262
|
+
content = memory.get("content", "")
|
|
263
|
+
|
|
264
|
+
suggestion = {
|
|
265
|
+
"memory_id": memory_id,
|
|
266
|
+
"type": memory_type,
|
|
267
|
+
"content_preview": content[:200] + "..." if len(content) > 200 else content,
|
|
268
|
+
"current_confidence": confidence,
|
|
269
|
+
"importance": importance
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Determine suggestion based on heuristics
|
|
273
|
+
if outcome_status == "success":
|
|
274
|
+
suggestion["suggested_decision"] = "keep"
|
|
275
|
+
suggestion["reason"] = "Already marked as successful"
|
|
276
|
+
elif outcome_status == "failed":
|
|
277
|
+
suggestion["suggested_decision"] = "discard"
|
|
278
|
+
suggestion["reason"] = "Already marked as failed"
|
|
279
|
+
elif outcome_status == "partial":
|
|
280
|
+
suggestion["suggested_decision"] = "partial"
|
|
281
|
+
suggestion["reason"] = "Already marked as partial success"
|
|
282
|
+
elif memory_type in high_value_types:
|
|
283
|
+
if importance >= 7:
|
|
284
|
+
suggestion["suggested_decision"] = "keep"
|
|
285
|
+
suggestion["reason"] = f"High importance {memory_type}"
|
|
286
|
+
else:
|
|
287
|
+
suggestion["suggested_decision"] = "partial"
|
|
288
|
+
suggestion["reason"] = f"Review this {memory_type}"
|
|
289
|
+
elif importance >= 8:
|
|
290
|
+
suggestion["suggested_decision"] = "keep"
|
|
291
|
+
suggestion["reason"] = "High importance score"
|
|
292
|
+
elif importance <= 3:
|
|
293
|
+
suggestion["suggested_decision"] = "discard"
|
|
294
|
+
suggestion["reason"] = "Low importance score"
|
|
295
|
+
else:
|
|
296
|
+
suggestion["suggested_decision"] = "partial"
|
|
297
|
+
suggestion["reason"] = "Review recommended"
|
|
298
|
+
|
|
299
|
+
suggestions.append(suggestion)
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
"success": True,
|
|
303
|
+
"session_id": session_id,
|
|
304
|
+
"suggestions": suggestions,
|
|
305
|
+
"summary": {
|
|
306
|
+
"total": len(suggestions),
|
|
307
|
+
"suggested_keep": sum(1 for s in suggestions if s["suggested_decision"] == "keep"),
|
|
308
|
+
"suggested_discard": sum(1 for s in suggestions if s["suggested_decision"] == "discard"),
|
|
309
|
+
"suggested_partial": sum(1 for s in suggestions if s["suggested_decision"] == "partial")
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
async def get_recent_sessions(
|
|
315
|
+
db: DatabaseService,
|
|
316
|
+
project_path: Optional[str] = None,
|
|
317
|
+
limit: int = 10
|
|
318
|
+
) -> Dict[str, Any]:
|
|
319
|
+
"""
|
|
320
|
+
Get recent sessions with memory counts for review selection.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
db: Database service instance
|
|
324
|
+
project_path: Optional filter by project
|
|
325
|
+
limit: Maximum number of sessions to return
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dict with session list
|
|
329
|
+
"""
|
|
330
|
+
query = """
|
|
331
|
+
SELECT
|
|
332
|
+
session_id,
|
|
333
|
+
COUNT(*) as memory_count,
|
|
334
|
+
MIN(created_at) as started_at,
|
|
335
|
+
MAX(created_at) as ended_at,
|
|
336
|
+
project_path,
|
|
337
|
+
project_name,
|
|
338
|
+
SUM(CASE WHEN outcome_status = 'success' THEN 1 ELSE 0 END) as success_count,
|
|
339
|
+
SUM(CASE WHEN outcome_status = 'failed' THEN 1 ELSE 0 END) as failed_count,
|
|
340
|
+
SUM(CASE WHEN outcome_status = 'pending' THEN 1 ELSE 0 END) as pending_count,
|
|
341
|
+
AVG(confidence) as avg_confidence
|
|
342
|
+
FROM memories
|
|
343
|
+
WHERE session_id IS NOT NULL
|
|
344
|
+
"""
|
|
345
|
+
params = []
|
|
346
|
+
|
|
347
|
+
if project_path:
|
|
348
|
+
query += " AND project_path = ?"
|
|
349
|
+
params.append(project_path)
|
|
350
|
+
|
|
351
|
+
query += """
|
|
352
|
+
GROUP BY session_id
|
|
353
|
+
ORDER BY MAX(created_at) DESC
|
|
354
|
+
LIMIT ?
|
|
355
|
+
"""
|
|
356
|
+
params.append(limit)
|
|
357
|
+
|
|
358
|
+
sessions = await db.execute_query(query, params)
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
"success": True,
|
|
362
|
+
"sessions": sessions or [],
|
|
363
|
+
"count": len(sessions) if sessions else 0
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
async def bulk_review_by_type(
|
|
368
|
+
db: DatabaseService,
|
|
369
|
+
session_id: str,
|
|
370
|
+
type_decisions: Dict[str, str]
|
|
371
|
+
) -> Dict[str, Any]:
|
|
372
|
+
"""
|
|
373
|
+
Apply review decisions to all memories of specific types in a session.
|
|
374
|
+
|
|
375
|
+
Useful for quickly processing sessions, e.g.:
|
|
376
|
+
- Keep all 'decision' and 'error' memories
|
|
377
|
+
- Discard all 'chunk' memories
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
db: Database service instance
|
|
381
|
+
session_id: Session identifier
|
|
382
|
+
type_decisions: Dict mapping memory types to decisions
|
|
383
|
+
e.g., {"decision": "keep", "chunk": "discard", "error": "keep"}
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Dict with processing results
|
|
387
|
+
"""
|
|
388
|
+
if not session_id:
|
|
389
|
+
return {
|
|
390
|
+
"success": False,
|
|
391
|
+
"error": "session_id is required"
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if not type_decisions:
|
|
395
|
+
return {
|
|
396
|
+
"success": False,
|
|
397
|
+
"error": "type_decisions is required"
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
# Get session memories
|
|
401
|
+
memories_result = await get_session_memories(db, session_id)
|
|
402
|
+
if not memories_result.get("success"):
|
|
403
|
+
return memories_result
|
|
404
|
+
|
|
405
|
+
memories = memories_result.get("memories", [])
|
|
406
|
+
|
|
407
|
+
# Build reviews based on type decisions
|
|
408
|
+
reviews = []
|
|
409
|
+
for memory in memories:
|
|
410
|
+
memory_type = memory.get("type", "chunk")
|
|
411
|
+
if memory_type in type_decisions:
|
|
412
|
+
reviews.append({
|
|
413
|
+
"memory_id": memory.get("id"),
|
|
414
|
+
"decision": type_decisions[memory_type]
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
# Process reviews
|
|
418
|
+
return await review_session_memories(db, session_id, reviews)
|