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
alma/types.py CHANGED
@@ -1,264 +1,292 @@
1
- """
2
- ALMA Memory Types
3
-
4
- Defines the core data structures for all memory types.
5
- """
6
-
7
- from dataclasses import dataclass, field
8
- from datetime import datetime, timezone
9
- from enum import Enum
10
- from typing import Any, Dict, List, Optional
11
-
12
-
13
- class MemoryType(Enum):
14
- """Categories of memory that agents can store and retrieve."""
15
-
16
- HEURISTIC = "heuristic"
17
- OUTCOME = "outcome"
18
- USER_PREFERENCE = "user_preference"
19
- DOMAIN_KNOWLEDGE = "domain_knowledge"
20
- ANTI_PATTERN = "anti_pattern"
21
-
22
-
23
- @dataclass
24
- class MemoryScope:
25
- """
26
- Defines what an agent is allowed to learn and share.
27
-
28
- Prevents scope creep by explicitly listing allowed and forbidden domains.
29
- Supports multi-agent memory sharing through share_with and inherit_from.
30
- """
31
-
32
- agent_name: str
33
- can_learn: List[str]
34
- cannot_learn: List[str]
35
- share_with: List[str] = field(
36
- default_factory=list
37
- ) # Agents that can read this agent's memories
38
- inherit_from: List[str] = field(
39
- default_factory=list
40
- ) # Agents whose memories this agent can read
41
- min_occurrences_for_heuristic: int = 3
42
-
43
- def is_allowed(self, domain: str) -> bool:
44
- """Check if learning in this domain is permitted."""
45
- if domain in self.cannot_learn:
46
- return False
47
- if not self.can_learn: # Empty means all allowed (except cannot_learn)
48
- return True
49
- return domain in self.can_learn
50
-
51
- def get_readable_agents(self) -> List[str]:
52
- """
53
- Get list of agents whose memories this agent can read.
54
-
55
- Returns:
56
- List containing this agent's name plus all inherited agents.
57
- """
58
- return [self.agent_name] + list(self.inherit_from)
59
-
60
- def can_read_from(self, other_agent: str) -> bool:
61
- """
62
- Check if this agent can read memories from another agent.
63
-
64
- Args:
65
- other_agent: Name of the agent to check
66
-
67
- Returns:
68
- True if this agent can read from other_agent
69
- """
70
- return other_agent == self.agent_name or other_agent in self.inherit_from
71
-
72
- def shares_with(self, other_agent: str) -> bool:
73
- """
74
- Check if this agent shares memories with another agent.
75
-
76
- Args:
77
- other_agent: Name of the agent to check
78
-
79
- Returns:
80
- True if this agent shares with other_agent
81
- """
82
- return other_agent in self.share_with
83
-
84
-
85
- @dataclass
86
- class Heuristic:
87
- """
88
- A learned rule: "When condition X, strategy Y works N% of the time."
89
-
90
- Heuristics are only created after min_occurrences validations.
91
- """
92
-
93
- id: str
94
- agent: str
95
- project_id: str
96
- condition: str # "form with multiple required fields"
97
- strategy: str # "test happy path first, then individual validation"
98
- confidence: float # 0.0 to 1.0
99
- occurrence_count: int
100
- success_count: int
101
- last_validated: datetime
102
- created_at: datetime
103
- embedding: Optional[List[float]] = None
104
- metadata: Dict[str, Any] = field(default_factory=dict)
105
-
106
- @property
107
- def success_rate(self) -> float:
108
- """Calculate success rate from occurrences."""
109
- if self.occurrence_count == 0:
110
- return 0.0
111
- return self.success_count / self.occurrence_count
112
-
113
-
114
- @dataclass
115
- class Outcome:
116
- """
117
- Record of a task execution - success or failure with context.
118
-
119
- Outcomes are raw data that can be consolidated into heuristics.
120
- """
121
-
122
- id: str
123
- agent: str
124
- project_id: str
125
- task_type: str # "api_validation", "form_testing", etc.
126
- task_description: str
127
- success: bool
128
- strategy_used: str
129
- duration_ms: Optional[int] = None
130
- error_message: Optional[str] = None
131
- user_feedback: Optional[str] = None
132
- timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
133
- embedding: Optional[List[float]] = None
134
- metadata: Dict[str, Any] = field(default_factory=dict)
135
-
136
-
137
- @dataclass
138
- class UserPreference:
139
- """
140
- A remembered user constraint or communication preference.
141
-
142
- Persists across sessions so users don't repeat themselves.
143
- """
144
-
145
- id: str
146
- user_id: str
147
- category: str # "communication", "code_style", "workflow"
148
- preference: str # "No emojis in documentation"
149
- source: str # "explicit_instruction", "inferred_from_correction"
150
- confidence: float = 1.0 # Lower for inferred preferences
151
- timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
152
- metadata: Dict[str, Any] = field(default_factory=dict)
153
-
154
-
155
- @dataclass
156
- class DomainKnowledge:
157
- """
158
- Accumulated domain-specific facts within agent's scope.
159
-
160
- Different from heuristics - these are facts, not strategies.
161
- """
162
-
163
- id: str
164
- agent: str
165
- project_id: str
166
- domain: str # "authentication", "database_schema", etc.
167
- fact: str # "Login endpoint uses JWT with 24h expiry"
168
- source: str # "code_analysis", "documentation", "user_stated"
169
- confidence: float = 1.0
170
- last_verified: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
171
- embedding: Optional[List[float]] = None
172
- metadata: Dict[str, Any] = field(default_factory=dict)
173
-
174
-
175
- @dataclass
176
- class AntiPattern:
177
- """
178
- What NOT to do - learned from validated failures.
179
-
180
- Helps agents avoid repeating mistakes.
181
- """
182
-
183
- id: str
184
- agent: str
185
- project_id: str
186
- pattern: str # "Using fixed sleep() for async waits"
187
- why_bad: str # "Causes flaky tests, doesn't adapt to load"
188
- better_alternative: str # "Use explicit waits with conditions"
189
- occurrence_count: int
190
- last_seen: datetime
191
- created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
192
- embedding: Optional[List[float]] = None
193
- metadata: Dict[str, Any] = field(default_factory=dict)
194
-
195
-
196
- @dataclass
197
- class MemorySlice:
198
- """
199
- A compact, relevant subset of memories for injection into context.
200
-
201
- This is what gets injected per-call - must stay under token budget.
202
- """
203
-
204
- heuristics: List[Heuristic] = field(default_factory=list)
205
- outcomes: List[Outcome] = field(default_factory=list)
206
- preferences: List[UserPreference] = field(default_factory=list)
207
- domain_knowledge: List[DomainKnowledge] = field(default_factory=list)
208
- anti_patterns: List[AntiPattern] = field(default_factory=list)
209
-
210
- # Retrieval metadata
211
- query: Optional[str] = None
212
- agent: Optional[str] = None
213
- retrieval_time_ms: Optional[int] = None
214
-
215
- def to_prompt(self, max_tokens: int = 2000) -> str:
216
- """
217
- Format memories for injection into agent context.
218
-
219
- Respects token budget by prioritizing high-confidence items.
220
- """
221
- sections = []
222
-
223
- if self.heuristics:
224
- h_text = "## Relevant Strategies\n"
225
- for h in sorted(self.heuristics, key=lambda x: -x.confidence)[:5]:
226
- h_text += f"- When: {h.condition}\n Do: {h.strategy} (confidence: {h.confidence:.0%})\n"
227
- sections.append(h_text)
228
-
229
- if self.anti_patterns:
230
- ap_text = "## Avoid These Patterns\n"
231
- for ap in self.anti_patterns[:3]:
232
- ap_text += f"- Don't: {ap.pattern}\n Why: {ap.why_bad}\n Instead: {ap.better_alternative}\n"
233
- sections.append(ap_text)
234
-
235
- if self.preferences:
236
- p_text = "## User Preferences\n"
237
- for p in self.preferences[:5]:
238
- p_text += f"- {p.preference}\n"
239
- sections.append(p_text)
240
-
241
- if self.domain_knowledge:
242
- dk_text = "## Domain Context\n"
243
- for dk in self.domain_knowledge[:5]:
244
- dk_text += f"- {dk.fact}\n"
245
- sections.append(dk_text)
246
-
247
- result = "\n".join(sections)
248
-
249
- # Basic token estimation (rough: 1 token ~ 4 chars)
250
- if len(result) > max_tokens * 4:
251
- result = result[: max_tokens * 4] + "\n[truncated]"
252
-
253
- return result
254
-
255
- @property
256
- def total_items(self) -> int:
257
- """Total number of memory items in this slice."""
258
- return (
259
- len(self.heuristics)
260
- + len(self.outcomes)
261
- + len(self.preferences)
262
- + len(self.domain_knowledge)
263
- + len(self.anti_patterns)
264
- )
1
+ """
2
+ ALMA Memory Types
3
+
4
+ Defines the core data structures for all memory types.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime, timezone
9
+ from enum import Enum
10
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
11
+
12
+ if TYPE_CHECKING:
13
+ from alma.workflow.outcomes import WorkflowOutcome
14
+
15
+
16
+ class MemoryType(Enum):
17
+ """Categories of memory that agents can store and retrieve."""
18
+
19
+ HEURISTIC = "heuristic"
20
+ OUTCOME = "outcome"
21
+ USER_PREFERENCE = "user_preference"
22
+ DOMAIN_KNOWLEDGE = "domain_knowledge"
23
+ ANTI_PATTERN = "anti_pattern"
24
+
25
+
26
+ @dataclass
27
+ class MemoryScope:
28
+ """
29
+ Defines what an agent is allowed to learn and share.
30
+
31
+ Prevents scope creep by explicitly listing allowed and forbidden domains.
32
+ Supports multi-agent memory sharing through share_with and inherit_from.
33
+ """
34
+
35
+ agent_name: str
36
+ can_learn: List[str]
37
+ cannot_learn: List[str]
38
+ share_with: List[str] = field(
39
+ default_factory=list
40
+ ) # Agents that can read this agent's memories
41
+ inherit_from: List[str] = field(
42
+ default_factory=list
43
+ ) # Agents whose memories this agent can read
44
+ min_occurrences_for_heuristic: int = 3
45
+
46
+ def is_allowed(self, domain: str) -> bool:
47
+ """Check if learning in this domain is permitted."""
48
+ if domain in self.cannot_learn:
49
+ return False
50
+ if not self.can_learn: # Empty means all allowed (except cannot_learn)
51
+ return True
52
+ return domain in self.can_learn
53
+
54
+ def get_readable_agents(self) -> List[str]:
55
+ """
56
+ Get list of agents whose memories this agent can read.
57
+
58
+ Returns:
59
+ List containing this agent's name plus all inherited agents.
60
+ """
61
+ return [self.agent_name] + list(self.inherit_from)
62
+
63
+ def can_read_from(self, other_agent: str) -> bool:
64
+ """
65
+ Check if this agent can read memories from another agent.
66
+
67
+ Args:
68
+ other_agent: Name of the agent to check
69
+
70
+ Returns:
71
+ True if this agent can read from other_agent
72
+ """
73
+ return other_agent == self.agent_name or other_agent in self.inherit_from
74
+
75
+ def shares_with(self, other_agent: str) -> bool:
76
+ """
77
+ Check if this agent shares memories with another agent.
78
+
79
+ Args:
80
+ other_agent: Name of the agent to check
81
+
82
+ Returns:
83
+ True if this agent shares with other_agent
84
+ """
85
+ return other_agent in self.share_with
86
+
87
+
88
+ @dataclass
89
+ class Heuristic:
90
+ """
91
+ A learned rule: "When condition X, strategy Y works N% of the time."
92
+
93
+ Heuristics are only created after min_occurrences validations.
94
+ """
95
+
96
+ id: str
97
+ agent: str
98
+ project_id: str
99
+ condition: str # "form with multiple required fields"
100
+ strategy: str # "test happy path first, then individual validation"
101
+ confidence: float # 0.0 to 1.0
102
+ occurrence_count: int
103
+ success_count: int
104
+ last_validated: datetime
105
+ created_at: datetime
106
+ embedding: Optional[List[float]] = None
107
+ metadata: Dict[str, Any] = field(default_factory=dict)
108
+
109
+ @property
110
+ def success_rate(self) -> float:
111
+ """Calculate success rate from occurrences."""
112
+ if self.occurrence_count == 0:
113
+ return 0.0
114
+ return self.success_count / self.occurrence_count
115
+
116
+
117
+ @dataclass
118
+ class Outcome:
119
+ """
120
+ Record of a task execution - success or failure with context.
121
+
122
+ Outcomes are raw data that can be consolidated into heuristics.
123
+ """
124
+
125
+ id: str
126
+ agent: str
127
+ project_id: str
128
+ task_type: str # "api_validation", "form_testing", etc.
129
+ task_description: str
130
+ success: bool
131
+ strategy_used: str
132
+ duration_ms: Optional[int] = None
133
+ error_message: Optional[str] = None
134
+ user_feedback: Optional[str] = None
135
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
136
+ embedding: Optional[List[float]] = None
137
+ metadata: Dict[str, Any] = field(default_factory=dict)
138
+
139
+
140
+ @dataclass
141
+ class UserPreference:
142
+ """
143
+ A remembered user constraint or communication preference.
144
+
145
+ Persists across sessions so users don't repeat themselves.
146
+ """
147
+
148
+ id: str
149
+ user_id: str
150
+ category: str # "communication", "code_style", "workflow"
151
+ preference: str # "No emojis in documentation"
152
+ source: str # "explicit_instruction", "inferred_from_correction"
153
+ confidence: float = 1.0 # Lower for inferred preferences
154
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
155
+ metadata: Dict[str, Any] = field(default_factory=dict)
156
+
157
+
158
+ @dataclass
159
+ class DomainKnowledge:
160
+ """
161
+ Accumulated domain-specific facts within agent's scope.
162
+
163
+ Different from heuristics - these are facts, not strategies.
164
+ """
165
+
166
+ id: str
167
+ agent: str
168
+ project_id: str
169
+ domain: str # "authentication", "database_schema", etc.
170
+ fact: str # "Login endpoint uses JWT with 24h expiry"
171
+ source: str # "code_analysis", "documentation", "user_stated"
172
+ confidence: float = 1.0
173
+ last_verified: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
174
+ embedding: Optional[List[float]] = None
175
+ metadata: Dict[str, Any] = field(default_factory=dict)
176
+
177
+
178
+ @dataclass
179
+ class AntiPattern:
180
+ """
181
+ What NOT to do - learned from validated failures.
182
+
183
+ Helps agents avoid repeating mistakes.
184
+ """
185
+
186
+ id: str
187
+ agent: str
188
+ project_id: str
189
+ pattern: str # "Using fixed sleep() for async waits"
190
+ why_bad: str # "Causes flaky tests, doesn't adapt to load"
191
+ better_alternative: str # "Use explicit waits with conditions"
192
+ occurrence_count: int
193
+ last_seen: datetime
194
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
195
+ embedding: Optional[List[float]] = None
196
+ metadata: Dict[str, Any] = field(default_factory=dict)
197
+
198
+
199
+ @dataclass
200
+ class MemorySlice:
201
+ """
202
+ A compact, relevant subset of memories for injection into context.
203
+
204
+ This is what gets injected per-call - must stay under token budget.
205
+ """
206
+
207
+ heuristics: List[Heuristic] = field(default_factory=list)
208
+ outcomes: List[Outcome] = field(default_factory=list)
209
+ preferences: List[UserPreference] = field(default_factory=list)
210
+ domain_knowledge: List[DomainKnowledge] = field(default_factory=list)
211
+ anti_patterns: List[AntiPattern] = field(default_factory=list)
212
+
213
+ # Workflow outcomes (v0.6.0+)
214
+ workflow_outcomes: List["WorkflowOutcome"] = field(default_factory=list)
215
+
216
+ # Retrieval metadata
217
+ query: Optional[str] = None
218
+ agent: Optional[str] = None
219
+ retrieval_time_ms: Optional[int] = None
220
+
221
+ def to_prompt(
222
+ self,
223
+ max_tokens: int = 2000,
224
+ model: Optional[str] = None,
225
+ ) -> str:
226
+ """
227
+ Format memories for injection into agent context.
228
+
229
+ Respects token budget by prioritizing high-confidence items.
230
+ Uses accurate token counting via tiktoken when available.
231
+
232
+ Args:
233
+ max_tokens: Maximum tokens allowed for the output
234
+ model: Optional model name for accurate tokenization
235
+ (e.g., "gpt-4", "claude-3-sonnet"). If not provided,
236
+ uses a general-purpose estimation.
237
+
238
+ Returns:
239
+ Formatted prompt string, truncated if necessary
240
+ """
241
+ from alma.utils.tokenizer import TokenEstimator
242
+
243
+ # Initialize token estimator
244
+ estimator = TokenEstimator(model=model) if model else TokenEstimator()
245
+
246
+ sections = []
247
+
248
+ if self.heuristics:
249
+ h_text = "## Relevant Strategies\n"
250
+ for h in sorted(self.heuristics, key=lambda x: -x.confidence)[:5]:
251
+ h_text += f"- When: {h.condition}\n Do: {h.strategy} (confidence: {h.confidence:.0%})\n"
252
+ sections.append(h_text)
253
+
254
+ if self.anti_patterns:
255
+ ap_text = "## Avoid These Patterns\n"
256
+ for ap in self.anti_patterns[:3]:
257
+ ap_text += f"- Don't: {ap.pattern}\n Why: {ap.why_bad}\n Instead: {ap.better_alternative}\n"
258
+ sections.append(ap_text)
259
+
260
+ if self.preferences:
261
+ p_text = "## User Preferences\n"
262
+ for p in self.preferences[:5]:
263
+ p_text += f"- {p.preference}\n"
264
+ sections.append(p_text)
265
+
266
+ if self.domain_knowledge:
267
+ dk_text = "## Domain Context\n"
268
+ for dk in self.domain_knowledge[:5]:
269
+ dk_text += f"- {dk.fact}\n"
270
+ sections.append(dk_text)
271
+
272
+ result = "\n".join(sections)
273
+
274
+ # Use accurate token estimation and truncation
275
+ result = estimator.truncate_to_token_limit(
276
+ text=result,
277
+ max_tokens=max_tokens,
278
+ suffix="\n[truncated]",
279
+ )
280
+
281
+ return result
282
+
283
+ @property
284
+ def total_items(self) -> int:
285
+ """Total number of memory items in this slice."""
286
+ return (
287
+ len(self.heuristics)
288
+ + len(self.outcomes)
289
+ + len(self.preferences)
290
+ + len(self.domain_knowledge)
291
+ + len(self.anti_patterns)
292
+ )
alma/utils/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """
2
+ ALMA Utility modules.
3
+
4
+ Provides shared utilities for token estimation and other common functionality.
5
+ """
6
+
7
+ from alma.utils.tokenizer import (
8
+ ModelTokenBudget,
9
+ TokenEstimator,
10
+ get_default_token_budget,
11
+ get_token_estimator,
12
+ )
13
+
14
+ __all__ = [
15
+ "TokenEstimator",
16
+ "ModelTokenBudget",
17
+ "get_token_estimator",
18
+ "get_default_token_budget",
19
+ ]