claude-memory-agent 2.0.1 → 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/README.md +206 -206
- package/agent_card.py +186 -0
- package/bin/cli.js +327 -185
- 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 +35 -270
- package/config.py +103 -4
- package/dashboard.html +4902 -2689
- package/hooks/extract_memories.py +439 -0
- package/hooks/grounding-hook.py +422 -348
- package/hooks/pre_compact_hook.py +76 -0
- package/hooks/session_end.py +293 -192
- package/hooks/session_end_hook.py +149 -0
- package/hooks/session_start.py +227 -227
- package/hooks/stop_hook.py +372 -0
- package/install.py +972 -902
- package/main.py +5240 -2859
- package/mcp_server.py +451 -0
- package/package.json +58 -47
- package/requirements.txt +12 -8
- package/services/__init__.py +50 -50
- 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/curator.py +1606 -0
- package/services/database.py +4118 -2485
- 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/__init__.py +21 -1
- package/skills/confidence_tracker.py +441 -0
- package/skills/context.py +675 -0
- package/skills/curator.py +348 -0
- package/skills/search.py +444 -213
- package/skills/session_review.py +605 -0
- package/skills/store.py +484 -179
- package/terminal_dashboard.py +474 -0
- package/update_system.py +829 -817
- 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/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/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/test_automation.py +0 -221
- package/test_complete.py +0 -338
- package/test_full.py +0 -322
- package/verify_db.py +0 -134
package/skills/search.py
CHANGED
|
@@ -1,213 +1,444 @@
|
|
|
1
|
-
"""Semantic search skill with context filtering and fallback support."""
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
from services.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
1
|
+
"""Semantic search skill with context filtering and fallback support."""
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Any, Optional, List
|
|
4
|
+
from services.database import DatabaseService
|
|
5
|
+
from services.embeddings import EmbeddingService
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def _enrich_with_graph_context(db: DatabaseService, results: list, max_related_items: int = 2) -> list:
|
|
11
|
+
"""
|
|
12
|
+
Enrich search results with relationship context using batch queries.
|
|
13
|
+
|
|
14
|
+
Uses batch DB queries (IN clauses) instead of per-result sequential queries,
|
|
15
|
+
providing 5-10x speedup on the enrichment phase for typical 10-result sets.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db: Database service instance
|
|
19
|
+
results: List of search results to enrich
|
|
20
|
+
max_related_items: Max related items per relationship type (caps response size)
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
List of enriched results with graph context added
|
|
24
|
+
"""
|
|
25
|
+
import asyncio
|
|
26
|
+
|
|
27
|
+
if not results:
|
|
28
|
+
return results
|
|
29
|
+
|
|
30
|
+
# Collect all memory IDs that need enrichment
|
|
31
|
+
all_ids = [r.get('id') for r in results if r.get('id')]
|
|
32
|
+
if not all_ids:
|
|
33
|
+
return results
|
|
34
|
+
|
|
35
|
+
# Categorize IDs by type for targeted batch queries
|
|
36
|
+
error_ids = [r['id'] for r in results if r.get('id') and r.get('type') == 'error']
|
|
37
|
+
decision_ids = [r['id'] for r in results if r.get('id') and r.get('type') == 'decision']
|
|
38
|
+
causal_ids = [r['id'] for r in results if r.get('id') and r.get('type') in ('error', 'decision', 'code')]
|
|
39
|
+
|
|
40
|
+
# Run all batch queries in parallel
|
|
41
|
+
batch_tasks = {}
|
|
42
|
+
|
|
43
|
+
# Batch: fixes for errors (incoming 'fixes' relationships)
|
|
44
|
+
if error_ids:
|
|
45
|
+
batch_tasks['fixes'] = db.get_related_memories_batch(error_ids, 'fixes', direction='incoming')
|
|
46
|
+
|
|
47
|
+
# Batch: supports for decisions (incoming 'supports' relationships)
|
|
48
|
+
if decision_ids:
|
|
49
|
+
batch_tasks['supports'] = db.get_related_memories_batch(decision_ids, 'supports', direction='incoming')
|
|
50
|
+
batch_tasks['caused'] = db.get_related_memories_batch(decision_ids, 'caused_by', direction='outgoing')
|
|
51
|
+
|
|
52
|
+
# Batch: contradictions for all results
|
|
53
|
+
batch_tasks['contradictions'] = db.find_contradictions_batch(all_ids)
|
|
54
|
+
|
|
55
|
+
# Causal chains still need per-ID traversal (recursive), but run in parallel
|
|
56
|
+
causal_futures = {}
|
|
57
|
+
for mid in causal_ids:
|
|
58
|
+
causal_futures[mid] = db.get_causal_chain(mid, max_depth=3)
|
|
59
|
+
|
|
60
|
+
# Gather all batch results
|
|
61
|
+
batch_results = {}
|
|
62
|
+
try:
|
|
63
|
+
if batch_tasks:
|
|
64
|
+
keys = list(batch_tasks.keys())
|
|
65
|
+
values = await asyncio.gather(*batch_tasks.values(), return_exceptions=True)
|
|
66
|
+
for k, v in zip(keys, values):
|
|
67
|
+
batch_results[k] = v if not isinstance(v, Exception) else {}
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.warning(f"Batch graph enrichment failed: {e}")
|
|
70
|
+
batch_results = {}
|
|
71
|
+
|
|
72
|
+
# Gather causal chain results in parallel
|
|
73
|
+
causal_results = {}
|
|
74
|
+
if causal_futures:
|
|
75
|
+
try:
|
|
76
|
+
keys = list(causal_futures.keys())
|
|
77
|
+
values = await asyncio.gather(*causal_futures.values(), return_exceptions=True)
|
|
78
|
+
for k, v in zip(keys, values):
|
|
79
|
+
causal_results[k] = v if not isinstance(v, Exception) else None
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.debug(f"Causal chain batch failed: {e}")
|
|
82
|
+
|
|
83
|
+
# Assemble enriched results
|
|
84
|
+
enriched = []
|
|
85
|
+
fixes_map = batch_results.get('fixes', {})
|
|
86
|
+
supports_map = batch_results.get('supports', {})
|
|
87
|
+
caused_map = batch_results.get('caused', {})
|
|
88
|
+
contradictions_map = batch_results.get('contradictions', {})
|
|
89
|
+
|
|
90
|
+
for result in results:
|
|
91
|
+
memory_id = result.get('id')
|
|
92
|
+
if not memory_id:
|
|
93
|
+
enriched.append(result)
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
enriched_result = dict(result)
|
|
97
|
+
memory_type = result.get('type', '')
|
|
98
|
+
|
|
99
|
+
# Errors: known fixes
|
|
100
|
+
if memory_type == 'error' and memory_id in fixes_map:
|
|
101
|
+
fixes = fixes_map[memory_id]
|
|
102
|
+
if fixes:
|
|
103
|
+
enriched_result['known_fixes'] = [
|
|
104
|
+
{'id': f['id'], 'content': f['content'][:200], 'outcome': f.get('outcome')}
|
|
105
|
+
for f in fixes[:max_related_items]
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
# Decisions: rationale and consequences
|
|
109
|
+
if memory_type == 'decision':
|
|
110
|
+
supports = supports_map.get(memory_id, [])
|
|
111
|
+
if supports:
|
|
112
|
+
enriched_result['rationale'] = [
|
|
113
|
+
{'id': s['id'], 'content': s['content'][:200]}
|
|
114
|
+
for s in supports[:max_related_items]
|
|
115
|
+
]
|
|
116
|
+
caused = caused_map.get(memory_id, [])
|
|
117
|
+
if caused:
|
|
118
|
+
enriched_result['consequences'] = [
|
|
119
|
+
{'id': c['id'], 'content': c['content'][:200]}
|
|
120
|
+
for c in caused[:max_related_items]
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
# All types: contradictions
|
|
124
|
+
contradictions = contradictions_map.get(memory_id, [])
|
|
125
|
+
if contradictions:
|
|
126
|
+
enriched_result['contradictions'] = [
|
|
127
|
+
{'id': c['id'], 'content': c['content'][:200]}
|
|
128
|
+
for c in contradictions[:max_related_items]
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# Causal chains
|
|
132
|
+
if memory_type in ('error', 'decision', 'code') and memory_id in causal_results:
|
|
133
|
+
chain = causal_results[memory_id]
|
|
134
|
+
if chain and (chain.get('causes') or chain.get('fixes') or chain.get('root_causes')):
|
|
135
|
+
enriched_result['causal_chain'] = chain
|
|
136
|
+
|
|
137
|
+
enriched.append(enriched_result)
|
|
138
|
+
|
|
139
|
+
return enriched
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def semantic_search(
|
|
143
|
+
db: DatabaseService,
|
|
144
|
+
embeddings: EmbeddingService,
|
|
145
|
+
query: str,
|
|
146
|
+
limit: int = 10,
|
|
147
|
+
memory_type: Optional[str] = None,
|
|
148
|
+
session_id: Optional[str] = None,
|
|
149
|
+
project_path: Optional[str] = None,
|
|
150
|
+
agent_type: Optional[str] = None,
|
|
151
|
+
success_only: bool = False,
|
|
152
|
+
threshold: float = 0.5,
|
|
153
|
+
# Outcome spectrum filters
|
|
154
|
+
include_failed: bool = False,
|
|
155
|
+
include_superseded: bool = False,
|
|
156
|
+
include_unreliable: bool = False,
|
|
157
|
+
outcome_status: Optional[str] = None,
|
|
158
|
+
# Context-aware search
|
|
159
|
+
current_context: Optional[Dict[str, Any]] = None,
|
|
160
|
+
auto_detect_context: bool = True,
|
|
161
|
+
# Graph enrichment
|
|
162
|
+
include_graph: bool = True,
|
|
163
|
+
# Adaptive ranking
|
|
164
|
+
temperature: Optional[float] = None
|
|
165
|
+
) -> Dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Search memories using semantic similarity with context filters.
|
|
168
|
+
|
|
169
|
+
Includes automatic fallback to keyword search when Ollama is unavailable.
|
|
170
|
+
|
|
171
|
+
Outcome-aware search behavior:
|
|
172
|
+
- 'success' memories rank highest (1.5x boost)
|
|
173
|
+
- 'partial' memories shown with warning (1.0x - no penalty)
|
|
174
|
+
- 'failed' memories excluded by default (use include_failed=True to show)
|
|
175
|
+
- 'superseded' memories excluded and replaced with their superseding memory
|
|
176
|
+
- 'pending' memories shown normally (1.0x)
|
|
177
|
+
- Unreliable memories (failure_count >= 3) excluded by default (use include_unreliable=True)
|
|
178
|
+
|
|
179
|
+
Context-aware search:
|
|
180
|
+
- If current_context provided, memories that worked in similar contexts get +0.2 boost
|
|
181
|
+
- Memories that failed in similar contexts get -0.2 penalty
|
|
182
|
+
- If auto_detect_context=True and project_path provided, context is auto-detected
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
db: Database service instance
|
|
186
|
+
embeddings: Embedding service instance
|
|
187
|
+
query: Search query text
|
|
188
|
+
limit: Maximum number of results
|
|
189
|
+
memory_type: Filter by type (session, decision, code, chunk, error)
|
|
190
|
+
session_id: Filter by session ID
|
|
191
|
+
project_path: Filter by project
|
|
192
|
+
agent_type: Filter by agent that created the memory
|
|
193
|
+
success_only: Only return memories marked as successful (legacy)
|
|
194
|
+
threshold: Minimum similarity threshold (0-1)
|
|
195
|
+
include_failed: Include memories with outcome_status='failed' (default False)
|
|
196
|
+
include_superseded: Include memories with outcome_status='superseded' (default False)
|
|
197
|
+
include_unreliable: Include memories with failure_count >= 3 (default False)
|
|
198
|
+
outcome_status: Filter by specific outcome status
|
|
199
|
+
current_context: Context dict with project_type, tech_stack, file_patterns
|
|
200
|
+
auto_detect_context: If True and project_path provided, auto-detect context
|
|
201
|
+
include_graph: Enrich results with graph context (fixes, rationale, contradictions)
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Dict with search results ranked by: (similarity * 0.7) + (confidence * 0.3) + context_adjustment
|
|
205
|
+
Each result includes outcome_status, outcome_warning, outcome_boost, context_adjustment,
|
|
206
|
+
and context_recommendation fields.
|
|
207
|
+
"""
|
|
208
|
+
# Auto-detect context from project_path if enabled
|
|
209
|
+
detected_context = None
|
|
210
|
+
if auto_detect_context and project_path and not current_context:
|
|
211
|
+
try:
|
|
212
|
+
from skills.context import detect_project_context
|
|
213
|
+
detected_context = detect_project_context(project_path)
|
|
214
|
+
except Exception:
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
# Use provided context or detected context
|
|
218
|
+
search_context = current_context or detected_context
|
|
219
|
+
|
|
220
|
+
# Generate embedding for the query (may return None if Ollama unavailable)
|
|
221
|
+
query_embedding = await embeddings.generate_embedding(query)
|
|
222
|
+
|
|
223
|
+
# Determine search method based on embedding availability
|
|
224
|
+
search_method = "semantic"
|
|
225
|
+
results = []
|
|
226
|
+
|
|
227
|
+
if query_embedding is not None:
|
|
228
|
+
# Use semantic search with embeddings
|
|
229
|
+
results = await db.search_similar(
|
|
230
|
+
embedding=query_embedding,
|
|
231
|
+
limit=limit,
|
|
232
|
+
memory_type=memory_type,
|
|
233
|
+
session_id=session_id,
|
|
234
|
+
project_path=project_path,
|
|
235
|
+
agent_type=agent_type,
|
|
236
|
+
success_only=success_only,
|
|
237
|
+
threshold=threshold,
|
|
238
|
+
include_failed=include_failed,
|
|
239
|
+
include_superseded=include_superseded,
|
|
240
|
+
include_unreliable=include_unreliable,
|
|
241
|
+
outcome_status=outcome_status,
|
|
242
|
+
current_context=search_context,
|
|
243
|
+
query_text=query,
|
|
244
|
+
temperature=temperature
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
# Fallback to keyword search
|
|
248
|
+
search_method = "keyword"
|
|
249
|
+
results = await db.keyword_search(
|
|
250
|
+
query=query,
|
|
251
|
+
limit=limit,
|
|
252
|
+
memory_type=memory_type,
|
|
253
|
+
session_id=session_id,
|
|
254
|
+
project_path=project_path,
|
|
255
|
+
agent_type=agent_type,
|
|
256
|
+
success_only=success_only,
|
|
257
|
+
include_failed=include_failed,
|
|
258
|
+
include_superseded=include_superseded,
|
|
259
|
+
include_unreliable=include_unreliable,
|
|
260
|
+
outcome_status=outcome_status
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Enrich with graph context if requested
|
|
264
|
+
if include_graph:
|
|
265
|
+
try:
|
|
266
|
+
results = await _enrich_with_graph_context(db, results)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.warning(f"Failed to enrich with graph context: {e}")
|
|
269
|
+
# Continue with unenriched results
|
|
270
|
+
|
|
271
|
+
# Promote accessed memories if their tier should be higher (fire-and-forget)
|
|
272
|
+
if results:
|
|
273
|
+
try:
|
|
274
|
+
from services.tier_manager import TierManager
|
|
275
|
+
tier_mgr = TierManager(db)
|
|
276
|
+
for r in results:
|
|
277
|
+
mem_id = r.get('id')
|
|
278
|
+
if mem_id:
|
|
279
|
+
await tier_mgr.promote_on_access(mem_id)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.debug(f"Tier promotion on access failed (non-fatal): {e}")
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
"success": True,
|
|
285
|
+
"query": query,
|
|
286
|
+
"results": results,
|
|
287
|
+
"count": len(results),
|
|
288
|
+
"search_method": search_method,
|
|
289
|
+
"degraded_mode": embeddings.is_degraded(),
|
|
290
|
+
"filters": {
|
|
291
|
+
"type": memory_type,
|
|
292
|
+
"project": project_path,
|
|
293
|
+
"agent": agent_type,
|
|
294
|
+
"success_only": success_only,
|
|
295
|
+
"include_failed": include_failed,
|
|
296
|
+
"include_superseded": include_superseded,
|
|
297
|
+
"include_unreliable": include_unreliable,
|
|
298
|
+
"outcome_status": outcome_status,
|
|
299
|
+
"include_graph": include_graph
|
|
300
|
+
},
|
|
301
|
+
"context_aware": search_context is not None,
|
|
302
|
+
"detected_context": detected_context,
|
|
303
|
+
"threshold": threshold if search_method == "semantic" else None,
|
|
304
|
+
"temperature": temperature
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
async def search_patterns(
|
|
309
|
+
db: DatabaseService,
|
|
310
|
+
embeddings: EmbeddingService,
|
|
311
|
+
query: str,
|
|
312
|
+
limit: int = 5,
|
|
313
|
+
problem_type: Optional[str] = None,
|
|
314
|
+
threshold: float = 0.5
|
|
315
|
+
) -> Dict[str, Any]:
|
|
316
|
+
"""
|
|
317
|
+
Search for reusable solution patterns.
|
|
318
|
+
|
|
319
|
+
Includes fallback to keyword search when Ollama is unavailable.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
db: Database service instance
|
|
323
|
+
embeddings: Embedding service instance
|
|
324
|
+
query: Problem description or search query
|
|
325
|
+
limit: Maximum number of results
|
|
326
|
+
problem_type: Filter by problem type
|
|
327
|
+
threshold: Minimum similarity threshold
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Dict with patterns ranked by similarity * success_rate
|
|
331
|
+
"""
|
|
332
|
+
query_embedding = await embeddings.generate_embedding(query)
|
|
333
|
+
|
|
334
|
+
search_method = "semantic"
|
|
335
|
+
results = []
|
|
336
|
+
|
|
337
|
+
if query_embedding is not None:
|
|
338
|
+
results = await db.search_patterns(
|
|
339
|
+
embedding=query_embedding,
|
|
340
|
+
limit=limit,
|
|
341
|
+
problem_type=problem_type,
|
|
342
|
+
threshold=threshold
|
|
343
|
+
)
|
|
344
|
+
else:
|
|
345
|
+
# Fallback: keyword search on patterns table
|
|
346
|
+
search_method = "keyword"
|
|
347
|
+
results = await db.keyword_search_patterns(
|
|
348
|
+
query=query,
|
|
349
|
+
limit=limit,
|
|
350
|
+
problem_type=problem_type
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
"success": True,
|
|
355
|
+
"query": query,
|
|
356
|
+
"patterns": results,
|
|
357
|
+
"count": len(results),
|
|
358
|
+
"search_method": search_method,
|
|
359
|
+
"degraded_mode": embeddings.is_degraded(),
|
|
360
|
+
"problem_type": problem_type
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
async def get_project_context(
|
|
365
|
+
db: DatabaseService,
|
|
366
|
+
embeddings: EmbeddingService,
|
|
367
|
+
project_path: str,
|
|
368
|
+
query: Optional[str] = None,
|
|
369
|
+
limit: int = 5
|
|
370
|
+
) -> Dict[str, Any]:
|
|
371
|
+
"""
|
|
372
|
+
Get all relevant context for a project.
|
|
373
|
+
|
|
374
|
+
Includes fallback to keyword search when Ollama is unavailable.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
db: Database service instance
|
|
378
|
+
embeddings: Embedding service instance
|
|
379
|
+
project_path: Path to the project
|
|
380
|
+
query: Optional query to filter relevant memories
|
|
381
|
+
limit: Max memories to return (default reduced to 5 for response size)
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dict with project info and relevant memories
|
|
385
|
+
"""
|
|
386
|
+
# Get project info
|
|
387
|
+
project = await db.get_project(project_path)
|
|
388
|
+
|
|
389
|
+
# Get recent decisions for this project
|
|
390
|
+
decisions = await db.get_memories_by_type(
|
|
391
|
+
memory_type="decision",
|
|
392
|
+
project_path=project_path,
|
|
393
|
+
limit=limit
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Get patterns used in this project
|
|
397
|
+
patterns = await db.get_memories_by_type(
|
|
398
|
+
memory_type="code",
|
|
399
|
+
project_path=project_path,
|
|
400
|
+
limit=limit
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# If query provided, search for relevant memories
|
|
404
|
+
relevant = []
|
|
405
|
+
search_method = None
|
|
406
|
+
if query:
|
|
407
|
+
query_embedding = await embeddings.generate_embedding(query)
|
|
408
|
+
if query_embedding is not None:
|
|
409
|
+
search_method = "semantic"
|
|
410
|
+
relevant = await db.search_similar(
|
|
411
|
+
embedding=query_embedding,
|
|
412
|
+
project_path=project_path,
|
|
413
|
+
limit=limit,
|
|
414
|
+
threshold=0.4
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
# Fallback to keyword search
|
|
418
|
+
search_method = "keyword"
|
|
419
|
+
relevant = await db.keyword_search(
|
|
420
|
+
query=query,
|
|
421
|
+
project_path=project_path,
|
|
422
|
+
limit=limit
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Enrich all result lists with graph context (relationships, contradictions, causal chains)
|
|
426
|
+
try:
|
|
427
|
+
if decisions:
|
|
428
|
+
decisions = await _enrich_with_graph_context(db, decisions)
|
|
429
|
+
if patterns:
|
|
430
|
+
patterns = await _enrich_with_graph_context(db, patterns)
|
|
431
|
+
if relevant:
|
|
432
|
+
relevant = await _enrich_with_graph_context(db, relevant)
|
|
433
|
+
except Exception as e:
|
|
434
|
+
logger.warning(f"Failed to enrich project context with graph: {e}")
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
"success": True,
|
|
438
|
+
"project": project,
|
|
439
|
+
"decisions": decisions,
|
|
440
|
+
"code_patterns": patterns,
|
|
441
|
+
"relevant_to_query": relevant if query else None,
|
|
442
|
+
"search_method": search_method,
|
|
443
|
+
"degraded_mode": embeddings.is_degraded()
|
|
444
|
+
}
|