alma-memory 0.5.0__py3-none-any.whl → 0.7.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 +296 -194
- alma/compression/__init__.py +33 -0
- alma/compression/pipeline.py +980 -0
- alma/confidence/__init__.py +47 -47
- alma/confidence/engine.py +540 -540
- alma/confidence/types.py +351 -351
- alma/config/loader.py +157 -157
- alma/consolidation/__init__.py +23 -23
- alma/consolidation/engine.py +678 -678
- alma/consolidation/prompts.py +84 -84
- alma/core.py +1189 -322
- alma/domains/__init__.py +30 -30
- alma/domains/factory.py +359 -359
- alma/domains/schemas.py +448 -448
- alma/domains/types.py +272 -272
- alma/events/__init__.py +75 -75
- alma/events/emitter.py +285 -284
- alma/events/storage_mixin.py +246 -246
- alma/events/types.py +126 -126
- alma/events/webhook.py +425 -425
- alma/exceptions.py +49 -49
- alma/extraction/__init__.py +31 -31
- alma/extraction/auto_learner.py +265 -264
- alma/extraction/extractor.py +420 -420
- alma/graph/__init__.py +106 -81
- alma/graph/backends/__init__.py +32 -18
- alma/graph/backends/kuzu.py +624 -0
- alma/graph/backends/memgraph.py +432 -0
- alma/graph/backends/memory.py +236 -236
- alma/graph/backends/neo4j.py +417 -417
- alma/graph/base.py +159 -159
- alma/graph/extraction.py +198 -198
- alma/graph/store.py +860 -860
- alma/harness/__init__.py +35 -35
- alma/harness/base.py +386 -386
- alma/harness/domains.py +705 -705
- alma/initializer/__init__.py +37 -37
- alma/initializer/initializer.py +418 -418
- alma/initializer/types.py +250 -250
- alma/integration/__init__.py +62 -62
- alma/integration/claude_agents.py +444 -432
- alma/integration/helena.py +423 -423
- alma/integration/victor.py +471 -471
- alma/learning/__init__.py +101 -86
- alma/learning/decay.py +878 -0
- alma/learning/forgetting.py +1446 -1446
- alma/learning/heuristic_extractor.py +390 -390
- alma/learning/protocols.py +374 -374
- alma/learning/validation.py +346 -346
- alma/mcp/__init__.py +123 -45
- alma/mcp/__main__.py +156 -156
- alma/mcp/resources.py +122 -122
- alma/mcp/server.py +955 -591
- alma/mcp/tools.py +3254 -511
- alma/observability/__init__.py +91 -0
- alma/observability/config.py +302 -0
- alma/observability/guidelines.py +170 -0
- alma/observability/logging.py +424 -0
- alma/observability/metrics.py +583 -0
- alma/observability/tracing.py +440 -0
- alma/progress/__init__.py +21 -21
- alma/progress/tracker.py +607 -607
- alma/progress/types.py +250 -250
- alma/retrieval/__init__.py +134 -53
- alma/retrieval/budget.py +525 -0
- alma/retrieval/cache.py +1304 -1061
- alma/retrieval/embeddings.py +202 -202
- alma/retrieval/engine.py +850 -366
- alma/retrieval/modes.py +365 -0
- alma/retrieval/progressive.py +560 -0
- alma/retrieval/scoring.py +344 -344
- alma/retrieval/trust_scoring.py +637 -0
- alma/retrieval/verification.py +797 -0
- alma/session/__init__.py +19 -19
- alma/session/manager.py +442 -399
- alma/session/types.py +288 -288
- alma/storage/__init__.py +101 -61
- alma/storage/archive.py +233 -0
- alma/storage/azure_cosmos.py +1259 -1048
- alma/storage/base.py +1083 -525
- alma/storage/chroma.py +1443 -1443
- alma/storage/constants.py +103 -0
- alma/storage/file_based.py +614 -619
- alma/storage/migrations/__init__.py +21 -0
- alma/storage/migrations/base.py +321 -0
- alma/storage/migrations/runner.py +323 -0
- alma/storage/migrations/version_stores.py +337 -0
- alma/storage/migrations/versions/__init__.py +11 -0
- alma/storage/migrations/versions/v1_0_0.py +373 -0
- alma/storage/migrations/versions/v1_1_0_workflow_context.py +551 -0
- alma/storage/pinecone.py +1080 -1080
- alma/storage/postgresql.py +1948 -1452
- alma/storage/qdrant.py +1306 -1306
- alma/storage/sqlite_local.py +3041 -1358
- alma/testing/__init__.py +46 -0
- alma/testing/factories.py +301 -0
- alma/testing/mocks.py +389 -0
- alma/types.py +292 -264
- alma/utils/__init__.py +19 -0
- alma/utils/tokenizer.py +521 -0
- alma/workflow/__init__.py +83 -0
- alma/workflow/artifacts.py +170 -0
- alma/workflow/checkpoint.py +311 -0
- alma/workflow/context.py +228 -0
- alma/workflow/outcomes.py +189 -0
- alma/workflow/reducers.py +393 -0
- {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/METADATA +244 -72
- alma_memory-0.7.0.dist-info/RECORD +112 -0
- alma_memory-0.5.0.dist-info/RECORD +0 -76
- {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
- {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
alma/session/types.py
CHANGED
|
@@ -1,288 +1,288 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Session Management Types.
|
|
3
|
-
|
|
4
|
-
Data models for session continuity and handoffs.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import uuid
|
|
8
|
-
from dataclasses import dataclass, field
|
|
9
|
-
from datetime import datetime, timezone
|
|
10
|
-
from typing import Any, Dict, List, Literal, Optional
|
|
11
|
-
|
|
12
|
-
SessionOutcome = Literal["success", "failure", "interrupted", "unknown"]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class SessionHandoff:
|
|
17
|
-
"""
|
|
18
|
-
Compressed context for session continuity.
|
|
19
|
-
|
|
20
|
-
Captures the essential state at session end so the next session
|
|
21
|
-
can quickly resume without full context reconstruction.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
id: str
|
|
25
|
-
project_id: str
|
|
26
|
-
agent: str
|
|
27
|
-
session_id: str
|
|
28
|
-
|
|
29
|
-
# Where we left off
|
|
30
|
-
last_action: str
|
|
31
|
-
last_outcome: SessionOutcome
|
|
32
|
-
current_goal: str
|
|
33
|
-
|
|
34
|
-
# Quick context (not full history)
|
|
35
|
-
key_decisions: List[str] = field(default_factory=list) # Max 10 most important
|
|
36
|
-
active_files: List[str] = field(default_factory=list) # Files being worked on
|
|
37
|
-
blockers: List[str] = field(default_factory=list) # Current blockers
|
|
38
|
-
next_steps: List[str] = field(default_factory=list) # Planned next actions
|
|
39
|
-
|
|
40
|
-
# Test/validation state
|
|
41
|
-
test_status: Dict[str, bool] = field(default_factory=dict) # test_name -> passing
|
|
42
|
-
|
|
43
|
-
# Confidence signals
|
|
44
|
-
confidence_level: float = 0.5 # 0-1, how well is this going
|
|
45
|
-
risk_flags: List[str] = field(default_factory=list) # Concerns noted
|
|
46
|
-
|
|
47
|
-
# Timing
|
|
48
|
-
session_start: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
49
|
-
session_end: Optional[datetime] = None
|
|
50
|
-
duration_ms: int = 0
|
|
51
|
-
|
|
52
|
-
# For semantic retrieval
|
|
53
|
-
embedding: Optional[List[float]] = None
|
|
54
|
-
|
|
55
|
-
# Timestamps
|
|
56
|
-
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
57
|
-
|
|
58
|
-
# Extensible metadata
|
|
59
|
-
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
60
|
-
|
|
61
|
-
@classmethod
|
|
62
|
-
def create(
|
|
63
|
-
cls,
|
|
64
|
-
project_id: str,
|
|
65
|
-
agent: str,
|
|
66
|
-
session_id: str,
|
|
67
|
-
last_action: str,
|
|
68
|
-
current_goal: str,
|
|
69
|
-
last_outcome: SessionOutcome = "unknown",
|
|
70
|
-
session_start: Optional[datetime] = None,
|
|
71
|
-
**kwargs,
|
|
72
|
-
) -> "SessionHandoff":
|
|
73
|
-
"""Factory method to create a new session handoff."""
|
|
74
|
-
return cls(
|
|
75
|
-
id=str(uuid.uuid4()),
|
|
76
|
-
project_id=project_id,
|
|
77
|
-
agent=agent,
|
|
78
|
-
session_id=session_id,
|
|
79
|
-
last_action=last_action,
|
|
80
|
-
last_outcome=last_outcome,
|
|
81
|
-
current_goal=current_goal,
|
|
82
|
-
session_start=session_start or datetime.now(timezone.utc),
|
|
83
|
-
**kwargs,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
def finalize(
|
|
87
|
-
self,
|
|
88
|
-
last_action: str,
|
|
89
|
-
last_outcome: SessionOutcome,
|
|
90
|
-
next_steps: Optional[List[str]] = None,
|
|
91
|
-
) -> None:
|
|
92
|
-
"""Finalize the handoff at session end."""
|
|
93
|
-
self.last_action = last_action
|
|
94
|
-
self.last_outcome = last_outcome
|
|
95
|
-
self.session_end = datetime.now(timezone.utc)
|
|
96
|
-
if self.session_start:
|
|
97
|
-
self.duration_ms = int(
|
|
98
|
-
(self.session_end - self.session_start).total_seconds() * 1000
|
|
99
|
-
)
|
|
100
|
-
if next_steps:
|
|
101
|
-
self.next_steps = next_steps[:10] # Cap at 10
|
|
102
|
-
|
|
103
|
-
def add_decision(self, decision: str) -> None:
|
|
104
|
-
"""Record a key decision (max 10)."""
|
|
105
|
-
if len(self.key_decisions) >= 10:
|
|
106
|
-
self.key_decisions.pop(0) # Remove oldest
|
|
107
|
-
self.key_decisions.append(decision)
|
|
108
|
-
|
|
109
|
-
def add_blocker(self, blocker: str) -> None:
|
|
110
|
-
"""Record a blocker."""
|
|
111
|
-
if blocker not in self.blockers:
|
|
112
|
-
self.blockers.append(blocker)
|
|
113
|
-
|
|
114
|
-
def remove_blocker(self, blocker: str) -> None:
|
|
115
|
-
"""Remove a resolved blocker."""
|
|
116
|
-
if blocker in self.blockers:
|
|
117
|
-
self.blockers.remove(blocker)
|
|
118
|
-
|
|
119
|
-
def set_test_status(self, test_name: str, passing: bool) -> None:
|
|
120
|
-
"""Update test status."""
|
|
121
|
-
self.test_status[test_name] = passing
|
|
122
|
-
|
|
123
|
-
def format_quick_reload(self) -> str:
|
|
124
|
-
"""
|
|
125
|
-
Format handoff as a quick reload string.
|
|
126
|
-
|
|
127
|
-
Returns a compact string that can be parsed by the next session
|
|
128
|
-
for rapid context restoration.
|
|
129
|
-
"""
|
|
130
|
-
lines = [
|
|
131
|
-
f"## Session Handoff: {self.session_id}",
|
|
132
|
-
f"Agent: {self.agent}",
|
|
133
|
-
f"Goal: {self.current_goal}",
|
|
134
|
-
f"Last Action: {self.last_action} ({self.last_outcome})",
|
|
135
|
-
f"Confidence: {int(self.confidence_level * 100)}%",
|
|
136
|
-
]
|
|
137
|
-
|
|
138
|
-
if self.next_steps:
|
|
139
|
-
lines.append("\n### Next Steps:")
|
|
140
|
-
for step in self.next_steps[:5]:
|
|
141
|
-
lines.append(f"- {step}")
|
|
142
|
-
|
|
143
|
-
if self.blockers:
|
|
144
|
-
lines.append("\n### Blockers:")
|
|
145
|
-
for blocker in self.blockers:
|
|
146
|
-
lines.append(f"- {blocker}")
|
|
147
|
-
|
|
148
|
-
if self.key_decisions:
|
|
149
|
-
lines.append("\n### Key Decisions:")
|
|
150
|
-
for decision in self.key_decisions[-5:]: # Last 5 decisions
|
|
151
|
-
lines.append(f"- {decision}")
|
|
152
|
-
|
|
153
|
-
if self.active_files:
|
|
154
|
-
lines.append("\n### Active Files:")
|
|
155
|
-
for f in self.active_files[:5]:
|
|
156
|
-
lines.append(f"- {f}")
|
|
157
|
-
|
|
158
|
-
if self.risk_flags:
|
|
159
|
-
lines.append("\n### Risks:")
|
|
160
|
-
for risk in self.risk_flags:
|
|
161
|
-
lines.append(f"- {risk}")
|
|
162
|
-
|
|
163
|
-
# Test summary
|
|
164
|
-
if self.test_status:
|
|
165
|
-
passing = sum(1 for v in self.test_status.values() if v)
|
|
166
|
-
total = len(self.test_status)
|
|
167
|
-
lines.append(f"\n### Tests: {passing}/{total} passing")
|
|
168
|
-
|
|
169
|
-
return "\n".join(lines)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@dataclass
|
|
173
|
-
class SessionContext:
|
|
174
|
-
"""
|
|
175
|
-
Full context for starting/resuming a session.
|
|
176
|
-
|
|
177
|
-
Provides everything an agent needs to orient itself at session start.
|
|
178
|
-
"""
|
|
179
|
-
|
|
180
|
-
project_id: str
|
|
181
|
-
agent: str
|
|
182
|
-
session_id: str
|
|
183
|
-
|
|
184
|
-
# Handoff from previous session
|
|
185
|
-
previous_handoff: Optional[SessionHandoff] = None
|
|
186
|
-
|
|
187
|
-
# Current progress state (from ProgressSummary)
|
|
188
|
-
progress: Optional[Any] = None # Will be ProgressSummary when integrated
|
|
189
|
-
|
|
190
|
-
# Recent outcomes (from ALMA)
|
|
191
|
-
recent_outcomes: List[Any] = field(default_factory=list)
|
|
192
|
-
|
|
193
|
-
# Relevant heuristics (from ALMA)
|
|
194
|
-
relevant_heuristics: List[Any] = field(default_factory=list)
|
|
195
|
-
|
|
196
|
-
# Environment orientation
|
|
197
|
-
codebase_state: Optional[Dict[str, Any]] = None # git status, recent commits
|
|
198
|
-
environment_state: Optional[Dict[str, Any]] = None # running services, etc.
|
|
199
|
-
|
|
200
|
-
# Suggested focus
|
|
201
|
-
suggested_focus: Optional[Any] = None # WorkItem when integrated
|
|
202
|
-
|
|
203
|
-
# Rules of engagement
|
|
204
|
-
rules_of_engagement: List[str] = field(default_factory=list)
|
|
205
|
-
|
|
206
|
-
# Timestamps
|
|
207
|
-
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
208
|
-
|
|
209
|
-
@classmethod
|
|
210
|
-
def create(
|
|
211
|
-
cls,
|
|
212
|
-
project_id: str,
|
|
213
|
-
agent: str,
|
|
214
|
-
session_id: Optional[str] = None,
|
|
215
|
-
**kwargs,
|
|
216
|
-
) -> "SessionContext":
|
|
217
|
-
"""Factory method to create new session context."""
|
|
218
|
-
return cls(
|
|
219
|
-
project_id=project_id,
|
|
220
|
-
agent=agent,
|
|
221
|
-
session_id=session_id or str(uuid.uuid4()),
|
|
222
|
-
**kwargs,
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
def format_orientation(self) -> str:
|
|
226
|
-
"""
|
|
227
|
-
Format context as an orientation briefing.
|
|
228
|
-
|
|
229
|
-
Returns a structured string for quick agent orientation.
|
|
230
|
-
"""
|
|
231
|
-
lines = [
|
|
232
|
-
"# Session Orientation",
|
|
233
|
-
f"Project: {self.project_id}",
|
|
234
|
-
f"Agent: {self.agent}",
|
|
235
|
-
f"Session: {self.session_id}",
|
|
236
|
-
]
|
|
237
|
-
|
|
238
|
-
# Previous session summary
|
|
239
|
-
if self.previous_handoff:
|
|
240
|
-
lines.append("\n## Previous Session")
|
|
241
|
-
lines.append(f"Outcome: {self.previous_handoff.last_outcome}")
|
|
242
|
-
lines.append(f"Last Action: {self.previous_handoff.last_action}")
|
|
243
|
-
lines.append(f"Goal: {self.previous_handoff.current_goal}")
|
|
244
|
-
if self.previous_handoff.next_steps:
|
|
245
|
-
lines.append("Next Steps from Last Session:")
|
|
246
|
-
for step in self.previous_handoff.next_steps[:3]:
|
|
247
|
-
lines.append(f" - {step}")
|
|
248
|
-
|
|
249
|
-
# Current progress
|
|
250
|
-
if self.progress:
|
|
251
|
-
lines.append("\n## Progress")
|
|
252
|
-
# Assuming progress has a format_summary method
|
|
253
|
-
if hasattr(self.progress, "format_summary"):
|
|
254
|
-
lines.append(self.progress.format_summary())
|
|
255
|
-
else:
|
|
256
|
-
lines.append(str(self.progress))
|
|
257
|
-
|
|
258
|
-
# Blockers from previous session
|
|
259
|
-
if self.previous_handoff and self.previous_handoff.blockers:
|
|
260
|
-
lines.append("\n## Outstanding Blockers")
|
|
261
|
-
for blocker in self.previous_handoff.blockers:
|
|
262
|
-
lines.append(f"- {blocker}")
|
|
263
|
-
|
|
264
|
-
# Suggested focus
|
|
265
|
-
if self.suggested_focus:
|
|
266
|
-
lines.append("\n## Suggested Focus")
|
|
267
|
-
if hasattr(self.suggested_focus, "title"):
|
|
268
|
-
lines.append(f"Task: {self.suggested_focus.title}")
|
|
269
|
-
else:
|
|
270
|
-
lines.append(str(self.suggested_focus))
|
|
271
|
-
|
|
272
|
-
# Environment state
|
|
273
|
-
if self.codebase_state:
|
|
274
|
-
lines.append("\n## Codebase State")
|
|
275
|
-
if "branch" in self.codebase_state:
|
|
276
|
-
lines.append(f"Branch: {self.codebase_state['branch']}")
|
|
277
|
-
if "uncommitted" in self.codebase_state:
|
|
278
|
-
lines.append(
|
|
279
|
-
f"Uncommitted Changes: {self.codebase_state['uncommitted']}"
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
# Rules
|
|
283
|
-
if self.rules_of_engagement:
|
|
284
|
-
lines.append("\n## Rules of Engagement")
|
|
285
|
-
for rule in self.rules_of_engagement:
|
|
286
|
-
lines.append(f"- {rule}")
|
|
287
|
-
|
|
288
|
-
return "\n".join(lines)
|
|
1
|
+
"""
|
|
2
|
+
Session Management Types.
|
|
3
|
+
|
|
4
|
+
Data models for session continuity and handoffs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
11
|
+
|
|
12
|
+
SessionOutcome = Literal["success", "failure", "interrupted", "unknown"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SessionHandoff:
|
|
17
|
+
"""
|
|
18
|
+
Compressed context for session continuity.
|
|
19
|
+
|
|
20
|
+
Captures the essential state at session end so the next session
|
|
21
|
+
can quickly resume without full context reconstruction.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
id: str
|
|
25
|
+
project_id: str
|
|
26
|
+
agent: str
|
|
27
|
+
session_id: str
|
|
28
|
+
|
|
29
|
+
# Where we left off
|
|
30
|
+
last_action: str
|
|
31
|
+
last_outcome: SessionOutcome
|
|
32
|
+
current_goal: str
|
|
33
|
+
|
|
34
|
+
# Quick context (not full history)
|
|
35
|
+
key_decisions: List[str] = field(default_factory=list) # Max 10 most important
|
|
36
|
+
active_files: List[str] = field(default_factory=list) # Files being worked on
|
|
37
|
+
blockers: List[str] = field(default_factory=list) # Current blockers
|
|
38
|
+
next_steps: List[str] = field(default_factory=list) # Planned next actions
|
|
39
|
+
|
|
40
|
+
# Test/validation state
|
|
41
|
+
test_status: Dict[str, bool] = field(default_factory=dict) # test_name -> passing
|
|
42
|
+
|
|
43
|
+
# Confidence signals
|
|
44
|
+
confidence_level: float = 0.5 # 0-1, how well is this going
|
|
45
|
+
risk_flags: List[str] = field(default_factory=list) # Concerns noted
|
|
46
|
+
|
|
47
|
+
# Timing
|
|
48
|
+
session_start: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
49
|
+
session_end: Optional[datetime] = None
|
|
50
|
+
duration_ms: int = 0
|
|
51
|
+
|
|
52
|
+
# For semantic retrieval
|
|
53
|
+
embedding: Optional[List[float]] = None
|
|
54
|
+
|
|
55
|
+
# Timestamps
|
|
56
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
57
|
+
|
|
58
|
+
# Extensible metadata
|
|
59
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def create(
|
|
63
|
+
cls,
|
|
64
|
+
project_id: str,
|
|
65
|
+
agent: str,
|
|
66
|
+
session_id: str,
|
|
67
|
+
last_action: str,
|
|
68
|
+
current_goal: str,
|
|
69
|
+
last_outcome: SessionOutcome = "unknown",
|
|
70
|
+
session_start: Optional[datetime] = None,
|
|
71
|
+
**kwargs,
|
|
72
|
+
) -> "SessionHandoff":
|
|
73
|
+
"""Factory method to create a new session handoff."""
|
|
74
|
+
return cls(
|
|
75
|
+
id=str(uuid.uuid4()),
|
|
76
|
+
project_id=project_id,
|
|
77
|
+
agent=agent,
|
|
78
|
+
session_id=session_id,
|
|
79
|
+
last_action=last_action,
|
|
80
|
+
last_outcome=last_outcome,
|
|
81
|
+
current_goal=current_goal,
|
|
82
|
+
session_start=session_start or datetime.now(timezone.utc),
|
|
83
|
+
**kwargs,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def finalize(
|
|
87
|
+
self,
|
|
88
|
+
last_action: str,
|
|
89
|
+
last_outcome: SessionOutcome,
|
|
90
|
+
next_steps: Optional[List[str]] = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Finalize the handoff at session end."""
|
|
93
|
+
self.last_action = last_action
|
|
94
|
+
self.last_outcome = last_outcome
|
|
95
|
+
self.session_end = datetime.now(timezone.utc)
|
|
96
|
+
if self.session_start:
|
|
97
|
+
self.duration_ms = int(
|
|
98
|
+
(self.session_end - self.session_start).total_seconds() * 1000
|
|
99
|
+
)
|
|
100
|
+
if next_steps:
|
|
101
|
+
self.next_steps = next_steps[:10] # Cap at 10
|
|
102
|
+
|
|
103
|
+
def add_decision(self, decision: str) -> None:
|
|
104
|
+
"""Record a key decision (max 10)."""
|
|
105
|
+
if len(self.key_decisions) >= 10:
|
|
106
|
+
self.key_decisions.pop(0) # Remove oldest
|
|
107
|
+
self.key_decisions.append(decision)
|
|
108
|
+
|
|
109
|
+
def add_blocker(self, blocker: str) -> None:
|
|
110
|
+
"""Record a blocker."""
|
|
111
|
+
if blocker not in self.blockers:
|
|
112
|
+
self.blockers.append(blocker)
|
|
113
|
+
|
|
114
|
+
def remove_blocker(self, blocker: str) -> None:
|
|
115
|
+
"""Remove a resolved blocker."""
|
|
116
|
+
if blocker in self.blockers:
|
|
117
|
+
self.blockers.remove(blocker)
|
|
118
|
+
|
|
119
|
+
def set_test_status(self, test_name: str, passing: bool) -> None:
|
|
120
|
+
"""Update test status."""
|
|
121
|
+
self.test_status[test_name] = passing
|
|
122
|
+
|
|
123
|
+
def format_quick_reload(self) -> str:
|
|
124
|
+
"""
|
|
125
|
+
Format handoff as a quick reload string.
|
|
126
|
+
|
|
127
|
+
Returns a compact string that can be parsed by the next session
|
|
128
|
+
for rapid context restoration.
|
|
129
|
+
"""
|
|
130
|
+
lines = [
|
|
131
|
+
f"## Session Handoff: {self.session_id}",
|
|
132
|
+
f"Agent: {self.agent}",
|
|
133
|
+
f"Goal: {self.current_goal}",
|
|
134
|
+
f"Last Action: {self.last_action} ({self.last_outcome})",
|
|
135
|
+
f"Confidence: {int(self.confidence_level * 100)}%",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
if self.next_steps:
|
|
139
|
+
lines.append("\n### Next Steps:")
|
|
140
|
+
for step in self.next_steps[:5]:
|
|
141
|
+
lines.append(f"- {step}")
|
|
142
|
+
|
|
143
|
+
if self.blockers:
|
|
144
|
+
lines.append("\n### Blockers:")
|
|
145
|
+
for blocker in self.blockers:
|
|
146
|
+
lines.append(f"- {blocker}")
|
|
147
|
+
|
|
148
|
+
if self.key_decisions:
|
|
149
|
+
lines.append("\n### Key Decisions:")
|
|
150
|
+
for decision in self.key_decisions[-5:]: # Last 5 decisions
|
|
151
|
+
lines.append(f"- {decision}")
|
|
152
|
+
|
|
153
|
+
if self.active_files:
|
|
154
|
+
lines.append("\n### Active Files:")
|
|
155
|
+
for f in self.active_files[:5]:
|
|
156
|
+
lines.append(f"- {f}")
|
|
157
|
+
|
|
158
|
+
if self.risk_flags:
|
|
159
|
+
lines.append("\n### Risks:")
|
|
160
|
+
for risk in self.risk_flags:
|
|
161
|
+
lines.append(f"- {risk}")
|
|
162
|
+
|
|
163
|
+
# Test summary
|
|
164
|
+
if self.test_status:
|
|
165
|
+
passing = sum(1 for v in self.test_status.values() if v)
|
|
166
|
+
total = len(self.test_status)
|
|
167
|
+
lines.append(f"\n### Tests: {passing}/{total} passing")
|
|
168
|
+
|
|
169
|
+
return "\n".join(lines)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass
|
|
173
|
+
class SessionContext:
|
|
174
|
+
"""
|
|
175
|
+
Full context for starting/resuming a session.
|
|
176
|
+
|
|
177
|
+
Provides everything an agent needs to orient itself at session start.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
project_id: str
|
|
181
|
+
agent: str
|
|
182
|
+
session_id: str
|
|
183
|
+
|
|
184
|
+
# Handoff from previous session
|
|
185
|
+
previous_handoff: Optional[SessionHandoff] = None
|
|
186
|
+
|
|
187
|
+
# Current progress state (from ProgressSummary)
|
|
188
|
+
progress: Optional[Any] = None # Will be ProgressSummary when integrated
|
|
189
|
+
|
|
190
|
+
# Recent outcomes (from ALMA)
|
|
191
|
+
recent_outcomes: List[Any] = field(default_factory=list)
|
|
192
|
+
|
|
193
|
+
# Relevant heuristics (from ALMA)
|
|
194
|
+
relevant_heuristics: List[Any] = field(default_factory=list)
|
|
195
|
+
|
|
196
|
+
# Environment orientation
|
|
197
|
+
codebase_state: Optional[Dict[str, Any]] = None # git status, recent commits
|
|
198
|
+
environment_state: Optional[Dict[str, Any]] = None # running services, etc.
|
|
199
|
+
|
|
200
|
+
# Suggested focus
|
|
201
|
+
suggested_focus: Optional[Any] = None # WorkItem when integrated
|
|
202
|
+
|
|
203
|
+
# Rules of engagement
|
|
204
|
+
rules_of_engagement: List[str] = field(default_factory=list)
|
|
205
|
+
|
|
206
|
+
# Timestamps
|
|
207
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def create(
|
|
211
|
+
cls,
|
|
212
|
+
project_id: str,
|
|
213
|
+
agent: str,
|
|
214
|
+
session_id: Optional[str] = None,
|
|
215
|
+
**kwargs,
|
|
216
|
+
) -> "SessionContext":
|
|
217
|
+
"""Factory method to create new session context."""
|
|
218
|
+
return cls(
|
|
219
|
+
project_id=project_id,
|
|
220
|
+
agent=agent,
|
|
221
|
+
session_id=session_id or str(uuid.uuid4()),
|
|
222
|
+
**kwargs,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def format_orientation(self) -> str:
|
|
226
|
+
"""
|
|
227
|
+
Format context as an orientation briefing.
|
|
228
|
+
|
|
229
|
+
Returns a structured string for quick agent orientation.
|
|
230
|
+
"""
|
|
231
|
+
lines = [
|
|
232
|
+
"# Session Orientation",
|
|
233
|
+
f"Project: {self.project_id}",
|
|
234
|
+
f"Agent: {self.agent}",
|
|
235
|
+
f"Session: {self.session_id}",
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
# Previous session summary
|
|
239
|
+
if self.previous_handoff:
|
|
240
|
+
lines.append("\n## Previous Session")
|
|
241
|
+
lines.append(f"Outcome: {self.previous_handoff.last_outcome}")
|
|
242
|
+
lines.append(f"Last Action: {self.previous_handoff.last_action}")
|
|
243
|
+
lines.append(f"Goal: {self.previous_handoff.current_goal}")
|
|
244
|
+
if self.previous_handoff.next_steps:
|
|
245
|
+
lines.append("Next Steps from Last Session:")
|
|
246
|
+
for step in self.previous_handoff.next_steps[:3]:
|
|
247
|
+
lines.append(f" - {step}")
|
|
248
|
+
|
|
249
|
+
# Current progress
|
|
250
|
+
if self.progress:
|
|
251
|
+
lines.append("\n## Progress")
|
|
252
|
+
# Assuming progress has a format_summary method
|
|
253
|
+
if hasattr(self.progress, "format_summary"):
|
|
254
|
+
lines.append(self.progress.format_summary())
|
|
255
|
+
else:
|
|
256
|
+
lines.append(str(self.progress))
|
|
257
|
+
|
|
258
|
+
# Blockers from previous session
|
|
259
|
+
if self.previous_handoff and self.previous_handoff.blockers:
|
|
260
|
+
lines.append("\n## Outstanding Blockers")
|
|
261
|
+
for blocker in self.previous_handoff.blockers:
|
|
262
|
+
lines.append(f"- {blocker}")
|
|
263
|
+
|
|
264
|
+
# Suggested focus
|
|
265
|
+
if self.suggested_focus:
|
|
266
|
+
lines.append("\n## Suggested Focus")
|
|
267
|
+
if hasattr(self.suggested_focus, "title"):
|
|
268
|
+
lines.append(f"Task: {self.suggested_focus.title}")
|
|
269
|
+
else:
|
|
270
|
+
lines.append(str(self.suggested_focus))
|
|
271
|
+
|
|
272
|
+
# Environment state
|
|
273
|
+
if self.codebase_state:
|
|
274
|
+
lines.append("\n## Codebase State")
|
|
275
|
+
if "branch" in self.codebase_state:
|
|
276
|
+
lines.append(f"Branch: {self.codebase_state['branch']}")
|
|
277
|
+
if "uncommitted" in self.codebase_state:
|
|
278
|
+
lines.append(
|
|
279
|
+
f"Uncommitted Changes: {self.codebase_state['uncommitted']}"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Rules
|
|
283
|
+
if self.rules_of_engagement:
|
|
284
|
+
lines.append("\n## Rules of Engagement")
|
|
285
|
+
for rule in self.rules_of_engagement:
|
|
286
|
+
lines.append(f"- {rule}")
|
|
287
|
+
|
|
288
|
+
return "\n".join(lines)
|