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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.
@@ -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