pi-mnemoteca 0.3.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 +141 -0
- package/index.ts +366 -0
- package/package.json +43 -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,141 @@
|
|
|
1
|
+
# pi-mnemoteca
|
|
2
|
+
|
|
3
|
+
Pi extension for **local persistent memory** using
|
|
4
|
+
[Mnemoteca](https://github.com/gandazgul/mnemoteca). It gives your AI coding
|
|
5
|
+
agent memory that persists across sessions. It is offline and does not use cloud
|
|
6
|
+
APIs.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
Install the `mnemoteca` binary first:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
curl -fsSL https://raw.githubusercontent.com/gandazgul/mnemoteca/main/install.sh | sh
|
|
14
|
+
mnemoteca setup
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
See the [Mnemoteca README](https://github.com/gandazgul/mnemoteca#installation)
|
|
18
|
+
for detailed setup instructions. On first use, Mnemoteca downloads its ML models,
|
|
19
|
+
approximately 500 MB one time.
|
|
20
|
+
|
|
21
|
+
Make sure the `mnemoteca` binary is in your `PATH`.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install from npm:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pi install npm:pi-mnemoteca
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Install from a local checkout during development:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pi install ./pi-mnemoteca
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Upgrade from pi-mnemosyne
|
|
38
|
+
|
|
39
|
+
If you already used the old Pi extension, stop Pi agents before you change
|
|
40
|
+
packages.
|
|
41
|
+
|
|
42
|
+
1. Migrate CLI data first if needed. Use the
|
|
43
|
+
[Mnemoteca migration guide](https://github.com/gandazgul/mnemoteca/blob/main/docs/migrate-from-mnemosyne.md).
|
|
44
|
+
2. Install the new extension at the same scope where the old extension was
|
|
45
|
+
installed:
|
|
46
|
+
```bash
|
|
47
|
+
pi install npm:pi-mnemoteca
|
|
48
|
+
```
|
|
49
|
+
3. Verify that Pi loads the new extension and that memory tools work:
|
|
50
|
+
```bash
|
|
51
|
+
pi list
|
|
52
|
+
```
|
|
53
|
+
Store and recall a harmless test memory if needed.
|
|
54
|
+
4. Remove the old package at the matching scope:
|
|
55
|
+
```bash
|
|
56
|
+
pi remove npm:pi-mnemosyne
|
|
57
|
+
```
|
|
58
|
+
5. Restart Pi agents.
|
|
59
|
+
|
|
60
|
+
If your old installation was project-local, run the install and remove commands
|
|
61
|
+
from that project. If it was user-level, use the same user-level Pi context. Do
|
|
62
|
+
not keep `pi-mnemoteca` and `pi-mnemosyne` active together for normal use.
|
|
63
|
+
|
|
64
|
+
Windows users must finish this replacement before restarting Pi agents. There is
|
|
65
|
+
no Windows `mnemosyne` compatibility shim, alias, copied executable, or renamed
|
|
66
|
+
executable.
|
|
67
|
+
|
|
68
|
+
## Memory tools
|
|
69
|
+
|
|
70
|
+
The agent-facing tool names stay stable. They describe memory capabilities, not
|
|
71
|
+
product branding.
|
|
72
|
+
|
|
73
|
+
| Tool | Purpose |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `memory_recall` | Search project memory. |
|
|
76
|
+
| `memory_recall_global` | Search global memory. |
|
|
77
|
+
| `memory_store` | Store a project memory. Set `core=true` to tag it as core. |
|
|
78
|
+
| `memory_store_global` | Store a global memory. Set `core=true` to tag it as core. |
|
|
79
|
+
| `memory_delete` | Delete a memory by the numeric document ID shown in recall or list output. |
|
|
80
|
+
|
|
81
|
+
Project memory uses a collection name derived from the project directory name.
|
|
82
|
+
If that name is empty or `global`, the extension uses `default`.
|
|
83
|
+
|
|
84
|
+
The project collection is initialized on `session_start`. The global collection
|
|
85
|
+
is created on first use of `mnemoteca add -g` or the equivalent global store
|
|
86
|
+
tool.
|
|
87
|
+
|
|
88
|
+
## Session behavior
|
|
89
|
+
|
|
90
|
+
On session start, the extension:
|
|
91
|
+
|
|
92
|
+
1. Stores the project working directory.
|
|
93
|
+
2. Checks for `.mnemoteca-debug`.
|
|
94
|
+
3. Initializes the project collection with `mnemoteca init`.
|
|
95
|
+
4. Fetches project and global core memories.
|
|
96
|
+
5. Caches the core-memory block.
|
|
97
|
+
|
|
98
|
+
Before each agent start, including after compaction, the extension appends the
|
|
99
|
+
cached core-memory block and memory-use guidance to the system prompt. Project
|
|
100
|
+
core memories appear before global core memories. If one core-memory query
|
|
101
|
+
fails, the other can still appear.
|
|
102
|
+
|
|
103
|
+
A non-core store does not invalidate the core cache. A core store and any delete
|
|
104
|
+
operation invalidate it, because the core-tag state can change.
|
|
105
|
+
|
|
106
|
+
## Debug files
|
|
107
|
+
|
|
108
|
+
Create `.mnemoteca-debug` in the project directory before session start to enable
|
|
109
|
+
debug output. The extension writes:
|
|
110
|
+
|
|
111
|
+
- `.mnemoteca-debug.log`
|
|
112
|
+
- `.mnemoteca-debug-prompt.txt`
|
|
113
|
+
|
|
114
|
+
Debug writes are best effort and do not stop agent execution.
|
|
115
|
+
|
|
116
|
+
## Commands taught to the agent
|
|
117
|
+
|
|
118
|
+
- Use `mnemoteca search -f plain [query]` and `mnemoteca search -g -f plain [query]` to search relevant memories.
|
|
119
|
+
- After significant decisions, use `mnemoteca add "memory content"` to save a concise fact. Use `mnemoteca add -g "memory content"` for cross-project preferences.
|
|
120
|
+
- Delete contradicted memories with `mnemoteca delete [memory id]` after storing the updated memory.
|
|
121
|
+
- Mark critical, always-relevant context as core with `-t core`. You can use repeated tags, such as `mnemoteca add "database is sqlite" -t core -t tech-stack`.
|
|
122
|
+
|
|
123
|
+
## How it works
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
session_start
|
|
127
|
+
├─ save cwd and derive project collection
|
|
128
|
+
├─ mnemoteca init
|
|
129
|
+
└─ fetch project and global core memories
|
|
130
|
+
|
|
131
|
+
before_agent_start
|
|
132
|
+
└─ append cached core memories and guidance
|
|
133
|
+
|
|
134
|
+
memory tools
|
|
135
|
+
├─ mnemoteca search
|
|
136
|
+
├─ mnemoteca add [-t core]
|
|
137
|
+
└─ mnemoteca delete
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The extension calls the `mnemoteca` executable with argument arrays. It does not
|
|
141
|
+
own data storage, select databases, or run migrations.
|
package/index.ts
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-mnemoteca — Local persistent memory for the pi AI agent.
|
|
3
|
+
*
|
|
4
|
+
* Gives the agent memory that persists across sessions using Mnemoteca,
|
|
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 mnemotecaExtension(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 || ".", ".mnemoteca-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
|
+
"=== Mnemoteca 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, ".mnemoteca-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 Mnemoteca CLI ─────────────────────────────────
|
|
70
|
+
|
|
71
|
+
async function mnemoteca(...args: string[]): Promise<string> {
|
|
72
|
+
debug(`exec: mnemoteca ${args.join(" ")}`);
|
|
73
|
+
try {
|
|
74
|
+
const result = await pi.exec("mnemoteca", 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() || `mnemoteca ${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: mnemoteca binary not found`);
|
|
82
|
+
return "Error: mnemoteca binary not found. Install it: https://github.com/gandazgul/mnemoteca#quick-start";
|
|
83
|
+
}
|
|
84
|
+
debug(`ERROR: ${errMsg}`);
|
|
85
|
+
throw new Error(errMsg);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Mnemoteca 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: mnemoteca binary not found. Install it: https://github.com/gandazgul/mnemoteca#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 mnemoteca(
|
|
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 mnemoteca(
|
|
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. Recall results include a document ID in square brackets (e.g. [42]). Pass that numeric ID to memory_delete to remove the memory.
|
|
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, ".mnemoteca-debug"));
|
|
173
|
+
debugEnabled = true;
|
|
174
|
+
// Clear previous debug log file
|
|
175
|
+
try { fs.writeFileSync(path.join(projectCwd, ".mnemoteca-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 mnemoteca("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 mnemoteca(
|
|
232
|
+
"search", "--name", projectName, "--format", "plain", safeQuery,
|
|
233
|
+
);
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: "text", text: result.trim() || "No memories found." }],
|
|
236
|
+
details: undefined,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
pi.registerTool({
|
|
242
|
+
name: "memory_recall_global",
|
|
243
|
+
label: "Memory Recall Global",
|
|
244
|
+
description:
|
|
245
|
+
"Search global memory for cross-project preferences, decisions and patterns.",
|
|
246
|
+
promptSnippet: "Search global memory for cross-project preferences",
|
|
247
|
+
parameters: Type.Object({
|
|
248
|
+
query: Type.String({ description: "Semantic search query" }),
|
|
249
|
+
}),
|
|
250
|
+
|
|
251
|
+
async execute(_toolCallId, params) {
|
|
252
|
+
const safeQuery = `"${params.query.replaceAll('"', '""')}"`;
|
|
253
|
+
const result = await mnemoteca(
|
|
254
|
+
"search", "--global", "--format", "plain", safeQuery,
|
|
255
|
+
);
|
|
256
|
+
return {
|
|
257
|
+
content: [{ type: "text", text: result.trim() || "No global memories found." }],
|
|
258
|
+
details: undefined,
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
pi.registerTool({
|
|
264
|
+
name: "memory_store",
|
|
265
|
+
label: "Memory Store",
|
|
266
|
+
description:
|
|
267
|
+
"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).",
|
|
268
|
+
promptSnippet: "Store a project-scoped memory (decision, preference, context)",
|
|
269
|
+
promptGuidelines: [
|
|
270
|
+
"Use memory_store to save important decisions, preferences, and context for future sessions.",
|
|
271
|
+
"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.",
|
|
272
|
+
],
|
|
273
|
+
parameters: Type.Object({
|
|
274
|
+
content: Type.String({ description: "Concise memory to store" }),
|
|
275
|
+
core: Type.Optional(Type.Boolean({
|
|
276
|
+
description: "If true, this memory is always injected into context (like AGENTS.md). Use sparingly.",
|
|
277
|
+
})),
|
|
278
|
+
}),
|
|
279
|
+
|
|
280
|
+
async execute(_toolCallId, params) {
|
|
281
|
+
const args = ["add", "--name", projectName];
|
|
282
|
+
if (params.core) {
|
|
283
|
+
args.push("--tag", "core");
|
|
284
|
+
}
|
|
285
|
+
args.push(params.content);
|
|
286
|
+
|
|
287
|
+
const result = await mnemoteca(...args);
|
|
288
|
+
|
|
289
|
+
if (params.core) {
|
|
290
|
+
invalidateCache();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: "text", text: result.trim() }],
|
|
295
|
+
details: undefined,
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
pi.registerTool({
|
|
301
|
+
name: "memory_store_global",
|
|
302
|
+
label: "Memory Store Global",
|
|
303
|
+
description:
|
|
304
|
+
"Store a cross-project memory: personal preferences, coding style, tool choices. Set core=true for critical cross-project context that should always be available.",
|
|
305
|
+
promptSnippet: "Store a cross-project memory (coding style, tool choices)",
|
|
306
|
+
parameters: Type.Object({
|
|
307
|
+
content: Type.String({ description: "Global memory to store" }),
|
|
308
|
+
core: Type.Optional(Type.Boolean({
|
|
309
|
+
description: "If true, this memory is always injected into context. Use sparingly.",
|
|
310
|
+
})),
|
|
311
|
+
}),
|
|
312
|
+
|
|
313
|
+
async execute(_toolCallId, params) {
|
|
314
|
+
// Ensure the global collection exists
|
|
315
|
+
try {
|
|
316
|
+
await mnemoteca("init", "--global");
|
|
317
|
+
} catch {
|
|
318
|
+
// Already exists — fine
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const args = ["add", "--global"];
|
|
322
|
+
if (params.core) {
|
|
323
|
+
args.push("--tag", "core");
|
|
324
|
+
}
|
|
325
|
+
args.push(params.content);
|
|
326
|
+
|
|
327
|
+
const result = await mnemoteca(...args);
|
|
328
|
+
|
|
329
|
+
if (params.core) {
|
|
330
|
+
invalidateCache();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
content: [{ type: "text", text: result.trim() }],
|
|
335
|
+
details: undefined,
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
pi.registerTool({
|
|
341
|
+
name: "memory_delete",
|
|
342
|
+
label: "Memory Delete",
|
|
343
|
+
description:
|
|
344
|
+
"Delete an outdated or incorrect memory by its document ID (shown in [brackets] in recall/list results).",
|
|
345
|
+
promptSnippet: "Delete an outdated memory by document ID",
|
|
346
|
+
promptGuidelines: [
|
|
347
|
+
"Use memory_delete to remove outdated, incorrect, or superseded memories.",
|
|
348
|
+
"First call memory_recall to find the memory. The document ID is shown in [brackets] (e.g. [42]) in recall and list results. Pass that numeric ID to memory_delete.",
|
|
349
|
+
],
|
|
350
|
+
parameters: Type.Object({
|
|
351
|
+
id: Type.Number({ description: "Document ID to delete" }),
|
|
352
|
+
}),
|
|
353
|
+
|
|
354
|
+
async execute(_toolCallId, params) {
|
|
355
|
+
const result = await mnemoteca("delete", String(params.id));
|
|
356
|
+
|
|
357
|
+
// Invalidate cache since we don't know if the deleted memory was core
|
|
358
|
+
invalidateCache();
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
content: [{ type: "text", text: result.trim() }],
|
|
362
|
+
details: undefined,
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-mnemoteca",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pi extension for local persistent memory using Mnemoteca — offline semantic search, no cloud required",
|
|
6
|
+
"author": "gandazgul",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/gandazgul/pi-mnemoteca.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"pi-package",
|
|
14
|
+
"mnemoteca",
|
|
15
|
+
"memory",
|
|
16
|
+
"local",
|
|
17
|
+
"offline",
|
|
18
|
+
"semantic-search",
|
|
19
|
+
"ai",
|
|
20
|
+
"coding-agent"
|
|
21
|
+
],
|
|
22
|
+
"pi": {
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"index.ts",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@mariozechner/pi-coding-agent": ">=0.53.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.5.2",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"tsx": "^4.20.6"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "tsc --noEmit -p tsconfig.test.json && tsx index.test.ts"
|
|
42
|
+
}
|
|
43
|
+
}
|