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.
Files changed (77) hide show
  1. alma/__init__.py +88 -44
  2. alma/confidence/__init__.py +1 -1
  3. alma/confidence/engine.py +92 -58
  4. alma/confidence/types.py +34 -14
  5. alma/config/loader.py +3 -2
  6. alma/consolidation/__init__.py +23 -0
  7. alma/consolidation/engine.py +678 -0
  8. alma/consolidation/prompts.py +84 -0
  9. alma/core.py +15 -15
  10. alma/domains/__init__.py +6 -6
  11. alma/domains/factory.py +12 -9
  12. alma/domains/schemas.py +17 -3
  13. alma/domains/types.py +8 -4
  14. alma/events/__init__.py +75 -0
  15. alma/events/emitter.py +284 -0
  16. alma/events/storage_mixin.py +246 -0
  17. alma/events/types.py +126 -0
  18. alma/events/webhook.py +425 -0
  19. alma/exceptions.py +49 -0
  20. alma/extraction/__init__.py +31 -0
  21. alma/extraction/auto_learner.py +264 -0
  22. alma/extraction/extractor.py +420 -0
  23. alma/graph/__init__.py +81 -0
  24. alma/graph/backends/__init__.py +18 -0
  25. alma/graph/backends/memory.py +236 -0
  26. alma/graph/backends/neo4j.py +417 -0
  27. alma/graph/base.py +159 -0
  28. alma/graph/extraction.py +198 -0
  29. alma/graph/store.py +860 -0
  30. alma/harness/__init__.py +4 -4
  31. alma/harness/base.py +18 -9
  32. alma/harness/domains.py +27 -11
  33. alma/initializer/__init__.py +1 -1
  34. alma/initializer/initializer.py +51 -43
  35. alma/initializer/types.py +25 -17
  36. alma/integration/__init__.py +9 -9
  37. alma/integration/claude_agents.py +10 -10
  38. alma/integration/helena.py +32 -22
  39. alma/integration/victor.py +57 -33
  40. alma/learning/__init__.py +27 -27
  41. alma/learning/forgetting.py +198 -148
  42. alma/learning/heuristic_extractor.py +40 -24
  43. alma/learning/protocols.py +62 -14
  44. alma/learning/validation.py +7 -2
  45. alma/mcp/__init__.py +4 -4
  46. alma/mcp/__main__.py +2 -1
  47. alma/mcp/resources.py +17 -16
  48. alma/mcp/server.py +102 -44
  49. alma/mcp/tools.py +174 -37
  50. alma/progress/__init__.py +3 -3
  51. alma/progress/tracker.py +26 -20
  52. alma/progress/types.py +8 -12
  53. alma/py.typed +0 -0
  54. alma/retrieval/__init__.py +11 -11
  55. alma/retrieval/cache.py +20 -21
  56. alma/retrieval/embeddings.py +4 -4
  57. alma/retrieval/engine.py +114 -35
  58. alma/retrieval/scoring.py +73 -63
  59. alma/session/__init__.py +2 -2
  60. alma/session/manager.py +5 -5
  61. alma/session/types.py +5 -4
  62. alma/storage/__init__.py +41 -0
  63. alma/storage/azure_cosmos.py +101 -31
  64. alma/storage/base.py +157 -4
  65. alma/storage/chroma.py +1443 -0
  66. alma/storage/file_based.py +56 -20
  67. alma/storage/pinecone.py +1080 -0
  68. alma/storage/postgresql.py +1452 -0
  69. alma/storage/qdrant.py +1306 -0
  70. alma/storage/sqlite_local.py +376 -31
  71. alma/types.py +62 -14
  72. alma_memory-0.5.0.dist-info/METADATA +905 -0
  73. alma_memory-0.5.0.dist-info/RECORD +76 -0
  74. {alma_memory-0.4.0.dist-info → alma_memory-0.5.0.dist-info}/WHEEL +1 -1
  75. alma_memory-0.4.0.dist-info/METADATA +0 -488
  76. alma_memory-0.4.0.dist-info/RECORD +0 -52
  77. {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
- MemorySchema,
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 MemorySlice, MemoryScope
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
- SEARCH = "search" # Web search, semantic search
32
- DATA_ACCESS = "data_access" # APIs, databases
33
- EXECUTION = "execution" # Code execution, automation
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" # Data processing, synthesis
36
- CREATION = "creation" # Content generation, design
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=", ".join(result.tools_used) if result.tools_used else "direct",
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 dataclasses import dataclass, field
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=["Use explicit waits, not sleep()", "Prefer role-based selectors"],
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=["Log all requests/responses", "Handle timeouts gracefully"],
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=["Note conflicting information", "Confidence levels required"],
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(a, kwargs.get("focus", "general")),
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(f"Unknown domain: {domain}. Available: {list(factories.keys())}")
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(
@@ -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",
@@ -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('\n')
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'^[-*•]\s+(.+)$', line)
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(WorkItem.create(
188
- project_id="", # Will be set by caller
189
- title=title,
190
- description=title,
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'^\d+\.\s+(.+)$', line)
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(WorkItem.create(
199
- project_id="",
200
- title=title,
201
- description=title,
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(WorkItem.create(
213
- project_id="",
214
- title=title,
215
- description=user_prompt,
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 = result.stdout.strip() or "HEAD detached"
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('\n')
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", "pyproject.toml", "setup.py", "Cargo.toml",
303
- "go.mod", "pom.xml", "build.gradle", "Makefile", "CMakeLists.txt",
304
- ]
305
- orientation.config_files = [
306
- f for f in config_files if (path / f).exists()
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, 'title', str(item)) for item in work_items]
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 for item in work_items
373
- if getattr(item, 'status', 'pending') == 'pending'
374
- and not getattr(item, 'blocked_by', [])
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 "pyproject.toml" in orientation.config_files or "setup.py" in orientation.config_files:
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, 'title', str(item))
173
- status = getattr(item, 'status', 'pending')
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, 'to_prompt'):
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(self.recommended_start, 'title', str(self.recommended_start))
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, 'to_dict') else str(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
- "current_branch": self.orientation.current_branch,
222
- "has_uncommitted_changes": self.orientation.has_uncommitted_changes,
223
- "recent_commits": self.orientation.recent_commits,
224
- "root_path": self.orientation.root_path,
225
- "key_directories": self.orientation.key_directories,
226
- "config_files": self.orientation.config_files,
227
- "summary": self.orientation.summary,
228
- } if self.orientation else None,
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, 'to_dict')
238
- else str(self.recommended_start) if self.recommended_start else None
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,
@@ -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
- HELENA_CATEGORIES,
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
- VICTOR_CATEGORIES,
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.user_preferences:
278
+ if memories.preferences:
276
279
  if include_section_headers:
277
280
  sections.append("\n### User Preferences:")
278
- for up in memories.user_preferences:
281
+ for up in memories.preferences:
279
282
  sections.append(f"- [{up.category}] {up.preference}")
280
283
 
281
284
  # Recent outcomes
282
- if memories.recent_outcomes:
285
+ if memories.outcomes:
283
286
  if include_section_headers:
284
287
  sections.append("\n### Recent Outcomes:")
285
- for o in memories.recent_outcomes[:3]: # Limit to 3 most recent
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(