alma-memory 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alma/__init__.py +75 -0
- alma/config/__init__.py +5 -0
- alma/config/loader.py +156 -0
- alma/core.py +322 -0
- alma/harness/__init__.py +35 -0
- alma/harness/base.py +377 -0
- alma/harness/domains.py +689 -0
- alma/integration/__init__.py +62 -0
- alma/integration/claude_agents.py +432 -0
- alma/integration/helena.py +413 -0
- alma/integration/victor.py +447 -0
- alma/learning/__init__.py +86 -0
- alma/learning/forgetting.py +1396 -0
- alma/learning/heuristic_extractor.py +374 -0
- alma/learning/protocols.py +326 -0
- alma/learning/validation.py +341 -0
- alma/mcp/__init__.py +45 -0
- alma/mcp/__main__.py +155 -0
- alma/mcp/resources.py +121 -0
- alma/mcp/server.py +533 -0
- alma/mcp/tools.py +374 -0
- alma/retrieval/__init__.py +53 -0
- alma/retrieval/cache.py +1062 -0
- alma/retrieval/embeddings.py +202 -0
- alma/retrieval/engine.py +287 -0
- alma/retrieval/scoring.py +334 -0
- alma/storage/__init__.py +20 -0
- alma/storage/azure_cosmos.py +972 -0
- alma/storage/base.py +372 -0
- alma/storage/file_based.py +583 -0
- alma/storage/sqlite_local.py +912 -0
- alma/types.py +216 -0
- alma_memory-0.2.0.dist-info/METADATA +327 -0
- alma_memory-0.2.0.dist-info/RECORD +36 -0
- alma_memory-0.2.0.dist-info/WHEEL +5 -0
- alma_memory-0.2.0.dist-info/top_level.txt +1 -0
alma/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ALMA - Agent Learning Memory Architecture
|
|
3
|
+
|
|
4
|
+
Persistent memory system for AI agents that learn and improve over time
|
|
5
|
+
through structured memory layers - without model weight updates.
|
|
6
|
+
|
|
7
|
+
The Harness Pattern:
|
|
8
|
+
1. Setting - Fixed environment (tools, constraints)
|
|
9
|
+
2. Context - Ephemeral per-run inputs
|
|
10
|
+
3. Agent - The executor with scoped intelligence
|
|
11
|
+
4. Memory Schema - Domain-specific learning structure
|
|
12
|
+
|
|
13
|
+
This makes any tool-using agent appear to "learn" by injecting relevant
|
|
14
|
+
memory slices before each run and updating memory after.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__version__ = "0.2.0"
|
|
18
|
+
|
|
19
|
+
# Core
|
|
20
|
+
from alma.core import ALMA
|
|
21
|
+
from alma.types import (
|
|
22
|
+
Heuristic,
|
|
23
|
+
Outcome,
|
|
24
|
+
UserPreference,
|
|
25
|
+
DomainKnowledge,
|
|
26
|
+
AntiPattern,
|
|
27
|
+
MemorySlice,
|
|
28
|
+
MemoryScope,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Harness Pattern
|
|
32
|
+
from alma.harness.base import (
|
|
33
|
+
Setting,
|
|
34
|
+
Context,
|
|
35
|
+
Agent,
|
|
36
|
+
MemorySchema,
|
|
37
|
+
Harness,
|
|
38
|
+
Tool,
|
|
39
|
+
ToolType,
|
|
40
|
+
RunResult,
|
|
41
|
+
)
|
|
42
|
+
from alma.harness.domains import (
|
|
43
|
+
CodingDomain,
|
|
44
|
+
ResearchDomain,
|
|
45
|
+
ContentDomain,
|
|
46
|
+
OperationsDomain,
|
|
47
|
+
create_harness,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# Core
|
|
52
|
+
"ALMA",
|
|
53
|
+
"Heuristic",
|
|
54
|
+
"Outcome",
|
|
55
|
+
"UserPreference",
|
|
56
|
+
"DomainKnowledge",
|
|
57
|
+
"AntiPattern",
|
|
58
|
+
"MemorySlice",
|
|
59
|
+
"MemoryScope",
|
|
60
|
+
# Harness Pattern
|
|
61
|
+
"Setting",
|
|
62
|
+
"Context",
|
|
63
|
+
"Agent",
|
|
64
|
+
"MemorySchema",
|
|
65
|
+
"Harness",
|
|
66
|
+
"Tool",
|
|
67
|
+
"ToolType",
|
|
68
|
+
"RunResult",
|
|
69
|
+
# Domain Configurations
|
|
70
|
+
"CodingDomain",
|
|
71
|
+
"ResearchDomain",
|
|
72
|
+
"ContentDomain",
|
|
73
|
+
"OperationsDomain",
|
|
74
|
+
"create_harness",
|
|
75
|
+
]
|
alma/config/__init__.py
ADDED
alma/config/loader.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ALMA Configuration Loader.
|
|
3
|
+
|
|
4
|
+
Handles loading configuration from files and environment variables,
|
|
5
|
+
with support for Azure Key Vault secret resolution.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import logging
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConfigLoader:
|
|
19
|
+
"""
|
|
20
|
+
Loads ALMA configuration from YAML files with environment variable expansion.
|
|
21
|
+
|
|
22
|
+
Supports:
|
|
23
|
+
- ${ENV_VAR} syntax for environment variables
|
|
24
|
+
- ${KEYVAULT:secret-name} syntax for Azure Key Vault (when configured)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_keyvault_client = None
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def load(cls, config_path: str) -> Dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Load configuration from YAML file.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config_path: Path to config.yaml
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Parsed and expanded configuration dict
|
|
39
|
+
"""
|
|
40
|
+
path = Path(config_path)
|
|
41
|
+
if not path.exists():
|
|
42
|
+
logger.warning(f"Config not found at {config_path}, using defaults")
|
|
43
|
+
return cls._get_defaults()
|
|
44
|
+
|
|
45
|
+
with open(path, "r") as f:
|
|
46
|
+
raw_config = yaml.safe_load(f)
|
|
47
|
+
|
|
48
|
+
# Get the 'alma' section or use whole file
|
|
49
|
+
config = raw_config.get("alma", raw_config)
|
|
50
|
+
|
|
51
|
+
# Expand environment variables and secrets
|
|
52
|
+
config = cls._expand_config(config)
|
|
53
|
+
|
|
54
|
+
return config
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def _expand_config(cls, config: Any) -> Any:
|
|
58
|
+
"""Recursively expand environment variables and secrets in config."""
|
|
59
|
+
if isinstance(config, dict):
|
|
60
|
+
return {k: cls._expand_config(v) for k, v in config.items()}
|
|
61
|
+
elif isinstance(config, list):
|
|
62
|
+
return [cls._expand_config(item) for item in config]
|
|
63
|
+
elif isinstance(config, str):
|
|
64
|
+
return cls._expand_value(config)
|
|
65
|
+
return config
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def _expand_value(cls, value: str) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Expand a single config value.
|
|
71
|
+
|
|
72
|
+
Handles:
|
|
73
|
+
- ${ENV_VAR} -> os.environ["ENV_VAR"]
|
|
74
|
+
- ${KEYVAULT:secret-name} -> Azure Key Vault lookup
|
|
75
|
+
"""
|
|
76
|
+
if not isinstance(value, str) or "${" not in value:
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
# Handle ${VAR} patterns
|
|
80
|
+
import re
|
|
81
|
+
pattern = r"\$\{([^}]+)\}"
|
|
82
|
+
|
|
83
|
+
def replace(match):
|
|
84
|
+
ref = match.group(1)
|
|
85
|
+
|
|
86
|
+
if ref.startswith("KEYVAULT:"):
|
|
87
|
+
secret_name = ref[9:] # Remove "KEYVAULT:" prefix
|
|
88
|
+
return cls._get_keyvault_secret(secret_name)
|
|
89
|
+
else:
|
|
90
|
+
# Environment variable
|
|
91
|
+
env_value = os.environ.get(ref)
|
|
92
|
+
if env_value is None:
|
|
93
|
+
logger.warning(f"Environment variable {ref} not set")
|
|
94
|
+
return match.group(0) # Keep original if not found
|
|
95
|
+
return env_value
|
|
96
|
+
|
|
97
|
+
return re.sub(pattern, replace, value)
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def _get_keyvault_secret(cls, secret_name: str) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Retrieve secret from Azure Key Vault.
|
|
103
|
+
|
|
104
|
+
Requires AZURE_KEYVAULT_URL environment variable.
|
|
105
|
+
"""
|
|
106
|
+
if cls._keyvault_client is None:
|
|
107
|
+
vault_url = os.environ.get("AZURE_KEYVAULT_URL")
|
|
108
|
+
if not vault_url:
|
|
109
|
+
logger.error("AZURE_KEYVAULT_URL not set, cannot retrieve secrets")
|
|
110
|
+
return f"${{KEYVAULT:{secret_name}}}"
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
from azure.identity import DefaultAzureCredential
|
|
114
|
+
from azure.keyvault.secrets import SecretClient
|
|
115
|
+
|
|
116
|
+
credential = DefaultAzureCredential()
|
|
117
|
+
cls._keyvault_client = SecretClient(
|
|
118
|
+
vault_url=vault_url,
|
|
119
|
+
credential=credential,
|
|
120
|
+
)
|
|
121
|
+
except ImportError:
|
|
122
|
+
logger.error(
|
|
123
|
+
"azure-identity and azure-keyvault-secrets packages required "
|
|
124
|
+
"for Key Vault integration"
|
|
125
|
+
)
|
|
126
|
+
return f"${{KEYVAULT:{secret_name}}}"
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
secret = cls._keyvault_client.get_secret(secret_name)
|
|
130
|
+
return secret.value
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(f"Failed to retrieve secret {secret_name}: {e}")
|
|
133
|
+
return f"${{KEYVAULT:{secret_name}}}"
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _get_defaults() -> Dict[str, Any]:
|
|
137
|
+
"""Return default configuration."""
|
|
138
|
+
return {
|
|
139
|
+
"project_id": "default",
|
|
140
|
+
"storage": "file",
|
|
141
|
+
"embedding_provider": "local",
|
|
142
|
+
"agents": {},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def save(cls, config: Dict[str, Any], config_path: str):
|
|
147
|
+
"""
|
|
148
|
+
Save configuration to YAML file.
|
|
149
|
+
|
|
150
|
+
Note: Does NOT save secrets - those should remain as ${} references.
|
|
151
|
+
"""
|
|
152
|
+
path = Path(config_path)
|
|
153
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
|
|
155
|
+
with open(path, "w") as f:
|
|
156
|
+
yaml.dump({"alma": config}, f, default_flow_style=False)
|
alma/core.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ALMA Core - Main interface for the Agent Learning Memory Architecture.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Dict, Any, List
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import yaml
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from alma.types import (
|
|
11
|
+
MemorySlice,
|
|
12
|
+
MemoryScope,
|
|
13
|
+
Heuristic,
|
|
14
|
+
Outcome,
|
|
15
|
+
UserPreference,
|
|
16
|
+
DomainKnowledge,
|
|
17
|
+
AntiPattern,
|
|
18
|
+
)
|
|
19
|
+
from alma.storage.base import StorageBackend
|
|
20
|
+
from alma.retrieval.engine import RetrievalEngine
|
|
21
|
+
from alma.learning.protocols import LearningProtocol
|
|
22
|
+
from alma.config.loader import ConfigLoader
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ALMA:
|
|
28
|
+
"""
|
|
29
|
+
Agent Learning Memory Architecture - Main Interface.
|
|
30
|
+
|
|
31
|
+
Provides methods for:
|
|
32
|
+
- Retrieving relevant memories for a task
|
|
33
|
+
- Learning from task outcomes
|
|
34
|
+
- Managing agent memory scopes
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
storage: StorageBackend,
|
|
40
|
+
retrieval_engine: RetrievalEngine,
|
|
41
|
+
learning_protocol: LearningProtocol,
|
|
42
|
+
scopes: Dict[str, MemoryScope],
|
|
43
|
+
project_id: str,
|
|
44
|
+
):
|
|
45
|
+
self.storage = storage
|
|
46
|
+
self.retrieval = retrieval_engine
|
|
47
|
+
self.learning = learning_protocol
|
|
48
|
+
self.scopes = scopes
|
|
49
|
+
self.project_id = project_id
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_config(cls, config_path: str) -> "ALMA":
|
|
53
|
+
"""
|
|
54
|
+
Initialize ALMA from a configuration file.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
config_path: Path to .alma/config.yaml
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Configured ALMA instance
|
|
61
|
+
"""
|
|
62
|
+
config = ConfigLoader.load(config_path)
|
|
63
|
+
|
|
64
|
+
# Initialize storage backend based on config
|
|
65
|
+
storage = cls._create_storage(config)
|
|
66
|
+
|
|
67
|
+
# Initialize retrieval engine
|
|
68
|
+
retrieval = RetrievalEngine(
|
|
69
|
+
storage=storage,
|
|
70
|
+
embedding_provider=config.get("embedding_provider", "local"),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Initialize learning protocol
|
|
74
|
+
learning = LearningProtocol(
|
|
75
|
+
storage=storage,
|
|
76
|
+
scopes={
|
|
77
|
+
name: MemoryScope(
|
|
78
|
+
agent_name=name,
|
|
79
|
+
can_learn=scope.get("can_learn", []),
|
|
80
|
+
cannot_learn=scope.get("cannot_learn", []),
|
|
81
|
+
min_occurrences_for_heuristic=scope.get(
|
|
82
|
+
"min_occurrences_for_heuristic", 3
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
for name, scope in config.get("agents", {}).items()
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Build scopes dict
|
|
90
|
+
scopes = {
|
|
91
|
+
name: MemoryScope(
|
|
92
|
+
agent_name=name,
|
|
93
|
+
can_learn=scope.get("can_learn", []),
|
|
94
|
+
cannot_learn=scope.get("cannot_learn", []),
|
|
95
|
+
min_occurrences_for_heuristic=scope.get(
|
|
96
|
+
"min_occurrences_for_heuristic", 3
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
for name, scope in config.get("agents", {}).items()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return cls(
|
|
103
|
+
storage=storage,
|
|
104
|
+
retrieval_engine=retrieval,
|
|
105
|
+
learning_protocol=learning,
|
|
106
|
+
scopes=scopes,
|
|
107
|
+
project_id=config.get("project_id", "default"),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _create_storage(config: Dict[str, Any]) -> StorageBackend:
|
|
112
|
+
"""Create appropriate storage backend based on config."""
|
|
113
|
+
storage_type = config.get("storage", "file")
|
|
114
|
+
|
|
115
|
+
if storage_type == "azure":
|
|
116
|
+
from alma.storage.azure_cosmos import AzureCosmosStorage
|
|
117
|
+
return AzureCosmosStorage.from_config(config)
|
|
118
|
+
elif storage_type == "sqlite":
|
|
119
|
+
from alma.storage.sqlite_local import SQLiteStorage
|
|
120
|
+
return SQLiteStorage.from_config(config)
|
|
121
|
+
else:
|
|
122
|
+
from alma.storage.file_based import FileBasedStorage
|
|
123
|
+
return FileBasedStorage.from_config(config)
|
|
124
|
+
|
|
125
|
+
def retrieve(
|
|
126
|
+
self,
|
|
127
|
+
task: str,
|
|
128
|
+
agent: str,
|
|
129
|
+
user_id: Optional[str] = None,
|
|
130
|
+
top_k: int = 5,
|
|
131
|
+
) -> MemorySlice:
|
|
132
|
+
"""
|
|
133
|
+
Retrieve relevant memories for a task.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
task: Description of the task to perform
|
|
137
|
+
agent: Name of the agent requesting memories
|
|
138
|
+
user_id: Optional user ID for preference retrieval
|
|
139
|
+
top_k: Maximum items per memory type
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
MemorySlice with relevant memories for context injection
|
|
143
|
+
"""
|
|
144
|
+
# Validate agent has a defined scope
|
|
145
|
+
if agent not in self.scopes:
|
|
146
|
+
logger.warning(f"Agent '{agent}' has no defined scope, using defaults")
|
|
147
|
+
|
|
148
|
+
return self.retrieval.retrieve(
|
|
149
|
+
query=task,
|
|
150
|
+
agent=agent,
|
|
151
|
+
project_id=self.project_id,
|
|
152
|
+
user_id=user_id,
|
|
153
|
+
top_k=top_k,
|
|
154
|
+
scope=self.scopes.get(agent),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def learn(
|
|
158
|
+
self,
|
|
159
|
+
agent: str,
|
|
160
|
+
task: str,
|
|
161
|
+
outcome: str, # "success" or "failure"
|
|
162
|
+
strategy_used: str,
|
|
163
|
+
task_type: Optional[str] = None,
|
|
164
|
+
duration_ms: Optional[int] = None,
|
|
165
|
+
error_message: Optional[str] = None,
|
|
166
|
+
feedback: Optional[str] = None,
|
|
167
|
+
) -> bool:
|
|
168
|
+
"""
|
|
169
|
+
Learn from a task outcome.
|
|
170
|
+
|
|
171
|
+
Validates that learning is within agent's scope before committing.
|
|
172
|
+
Invalidates cache after learning to ensure fresh retrieval results.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
agent: Name of the agent that executed the task
|
|
176
|
+
task: Description of the task
|
|
177
|
+
outcome: "success" or "failure"
|
|
178
|
+
strategy_used: What approach was taken
|
|
179
|
+
task_type: Category of task (for grouping)
|
|
180
|
+
duration_ms: How long the task took
|
|
181
|
+
error_message: Error details if failed
|
|
182
|
+
feedback: User feedback if provided
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if learning was accepted, False if rejected (scope violation)
|
|
186
|
+
"""
|
|
187
|
+
result = self.learning.learn(
|
|
188
|
+
agent=agent,
|
|
189
|
+
project_id=self.project_id,
|
|
190
|
+
task=task,
|
|
191
|
+
outcome=outcome == "success",
|
|
192
|
+
strategy_used=strategy_used,
|
|
193
|
+
task_type=task_type,
|
|
194
|
+
duration_ms=duration_ms,
|
|
195
|
+
error_message=error_message,
|
|
196
|
+
feedback=feedback,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Invalidate cache for this agent/project after learning
|
|
200
|
+
if result:
|
|
201
|
+
self.retrieval.invalidate_cache(agent=agent, project_id=self.project_id)
|
|
202
|
+
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
def add_user_preference(
|
|
206
|
+
self,
|
|
207
|
+
user_id: str,
|
|
208
|
+
category: str,
|
|
209
|
+
preference: str,
|
|
210
|
+
source: str = "explicit_instruction",
|
|
211
|
+
) -> UserPreference:
|
|
212
|
+
"""
|
|
213
|
+
Add a user preference to memory.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
user_id: User identifier
|
|
217
|
+
category: Category (communication, code_style, workflow)
|
|
218
|
+
preference: The preference text
|
|
219
|
+
source: How this was learned
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
The created UserPreference
|
|
223
|
+
"""
|
|
224
|
+
result = self.learning.add_preference(
|
|
225
|
+
user_id=user_id,
|
|
226
|
+
category=category,
|
|
227
|
+
preference=preference,
|
|
228
|
+
source=source,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Invalidate cache for project (user preferences affect all agents)
|
|
232
|
+
self.retrieval.invalidate_cache(project_id=self.project_id)
|
|
233
|
+
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
def add_domain_knowledge(
|
|
237
|
+
self,
|
|
238
|
+
agent: str,
|
|
239
|
+
domain: str,
|
|
240
|
+
fact: str,
|
|
241
|
+
source: str = "user_stated",
|
|
242
|
+
) -> Optional[DomainKnowledge]:
|
|
243
|
+
"""
|
|
244
|
+
Add domain knowledge within agent's scope.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
agent: Agent this knowledge belongs to
|
|
248
|
+
domain: Knowledge domain
|
|
249
|
+
fact: The fact to remember
|
|
250
|
+
source: How this was learned
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The created DomainKnowledge or None if scope violation
|
|
254
|
+
"""
|
|
255
|
+
# Check scope
|
|
256
|
+
scope = self.scopes.get(agent)
|
|
257
|
+
if scope and not scope.is_allowed(domain):
|
|
258
|
+
logger.warning(
|
|
259
|
+
f"Agent '{agent}' not allowed to learn in domain '{domain}'"
|
|
260
|
+
)
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
result = self.learning.add_domain_knowledge(
|
|
264
|
+
agent=agent,
|
|
265
|
+
project_id=self.project_id,
|
|
266
|
+
domain=domain,
|
|
267
|
+
fact=fact,
|
|
268
|
+
source=source,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Invalidate cache for this agent/project after adding knowledge
|
|
272
|
+
if result:
|
|
273
|
+
self.retrieval.invalidate_cache(agent=agent, project_id=self.project_id)
|
|
274
|
+
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
def forget(
|
|
278
|
+
self,
|
|
279
|
+
agent: Optional[str] = None,
|
|
280
|
+
older_than_days: int = 90,
|
|
281
|
+
below_confidence: float = 0.3,
|
|
282
|
+
) -> int:
|
|
283
|
+
"""
|
|
284
|
+
Prune stale or low-confidence memories.
|
|
285
|
+
|
|
286
|
+
Invalidates cache after pruning to ensure fresh retrieval results.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
agent: Specific agent to prune, or None for all
|
|
290
|
+
older_than_days: Remove outcomes older than this
|
|
291
|
+
below_confidence: Remove heuristics below this confidence
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Number of items pruned
|
|
295
|
+
"""
|
|
296
|
+
count = self.learning.forget(
|
|
297
|
+
project_id=self.project_id,
|
|
298
|
+
agent=agent,
|
|
299
|
+
older_than_days=older_than_days,
|
|
300
|
+
below_confidence=below_confidence,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Invalidate cache after forgetting (memories were removed)
|
|
304
|
+
if count > 0:
|
|
305
|
+
self.retrieval.invalidate_cache(agent=agent, project_id=self.project_id)
|
|
306
|
+
|
|
307
|
+
return count
|
|
308
|
+
|
|
309
|
+
def get_stats(self, agent: Optional[str] = None) -> Dict[str, Any]:
|
|
310
|
+
"""
|
|
311
|
+
Get memory statistics.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
agent: Specific agent or None for all
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Dict with counts and metadata
|
|
318
|
+
"""
|
|
319
|
+
return self.storage.get_stats(
|
|
320
|
+
project_id=self.project_id,
|
|
321
|
+
agent=agent,
|
|
322
|
+
)
|
alma/harness/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ALMA Harness Pattern.
|
|
3
|
+
|
|
4
|
+
A structured framework for creating learning agents across any domain:
|
|
5
|
+
- Setting: Environment, tools, constraints
|
|
6
|
+
- Context: Task-specific inputs per run
|
|
7
|
+
- Agent: The executor with scoped intelligence
|
|
8
|
+
- Memory Schema: Domain-specific persistent learning
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from alma.harness.base import (
|
|
12
|
+
Setting,
|
|
13
|
+
Context,
|
|
14
|
+
Agent,
|
|
15
|
+
MemorySchema,
|
|
16
|
+
Harness,
|
|
17
|
+
)
|
|
18
|
+
from alma.harness.domains import (
|
|
19
|
+
CodingDomain,
|
|
20
|
+
ResearchDomain,
|
|
21
|
+
ContentDomain,
|
|
22
|
+
OperationsDomain,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Setting",
|
|
27
|
+
"Context",
|
|
28
|
+
"Agent",
|
|
29
|
+
"MemorySchema",
|
|
30
|
+
"Harness",
|
|
31
|
+
"CodingDomain",
|
|
32
|
+
"ResearchDomain",
|
|
33
|
+
"ContentDomain",
|
|
34
|
+
"OperationsDomain",
|
|
35
|
+
]
|