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.
Files changed (111) hide show
  1. alma/__init__.py +296 -194
  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 -322
  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 -264
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -81
  26. alma/graph/backends/__init__.py +32 -18
  27. alma/graph/backends/kuzu.py +624 -0
  28. alma/graph/backends/memgraph.py +432 -0
  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 -432
  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 -511
  55. alma/observability/__init__.py +91 -0
  56. alma/observability/config.py +302 -0
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -0
  59. alma/observability/metrics.py +583 -0
  60. alma/observability/tracing.py +440 -0
  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 -366
  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 -61
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1048
  80. alma/storage/base.py +1083 -525
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -0
  83. alma/storage/file_based.py +614 -619
  84. alma/storage/migrations/__init__.py +21 -0
  85. alma/storage/migrations/base.py +321 -0
  86. alma/storage/migrations/runner.py +323 -0
  87. alma/storage/migrations/version_stores.py +337 -0
  88. alma/storage/migrations/versions/__init__.py +11 -0
  89. alma/storage/migrations/versions/v1_0_0.py +373 -0
  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 -1452
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1358
  95. alma/testing/__init__.py +46 -0
  96. alma/testing/factories.py +301 -0
  97. alma/testing/mocks.py +389 -0
  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.0.dist-info → alma_memory-0.7.0.dist-info}/METADATA +244 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.0.dist-info/RECORD +0 -76
  110. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,432 +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
216
- return 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
-
227
- def format_memories_for_prompt(
228
- self,
229
- memories: MemorySlice,
230
- include_section_headers: bool = True,
231
- ) -> str:
232
- """
233
- Format memories for injection into agent prompt.
234
-
235
- Converts MemorySlice into a formatted string suitable for
236
- inclusion in the agent's system prompt.
237
-
238
- Args:
239
- memories: Retrieved memory slice
240
- include_section_headers: Whether to include markdown headers
241
-
242
- Returns:
243
- Formatted string for prompt injection
244
- """
245
- if memories.total_items == 0:
246
- return ""
247
-
248
- sections = []
249
-
250
- if include_section_headers:
251
- sections.append("## Relevant Memory (from past runs)")
252
-
253
- # Heuristics
254
- if memories.heuristics:
255
- if include_section_headers:
256
- sections.append("\n### Proven Strategies:")
257
- for h in memories.heuristics:
258
- confidence_pct = int(h.confidence * 100)
259
- sections.append(
260
- f"- **{h.condition}**: {h.strategy} (confidence: {confidence_pct}%)"
261
- )
262
-
263
- # Anti-patterns
264
- if memories.anti_patterns:
265
- if include_section_headers:
266
- sections.append("\n### Avoid These:")
267
- for ap in memories.anti_patterns:
268
- sections.append(f"- ⚠️ {ap.pattern}: {ap.consequence}")
269
-
270
- # Domain knowledge
271
- if memories.domain_knowledge:
272
- if include_section_headers:
273
- sections.append("\n### Domain Knowledge:")
274
- for dk in memories.domain_knowledge:
275
- sections.append(f"- [{dk.domain}] {dk.fact}")
276
-
277
- # User preferences
278
- if memories.preferences:
279
- if include_section_headers:
280
- sections.append("\n### User Preferences:")
281
- for up in memories.preferences:
282
- sections.append(f"- [{up.category}] {up.preference}")
283
-
284
- # Recent outcomes
285
- if memories.outcomes:
286
- if include_section_headers:
287
- sections.append("\n### Recent Outcomes:")
288
- for o in memories.outcomes[:3]: # Limit to 3 most recent
289
- status = "✓" if o.success else "✗"
290
- sections.append(
291
- f"- {status} {o.task_type}: {o.task_description[:50]}..."
292
- )
293
-
294
- return "\n".join(sections)
295
-
296
- def get_agent_stats(self) -> Dict[str, Any]:
297
- """Get memory statistics for this agent."""
298
- return self.alma.get_stats(agent=self.agent_name)
299
-
300
- def add_knowledge(
301
- self,
302
- domain: str,
303
- fact: str,
304
- source: str = "agent_discovered",
305
- ) -> bool:
306
- """
307
- Add domain knowledge discovered by the agent.
308
-
309
- Args:
310
- domain: Knowledge domain (must be within agent's scope)
311
- fact: The fact to remember
312
- source: How this was discovered
313
-
314
- Returns:
315
- True if knowledge was added, False if scope violation
316
- """
317
- result = self.alma.add_domain_knowledge(
318
- agent=self.agent_name,
319
- domain=domain,
320
- fact=fact,
321
- source=source,
322
- )
323
- return result is not None
324
-
325
-
326
- class AgentIntegration:
327
- """
328
- High-level integration manager for multiple Claude Code agents.
329
-
330
- Manages hooks for all registered agents and provides a unified
331
- interface for the Claude Code runtime.
332
-
333
- Usage:
334
- integration = AgentIntegration(alma)
335
- integration.register_agent(AgentType.HELENA, helena_harness)
336
- integration.register_agent(AgentType.VICTOR, victor_harness)
337
-
338
- # Get hooks for a specific agent
339
- helena_hooks = integration.get_hooks("helena")
340
- """
341
-
342
- def __init__(self, alma: ALMA):
343
- """
344
- Initialize the integration manager.
345
-
346
- Args:
347
- alma: ALMA instance for memory operations
348
- """
349
- self.alma = alma
350
- self._agents: Dict[str, ClaudeAgentHooks] = {}
351
-
352
- def register_agent(
353
- self,
354
- agent_type: AgentType,
355
- harness: Optional[Harness] = None,
356
- auto_learn: bool = True,
357
- ) -> ClaudeAgentHooks:
358
- """
359
- Register an agent for integration.
360
-
361
- Args:
362
- agent_type: Type of agent
363
- harness: Optional pre-configured harness
364
- auto_learn: Whether to automatically learn from outcomes
365
-
366
- Returns:
367
- ClaudeAgentHooks for the agent
368
- """
369
- hooks = ClaudeAgentHooks(
370
- alma=self.alma,
371
- agent_type=agent_type,
372
- harness=harness,
373
- auto_learn=auto_learn,
374
- )
375
- self._agents[agent_type.value] = hooks
376
- logger.info(f"Registered agent: {agent_type.value}")
377
- return hooks
378
-
379
- def get_hooks(self, agent_name: str) -> Optional[ClaudeAgentHooks]:
380
- """
381
- Get hooks for a specific agent.
382
-
383
- Args:
384
- agent_name: Name of the agent
385
-
386
- Returns:
387
- ClaudeAgentHooks or None if not registered
388
- """
389
- return self._agents.get(agent_name)
390
-
391
- def list_agents(self) -> List[str]:
392
- """List all registered agents."""
393
- return list(self._agents.keys())
394
-
395
- def get_all_stats(self) -> Dict[str, Dict[str, Any]]:
396
- """Get memory statistics for all registered agents."""
397
- return {name: hooks.get_agent_stats() for name, hooks in self._agents.items()}
398
-
399
-
400
- def create_integration(
401
- alma: ALMA,
402
- agents: Optional[List[AgentType]] = None,
403
- ) -> AgentIntegration:
404
- """
405
- Convenience function to create an integration with default agents.
406
-
407
- Args:
408
- alma: ALMA instance
409
- agents: List of agents to register, or None for defaults (Helena, Victor)
410
-
411
- Returns:
412
- Configured AgentIntegration
413
- """
414
- from alma.harness.domains import CodingDomain
415
-
416
- integration = AgentIntegration(alma)
417
-
418
- if agents is None:
419
- agents = [AgentType.HELENA, AgentType.VICTOR]
420
-
421
- for agent_type in agents:
422
- if agent_type == AgentType.HELENA:
423
- harness = CodingDomain.create_helena(alma)
424
- integration.register_agent(agent_type, harness)
425
- elif agent_type == AgentType.VICTOR:
426
- harness = CodingDomain.create_victor(alma)
427
- integration.register_agent(agent_type, harness)
428
- else:
429
- # Register without harness for custom agents
430
- integration.register_agent(agent_type)
431
-
432
- 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