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.
@@ -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,4 @@
1
+ crewai>=0.30.0
2
+
3
+ [dev]
4
+ pytest
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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]}