alma-memory 0.4.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 +88 -44
- alma/confidence/__init__.py +1 -1
- alma/confidence/engine.py +92 -58
- alma/confidence/types.py +34 -14
- 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 +1 -1
- alma/initializer/initializer.py +51 -43
- alma/initializer/types.py +25 -17
- 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 +101 -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.4.0.dist-info → alma_memory-0.5.0.dist-info}/WHEEL +1 -1
- alma_memory-0.4.0.dist-info/METADATA +0 -488
- alma_memory-0.4.0.dist-info/RECORD +0 -52
- {alma_memory-0.4.0.dist-info → alma_memory-0.5.0.dist-info}/top_level.txt +0 -0
alma/harness/__init__.py
CHANGED
|
@@ -9,17 +9,17 @@ A structured framework for creating learning agents across any domain:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from alma.harness.base import (
|
|
12
|
-
Setting,
|
|
13
|
-
Context,
|
|
14
12
|
Agent,
|
|
15
|
-
|
|
13
|
+
Context,
|
|
16
14
|
Harness,
|
|
15
|
+
MemorySchema,
|
|
16
|
+
Setting,
|
|
17
17
|
)
|
|
18
18
|
from alma.harness.domains import (
|
|
19
19
|
CodingDomain,
|
|
20
|
-
ResearchDomain,
|
|
21
20
|
ContentDomain,
|
|
22
21
|
OperationsDomain,
|
|
22
|
+
ResearchDomain,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
__all__ = [
|
alma/harness/base.py
CHANGED
|
@@ -17,23 +17,23 @@ Flow:
|
|
|
17
17
|
Repeat -> Agent appears to "learn" without weight changes
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
from abc import ABC, abstractmethod
|
|
21
20
|
from dataclasses import dataclass, field
|
|
22
21
|
from datetime import datetime, timezone
|
|
23
|
-
from typing import Optional, List, Dict, Any, Callable
|
|
24
22
|
from enum import Enum
|
|
23
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
25
24
|
|
|
26
|
-
from alma.types import
|
|
25
|
+
from alma.types import MemoryScope, MemorySlice
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class ToolType(Enum):
|
|
30
29
|
"""Categories of tools available to agents."""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
|
|
31
|
+
SEARCH = "search" # Web search, semantic search
|
|
32
|
+
DATA_ACCESS = "data_access" # APIs, databases
|
|
33
|
+
EXECUTION = "execution" # Code execution, automation
|
|
34
34
|
COMMUNICATION = "communication" # Email, messaging
|
|
35
|
-
ANALYSIS = "analysis"
|
|
36
|
-
CREATION = "creation"
|
|
35
|
+
ANALYSIS = "analysis" # Data processing, synthesis
|
|
36
|
+
CREATION = "creation" # Content generation, design
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
@dataclass
|
|
@@ -43,6 +43,7 @@ class Tool:
|
|
|
43
43
|
|
|
44
44
|
Tools are the building blocks agents use to accomplish tasks.
|
|
45
45
|
"""
|
|
46
|
+
|
|
46
47
|
name: str
|
|
47
48
|
description: str
|
|
48
49
|
tool_type: ToolType
|
|
@@ -66,6 +67,7 @@ class Setting:
|
|
|
66
67
|
Includes available tools and immutable constraints that don't change
|
|
67
68
|
between runs. The setting defines WHAT the agent CAN do.
|
|
68
69
|
"""
|
|
70
|
+
|
|
69
71
|
name: str
|
|
70
72
|
description: str
|
|
71
73
|
tools: List[Tool] = field(default_factory=list)
|
|
@@ -100,6 +102,7 @@ class Context:
|
|
|
100
102
|
This is injected fresh each time and contains task-specific information.
|
|
101
103
|
The context defines WHAT the agent should do THIS run.
|
|
102
104
|
"""
|
|
105
|
+
|
|
103
106
|
task: str
|
|
104
107
|
user_id: Optional[str] = None
|
|
105
108
|
project_id: Optional[str] = None
|
|
@@ -137,6 +140,7 @@ class MemorySchema:
|
|
|
137
140
|
This defines WHAT gets remembered and HOW, ensuring relevance
|
|
138
141
|
and preventing scope creep. Each domain has its own schema.
|
|
139
142
|
"""
|
|
143
|
+
|
|
140
144
|
domain: str
|
|
141
145
|
description: str
|
|
142
146
|
|
|
@@ -195,6 +199,7 @@ class Agent:
|
|
|
195
199
|
Agents start "dumb" but get smarter via memory injections.
|
|
196
200
|
They use tools to accomplish tasks and log reflections post-run.
|
|
197
201
|
"""
|
|
202
|
+
|
|
198
203
|
name: str
|
|
199
204
|
role: str
|
|
200
205
|
description: str
|
|
@@ -227,6 +232,7 @@ class Agent:
|
|
|
227
232
|
@dataclass
|
|
228
233
|
class RunResult:
|
|
229
234
|
"""Result of a harness run."""
|
|
235
|
+
|
|
230
236
|
success: bool
|
|
231
237
|
output: Any
|
|
232
238
|
reflections: List[str] = field(default_factory=list)
|
|
@@ -319,7 +325,9 @@ class Harness:
|
|
|
319
325
|
agent=self.agent.name,
|
|
320
326
|
task=context.task,
|
|
321
327
|
outcome="success" if result.success else "failure",
|
|
322
|
-
strategy_used=
|
|
328
|
+
strategy_used=(
|
|
329
|
+
", ".join(result.tools_used) if result.tools_used else "direct"
|
|
330
|
+
),
|
|
323
331
|
duration_ms=result.duration_ms,
|
|
324
332
|
error_message=result.error,
|
|
325
333
|
feedback="; ".join(result.reflections) if result.reflections else None,
|
|
@@ -347,6 +355,7 @@ class Harness:
|
|
|
347
355
|
RunResult with output or prompt
|
|
348
356
|
"""
|
|
349
357
|
import time
|
|
358
|
+
|
|
350
359
|
start_time = time.time()
|
|
351
360
|
|
|
352
361
|
# 1. Pre-run: Get relevant memories
|
alma/harness/domains.py
CHANGED
|
@@ -13,23 +13,22 @@ Each domain includes:
|
|
|
13
13
|
- Agent templates
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from typing import List, Dict, Any, Optional
|
|
16
|
+
from typing import Any
|
|
18
17
|
|
|
19
18
|
from alma.harness.base import (
|
|
19
|
+
Agent,
|
|
20
|
+
Harness,
|
|
21
|
+
MemorySchema,
|
|
20
22
|
Setting,
|
|
21
23
|
Tool,
|
|
22
24
|
ToolType,
|
|
23
|
-
Agent,
|
|
24
|
-
MemorySchema,
|
|
25
|
-
Harness,
|
|
26
25
|
)
|
|
27
26
|
|
|
28
|
-
|
|
29
27
|
# =============================================================================
|
|
30
28
|
# CODING DOMAIN
|
|
31
29
|
# =============================================================================
|
|
32
30
|
|
|
31
|
+
|
|
33
32
|
class CodingDomain:
|
|
34
33
|
"""Pre-built configurations for coding/development agents."""
|
|
35
34
|
|
|
@@ -114,13 +113,19 @@ class CodingDomain:
|
|
|
114
113
|
name="playwright",
|
|
115
114
|
description="Browser automation for UI testing",
|
|
116
115
|
tool_type=ToolType.EXECUTION,
|
|
117
|
-
constraints=[
|
|
116
|
+
constraints=[
|
|
117
|
+
"Use explicit waits, not sleep()",
|
|
118
|
+
"Prefer role-based selectors",
|
|
119
|
+
],
|
|
118
120
|
),
|
|
119
121
|
Tool(
|
|
120
122
|
name="api_client",
|
|
121
123
|
description="HTTP client for API testing",
|
|
122
124
|
tool_type=ToolType.DATA_ACCESS,
|
|
123
|
-
constraints=[
|
|
125
|
+
constraints=[
|
|
126
|
+
"Log all requests/responses",
|
|
127
|
+
"Handle timeouts gracefully",
|
|
128
|
+
],
|
|
124
129
|
),
|
|
125
130
|
Tool(
|
|
126
131
|
name="database_query",
|
|
@@ -201,6 +206,7 @@ class CodingDomain:
|
|
|
201
206
|
# RESEARCH DOMAIN
|
|
202
207
|
# =============================================================================
|
|
203
208
|
|
|
209
|
+
|
|
204
210
|
class ResearchDomain:
|
|
205
211
|
"""Pre-built configurations for research and analysis agents."""
|
|
206
212
|
|
|
@@ -268,7 +274,10 @@ class ResearchDomain:
|
|
|
268
274
|
name="synthesis",
|
|
269
275
|
description="Combine multiple sources into insights",
|
|
270
276
|
tool_type=ToolType.ANALYSIS,
|
|
271
|
-
constraints=[
|
|
277
|
+
constraints=[
|
|
278
|
+
"Note conflicting information",
|
|
279
|
+
"Confidence levels required",
|
|
280
|
+
],
|
|
272
281
|
),
|
|
273
282
|
],
|
|
274
283
|
global_constraints=[
|
|
@@ -314,6 +323,7 @@ class ResearchDomain:
|
|
|
314
323
|
# CONTENT DOMAIN
|
|
315
324
|
# =============================================================================
|
|
316
325
|
|
|
326
|
+
|
|
317
327
|
class ContentDomain:
|
|
318
328
|
"""Pre-built configurations for content creation agents."""
|
|
319
329
|
|
|
@@ -490,6 +500,7 @@ class ContentDomain:
|
|
|
490
500
|
# OPERATIONS DOMAIN
|
|
491
501
|
# =============================================================================
|
|
492
502
|
|
|
503
|
+
|
|
493
504
|
class OperationsDomain:
|
|
494
505
|
"""Pre-built configurations for operations and support agents."""
|
|
495
506
|
|
|
@@ -636,6 +647,7 @@ class OperationsDomain:
|
|
|
636
647
|
# FACTORY FUNCTION
|
|
637
648
|
# =============================================================================
|
|
638
649
|
|
|
650
|
+
|
|
639
651
|
def create_harness(
|
|
640
652
|
domain: str,
|
|
641
653
|
agent_type: str,
|
|
@@ -666,7 +678,9 @@ def create_harness(
|
|
|
666
678
|
"victor": CodingDomain.create_victor,
|
|
667
679
|
},
|
|
668
680
|
"research": {
|
|
669
|
-
"researcher": lambda a: ResearchDomain.create_researcher(
|
|
681
|
+
"researcher": lambda a: ResearchDomain.create_researcher(
|
|
682
|
+
a, kwargs.get("focus", "general")
|
|
683
|
+
),
|
|
670
684
|
},
|
|
671
685
|
"content": {
|
|
672
686
|
"copywriter": ContentDomain.create_copywriter,
|
|
@@ -678,7 +692,9 @@ def create_harness(
|
|
|
678
692
|
}
|
|
679
693
|
|
|
680
694
|
if domain not in factories:
|
|
681
|
-
raise ValueError(
|
|
695
|
+
raise ValueError(
|
|
696
|
+
f"Unknown domain: {domain}. Available: {list(factories.keys())}"
|
|
697
|
+
)
|
|
682
698
|
|
|
683
699
|
if agent_type not in factories[domain]:
|
|
684
700
|
raise ValueError(
|
alma/initializer/__init__.py
CHANGED
|
@@ -22,12 +22,12 @@ Usage:
|
|
|
22
22
|
'''
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
from alma.initializer.initializer import SessionInitializer
|
|
25
26
|
from alma.initializer.types import (
|
|
26
27
|
CodebaseOrientation,
|
|
27
28
|
InitializationResult,
|
|
28
29
|
RulesOfEngagement,
|
|
29
30
|
)
|
|
30
|
-
from alma.initializer.initializer import SessionInitializer
|
|
31
31
|
|
|
32
32
|
__all__ = [
|
|
33
33
|
"CodebaseOrientation",
|
alma/initializer/initializer.py
CHANGED
|
@@ -173,7 +173,7 @@ class SessionInitializer:
|
|
|
173
173
|
work_items = []
|
|
174
174
|
|
|
175
175
|
# Simple extraction: look for bullet points and numbered items
|
|
176
|
-
lines = user_prompt.strip().split(
|
|
176
|
+
lines = user_prompt.strip().split("\n")
|
|
177
177
|
|
|
178
178
|
for line in lines:
|
|
179
179
|
line = line.strip()
|
|
@@ -181,25 +181,29 @@ class SessionInitializer:
|
|
|
181
181
|
continue
|
|
182
182
|
|
|
183
183
|
# Match bullet points: -, *, •
|
|
184
|
-
bullet_match = re.match(r
|
|
184
|
+
bullet_match = re.match(r"^[-*•]\s+(.+)$", line)
|
|
185
185
|
if bullet_match:
|
|
186
186
|
title = bullet_match.group(1).strip()
|
|
187
|
-
work_items.append(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
work_items.append(
|
|
188
|
+
WorkItem.create(
|
|
189
|
+
project_id="", # Will be set by caller
|
|
190
|
+
title=title,
|
|
191
|
+
description=title,
|
|
192
|
+
)
|
|
193
|
+
)
|
|
192
194
|
continue
|
|
193
195
|
|
|
194
196
|
# Match numbered items: 1., 2., etc.
|
|
195
|
-
number_match = re.match(r
|
|
197
|
+
number_match = re.match(r"^\d+\.\s+(.+)$", line)
|
|
196
198
|
if number_match:
|
|
197
199
|
title = number_match.group(1).strip()
|
|
198
|
-
work_items.append(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
work_items.append(
|
|
201
|
+
WorkItem.create(
|
|
202
|
+
project_id="",
|
|
203
|
+
title=title,
|
|
204
|
+
description=title,
|
|
205
|
+
)
|
|
206
|
+
)
|
|
203
207
|
continue
|
|
204
208
|
|
|
205
209
|
# If no structured items found, create single item from prompt
|
|
@@ -209,11 +213,13 @@ class SessionInitializer:
|
|
|
209
213
|
if len(user_prompt) > 100:
|
|
210
214
|
title += "..."
|
|
211
215
|
|
|
212
|
-
work_items.append(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
work_items.append(
|
|
217
|
+
WorkItem.create(
|
|
218
|
+
project_id="",
|
|
219
|
+
title=title,
|
|
220
|
+
description=user_prompt,
|
|
221
|
+
)
|
|
222
|
+
)
|
|
217
223
|
|
|
218
224
|
return work_items
|
|
219
225
|
|
|
@@ -261,7 +267,9 @@ class SessionInitializer:
|
|
|
261
267
|
timeout=5,
|
|
262
268
|
)
|
|
263
269
|
if result.returncode == 0:
|
|
264
|
-
orientation.current_branch =
|
|
270
|
+
orientation.current_branch = (
|
|
271
|
+
result.stdout.strip() or "HEAD detached"
|
|
272
|
+
)
|
|
265
273
|
|
|
266
274
|
# Check for uncommitted changes
|
|
267
275
|
result = subprocess.run(
|
|
@@ -283,7 +291,7 @@ class SessionInitializer:
|
|
|
283
291
|
timeout=5,
|
|
284
292
|
)
|
|
285
293
|
if result.returncode == 0:
|
|
286
|
-
commits = result.stdout.strip().split(
|
|
294
|
+
commits = result.stdout.strip().split("\n")
|
|
287
295
|
orientation.recent_commits = [c for c in commits if c]
|
|
288
296
|
|
|
289
297
|
except subprocess.TimeoutExpired:
|
|
@@ -293,18 +301,21 @@ class SessionInitializer:
|
|
|
293
301
|
|
|
294
302
|
# Find key directories
|
|
295
303
|
key_dirs = ["src", "lib", "tests", "test", "app", "api", "core"]
|
|
296
|
-
orientation.key_directories = [
|
|
297
|
-
d for d in key_dirs if (path / d).is_dir()
|
|
298
|
-
]
|
|
304
|
+
orientation.key_directories = [d for d in key_dirs if (path / d).is_dir()]
|
|
299
305
|
|
|
300
306
|
# Find config files
|
|
301
307
|
config_files = [
|
|
302
|
-
"package.json",
|
|
303
|
-
"
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
308
|
+
"package.json",
|
|
309
|
+
"pyproject.toml",
|
|
310
|
+
"setup.py",
|
|
311
|
+
"Cargo.toml",
|
|
312
|
+
"go.mod",
|
|
313
|
+
"pom.xml",
|
|
314
|
+
"build.gradle",
|
|
315
|
+
"Makefile",
|
|
316
|
+
"CMakeLists.txt",
|
|
307
317
|
]
|
|
318
|
+
orientation.config_files = [f for f in config_files if (path / f).exists()]
|
|
308
319
|
|
|
309
320
|
# Generate summary
|
|
310
321
|
orientation.summary = self._generate_orientation_summary(orientation)
|
|
@@ -336,14 +347,10 @@ class SessionInitializer:
|
|
|
336
347
|
|
|
337
348
|
# Convert scope to rules
|
|
338
349
|
if scope.can_learn:
|
|
339
|
-
rules.scope_rules = [
|
|
340
|
-
f"Learn from: {', '.join(scope.can_learn)}"
|
|
341
|
-
]
|
|
350
|
+
rules.scope_rules = [f"Learn from: {', '.join(scope.can_learn)}"]
|
|
342
351
|
|
|
343
352
|
if scope.cannot_learn:
|
|
344
|
-
rules.constraints = [
|
|
345
|
-
f"Do not learn from: {', '.join(scope.cannot_learn)}"
|
|
346
|
-
]
|
|
353
|
+
rules.constraints = [f"Do not learn from: {', '.join(scope.cannot_learn)}"]
|
|
347
354
|
|
|
348
355
|
# Default quality gates
|
|
349
356
|
rules.quality_gates = [
|
|
@@ -359,7 +366,7 @@ class SessionInitializer:
|
|
|
359
366
|
if len(work_items) == 1:
|
|
360
367
|
return prompt
|
|
361
368
|
|
|
362
|
-
item_titles = [getattr(item,
|
|
369
|
+
item_titles = [getattr(item, "title", str(item)) for item in work_items]
|
|
363
370
|
return f"{prompt}\n\nBroken down into {len(work_items)} items: {', '.join(item_titles[:3])}{'...' if len(item_titles) > 3 else ''}"
|
|
364
371
|
|
|
365
372
|
def _select_starting_point(self, work_items: List[Any]) -> Optional[Any]:
|
|
@@ -369,17 +376,15 @@ class SessionInitializer:
|
|
|
369
376
|
|
|
370
377
|
# Find highest priority unblocked item
|
|
371
378
|
actionable = [
|
|
372
|
-
item
|
|
373
|
-
|
|
374
|
-
|
|
379
|
+
item
|
|
380
|
+
for item in work_items
|
|
381
|
+
if getattr(item, "status", "pending") == "pending"
|
|
382
|
+
and not getattr(item, "blocked_by", [])
|
|
375
383
|
]
|
|
376
384
|
|
|
377
385
|
if actionable:
|
|
378
386
|
# Sort by priority (higher = more important)
|
|
379
|
-
actionable.sort(
|
|
380
|
-
key=lambda x: getattr(x, 'priority', 50),
|
|
381
|
-
reverse=True
|
|
382
|
-
)
|
|
387
|
+
actionable.sort(key=lambda x: getattr(x, "priority", 50), reverse=True)
|
|
383
388
|
return actionable[0]
|
|
384
389
|
|
|
385
390
|
return work_items[0]
|
|
@@ -400,7 +405,10 @@ class SessionInitializer:
|
|
|
400
405
|
# Infer project type from config files
|
|
401
406
|
if "package.json" in orientation.config_files:
|
|
402
407
|
parts.append("Node.js project")
|
|
403
|
-
elif
|
|
408
|
+
elif (
|
|
409
|
+
"pyproject.toml" in orientation.config_files
|
|
410
|
+
or "setup.py" in orientation.config_files
|
|
411
|
+
):
|
|
404
412
|
parts.append("Python project")
|
|
405
413
|
elif "Cargo.toml" in orientation.config_files:
|
|
406
414
|
parts.append("Rust project")
|
alma/initializer/types.py
CHANGED
|
@@ -4,10 +4,10 @@ Initializer Types.
|
|
|
4
4
|
Data structures for the Session Initializer pattern.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import uuid
|
|
7
8
|
from dataclasses import dataclass, field
|
|
8
9
|
from datetime import datetime, timezone
|
|
9
10
|
from typing import Any, Dict, List, Optional
|
|
10
|
-
import uuid
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@dataclass
|
|
@@ -169,8 +169,8 @@ class InitializationResult:
|
|
|
169
169
|
if self.work_items:
|
|
170
170
|
sections.append("### Work Items")
|
|
171
171
|
for i, item in enumerate(self.work_items, 1):
|
|
172
|
-
title = getattr(item,
|
|
173
|
-
status = getattr(item,
|
|
172
|
+
title = getattr(item, "title", str(item))
|
|
173
|
+
status = getattr(item, "status", "pending")
|
|
174
174
|
sections.append(f"{i}. [{status}] {title}")
|
|
175
175
|
sections.append("")
|
|
176
176
|
|
|
@@ -183,7 +183,7 @@ class InitializationResult:
|
|
|
183
183
|
# Relevant memories
|
|
184
184
|
if self.relevant_memories:
|
|
185
185
|
sections.append("### Relevant Knowledge from Past Runs")
|
|
186
|
-
if hasattr(self.relevant_memories,
|
|
186
|
+
if hasattr(self.relevant_memories, "to_prompt"):
|
|
187
187
|
sections.append(self.relevant_memories.to_prompt())
|
|
188
188
|
else:
|
|
189
189
|
sections.append(str(self.relevant_memories))
|
|
@@ -198,7 +198,9 @@ class InitializationResult:
|
|
|
198
198
|
# Recommended start
|
|
199
199
|
if self.recommended_start:
|
|
200
200
|
sections.append("### Recommended First Action")
|
|
201
|
-
title = getattr(
|
|
201
|
+
title = getattr(
|
|
202
|
+
self.recommended_start, "title", str(self.recommended_start)
|
|
203
|
+
)
|
|
202
204
|
sections.append(f"Start with: {title}")
|
|
203
205
|
sections.append("")
|
|
204
206
|
|
|
@@ -214,18 +216,22 @@ class InitializationResult:
|
|
|
214
216
|
"original_prompt": self.original_prompt,
|
|
215
217
|
"goal": self.goal,
|
|
216
218
|
"work_items": [
|
|
217
|
-
item.to_dict() if hasattr(item,
|
|
219
|
+
item.to_dict() if hasattr(item, "to_dict") else str(item)
|
|
218
220
|
for item in self.work_items
|
|
219
221
|
],
|
|
220
|
-
"orientation":
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
"orientation": (
|
|
223
|
+
{
|
|
224
|
+
"current_branch": self.orientation.current_branch,
|
|
225
|
+
"has_uncommitted_changes": self.orientation.has_uncommitted_changes,
|
|
226
|
+
"recent_commits": self.orientation.recent_commits,
|
|
227
|
+
"root_path": self.orientation.root_path,
|
|
228
|
+
"key_directories": self.orientation.key_directories,
|
|
229
|
+
"config_files": self.orientation.config_files,
|
|
230
|
+
"summary": self.orientation.summary,
|
|
231
|
+
}
|
|
232
|
+
if self.orientation
|
|
233
|
+
else None
|
|
234
|
+
),
|
|
229
235
|
"recent_activity": self.recent_activity,
|
|
230
236
|
"rules": {
|
|
231
237
|
"scope_rules": self.rules.scope_rules,
|
|
@@ -234,8 +240,10 @@ class InitializationResult:
|
|
|
234
240
|
},
|
|
235
241
|
"recommended_start": (
|
|
236
242
|
self.recommended_start.to_dict()
|
|
237
|
-
if self.recommended_start and hasattr(self.recommended_start,
|
|
238
|
-
else str(self.recommended_start)
|
|
243
|
+
if self.recommended_start and hasattr(self.recommended_start, "to_dict")
|
|
244
|
+
else str(self.recommended_start)
|
|
245
|
+
if self.recommended_start
|
|
246
|
+
else None
|
|
239
247
|
),
|
|
240
248
|
"initialized_at": self.initialized_at.isoformat(),
|
|
241
249
|
"metadata": self.metadata,
|
alma/integration/__init__.py
CHANGED
|
@@ -5,32 +5,32 @@ Provides integration hooks for Claude Code agents (Helena, Victor, etc).
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from alma.integration.claude_agents import (
|
|
8
|
+
AgentIntegration,
|
|
8
9
|
AgentType,
|
|
10
|
+
ClaudeAgentHooks,
|
|
9
11
|
TaskContext,
|
|
10
12
|
TaskOutcome,
|
|
11
|
-
ClaudeAgentHooks,
|
|
12
|
-
AgentIntegration,
|
|
13
13
|
create_integration,
|
|
14
14
|
)
|
|
15
15
|
from alma.integration.helena import (
|
|
16
|
+
HELENA_CATEGORIES,
|
|
17
|
+
HELENA_FORBIDDEN,
|
|
18
|
+
HelenaHooks,
|
|
16
19
|
UITestContext,
|
|
17
20
|
UITestOutcome,
|
|
18
|
-
HelenaHooks,
|
|
19
21
|
create_helena_hooks,
|
|
20
|
-
helena_pre_task,
|
|
21
22
|
helena_post_task,
|
|
22
|
-
|
|
23
|
-
HELENA_FORBIDDEN,
|
|
23
|
+
helena_pre_task,
|
|
24
24
|
)
|
|
25
25
|
from alma.integration.victor import (
|
|
26
|
+
VICTOR_CATEGORIES,
|
|
27
|
+
VICTOR_FORBIDDEN,
|
|
26
28
|
APITestContext,
|
|
27
29
|
APITestOutcome,
|
|
28
30
|
VictorHooks,
|
|
29
31
|
create_victor_hooks,
|
|
30
|
-
victor_pre_task,
|
|
31
32
|
victor_post_task,
|
|
32
|
-
|
|
33
|
-
VICTOR_FORBIDDEN,
|
|
33
|
+
victor_pre_task,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
__all__ = [
|
|
@@ -9,20 +9,21 @@ These hooks enable agents to:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
|
-
from typing import Optional, Dict, Any, List
|
|
13
12
|
from dataclasses import dataclass, field
|
|
14
13
|
from datetime import datetime, timezone
|
|
15
14
|
from enum import Enum
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
16
|
|
|
17
17
|
from alma.core import ALMA
|
|
18
|
+
from alma.harness.base import Context, Harness, RunResult
|
|
18
19
|
from alma.types import MemorySlice
|
|
19
|
-
from alma.harness.base import Harness, Context, RunResult
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class AgentType(Enum):
|
|
25
25
|
"""Supported Claude Code agent types."""
|
|
26
|
+
|
|
26
27
|
HELENA = "helena"
|
|
27
28
|
VICTOR = "victor"
|
|
28
29
|
CLARA = "clara"
|
|
@@ -37,6 +38,7 @@ class TaskContext:
|
|
|
37
38
|
|
|
38
39
|
Captures all relevant information for memory retrieval and learning.
|
|
39
40
|
"""
|
|
41
|
+
|
|
40
42
|
task_description: str
|
|
41
43
|
task_type: str
|
|
42
44
|
agent_name: str
|
|
@@ -67,6 +69,7 @@ class TaskOutcome:
|
|
|
67
69
|
|
|
68
70
|
Used for learning from task results.
|
|
69
71
|
"""
|
|
72
|
+
|
|
70
73
|
success: bool
|
|
71
74
|
strategy_used: str
|
|
72
75
|
output: Any = None
|
|
@@ -272,17 +275,17 @@ class ClaudeAgentHooks:
|
|
|
272
275
|
sections.append(f"- [{dk.domain}] {dk.fact}")
|
|
273
276
|
|
|
274
277
|
# User preferences
|
|
275
|
-
if memories.
|
|
278
|
+
if memories.preferences:
|
|
276
279
|
if include_section_headers:
|
|
277
280
|
sections.append("\n### User Preferences:")
|
|
278
|
-
for up in memories.
|
|
281
|
+
for up in memories.preferences:
|
|
279
282
|
sections.append(f"- [{up.category}] {up.preference}")
|
|
280
283
|
|
|
281
284
|
# Recent outcomes
|
|
282
|
-
if memories.
|
|
285
|
+
if memories.outcomes:
|
|
283
286
|
if include_section_headers:
|
|
284
287
|
sections.append("\n### Recent Outcomes:")
|
|
285
|
-
for o in memories.
|
|
288
|
+
for o in memories.outcomes[:3]: # Limit to 3 most recent
|
|
286
289
|
status = "✓" if o.success else "✗"
|
|
287
290
|
sections.append(
|
|
288
291
|
f"- {status} {o.task_type}: {o.task_description[:50]}..."
|
|
@@ -391,10 +394,7 @@ class AgentIntegration:
|
|
|
391
394
|
|
|
392
395
|
def get_all_stats(self) -> Dict[str, Dict[str, Any]]:
|
|
393
396
|
"""Get memory statistics for all registered agents."""
|
|
394
|
-
return {
|
|
395
|
-
name: hooks.get_agent_stats()
|
|
396
|
-
for name, hooks in self._agents.items()
|
|
397
|
-
}
|
|
397
|
+
return {name: hooks.get_agent_stats() for name, hooks in self._agents.items()}
|
|
398
398
|
|
|
399
399
|
|
|
400
400
|
def create_integration(
|