mcal-ai-crewai 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.
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcal-ai-crewai
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CrewAI integration for MCAL - Goal-aware memory for AI agent crews
|
|
5
|
+
Project-URL: Homepage, https://github.com/Shivakoreddi/mcal-ai
|
|
6
|
+
Project-URL: Documentation, https://github.com/Shivakoreddi/mcal-ai/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/Shivakoreddi/mcal-ai
|
|
8
|
+
Author: MCAL Team
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,crewai,goal-tracking,mcal,memory
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: crewai>=0.100.0
|
|
22
|
+
Requires-Dist: mcal-ai>=0.1.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# mcal-crewai
|
|
30
|
+
|
|
31
|
+
Goal-aware memory integration for CrewAI agent crews.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install mcal-crewai
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Using MCALStorage (Mem0-style)
|
|
42
|
+
|
|
43
|
+
MCAL provides a storage backend that integrates directly with CrewAI's memory system:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from crewai import Crew, Agent, Task, Process
|
|
47
|
+
from crewai.memory.short_term.short_term_memory import ShortTermMemory
|
|
48
|
+
from crewai.memory.long_term.long_term_memory import LongTermMemory
|
|
49
|
+
from crewai.memory.entity.entity_memory import EntityMemory
|
|
50
|
+
from mcal_crewai import MCALStorage
|
|
51
|
+
|
|
52
|
+
# Create MCAL-backed memories
|
|
53
|
+
short_term = ShortTermMemory(
|
|
54
|
+
storage=MCALStorage(type="short_term", user_id="john")
|
|
55
|
+
)
|
|
56
|
+
long_term = LongTermMemory(
|
|
57
|
+
storage=MCALStorage(type="long_term", user_id="john")
|
|
58
|
+
)
|
|
59
|
+
entity_memory = EntityMemory(
|
|
60
|
+
storage=MCALStorage(type="entities", user_id="john")
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Use with CrewAI
|
|
64
|
+
crew = Crew(
|
|
65
|
+
agents=[agent],
|
|
66
|
+
tasks=[task],
|
|
67
|
+
memory=True,
|
|
68
|
+
short_term_memory=short_term,
|
|
69
|
+
long_term_memory=long_term,
|
|
70
|
+
entity_memory=entity_memory,
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Using External Memory
|
|
75
|
+
|
|
76
|
+
For cross-session persistence with goal awareness:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from crewai.memory.external.external_memory import ExternalMemory
|
|
80
|
+
from mcal_crewai import MCALStorage
|
|
81
|
+
|
|
82
|
+
external = ExternalMemory(
|
|
83
|
+
embedder_config={
|
|
84
|
+
"provider": "mcal",
|
|
85
|
+
"config": {
|
|
86
|
+
"user_id": "john",
|
|
87
|
+
"llm_provider": "anthropic",
|
|
88
|
+
"enable_goal_tracking": True,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
crew = Crew(
|
|
94
|
+
agents=[...],
|
|
95
|
+
tasks=[...],
|
|
96
|
+
external_memory=external,
|
|
97
|
+
process=Process.sequential,
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Features
|
|
102
|
+
|
|
103
|
+
### Goal-Aware Memory
|
|
104
|
+
Unlike basic memory systems, MCAL tracks user goals and priorities:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
storage = MCALStorage(
|
|
108
|
+
type="long_term",
|
|
109
|
+
user_id="project_manager",
|
|
110
|
+
config={
|
|
111
|
+
"enable_goal_tracking": True,
|
|
112
|
+
"extract_priorities": True,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Context Preservation
|
|
118
|
+
MCAL maintains reasoning context across agent handoffs:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
# Agent 1 saves with context
|
|
122
|
+
await storage.save(
|
|
123
|
+
"Research findings on market trends",
|
|
124
|
+
metadata={
|
|
125
|
+
"agent": "researcher",
|
|
126
|
+
"goal": "market_analysis",
|
|
127
|
+
"confidence": 0.95
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Agent 2 retrieves with goal awareness
|
|
132
|
+
results = await storage.search(
|
|
133
|
+
"What do we know about market trends?",
|
|
134
|
+
limit=5,
|
|
135
|
+
score_threshold=0.7
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### TTL Support
|
|
140
|
+
Automatic expiration for short-term memories:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
storage = MCALStorage(
|
|
144
|
+
type="short_term",
|
|
145
|
+
user_id="session_user",
|
|
146
|
+
default_ttl=3600, # 1 hour
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
| Parameter | Type | Default | Description |
|
|
153
|
+
|-----------|------|---------|-------------|
|
|
154
|
+
| `type` | str | required | Memory type: "short_term", "long_term", "entities", "external" |
|
|
155
|
+
| `user_id` | str | "default" | User identifier for memory isolation |
|
|
156
|
+
| `llm_provider` | str | "anthropic" | LLM for goal extraction |
|
|
157
|
+
| `embedding_provider` | str | "openai" | Embedding model provider |
|
|
158
|
+
| `default_ttl` | int | None | Default TTL in seconds |
|
|
159
|
+
| `enable_goal_tracking` | bool | True | Enable goal extraction |
|
|
160
|
+
|
|
161
|
+
## API Reference
|
|
162
|
+
|
|
163
|
+
### MCALStorage
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
class MCALStorage(Storage):
|
|
167
|
+
"""MCAL storage backend for CrewAI memory."""
|
|
168
|
+
|
|
169
|
+
def save(self, value: Any, metadata: dict) -> None:
|
|
170
|
+
"""Save value with goal-aware processing."""
|
|
171
|
+
|
|
172
|
+
def search(
|
|
173
|
+
self,
|
|
174
|
+
query: str,
|
|
175
|
+
limit: int = 5,
|
|
176
|
+
score_threshold: float = 0.6
|
|
177
|
+
) -> list:
|
|
178
|
+
"""Search with goal-aware relevance."""
|
|
179
|
+
|
|
180
|
+
def reset(self) -> None:
|
|
181
|
+
"""Clear all stored memories."""
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Comparison with Mem0
|
|
185
|
+
|
|
186
|
+
| Feature | Mem0 | MCAL |
|
|
187
|
+
|---------|------|------|
|
|
188
|
+
| Basic Memory | ✓ | ✓ |
|
|
189
|
+
| Goal Tracking | ✗ | ✓ |
|
|
190
|
+
| Priority Extraction | ✗ | ✓ |
|
|
191
|
+
| Context Preservation | ✗ | ✓ |
|
|
192
|
+
| TTL Support | ✗ | ✓ |
|
|
193
|
+
| Local Storage | ✓ | ✓ |
|
|
194
|
+
| Cloud API | ✓ | Coming |
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT License
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
mcal_crewai/__init__.py,sha256=wCpTrgdQ745JHsCgFEUxAvuoH1d6wec9Ybrz26UE9IQ,465
|
|
2
|
+
mcal_crewai/storage.py,sha256=DHaAJKeJd4L7Id4JbpV8T91lsYNnS9DGocC4fV_LPdw,10791
|
|
3
|
+
mcal_ai_crewai-0.2.0.dist-info/METADATA,sha256=5kd-_oMQw36xDk_HKp_tf4elU7YlHrg4H1QgOVgrz7o,5026
|
|
4
|
+
mcal_ai_crewai-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
+
mcal_ai_crewai-0.2.0.dist-info/licenses/LICENSE,sha256=zdp5kxDzb-kYvBiEZ_h1Hi96z-o6e5oXoXFx2IIefCs,1062
|
|
6
|
+
mcal_ai_crewai-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shiva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mcal_crewai/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCAL CrewAI Integration
|
|
3
|
+
|
|
4
|
+
Goal-aware memory for CrewAI agent crews.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from mcal_crewai import MCALStorage
|
|
8
|
+
|
|
9
|
+
storage = MCALStorage(type="short_term", user_id="john")
|
|
10
|
+
|
|
11
|
+
# Use with CrewAI memory
|
|
12
|
+
from crewai.memory.short_term.short_term_memory import ShortTermMemory
|
|
13
|
+
memory = ShortTermMemory(storage=storage)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from mcal_crewai.storage import MCALStorage
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"MCALStorage",
|
|
22
|
+
"__version__",
|
|
23
|
+
]
|
mcal_crewai/storage.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCAL Storage Backend for CrewAI
|
|
3
|
+
|
|
4
|
+
Implements CrewAI's Storage interface with goal-aware memory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
# Lazy import for MCAL to avoid circular dependencies
|
|
15
|
+
_mcal_instance: Optional[Any] = None
|
|
16
|
+
_mcal_lock = threading.Lock()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_mcal(
|
|
20
|
+
llm_provider: str = "anthropic",
|
|
21
|
+
embedding_provider: str = "openai",
|
|
22
|
+
storage_path: Optional[str] = None,
|
|
23
|
+
**kwargs
|
|
24
|
+
) -> Any:
|
|
25
|
+
"""Get or create MCAL instance (lazy singleton)."""
|
|
26
|
+
global _mcal_instance
|
|
27
|
+
|
|
28
|
+
if _mcal_instance is None:
|
|
29
|
+
with _mcal_lock:
|
|
30
|
+
if _mcal_instance is None:
|
|
31
|
+
from mcal import MCAL
|
|
32
|
+
_mcal_instance = MCAL(
|
|
33
|
+
llm_provider=llm_provider,
|
|
34
|
+
embedding_provider=embedding_provider,
|
|
35
|
+
storage_path=storage_path,
|
|
36
|
+
**kwargs
|
|
37
|
+
)
|
|
38
|
+
return _mcal_instance
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MCALStorage:
|
|
42
|
+
"""
|
|
43
|
+
MCAL storage backend for CrewAI memory.
|
|
44
|
+
|
|
45
|
+
This class implements CrewAI's Storage interface, providing
|
|
46
|
+
goal-aware memory with context preservation for agent crews.
|
|
47
|
+
|
|
48
|
+
Compatible with:
|
|
49
|
+
- ShortTermMemory
|
|
50
|
+
- LongTermMemory
|
|
51
|
+
- EntityMemory
|
|
52
|
+
- ExternalMemory
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
from mcal_crewai import MCALStorage
|
|
56
|
+
from crewai.memory.short_term.short_term_memory import ShortTermMemory
|
|
57
|
+
|
|
58
|
+
storage = MCALStorage(type="short_term", user_id="john")
|
|
59
|
+
memory = ShortTermMemory(storage=storage)
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
type: Memory type - "short_term", "long_term", "entities", "external"
|
|
63
|
+
crew: Optional CrewAI Crew instance
|
|
64
|
+
config: Configuration dictionary with MCAL options
|
|
65
|
+
user_id: User identifier for memory isolation
|
|
66
|
+
default_ttl: Default TTL in seconds for memory items
|
|
67
|
+
enable_goal_tracking: Whether to extract goals from content
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
SUPPORTED_TYPES = {"short_term", "long_term", "entities", "external"}
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
type: str,
|
|
75
|
+
crew: Any = None,
|
|
76
|
+
config: Optional[dict] = None,
|
|
77
|
+
user_id: str = "default",
|
|
78
|
+
default_ttl: Optional[int] = None,
|
|
79
|
+
enable_goal_tracking: bool = True,
|
|
80
|
+
**kwargs
|
|
81
|
+
):
|
|
82
|
+
# Validate type
|
|
83
|
+
if type not in self.SUPPORTED_TYPES:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Invalid type '{type}'. Must be one of: {', '.join(self.SUPPORTED_TYPES)}"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.memory_type = type
|
|
89
|
+
self.crew = crew
|
|
90
|
+
self.config = config or {}
|
|
91
|
+
# user_id priority: explicit param > config > default
|
|
92
|
+
self.user_id = user_id if user_id != "default" else self.config.get("user_id", user_id)
|
|
93
|
+
self.default_ttl = default_ttl
|
|
94
|
+
self.enable_goal_tracking = enable_goal_tracking
|
|
95
|
+
|
|
96
|
+
# Extract config values
|
|
97
|
+
self.llm_provider = self.config.get("llm_provider", "anthropic")
|
|
98
|
+
self.embedding_provider = self.config.get("embedding_provider", "openai")
|
|
99
|
+
self.storage_path = self.config.get("storage_path")
|
|
100
|
+
|
|
101
|
+
# TTL tracking (lazy expiration)
|
|
102
|
+
self._ttl: dict[str, int] = {} # key -> ttl_seconds
|
|
103
|
+
self._expires_at: dict[str, float] = {} # key -> expiration_timestamp
|
|
104
|
+
|
|
105
|
+
# Thread safety
|
|
106
|
+
self._lock = threading.RLock()
|
|
107
|
+
|
|
108
|
+
# Internal storage (in-memory for now, will use MCAL graph later)
|
|
109
|
+
self._data: dict[str, dict[str, Any]] = {}
|
|
110
|
+
|
|
111
|
+
# MCAL instance (lazy loaded)
|
|
112
|
+
self._mcal: Optional[Any] = None
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def mcal(self) -> Any:
|
|
116
|
+
"""Lazy load MCAL instance."""
|
|
117
|
+
if self._mcal is None:
|
|
118
|
+
self._mcal = _get_mcal(
|
|
119
|
+
llm_provider=self.llm_provider,
|
|
120
|
+
embedding_provider=self.embedding_provider,
|
|
121
|
+
storage_path=self.storage_path,
|
|
122
|
+
)
|
|
123
|
+
return self._mcal
|
|
124
|
+
|
|
125
|
+
def _generate_key(self, value: Any, metadata: dict) -> str:
|
|
126
|
+
"""Generate a unique key for storage."""
|
|
127
|
+
import hashlib
|
|
128
|
+
content = str(value) + str(metadata)
|
|
129
|
+
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
130
|
+
|
|
131
|
+
def _is_expired(self, key: str) -> bool:
|
|
132
|
+
"""Check if a key has expired."""
|
|
133
|
+
if key not in self._expires_at:
|
|
134
|
+
return False
|
|
135
|
+
return time.time() > self._expires_at[key]
|
|
136
|
+
|
|
137
|
+
def _set_ttl(self, key: str, ttl: Optional[int] = None) -> None:
|
|
138
|
+
"""Set TTL for a key."""
|
|
139
|
+
ttl_value = ttl or self.default_ttl
|
|
140
|
+
if ttl_value is not None:
|
|
141
|
+
self._ttl[key] = ttl_value
|
|
142
|
+
self._expires_at[key] = time.time() + ttl_value
|
|
143
|
+
|
|
144
|
+
def _cleanup_expired(self) -> None:
|
|
145
|
+
"""Remove expired entries (lazy cleanup)."""
|
|
146
|
+
expired_keys = [
|
|
147
|
+
key for key in self._expires_at
|
|
148
|
+
if time.time() > self._expires_at[key]
|
|
149
|
+
]
|
|
150
|
+
for key in expired_keys:
|
|
151
|
+
self._data.pop(key, None)
|
|
152
|
+
self._ttl.pop(key, None)
|
|
153
|
+
self._expires_at.pop(key, None)
|
|
154
|
+
|
|
155
|
+
def _extract_last_content(
|
|
156
|
+
self,
|
|
157
|
+
messages: Iterable[dict[str, Any]],
|
|
158
|
+
role: str
|
|
159
|
+
) -> str:
|
|
160
|
+
"""Extract last message content for a given role."""
|
|
161
|
+
return next(
|
|
162
|
+
(
|
|
163
|
+
m.get("content", "")
|
|
164
|
+
for m in reversed(list(messages))
|
|
165
|
+
if m.get("role") == role
|
|
166
|
+
),
|
|
167
|
+
"",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def _get_agent_name(self) -> str:
|
|
171
|
+
"""Get current agent name from crew context."""
|
|
172
|
+
if self.crew and hasattr(self.crew, "_current_agent"):
|
|
173
|
+
agent = self.crew._current_agent
|
|
174
|
+
if agent and hasattr(agent, "role"):
|
|
175
|
+
return str(agent.role)
|
|
176
|
+
return self.config.get("agent_id", "default_agent")
|
|
177
|
+
|
|
178
|
+
def save(self, value: Any, metadata: dict[str, Any]) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Save a value to MCAL storage.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
value: The content to save (string, dict, or conversation)
|
|
184
|
+
metadata: Additional metadata (agent, task, etc.)
|
|
185
|
+
"""
|
|
186
|
+
with self._lock:
|
|
187
|
+
# Generate key
|
|
188
|
+
key = self._generate_key(value, metadata)
|
|
189
|
+
|
|
190
|
+
# Process value based on type
|
|
191
|
+
if isinstance(value, dict) and "messages" in value:
|
|
192
|
+
# Conversation format
|
|
193
|
+
messages = value.get("messages", [])
|
|
194
|
+
content = self._extract_last_content(messages, "assistant")
|
|
195
|
+
if not content:
|
|
196
|
+
content = self._extract_last_content(messages, "user")
|
|
197
|
+
elif isinstance(value, str):
|
|
198
|
+
content = value
|
|
199
|
+
else:
|
|
200
|
+
content = str(value)
|
|
201
|
+
|
|
202
|
+
# Build storage entry
|
|
203
|
+
entry = {
|
|
204
|
+
"content": content,
|
|
205
|
+
"raw_value": value,
|
|
206
|
+
"metadata": {
|
|
207
|
+
"type": self.memory_type,
|
|
208
|
+
"user_id": self.user_id,
|
|
209
|
+
"agent": self._get_agent_name(),
|
|
210
|
+
"timestamp": time.time(),
|
|
211
|
+
**metadata,
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Extract goals if enabled
|
|
216
|
+
if self.enable_goal_tracking and content:
|
|
217
|
+
entry["metadata"]["goal_tracked"] = True
|
|
218
|
+
# TODO: Use MCAL's goal extraction when integrated
|
|
219
|
+
# goals = self.mcal.extract_goals(content)
|
|
220
|
+
# entry["metadata"]["goals"] = goals
|
|
221
|
+
|
|
222
|
+
# Store with TTL
|
|
223
|
+
self._data[key] = entry
|
|
224
|
+
|
|
225
|
+
# Set TTL if configured
|
|
226
|
+
ttl = metadata.get("ttl") or self.default_ttl
|
|
227
|
+
if ttl:
|
|
228
|
+
self._set_ttl(key, ttl)
|
|
229
|
+
|
|
230
|
+
# Short-term memory gets default TTL if not set
|
|
231
|
+
if self.memory_type == "short_term" and key not in self._ttl:
|
|
232
|
+
self._set_ttl(key, 3600) # 1 hour default for short-term
|
|
233
|
+
|
|
234
|
+
def search(
|
|
235
|
+
self,
|
|
236
|
+
query: str,
|
|
237
|
+
limit: int = 5,
|
|
238
|
+
score_threshold: float = 0.6
|
|
239
|
+
) -> list[Any]:
|
|
240
|
+
"""
|
|
241
|
+
Search storage for relevant content.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
query: Search query
|
|
245
|
+
limit: Maximum results to return
|
|
246
|
+
score_threshold: Minimum relevance score (0-1)
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
List of matching results with 'content' key
|
|
250
|
+
"""
|
|
251
|
+
with self._lock:
|
|
252
|
+
# Cleanup expired entries
|
|
253
|
+
self._cleanup_expired()
|
|
254
|
+
|
|
255
|
+
# Simple keyword-based search for now
|
|
256
|
+
# TODO: Use MCAL's semantic search when integrated
|
|
257
|
+
results = []
|
|
258
|
+
query_lower = query.lower()
|
|
259
|
+
query_words = set(query_lower.split())
|
|
260
|
+
|
|
261
|
+
for key, entry in self._data.items():
|
|
262
|
+
# Skip expired
|
|
263
|
+
if self._is_expired(key):
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
content = entry.get("content", "")
|
|
267
|
+
content_lower = content.lower()
|
|
268
|
+
|
|
269
|
+
# Calculate simple relevance score
|
|
270
|
+
content_words = set(content_lower.split())
|
|
271
|
+
overlap = query_words & content_words
|
|
272
|
+
|
|
273
|
+
if overlap:
|
|
274
|
+
score = len(overlap) / max(len(query_words), 1)
|
|
275
|
+
|
|
276
|
+
if score >= score_threshold:
|
|
277
|
+
results.append({
|
|
278
|
+
"content": content,
|
|
279
|
+
"memory": content, # Compatibility with Mem0 format
|
|
280
|
+
"score": score,
|
|
281
|
+
"metadata": entry.get("metadata", {}),
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
# Sort by score and limit
|
|
285
|
+
results.sort(key=lambda x: x["score"], reverse=True)
|
|
286
|
+
return results[:limit]
|
|
287
|
+
|
|
288
|
+
def reset(self) -> None:
|
|
289
|
+
"""Clear all stored memories for this type."""
|
|
290
|
+
with self._lock:
|
|
291
|
+
self._data.clear()
|
|
292
|
+
self._ttl.clear()
|
|
293
|
+
self._expires_at.clear()
|
|
294
|
+
|
|
295
|
+
def get_all(self) -> list[dict[str, Any]]:
|
|
296
|
+
"""Get all non-expired entries."""
|
|
297
|
+
with self._lock:
|
|
298
|
+
self._cleanup_expired()
|
|
299
|
+
return [
|
|
300
|
+
entry for key, entry in self._data.items()
|
|
301
|
+
if not self._is_expired(key)
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
def delete(self, key: str) -> bool:
|
|
305
|
+
"""Delete a specific entry by key."""
|
|
306
|
+
with self._lock:
|
|
307
|
+
if key in self._data:
|
|
308
|
+
del self._data[key]
|
|
309
|
+
self._ttl.pop(key, None)
|
|
310
|
+
self._expires_at.pop(key, None)
|
|
311
|
+
return True
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# Alias for backward compatibility
|
|
316
|
+
MCALMemoryStorage = MCALStorage
|