adk-mimir-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.
@@ -0,0 +1,16 @@
1
+ """ADK Mimir Memory Service — persistent, local, encrypted cross-session memory.
2
+
3
+ Mimir (github.com/Perseus-Computing-LLC/mimir) is an open-source (MIT)
4
+ persistent memory engine with 30+ MCP tools, FTS5 + dense hybrid search,
5
+ and optional AES-256-GCM encryption. This service talks to the Mimir
6
+ binary via JSON-RPC over stdin/stdout (MCP stdio transport).
7
+
8
+ Requirements:
9
+ A ``mimir`` binary must be on ``$PATH`` or passed explicitly via
10
+ ``mimir_binary``. Download from:
11
+ https://github.com/Perseus-Computing-LLC/mimir/releases
12
+ """
13
+
14
+ from .mimir_memory_service import MimirMemoryService
15
+
16
+ __all__ = ["MimirMemoryService"]
@@ -0,0 +1,445 @@
1
+ """Mimir persistent memory service for ADK.
2
+
3
+ Mimir (github.com/Perseus-Computing-LLC/mimir) is an open-source (MIT)
4
+ persistent memory engine with 30+ MCP tools, FTS5 + dense hybrid search,
5
+ and optional AES-256-GCM encryption. This service talks to the Mimir
6
+ binary via JSON-RPC over stdin/stdout (MCP stdio transport).
7
+
8
+ Requirements:
9
+ A ``mimir`` binary must be on ``$PATH`` or passed explicitly via
10
+ ``mimir_binary``. Build from source or download a pre-built binary from
11
+ the Mimir releases page.
12
+
13
+ Usage::
14
+
15
+ from adk_mimir_memory import MimirMemoryService
16
+ from google.adk.memory import InMemoryMemoryService
17
+
18
+ # Swap out the default in-memory service for persistent Mimir
19
+ agent = Agent(
20
+ name="my_agent",
21
+ memory_service=MimirMemoryService(
22
+ db_path="~/.adk/mimir.db",
23
+ ),
24
+ )
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import atexit
30
+ import json
31
+ import logging
32
+ import os
33
+ import shutil
34
+ import subprocess
35
+ import threading
36
+ from collections.abc import Mapping
37
+ from collections.abc import Sequence
38
+ from datetime import datetime
39
+ from typing import TYPE_CHECKING
40
+
41
+ from typing_extensions import override
42
+
43
+ from google.adk.memory.base_memory_service import BaseMemoryService
44
+ from google.adk.memory.base_memory_service import SearchMemoryResponse
45
+ from google.adk.memory.memory_entry import MemoryEntry
46
+ from google.genai import types
47
+
48
+ if TYPE_CHECKING:
49
+ from google.adk.events.event import Event
50
+ from google.adk.sessions.session import Session
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+ _MIMIR_CATEGORY = "adk-memory"
55
+
56
+
57
+ def _format_timestamp(timestamp: float) -> str:
58
+ """Formats a unix timestamp as an ISO 8601 string."""
59
+ return datetime.fromtimestamp(timestamp).isoformat()
60
+
61
+
62
+ class MimirMemoryService(BaseMemoryService):
63
+ """Persistent memory service backed by Mimir.
64
+
65
+ Talks to a local ``mimir`` binary via JSON-RPC (MCP stdio). Stores
66
+ session events as structured entities and supports keyword (FTS5) search
67
+ across sessions.
68
+
69
+ This class is thread-safe.
70
+
71
+ Attributes:
72
+ db_path: Filesystem path to the Mimir SQLite database.
73
+ mimir_binary: Path or name of the ``mimir`` executable.
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ db_path: str = "~/.adk/mimir.db",
79
+ mimir_binary: str = "mimir",
80
+ ):
81
+ """Initializes the Mimir memory service.
82
+
83
+ Args:
84
+ db_path: Path to the Mimir database file. Defaults to
85
+ ``~/.adk/mimir.db``.
86
+ mimir_binary: Name or absolute path of the ``mimir`` executable.
87
+ Defaults to ``mimir`` (resolved from ``$PATH``).
88
+
89
+ Raises:
90
+ RuntimeError: If the ``mimir`` binary cannot be found or the
91
+ subprocess fails to start.
92
+ """
93
+ self.db_path = os.path.expanduser(db_path)
94
+
95
+ # Resolve the mimir binary.
96
+ if os.path.isabs(mimir_binary):
97
+ self._mimir_binary = mimir_binary
98
+ else:
99
+ resolved = shutil.which(mimir_binary)
100
+ if resolved is None:
101
+ raise RuntimeError(
102
+ f"mimir binary not found on $PATH (looked for '{mimir_binary}'). "
103
+ "Install Mimir from https://github.com/Perseus-Computing-LLC/mimir "
104
+ "or pass the absolute path via mimir_binary=."
105
+ )
106
+ self._mimir_binary = resolved
107
+
108
+ # Ensure the database directory exists.
109
+ os.makedirs(os.path.dirname(self.db_path) or ".", exist_ok=True)
110
+
111
+ # Start the Mimir MCP stdio subprocess.
112
+ self._proc = subprocess.Popen(
113
+ [self._mimir_binary, "--db", self.db_path],
114
+ stdin=subprocess.PIPE,
115
+ stdout=subprocess.PIPE,
116
+ stderr=subprocess.PIPE,
117
+ text=True,
118
+ )
119
+ self._lock = threading.Lock()
120
+ self._request_id = 0
121
+
122
+ # Initialize the MCP session.
123
+ self._rpc(
124
+ "initialize",
125
+ {
126
+ "protocolVersion": "2024-11-05",
127
+ "capabilities": {},
128
+ "clientInfo": {"name": "adk-mimir-memory-service", "version": "1.0"},
129
+ },
130
+ )
131
+
132
+ # Clean up the subprocess on exit.
133
+ atexit.register(self._close)
134
+
135
+ def _close(self) -> None:
136
+ """Terminates the Mimir subprocess."""
137
+ try:
138
+ self._proc.terminate()
139
+ self._proc.wait(timeout=5)
140
+ except Exception:
141
+ try:
142
+ self._proc.kill()
143
+ except Exception:
144
+ pass
145
+
146
+ def _next_id(self) -> int:
147
+ self._request_id += 1
148
+ return self._request_id
149
+
150
+ def _rpc(self, method: str, params: object) -> dict:
151
+ """Sends a JSON-RPC request to Mimir and returns the result dict.
152
+
153
+ Args:
154
+ method: The MCP method name (e.g. ``tools/call``).
155
+ params: The method parameters.
156
+
157
+ Returns:
158
+ The ``result`` field of the JSON-RPC response.
159
+
160
+ Raises:
161
+ RuntimeError: If the RPC returns an error.
162
+ """
163
+ req = {
164
+ "jsonrpc": "2.0",
165
+ "id": self._next_id(),
166
+ "method": method,
167
+ "params": params,
168
+ }
169
+ payload = json.dumps(req, default=str)
170
+
171
+ with self._lock:
172
+ try:
173
+ self._proc.stdin.write(payload + "\n")
174
+ self._proc.stdin.flush()
175
+ raw = self._proc.stdout.readline()
176
+ except (BrokenPipeError, OSError) as e:
177
+ raise RuntimeError(
178
+ f"Mimir subprocess communication failed: {e}. "
179
+ "The mimir process may have crashed."
180
+ ) from e
181
+
182
+ try:
183
+ resp = json.loads(raw)
184
+ except json.JSONDecodeError as e:
185
+ raise RuntimeError(
186
+ f"Failed to parse Mimir response: {e}. Raw: {raw[:200]}"
187
+ ) from e
188
+
189
+ if "error" in resp:
190
+ err = resp["error"]
191
+ raise RuntimeError(
192
+ f"Mimir RPC error [{err.get('code')}]: {err.get('message')}"
193
+ )
194
+
195
+ return resp.get("result", {})
196
+
197
+ def _call_tool(self, name: str, arguments: dict) -> dict:
198
+ """Calls a Mimir MCP tool and returns the ``structuredContent``."""
199
+ result = self._rpc(
200
+ "tools/call",
201
+ {"name": name, "arguments": arguments},
202
+ )
203
+ # MCP result is {content: [{type: "text", text: "..."}], structuredContent: {...}}
204
+ sc = result.get("structuredContent")
205
+ if sc is not None:
206
+ return sc
207
+ # Fallback: parse the text content
208
+ content = result.get("content", [])
209
+ if content:
210
+ try:
211
+ return json.loads(content[0].get("text", "{}"))
212
+ except (json.JSONDecodeError, IndexError, KeyError):
213
+ pass
214
+ return {}
215
+
216
+ @override
217
+ async def add_session_to_memory(self, session: Session) -> None:
218
+ """Stores all events from a session in Mimir.
219
+
220
+ Each session is stored as a single Mimir entity keyed by session ID.
221
+ Subsequent calls for the same session will update the stored events.
222
+ """
223
+ if not session.events:
224
+ return
225
+
226
+ events_data = []
227
+ for event in session.events:
228
+ if not event.content or not event.content.parts:
229
+ continue
230
+ parts = []
231
+ for part in event.content.parts:
232
+ if part.text:
233
+ parts.append({"text": part.text})
234
+ elif hasattr(part, "function_call") and part.function_call:
235
+ parts.append({
236
+ "function_call": {
237
+ "name": part.function_call.name,
238
+ "args": part.function_call.args,
239
+ }
240
+ })
241
+ elif hasattr(part, "function_response") and part.function_response:
242
+ parts.append({
243
+ "function_response": {
244
+ "name": part.function_response.name,
245
+ "response": str(part.function_response.response)[:2000],
246
+ }
247
+ })
248
+ if parts:
249
+ events_data.append({
250
+ "author": event.author,
251
+ "timestamp": event.timestamp,
252
+ "parts": parts,
253
+ })
254
+
255
+ if not events_data:
256
+ return
257
+
258
+ self._call_tool(
259
+ "mimir_remember",
260
+ {
261
+ "category": _MIMIR_CATEGORY,
262
+ "key": f"session:{session.app_name}:{session.user_id}:{session.id}",
263
+ "body_json": json.dumps({
264
+ "session_id": session.id,
265
+ "app_name": session.app_name,
266
+ "user_id": session.user_id,
267
+ "events": events_data,
268
+ "event_count": len(events_data),
269
+ }),
270
+ "tags": ["adk", "session", session.app_name],
271
+ },
272
+ )
273
+
274
+ @override
275
+ async def add_events_to_memory(
276
+ self,
277
+ *,
278
+ app_name: str,
279
+ user_id: str,
280
+ events: Sequence[Event],
281
+ session_id: str | None = None,
282
+ custom_metadata: Mapping[str, object] | None = None,
283
+ ) -> None:
284
+ """Adds a delta of events to Mimir.
285
+
286
+ Events are appended to an existing session entity if one exists, or a
287
+ new entity is created. This is the recommended method for incremental
288
+ memory updates during long-running sessions.
289
+ """
290
+ _ = custom_metadata
291
+ events_data = []
292
+ for event in events:
293
+ if not event.content or not event.content.parts:
294
+ continue
295
+ parts = []
296
+ for part in event.content.parts:
297
+ if part.text:
298
+ parts.append({"text": part.text})
299
+ elif hasattr(part, "function_call") and part.function_call:
300
+ parts.append({
301
+ "function_call": {
302
+ "name": part.function_call.name,
303
+ "args": part.function_call.args,
304
+ }
305
+ })
306
+ if parts:
307
+ events_data.append({
308
+ "author": event.author,
309
+ "timestamp": event.timestamp,
310
+ "parts": parts,
311
+ })
312
+
313
+ if not events_data:
314
+ return
315
+
316
+ import time
317
+
318
+ sid = session_id or "__unknown__"
319
+ delta_key = f"delta:{app_name}:{user_id}:{sid}:{int(time.time() * 1000)}"
320
+
321
+ self._call_tool(
322
+ "mimir_remember",
323
+ {
324
+ "category": _MIMIR_CATEGORY,
325
+ "key": delta_key,
326
+ "body_json": json.dumps({
327
+ "session_id": sid,
328
+ "app_name": app_name,
329
+ "user_id": user_id,
330
+ "events": events_data,
331
+ "event_count": len(events_data),
332
+ }),
333
+ "tags": ["adk", "delta", app_name],
334
+ },
335
+ )
336
+
337
+ @override
338
+ async def add_memory(
339
+ self,
340
+ *,
341
+ app_name: str,
342
+ user_id: str,
343
+ memories: Sequence[MemoryEntry],
344
+ custom_metadata: Mapping[str, object] | None = None,
345
+ ) -> None:
346
+ """Adds explicit memory entries directly to Mimir.
347
+
348
+ Each MemoryEntry is stored as a separate entity tagged for the given
349
+ application and user.
350
+ """
351
+ _ = custom_metadata
352
+ for i, entry in enumerate(memories):
353
+ content_text = ""
354
+ if entry.content and entry.content.parts:
355
+ content_text = " ".join(
356
+ p.text for p in entry.content.parts if p.text
357
+ )
358
+
359
+ if not content_text:
360
+ continue
361
+
362
+ self._call_tool(
363
+ "mimir_remember",
364
+ {
365
+ "category": _MIMIR_CATEGORY,
366
+ "key": f"memory:{app_name}:{user_id}:{entry.id or i}",
367
+ "body_json": json.dumps({
368
+ "content": content_text,
369
+ "author": entry.author,
370
+ "timestamp": entry.timestamp,
371
+ "metadata": entry.custom_metadata,
372
+ }),
373
+ "tags": ["adk", "explicit", app_name],
374
+ },
375
+ )
376
+
377
+ @override
378
+ async def search_memory(
379
+ self,
380
+ *,
381
+ app_name: str,
382
+ user_id: str,
383
+ query: str,
384
+ ) -> SearchMemoryResponse:
385
+ """Searches Mimir for memories matching the query.
386
+
387
+ Uses Mimir's FTS5 keyword search. Results are scoped to the given
388
+ application and user by filtering on the stored tags and body content.
389
+
390
+ Args:
391
+ app_name: The application name for memory scope.
392
+ user_id: The user ID for memory scope.
393
+ query: The natural-language query to search for.
394
+
395
+ Returns:
396
+ A SearchMemoryResponse containing matching MemoryEntry objects.
397
+ """
398
+ scoped_query = f"{query} {app_name} adk-memory {user_id}"
399
+ result = self._call_tool(
400
+ "mimir_recall",
401
+ {
402
+ "query": scoped_query,
403
+ "limit": 20,
404
+ "category": _MIMIR_CATEGORY,
405
+ },
406
+ )
407
+
408
+ response = SearchMemoryResponse()
409
+ items = result.get("items", [])
410
+ for item in items:
411
+ body = item.get("body_json", "{}")
412
+ try:
413
+ body_data = json.loads(body) if isinstance(body, str) else body
414
+ except json.JSONDecodeError:
415
+ body_data = {}
416
+
417
+ # Determine the best text content to surface.
418
+ content_text = body_data.get("content", "")
419
+ if not content_text:
420
+ events = body_data.get("events", [])
421
+ texts = []
422
+ for ev in events:
423
+ for part in ev.get("parts", []):
424
+ if part.get("text"):
425
+ texts.append(part["text"])
426
+ content_text = " | ".join(texts[:5]) if texts else ""
427
+
428
+ if not content_text:
429
+ continue
430
+
431
+ response.memories.append(
432
+ MemoryEntry(
433
+ content=types.Content(
434
+ role="model",
435
+ parts=[types.Part.from_text(text=content_text)],
436
+ ),
437
+ author=body_data.get("author") or "mimir",
438
+ timestamp=body_data.get("timestamp")
439
+ or _format_timestamp(
440
+ item.get("created_at_unix_ms", 0) / 1000.0
441
+ ),
442
+ )
443
+ )
444
+
445
+ return response
@@ -0,0 +1,97 @@
1
+ """Agent that uses Perseus for live workspace context resolution.
2
+
3
+ Perseus (github.com/Perseus-Computing-LLC/perseus) is an open-source (MIT)
4
+ live context engine that resolves workspace state at inference time. Instead
5
+ of baking static instructions into prompts, agents use Perseus directives
6
+ (``@file``, ``@search``, ``@memory``, etc.) to pull in exactly what they
7
+ need.
8
+
9
+ This module provides a drop-in agent demonstrating the pattern:
10
+ 1. A ``before_agent_callback`` resolves Perseus context before each run.
11
+ 2. The resolved context is injected into the agent's instruction template.
12
+ 3. The agent knows about workspace state without it being hardcoded.
13
+
14
+ Usage::
15
+
16
+ from adk_mimir_memory.perseus_context import perseus_context_agent
17
+
18
+ # Use as a standalone agent
19
+ runner.run_async(
20
+ user_id="user",
21
+ session_id="session",
22
+ new_message=types.Content(...),
23
+ agent=perseus_context_agent,
24
+ )
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import os
30
+ import subprocess
31
+
32
+ from google.adk.agents import Agent
33
+ from google.adk.agents.callback_context import CallbackContext
34
+
35
+ _PERSEUS_BINARY = os.environ.get("PERSEUS_BINARY", "perseus")
36
+
37
+
38
+ def resolve_perseus_context(callback_context: CallbackContext) -> None:
39
+ """Resolves Perseus directives and stores the result in agent state.
40
+
41
+ Called before each agent run. Reads directives from a state key
42
+ (defaulting to a sensible set) and resolves them via the Perseus CLI.
43
+ """
44
+ directives = callback_context.state.get(
45
+ "_perseus_directives",
46
+ "@file AGENTS.md @file README.md",
47
+ )
48
+
49
+ workspace = callback_context.state.get("_perseus_workspace", os.getcwd())
50
+
51
+ try:
52
+ result = subprocess.run(
53
+ [_PERSEUS_BINARY, "resolve", directives],
54
+ capture_output=True,
55
+ text=True,
56
+ timeout=15,
57
+ cwd=workspace,
58
+ )
59
+ if result.returncode == 0 and result.stdout.strip():
60
+ callback_context.state["_perseus_context"] = result.stdout.strip()
61
+ else:
62
+ callback_context.state["_perseus_context"] = (
63
+ f"(Perseus: no context resolved for directives: {directives})"
64
+ )
65
+ except FileNotFoundError:
66
+ callback_context.state["_perseus_context"] = (
67
+ "(Perseus CLI not installed. Install with: pip install perseus-ctx)"
68
+ )
69
+ except subprocess.TimeoutExpired:
70
+ callback_context.state["_perseus_context"] = (
71
+ "(Perseus resolution timed out)"
72
+ )
73
+
74
+
75
+ perseus_context_agent = Agent(
76
+ name="perseus_context_agent",
77
+ description=(
78
+ "Agent with live workspace context via Perseus. Knows about "
79
+ "project files, git state, and workspace structure without "
80
+ "hardcoded instructions."
81
+ ),
82
+ before_agent_callback=resolve_perseus_context,
83
+ instruction="""\
84
+ You are a helpful assistant with live context about the current workspace.
85
+
86
+ The following context was resolved by Perseus from the workspace files
87
+ and state. Use it to answer questions accurately.
88
+
89
+ --- BEGIN PERSEUS CONTEXT ---
90
+ {_perseus_context}
91
+ --- END PERSEUS CONTEXT ---
92
+
93
+ Use this context to give grounded, file-aware answers. If the context
94
+ is empty or unavailable, let the user know and fall back to general
95
+ knowledge.
96
+ """,
97
+ )
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: adk-mimir-memory
3
+ Version: 0.2.0
4
+ Summary: Persistent, local, encrypted cross-session memory for Google ADK agents — backed by Mimir.
5
+ Project-URL: Homepage, https://github.com/Perseus-Computing-LLC/adk-mimir-memory
6
+ Project-URL: Repository, https://github.com/Perseus-Computing-LLC/adk-mimir-memory
7
+ Project-URL: Bug Tracker, https://github.com/Perseus-Computing-LLC/adk-mimir-memory/issues
8
+ Author-email: Thomas Connally <51974392+tcconnally@users.noreply.github.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: google-adk>=1.0.0
22
+ Provides-Extra: perseus
23
+ Requires-Dist: perseus-ctx; extra == 'perseus'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # ADK Mimir Memory
27
+
28
+ Persistent, local, encrypted cross-session memory for [Google ADK](https://github.com/google/adk-python) agents — backed by [Mimir](https://github.com/Perseus-Computing-LLC/mimir).
29
+
30
+ ## Why Mimir?
31
+
32
+ | Backend | Dependencies | Encryption | Hybrid Search | Local |
33
+ |---|---|---|---|---|
34
+ | **InMemoryMemoryService** | None | ❌ | ❌ | ✅ |
35
+ | **VertexAiMemoryBankService** | GCP + Gemini | ❌ | Gemini-driven | ❌ |
36
+ | **VertexAiRagMemoryService** | GCP + RAG | ❌ | GCP vector | ❌ |
37
+ | **MimirMemoryService** | **Single binary** | **✅ AES-256** | **✅ BM25+FTS5+Dense** | **✅** |
38
+
39
+ - **Zero cloud dependencies** — a single Rust binary, SQLite database, fully local
40
+ - **AES-256-GCM encryption** at rest — your memory data stays private
41
+ - **Hybrid search** — BM25 keyword + FTS5 + dense vector search
42
+ - **30+ MCP tools** — remember, recall, synthesize, benchmark, federate, and more
43
+ - **Ebbinghaus confidence decay** — memories fade naturally, important ones persist
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install adk-mimir-memory
49
+ ```
50
+
51
+ This package requires the `mimir` binary. Download it from:
52
+ https://github.com/Perseus-Computing-LLC/mimir/releases
53
+
54
+ Or build from source:
55
+ ```bash
56
+ cargo install mimir
57
+ ```
58
+
59
+ ## Quick Start
60
+
61
+ ```python
62
+ from google.adk.agents import Agent
63
+ from adk_mimir_memory import MimirMemoryService
64
+
65
+ agent = Agent(
66
+ name="my_agent",
67
+ model="gemini-2.5-flash",
68
+ instruction="You are a helpful assistant with persistent memory.",
69
+ memory_service=MimirMemoryService(
70
+ db_path="~/.adk/mimir.db",
71
+ ),
72
+ )
73
+ ```
74
+
75
+ That's it. Sessions, events, and explicit memories are now persisted across restarts.
76
+
77
+ ### Configuration
78
+
79
+ ```python
80
+ # Custom database location
81
+ MimirMemoryService(db_path="/data/agent_memory.db")
82
+
83
+ # Custom mimir binary path (if not on $PATH)
84
+ MimirMemoryService(mimir_binary="/usr/local/bin/mimir")
85
+
86
+ # Both
87
+ MimirMemoryService(
88
+ db_path="/data/agent_memory.db",
89
+ mimir_binary="/usr/local/bin/mimir",
90
+ )
91
+ ```
92
+
93
+ ## Perseus Live Context (Optional)
94
+
95
+ This package also includes a drop-in agent with live workspace awareness via [Perseus](https://github.com/Perseus-Computing-LLC/perseus):
96
+
97
+ ```python
98
+ from adk_mimir_memory.perseus_context import perseus_context_agent
99
+
100
+ # The agent resolves @file, @search, @memory directives at inference time
101
+ runner.run_async(
102
+ user_id="user",
103
+ session_id="session",
104
+ new_message=types.Content(role="user", parts=[types.Part.from_text(
105
+ text="What does the README say about deployment?"
106
+ )]),
107
+ agent=perseus_context_agent,
108
+ )
109
+ ```
110
+
111
+ ```bash
112
+ pip install adk-mimir-memory[perseus] # installs perseus-ctx
113
+ ```
114
+
115
+ Set directives via session state:
116
+ ```python
117
+ session = await runner.session_service.create_session(
118
+ app_name="my_app",
119
+ user_id="user",
120
+ state={
121
+ "_perseus_directives": "@file AGENTS.md @file README.md @memory deployment",
122
+ "_perseus_workspace": "/path/to/project",
123
+ },
124
+ )
125
+ ```
126
+
127
+ ## How It Works
128
+
129
+ ```
130
+ ┌─────────────┐ JSON-RPC (MCP stdio) ┌──────────┐
131
+ │ ADK Agent │ ──────────────────────────▶ │ Mimir │
132
+ │ (Python) │ ◀────────────────────────── │ (Rust) │
133
+ └─────────────┘ └────┬─────┘
134
+
135
+ SQLite + FTS5
136
+ (AES-256-GCM)
137
+ ```
138
+
139
+ The `MimirMemoryService` spawns a `mimir` subprocess and communicates via JSON-RPC over stdin/stdout (MCP stdio transport). Each `add_session_to_memory`, `add_memory`, and `search_memory` call translates to a Mimir MCP tool invocation.
140
+
141
+ ## License
142
+
143
+ MIT — see [Mimir](https://github.com/Perseus-Computing-LLC/mimir) and [Perseus](https://github.com/Perseus-Computing-LLC/perseus) for the backing services.
@@ -0,0 +1,7 @@
1
+ adk_mimir_memory/__init__.py,sha256=UDGvEDFhgbT1YvvIfDxySdYX7IWKFuxB78MIMRitWt8,631
2
+ adk_mimir_memory/mimir_memory_service.py,sha256=8n90pabJ_LvLcI2QRR0eGNCGEPnNHgEmsOwJBXEfCWU,14799
3
+ adk_mimir_memory/perseus_context.py,sha256=kqUQflvk0bOpYk6eOekD656ipE1opb_aEjl77lKIniM,3279
4
+ adk_mimir_memory-0.2.0.dist-info/METADATA,sha256=MEFFX9OUFWGJKV2CVSXizsxoWUkxoKHGzpCUY0dwl-c,5164
5
+ adk_mimir_memory-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
6
+ adk_mimir_memory-0.2.0.dist-info/licenses/LICENSE,sha256=bqAeZtvmBxdLFJqhQxro1FOiMkDOdSJS5sMi8QKLjd8,1074
7
+ adk_mimir_memory-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Perseus Computing
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.