alma-memory 0.5.1__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.
Files changed (111) hide show
  1. alma/__init__.py +296 -226
  2. alma/compression/__init__.py +33 -0
  3. alma/compression/pipeline.py +980 -0
  4. alma/confidence/__init__.py +47 -47
  5. alma/confidence/engine.py +540 -540
  6. alma/confidence/types.py +351 -351
  7. alma/config/loader.py +157 -157
  8. alma/consolidation/__init__.py +23 -23
  9. alma/consolidation/engine.py +678 -678
  10. alma/consolidation/prompts.py +84 -84
  11. alma/core.py +1189 -430
  12. alma/domains/__init__.py +30 -30
  13. alma/domains/factory.py +359 -359
  14. alma/domains/schemas.py +448 -448
  15. alma/domains/types.py +272 -272
  16. alma/events/__init__.py +75 -75
  17. alma/events/emitter.py +285 -284
  18. alma/events/storage_mixin.py +246 -246
  19. alma/events/types.py +126 -126
  20. alma/events/webhook.py +425 -425
  21. alma/exceptions.py +49 -49
  22. alma/extraction/__init__.py +31 -31
  23. alma/extraction/auto_learner.py +265 -265
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -106
  26. alma/graph/backends/__init__.py +32 -32
  27. alma/graph/backends/kuzu.py +624 -624
  28. alma/graph/backends/memgraph.py +432 -432
  29. alma/graph/backends/memory.py +236 -236
  30. alma/graph/backends/neo4j.py +417 -417
  31. alma/graph/base.py +159 -159
  32. alma/graph/extraction.py +198 -198
  33. alma/graph/store.py +860 -860
  34. alma/harness/__init__.py +35 -35
  35. alma/harness/base.py +386 -386
  36. alma/harness/domains.py +705 -705
  37. alma/initializer/__init__.py +37 -37
  38. alma/initializer/initializer.py +418 -418
  39. alma/initializer/types.py +250 -250
  40. alma/integration/__init__.py +62 -62
  41. alma/integration/claude_agents.py +444 -444
  42. alma/integration/helena.py +423 -423
  43. alma/integration/victor.py +471 -471
  44. alma/learning/__init__.py +101 -86
  45. alma/learning/decay.py +878 -0
  46. alma/learning/forgetting.py +1446 -1446
  47. alma/learning/heuristic_extractor.py +390 -390
  48. alma/learning/protocols.py +374 -374
  49. alma/learning/validation.py +346 -346
  50. alma/mcp/__init__.py +123 -45
  51. alma/mcp/__main__.py +156 -156
  52. alma/mcp/resources.py +122 -122
  53. alma/mcp/server.py +955 -591
  54. alma/mcp/tools.py +3254 -509
  55. alma/observability/__init__.py +91 -84
  56. alma/observability/config.py +302 -302
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -424
  59. alma/observability/metrics.py +583 -583
  60. alma/observability/tracing.py +440 -440
  61. alma/progress/__init__.py +21 -21
  62. alma/progress/tracker.py +607 -607
  63. alma/progress/types.py +250 -250
  64. alma/retrieval/__init__.py +134 -53
  65. alma/retrieval/budget.py +525 -0
  66. alma/retrieval/cache.py +1304 -1061
  67. alma/retrieval/embeddings.py +202 -202
  68. alma/retrieval/engine.py +850 -427
  69. alma/retrieval/modes.py +365 -0
  70. alma/retrieval/progressive.py +560 -0
  71. alma/retrieval/scoring.py +344 -344
  72. alma/retrieval/trust_scoring.py +637 -0
  73. alma/retrieval/verification.py +797 -0
  74. alma/session/__init__.py +19 -19
  75. alma/session/manager.py +442 -399
  76. alma/session/types.py +288 -288
  77. alma/storage/__init__.py +101 -90
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1259
  80. alma/storage/base.py +1083 -583
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -103
  83. alma/storage/file_based.py +614 -614
  84. alma/storage/migrations/__init__.py +21 -21
  85. alma/storage/migrations/base.py +321 -321
  86. alma/storage/migrations/runner.py +323 -323
  87. alma/storage/migrations/version_stores.py +337 -337
  88. alma/storage/migrations/versions/__init__.py +11 -11
  89. alma/storage/migrations/versions/v1_0_0.py +373 -373
  90. alma/storage/migrations/versions/v1_1_0_workflow_context.py +551 -0
  91. alma/storage/pinecone.py +1080 -1080
  92. alma/storage/postgresql.py +1948 -1559
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1457
  95. alma/testing/__init__.py +46 -46
  96. alma/testing/factories.py +301 -301
  97. alma/testing/mocks.py +389 -389
  98. alma/types.py +292 -264
  99. alma/utils/__init__.py +19 -0
  100. alma/utils/tokenizer.py +521 -0
  101. alma/workflow/__init__.py +83 -0
  102. alma/workflow/artifacts.py +170 -0
  103. alma/workflow/checkpoint.py +311 -0
  104. alma/workflow/context.py +228 -0
  105. alma/workflow/outcomes.py +189 -0
  106. alma/workflow/reducers.py +393 -0
  107. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/METADATA +210 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.1.dist-info/RECORD +0 -93
  110. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,444 +1,444 @@
1
- """
2
- ALMA Claude Code Integration.
3
-
4
- Provides hooks for integrating ALMA with Claude Code agents (Helena, Victor, etc).
5
- These hooks enable agents to:
6
- - Retrieve relevant memories before executing tasks
7
- - Learn from task outcomes automatically
8
- - Access domain-specific heuristics and patterns
9
- """
10
-
11
- import logging
12
- from dataclasses import dataclass, field
13
- from datetime import datetime, timezone
14
- from enum import Enum
15
- from typing import Any, Dict, List, Optional
16
-
17
- from alma.core import ALMA
18
- from alma.harness.base import Context, Harness, RunResult
19
- from alma.types import MemorySlice
20
-
21
- logger = logging.getLogger(__name__)
22
-
23
-
24
- class AgentType(Enum):
25
- """Supported Claude Code agent types."""
26
-
27
- HELENA = "helena"
28
- VICTOR = "victor"
29
- CLARA = "clara"
30
- ALEX = "alex"
31
- CUSTOM = "custom"
32
-
33
-
34
- @dataclass
35
- class TaskContext:
36
- """
37
- Context for a Claude Code agent task.
38
-
39
- Captures all relevant information for memory retrieval and learning.
40
- """
41
-
42
- task_description: str
43
- task_type: str
44
- agent_name: str
45
- project_id: str
46
- user_id: Optional[str] = None
47
- session_id: Optional[str] = None
48
- inputs: Dict[str, Any] = field(default_factory=dict)
49
- constraints: List[str] = field(default_factory=list)
50
- timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
51
-
52
- def to_harness_context(self) -> Context:
53
- """Convert to Harness Context."""
54
- return Context(
55
- task=self.task_description,
56
- user_id=self.user_id,
57
- project_id=self.project_id,
58
- session_id=self.session_id,
59
- inputs=self.inputs,
60
- constraints=self.constraints,
61
- timestamp=self.timestamp,
62
- )
63
-
64
-
65
- @dataclass
66
- class TaskOutcome:
67
- """
68
- Outcome of a Claude Code agent task.
69
-
70
- Used for learning from task results.
71
- """
72
-
73
- success: bool
74
- strategy_used: str
75
- output: Any = None
76
- tools_used: List[str] = field(default_factory=list)
77
- duration_ms: Optional[int] = None
78
- error_message: Optional[str] = None
79
- feedback: Optional[str] = None
80
- reflections: List[str] = field(default_factory=list)
81
-
82
- def to_run_result(self) -> RunResult:
83
- """Convert to Harness RunResult."""
84
- return RunResult(
85
- success=self.success,
86
- output=self.output,
87
- reflections=self.reflections,
88
- tools_used=self.tools_used,
89
- duration_ms=self.duration_ms,
90
- error=self.error_message,
91
- )
92
-
93
-
94
- class ClaudeAgentHooks:
95
- """
96
- Integration hooks for Claude Code agents.
97
-
98
- Provides a consistent interface for agents to interact with ALMA.
99
-
100
- Usage:
101
- hooks = ClaudeAgentHooks(alma, agent_type=AgentType.HELENA)
102
-
103
- # Before task execution
104
- memories = hooks.pre_task(task_context)
105
- prompt_enhancement = hooks.format_memories_for_prompt(memories)
106
-
107
- # After task execution
108
- hooks.post_task(task_context, task_outcome)
109
- """
110
-
111
- def __init__(
112
- self,
113
- alma: ALMA,
114
- agent_type: AgentType,
115
- harness: Optional[Harness] = None,
116
- auto_learn: bool = True,
117
- ):
118
- """
119
- Initialize hooks for an agent.
120
-
121
- Args:
122
- alma: ALMA instance for memory operations
123
- agent_type: Type of Claude Code agent
124
- harness: Optional pre-configured harness
125
- auto_learn: Whether to automatically learn from outcomes
126
- """
127
- self.alma = alma
128
- self.agent_type = agent_type
129
- self.agent_name = agent_type.value
130
- self.harness = harness
131
- self.auto_learn = auto_learn
132
- self._task_start_times: Dict[str, datetime] = {}
133
-
134
- def pre_task(
135
- self,
136
- context: TaskContext,
137
- top_k: int = 5,
138
- ) -> MemorySlice:
139
- """
140
- Pre-task hook: Retrieve relevant memories.
141
-
142
- Called before the agent executes a task to get relevant
143
- heuristics, patterns, and domain knowledge.
144
-
145
- Args:
146
- context: Task context
147
- top_k: Maximum items per memory type
148
-
149
- Returns:
150
- MemorySlice with relevant memories
151
- """
152
- # Track start time for duration calculation
153
- task_id = f"{context.project_id}:{context.timestamp.isoformat()}"
154
- self._task_start_times[task_id] = datetime.now(timezone.utc)
155
-
156
- logger.debug(
157
- f"[{self.agent_name}] Pre-task: Retrieving memories for '{context.task_description[:50]}...'"
158
- )
159
-
160
- if self.harness:
161
- # Use harness pre_run method
162
- harness_context = context.to_harness_context()
163
- return self.harness.pre_run(harness_context)
164
- else:
165
- # Direct ALMA retrieval
166
- return self.alma.retrieve(
167
- task=context.task_description,
168
- agent=self.agent_name,
169
- user_id=context.user_id,
170
- top_k=top_k,
171
- )
172
-
173
- def post_task(
174
- self,
175
- context: TaskContext,
176
- outcome: TaskOutcome,
177
- ) -> bool:
178
- """
179
- Post-task hook: Learn from the outcome.
180
-
181
- Called after the agent completes a task to record the outcome
182
- and potentially update heuristics.
183
-
184
- Args:
185
- context: Original task context
186
- outcome: Task outcome
187
-
188
- Returns:
189
- True if learning was recorded, False otherwise
190
- """
191
- if not self.auto_learn:
192
- logger.debug(f"[{self.agent_name}] Auto-learn disabled, skipping")
193
- return False
194
-
195
- # Calculate duration if not provided
196
- task_id = f"{context.project_id}:{context.timestamp.isoformat()}"
197
- if outcome.duration_ms is None and task_id in self._task_start_times:
198
- start = self._task_start_times.pop(task_id)
199
- outcome.duration_ms = int(
200
- (datetime.now(timezone.utc) - start).total_seconds() * 1000
201
- )
202
-
203
- logger.debug(
204
- f"[{self.agent_name}] Post-task: Recording {'success' if outcome.success else 'failure'} "
205
- f"for '{context.task_description[:50]}...'"
206
- )
207
-
208
- if self.harness:
209
- # Use harness post_run method
210
- harness_context = context.to_harness_context()
211
- run_result = outcome.to_run_result()
212
- self.harness.post_run(harness_context, run_result)
213
- return True
214
- else:
215
- # Direct ALMA learning - now returns Outcome object
216
- self.alma.learn(
217
- agent=self.agent_name,
218
- task=context.task_description,
219
- outcome="success" if outcome.success else "failure",
220
- strategy_used=outcome.strategy_used,
221
- task_type=context.task_type,
222
- duration_ms=outcome.duration_ms,
223
- error_message=outcome.error_message,
224
- feedback=outcome.feedback,
225
- )
226
- return True
227
-
228
- def format_memories_for_prompt(
229
- self,
230
- memories: MemorySlice,
231
- include_section_headers: bool = True,
232
- ) -> str:
233
- """
234
- Format memories for injection into agent prompt.
235
-
236
- Converts MemorySlice into a formatted string suitable for
237
- inclusion in the agent's system prompt.
238
-
239
- Args:
240
- memories: Retrieved memory slice
241
- include_section_headers: Whether to include markdown headers
242
-
243
- Returns:
244
- Formatted string for prompt injection
245
- """
246
- if memories.total_items == 0:
247
- return ""
248
-
249
- sections = []
250
-
251
- if include_section_headers:
252
- sections.append("## Relevant Memory (from past runs)")
253
-
254
- # Heuristics
255
- if memories.heuristics:
256
- if include_section_headers:
257
- sections.append("\n### Proven Strategies:")
258
- for h in memories.heuristics:
259
- confidence_pct = int(h.confidence * 100)
260
- sections.append(
261
- f"- **{h.condition}**: {h.strategy} (confidence: {confidence_pct}%)"
262
- )
263
-
264
- # Anti-patterns
265
- if memories.anti_patterns:
266
- if include_section_headers:
267
- sections.append("\n### Avoid These:")
268
- for ap in memories.anti_patterns:
269
- sections.append(f"- ⚠️ {ap.pattern}: {ap.consequence}")
270
-
271
- # Domain knowledge
272
- if memories.domain_knowledge:
273
- if include_section_headers:
274
- sections.append("\n### Domain Knowledge:")
275
- for dk in memories.domain_knowledge:
276
- sections.append(f"- [{dk.domain}] {dk.fact}")
277
-
278
- # User preferences
279
- if memories.preferences:
280
- if include_section_headers:
281
- sections.append("\n### User Preferences:")
282
- for up in memories.preferences:
283
- sections.append(f"- [{up.category}] {up.preference}")
284
-
285
- # Recent outcomes
286
- if memories.outcomes:
287
- if include_section_headers:
288
- sections.append("\n### Recent Outcomes:")
289
- for o in memories.outcomes[:3]: # Limit to 3 most recent
290
- status = "✓" if o.success else "✗"
291
- sections.append(
292
- f"- {status} {o.task_type}: {o.task_description[:50]}..."
293
- )
294
-
295
- return "\n".join(sections)
296
-
297
- def get_agent_stats(self) -> Dict[str, Any]:
298
- """Get memory statistics for this agent."""
299
- return self.alma.get_stats(agent=self.agent_name)
300
-
301
- def add_knowledge(
302
- self,
303
- domain: str,
304
- fact: str,
305
- source: str = "agent_discovered",
306
- ) -> bool:
307
- """
308
- Add domain knowledge discovered by the agent.
309
-
310
- Args:
311
- domain: Knowledge domain (must be within agent's scope)
312
- fact: The fact to remember
313
- source: How this was discovered
314
-
315
- Returns:
316
- True if knowledge was added
317
-
318
- Raises:
319
- ScopeViolationError: If domain is not within agent's scope
320
- """
321
- from alma.exceptions import ScopeViolationError
322
-
323
- try:
324
- self.alma.add_domain_knowledge(
325
- agent=self.agent_name,
326
- domain=domain,
327
- fact=fact,
328
- source=source,
329
- )
330
- return True
331
- except ScopeViolationError:
332
- logger.warning(
333
- f"[{self.agent_name}] Scope violation: cannot add knowledge in domain '{domain}'"
334
- )
335
- return False
336
-
337
-
338
- class AgentIntegration:
339
- """
340
- High-level integration manager for multiple Claude Code agents.
341
-
342
- Manages hooks for all registered agents and provides a unified
343
- interface for the Claude Code runtime.
344
-
345
- Usage:
346
- integration = AgentIntegration(alma)
347
- integration.register_agent(AgentType.HELENA, helena_harness)
348
- integration.register_agent(AgentType.VICTOR, victor_harness)
349
-
350
- # Get hooks for a specific agent
351
- helena_hooks = integration.get_hooks("helena")
352
- """
353
-
354
- def __init__(self, alma: ALMA):
355
- """
356
- Initialize the integration manager.
357
-
358
- Args:
359
- alma: ALMA instance for memory operations
360
- """
361
- self.alma = alma
362
- self._agents: Dict[str, ClaudeAgentHooks] = {}
363
-
364
- def register_agent(
365
- self,
366
- agent_type: AgentType,
367
- harness: Optional[Harness] = None,
368
- auto_learn: bool = True,
369
- ) -> ClaudeAgentHooks:
370
- """
371
- Register an agent for integration.
372
-
373
- Args:
374
- agent_type: Type of agent
375
- harness: Optional pre-configured harness
376
- auto_learn: Whether to automatically learn from outcomes
377
-
378
- Returns:
379
- ClaudeAgentHooks for the agent
380
- """
381
- hooks = ClaudeAgentHooks(
382
- alma=self.alma,
383
- agent_type=agent_type,
384
- harness=harness,
385
- auto_learn=auto_learn,
386
- )
387
- self._agents[agent_type.value] = hooks
388
- logger.info(f"Registered agent: {agent_type.value}")
389
- return hooks
390
-
391
- def get_hooks(self, agent_name: str) -> Optional[ClaudeAgentHooks]:
392
- """
393
- Get hooks for a specific agent.
394
-
395
- Args:
396
- agent_name: Name of the agent
397
-
398
- Returns:
399
- ClaudeAgentHooks or None if not registered
400
- """
401
- return self._agents.get(agent_name)
402
-
403
- def list_agents(self) -> List[str]:
404
- """List all registered agents."""
405
- return list(self._agents.keys())
406
-
407
- def get_all_stats(self) -> Dict[str, Dict[str, Any]]:
408
- """Get memory statistics for all registered agents."""
409
- return {name: hooks.get_agent_stats() for name, hooks in self._agents.items()}
410
-
411
-
412
- def create_integration(
413
- alma: ALMA,
414
- agents: Optional[List[AgentType]] = None,
415
- ) -> AgentIntegration:
416
- """
417
- Convenience function to create an integration with default agents.
418
-
419
- Args:
420
- alma: ALMA instance
421
- agents: List of agents to register, or None for defaults (Helena, Victor)
422
-
423
- Returns:
424
- Configured AgentIntegration
425
- """
426
- from alma.harness.domains import CodingDomain
427
-
428
- integration = AgentIntegration(alma)
429
-
430
- if agents is None:
431
- agents = [AgentType.HELENA, AgentType.VICTOR]
432
-
433
- for agent_type in agents:
434
- if agent_type == AgentType.HELENA:
435
- harness = CodingDomain.create_helena(alma)
436
- integration.register_agent(agent_type, harness)
437
- elif agent_type == AgentType.VICTOR:
438
- harness = CodingDomain.create_victor(alma)
439
- integration.register_agent(agent_type, harness)
440
- else:
441
- # Register without harness for custom agents
442
- integration.register_agent(agent_type)
443
-
444
- return integration
1
+ """
2
+ ALMA Claude Code Integration.
3
+
4
+ Provides hooks for integrating ALMA with Claude Code agents (Helena, Victor, etc).
5
+ These hooks enable agents to:
6
+ - Retrieve relevant memories before executing tasks
7
+ - Learn from task outcomes automatically
8
+ - Access domain-specific heuristics and patterns
9
+ """
10
+
11
+ import logging
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime, timezone
14
+ from enum import Enum
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from alma.core import ALMA
18
+ from alma.harness.base import Context, Harness, RunResult
19
+ from alma.types import MemorySlice
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class AgentType(Enum):
25
+ """Supported Claude Code agent types."""
26
+
27
+ HELENA = "helena"
28
+ VICTOR = "victor"
29
+ CLARA = "clara"
30
+ ALEX = "alex"
31
+ CUSTOM = "custom"
32
+
33
+
34
+ @dataclass
35
+ class TaskContext:
36
+ """
37
+ Context for a Claude Code agent task.
38
+
39
+ Captures all relevant information for memory retrieval and learning.
40
+ """
41
+
42
+ task_description: str
43
+ task_type: str
44
+ agent_name: str
45
+ project_id: str
46
+ user_id: Optional[str] = None
47
+ session_id: Optional[str] = None
48
+ inputs: Dict[str, Any] = field(default_factory=dict)
49
+ constraints: List[str] = field(default_factory=list)
50
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
51
+
52
+ def to_harness_context(self) -> Context:
53
+ """Convert to Harness Context."""
54
+ return Context(
55
+ task=self.task_description,
56
+ user_id=self.user_id,
57
+ project_id=self.project_id,
58
+ session_id=self.session_id,
59
+ inputs=self.inputs,
60
+ constraints=self.constraints,
61
+ timestamp=self.timestamp,
62
+ )
63
+
64
+
65
+ @dataclass
66
+ class TaskOutcome:
67
+ """
68
+ Outcome of a Claude Code agent task.
69
+
70
+ Used for learning from task results.
71
+ """
72
+
73
+ success: bool
74
+ strategy_used: str
75
+ output: Any = None
76
+ tools_used: List[str] = field(default_factory=list)
77
+ duration_ms: Optional[int] = None
78
+ error_message: Optional[str] = None
79
+ feedback: Optional[str] = None
80
+ reflections: List[str] = field(default_factory=list)
81
+
82
+ def to_run_result(self) -> RunResult:
83
+ """Convert to Harness RunResult."""
84
+ return RunResult(
85
+ success=self.success,
86
+ output=self.output,
87
+ reflections=self.reflections,
88
+ tools_used=self.tools_used,
89
+ duration_ms=self.duration_ms,
90
+ error=self.error_message,
91
+ )
92
+
93
+
94
+ class ClaudeAgentHooks:
95
+ """
96
+ Integration hooks for Claude Code agents.
97
+
98
+ Provides a consistent interface for agents to interact with ALMA.
99
+
100
+ Usage:
101
+ hooks = ClaudeAgentHooks(alma, agent_type=AgentType.HELENA)
102
+
103
+ # Before task execution
104
+ memories = hooks.pre_task(task_context)
105
+ prompt_enhancement = hooks.format_memories_for_prompt(memories)
106
+
107
+ # After task execution
108
+ hooks.post_task(task_context, task_outcome)
109
+ """
110
+
111
+ def __init__(
112
+ self,
113
+ alma: ALMA,
114
+ agent_type: AgentType,
115
+ harness: Optional[Harness] = None,
116
+ auto_learn: bool = True,
117
+ ):
118
+ """
119
+ Initialize hooks for an agent.
120
+
121
+ Args:
122
+ alma: ALMA instance for memory operations
123
+ agent_type: Type of Claude Code agent
124
+ harness: Optional pre-configured harness
125
+ auto_learn: Whether to automatically learn from outcomes
126
+ """
127
+ self.alma = alma
128
+ self.agent_type = agent_type
129
+ self.agent_name = agent_type.value
130
+ self.harness = harness
131
+ self.auto_learn = auto_learn
132
+ self._task_start_times: Dict[str, datetime] = {}
133
+
134
+ def pre_task(
135
+ self,
136
+ context: TaskContext,
137
+ top_k: int = 5,
138
+ ) -> MemorySlice:
139
+ """
140
+ Pre-task hook: Retrieve relevant memories.
141
+
142
+ Called before the agent executes a task to get relevant
143
+ heuristics, patterns, and domain knowledge.
144
+
145
+ Args:
146
+ context: Task context
147
+ top_k: Maximum items per memory type
148
+
149
+ Returns:
150
+ MemorySlice with relevant memories
151
+ """
152
+ # Track start time for duration calculation
153
+ task_id = f"{context.project_id}:{context.timestamp.isoformat()}"
154
+ self._task_start_times[task_id] = datetime.now(timezone.utc)
155
+
156
+ logger.debug(
157
+ f"[{self.agent_name}] Pre-task: Retrieving memories for '{context.task_description[:50]}...'"
158
+ )
159
+
160
+ if self.harness:
161
+ # Use harness pre_run method
162
+ harness_context = context.to_harness_context()
163
+ return self.harness.pre_run(harness_context)
164
+ else:
165
+ # Direct ALMA retrieval
166
+ return self.alma.retrieve(
167
+ task=context.task_description,
168
+ agent=self.agent_name,
169
+ user_id=context.user_id,
170
+ top_k=top_k,
171
+ )
172
+
173
+ def post_task(
174
+ self,
175
+ context: TaskContext,
176
+ outcome: TaskOutcome,
177
+ ) -> bool:
178
+ """
179
+ Post-task hook: Learn from the outcome.
180
+
181
+ Called after the agent completes a task to record the outcome
182
+ and potentially update heuristics.
183
+
184
+ Args:
185
+ context: Original task context
186
+ outcome: Task outcome
187
+
188
+ Returns:
189
+ True if learning was recorded, False otherwise
190
+ """
191
+ if not self.auto_learn:
192
+ logger.debug(f"[{self.agent_name}] Auto-learn disabled, skipping")
193
+ return False
194
+
195
+ # Calculate duration if not provided
196
+ task_id = f"{context.project_id}:{context.timestamp.isoformat()}"
197
+ if outcome.duration_ms is None and task_id in self._task_start_times:
198
+ start = self._task_start_times.pop(task_id)
199
+ outcome.duration_ms = int(
200
+ (datetime.now(timezone.utc) - start).total_seconds() * 1000
201
+ )
202
+
203
+ logger.debug(
204
+ f"[{self.agent_name}] Post-task: Recording {'success' if outcome.success else 'failure'} "
205
+ f"for '{context.task_description[:50]}...'"
206
+ )
207
+
208
+ if self.harness:
209
+ # Use harness post_run method
210
+ harness_context = context.to_harness_context()
211
+ run_result = outcome.to_run_result()
212
+ self.harness.post_run(harness_context, run_result)
213
+ return True
214
+ else:
215
+ # Direct ALMA learning - now returns Outcome object
216
+ self.alma.learn(
217
+ agent=self.agent_name,
218
+ task=context.task_description,
219
+ outcome="success" if outcome.success else "failure",
220
+ strategy_used=outcome.strategy_used,
221
+ task_type=context.task_type,
222
+ duration_ms=outcome.duration_ms,
223
+ error_message=outcome.error_message,
224
+ feedback=outcome.feedback,
225
+ )
226
+ return True
227
+
228
+ def format_memories_for_prompt(
229
+ self,
230
+ memories: MemorySlice,
231
+ include_section_headers: bool = True,
232
+ ) -> str:
233
+ """
234
+ Format memories for injection into agent prompt.
235
+
236
+ Converts MemorySlice into a formatted string suitable for
237
+ inclusion in the agent's system prompt.
238
+
239
+ Args:
240
+ memories: Retrieved memory slice
241
+ include_section_headers: Whether to include markdown headers
242
+
243
+ Returns:
244
+ Formatted string for prompt injection
245
+ """
246
+ if memories.total_items == 0:
247
+ return ""
248
+
249
+ sections = []
250
+
251
+ if include_section_headers:
252
+ sections.append("## Relevant Memory (from past runs)")
253
+
254
+ # Heuristics
255
+ if memories.heuristics:
256
+ if include_section_headers:
257
+ sections.append("\n### Proven Strategies:")
258
+ for h in memories.heuristics:
259
+ confidence_pct = int(h.confidence * 100)
260
+ sections.append(
261
+ f"- **{h.condition}**: {h.strategy} (confidence: {confidence_pct}%)"
262
+ )
263
+
264
+ # Anti-patterns
265
+ if memories.anti_patterns:
266
+ if include_section_headers:
267
+ sections.append("\n### Avoid These:")
268
+ for ap in memories.anti_patterns:
269
+ sections.append(f"- ⚠️ {ap.pattern}: {ap.consequence}")
270
+
271
+ # Domain knowledge
272
+ if memories.domain_knowledge:
273
+ if include_section_headers:
274
+ sections.append("\n### Domain Knowledge:")
275
+ for dk in memories.domain_knowledge:
276
+ sections.append(f"- [{dk.domain}] {dk.fact}")
277
+
278
+ # User preferences
279
+ if memories.preferences:
280
+ if include_section_headers:
281
+ sections.append("\n### User Preferences:")
282
+ for up in memories.preferences:
283
+ sections.append(f"- [{up.category}] {up.preference}")
284
+
285
+ # Recent outcomes
286
+ if memories.outcomes:
287
+ if include_section_headers:
288
+ sections.append("\n### Recent Outcomes:")
289
+ for o in memories.outcomes[:3]: # Limit to 3 most recent
290
+ status = "✓" if o.success else "✗"
291
+ sections.append(
292
+ f"- {status} {o.task_type}: {o.task_description[:50]}..."
293
+ )
294
+
295
+ return "\n".join(sections)
296
+
297
+ def get_agent_stats(self) -> Dict[str, Any]:
298
+ """Get memory statistics for this agent."""
299
+ return self.alma.get_stats(agent=self.agent_name)
300
+
301
+ def add_knowledge(
302
+ self,
303
+ domain: str,
304
+ fact: str,
305
+ source: str = "agent_discovered",
306
+ ) -> bool:
307
+ """
308
+ Add domain knowledge discovered by the agent.
309
+
310
+ Args:
311
+ domain: Knowledge domain (must be within agent's scope)
312
+ fact: The fact to remember
313
+ source: How this was discovered
314
+
315
+ Returns:
316
+ True if knowledge was added
317
+
318
+ Raises:
319
+ ScopeViolationError: If domain is not within agent's scope
320
+ """
321
+ from alma.exceptions import ScopeViolationError
322
+
323
+ try:
324
+ self.alma.add_domain_knowledge(
325
+ agent=self.agent_name,
326
+ domain=domain,
327
+ fact=fact,
328
+ source=source,
329
+ )
330
+ return True
331
+ except ScopeViolationError:
332
+ logger.warning(
333
+ f"[{self.agent_name}] Scope violation: cannot add knowledge in domain '{domain}'"
334
+ )
335
+ return False
336
+
337
+
338
+ class AgentIntegration:
339
+ """
340
+ High-level integration manager for multiple Claude Code agents.
341
+
342
+ Manages hooks for all registered agents and provides a unified
343
+ interface for the Claude Code runtime.
344
+
345
+ Usage:
346
+ integration = AgentIntegration(alma)
347
+ integration.register_agent(AgentType.HELENA, helena_harness)
348
+ integration.register_agent(AgentType.VICTOR, victor_harness)
349
+
350
+ # Get hooks for a specific agent
351
+ helena_hooks = integration.get_hooks("helena")
352
+ """
353
+
354
+ def __init__(self, alma: ALMA):
355
+ """
356
+ Initialize the integration manager.
357
+
358
+ Args:
359
+ alma: ALMA instance for memory operations
360
+ """
361
+ self.alma = alma
362
+ self._agents: Dict[str, ClaudeAgentHooks] = {}
363
+
364
+ def register_agent(
365
+ self,
366
+ agent_type: AgentType,
367
+ harness: Optional[Harness] = None,
368
+ auto_learn: bool = True,
369
+ ) -> ClaudeAgentHooks:
370
+ """
371
+ Register an agent for integration.
372
+
373
+ Args:
374
+ agent_type: Type of agent
375
+ harness: Optional pre-configured harness
376
+ auto_learn: Whether to automatically learn from outcomes
377
+
378
+ Returns:
379
+ ClaudeAgentHooks for the agent
380
+ """
381
+ hooks = ClaudeAgentHooks(
382
+ alma=self.alma,
383
+ agent_type=agent_type,
384
+ harness=harness,
385
+ auto_learn=auto_learn,
386
+ )
387
+ self._agents[agent_type.value] = hooks
388
+ logger.info(f"Registered agent: {agent_type.value}")
389
+ return hooks
390
+
391
+ def get_hooks(self, agent_name: str) -> Optional[ClaudeAgentHooks]:
392
+ """
393
+ Get hooks for a specific agent.
394
+
395
+ Args:
396
+ agent_name: Name of the agent
397
+
398
+ Returns:
399
+ ClaudeAgentHooks or None if not registered
400
+ """
401
+ return self._agents.get(agent_name)
402
+
403
+ def list_agents(self) -> List[str]:
404
+ """List all registered agents."""
405
+ return list(self._agents.keys())
406
+
407
+ def get_all_stats(self) -> Dict[str, Dict[str, Any]]:
408
+ """Get memory statistics for all registered agents."""
409
+ return {name: hooks.get_agent_stats() for name, hooks in self._agents.items()}
410
+
411
+
412
+ def create_integration(
413
+ alma: ALMA,
414
+ agents: Optional[List[AgentType]] = None,
415
+ ) -> AgentIntegration:
416
+ """
417
+ Convenience function to create an integration with default agents.
418
+
419
+ Args:
420
+ alma: ALMA instance
421
+ agents: List of agents to register, or None for defaults (Helena, Victor)
422
+
423
+ Returns:
424
+ Configured AgentIntegration
425
+ """
426
+ from alma.harness.domains import CodingDomain
427
+
428
+ integration = AgentIntegration(alma)
429
+
430
+ if agents is None:
431
+ agents = [AgentType.HELENA, AgentType.VICTOR]
432
+
433
+ for agent_type in agents:
434
+ if agent_type == AgentType.HELENA:
435
+ harness = CodingDomain.create_helena(alma)
436
+ integration.register_agent(agent_type, harness)
437
+ elif agent_type == AgentType.VICTOR:
438
+ harness = CodingDomain.create_victor(alma)
439
+ integration.register_agent(agent_type, harness)
440
+ else:
441
+ # Register without harness for custom agents
442
+ integration.register_agent(agent_type)
443
+
444
+ return integration