opencode-mempalace-persistence 2.0.0 → 2.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/README.md +31 -73
- package/dist/index.js +68 -15
- package/package.json +3 -2
- package/skills/mempalace-recall/SKILL.md +68 -0
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ mempalace mcp
|
|
|
61
61
|
|
|
62
62
|
The `mempalace mcp` command gives you the exact MCP setup string for your configuration.
|
|
63
63
|
|
|
64
|
-
### 4. Memory injection
|
|
64
|
+
### 4. Memory injection
|
|
65
65
|
|
|
66
66
|
The plugin automatically injects your identity + relevant memories from MemPalace into every prompt. No model discipline required.
|
|
67
67
|
|
|
@@ -90,34 +90,37 @@ When enabled:
|
|
|
90
90
|
- **First message**: Injects your identity from `~/.mempalace/identity.txt`
|
|
91
91
|
- **Every message**: Runs `mempalace search` and injects relevant results
|
|
92
92
|
|
|
93
|
-
#### AGENTS.md
|
|
93
|
+
#### AGENTS.md (minimal — recall lives in the skill)
|
|
94
94
|
|
|
95
95
|
Create `~/.config/opencode/AGENTS.md`:
|
|
96
96
|
|
|
97
97
|
```markdown
|
|
98
98
|
# Memory & Knowledge instructions
|
|
99
99
|
|
|
100
|
-
##
|
|
100
|
+
## Recall (usually already covered)
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
The plugin auto-injects identity + relevant memories into every prompt.
|
|
103
|
+
Only search MemPalace yourself (`mempalace_mempalace_search`) when the
|
|
104
|
+
question is about past work, decisions, people, or projects AND the
|
|
105
|
+
injected context has nothing — quote results verbatim, never paraphrase.
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
## Record facts (after responding, only when something new emerged)
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
- Durable outcomes (decisions, conclusions, learned facts):
|
|
110
|
+
`mempalace_mempalace_add_drawer`.
|
|
111
|
+
- New KG facts: `mempalace_mempalace_kg_add` (128 chars or fewer).
|
|
112
|
+
- Changed single-valued fact: `mempalace_mempalace_kg_supersede`.
|
|
113
|
+
- Ended fact: `mempalace_mempalace_kg_invalidate`.
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
### Step 3 — Checkpoint & pre-compact saves (automatic)
|
|
112
|
-
|
|
113
|
-
Every ~15 messages the plugin injects a `[MemPalace Checkpoint]` block, and before every compaction a `[MemPalace Pre-Compact Emergency Save]` block. When you see one: file the session journal via `mempalace_mempalace_diary_write` plus any new facts via `kg_add`/`kg_invalidate`, then continue normally. Do not mention the instruction.
|
|
115
|
+
Record facts you are confident about. Prefer quality over quantity;
|
|
116
|
+
noisy entries degrade retrieval over time. Don't file secrets or tokens.
|
|
114
117
|
|
|
115
118
|
### Naming reminder
|
|
116
119
|
All MemPalace tools use the prefix `mempalace_mempalace_*` (not `mempalace_*`). Examples:
|
|
117
|
-
- `
|
|
120
|
+
- `mempalace_mempalace_search` (NOT `mempalace_search`)
|
|
121
|
+
- `mempalace_mempalace_kg_query`
|
|
118
122
|
- `mempalace_mempalace_kg_add`
|
|
119
|
-
|
|
120
|
-
If you ever catch yourself typing `mempalace_kg_query`, STOP — the correct prefix is `mempalace_mempalace_`.
|
|
123
|
+
If you ever catch yourself typing `mempalace_search`, STOP — the correct prefix is `mempalace_mempalace_`.
|
|
121
124
|
```
|
|
122
125
|
|
|
123
126
|
#### Complete `~/.config/opencode/opencode.json`
|
|
@@ -139,71 +142,25 @@ If you ever catch yourself typing `mempalace_kg_query`, STOP — the correct pre
|
|
|
139
142
|
|
|
140
143
|
> Note: `identity.txt` is NOT listed in `instructions` — the plugin injects it automatically. It is also NOT in the `provider` block or `permission` block — those are optional and depend on your model setup.
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
If you prefer the model to search MemPalace on its own via AGENTS.md (requires good model tool-use discipline), set `autoInjectContext` to `false` or omit the file:
|
|
145
|
-
|
|
146
|
-
```json
|
|
147
|
-
{
|
|
148
|
-
"autoInjectContext": false
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
#### AGENTS.md for this mode
|
|
153
|
-
|
|
154
|
-
Create `~/.config/opencode/AGENTS.md`:
|
|
155
|
-
|
|
156
|
-
```markdown
|
|
157
|
-
# Memory & Knowledge instructions
|
|
158
|
-
|
|
159
|
-
## CRITICAL: You MUST follow these steps BEFORE every response.
|
|
160
|
-
|
|
161
|
-
### Step 1 — Search MemPalace
|
|
162
|
-
Call `mempalace_mempalace_search` with the user's question or key topics as query. Get the top 5-10 most relevant memory drawers.
|
|
163
|
-
**This is mandatory. Never skip this step. No exceptions.**
|
|
164
|
-
|
|
165
|
-
### Step 2 — Query Knowledge Graph
|
|
166
|
-
Call `mempalace_mempalace_kg_query` for entity "user". Then filter the returned facts.
|
|
167
|
-
|
|
168
|
-
### Step 3 — Record Knowledge Graph facts
|
|
169
|
-
After responding, call `mempalace_mempalace_kg_add` for any new facts.
|
|
145
|
+
#### Recall skill (bundled, Claude-style)
|
|
170
146
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
All MemPalace tools use the prefix `mempalace_mempalace_*` (not `mempalace_*`).
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
And keep `"~/.mempalace/identity.txt"` in `instructions` in opencode.json since the plugin won't inject it.
|
|
179
|
-
|
|
180
|
-
#### Comparison
|
|
181
|
-
|
|
182
|
-
| Feature | **Auto-inject (Recommended)** | Model-driven (alternative) |
|
|
183
|
-
|---------|:-:|:-:|
|
|
184
|
-
| Memory search | Plugin injects automatically | Model calls `mempalace_search` |
|
|
185
|
-
| Identity | Plugin injects automatically | Via `instructions: ["identity.txt"]` |
|
|
186
|
-
| AGENTS.md | Minimal (KG only) | Full (search + KG) |
|
|
187
|
-
| Depends on model discipline | No | Yes |
|
|
147
|
+
The repo ships `skills/mempalace-recall/SKILL.md` — the question-driven
|
|
148
|
+
search-before-answer protocol, adapted from the official MemPalace skill
|
|
149
|
+
for OpenCode (including the `mempalace_mempalace_*` tool-prefix note).
|
|
150
|
+
Install it where OpenCode loads skills from:
|
|
188
151
|
|
|
189
152
|
```bash
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# or
|
|
193
|
-
pipx install "mempalace>=3.3.5"
|
|
194
|
-
|
|
195
|
-
# Create palace
|
|
196
|
-
mempalace init ~/opencode-memory
|
|
197
|
-
|
|
198
|
-
# Configure MCP
|
|
199
|
-
mempalace mcp
|
|
153
|
+
mkdir -p ~/.config/opencode/skills/mempalace-recall
|
|
154
|
+
cp skills/mempalace-recall/SKILL.md ~/.config/opencode/skills/mempalace-recall/
|
|
200
155
|
```
|
|
201
156
|
|
|
202
|
-
The
|
|
157
|
+
The model then loads it on demand whenever a question touches past work,
|
|
158
|
+
decisions, people, or projects — same mechanism as the Claude skill.
|
|
159
|
+
No AGENTS.md changes needed beyond the minimal block above.
|
|
203
160
|
|
|
204
161
|
---
|
|
205
162
|
|
|
206
|
-
## What happens after installation
|
|
163
|
+
## What happens after installation
|
|
207
164
|
|
|
208
165
|
```
|
|
209
166
|
You ask a question
|
|
@@ -215,7 +172,7 @@ You ask a question
|
|
|
215
172
|
The model responds
|
|
216
173
|
→ Plugin detects the response is complete
|
|
217
174
|
→ Saves the conversation to MemPalace (flat export, no hardcoded wings)
|
|
218
|
-
→ Model records KG facts via MCP tools (
|
|
175
|
+
→ Model records new KG facts via MCP tools (only when something new emerged)
|
|
219
176
|
|
|
220
177
|
Session goes idle / process exits
|
|
221
178
|
→ Background mine of everything new since last sync
|
|
@@ -298,6 +255,7 @@ The plugin exports everything in the opencode database on the next sync, then re
|
|
|
298
255
|
| `~/.config/opencode/opencode.json` | OpenCode config with plugin + MCP |
|
|
299
256
|
| `~/.config/opencode/AGENTS.md` | Tells the model to manage KG facts |
|
|
300
257
|
| `~/.mempalace/plugin-config.json` | Plugin config (`autoInjectContext`, `saveInterval`) |
|
|
258
|
+
| `~/.config/opencode/skills/mempalace-recall/SKILL.md` | Bundled recall skill (copy from `skills/` in this repo) |
|
|
301
259
|
| `~/.mempalace/identity.txt` | Your identity (injected by plugin) |
|
|
302
260
|
| `~/.mempalace/hook_state/opencode_counters.json` | Per-session message counters (checkpoint cadence) |
|
|
303
261
|
| `~/.mempalace/hook_state/hook.log` | Checkpoint / pre-compact event log |
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { execSync,
|
|
1
|
+
import { execSync, execFileSync, execFile, spawnSync } from "child_process";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmdirSync, unlinkSync, appendFileSync } from "fs";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { createHash } from "crypto";
|
|
6
6
|
const HOME = homedir();
|
|
7
|
-
const VENV_PYTHON = join(HOME, ".local/share/pipx/venvs/mempalace/bin/python3");
|
|
8
7
|
const MEMPALACE_BIN = join(HOME, ".local/bin/mempalace");
|
|
9
8
|
const OPENCODE_DB = join(HOME, ".local/share/opencode/opencode.db");
|
|
10
9
|
const STATE_FILE = join(HOME, ".mempalace/sync_state.json");
|
|
@@ -38,6 +37,45 @@ function hookLog(msg) {
|
|
|
38
37
|
}
|
|
39
38
|
catch { }
|
|
40
39
|
}
|
|
40
|
+
// Errors are never silent: hook.log is always written (unlike the
|
|
41
|
+
// DEBUG-gated log), so a broken pipeline is visible by default.
|
|
42
|
+
function errLog(msg) {
|
|
43
|
+
log("ERROR: " + msg);
|
|
44
|
+
hookLog("ERROR: " + msg);
|
|
45
|
+
}
|
|
46
|
+
// Probe for a working Python interpreter at startup instead of hardcoding
|
|
47
|
+
// one installer layout (pipx vs uv tool vs system). runPython only needs
|
|
48
|
+
// stdlib (sqlite3/json), so any python3 works. Priority: explicit env
|
|
49
|
+
// override, legacy pipx venv, uv tool venv, PATH fallback.
|
|
50
|
+
let resolvedPython = undefined;
|
|
51
|
+
function resolvePython() {
|
|
52
|
+
if (resolvedPython !== undefined)
|
|
53
|
+
return resolvedPython;
|
|
54
|
+
const candidates = [
|
|
55
|
+
process.env.MEMPALACE_PYTHON,
|
|
56
|
+
join(HOME, ".local/share/pipx/venvs/mempalace/bin/python3"),
|
|
57
|
+
join(HOME, ".local/share/uv/tools/mempalace/bin/python3"),
|
|
58
|
+
].filter((p) => !!p && existsSync(p));
|
|
59
|
+
if (candidates.length > 0) {
|
|
60
|
+
resolvedPython = candidates[0];
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
try {
|
|
64
|
+
execSync("python3 --version", { encoding: "utf-8", timeout: 10000 });
|
|
65
|
+
resolvedPython = "python3";
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
resolvedPython = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (resolvedPython) {
|
|
72
|
+
log("using python: " + resolvedPython);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
errLog("no working Python interpreter found (tried MEMPALACE_PYTHON, pipx venv, uv tool venv, PATH python3) — DB export disabled");
|
|
76
|
+
}
|
|
77
|
+
return resolvedPython;
|
|
78
|
+
}
|
|
41
79
|
let miningLock = false;
|
|
42
80
|
let lastSyncTs = 0;
|
|
43
81
|
let wakeupDone = false;
|
|
@@ -46,12 +84,19 @@ let wakeupDone = false;
|
|
|
46
84
|
// official Stop hook: the hook decides WHEN, the model decides WHAT).
|
|
47
85
|
let pendingCheckpoint = null;
|
|
48
86
|
function runPython(code) {
|
|
87
|
+
const python = resolvePython();
|
|
88
|
+
if (!python)
|
|
89
|
+
throw new Error("no working Python interpreter (see hook.log)");
|
|
49
90
|
writeFileSync(TMP_SCRIPT, code);
|
|
50
91
|
try {
|
|
51
|
-
|
|
92
|
+
// argv array, no shell (see PR #2): paths here are fixed, never user input.
|
|
93
|
+
return execFileSync(python, [TMP_SCRIPT], { encoding: "utf-8", timeout: 30000 }).trim();
|
|
52
94
|
}
|
|
53
95
|
finally {
|
|
54
|
-
|
|
96
|
+
try {
|
|
97
|
+
unlinkSync(TMP_SCRIPT);
|
|
98
|
+
}
|
|
99
|
+
catch { }
|
|
55
100
|
}
|
|
56
101
|
}
|
|
57
102
|
function hasText(parts) {
|
|
@@ -99,7 +144,8 @@ function persistCounters(counters) {
|
|
|
99
144
|
}
|
|
100
145
|
function mempalaceWakeup() {
|
|
101
146
|
try {
|
|
102
|
-
|
|
147
|
+
// argv array, no shell.
|
|
148
|
+
const out = execFileSync(MEMPALACE_BIN, ["wake-up"], { encoding: "utf-8", timeout: 15000 }).trim();
|
|
103
149
|
if (!out)
|
|
104
150
|
return "";
|
|
105
151
|
return out.slice(0, MAX_WAKEUP_CHARS);
|
|
@@ -133,7 +179,9 @@ function readIdentity() {
|
|
|
133
179
|
}
|
|
134
180
|
function mempalaceSearch(query) {
|
|
135
181
|
try {
|
|
136
|
-
|
|
182
|
+
// argv array, no shell (see PR #2): the query is raw user message
|
|
183
|
+
// text, so it must never pass through /bin/sh. No manual escaping needed.
|
|
184
|
+
const out = execFileSync(MEMPALACE_BIN, ["search", query, "--results", String(MAX_SEARCH_RESULTS)], {
|
|
137
185
|
encoding: "utf-8",
|
|
138
186
|
timeout: 15000,
|
|
139
187
|
}).trim();
|
|
@@ -162,7 +210,7 @@ function dbSync() {
|
|
|
162
210
|
doDbSync();
|
|
163
211
|
}
|
|
164
212
|
catch (e) {
|
|
165
|
-
|
|
213
|
+
errLog("sync err: " + String(e));
|
|
166
214
|
}
|
|
167
215
|
}
|
|
168
216
|
function backfillRequested() {
|
|
@@ -270,8 +318,10 @@ function markSynced(now) {
|
|
|
270
318
|
// Official classification: decisions, preferences, milestones, problems,
|
|
271
319
|
// emotional context. Agent tag keeps opencode-mined drawers attributable.
|
|
272
320
|
// One wing per project (official multi-project pattern).
|
|
273
|
-
|
|
274
|
-
|
|
321
|
+
// Argv array, no shell (see PR #2): wing names are sanitized, but the
|
|
322
|
+
// spawn path stays shell-free regardless.
|
|
323
|
+
function mineArgs(wingDir, wing) {
|
|
324
|
+
return ["mine", wingDir, "--mode", "convos", "--extract", "general", "--agent", "opencode", "--wing", wing];
|
|
275
325
|
}
|
|
276
326
|
function cleanupExport(wings) {
|
|
277
327
|
for (const files of wings.values()) {
|
|
@@ -323,13 +373,16 @@ function doDbSync() {
|
|
|
323
373
|
return;
|
|
324
374
|
}
|
|
325
375
|
const [wing, files] = entries[i];
|
|
326
|
-
|
|
376
|
+
// No timeout here by design (see PR #4): Node would kill only the
|
|
377
|
+
// wrapper shell and orphan the python mine process, which keeps
|
|
378
|
+
// holding the palace lock while the next mine piles up. miningLock
|
|
379
|
+
// already serializes concurrent mines; long mines run to completion.
|
|
380
|
+
execFile(MEMPALACE_BIN, mineArgs(join(OUT_DIR, wing), wing), {
|
|
327
381
|
encoding: "utf-8",
|
|
328
|
-
timeout: 300000,
|
|
329
382
|
}, (err) => {
|
|
330
383
|
if (err) {
|
|
331
384
|
miningLock = false;
|
|
332
|
-
|
|
385
|
+
errLog(`mine err (${wing}): ${err.message}`);
|
|
333
386
|
return;
|
|
334
387
|
}
|
|
335
388
|
log(`mined wing ${wing} (${files.length} sessions)`);
|
|
@@ -347,12 +400,12 @@ function exitSync() {
|
|
|
347
400
|
return;
|
|
348
401
|
for (const [wing] of wings) {
|
|
349
402
|
log(`exit save: mining wing ${wing}`);
|
|
350
|
-
const res = spawnSync(MEMPALACE_BIN,
|
|
403
|
+
const res = spawnSync(MEMPALACE_BIN, mineArgs(join(OUT_DIR, wing), wing), {
|
|
351
404
|
encoding: "utf-8",
|
|
352
405
|
timeout: 60000,
|
|
353
406
|
});
|
|
354
407
|
if (res.error || res.status !== 0) {
|
|
355
|
-
|
|
408
|
+
errLog(`exit mine err (${wing}): ${String(res.error || res.status)}`);
|
|
356
409
|
return;
|
|
357
410
|
}
|
|
358
411
|
}
|
|
@@ -361,7 +414,7 @@ function exitSync() {
|
|
|
361
414
|
log("exit save done");
|
|
362
415
|
}
|
|
363
416
|
catch (e) {
|
|
364
|
-
|
|
417
|
+
errLog("exit save err: " + String(e));
|
|
365
418
|
}
|
|
366
419
|
}
|
|
367
420
|
export default (async () => {
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-mempalace-persistence",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "OpenCode plugin — auto-sync conversations to MemPalace memory in real-time. No forced wings, KG extraction via MCP tools.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"dist"
|
|
8
|
+
"dist",
|
|
9
|
+
"skills"
|
|
9
10
|
],
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "tsc",
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mempalace-recall
|
|
3
|
+
description: Recall protocol for MemPalace — search the palace before answering about past work, people, projects, or prior decisions. Use when the user asks what was decided, what happened before, who someone is, what was discussed last time, or anything that may already be filed in their memory palace. Requires the mempalace MCP server (or the opencode-mempalace-persistence plugin).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MemPalace Recall
|
|
7
|
+
|
|
8
|
+
Search-before-answer protocol for MemPalace. Read the user's memory
|
|
9
|
+
palace before answering anything that may already be filed there,
|
|
10
|
+
instead of guessing from model memory.
|
|
11
|
+
|
|
12
|
+
If the `opencode-mempalace-persistence` plugin is active, identity and
|
|
13
|
+
relevant memories are usually already injected into the prompt — this
|
|
14
|
+
skill covers the cases where they are not, plus filing.
|
|
15
|
+
|
|
16
|
+
## When to recall
|
|
17
|
+
|
|
18
|
+
Search the palace **before answering** whenever the user asks about
|
|
19
|
+
something that may already be filed:
|
|
20
|
+
|
|
21
|
+
- Past work or prior decisions — "what did we decide / try / do?"
|
|
22
|
+
- A person, project, or entity — "who is …", "what is …"
|
|
23
|
+
- An earlier session — "remember when …", "last time …"
|
|
24
|
+
- A preference or fact that could have changed over time
|
|
25
|
+
|
|
26
|
+
Do **not** search on pure greenfield work with no memory relevance
|
|
27
|
+
(e.g. "rename this variable", "fix this typo"). Recall is
|
|
28
|
+
question-driven, not reflexive — a search on every turn wastes latency.
|
|
29
|
+
|
|
30
|
+
## Protocol
|
|
31
|
+
|
|
32
|
+
1. Before responding about people / projects / past events / prior
|
|
33
|
+
decisions: call `mempalace_search` first (short natural-language
|
|
34
|
+
query, optional `wing` / `room` filters, `limit` default 5). Use
|
|
35
|
+
`mempalace_kg_query` for relational or time-bound facts.
|
|
36
|
+
2. If unsure about a fact: say "let me check the palace" and query.
|
|
37
|
+
3. Return the drawer's **verbatim** text. Never summarize or paraphrase
|
|
38
|
+
stored content — quoting the exact words is the point of the system.
|
|
39
|
+
4. After a substantive session, record continuity with
|
|
40
|
+
`mempalace_diary_write` (skip if a checkpoint hook already saved).
|
|
41
|
+
5. File durable outcomes with `mempalace_add_drawer`; new KG facts with
|
|
42
|
+
`mempalace_kg_add` (128 chars or fewer); single-valued replacements
|
|
43
|
+
with `mempalace_kg_supersede`; ended facts with
|
|
44
|
+
`mempalace_kg_invalidate`.
|
|
45
|
+
|
|
46
|
+
## Tool names in OpenCode
|
|
47
|
+
|
|
48
|
+
When the MCP server is registered in OpenCode as `mempalace`, every
|
|
49
|
+
tool carries a double prefix: `mempalace_mempalace_search`,
|
|
50
|
+
`mempalace_mempalace_kg_query`, `mempalace_mempalace_diary_write`, and
|
|
51
|
+
so on. (In Claude Code the same tools have a single `mempalace_`
|
|
52
|
+
prefix.)
|
|
53
|
+
|
|
54
|
+
## Unhappy paths
|
|
55
|
+
|
|
56
|
+
- **Empty results.** Say the palace has nothing on this; do not invent
|
|
57
|
+
an answer. Offer to widen the search or file the new information.
|
|
58
|
+
- **MCP error / server down.** Surface the error and suggest
|
|
59
|
+
`mempalace status`. Never fall back to guessing.
|
|
60
|
+
- **Conflicting facts.** Trust the knowledge graph's time-valid answer.
|
|
61
|
+
|
|
62
|
+
## References
|
|
63
|
+
|
|
64
|
+
- MemPalace: <https://github.com/MemPalace/mempalace>
|
|
65
|
+
- Recall protocol: <https://github.com/MemPalace/mempalace/blob/main/integrations/shared/recall-protocol.md>
|
|
66
|
+
|
|
67
|
+
Derived from the official `mempalace-recall` skill (MIT), adapted for
|
|
68
|
+
OpenCode and the opencode-mempalace-persistence plugin.
|