pi-mnemosyne 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/index.ts +357 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 gandazgul
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# pi-mnemosyne
|
|
2
|
+
|
|
3
|
+
Pi extension for **local persistent memory** using [Mnemosyne](https://github.com/gandazgul/mnemosyne). Gives your AI coding agent memory that persists across sessions — entirely offline, no cloud APIs.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Install the mnemosyne binary first:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# From source (requires Go 1.21+, GCC, Task)
|
|
11
|
+
git clone https://github.com/gandazgul/mnemosyne.git
|
|
12
|
+
cd mnemosyne
|
|
13
|
+
task install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
See the [mnemosyne README](https://github.com/gandazgul/mnemosyne#quick-start) for detailed setup instructions. On first use, mnemosyne will automatically download its ML models (~500 MB one-time).
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# From npm (when published)
|
|
22
|
+
pi install npm:pi-mnemosyne
|
|
23
|
+
|
|
24
|
+
# From local path (for development)
|
|
25
|
+
pi install ./pi-mnemosyne
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## What it does
|
|
29
|
+
|
|
30
|
+
### Core Memories
|
|
31
|
+
|
|
32
|
+
Core memories are tagged with `core` and **automatically injected into the system prompt** at the start of every session (and after compaction). They work like `AGENTS.md` — always-available context that the agent can reference without explicitly searching.
|
|
33
|
+
|
|
34
|
+
Use core memories for:
|
|
35
|
+
- Project architecture and key conventions
|
|
36
|
+
- Important user preferences
|
|
37
|
+
- Critical decisions that should never be forgotten
|
|
38
|
+
|
|
39
|
+
**Keep core memories lean** — they're injected into every prompt and consume context tokens.
|
|
40
|
+
|
|
41
|
+
### Tools
|
|
42
|
+
|
|
43
|
+
The extension registers five tools available to the AI agent:
|
|
44
|
+
|
|
45
|
+
| Tool | Description |
|
|
46
|
+
|------|-------------|
|
|
47
|
+
| `memory_recall` | Search project memory for relevant context and past decisions |
|
|
48
|
+
| `memory_recall_global` | Search global memory for cross-project preferences |
|
|
49
|
+
| `memory_store` | Store a project-scoped memory (optionally as `core`) |
|
|
50
|
+
| `memory_store_global` | Store a cross-project memory (optionally as `core`) |
|
|
51
|
+
| `memory_delete` | Delete an outdated memory by its document ID |
|
|
52
|
+
|
|
53
|
+
### Memory Scoping
|
|
54
|
+
|
|
55
|
+
| Scope | Collection | Persists across |
|
|
56
|
+
|-------|-----------|-----------------|
|
|
57
|
+
| Project | `<directory-name>` | Sessions in the same project |
|
|
58
|
+
| Global | `global` | All projects |
|
|
59
|
+
| Core (project) | `<directory-name>` (tagged `core`) | Sessions + injected into system prompt |
|
|
60
|
+
| Core (global) | `global` (tagged `core`) | All projects + injected into system prompt |
|
|
61
|
+
|
|
62
|
+
The project collection is auto-initialized when the extension loads. The global collection is created on first use of `memory_store_global`.
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
Mnemosyne is a local document store with hybrid search:
|
|
67
|
+
- **Full-text search** (SQLite FTS5, BM25 ranking)
|
|
68
|
+
- **Vector search** (sqlite-vec, cosine similarity with snowflake-arctic-embed-m-v1.5)
|
|
69
|
+
- **Reciprocal Rank Fusion** combines both for best results
|
|
70
|
+
|
|
71
|
+
All ML inference runs locally via ONNX Runtime. Your memories never leave your machine.
|
|
72
|
+
|
|
73
|
+
### Architecture
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Session start
|
|
77
|
+
│
|
|
78
|
+
├─► Auto-init project collection (mnemosyne init)
|
|
79
|
+
└─► Fetch core memories (local + global, tagged "core")
|
|
80
|
+
│
|
|
81
|
+
▼
|
|
82
|
+
Cached in memory
|
|
83
|
+
│
|
|
84
|
+
Each turn (before_agent_start)
|
|
85
|
+
│
|
|
86
|
+
└─► Append cached core memories to system prompt
|
|
87
|
+
(provider-cached, survives compaction)
|
|
88
|
+
|
|
89
|
+
Agent uses tools
|
|
90
|
+
│
|
|
91
|
+
├─► memory_recall / memory_recall_global → mnemosyne search
|
|
92
|
+
├─► memory_store / memory_store_global → mnemosyne add [--tag core]
|
|
93
|
+
│ └─► If core=true → invalidate cache (re-fetched next turn)
|
|
94
|
+
└─► memory_delete → mnemosyne delete
|
|
95
|
+
└─► Invalidate cache (re-fetched next turn)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Link locally for development
|
|
102
|
+
pi install ./pi-mnemosyne
|
|
103
|
+
|
|
104
|
+
# Check it's installed
|
|
105
|
+
pi list
|
|
106
|
+
|
|
107
|
+
# Start pi — the extension loads automatically
|
|
108
|
+
pi
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The extension uses TypeScript with pi's built-in jiti loader — no build step required.
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-mnemosyne — Local persistent memory for the pi AI agent.
|
|
3
|
+
*
|
|
4
|
+
* Gives the agent memory that persists across sessions using Mnemosyne,
|
|
5
|
+
* a local document store with hybrid search (BM25 + vector similarity).
|
|
6
|
+
* All ML inference runs locally via ONNX Runtime. No cloud APIs required.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Core memories (tagged `core`) injected into the system prompt at session start
|
|
10
|
+
* - memory_recall / memory_recall_global tools for on-demand search
|
|
11
|
+
* - memory_store / memory_store_global tools with optional `core` tagging
|
|
12
|
+
* - memory_delete tool for removing outdated memories
|
|
13
|
+
* - Cache invalidation: core memory cache refreshed only when dirty
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import type {ExtensionAPI} from "@mariozechner/pi-coding-agent";
|
|
19
|
+
import {Type} from "@sinclair/typebox";
|
|
20
|
+
|
|
21
|
+
export default function mnemosyneExtension(pi: ExtensionAPI): void {
|
|
22
|
+
let projectName = "";
|
|
23
|
+
let projectCwd = "";
|
|
24
|
+
|
|
25
|
+
// ── Debug support ────────────────────────────────────────────────
|
|
26
|
+
let debugEnabled = false;
|
|
27
|
+
const debugLog: string[] = [];
|
|
28
|
+
|
|
29
|
+
function debug(message: string): void {
|
|
30
|
+
const timestamp = new Date().toISOString();
|
|
31
|
+
const line = `[${timestamp}] ${message}`;
|
|
32
|
+
debugLog.push(line);
|
|
33
|
+
if (debugEnabled) {
|
|
34
|
+
try {
|
|
35
|
+
const debugPath = path.join(projectCwd || ".", ".mnemosyne-debug.log");
|
|
36
|
+
fs.appendFileSync(debugPath, line + "\n", "utf-8");
|
|
37
|
+
} catch { /* best effort */ }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeDebugPrompt(fullPrompt: string): void {
|
|
42
|
+
if (!debugEnabled) return;
|
|
43
|
+
try {
|
|
44
|
+
const debugInfo = [
|
|
45
|
+
"=== Mnemosyne Debug Info ===",
|
|
46
|
+
`Timestamp: ${new Date().toISOString()}`,
|
|
47
|
+
`Project Name: ${projectName}`,
|
|
48
|
+
`Project CWD: ${projectCwd}`,
|
|
49
|
+
`Cache Valid: ${cacheValid}`,
|
|
50
|
+
`Cached Core Block Length: ${cachedCoreBlock.length}`,
|
|
51
|
+
`Debug Log (last 50 entries):`,
|
|
52
|
+
...debugLog.slice(-50),
|
|
53
|
+
"",
|
|
54
|
+
"=== System Prompt ===",
|
|
55
|
+
fullPrompt,
|
|
56
|
+
].join("\n");
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
path.join(projectCwd, ".mnemosyne-debug-prompt.txt"),
|
|
59
|
+
debugInfo,
|
|
60
|
+
"utf-8",
|
|
61
|
+
);
|
|
62
|
+
} catch { /* best effort */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Core memory cache ─────────────────────────────────────────────
|
|
66
|
+
let cachedCoreBlock = "";
|
|
67
|
+
let cacheValid = false;
|
|
68
|
+
|
|
69
|
+
// ── Helper: execute mnemosyne CLI ─────────────────────────────────
|
|
70
|
+
|
|
71
|
+
async function mnemosyne(...args: string[]): Promise<string> {
|
|
72
|
+
debug(`exec: mnemosyne ${args.join(" ")}`);
|
|
73
|
+
try {
|
|
74
|
+
const result = await pi.exec("mnemosyne", args, { cwd: projectCwd });
|
|
75
|
+
debug(`exec result: code=${result.code} stdout=${result.stdout.length}bytes stderr=${result.stderr.length}bytes`);
|
|
76
|
+
|
|
77
|
+
if (result.code !== 0) {
|
|
78
|
+
const errMsg = result.stderr.trim() || `mnemosyne ${args[0]} failed (exit ${result.code})`;
|
|
79
|
+
// Exit code 127 = command not found in shell
|
|
80
|
+
if (result.code === 127 || errMsg.includes("not found") || errMsg.includes("ENOENT") || errMsg.includes("No such file")) {
|
|
81
|
+
debug(`ERROR: mnemosyne binary not found`);
|
|
82
|
+
return "Error: mnemosyne binary not found. Install it: https://github.com/gandazgul/mnemosyne#quick-start";
|
|
83
|
+
}
|
|
84
|
+
debug(`ERROR: ${errMsg}`);
|
|
85
|
+
throw new Error(errMsg);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// mnemosyne writes output to stderr, use whichever has content
|
|
89
|
+
return result.stdout || result.stderr;
|
|
90
|
+
} catch (e: unknown) {
|
|
91
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
92
|
+
debug(`CATCH: ${msg}`);
|
|
93
|
+
if (
|
|
94
|
+
msg.includes("not found") ||
|
|
95
|
+
msg.includes("ENOENT") ||
|
|
96
|
+
msg.includes("No such file")
|
|
97
|
+
) {
|
|
98
|
+
return "Error: mnemosyne binary not found. Install it: https://github.com/gandazgul/mnemosyne#quick-start";
|
|
99
|
+
}
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Helper: fetch and format core memories ────────────────────────
|
|
105
|
+
|
|
106
|
+
async function fetchCoreMemories(): Promise<string> {
|
|
107
|
+
debug(`fetchCoreMemories: projectName=${projectName}, cwd=${projectCwd}`);
|
|
108
|
+
const sections: string[] = [];
|
|
109
|
+
|
|
110
|
+
// Fetch project core memories
|
|
111
|
+
try {
|
|
112
|
+
const localCore = await mnemosyne(
|
|
113
|
+
"list", "--name", projectName, "--tag", "core", "--format", "plain",
|
|
114
|
+
);
|
|
115
|
+
const trimmed = localCore.trim();
|
|
116
|
+
debug(`project core: ${trimmed.length} chars, starts with: ${JSON.stringify(trimmed.substring(0, 80))}`);
|
|
117
|
+
if (trimmed && !trimmed.startsWith("No documents")) {
|
|
118
|
+
sections.push(`Project Core Memories (${projectName}):\n\n${trimmed}`);
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
debug(`project core error: ${e instanceof Error ? e.message : String(e)}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fetch global core memories
|
|
125
|
+
try {
|
|
126
|
+
const globalCore = await mnemosyne(
|
|
127
|
+
"list", "--global", "--tag", "core", "--format", "plain",
|
|
128
|
+
);
|
|
129
|
+
const trimmed = globalCore.trim();
|
|
130
|
+
debug(`global core: ${trimmed.length} chars, starts with: ${JSON.stringify(trimmed.substring(0, 80))}`);
|
|
131
|
+
if (trimmed && !trimmed.startsWith("No documents")) {
|
|
132
|
+
sections.push(`Global Core Memories:\n\n${trimmed}`);
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
debug(`global core error: ${e instanceof Error ? e.message : String(e)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const memoriesBlock = sections.length > 0
|
|
139
|
+
? `\n\n${sections.join("\n\n")}`
|
|
140
|
+
: "";
|
|
141
|
+
|
|
142
|
+
return `\n\n${memoriesBlock}
|
|
143
|
+
|
|
144
|
+
When to use memory:
|
|
145
|
+
- Search memory when past context would help answer the user's request.
|
|
146
|
+
- Store concise summaries of important decisions, preferences, and patterns.
|
|
147
|
+
- Delete outdated memories when new decisions contradict them.
|
|
148
|
+
- Use **core** for facts that should always be in context (project architecture, key conventions, user preferences).
|
|
149
|
+
- Use **global** variants for cross-project preferences (coding style, tool choices).
|
|
150
|
+
- At the end of a session, store any relevant memories for future sessions.`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Helper: refresh cache if needed ───────────────────────────────
|
|
154
|
+
|
|
155
|
+
async function ensureCacheValid(): Promise<void> {
|
|
156
|
+
if (cacheValid) return;
|
|
157
|
+
cachedCoreBlock = await fetchCoreMemories();
|
|
158
|
+
cacheValid = true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function invalidateCache(): void {
|
|
162
|
+
cacheValid = false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Session start: init collection + load core memories ──────────
|
|
166
|
+
|
|
167
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
168
|
+
projectCwd = ctx.cwd;
|
|
169
|
+
|
|
170
|
+
// Check for debug flag file
|
|
171
|
+
try {
|
|
172
|
+
fs.accessSync(path.join(projectCwd, ".mnemosyne-debug"));
|
|
173
|
+
debugEnabled = true;
|
|
174
|
+
// Clear previous debug log file
|
|
175
|
+
try { fs.writeFileSync(path.join(projectCwd, ".mnemosyne-debug.log"), "", "utf-8"); } catch { /* ok */ }
|
|
176
|
+
} catch {
|
|
177
|
+
debugEnabled = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
debug(`session_start: cwd=${projectCwd}, debugEnabled=${debugEnabled}`);
|
|
181
|
+
|
|
182
|
+
// Resolve project name from cwd basename
|
|
183
|
+
const rawName = path.basename(projectCwd);
|
|
184
|
+
projectName = rawName === "global" ? "default" : (rawName || "default");
|
|
185
|
+
|
|
186
|
+
debug(`project: name=${projectName}`);
|
|
187
|
+
|
|
188
|
+
// Auto-init the project collection (idempotent)
|
|
189
|
+
try {
|
|
190
|
+
await mnemosyne("init", "--name", projectName);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
debug(`init error: ${e instanceof Error ? e.message : String(e)}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Pre-fetch core memories
|
|
196
|
+
invalidateCache();
|
|
197
|
+
await ensureCacheValid();
|
|
198
|
+
debug(`session_start complete: cacheValid=${cacheValid}, coreBlockLength=${cachedCoreBlock.length}`);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ── Before agent start: inject core memories into system prompt ──
|
|
202
|
+
|
|
203
|
+
pi.on("before_agent_start", async (event) => {
|
|
204
|
+
await ensureCacheValid();
|
|
205
|
+
|
|
206
|
+
const fullPrompt = event.systemPrompt + cachedCoreBlock;
|
|
207
|
+
|
|
208
|
+
debug(`before_agent_start: systemPrompt=${event.systemPrompt.length}chars, coreBlock=${cachedCoreBlock.length}chars, total=${fullPrompt.length}chars`);
|
|
209
|
+
writeDebugPrompt(fullPrompt);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
systemPrompt: fullPrompt,
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ── Tools ─────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
pi.registerTool({
|
|
219
|
+
name: "memory_recall",
|
|
220
|
+
label: "Memory Recall",
|
|
221
|
+
description:
|
|
222
|
+
"Search project memory for relevant context, past decisions, and preferences. Use this at the start of conversations and whenever past context would help.",
|
|
223
|
+
promptSnippet: "Search project memory for past context and decisions",
|
|
224
|
+
parameters: Type.Object({
|
|
225
|
+
query: Type.String({ description: "Semantic search query" }),
|
|
226
|
+
}),
|
|
227
|
+
|
|
228
|
+
async execute(_toolCallId, params) {
|
|
229
|
+
// Quote the query to prevent SQLite FTS errors with hyphens and special characters
|
|
230
|
+
const safeQuery = `"${params.query.replaceAll('"', '""')}"`;
|
|
231
|
+
const result = await mnemosyne(
|
|
232
|
+
"search", "--name", projectName, "--format", "plain", safeQuery,
|
|
233
|
+
);
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: "text", text: result.trim() || "No memories found." }],
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
pi.registerTool({
|
|
241
|
+
name: "memory_recall_global",
|
|
242
|
+
label: "Memory Recall Global",
|
|
243
|
+
description:
|
|
244
|
+
"Search global memory for cross-project preferences, decisions and patterns.",
|
|
245
|
+
promptSnippet: "Search global memory for cross-project preferences",
|
|
246
|
+
parameters: Type.Object({
|
|
247
|
+
query: Type.String({ description: "Semantic search query" }),
|
|
248
|
+
}),
|
|
249
|
+
|
|
250
|
+
async execute(_toolCallId, params) {
|
|
251
|
+
const safeQuery = `"${params.query.replaceAll('"', '""')}"`;
|
|
252
|
+
const result = await mnemosyne(
|
|
253
|
+
"search", "--global", "--format", "plain", safeQuery,
|
|
254
|
+
);
|
|
255
|
+
return {
|
|
256
|
+
content: [{ type: "text", text: result.trim() || "No global memories found." }],
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
pi.registerTool({
|
|
262
|
+
name: "memory_store",
|
|
263
|
+
label: "Memory Store",
|
|
264
|
+
description:
|
|
265
|
+
"Store a project memory: a decision, preference, or important context. One concise concept per memory. Set core=true for critical context that should always be available in every session (use sparingly).",
|
|
266
|
+
promptSnippet: "Store a project-scoped memory (decision, preference, context)",
|
|
267
|
+
promptGuidelines: [
|
|
268
|
+
"Use memory_store to save important decisions, preferences, and context for future sessions.",
|
|
269
|
+
"Set core=true only for critical, always-relevant context (like project architecture or key conventions). Core memories are injected into every prompt, so keep them lean.",
|
|
270
|
+
],
|
|
271
|
+
parameters: Type.Object({
|
|
272
|
+
content: Type.String({ description: "Concise memory to store" }),
|
|
273
|
+
core: Type.Optional(Type.Boolean({
|
|
274
|
+
description: "If true, this memory is always injected into context (like AGENTS.md). Use sparingly.",
|
|
275
|
+
})),
|
|
276
|
+
}),
|
|
277
|
+
|
|
278
|
+
async execute(_toolCallId, params) {
|
|
279
|
+
const args = ["add", "--name", projectName];
|
|
280
|
+
if (params.core) {
|
|
281
|
+
args.push("--tag", "core");
|
|
282
|
+
}
|
|
283
|
+
args.push(params.content);
|
|
284
|
+
|
|
285
|
+
const result = await mnemosyne(...args);
|
|
286
|
+
|
|
287
|
+
if (params.core) {
|
|
288
|
+
invalidateCache();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
content: [{ type: "text", text: result.trim() }],
|
|
293
|
+
};
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
pi.registerTool({
|
|
298
|
+
name: "memory_store_global",
|
|
299
|
+
label: "Memory Store Global",
|
|
300
|
+
description:
|
|
301
|
+
"Store a cross-project memory: personal preferences, coding style, tool choices. Set core=true for critical cross-project context that should always be available.",
|
|
302
|
+
promptSnippet: "Store a cross-project memory (coding style, tool choices)",
|
|
303
|
+
parameters: Type.Object({
|
|
304
|
+
content: Type.String({ description: "Global memory to store" }),
|
|
305
|
+
core: Type.Optional(Type.Boolean({
|
|
306
|
+
description: "If true, this memory is always injected into context. Use sparingly.",
|
|
307
|
+
})),
|
|
308
|
+
}),
|
|
309
|
+
|
|
310
|
+
async execute(_toolCallId, params) {
|
|
311
|
+
// Ensure the global collection exists
|
|
312
|
+
try {
|
|
313
|
+
await mnemosyne("init", "--global");
|
|
314
|
+
} catch {
|
|
315
|
+
// Already exists — fine
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const args = ["add", "--global"];
|
|
319
|
+
if (params.core) {
|
|
320
|
+
args.push("--tag", "core");
|
|
321
|
+
}
|
|
322
|
+
args.push(params.content);
|
|
323
|
+
|
|
324
|
+
const result = await mnemosyne(...args);
|
|
325
|
+
|
|
326
|
+
if (params.core) {
|
|
327
|
+
invalidateCache();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
content: [{ type: "text", text: result.trim() }],
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
pi.registerTool({
|
|
337
|
+
name: "memory_delete",
|
|
338
|
+
label: "Memory Delete",
|
|
339
|
+
description:
|
|
340
|
+
"Delete an outdated or incorrect memory by its document ID (shown in [brackets] in recall/list results).",
|
|
341
|
+
promptSnippet: "Delete an outdated memory by document ID",
|
|
342
|
+
parameters: Type.Object({
|
|
343
|
+
id: Type.Number({ description: "Document ID to delete" }),
|
|
344
|
+
}),
|
|
345
|
+
|
|
346
|
+
async execute(_toolCallId, params) {
|
|
347
|
+
const result = await mnemosyne("delete", String(params.id));
|
|
348
|
+
|
|
349
|
+
// Invalidate cache since we don't know if the deleted memory was core
|
|
350
|
+
invalidateCache();
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
content: [{ type: "text", text: result.trim() }],
|
|
354
|
+
};
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-mnemosyne",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pi extension for local persistent memory using Mnemosyne — offline semantic search, no cloud required",
|
|
6
|
+
"author": "gandazgul",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/gandazgul/mnemosyne.git",
|
|
11
|
+
"directory": "pi-mnemosyne"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pi-package",
|
|
15
|
+
"mnemosyne",
|
|
16
|
+
"memory",
|
|
17
|
+
"local",
|
|
18
|
+
"offline",
|
|
19
|
+
"semantic-search",
|
|
20
|
+
"ai",
|
|
21
|
+
"coding-agent"
|
|
22
|
+
],
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.ts",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@mariozechner/pi-coding-agent": ">=0.53.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.5.2"
|
|
37
|
+
}
|
|
38
|
+
}
|