alma-memory 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.
- alma/__init__.py +99 -29
- alma/confidence/__init__.py +47 -0
- alma/confidence/engine.py +540 -0
- alma/confidence/types.py +351 -0
- alma/config/loader.py +3 -2
- alma/consolidation/__init__.py +23 -0
- alma/consolidation/engine.py +678 -0
- alma/consolidation/prompts.py +84 -0
- alma/core.py +15 -15
- alma/domains/__init__.py +6 -6
- alma/domains/factory.py +12 -9
- alma/domains/schemas.py +17 -3
- alma/domains/types.py +8 -4
- alma/events/__init__.py +75 -0
- alma/events/emitter.py +284 -0
- alma/events/storage_mixin.py +246 -0
- alma/events/types.py +126 -0
- alma/events/webhook.py +425 -0
- alma/exceptions.py +49 -0
- alma/extraction/__init__.py +31 -0
- alma/extraction/auto_learner.py +264 -0
- alma/extraction/extractor.py +420 -0
- alma/graph/__init__.py +81 -0
- alma/graph/backends/__init__.py +18 -0
- alma/graph/backends/memory.py +236 -0
- alma/graph/backends/neo4j.py +417 -0
- alma/graph/base.py +159 -0
- alma/graph/extraction.py +198 -0
- alma/graph/store.py +860 -0
- alma/harness/__init__.py +4 -4
- alma/harness/base.py +18 -9
- alma/harness/domains.py +27 -11
- alma/initializer/__init__.py +37 -0
- alma/initializer/initializer.py +418 -0
- alma/initializer/types.py +250 -0
- alma/integration/__init__.py +9 -9
- alma/integration/claude_agents.py +10 -10
- alma/integration/helena.py +32 -22
- alma/integration/victor.py +57 -33
- alma/learning/__init__.py +27 -27
- alma/learning/forgetting.py +198 -148
- alma/learning/heuristic_extractor.py +40 -24
- alma/learning/protocols.py +62 -14
- alma/learning/validation.py +7 -2
- alma/mcp/__init__.py +4 -4
- alma/mcp/__main__.py +2 -1
- alma/mcp/resources.py +17 -16
- alma/mcp/server.py +102 -44
- alma/mcp/tools.py +174 -37
- alma/progress/__init__.py +3 -3
- alma/progress/tracker.py +26 -20
- alma/progress/types.py +8 -12
- alma/py.typed +0 -0
- alma/retrieval/__init__.py +11 -11
- alma/retrieval/cache.py +20 -21
- alma/retrieval/embeddings.py +4 -4
- alma/retrieval/engine.py +114 -35
- alma/retrieval/scoring.py +73 -63
- alma/session/__init__.py +2 -2
- alma/session/manager.py +5 -5
- alma/session/types.py +5 -4
- alma/storage/__init__.py +41 -0
- alma/storage/azure_cosmos.py +107 -31
- alma/storage/base.py +157 -4
- alma/storage/chroma.py +1443 -0
- alma/storage/file_based.py +56 -20
- alma/storage/pinecone.py +1080 -0
- alma/storage/postgresql.py +1452 -0
- alma/storage/qdrant.py +1306 -0
- alma/storage/sqlite_local.py +376 -31
- alma/types.py +62 -14
- alma_memory-0.5.0.dist-info/METADATA +905 -0
- alma_memory-0.5.0.dist-info/RECORD +76 -0
- {alma_memory-0.3.0.dist-info → alma_memory-0.5.0.dist-info}/WHEEL +1 -1
- alma_memory-0.3.0.dist-info/METADATA +0 -438
- alma_memory-0.3.0.dist-info/RECORD +0 -46
- {alma_memory-0.3.0.dist-info → alma_memory-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -4,15 +4,15 @@ ALMA Heuristic Extraction.
|
|
|
4
4
|
Analyzes outcomes to identify patterns and create heuristics.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import uuid
|
|
8
7
|
import logging
|
|
9
|
-
|
|
10
|
-
from typing import Optional, List, Dict, Any, Tuple
|
|
11
|
-
from dataclasses import dataclass, field
|
|
8
|
+
import uuid
|
|
12
9
|
from collections import defaultdict
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
13
13
|
|
|
14
|
-
from alma.types import Heuristic, Outcome, MemoryScope
|
|
15
14
|
from alma.storage.base import StorageBackend
|
|
15
|
+
from alma.types import Heuristic, MemoryScope, Outcome
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
@@ -20,6 +20,7 @@ logger = logging.getLogger(__name__)
|
|
|
20
20
|
@dataclass
|
|
21
21
|
class PatternCandidate:
|
|
22
22
|
"""A potential pattern for heuristic creation."""
|
|
23
|
+
|
|
23
24
|
task_type: str
|
|
24
25
|
strategy: str
|
|
25
26
|
occurrence_count: int
|
|
@@ -56,6 +57,7 @@ class PatternCandidate:
|
|
|
56
57
|
@dataclass
|
|
57
58
|
class ExtractionResult:
|
|
58
59
|
"""Result of heuristic extraction."""
|
|
60
|
+
|
|
59
61
|
heuristics_created: int = 0
|
|
60
62
|
heuristics_updated: int = 0
|
|
61
63
|
patterns_analyzed: int = 0
|
|
@@ -137,7 +139,7 @@ class HeuristicExtractor:
|
|
|
137
139
|
# Group outcomes by agent and task type
|
|
138
140
|
grouped = self._group_outcomes(outcomes)
|
|
139
141
|
|
|
140
|
-
for (ag,
|
|
142
|
+
for (ag, _task_type), type_outcomes in grouped.items():
|
|
141
143
|
# Find patterns within this group
|
|
142
144
|
patterns = self._identify_patterns(type_outcomes)
|
|
143
145
|
result.patterns_analyzed += len(patterns)
|
|
@@ -204,14 +206,16 @@ class HeuristicExtractor:
|
|
|
204
206
|
patterns = []
|
|
205
207
|
for strategy, group_outcomes in strategy_groups.items():
|
|
206
208
|
success_count = sum(1 for o in group_outcomes if o.success)
|
|
207
|
-
patterns.append(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
patterns.append(
|
|
210
|
+
PatternCandidate(
|
|
211
|
+
task_type=group_outcomes[0].task_type,
|
|
212
|
+
strategy=strategy,
|
|
213
|
+
occurrence_count=len(group_outcomes),
|
|
214
|
+
success_count=success_count,
|
|
215
|
+
failure_count=len(group_outcomes) - success_count,
|
|
216
|
+
outcomes=group_outcomes,
|
|
217
|
+
)
|
|
218
|
+
)
|
|
215
219
|
|
|
216
220
|
return patterns
|
|
217
221
|
|
|
@@ -291,8 +295,9 @@ class HeuristicExtractor:
|
|
|
291
295
|
)
|
|
292
296
|
|
|
293
297
|
for h in heuristics:
|
|
294
|
-
if
|
|
295
|
-
|
|
298
|
+
if task_type in h.condition and self._strategies_similar(
|
|
299
|
+
h.strategy, strategy
|
|
300
|
+
):
|
|
296
301
|
return h
|
|
297
302
|
|
|
298
303
|
return None
|
|
@@ -305,13 +310,9 @@ class HeuristicExtractor:
|
|
|
305
310
|
"""Update an existing heuristic with new data."""
|
|
306
311
|
# Merge counts
|
|
307
312
|
heuristic.occurrence_count = max(
|
|
308
|
-
heuristic.occurrence_count,
|
|
309
|
-
pattern.occurrence_count
|
|
310
|
-
)
|
|
311
|
-
heuristic.success_count = max(
|
|
312
|
-
heuristic.success_count,
|
|
313
|
-
pattern.success_count
|
|
313
|
+
heuristic.occurrence_count, pattern.occurrence_count
|
|
314
314
|
)
|
|
315
|
+
heuristic.success_count = max(heuristic.success_count, pattern.success_count)
|
|
315
316
|
|
|
316
317
|
# Update confidence
|
|
317
318
|
heuristic.confidence = pattern.confidence
|
|
@@ -344,8 +345,23 @@ class HeuristicExtractor:
|
|
|
344
345
|
"""Normalize strategy text for comparison."""
|
|
345
346
|
# Remove common stop words and normalize
|
|
346
347
|
stop_words = {
|
|
347
|
-
"the",
|
|
348
|
-
"
|
|
348
|
+
"the",
|
|
349
|
+
"a",
|
|
350
|
+
"an",
|
|
351
|
+
"and",
|
|
352
|
+
"or",
|
|
353
|
+
"but",
|
|
354
|
+
"in",
|
|
355
|
+
"on",
|
|
356
|
+
"at",
|
|
357
|
+
"to",
|
|
358
|
+
"for",
|
|
359
|
+
"of",
|
|
360
|
+
"with",
|
|
361
|
+
"by",
|
|
362
|
+
"then",
|
|
363
|
+
"first",
|
|
364
|
+
"next",
|
|
349
365
|
}
|
|
350
366
|
|
|
351
367
|
words = strategy.lower().replace(",", " ").replace(".", " ").split()
|
alma/learning/protocols.py
CHANGED
|
@@ -4,20 +4,23 @@ ALMA Learning Protocols.
|
|
|
4
4
|
Defines how agents learn from outcomes while respecting scope constraints.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import uuid
|
|
8
7
|
import logging
|
|
9
|
-
|
|
10
|
-
from
|
|
8
|
+
import uuid
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
|
11
11
|
|
|
12
|
+
from alma.storage.base import StorageBackend
|
|
12
13
|
from alma.types import (
|
|
14
|
+
AntiPattern,
|
|
15
|
+
DomainKnowledge,
|
|
13
16
|
Heuristic,
|
|
17
|
+
MemoryScope,
|
|
14
18
|
Outcome,
|
|
15
19
|
UserPreference,
|
|
16
|
-
DomainKnowledge,
|
|
17
|
-
AntiPattern,
|
|
18
|
-
MemoryScope,
|
|
19
20
|
)
|
|
20
|
-
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from alma.retrieval.embeddings import EmbeddingProvider
|
|
21
24
|
|
|
22
25
|
logger = logging.getLogger(__name__)
|
|
23
26
|
|
|
@@ -36,6 +39,8 @@ class LearningProtocol:
|
|
|
36
39
|
self,
|
|
37
40
|
storage: StorageBackend,
|
|
38
41
|
scopes: Dict[str, MemoryScope],
|
|
42
|
+
embedder: Optional["EmbeddingProvider"] = None,
|
|
43
|
+
similarity_threshold: float = 0.75,
|
|
39
44
|
):
|
|
40
45
|
"""
|
|
41
46
|
Initialize learning protocol.
|
|
@@ -43,9 +48,13 @@ class LearningProtocol:
|
|
|
43
48
|
Args:
|
|
44
49
|
storage: Storage backend for persistence
|
|
45
50
|
scopes: Dict of agent_name -> MemoryScope
|
|
51
|
+
embedder: Optional embedding provider for semantic similarity
|
|
52
|
+
similarity_threshold: Cosine similarity threshold for strategy matching (default 0.75)
|
|
46
53
|
"""
|
|
47
54
|
self.storage = storage
|
|
48
55
|
self.scopes = scopes
|
|
56
|
+
self.embedder = embedder
|
|
57
|
+
self.similarity_threshold = similarity_threshold
|
|
49
58
|
|
|
50
59
|
def learn(
|
|
51
60
|
self,
|
|
@@ -100,7 +109,9 @@ class LearningProtocol:
|
|
|
100
109
|
|
|
101
110
|
# Save outcome
|
|
102
111
|
self.storage.save_outcome(outcome_record)
|
|
103
|
-
logger.info(
|
|
112
|
+
logger.info(
|
|
113
|
+
f"Recorded outcome for {agent}: {'success' if outcome else 'failure'}"
|
|
114
|
+
)
|
|
104
115
|
|
|
105
116
|
# Check if we should create/update a heuristic
|
|
106
117
|
self._maybe_create_heuristic(
|
|
@@ -153,7 +164,8 @@ class LearningProtocol:
|
|
|
153
164
|
|
|
154
165
|
# Filter to same strategy
|
|
155
166
|
same_strategy = [
|
|
156
|
-
o
|
|
167
|
+
o
|
|
168
|
+
for o in similar_outcomes
|
|
157
169
|
if self._strategies_similar(o.strategy_used, strategy)
|
|
158
170
|
]
|
|
159
171
|
|
|
@@ -200,9 +212,11 @@ class LearningProtocol:
|
|
|
200
212
|
|
|
201
213
|
# Filter to failures with similar error
|
|
202
214
|
similar = [
|
|
203
|
-
o
|
|
204
|
-
|
|
205
|
-
|
|
215
|
+
o
|
|
216
|
+
for o in similar_failures
|
|
217
|
+
if not o.success
|
|
218
|
+
and o.error_message
|
|
219
|
+
and self._errors_similar(o.error_message, error)
|
|
206
220
|
]
|
|
207
221
|
|
|
208
222
|
if len(similar) >= 2: # At least 2 similar failures
|
|
@@ -311,13 +325,47 @@ class LearningProtocol:
|
|
|
311
325
|
return "general"
|
|
312
326
|
|
|
313
327
|
def _strategies_similar(self, s1: str, s2: str) -> bool:
|
|
314
|
-
"""
|
|
315
|
-
|
|
328
|
+
"""
|
|
329
|
+
Check if two strategies are similar enough to count together.
|
|
330
|
+
|
|
331
|
+
Uses embedding-based cosine similarity when an embedder is available,
|
|
332
|
+
otherwise falls back to simple word overlap.
|
|
333
|
+
"""
|
|
334
|
+
if self.embedder is not None:
|
|
335
|
+
return self._strategies_similar_embedding(s1, s2)
|
|
336
|
+
return self._strategies_similar_word_overlap(s1, s2)
|
|
337
|
+
|
|
338
|
+
def _strategies_similar_embedding(self, s1: str, s2: str) -> bool:
|
|
339
|
+
"""Check strategy similarity using embedding cosine similarity."""
|
|
340
|
+
try:
|
|
341
|
+
emb1 = self.embedder.encode(s1)
|
|
342
|
+
emb2 = self.embedder.encode(s2)
|
|
343
|
+
similarity = self._cosine_similarity(emb1, emb2)
|
|
344
|
+
return similarity >= self.similarity_threshold
|
|
345
|
+
except Exception as e:
|
|
346
|
+
logger.warning(
|
|
347
|
+
f"Embedding similarity failed, falling back to word overlap: {e}"
|
|
348
|
+
)
|
|
349
|
+
return self._strategies_similar_word_overlap(s1, s2)
|
|
350
|
+
|
|
351
|
+
def _strategies_similar_word_overlap(self, s1: str, s2: str) -> bool:
|
|
352
|
+
"""Check strategy similarity using simple word overlap."""
|
|
316
353
|
words1 = set(s1.lower().split())
|
|
317
354
|
words2 = set(s2.lower().split())
|
|
318
355
|
overlap = len(words1 & words2)
|
|
319
356
|
return overlap >= min(3, len(words1) // 2)
|
|
320
357
|
|
|
358
|
+
def _cosine_similarity(self, v1: list, v2: list) -> float:
|
|
359
|
+
"""Compute cosine similarity between two vectors."""
|
|
360
|
+
import math
|
|
361
|
+
|
|
362
|
+
dot_product = sum(a * b for a, b in zip(v1, v2, strict=False))
|
|
363
|
+
norm1 = math.sqrt(sum(a * a for a in v1))
|
|
364
|
+
norm2 = math.sqrt(sum(b * b for b in v2))
|
|
365
|
+
if norm1 == 0 or norm2 == 0:
|
|
366
|
+
return 0.0
|
|
367
|
+
return dot_product / (norm1 * norm2)
|
|
368
|
+
|
|
321
369
|
def _errors_similar(self, e1: str, e2: str) -> bool:
|
|
322
370
|
"""Check if two errors are similar."""
|
|
323
371
|
# Simple substring check
|
alma/learning/validation.py
CHANGED
|
@@ -5,9 +5,9 @@ Enforces scope constraints and validates learning requests.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
-
from typing import Optional, List, Dict, Any, Set
|
|
9
8
|
from dataclasses import dataclass, field
|
|
10
9
|
from enum import Enum
|
|
10
|
+
from typing import Any, Dict, List, Optional, Set
|
|
11
11
|
|
|
12
12
|
from alma.types import MemoryScope
|
|
13
13
|
|
|
@@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
class ValidationResult(Enum):
|
|
18
18
|
"""Result of a validation check."""
|
|
19
|
+
|
|
19
20
|
ALLOWED = "allowed"
|
|
20
21
|
DENIED_OUT_OF_SCOPE = "denied_out_of_scope"
|
|
21
22
|
DENIED_FORBIDDEN = "denied_forbidden"
|
|
@@ -26,6 +27,7 @@ class ValidationResult(Enum):
|
|
|
26
27
|
@dataclass
|
|
27
28
|
class ValidationReport:
|
|
28
29
|
"""Detailed report of a validation check."""
|
|
30
|
+
|
|
29
31
|
result: ValidationResult
|
|
30
32
|
agent: str
|
|
31
33
|
domain: str
|
|
@@ -36,7 +38,10 @@ class ValidationReport:
|
|
|
36
38
|
@property
|
|
37
39
|
def is_allowed(self) -> bool:
|
|
38
40
|
"""Check if the validation passed."""
|
|
39
|
-
return self.result in (
|
|
41
|
+
return self.result in (
|
|
42
|
+
ValidationResult.ALLOWED,
|
|
43
|
+
ValidationResult.WARNING_NO_SCOPE,
|
|
44
|
+
)
|
|
40
45
|
|
|
41
46
|
def to_dict(self) -> Dict[str, Any]:
|
|
42
47
|
"""Convert to dictionary."""
|
alma/mcp/__init__.py
CHANGED
|
@@ -24,13 +24,13 @@ Integration with Claude Code (.mcp.json):
|
|
|
24
24
|
|
|
25
25
|
from alma.mcp.server import ALMAMCPServer
|
|
26
26
|
from alma.mcp.tools import (
|
|
27
|
-
alma_retrieve,
|
|
28
|
-
alma_learn,
|
|
29
|
-
alma_add_preference,
|
|
30
27
|
alma_add_knowledge,
|
|
28
|
+
alma_add_preference,
|
|
31
29
|
alma_forget,
|
|
32
|
-
alma_stats,
|
|
33
30
|
alma_health,
|
|
31
|
+
alma_learn,
|
|
32
|
+
alma_retrieve,
|
|
33
|
+
alma_stats,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
__all__ = [
|
alma/mcp/__main__.py
CHANGED
alma/mcp/resources.py
CHANGED
|
@@ -6,10 +6,9 @@ Resources represent configuration and metadata about the ALMA instance.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
10
|
|
|
11
11
|
from alma import ALMA
|
|
12
|
-
from alma.types import MemoryScope
|
|
13
12
|
|
|
14
13
|
logger = logging.getLogger(__name__)
|
|
15
14
|
|
|
@@ -70,20 +69,22 @@ def get_agents_resource(alma: ALMA) -> Dict[str, Any]:
|
|
|
70
69
|
except Exception:
|
|
71
70
|
stats = {}
|
|
72
71
|
|
|
73
|
-
agents.append(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
agents.append(
|
|
73
|
+
{
|
|
74
|
+
"name": name,
|
|
75
|
+
"scope": {
|
|
76
|
+
"can_learn": scope.can_learn,
|
|
77
|
+
"cannot_learn": scope.cannot_learn,
|
|
78
|
+
"min_occurrences_for_heuristic": scope.min_occurrences_for_heuristic,
|
|
79
|
+
},
|
|
80
|
+
"stats": {
|
|
81
|
+
"heuristics_count": stats.get("heuristics_count", 0),
|
|
82
|
+
"outcomes_count": stats.get("outcomes_count", 0),
|
|
83
|
+
"domain_knowledge_count": stats.get("domain_knowledge_count", 0),
|
|
84
|
+
"anti_patterns_count": stats.get("anti_patterns_count", 0),
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
)
|
|
87
88
|
|
|
88
89
|
return {
|
|
89
90
|
"uri": "alma://agents",
|
alma/mcp/server.py
CHANGED
|
@@ -9,23 +9,23 @@ import asyncio
|
|
|
9
9
|
import json
|
|
10
10
|
import logging
|
|
11
11
|
import sys
|
|
12
|
-
from typing import
|
|
13
|
-
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
14
13
|
|
|
15
14
|
from alma import ALMA
|
|
15
|
+
from alma.mcp.resources import (
|
|
16
|
+
get_agents_resource,
|
|
17
|
+
get_config_resource,
|
|
18
|
+
list_resources,
|
|
19
|
+
)
|
|
16
20
|
from alma.mcp.tools import (
|
|
17
|
-
alma_retrieve,
|
|
18
|
-
alma_learn,
|
|
19
|
-
alma_add_preference,
|
|
20
21
|
alma_add_knowledge,
|
|
22
|
+
alma_add_preference,
|
|
23
|
+
alma_consolidate,
|
|
21
24
|
alma_forget,
|
|
22
|
-
alma_stats,
|
|
23
25
|
alma_health,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
get_agents_resource,
|
|
28
|
-
list_resources,
|
|
26
|
+
alma_learn,
|
|
27
|
+
alma_retrieve,
|
|
28
|
+
alma_stats,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
logger = logging.getLogger(__name__)
|
|
@@ -235,6 +235,41 @@ class ALMAMCPServer:
|
|
|
235
235
|
"properties": {},
|
|
236
236
|
},
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
"name": "alma_consolidate",
|
|
240
|
+
"description": "Consolidate similar memories to reduce redundancy. Merges near-duplicate memories based on semantic similarity. Use dry_run=true first to preview what would be merged.",
|
|
241
|
+
"inputSchema": {
|
|
242
|
+
"type": "object",
|
|
243
|
+
"properties": {
|
|
244
|
+
"agent": {
|
|
245
|
+
"type": "string",
|
|
246
|
+
"description": "Agent whose memories to consolidate",
|
|
247
|
+
},
|
|
248
|
+
"memory_type": {
|
|
249
|
+
"type": "string",
|
|
250
|
+
"enum": [
|
|
251
|
+
"heuristics",
|
|
252
|
+
"outcomes",
|
|
253
|
+
"domain_knowledge",
|
|
254
|
+
"anti_patterns",
|
|
255
|
+
],
|
|
256
|
+
"description": "Type of memory to consolidate (default: heuristics)",
|
|
257
|
+
"default": "heuristics",
|
|
258
|
+
},
|
|
259
|
+
"similarity_threshold": {
|
|
260
|
+
"type": "number",
|
|
261
|
+
"description": "Minimum cosine similarity to group memories (0.0-1.0, default: 0.85). Higher values are more conservative.",
|
|
262
|
+
"default": 0.85,
|
|
263
|
+
},
|
|
264
|
+
"dry_run": {
|
|
265
|
+
"type": "boolean",
|
|
266
|
+
"description": "If true, preview what would be merged without modifying storage (default: true)",
|
|
267
|
+
"default": True,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
"required": ["agent"],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
238
273
|
]
|
|
239
274
|
|
|
240
275
|
async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -281,17 +316,20 @@ class ALMAMCPServer:
|
|
|
281
316
|
params: Dict[str, Any],
|
|
282
317
|
) -> Dict[str, Any]:
|
|
283
318
|
"""Handle initialize request."""
|
|
284
|
-
return self._success_response(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
"
|
|
288
|
-
"
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
"
|
|
319
|
+
return self._success_response(
|
|
320
|
+
request_id,
|
|
321
|
+
{
|
|
322
|
+
"protocolVersion": "2024-11-05",
|
|
323
|
+
"serverInfo": {
|
|
324
|
+
"name": self.server_name,
|
|
325
|
+
"version": self.server_version,
|
|
326
|
+
},
|
|
327
|
+
"capabilities": {
|
|
328
|
+
"tools": {},
|
|
329
|
+
"resources": {},
|
|
330
|
+
},
|
|
293
331
|
},
|
|
294
|
-
|
|
332
|
+
)
|
|
295
333
|
|
|
296
334
|
def _handle_tools_list(self, request_id: Optional[int]) -> Dict[str, Any]:
|
|
297
335
|
"""Handle tools/list request."""
|
|
@@ -351,6 +389,13 @@ class ALMAMCPServer:
|
|
|
351
389
|
agent=arguments.get("agent"),
|
|
352
390
|
),
|
|
353
391
|
"alma_health": lambda: alma_health(self.alma),
|
|
392
|
+
"alma_consolidate": lambda: alma_consolidate(
|
|
393
|
+
self.alma,
|
|
394
|
+
agent=arguments.get("agent", ""),
|
|
395
|
+
memory_type=arguments.get("memory_type", "heuristics"),
|
|
396
|
+
similarity_threshold=arguments.get("similarity_threshold", 0.85),
|
|
397
|
+
dry_run=arguments.get("dry_run", True),
|
|
398
|
+
),
|
|
354
399
|
}
|
|
355
400
|
|
|
356
401
|
if tool_name not in tool_handlers:
|
|
@@ -362,14 +407,21 @@ class ALMAMCPServer:
|
|
|
362
407
|
|
|
363
408
|
result = tool_handlers[tool_name]()
|
|
364
409
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
410
|
+
# Handle async functions (like alma_consolidate)
|
|
411
|
+
if asyncio.iscoroutine(result):
|
|
412
|
+
result = await result
|
|
413
|
+
|
|
414
|
+
return self._success_response(
|
|
415
|
+
request_id,
|
|
416
|
+
{
|
|
417
|
+
"content": [
|
|
418
|
+
{
|
|
419
|
+
"type": "text",
|
|
420
|
+
"text": json.dumps(result, indent=2),
|
|
421
|
+
}
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
)
|
|
373
425
|
|
|
374
426
|
def _handle_resources_list(
|
|
375
427
|
self,
|
|
@@ -397,15 +449,18 @@ class ALMAMCPServer:
|
|
|
397
449
|
f"Unknown resource: {uri}",
|
|
398
450
|
)
|
|
399
451
|
|
|
400
|
-
return self._success_response(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
452
|
+
return self._success_response(
|
|
453
|
+
request_id,
|
|
454
|
+
{
|
|
455
|
+
"contents": [
|
|
456
|
+
{
|
|
457
|
+
"uri": resource["uri"],
|
|
458
|
+
"mimeType": resource["mimeType"],
|
|
459
|
+
"text": json.dumps(resource["content"], indent=2),
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
)
|
|
409
464
|
|
|
410
465
|
def _success_response(
|
|
411
466
|
self,
|
|
@@ -437,15 +492,16 @@ class ALMAMCPServer:
|
|
|
437
492
|
|
|
438
493
|
async def run_stdio(self):
|
|
439
494
|
"""Run the server in stdio mode for Claude Code integration."""
|
|
440
|
-
logger.info(
|
|
495
|
+
logger.info("Starting ALMA MCP Server (stdio mode)")
|
|
441
496
|
|
|
442
497
|
reader = asyncio.StreamReader()
|
|
443
498
|
protocol = asyncio.StreamReaderProtocol(reader)
|
|
444
|
-
await asyncio.get_event_loop().connect_read_pipe(
|
|
445
|
-
lambda: protocol, sys.stdin
|
|
446
|
-
)
|
|
499
|
+
await asyncio.get_event_loop().connect_read_pipe(lambda: protocol, sys.stdin)
|
|
447
500
|
|
|
448
|
-
|
|
501
|
+
(
|
|
502
|
+
writer_transport,
|
|
503
|
+
writer_protocol,
|
|
504
|
+
) = await asyncio.get_event_loop().connect_write_pipe(
|
|
449
505
|
asyncio.streams.FlowControlMixin, sys.stdout
|
|
450
506
|
)
|
|
451
507
|
writer = asyncio.StreamWriter(
|
|
@@ -497,7 +553,9 @@ class ALMAMCPServer:
|
|
|
497
553
|
try:
|
|
498
554
|
from aiohttp import web
|
|
499
555
|
except ImportError:
|
|
500
|
-
logger.error(
|
|
556
|
+
logger.error(
|
|
557
|
+
"aiohttp required for HTTP mode. Install with: pip install aiohttp"
|
|
558
|
+
)
|
|
501
559
|
return
|
|
502
560
|
|
|
503
561
|
async def handle_post(request: web.Request) -> web.Response:
|