mimir-crewai 0.1.1__tar.gz
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.
- mimir_crewai-0.1.1/PKG-INFO +92 -0
- mimir_crewai-0.1.1/README.md +74 -0
- mimir_crewai-0.1.1/mimir_crewai/__init__.py +234 -0
- mimir_crewai-0.1.1/mimir_crewai.egg-info/PKG-INFO +92 -0
- mimir_crewai-0.1.1/mimir_crewai.egg-info/SOURCES.txt +10 -0
- mimir_crewai-0.1.1/mimir_crewai.egg-info/dependency_links.txt +1 -0
- mimir_crewai-0.1.1/mimir_crewai.egg-info/requires.txt +4 -0
- mimir_crewai-0.1.1/mimir_crewai.egg-info/top_level.txt +1 -0
- mimir_crewai-0.1.1/pyproject.toml +32 -0
- mimir_crewai-0.1.1/setup.cfg +4 -0
- mimir_crewai-0.1.1/tests/test_mimir_tool.py +121 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mimir-crewai
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Mimir persistent memory tool for CrewAI agents
|
|
5
|
+
Author: Perseus Computing LLC
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Perseus-Computing-LLC/mimir
|
|
8
|
+
Project-URL: Repository, https://github.com/Perseus-Computing-LLC/mimir
|
|
9
|
+
Keywords: crewai,mimir,memory,mcp,agents
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: crewai>=0.30.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# Mimir CrewAI Integration
|
|
20
|
+
|
|
21
|
+
Persistent memory for CrewAI agents via Mimir.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Install from source (not yet published to PyPI):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install crewai
|
|
29
|
+
pip install -e integrations/crewai/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from crewai import Agent, Task, Crew
|
|
36
|
+
from mimir_crewai import MimirMemoryTool
|
|
37
|
+
|
|
38
|
+
# Create the memory tool
|
|
39
|
+
memory = MimirMemoryTool(
|
|
40
|
+
db_path="~/.mimir/data/crew.db"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Give it to your agents
|
|
44
|
+
researcher = Agent(
|
|
45
|
+
role="Senior Researcher",
|
|
46
|
+
goal="Find and analyze information",
|
|
47
|
+
backstory="Expert at gathering and synthesizing data",
|
|
48
|
+
tools=[memory],
|
|
49
|
+
verbose=True,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Agents use it naturally
|
|
53
|
+
task = Task(
|
|
54
|
+
description=(
|
|
55
|
+
"Research the competitor's pricing strategy. "
|
|
56
|
+
"Use Mimir Memory to recall any previous findings on this topic, "
|
|
57
|
+
"then remember your new conclusions."
|
|
58
|
+
),
|
|
59
|
+
agent=researcher,
|
|
60
|
+
expected_output="A report with pricing analysis",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
crew = Crew(agents=[researcher], tasks=[task])
|
|
64
|
+
result = crew.kickoff()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Available Actions
|
|
68
|
+
|
|
69
|
+
| Action | Description | Parameters |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `remember` | Store a fact or decision | `category`, `key`, `content`, `entity_type?` |
|
|
72
|
+
| `recall` | Search stored memories | `query`, `category?`, `limit?` |
|
|
73
|
+
| `journal` | Record a significant event | `event_type`, `description`, `context?` |
|
|
74
|
+
| `context` | Get session context summary | (none) |
|
|
75
|
+
|
|
76
|
+
## How It Works
|
|
77
|
+
|
|
78
|
+
The `MimirMemoryTool` wraps Mimir's MCP tools as a CrewAI tool:
|
|
79
|
+
|
|
80
|
+
- `remember` → `mimir_remember`
|
|
81
|
+
- `recall` → `mimir_recall`
|
|
82
|
+
- `journal` → `mimir_journal`
|
|
83
|
+
- `context` → `mimir_context`
|
|
84
|
+
|
|
85
|
+
All memories persist across sessions and crews. Agents can build up
|
|
86
|
+
a shared knowledge base over time.
|
|
87
|
+
|
|
88
|
+
## Requirements
|
|
89
|
+
|
|
90
|
+
- Mimir v1.0.0+ (`curl -sSL https://raw.githubusercontent.com/Perseus-Computing-LLC/mimir/main/scripts/bootstrap.sh | bash`)
|
|
91
|
+
- CrewAI >= 0.30.0
|
|
92
|
+
- Python 3.10+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Mimir CrewAI Integration
|
|
2
|
+
|
|
3
|
+
Persistent memory for CrewAI agents via Mimir.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Install from source (not yet published to PyPI):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install crewai
|
|
11
|
+
pip install -e integrations/crewai/
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from crewai import Agent, Task, Crew
|
|
18
|
+
from mimir_crewai import MimirMemoryTool
|
|
19
|
+
|
|
20
|
+
# Create the memory tool
|
|
21
|
+
memory = MimirMemoryTool(
|
|
22
|
+
db_path="~/.mimir/data/crew.db"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Give it to your agents
|
|
26
|
+
researcher = Agent(
|
|
27
|
+
role="Senior Researcher",
|
|
28
|
+
goal="Find and analyze information",
|
|
29
|
+
backstory="Expert at gathering and synthesizing data",
|
|
30
|
+
tools=[memory],
|
|
31
|
+
verbose=True,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Agents use it naturally
|
|
35
|
+
task = Task(
|
|
36
|
+
description=(
|
|
37
|
+
"Research the competitor's pricing strategy. "
|
|
38
|
+
"Use Mimir Memory to recall any previous findings on this topic, "
|
|
39
|
+
"then remember your new conclusions."
|
|
40
|
+
),
|
|
41
|
+
agent=researcher,
|
|
42
|
+
expected_output="A report with pricing analysis",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
crew = Crew(agents=[researcher], tasks=[task])
|
|
46
|
+
result = crew.kickoff()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Available Actions
|
|
50
|
+
|
|
51
|
+
| Action | Description | Parameters |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `remember` | Store a fact or decision | `category`, `key`, `content`, `entity_type?` |
|
|
54
|
+
| `recall` | Search stored memories | `query`, `category?`, `limit?` |
|
|
55
|
+
| `journal` | Record a significant event | `event_type`, `description`, `context?` |
|
|
56
|
+
| `context` | Get session context summary | (none) |
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
The `MimirMemoryTool` wraps Mimir's MCP tools as a CrewAI tool:
|
|
61
|
+
|
|
62
|
+
- `remember` → `mimir_remember`
|
|
63
|
+
- `recall` → `mimir_recall`
|
|
64
|
+
- `journal` → `mimir_journal`
|
|
65
|
+
- `context` → `mimir_context`
|
|
66
|
+
|
|
67
|
+
All memories persist across sessions and crews. Agents can build up
|
|
68
|
+
a shared knowledge base over time.
|
|
69
|
+
|
|
70
|
+
## Requirements
|
|
71
|
+
|
|
72
|
+
- Mimir v1.0.0+ (`curl -sSL https://raw.githubusercontent.com/Perseus-Computing-LLC/mimir/main/scripts/bootstrap.sh | bash`)
|
|
73
|
+
- CrewAI >= 0.30.0
|
|
74
|
+
- Python 3.10+
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CrewAI Mimir Memory Tool — provides persistent memory for CrewAI agents.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from crewai import Agent, Task, Crew
|
|
6
|
+
from mimir_crewai import MimirMemoryTool
|
|
7
|
+
|
|
8
|
+
memory = MimirMemoryTool(db_path="~/.mimir/data/crew.db")
|
|
9
|
+
|
|
10
|
+
agent = Agent(
|
|
11
|
+
role="Researcher",
|
|
12
|
+
goal="Find information",
|
|
13
|
+
tools=[memory],
|
|
14
|
+
)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import subprocess
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional, Any
|
|
22
|
+
from crewai.tools import BaseTool
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MimirMemoryTool(BaseTool):
|
|
26
|
+
"""CrewAI tool that provides persistent memory via Mimir.
|
|
27
|
+
|
|
28
|
+
Agents can remember facts, recall past decisions, and search
|
|
29
|
+
the knowledge base — all persisted across sessions and crews.
|
|
30
|
+
|
|
31
|
+
Available actions:
|
|
32
|
+
remember — Store a fact or decision
|
|
33
|
+
recall — Search stored memories
|
|
34
|
+
journal — Record a significant event
|
|
35
|
+
context — Get the current session context summary
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
name: str = "Mimir Memory"
|
|
39
|
+
description: str = (
|
|
40
|
+
"Persistent memory tool for storing and retrieving information "
|
|
41
|
+
"across sessions. Use this to remember facts, recall past "
|
|
42
|
+
"decisions, and maintain context between agent interactions.\n"
|
|
43
|
+
"Actions: remember(category, key, content), "
|
|
44
|
+
"recall(query, category?), "
|
|
45
|
+
"journal(event_type, description, context?), "
|
|
46
|
+
"context() — get session summary"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
binary: str = "mimir",
|
|
52
|
+
db_path: str = "~/.mimir/data/crew.db",
|
|
53
|
+
timeout: float = 30.0,
|
|
54
|
+
encryption_key: Optional[str] = None,
|
|
55
|
+
):
|
|
56
|
+
super().__init__()
|
|
57
|
+
self.binary = binary
|
|
58
|
+
self.db_path = str(Path(db_path).expanduser())
|
|
59
|
+
self.timeout = timeout
|
|
60
|
+
self.encryption_key = encryption_key
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _unwrap_result(result: dict) -> dict:
|
|
64
|
+
"""Unwrap an MCP ``tools/call`` result into Mimir's payload dict.
|
|
65
|
+
|
|
66
|
+
Mimir returns the standard MCP envelope::
|
|
67
|
+
|
|
68
|
+
{"content": [{"type": "text", "text": "<json>"}],
|
|
69
|
+
"structuredContent": {...parsed json...}}
|
|
70
|
+
|
|
71
|
+
The payload (``items``, ``context`` ...) lives in ``structuredContent``
|
|
72
|
+
(preferred) or the JSON text of the first content block. Reading
|
|
73
|
+
``result["items"]`` directly always yields nothing.
|
|
74
|
+
"""
|
|
75
|
+
structured = result.get("structuredContent")
|
|
76
|
+
if isinstance(structured, dict):
|
|
77
|
+
return structured
|
|
78
|
+
content = result.get("content")
|
|
79
|
+
if isinstance(content, list) and content:
|
|
80
|
+
text = content[0].get("text", "") if isinstance(content[0], dict) else ""
|
|
81
|
+
try:
|
|
82
|
+
parsed = json.loads(text)
|
|
83
|
+
except (json.JSONDecodeError, TypeError):
|
|
84
|
+
return {}
|
|
85
|
+
if isinstance(parsed, dict):
|
|
86
|
+
return parsed
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
def _call_mimir(self, method: str, params: dict) -> dict:
|
|
90
|
+
"""Call a Mimir MCP tool via stdio."""
|
|
91
|
+
args = [self.binary, "--db", self.db_path]
|
|
92
|
+
if self.encryption_key:
|
|
93
|
+
args.extend(["--encryption-key", self.encryption_key])
|
|
94
|
+
|
|
95
|
+
init_req = json.dumps({
|
|
96
|
+
"jsonrpc": "2.0", "id": 1,
|
|
97
|
+
"method": "initialize",
|
|
98
|
+
"params": {
|
|
99
|
+
"protocolVersion": "2024-11-05",
|
|
100
|
+
"capabilities": {},
|
|
101
|
+
"clientInfo": {"name": "crewai-mimir", "version": "1.0.0"},
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
call_req = json.dumps({
|
|
105
|
+
"jsonrpc": "2.0", "id": 2,
|
|
106
|
+
"method": "tools/call",
|
|
107
|
+
"params": {"name": method, "arguments": params},
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
proc = subprocess.Popen(
|
|
111
|
+
args,
|
|
112
|
+
stdin=subprocess.PIPE,
|
|
113
|
+
stdout=subprocess.PIPE,
|
|
114
|
+
stderr=subprocess.PIPE,
|
|
115
|
+
text=True,
|
|
116
|
+
)
|
|
117
|
+
try:
|
|
118
|
+
stdout, _ = proc.communicate(
|
|
119
|
+
input=init_req + "\n" + call_req + "\n", timeout=self.timeout
|
|
120
|
+
)
|
|
121
|
+
except subprocess.TimeoutExpired:
|
|
122
|
+
proc.kill()
|
|
123
|
+
proc.communicate()
|
|
124
|
+
return {"error": "timeout"}
|
|
125
|
+
|
|
126
|
+
response = None
|
|
127
|
+
for line in stdout.splitlines():
|
|
128
|
+
line = line.strip()
|
|
129
|
+
if not line:
|
|
130
|
+
continue
|
|
131
|
+
try:
|
|
132
|
+
msg = json.loads(line)
|
|
133
|
+
except json.JSONDecodeError:
|
|
134
|
+
continue
|
|
135
|
+
if isinstance(msg, dict) and msg.get("id") == 2:
|
|
136
|
+
response = msg
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
if response is None:
|
|
140
|
+
return {"error": "no response from mimir"}
|
|
141
|
+
if response.get("error"):
|
|
142
|
+
return {"error": response["error"]}
|
|
143
|
+
return self._unwrap_result(response.get("result", {}))
|
|
144
|
+
|
|
145
|
+
def _run(self, action: str, **kwargs) -> str:
|
|
146
|
+
"""Execute a memory action.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
action: One of 'remember', 'recall', 'journal', 'context'
|
|
150
|
+
**kwargs: Action-specific parameters
|
|
151
|
+
"""
|
|
152
|
+
if action == "remember":
|
|
153
|
+
return self._remember(**kwargs)
|
|
154
|
+
elif action == "recall":
|
|
155
|
+
return self._recall(**kwargs)
|
|
156
|
+
elif action == "journal":
|
|
157
|
+
return self._journal(**kwargs)
|
|
158
|
+
elif action == "context":
|
|
159
|
+
return self._context()
|
|
160
|
+
else:
|
|
161
|
+
return f"Unknown action: {action}. Use: remember, recall, journal, context"
|
|
162
|
+
|
|
163
|
+
def _remember(
|
|
164
|
+
self,
|
|
165
|
+
category: str = "crewai",
|
|
166
|
+
key: str = "",
|
|
167
|
+
content: str = "",
|
|
168
|
+
entity_type: str = "fact",
|
|
169
|
+
) -> str:
|
|
170
|
+
"""Store a fact, decision, or piece of knowledge."""
|
|
171
|
+
result = self._call_mimir("mimir_remember", {
|
|
172
|
+
"category": category,
|
|
173
|
+
"key": key or f"auto-{int(time.time())}",
|
|
174
|
+
"body_json": json.dumps({"content": content}),
|
|
175
|
+
"type": entity_type,
|
|
176
|
+
})
|
|
177
|
+
if "error" in result:
|
|
178
|
+
return f"Failed to remember: {result['error']}"
|
|
179
|
+
return f"Remembered: [{category}] {key or 'auto'}: {content[:100]}"
|
|
180
|
+
|
|
181
|
+
def _recall(
|
|
182
|
+
self,
|
|
183
|
+
query: str = "",
|
|
184
|
+
category: str = "",
|
|
185
|
+
limit: int = 5,
|
|
186
|
+
) -> str:
|
|
187
|
+
"""Search stored memories."""
|
|
188
|
+
params = {"query": query, "limit": limit}
|
|
189
|
+
if category:
|
|
190
|
+
params["category"] = category
|
|
191
|
+
|
|
192
|
+
result = self._call_mimir("mimir_recall", params)
|
|
193
|
+
items = result.get("items", [])
|
|
194
|
+
|
|
195
|
+
if not items:
|
|
196
|
+
return f"No memories found for '{query}'"
|
|
197
|
+
|
|
198
|
+
lines = [f"Found {len(items)} memor{'y' if len(items)==1 else 'ies'}:"]
|
|
199
|
+
for item in items:
|
|
200
|
+
body = item.get("body_json", "{}")
|
|
201
|
+
try:
|
|
202
|
+
content = json.loads(body).get("content", body)
|
|
203
|
+
except (json.JSONDecodeError, TypeError):
|
|
204
|
+
content = body
|
|
205
|
+
lines.append(f" [{item.get('category', '?')}] {item.get('key', '?')}: {content[:200]}")
|
|
206
|
+
return "\n".join(lines)
|
|
207
|
+
|
|
208
|
+
def _journal(
|
|
209
|
+
self,
|
|
210
|
+
event_type: str = "observation",
|
|
211
|
+
description: str = "",
|
|
212
|
+
context: str = "",
|
|
213
|
+
) -> str:
|
|
214
|
+
"""Record a significant event in the journal."""
|
|
215
|
+
result = self._call_mimir("mimir_journal", {
|
|
216
|
+
"event_type": event_type,
|
|
217
|
+
"category": "crewai",
|
|
218
|
+
"key": f"event-{int(time.time())}",
|
|
219
|
+
"evaluated": {"description": description, "context": context},
|
|
220
|
+
})
|
|
221
|
+
if "error" in result:
|
|
222
|
+
return f"Failed to journal: {result['error']}"
|
|
223
|
+
return f"Journaled {event_type}: {description[:100]}"
|
|
224
|
+
|
|
225
|
+
def _context(self) -> str:
|
|
226
|
+
"""Get a summary of recent memories for session context."""
|
|
227
|
+
result = self._call_mimir("mimir_context", {})
|
|
228
|
+
if "error" in result:
|
|
229
|
+
return f"Failed to get context: {result['error']}"
|
|
230
|
+
context_text = result.get("context", "")
|
|
231
|
+
if not context_text:
|
|
232
|
+
return "No stored context. Use 'remember' to store information first."
|
|
233
|
+
# Return first 1000 chars to avoid overwhelming the agent
|
|
234
|
+
return context_text[:1000] + ("..." if len(context_text) > 1000 else "")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mimir-crewai
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Mimir persistent memory tool for CrewAI agents
|
|
5
|
+
Author: Perseus Computing LLC
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Perseus-Computing-LLC/mimir
|
|
8
|
+
Project-URL: Repository, https://github.com/Perseus-Computing-LLC/mimir
|
|
9
|
+
Keywords: crewai,mimir,memory,mcp,agents
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: crewai>=0.30.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# Mimir CrewAI Integration
|
|
20
|
+
|
|
21
|
+
Persistent memory for CrewAI agents via Mimir.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Install from source (not yet published to PyPI):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install crewai
|
|
29
|
+
pip install -e integrations/crewai/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from crewai import Agent, Task, Crew
|
|
36
|
+
from mimir_crewai import MimirMemoryTool
|
|
37
|
+
|
|
38
|
+
# Create the memory tool
|
|
39
|
+
memory = MimirMemoryTool(
|
|
40
|
+
db_path="~/.mimir/data/crew.db"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Give it to your agents
|
|
44
|
+
researcher = Agent(
|
|
45
|
+
role="Senior Researcher",
|
|
46
|
+
goal="Find and analyze information",
|
|
47
|
+
backstory="Expert at gathering and synthesizing data",
|
|
48
|
+
tools=[memory],
|
|
49
|
+
verbose=True,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Agents use it naturally
|
|
53
|
+
task = Task(
|
|
54
|
+
description=(
|
|
55
|
+
"Research the competitor's pricing strategy. "
|
|
56
|
+
"Use Mimir Memory to recall any previous findings on this topic, "
|
|
57
|
+
"then remember your new conclusions."
|
|
58
|
+
),
|
|
59
|
+
agent=researcher,
|
|
60
|
+
expected_output="A report with pricing analysis",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
crew = Crew(agents=[researcher], tasks=[task])
|
|
64
|
+
result = crew.kickoff()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Available Actions
|
|
68
|
+
|
|
69
|
+
| Action | Description | Parameters |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `remember` | Store a fact or decision | `category`, `key`, `content`, `entity_type?` |
|
|
72
|
+
| `recall` | Search stored memories | `query`, `category?`, `limit?` |
|
|
73
|
+
| `journal` | Record a significant event | `event_type`, `description`, `context?` |
|
|
74
|
+
| `context` | Get session context summary | (none) |
|
|
75
|
+
|
|
76
|
+
## How It Works
|
|
77
|
+
|
|
78
|
+
The `MimirMemoryTool` wraps Mimir's MCP tools as a CrewAI tool:
|
|
79
|
+
|
|
80
|
+
- `remember` → `mimir_remember`
|
|
81
|
+
- `recall` → `mimir_recall`
|
|
82
|
+
- `journal` → `mimir_journal`
|
|
83
|
+
- `context` → `mimir_context`
|
|
84
|
+
|
|
85
|
+
All memories persist across sessions and crews. Agents can build up
|
|
86
|
+
a shared knowledge base over time.
|
|
87
|
+
|
|
88
|
+
## Requirements
|
|
89
|
+
|
|
90
|
+
- Mimir v1.0.0+ (`curl -sSL https://raw.githubusercontent.com/Perseus-Computing-LLC/mimir/main/scripts/bootstrap.sh | bash`)
|
|
91
|
+
- CrewAI >= 0.30.0
|
|
92
|
+
- Python 3.10+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
./mimir_crewai/__init__.py
|
|
4
|
+
mimir_crewai/__init__.py
|
|
5
|
+
mimir_crewai.egg-info/PKG-INFO
|
|
6
|
+
mimir_crewai.egg-info/SOURCES.txt
|
|
7
|
+
mimir_crewai.egg-info/dependency_links.txt
|
|
8
|
+
mimir_crewai.egg-info/requires.txt
|
|
9
|
+
mimir_crewai.egg-info/top_level.txt
|
|
10
|
+
tests/test_mimir_tool.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mimir_crewai
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mimir-crewai"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Mimir persistent memory tool for CrewAI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Perseus Computing LLC" }]
|
|
13
|
+
keywords = ["crewai", "mimir", "memory", "mcp", "agents"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"crewai>=0.30.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = ["pytest"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/Perseus-Computing-LLC/mimir"
|
|
28
|
+
Repository = "https://github.com/Perseus-Computing-LLC/mimir"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools]
|
|
31
|
+
packages = ["mimir_crewai"]
|
|
32
|
+
package-dir = { "" = "." }
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Tests for the CrewAI MimirMemoryTool.
|
|
2
|
+
|
|
3
|
+
CrewAI (and its heavy dependency tree) need not be installed to exercise the
|
|
4
|
+
Mimir wiring: we stub ``crewai.tools.BaseTool`` with a minimal base class, then
|
|
5
|
+
drive the tool against Mimir's real MCP JSON-RPC envelope via a fake subprocess.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
import types
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(scope="module")
|
|
18
|
+
def MimirMemoryTool():
|
|
19
|
+
"""Import MimirMemoryTool with a stubbed ``crewai.tools.BaseTool``."""
|
|
20
|
+
if "crewai" not in sys.modules:
|
|
21
|
+
crewai = types.ModuleType("crewai")
|
|
22
|
+
tools = types.ModuleType("crewai.tools")
|
|
23
|
+
|
|
24
|
+
class BaseTool:
|
|
25
|
+
name: str = ""
|
|
26
|
+
description: str = ""
|
|
27
|
+
|
|
28
|
+
def __init__(self, *args, **kwargs):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
tools.BaseTool = BaseTool
|
|
32
|
+
crewai.tools = tools
|
|
33
|
+
sys.modules["crewai"] = crewai
|
|
34
|
+
sys.modules["crewai.tools"] = tools
|
|
35
|
+
|
|
36
|
+
from mimir_crewai import MimirMemoryTool as tool_cls
|
|
37
|
+
|
|
38
|
+
return tool_cls
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _fake_popen(routes):
|
|
42
|
+
class FakePopen:
|
|
43
|
+
last_input = None
|
|
44
|
+
|
|
45
|
+
def __init__(self, *args, **kwargs):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def communicate(self, input=None, timeout=None): # noqa: A002
|
|
49
|
+
FakePopen.last_input = input
|
|
50
|
+
call = next(
|
|
51
|
+
m
|
|
52
|
+
for m in (json.loads(line) for line in input.splitlines() if line)
|
|
53
|
+
if m.get("id") == 2
|
|
54
|
+
)
|
|
55
|
+
payload = routes[call["params"]["name"]](call["params"]["arguments"])
|
|
56
|
+
init_resp = json.dumps({"jsonrpc": "2.0", "id": 1, "result": {}})
|
|
57
|
+
tool_resp = json.dumps(
|
|
58
|
+
{
|
|
59
|
+
"jsonrpc": "2.0",
|
|
60
|
+
"id": 2,
|
|
61
|
+
"result": {
|
|
62
|
+
"content": [{"type": "text", "text": json.dumps(payload)}],
|
|
63
|
+
"structuredContent": payload,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
return (init_resp + "\n" + tool_resp + "\n", "")
|
|
68
|
+
|
|
69
|
+
def kill(self):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
return FakePopen
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_remember_sends_type(monkeypatch, MimirMemoryTool):
|
|
76
|
+
captured = {}
|
|
77
|
+
|
|
78
|
+
def remember(args):
|
|
79
|
+
captured.update(args)
|
|
80
|
+
return {"id": "mem-1", "status": "ok"}
|
|
81
|
+
|
|
82
|
+
monkeypatch.setattr(
|
|
83
|
+
"mimir_crewai.subprocess.Popen", _fake_popen({"mimir_remember": remember})
|
|
84
|
+
)
|
|
85
|
+
tool = MimirMemoryTool()
|
|
86
|
+
out = tool._remember(category="crewai", key="k1", content="hello world")
|
|
87
|
+
|
|
88
|
+
assert captured.get("type") == "fact" # regression: was the dropped "entity_type"
|
|
89
|
+
assert "entity_type" not in captured
|
|
90
|
+
assert json.loads(captured["body_json"]) == {"content": "hello world"}
|
|
91
|
+
assert "Remembered" in out
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_recall_parses_structured_items(monkeypatch, MimirMemoryTool):
|
|
95
|
+
def recall(args):
|
|
96
|
+
return {
|
|
97
|
+
"items": [
|
|
98
|
+
{
|
|
99
|
+
"category": "crewai",
|
|
100
|
+
"key": "k1",
|
|
101
|
+
"body_json": json.dumps({"content": "the answer is 42"}),
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"total": 1,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
monkeypatch.setattr(
|
|
108
|
+
"mimir_crewai.subprocess.Popen", _fake_popen({"mimir_recall": recall})
|
|
109
|
+
)
|
|
110
|
+
tool = MimirMemoryTool()
|
|
111
|
+
out = tool._recall(query="answer")
|
|
112
|
+
|
|
113
|
+
# Before the envelope-unwrap fix this returned "No memories found".
|
|
114
|
+
assert "Found 1 memory" in out
|
|
115
|
+
assert "the answer is 42" in out
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_unwrap_handles_text_only_envelope(MimirMemoryTool):
|
|
119
|
+
assert MimirMemoryTool._unwrap_result(
|
|
120
|
+
{"content": [{"type": "text", "text": json.dumps({"items": [1, 2]})}]}
|
|
121
|
+
) == {"items": [1, 2]}
|