claude-memory-agent 2.0.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/.env.example +107 -0
- package/README.md +200 -0
- package/agent_card.py +512 -0
- package/bin/cli.js +181 -0
- package/bin/postinstall.js +216 -0
- package/config.py +104 -0
- package/dashboard.html +2689 -0
- package/hooks/README.md +196 -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__/session_end.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/hooks/auto-detect-response.py +348 -0
- package/hooks/auto_capture.py +255 -0
- package/hooks/detect-correction.py +173 -0
- package/hooks/grounding-hook.py +348 -0
- package/hooks/log-tool-use.py +234 -0
- package/hooks/log-user-request.py +208 -0
- package/hooks/pre-tool-decision.py +218 -0
- package/hooks/problem-detector.py +343 -0
- package/hooks/session_end.py +192 -0
- package/hooks/session_start.py +227 -0
- package/install.py +887 -0
- package/main.py +2859 -0
- package/manager.py +997 -0
- package/package.json +55 -0
- package/requirements.txt +8 -0
- package/run_server.py +136 -0
- package/services/__init__.py +50 -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__/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/services/agent_registry.py +753 -0
- package/services/auth.py +331 -0
- package/services/auto_inject.py +250 -0
- package/services/claude_md_sync.py +275 -0
- package/services/cleanup.py +667 -0
- package/services/compaction_flush.py +447 -0
- package/services/confidence.py +301 -0
- package/services/daily_log.py +333 -0
- package/services/database.py +2485 -0
- package/services/embeddings.py +358 -0
- package/services/insights.py +632 -0
- package/services/llm_analyzer.py +595 -0
- package/services/memory_md_sync.py +409 -0
- package/services/retry_queue.py +453 -0
- package/services/timeline.py +579 -0
- package/services/vector_index.py +398 -0
- package/services/websocket.py +257 -0
- package/skills/__init__.py +6 -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__/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__/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/skills/admin.py +469 -0
- package/skills/checkpoint.py +198 -0
- package/skills/claude_md.py +363 -0
- package/skills/cleanup.py +241 -0
- package/skills/grounding.py +801 -0
- package/skills/insights.py +231 -0
- package/skills/natural_language.py +277 -0
- package/skills/retrieve.py +67 -0
- package/skills/search.py +213 -0
- package/skills/state.py +182 -0
- package/skills/store.py +179 -0
- package/skills/summarize.py +588 -0
- package/skills/timeline.py +387 -0
- package/skills/verification.py +391 -0
- package/start_daemon.py +155 -0
- package/test_automation.py +221 -0
- package/test_complete.py +338 -0
- package/test_full.py +322 -0
- package/update_system.py +817 -0
- package/verify_db.py +134 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""Timeline skills for session event tracking."""
|
|
2
|
+
from typing import Dict, Any, Optional, List
|
|
3
|
+
from services.database import DatabaseService
|
|
4
|
+
from services.embeddings import EmbeddingService
|
|
5
|
+
from services.timeline import TimelineService
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def timeline_log(
|
|
9
|
+
db: DatabaseService,
|
|
10
|
+
embeddings: EmbeddingService,
|
|
11
|
+
session_id: str,
|
|
12
|
+
event_type: str,
|
|
13
|
+
summary: str,
|
|
14
|
+
details: Optional[str] = None,
|
|
15
|
+
project_path: Optional[str] = None,
|
|
16
|
+
parent_event_id: Optional[int] = None,
|
|
17
|
+
root_event_id: Optional[int] = None,
|
|
18
|
+
entities: Optional[Dict[str, List[str]]] = None,
|
|
19
|
+
status: str = "completed",
|
|
20
|
+
outcome: Optional[str] = None,
|
|
21
|
+
confidence: Optional[float] = None,
|
|
22
|
+
is_anchor: bool = False
|
|
23
|
+
) -> Dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Log an event to the session timeline.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
db: Database service instance
|
|
29
|
+
embeddings: Embedding service instance
|
|
30
|
+
session_id: The session ID
|
|
31
|
+
event_type: Type of event:
|
|
32
|
+
- 'user_request': User asks for something
|
|
33
|
+
- 'clarification': User clarifies or corrects
|
|
34
|
+
- 'action': Claude takes an action (file edit, command)
|
|
35
|
+
- 'decision': Explicit choice made
|
|
36
|
+
- 'observation': Something Claude noticed
|
|
37
|
+
- 'error': Error encountered
|
|
38
|
+
- 'checkpoint': Session milestone
|
|
39
|
+
summary: Brief description (<200 chars)
|
|
40
|
+
details: Full context (optional)
|
|
41
|
+
project_path: Project path (optional)
|
|
42
|
+
parent_event_id: ID of parent event (causal chain)
|
|
43
|
+
root_event_id: ID of root user request
|
|
44
|
+
entities: Dict of entity references {"files": [], "functions": [], etc.}
|
|
45
|
+
status: Event status (pending, in_progress, completed, failed)
|
|
46
|
+
outcome: Result or error message
|
|
47
|
+
confidence: Confidence level 0-1
|
|
48
|
+
is_anchor: Whether this is a verified fact
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dict with event info
|
|
52
|
+
"""
|
|
53
|
+
timeline = TimelineService(db, embeddings)
|
|
54
|
+
|
|
55
|
+
event_id = await timeline.log_event(
|
|
56
|
+
session_id=session_id,
|
|
57
|
+
event_type=event_type,
|
|
58
|
+
summary=summary,
|
|
59
|
+
details=details,
|
|
60
|
+
project_path=project_path,
|
|
61
|
+
parent_event_id=parent_event_id,
|
|
62
|
+
root_event_id=root_event_id,
|
|
63
|
+
entities=entities,
|
|
64
|
+
status=status,
|
|
65
|
+
outcome=outcome,
|
|
66
|
+
confidence=confidence,
|
|
67
|
+
is_anchor=is_anchor
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"success": True,
|
|
72
|
+
"event_id": event_id,
|
|
73
|
+
"session_id": session_id,
|
|
74
|
+
"event_type": event_type,
|
|
75
|
+
"message": f"Event logged: {summary[:50]}..."
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def timeline_get(
|
|
80
|
+
db: DatabaseService,
|
|
81
|
+
session_id: str,
|
|
82
|
+
limit: int = 20,
|
|
83
|
+
event_type: Optional[str] = None,
|
|
84
|
+
since_event_id: Optional[int] = None,
|
|
85
|
+
anchors_only: bool = False,
|
|
86
|
+
include_state: bool = True,
|
|
87
|
+
include_checkpoint: bool = True
|
|
88
|
+
) -> Dict[str, Any]:
|
|
89
|
+
"""
|
|
90
|
+
Retrieve timeline events for a session.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
db: Database service instance
|
|
94
|
+
session_id: The session ID
|
|
95
|
+
limit: Max events to return (default 20)
|
|
96
|
+
event_type: Filter by event type
|
|
97
|
+
since_event_id: Only events after this ID
|
|
98
|
+
anchors_only: Only return verified facts
|
|
99
|
+
include_state: Include current session state
|
|
100
|
+
include_checkpoint: Include latest checkpoint
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dict with events, state, and checkpoint
|
|
104
|
+
"""
|
|
105
|
+
result = {
|
|
106
|
+
"success": True,
|
|
107
|
+
"session_id": session_id,
|
|
108
|
+
"events": [],
|
|
109
|
+
"state": None,
|
|
110
|
+
"checkpoint": None
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Get events
|
|
114
|
+
result["events"] = await db.get_timeline_events(
|
|
115
|
+
session_id=session_id,
|
|
116
|
+
limit=limit,
|
|
117
|
+
event_type=event_type,
|
|
118
|
+
since_event_id=since_event_id,
|
|
119
|
+
anchors_only=anchors_only
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Get state
|
|
123
|
+
if include_state:
|
|
124
|
+
result["state"] = await db.get_or_create_session_state(session_id)
|
|
125
|
+
|
|
126
|
+
# Get checkpoint
|
|
127
|
+
if include_checkpoint:
|
|
128
|
+
result["checkpoint"] = await db.get_latest_checkpoint(session_id)
|
|
129
|
+
|
|
130
|
+
result["event_count"] = len(result["events"])
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def timeline_search(
|
|
136
|
+
db: DatabaseService,
|
|
137
|
+
embeddings: EmbeddingService,
|
|
138
|
+
query: str,
|
|
139
|
+
session_id: Optional[str] = None,
|
|
140
|
+
limit: int = 10,
|
|
141
|
+
threshold: float = 0.5
|
|
142
|
+
) -> Dict[str, Any]:
|
|
143
|
+
"""
|
|
144
|
+
Semantic search across timeline events.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
db: Database service instance
|
|
148
|
+
embeddings: Embedding service instance
|
|
149
|
+
query: Search query
|
|
150
|
+
session_id: Limit to specific session (optional)
|
|
151
|
+
limit: Max results (default 10)
|
|
152
|
+
threshold: Minimum similarity (default 0.5)
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Dict with matching events
|
|
156
|
+
"""
|
|
157
|
+
timeline = TimelineService(db, embeddings)
|
|
158
|
+
|
|
159
|
+
events = await timeline.search_events(
|
|
160
|
+
query=query,
|
|
161
|
+
session_id=session_id,
|
|
162
|
+
limit=limit,
|
|
163
|
+
threshold=threshold
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"success": True,
|
|
168
|
+
"query": query,
|
|
169
|
+
"session_id": session_id,
|
|
170
|
+
"events": events,
|
|
171
|
+
"count": len(events),
|
|
172
|
+
"message": f"Found {len(events)} matching events"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def timeline_chain(
|
|
177
|
+
db: DatabaseService,
|
|
178
|
+
session_id: str,
|
|
179
|
+
root_event_id: int,
|
|
180
|
+
include_details: bool = False
|
|
181
|
+
) -> Dict[str, Any]:
|
|
182
|
+
"""
|
|
183
|
+
Get the full causal chain for a user request.
|
|
184
|
+
|
|
185
|
+
Shows the complete timeline:
|
|
186
|
+
user_request → thinking → decision → action → outcome
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
db: Database service instance
|
|
190
|
+
session_id: The session ID
|
|
191
|
+
root_event_id: The root user_request event ID
|
|
192
|
+
include_details: Whether to include full event details
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Dict with the causal chain as a tree structure
|
|
196
|
+
"""
|
|
197
|
+
# Get all events linked to this root
|
|
198
|
+
events = await db.get_timeline_events(
|
|
199
|
+
session_id=session_id,
|
|
200
|
+
limit=100 # Get all related events
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Filter events linked to this root
|
|
204
|
+
chain_events = [
|
|
205
|
+
e for e in events
|
|
206
|
+
if e.get("root_event_id") == root_event_id or e.get("id") == root_event_id
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
# Sort by sequence number
|
|
210
|
+
chain_events.sort(key=lambda x: x.get("sequence_num", 0))
|
|
211
|
+
|
|
212
|
+
# Build tree structure
|
|
213
|
+
def build_tree(parent_id: Optional[int]) -> List[Dict]:
|
|
214
|
+
children = []
|
|
215
|
+
for event in chain_events:
|
|
216
|
+
if event.get("parent_event_id") == parent_id or (parent_id is None and event.get("id") == root_event_id):
|
|
217
|
+
node = {
|
|
218
|
+
"id": event.get("id"),
|
|
219
|
+
"type": event.get("event_type"),
|
|
220
|
+
"summary": event.get("summary", "")[:100],
|
|
221
|
+
"sequence": event.get("sequence_num"),
|
|
222
|
+
}
|
|
223
|
+
if include_details:
|
|
224
|
+
node["details"] = event.get("details")
|
|
225
|
+
node["created_at"] = event.get("created_at")
|
|
226
|
+
|
|
227
|
+
# Recursively add children
|
|
228
|
+
node["children"] = build_tree(event.get("id"))
|
|
229
|
+
children.append(node)
|
|
230
|
+
return children
|
|
231
|
+
|
|
232
|
+
tree = build_tree(None)
|
|
233
|
+
|
|
234
|
+
# Also create a flat timeline view
|
|
235
|
+
flat_timeline = []
|
|
236
|
+
for event in chain_events:
|
|
237
|
+
flat_timeline.append({
|
|
238
|
+
"seq": event.get("sequence_num"),
|
|
239
|
+
"type": event.get("event_type"),
|
|
240
|
+
"summary": event.get("summary", "")[:80]
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
"success": True,
|
|
245
|
+
"session_id": session_id,
|
|
246
|
+
"root_event_id": root_event_id,
|
|
247
|
+
"tree": tree,
|
|
248
|
+
"flat_timeline": flat_timeline,
|
|
249
|
+
"event_count": len(chain_events),
|
|
250
|
+
"message": f"Chain has {len(chain_events)} events"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
async def timeline_log_batch(
|
|
255
|
+
db: DatabaseService,
|
|
256
|
+
embeddings: EmbeddingService,
|
|
257
|
+
session_id: str,
|
|
258
|
+
events: List[Dict[str, Any]],
|
|
259
|
+
project_path: Optional[str] = None,
|
|
260
|
+
parent_event_id: Optional[int] = None,
|
|
261
|
+
root_event_id: Optional[int] = None
|
|
262
|
+
) -> Dict[str, Any]:
|
|
263
|
+
"""
|
|
264
|
+
Log multiple events to the session timeline in a single batch operation.
|
|
265
|
+
|
|
266
|
+
This is the efficient way to log multiple events - use this instead of
|
|
267
|
+
calling timeline_log() multiple times. It reduces HTTP overhead and
|
|
268
|
+
database transactions.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
db: Database service instance
|
|
272
|
+
embeddings: Embedding service instance
|
|
273
|
+
session_id: The session ID
|
|
274
|
+
events: List of event dicts, each containing:
|
|
275
|
+
- event_type: Type of event (required)
|
|
276
|
+
- 'user_request': User asks for something
|
|
277
|
+
- 'clarification': User clarifies or corrects
|
|
278
|
+
- 'action': Claude takes an action (file edit, command)
|
|
279
|
+
- 'decision': Explicit choice made
|
|
280
|
+
- 'observation': Something Claude noticed
|
|
281
|
+
- 'thinking': Claude's reasoning process
|
|
282
|
+
- 'outcome': Result of a request
|
|
283
|
+
- 'error': Error encountered
|
|
284
|
+
- 'checkpoint': Session milestone
|
|
285
|
+
- summary: Brief description (<200 chars, required)
|
|
286
|
+
- details: Full context (optional)
|
|
287
|
+
- entities: Dict of entity references (optional)
|
|
288
|
+
- status: Event status - pending, in_progress, completed, failed (optional)
|
|
289
|
+
- outcome: Result or error message (optional)
|
|
290
|
+
- confidence: Confidence level 0-1 (optional)
|
|
291
|
+
- is_anchor: Whether this is a verified fact (optional)
|
|
292
|
+
project_path: Project path for all events (optional)
|
|
293
|
+
parent_event_id: ID of parent event for causal chain (optional)
|
|
294
|
+
root_event_id: ID of root user request for causal chain (optional)
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Dict with event IDs and summary
|
|
298
|
+
"""
|
|
299
|
+
if not events:
|
|
300
|
+
return {
|
|
301
|
+
"success": True,
|
|
302
|
+
"event_ids": [],
|
|
303
|
+
"session_id": session_id,
|
|
304
|
+
"events_logged": 0,
|
|
305
|
+
"message": "No events to log"
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
timeline = TimelineService(db, embeddings)
|
|
309
|
+
|
|
310
|
+
event_ids = await timeline.log_events_batch(
|
|
311
|
+
session_id=session_id,
|
|
312
|
+
events=events,
|
|
313
|
+
project_path=project_path,
|
|
314
|
+
parent_event_id=parent_event_id,
|
|
315
|
+
root_event_id=root_event_id,
|
|
316
|
+
generate_embeddings=True
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Summarize what was logged
|
|
320
|
+
event_types = {}
|
|
321
|
+
for event in events:
|
|
322
|
+
et = event.get("event_type", "unknown")
|
|
323
|
+
event_types[et] = event_types.get(et, 0) + 1
|
|
324
|
+
|
|
325
|
+
type_summary = ", ".join(f"{count} {etype}" for etype, count in event_types.items())
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
"success": True,
|
|
329
|
+
"event_ids": event_ids,
|
|
330
|
+
"session_id": session_id,
|
|
331
|
+
"events_logged": len(event_ids),
|
|
332
|
+
"event_types": event_types,
|
|
333
|
+
"message": f"Batch logged {len(event_ids)} events: {type_summary}"
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
async def timeline_auto_detect(
|
|
338
|
+
db: DatabaseService,
|
|
339
|
+
embeddings: EmbeddingService,
|
|
340
|
+
session_id: str,
|
|
341
|
+
response_text: str,
|
|
342
|
+
project_path: Optional[str] = None,
|
|
343
|
+
parent_event_id: Optional[int] = None,
|
|
344
|
+
root_event_id: Optional[int] = None
|
|
345
|
+
) -> Dict[str, Any]:
|
|
346
|
+
"""
|
|
347
|
+
Auto-detect and log decisions/observations from a response.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
db: Database service instance
|
|
351
|
+
embeddings: Embedding service instance
|
|
352
|
+
session_id: The session ID
|
|
353
|
+
response_text: Claude's response text to analyze
|
|
354
|
+
project_path: Project path (optional)
|
|
355
|
+
parent_event_id: Parent event ID for causal chain (optional)
|
|
356
|
+
root_event_id: Root user request event ID (optional)
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Dict with detected and logged events
|
|
360
|
+
"""
|
|
361
|
+
timeline = TimelineService(db, embeddings)
|
|
362
|
+
|
|
363
|
+
# Detect patterns
|
|
364
|
+
decisions = timeline.detect_decisions(response_text)
|
|
365
|
+
observations = timeline.detect_observations(response_text)
|
|
366
|
+
entities = timeline.extract_entities(response_text)
|
|
367
|
+
|
|
368
|
+
# Auto-log events with causal chain linking
|
|
369
|
+
event_ids = await timeline.auto_log_from_response(
|
|
370
|
+
session_id=session_id,
|
|
371
|
+
response_text=response_text,
|
|
372
|
+
project_path=project_path,
|
|
373
|
+
parent_event_id=parent_event_id,
|
|
374
|
+
root_event_id=root_event_id
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
"success": True,
|
|
379
|
+
"session_id": session_id,
|
|
380
|
+
"detected": {
|
|
381
|
+
"decisions": decisions[:5],
|
|
382
|
+
"observations": observations[:5],
|
|
383
|
+
"entities": entities
|
|
384
|
+
},
|
|
385
|
+
"logged_event_ids": event_ids,
|
|
386
|
+
"message": f"Auto-detected {len(decisions)} decisions, {len(observations)} observations"
|
|
387
|
+
}
|