alma-memory 0.2.0__py3-none-any.whl → 0.4.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 +76 -1
- alma/confidence/__init__.py +47 -0
- alma/confidence/engine.py +506 -0
- alma/confidence/types.py +331 -0
- alma/domains/__init__.py +30 -0
- alma/domains/factory.py +356 -0
- alma/domains/schemas.py +434 -0
- alma/domains/types.py +268 -0
- alma/initializer/__init__.py +37 -0
- alma/initializer/initializer.py +410 -0
- alma/initializer/types.py +242 -0
- alma/progress/__init__.py +21 -0
- alma/progress/tracker.py +601 -0
- alma/progress/types.py +254 -0
- alma/session/__init__.py +19 -0
- alma/session/manager.py +399 -0
- alma/session/types.py +287 -0
- alma/storage/azure_cosmos.py +6 -0
- alma/storage/sqlite_local.py +101 -0
- alma_memory-0.4.0.dist-info/METADATA +488 -0
- {alma_memory-0.2.0.dist-info → alma_memory-0.4.0.dist-info}/RECORD +23 -7
- alma_memory-0.2.0.dist-info/METADATA +0 -327
- {alma_memory-0.2.0.dist-info → alma_memory-0.4.0.dist-info}/WHEEL +0 -0
- {alma_memory-0.2.0.dist-info → alma_memory-0.4.0.dist-info}/top_level.txt +0 -0
alma/session/types.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session Management Types.
|
|
3
|
+
|
|
4
|
+
Data models for session continuity and handoffs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Optional, List, Dict, Any, Literal
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
SessionOutcome = Literal["success", "failure", "interrupted", "unknown"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SessionHandoff:
|
|
18
|
+
"""
|
|
19
|
+
Compressed context for session continuity.
|
|
20
|
+
|
|
21
|
+
Captures the essential state at session end so the next session
|
|
22
|
+
can quickly resume without full context reconstruction.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
project_id: str
|
|
27
|
+
agent: str
|
|
28
|
+
session_id: str
|
|
29
|
+
|
|
30
|
+
# Where we left off
|
|
31
|
+
last_action: str
|
|
32
|
+
last_outcome: SessionOutcome
|
|
33
|
+
current_goal: str
|
|
34
|
+
|
|
35
|
+
# Quick context (not full history)
|
|
36
|
+
key_decisions: List[str] = field(default_factory=list) # Max 10 most important
|
|
37
|
+
active_files: List[str] = field(default_factory=list) # Files being worked on
|
|
38
|
+
blockers: List[str] = field(default_factory=list) # Current blockers
|
|
39
|
+
next_steps: List[str] = field(default_factory=list) # Planned next actions
|
|
40
|
+
|
|
41
|
+
# Test/validation state
|
|
42
|
+
test_status: Dict[str, bool] = field(default_factory=dict) # test_name -> passing
|
|
43
|
+
|
|
44
|
+
# Confidence signals
|
|
45
|
+
confidence_level: float = 0.5 # 0-1, how well is this going
|
|
46
|
+
risk_flags: List[str] = field(default_factory=list) # Concerns noted
|
|
47
|
+
|
|
48
|
+
# Timing
|
|
49
|
+
session_start: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
50
|
+
session_end: Optional[datetime] = None
|
|
51
|
+
duration_ms: int = 0
|
|
52
|
+
|
|
53
|
+
# For semantic retrieval
|
|
54
|
+
embedding: Optional[List[float]] = None
|
|
55
|
+
|
|
56
|
+
# Timestamps
|
|
57
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
58
|
+
|
|
59
|
+
# Extensible metadata
|
|
60
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def create(
|
|
64
|
+
cls,
|
|
65
|
+
project_id: str,
|
|
66
|
+
agent: str,
|
|
67
|
+
session_id: str,
|
|
68
|
+
last_action: str,
|
|
69
|
+
current_goal: str,
|
|
70
|
+
last_outcome: SessionOutcome = "unknown",
|
|
71
|
+
session_start: Optional[datetime] = None,
|
|
72
|
+
**kwargs,
|
|
73
|
+
) -> "SessionHandoff":
|
|
74
|
+
"""Factory method to create a new session handoff."""
|
|
75
|
+
return cls(
|
|
76
|
+
id=str(uuid.uuid4()),
|
|
77
|
+
project_id=project_id,
|
|
78
|
+
agent=agent,
|
|
79
|
+
session_id=session_id,
|
|
80
|
+
last_action=last_action,
|
|
81
|
+
last_outcome=last_outcome,
|
|
82
|
+
current_goal=current_goal,
|
|
83
|
+
session_start=session_start or datetime.now(timezone.utc),
|
|
84
|
+
**kwargs,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def finalize(
|
|
88
|
+
self,
|
|
89
|
+
last_action: str,
|
|
90
|
+
last_outcome: SessionOutcome,
|
|
91
|
+
next_steps: Optional[List[str]] = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Finalize the handoff at session end."""
|
|
94
|
+
self.last_action = last_action
|
|
95
|
+
self.last_outcome = last_outcome
|
|
96
|
+
self.session_end = datetime.now(timezone.utc)
|
|
97
|
+
if self.session_start:
|
|
98
|
+
self.duration_ms = int(
|
|
99
|
+
(self.session_end - self.session_start).total_seconds() * 1000
|
|
100
|
+
)
|
|
101
|
+
if next_steps:
|
|
102
|
+
self.next_steps = next_steps[:10] # Cap at 10
|
|
103
|
+
|
|
104
|
+
def add_decision(self, decision: str) -> None:
|
|
105
|
+
"""Record a key decision (max 10)."""
|
|
106
|
+
if len(self.key_decisions) >= 10:
|
|
107
|
+
self.key_decisions.pop(0) # Remove oldest
|
|
108
|
+
self.key_decisions.append(decision)
|
|
109
|
+
|
|
110
|
+
def add_blocker(self, blocker: str) -> None:
|
|
111
|
+
"""Record a blocker."""
|
|
112
|
+
if blocker not in self.blockers:
|
|
113
|
+
self.blockers.append(blocker)
|
|
114
|
+
|
|
115
|
+
def remove_blocker(self, blocker: str) -> None:
|
|
116
|
+
"""Remove a resolved blocker."""
|
|
117
|
+
if blocker in self.blockers:
|
|
118
|
+
self.blockers.remove(blocker)
|
|
119
|
+
|
|
120
|
+
def set_test_status(self, test_name: str, passing: bool) -> None:
|
|
121
|
+
"""Update test status."""
|
|
122
|
+
self.test_status[test_name] = passing
|
|
123
|
+
|
|
124
|
+
def format_quick_reload(self) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Format handoff as a quick reload string.
|
|
127
|
+
|
|
128
|
+
Returns a compact string that can be parsed by the next session
|
|
129
|
+
for rapid context restoration.
|
|
130
|
+
"""
|
|
131
|
+
lines = [
|
|
132
|
+
f"## Session Handoff: {self.session_id}",
|
|
133
|
+
f"Agent: {self.agent}",
|
|
134
|
+
f"Goal: {self.current_goal}",
|
|
135
|
+
f"Last Action: {self.last_action} ({self.last_outcome})",
|
|
136
|
+
f"Confidence: {int(self.confidence_level * 100)}%",
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
if self.next_steps:
|
|
140
|
+
lines.append("\n### Next Steps:")
|
|
141
|
+
for step in self.next_steps[:5]:
|
|
142
|
+
lines.append(f"- {step}")
|
|
143
|
+
|
|
144
|
+
if self.blockers:
|
|
145
|
+
lines.append("\n### Blockers:")
|
|
146
|
+
for blocker in self.blockers:
|
|
147
|
+
lines.append(f"- {blocker}")
|
|
148
|
+
|
|
149
|
+
if self.key_decisions:
|
|
150
|
+
lines.append("\n### Key Decisions:")
|
|
151
|
+
for decision in self.key_decisions[-5:]: # Last 5 decisions
|
|
152
|
+
lines.append(f"- {decision}")
|
|
153
|
+
|
|
154
|
+
if self.active_files:
|
|
155
|
+
lines.append("\n### Active Files:")
|
|
156
|
+
for f in self.active_files[:5]:
|
|
157
|
+
lines.append(f"- {f}")
|
|
158
|
+
|
|
159
|
+
if self.risk_flags:
|
|
160
|
+
lines.append("\n### Risks:")
|
|
161
|
+
for risk in self.risk_flags:
|
|
162
|
+
lines.append(f"- {risk}")
|
|
163
|
+
|
|
164
|
+
# Test summary
|
|
165
|
+
if self.test_status:
|
|
166
|
+
passing = sum(1 for v in self.test_status.values() if v)
|
|
167
|
+
total = len(self.test_status)
|
|
168
|
+
lines.append(f"\n### Tests: {passing}/{total} passing")
|
|
169
|
+
|
|
170
|
+
return "\n".join(lines)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@dataclass
|
|
174
|
+
class SessionContext:
|
|
175
|
+
"""
|
|
176
|
+
Full context for starting/resuming a session.
|
|
177
|
+
|
|
178
|
+
Provides everything an agent needs to orient itself at session start.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
project_id: str
|
|
182
|
+
agent: str
|
|
183
|
+
session_id: str
|
|
184
|
+
|
|
185
|
+
# Handoff from previous session
|
|
186
|
+
previous_handoff: Optional[SessionHandoff] = None
|
|
187
|
+
|
|
188
|
+
# Current progress state (from ProgressSummary)
|
|
189
|
+
progress: Optional[Any] = None # Will be ProgressSummary when integrated
|
|
190
|
+
|
|
191
|
+
# Recent outcomes (from ALMA)
|
|
192
|
+
recent_outcomes: List[Any] = field(default_factory=list)
|
|
193
|
+
|
|
194
|
+
# Relevant heuristics (from ALMA)
|
|
195
|
+
relevant_heuristics: List[Any] = field(default_factory=list)
|
|
196
|
+
|
|
197
|
+
# Environment orientation
|
|
198
|
+
codebase_state: Optional[Dict[str, Any]] = None # git status, recent commits
|
|
199
|
+
environment_state: Optional[Dict[str, Any]] = None # running services, etc.
|
|
200
|
+
|
|
201
|
+
# Suggested focus
|
|
202
|
+
suggested_focus: Optional[Any] = None # WorkItem when integrated
|
|
203
|
+
|
|
204
|
+
# Rules of engagement
|
|
205
|
+
rules_of_engagement: List[str] = field(default_factory=list)
|
|
206
|
+
|
|
207
|
+
# Timestamps
|
|
208
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def create(
|
|
212
|
+
cls,
|
|
213
|
+
project_id: str,
|
|
214
|
+
agent: str,
|
|
215
|
+
session_id: Optional[str] = None,
|
|
216
|
+
**kwargs,
|
|
217
|
+
) -> "SessionContext":
|
|
218
|
+
"""Factory method to create new session context."""
|
|
219
|
+
return cls(
|
|
220
|
+
project_id=project_id,
|
|
221
|
+
agent=agent,
|
|
222
|
+
session_id=session_id or str(uuid.uuid4()),
|
|
223
|
+
**kwargs,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def format_orientation(self) -> str:
|
|
227
|
+
"""
|
|
228
|
+
Format context as an orientation briefing.
|
|
229
|
+
|
|
230
|
+
Returns a structured string for quick agent orientation.
|
|
231
|
+
"""
|
|
232
|
+
lines = [
|
|
233
|
+
"# Session Orientation",
|
|
234
|
+
f"Project: {self.project_id}",
|
|
235
|
+
f"Agent: {self.agent}",
|
|
236
|
+
f"Session: {self.session_id}",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
# Previous session summary
|
|
240
|
+
if self.previous_handoff:
|
|
241
|
+
lines.append("\n## Previous Session")
|
|
242
|
+
lines.append(f"Outcome: {self.previous_handoff.last_outcome}")
|
|
243
|
+
lines.append(f"Last Action: {self.previous_handoff.last_action}")
|
|
244
|
+
lines.append(f"Goal: {self.previous_handoff.current_goal}")
|
|
245
|
+
if self.previous_handoff.next_steps:
|
|
246
|
+
lines.append("Next Steps from Last Session:")
|
|
247
|
+
for step in self.previous_handoff.next_steps[:3]:
|
|
248
|
+
lines.append(f" - {step}")
|
|
249
|
+
|
|
250
|
+
# Current progress
|
|
251
|
+
if self.progress:
|
|
252
|
+
lines.append("\n## Progress")
|
|
253
|
+
# Assuming progress has a format_summary method
|
|
254
|
+
if hasattr(self.progress, "format_summary"):
|
|
255
|
+
lines.append(self.progress.format_summary())
|
|
256
|
+
else:
|
|
257
|
+
lines.append(str(self.progress))
|
|
258
|
+
|
|
259
|
+
# Blockers from previous session
|
|
260
|
+
if self.previous_handoff and self.previous_handoff.blockers:
|
|
261
|
+
lines.append("\n## Outstanding Blockers")
|
|
262
|
+
for blocker in self.previous_handoff.blockers:
|
|
263
|
+
lines.append(f"- {blocker}")
|
|
264
|
+
|
|
265
|
+
# Suggested focus
|
|
266
|
+
if self.suggested_focus:
|
|
267
|
+
lines.append("\n## Suggested Focus")
|
|
268
|
+
if hasattr(self.suggested_focus, "title"):
|
|
269
|
+
lines.append(f"Task: {self.suggested_focus.title}")
|
|
270
|
+
else:
|
|
271
|
+
lines.append(str(self.suggested_focus))
|
|
272
|
+
|
|
273
|
+
# Environment state
|
|
274
|
+
if self.codebase_state:
|
|
275
|
+
lines.append("\n## Codebase State")
|
|
276
|
+
if "branch" in self.codebase_state:
|
|
277
|
+
lines.append(f"Branch: {self.codebase_state['branch']}")
|
|
278
|
+
if "uncommitted" in self.codebase_state:
|
|
279
|
+
lines.append(f"Uncommitted Changes: {self.codebase_state['uncommitted']}")
|
|
280
|
+
|
|
281
|
+
# Rules
|
|
282
|
+
if self.rules_of_engagement:
|
|
283
|
+
lines.append("\n## Rules of Engagement")
|
|
284
|
+
for rule in self.rules_of_engagement:
|
|
285
|
+
lines.append(f"- {rule}")
|
|
286
|
+
|
|
287
|
+
return "\n".join(lines)
|
alma/storage/azure_cosmos.py
CHANGED
|
@@ -42,6 +42,12 @@ try:
|
|
|
42
42
|
AZURE_COSMOS_AVAILABLE = True
|
|
43
43
|
except ImportError:
|
|
44
44
|
AZURE_COSMOS_AVAILABLE = False
|
|
45
|
+
# Define placeholders for type hints when SDK not available
|
|
46
|
+
CosmosClient = None # type: ignore
|
|
47
|
+
PartitionKey = None # type: ignore
|
|
48
|
+
exceptions = None # type: ignore
|
|
49
|
+
ContainerProxy = Any # type: ignore
|
|
50
|
+
DatabaseProxy = Any # type: ignore
|
|
45
51
|
logger.warning(
|
|
46
52
|
"azure-cosmos package not installed. "
|
|
47
53
|
"Install with: pip install azure-cosmos"
|
alma/storage/sqlite_local.py
CHANGED
|
@@ -910,3 +910,104 @@ class SQLiteStorage(StorageBackend):
|
|
|
910
910
|
or datetime.now(timezone.utc),
|
|
911
911
|
metadata=json.loads(row["metadata"]) if row["metadata"] else {},
|
|
912
912
|
)
|
|
913
|
+
|
|
914
|
+
# ===== Additional abstract method implementations =====
|
|
915
|
+
|
|
916
|
+
def update_heuristic_confidence(
|
|
917
|
+
self,
|
|
918
|
+
heuristic_id: str,
|
|
919
|
+
new_confidence: float,
|
|
920
|
+
) -> bool:
|
|
921
|
+
"""Update confidence score for a heuristic."""
|
|
922
|
+
with self._get_connection() as conn:
|
|
923
|
+
cursor = conn.execute(
|
|
924
|
+
"UPDATE heuristics SET confidence = ? WHERE id = ?",
|
|
925
|
+
(new_confidence, heuristic_id),
|
|
926
|
+
)
|
|
927
|
+
return cursor.rowcount > 0
|
|
928
|
+
|
|
929
|
+
def update_knowledge_confidence(
|
|
930
|
+
self,
|
|
931
|
+
knowledge_id: str,
|
|
932
|
+
new_confidence: float,
|
|
933
|
+
) -> bool:
|
|
934
|
+
"""Update confidence score for domain knowledge."""
|
|
935
|
+
with self._get_connection() as conn:
|
|
936
|
+
cursor = conn.execute(
|
|
937
|
+
"UPDATE domain_knowledge SET confidence = ? WHERE id = ?",
|
|
938
|
+
(new_confidence, knowledge_id),
|
|
939
|
+
)
|
|
940
|
+
return cursor.rowcount > 0
|
|
941
|
+
|
|
942
|
+
def delete_heuristic(self, heuristic_id: str) -> bool:
|
|
943
|
+
"""Delete a heuristic by ID."""
|
|
944
|
+
with self._get_connection() as conn:
|
|
945
|
+
# Also remove from embedding index
|
|
946
|
+
conn.execute(
|
|
947
|
+
"DELETE FROM embeddings WHERE memory_type = 'heuristic' AND memory_id = ?",
|
|
948
|
+
(heuristic_id,),
|
|
949
|
+
)
|
|
950
|
+
cursor = conn.execute(
|
|
951
|
+
"DELETE FROM heuristics WHERE id = ?",
|
|
952
|
+
(heuristic_id,),
|
|
953
|
+
)
|
|
954
|
+
if cursor.rowcount > 0:
|
|
955
|
+
# Rebuild index if we had one
|
|
956
|
+
if "heuristic" in self._indices:
|
|
957
|
+
self._load_faiss_indices()
|
|
958
|
+
return True
|
|
959
|
+
return False
|
|
960
|
+
|
|
961
|
+
def delete_outcome(self, outcome_id: str) -> bool:
|
|
962
|
+
"""Delete an outcome by ID."""
|
|
963
|
+
with self._get_connection() as conn:
|
|
964
|
+
# Also remove from embedding index
|
|
965
|
+
conn.execute(
|
|
966
|
+
"DELETE FROM embeddings WHERE memory_type = 'outcome' AND memory_id = ?",
|
|
967
|
+
(outcome_id,),
|
|
968
|
+
)
|
|
969
|
+
cursor = conn.execute(
|
|
970
|
+
"DELETE FROM outcomes WHERE id = ?",
|
|
971
|
+
(outcome_id,),
|
|
972
|
+
)
|
|
973
|
+
if cursor.rowcount > 0:
|
|
974
|
+
if "outcome" in self._indices:
|
|
975
|
+
self._load_faiss_indices()
|
|
976
|
+
return True
|
|
977
|
+
return False
|
|
978
|
+
|
|
979
|
+
def delete_domain_knowledge(self, knowledge_id: str) -> bool:
|
|
980
|
+
"""Delete domain knowledge by ID."""
|
|
981
|
+
with self._get_connection() as conn:
|
|
982
|
+
# Also remove from embedding index
|
|
983
|
+
conn.execute(
|
|
984
|
+
"DELETE FROM embeddings WHERE memory_type = 'domain_knowledge' AND memory_id = ?",
|
|
985
|
+
(knowledge_id,),
|
|
986
|
+
)
|
|
987
|
+
cursor = conn.execute(
|
|
988
|
+
"DELETE FROM domain_knowledge WHERE id = ?",
|
|
989
|
+
(knowledge_id,),
|
|
990
|
+
)
|
|
991
|
+
if cursor.rowcount > 0:
|
|
992
|
+
if "domain_knowledge" in self._indices:
|
|
993
|
+
self._load_faiss_indices()
|
|
994
|
+
return True
|
|
995
|
+
return False
|
|
996
|
+
|
|
997
|
+
def delete_anti_pattern(self, anti_pattern_id: str) -> bool:
|
|
998
|
+
"""Delete an anti-pattern by ID."""
|
|
999
|
+
with self._get_connection() as conn:
|
|
1000
|
+
# Also remove from embedding index
|
|
1001
|
+
conn.execute(
|
|
1002
|
+
"DELETE FROM embeddings WHERE memory_type = 'anti_pattern' AND memory_id = ?",
|
|
1003
|
+
(anti_pattern_id,),
|
|
1004
|
+
)
|
|
1005
|
+
cursor = conn.execute(
|
|
1006
|
+
"DELETE FROM anti_patterns WHERE id = ?",
|
|
1007
|
+
(anti_pattern_id,),
|
|
1008
|
+
)
|
|
1009
|
+
if cursor.rowcount > 0:
|
|
1010
|
+
if "anti_pattern" in self._indices:
|
|
1011
|
+
self._load_faiss_indices()
|
|
1012
|
+
return True
|
|
1013
|
+
return False
|