opencode-codex-memory 0.7.4 → 0.7.6

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 CHANGED
@@ -23,9 +23,10 @@ See the [changelog](./CHANGELOG.md) for release history.
23
23
  **Local-first by design.** Memory is plain markdown files plus a small SQLite
24
24
  database on your own machine — no memory service to sign up for, no MCP server,
25
25
  no separate process, no sync. Installing it is one line in your `opencode.json`;
26
- from there everything lives under `~/.local/share/opencode/`, so you can read it,
27
- grep it, edit it, or delete it like anything else you own. Nothing leaves your
28
- machine beyond the model calls OpenCode already makes.
26
+ from there everything lives under `~/.local/share/opencode/` by default (or a
27
+ directory you set with `home`), so you can read it, grep it, edit it, or delete
28
+ it like anything else you own. Nothing leaves your machine beyond the model
29
+ calls OpenCode already makes.
29
30
 
30
31
  If you *do* also use the Codex CLI or Claude Code on the same machine: the
31
32
  plugin can bring their memories in (and, for Codex, push ours back). Off by
@@ -52,7 +53,7 @@ If you want the mental model — learning, remembering, forgetting — see
52
53
 
53
54
  ```json
54
55
  {
55
- "plugin": ["opencode-codex-memory@0.7.4"]
56
+ "plugin": ["opencode-codex-memory@0.7.6"]
56
57
  }
57
58
  ```
58
59
 
@@ -72,7 +73,7 @@ V2 plugin syntax:
72
73
 
73
74
  ```jsonc
74
75
  {
75
- "plugins": [{ "package": "opencode-codex-memory@0.7.4" }],
76
+ "plugins": [{ "package": "opencode-codex-memory@0.7.6" }],
76
77
  }
77
78
  ```
78
79
 
@@ -126,9 +127,15 @@ echo 'I prefer TypeScript strict mode and 2-space indentation.' \
126
127
  └── extensions/ad_hoc/notes/ # things you explicitly asked it to remember
127
128
  ```
128
129
 
129
- The location follows OpenCode's own data directory — `$XDG_DATA_HOME/opencode`
130
+ By default this is OpenCode's own data directory — `$XDG_DATA_HOME/opencode`
130
131
  when that's set, otherwise `~/.local/share/opencode` (same resolution on macOS,
131
- Linux, and Windows).
132
+ Linux, and Windows). Relocating OpenCode's local dir (for example by setting
133
+ `XDG_DATA_HOME`) therefore moves memory with it. To pin **only** `memory.db`
134
+ and `memories/` somewhere else — a sandbox mount, without the rest of
135
+ OpenCode's database — set `home`. That pin is always enforced; OpenCode's data
136
+ dir and `XDG_DATA_HOME` no longer apply. Precedence:
137
+ [Relocating memory](#relocating-memory-sandbox-mounts). Do not symlink
138
+ `memories/` — the plugin refuses a symlinked memory root.
132
139
 
133
140
  It's all plain files and a local SQLite database. Read them, edit them, delete
134
141
  them — it's yours. (The `memories/` folder also holds a few working files and
@@ -143,11 +150,13 @@ leave job state, Git baseline, and memory files out of sync. Include hidden
143
150
  files, especially `memories/.git/`, and SQLite sidecars such as `memory.db-wal`
144
151
  or `memory.db-shm` when present.
145
152
 
146
- The directory is `$XDG_DATA_HOME/opencode` when `XDG_DATA_HOME` is set,
147
- otherwise `~/.local/share/opencode`. Copy that whole directory to a dated
148
- backup location. To restore, stop OpenCode, replace the current `opencode/`
149
- data directory with the backup copy, then start OpenCode again. Do not restore
150
- while OpenCode is running or copy only `memory.db` or only `memories/`.
153
+ The default directory is `$XDG_DATA_HOME/opencode` when `XDG_DATA_HOME` is set,
154
+ otherwise `~/.local/share/opencode`. If you set `home` (or
155
+ `OPENCODE_CODEX_MEMORY_HOME`), back up that directory instead it is the pair
156
+ `memory.db` + `memories/`. Copy it to a dated backup location. To restore, stop
157
+ OpenCode, replace the current home with the backup copy, then start OpenCode
158
+ again. Do not restore while OpenCode is running or copy only `memory.db` or
159
+ only `memories/`.
151
160
 
152
161
  ## Privacy & safety
153
162
 
@@ -192,13 +201,14 @@ Codex's `[memories]` config so the two stay easy to compare:
192
201
  | `max_unused_days` | `30` | Prune memories unused for this long |
193
202
  | `codex_interop` | `{ "import": false, "export": false }` | Two-way memory exchange with a local Codex CLI (see below) |
194
203
  | `claude_import` | `{ "enabled": false }` | One-way import of Claude Code project memories (see below) |
204
+ | `home` | unset (follows OpenCode data dir) | Pin `memory.db` + `memories/` here. When set, OpenCode's data dir and `XDG_DATA_HOME` are ignored. See [Relocating memory](#relocating-memory-sandbox-mounts) |
195
205
 
196
206
  To set options, turn the plugin entry into a `[name, options]` pair:
197
207
 
198
208
  ```json
199
209
  {
200
210
  "plugin": [
201
- ["opencode-codex-memory@0.7.4", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
211
+ ["opencode-codex-memory@0.7.6", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
202
212
  ]
203
213
  }
204
214
  ```
@@ -253,6 +263,56 @@ nearest OpenCode variant on `none < minimal < low < medium < high < xhigh < max`
253
263
  > maintenance tools (`memory_reset`, `memory_inspect`, `memory_mode`) stay
254
264
  > available either way.
255
265
 
266
+ ### Relocating memory (sandbox mounts)
267
+
268
+ First match wins:
269
+
270
+ 1. **`home` plugin option** — pinned. Always enforced. OpenCode's data
271
+ directory and `XDG_DATA_HOME` do not apply.
272
+ 2. **`OPENCODE_CODEX_MEMORY_HOME`** — same pin, only if `home` is unset.
273
+ 3. **OpenCode data dir** (default) — `$XDG_DATA_HOME/opencode` when that env
274
+ is set, otherwise `~/.local/share/opencode`. Memory moves with OpenCode's
275
+ local directory.
276
+
277
+ To mount **only** memory into a sandbox, use (1) or (2) and point at a
278
+ dedicated directory:
279
+
280
+ ```json
281
+ {
282
+ "plugin": [
283
+ ["opencode-codex-memory@0.7.6", { "home": "/path/to/opencode-memory" }]
284
+ ]
285
+ }
286
+ ```
287
+
288
+ OpenCode 2:
289
+
290
+ ```jsonc
291
+ {
292
+ "plugins": [
293
+ { "package": "opencode-codex-memory@0.7.6", "options": { "home": "/path/to/opencode-memory" } }
294
+ ]
295
+ }
296
+ ```
297
+
298
+ `~` is expanded. The path must be absolute — a project-relative path would
299
+ make memory follow the cwd, and memory is global. Same layout as the default,
300
+ just elsewhere:
301
+
302
+ ```
303
+ /path/to/opencode-memory/
304
+ ├── memory.db
305
+ └── memories/
306
+ ```
307
+
308
+ Mount that directory into the sandbox at the **same absolute path**.
309
+
310
+ This does not move existing files. Stop OpenCode, copy `memory.db` (and
311
+ `memory.db-wal` / `memory.db-shm` if present) plus `memories/` into the new
312
+ home, then start it again. Do not symlink `memories/` in place of `home` —
313
+ a symlinked memory root is refused. Ask the agent to run `memory_inspect` to
314
+ confirm the resolved path.
315
+
256
316
  ### Sharing memory with the Codex CLI
257
317
 
258
318
  If you use OpenCode and the Codex CLI on the same machine, turn this on so each
@@ -265,7 +325,7 @@ Off by default; no changes to Codex's own config are required.
265
325
  {
266
326
  "plugin": [
267
327
  [
268
- "opencode-codex-memory@0.7.4",
328
+ "opencode-codex-memory@0.7.6",
269
329
  { "codex_interop": { "import": true, "export": true } }
270
330
  ]
271
331
  ]
@@ -322,7 +382,7 @@ from the project memories Claude already keeps on your machine. **One-way only**
322
382
  ```json
323
383
  {
324
384
  "plugin": [
325
- ["opencode-codex-memory@0.7.4", { "claude_import": { "enabled": true } }]
385
+ ["opencode-codex-memory@0.7.6", { "claude_import": { "enabled": true } }]
326
386
  ]
327
387
  }
328
388
  ```
@@ -349,7 +409,7 @@ Claude names each project with an opaque id (a folder under
349
409
  {
350
410
  "plugin": [
351
411
  [
352
- "opencode-codex-memory@0.7.4",
412
+ "opencode-codex-memory@0.7.6",
353
413
  {
354
414
  "claude_import": {
355
415
  "enabled": true,
package/dist/src/db.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { Database } from "bun:sqlite";
2
+ import fs from "fs";
3
+ import path from "path";
2
4
  import { memoryDbPath } from "./paths.js";
3
5
  const SCHEMA_V1 = [
4
6
  `CREATE TABLE IF NOT EXISTS memory_stage1_outputs (
@@ -46,6 +48,7 @@ export function openDb() {
46
48
  if (dbInstance)
47
49
  return dbInstance;
48
50
  const dbPath = memoryDbPath();
51
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
49
52
  const db = new Database(dbPath, { create: true, readwrite: true, strict: false });
50
53
  try {
51
54
  // Match codex's memories-DB open options (runtime.rs): WAL, NORMAL sync,
package/dist/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ensureMemoryLayout, buildMemorySystemPrompt, invalidateCache } from "./source.js";
2
- import { memoryRoot } from "./paths.js";
2
+ import { memoryRoot, memoryDbPath, setConfiguredHome, resolveHomePath } from "./paths.js";
3
+ import { closeDb } from "./db.js";
3
4
  import { stripCitations, extractCitedSessionIds, hasCitationMarkup } from "./citation.js";
4
5
  import { memory_read, memory_search, memory_list, memory_add_note } from "../tools/memory.js";
5
6
  import { memory_reset, memory_inspect, memory_mode } from "../tools/control.js";
@@ -137,8 +138,12 @@ export default {
137
138
  clearConfigWarnings();
138
139
  if (opts)
139
140
  applyPluginOptions(opts);
140
- else
141
+ else {
142
+ const previousDb = memoryDbPath();
141
143
  resetPluginOptions();
144
+ if (memoryDbPath() !== previousDb)
145
+ closeDb();
146
+ }
142
147
  // Finish bounded reseeding before hooks can see a surviving memory
143
148
  // sub-session after a plugin reload.
144
149
  await cleanupOldSubSessions();
@@ -165,6 +170,7 @@ const KNOWN_OPTION_KEYS = new Set([
165
170
  "max_rollout_age_days",
166
171
  "max_rollouts_per_startup",
167
172
  "min_rollout_idle_hours",
173
+ "home",
168
174
  "codex_interop",
169
175
  "claude_import",
170
176
  ]);
@@ -183,6 +189,7 @@ function clampInt(key, value, min, max, fallback) {
183
189
  export function applyPluginOptions(opts) {
184
190
  // Fresh pass per apply so memory_inspect never shows warnings for keys the
185
191
  // caller has since fixed. server() clears too, for boots without options.
192
+ const previousDb = memoryDbPath();
186
193
  clearConfigWarnings();
187
194
  resetPluginOptions();
188
195
  const raw = opts;
@@ -220,6 +227,28 @@ export function applyPluginOptions(opts) {
220
227
  pluginOptions.max_rollouts_per_startup = clampInt("max_rollouts_per_startup", opts.max_rollouts_per_startup, 1, 128, 2);
221
228
  if ("min_rollout_idle_hours" in opts)
222
229
  pluginOptions.min_rollout_idle_hours = clampInt("min_rollout_idle_hours", opts.min_rollout_idle_hours, 1, 48, 6);
230
+ if ("home" in raw) {
231
+ if (typeof raw.home !== "string") {
232
+ recordConfigWarning("home must be a string; using the OpenCode data dir");
233
+ }
234
+ else {
235
+ const resolved = resolveHomePath(raw.home);
236
+ if (!resolved) {
237
+ recordConfigWarning("home must be an absolute path (tilde ~ is ok); using the OpenCode data dir");
238
+ }
239
+ else {
240
+ pluginOptions.home = resolved;
241
+ setConfiguredHome(resolved);
242
+ const envHome = process.env.OPENCODE_CODEX_MEMORY_HOME;
243
+ if (envHome) {
244
+ const envResolved = resolveHomePath(envHome);
245
+ if (envResolved && envResolved !== resolved) {
246
+ recordConfigWarning("OPENCODE_CODEX_MEMORY_HOME ignored because home option is set");
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
223
252
  if ("codex_interop" in opts) {
224
253
  const raw = opts.codex_interop;
225
254
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
@@ -282,6 +311,8 @@ export function applyPluginOptions(opts) {
282
311
  recordConfigWarning("claude_import must be an object like { enabled, claude_home, projects }; ignored");
283
312
  }
284
313
  }
314
+ if (memoryDbPath() !== previousDb)
315
+ closeDb();
285
316
  }
286
317
  /**
287
318
  * codex marks every MCP server as memory-polluting unconditionally
@@ -376,7 +407,7 @@ export function injectAgentDefinitions(config) {
376
407
  }
377
408
  // Sub-sessions use directory=memoryRoot (llm.ts), so memory paths are usually
378
409
  // in-bounds. Keep an explicit external_directory allow for the memory root as
379
- // belt-and-suspenders (path is homedir/env-dependent; out-ranks `"*": deny`).
410
+ // belt-and-suspenders (path is home/env-dependent; out-ranks `"*": deny`).
380
411
  const memorize = defs["memorize"];
381
412
  if (memorize?.permission && !("external_directory" in memorize.permission)) {
382
413
  memorize.permission["external_directory"] = { [path.join(memoryRoot(), "*")]: "allow" };
@@ -7,9 +7,9 @@ import type { ClaudeImportOptions } from "./claude-import.js";
7
7
  *
8
8
  * Option names and defaults mirror codex's MemoriesToml/MemoriesConfig
9
9
  * (codex-rs/config/src/types.rs). Keep them 1:1 so the drift script and
10
- * manual syncing stay trivial; do not rename for taste. codex_interop and
11
- * claude_import are opencode-facing knobs for external-agent exchange
12
- * (codex's Claude importer is migration-UI gated / default-off).
10
+ * manual syncing stay trivial; do not rename for taste. codex_interop,
11
+ * claude_import, and home are opencode-facing knobs (Codex has no equivalent
12
+ * of relocating just the memory files away from the host data dir).
13
13
  */
14
14
  export interface PluginOptionsState {
15
15
  generate_memories: boolean;
@@ -18,6 +18,8 @@ export interface PluginOptionsState {
18
18
  disable_on_external_context: boolean;
19
19
  extract_model?: string;
20
20
  consolidation_model?: string;
21
+ /** Resolved absolute plugin home (parent of memory.db + memories/). Unset = default. */
22
+ home?: string;
21
23
  max_raw_memories_for_consolidation: number;
22
24
  max_unused_days: number;
23
25
  max_rollout_age_days: number;
@@ -1,3 +1,4 @@
1
+ import { setConfiguredHome } from "./paths.js";
1
2
  const DEFAULT_PLUGIN_OPTIONS = {
2
3
  generate_memories: true,
3
4
  use_memories: true,
@@ -19,6 +20,8 @@ export const pluginOptions = {
19
20
  export function resetPluginOptions() {
20
21
  delete pluginOptions.extract_model;
21
22
  delete pluginOptions.consolidation_model;
23
+ delete pluginOptions.home;
24
+ setConfiguredHome(undefined);
22
25
  Object.assign(pluginOptions, DEFAULT_PLUGIN_OPTIONS, {
23
26
  codex_interop: { ...DEFAULT_PLUGIN_OPTIONS.codex_interop },
24
27
  claude_import: { ...DEFAULT_PLUGIN_OPTIONS.claude_import },
@@ -1,3 +1,30 @@
1
+ export type MemoryHomeSource = "test" | "option" | "env" | "default";
2
+ /**
3
+ * Memory lives under a dedicated home, mirroring codex's
4
+ * `<codex_home>/memories` (codex-rs `find_codex_home` + `from_codex_home`) but
5
+ * with an opencode-specific default. opencode resolves its data dir as
6
+ * `path.join(xdgData, "opencode")` via the `xdg-basedir` lib
7
+ * (packages/core/src/global.ts), so we reuse the SAME lib to stay byte-identical
8
+ * across platforms and `XDG_DATA_HOME` overrides. opencode does not surface this
9
+ * directory through the plugin API (`/path` gives home/config/state/worktree/
10
+ * directory, not data), so it must be recomputed here.
11
+ *
12
+ * User-facing precedence (first match). `home` / OPENCODE_CODEX_MEMORY_HOME
13
+ * pin this plugin's files and do NOT follow OpenCode's data dir or XDG_DATA_HOME:
14
+ * 1. plugin option `home` — always enforced when set
15
+ * 2. `OPENCODE_CODEX_MEMORY_HOME` — same pin if `home` is unset
16
+ * 3. OpenCode data dir (`$XDG_DATA_HOME/opencode` or `~/.local/share/opencode`)
17
+ *
18
+ * Tests only: `OPENCODE_CODEX_MEMORY_TEST_ROOT` wins over all of the above
19
+ * (write-pipeline sandbox + unit tests). Not a user relocation knob.
20
+ *
21
+ * (1) and (2) exist so a sandbox can mount `memory.db` + `memories/` without
22
+ * the rest of OpenCode's local database. They are not Codex knobs.
23
+ */
24
+ export declare function resolveHomePath(raw: string): string | null;
25
+ export declare function setConfiguredHome(home: string | undefined): void;
26
+ export declare function dataRoot(): string;
27
+ export declare function memoryHomeSource(): MemoryHomeSource;
1
28
  export declare function memoryRoot(): string;
2
29
  export declare function memoryDbPath(): string;
3
30
  export declare function memorySummaryPath(): string;
package/dist/src/paths.js CHANGED
@@ -3,30 +3,77 @@ import os from "os";
3
3
  import { xdgData } from "xdg-basedir";
4
4
  const MEMORY_DIR_NAME = "memories";
5
5
  const MEMORY_DB_NAME = "memory.db";
6
- const OVERRIDE_ENV = "OPENCODE_CODEX_MEMORY_TEST_ROOT";
6
+ const TEST_ROOT_ENV = "OPENCODE_CODEX_MEMORY_TEST_ROOT";
7
+ const HOME_ENV = "OPENCODE_CODEX_MEMORY_HOME";
7
8
  const OPENCODE_APP_DIR = "opencode";
9
+ /** Plugin-option override of the memory home (parent of memory.db + memories/). */
10
+ let configuredHome;
8
11
  /**
9
- * Memory lives under opencode's data dir, mirroring codex's
12
+ * Memory lives under a dedicated home, mirroring codex's
10
13
  * `<codex_home>/memories` (codex-rs `find_codex_home` + `from_codex_home`) but
11
- * with the opencode-specific home. opencode resolves its data dir as
14
+ * with an opencode-specific default. opencode resolves its data dir as
12
15
  * `path.join(xdgData, "opencode")` via the `xdg-basedir` lib
13
16
  * (packages/core/src/global.ts), so we reuse the SAME lib to stay byte-identical
14
17
  * across platforms and `XDG_DATA_HOME` overrides. opencode does not surface this
15
18
  * directory through the plugin API (`/path` gives home/config/state/worktree/
16
19
  * directory, not data), so it must be recomputed here.
17
20
  *
18
- * `OPENCODE_CODEX_MEMORY_TEST_ROOT` is our `CODEX_HOME` analog: an explicit
19
- * override that wins outright (tests + the write-pipeline sandbox).
21
+ * User-facing precedence (first match). `home` / OPENCODE_CODEX_MEMORY_HOME
22
+ * pin this plugin's files and do NOT follow OpenCode's data dir or XDG_DATA_HOME:
23
+ * 1. plugin option `home` — always enforced when set
24
+ * 2. `OPENCODE_CODEX_MEMORY_HOME` — same pin if `home` is unset
25
+ * 3. OpenCode data dir (`$XDG_DATA_HOME/opencode` or `~/.local/share/opencode`)
26
+ *
27
+ * Tests only: `OPENCODE_CODEX_MEMORY_TEST_ROOT` wins over all of the above
28
+ * (write-pipeline sandbox + unit tests). Not a user relocation knob.
29
+ *
30
+ * (1) and (2) exist so a sandbox can mount `memory.db` + `memories/` without
31
+ * the rest of OpenCode's local database. They are not Codex knobs.
20
32
  */
21
- function dataRoot() {
22
- const override = process.env[OVERRIDE_ENV];
23
- if (override)
24
- return override;
33
+ export function resolveHomePath(raw) {
34
+ const trimmed = raw.trim();
35
+ if (!trimmed)
36
+ return null;
37
+ let expanded = trimmed;
38
+ if (trimmed === "~")
39
+ expanded = os.homedir();
40
+ else if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
41
+ expanded = path.join(os.homedir(), trimmed.slice(2));
42
+ }
43
+ if (!path.isAbsolute(expanded))
44
+ return null;
45
+ return path.normalize(expanded);
46
+ }
47
+ export function setConfiguredHome(home) {
48
+ configuredHome = home;
49
+ }
50
+ export function dataRoot() {
51
+ const testRoot = process.env[TEST_ROOT_ENV];
52
+ if (testRoot)
53
+ return testRoot;
54
+ if (configuredHome)
55
+ return configuredHome;
56
+ const envHome = process.env[HOME_ENV];
57
+ if (envHome) {
58
+ const resolved = resolveHomePath(envHome);
59
+ if (resolved)
60
+ return resolved;
61
+ }
25
62
  // xdgData = XDG_DATA_HOME || ~/.local/share (identical on every platform).
26
63
  // The `??` mirrors xdg-basedir's own guard for a missing home directory.
27
64
  const base = xdgData ?? path.join(os.homedir(), ".local", "share");
28
65
  return path.join(base, OPENCODE_APP_DIR);
29
66
  }
67
+ export function memoryHomeSource() {
68
+ if (process.env[TEST_ROOT_ENV])
69
+ return "test";
70
+ if (configuredHome)
71
+ return "option";
72
+ const envHome = process.env[HOME_ENV];
73
+ if (envHome && resolveHomePath(envHome))
74
+ return "env";
75
+ return "default";
76
+ }
30
77
  export function memoryRoot() {
31
78
  return path.join(dataRoot(), MEMORY_DIR_NAME);
32
79
  }
@@ -3,5 +3,5 @@
3
3
  * the TUI renders fenced `memory-citation` blocks, so the V2 context hook
4
4
  * overlays this section onto the shared inject prompt.
5
5
  */
6
- export declare const V2_CITATION_INSTRUCTIONS = "Memory citation requirements:\n\n- If ANY relevant memory files were used: append exactly one fenced code\n block with the language tag `memory-citation` as the VERY LAST content of\n the final reply. Normal responses should include the answer first, then\n append the block at the end. The host renders this block natively.\n- Use this exact structure for programmatic parsing:\n````\n```memory-citation\nMEMORY.md:234-236|note=build command for the api service\nrollout_summaries/2026-02-17T21-23-02-ln3m-example.md:10-12|note=weekly report format\nsessions: ses_abc123 ses_def456\n```\n````\n- Do not wrap it in `<memory-citation>` XML tags; the fenced form replaces\n that older format.\n- Citation entry lines are for rendering:\n - one citation entry per line\n - format: `<file>:<line_start>-<line_end>|note=<how memory was used>`\n - use file paths relative to the memory base path (for example, `MEMORY.md`,\n `rollout_summaries/...`, `skills/...`)\n - only cite files actually used under the memory base path (do not cite\n workspace files as memory citations)\n - if you used `MEMORY.md` and then a rollout summary/skill file, cite both\n - list entries in order of importance (most important first)\n - `note` should be short, single-line, and use simple characters only (avoid\n unusual symbols, no newlines)\n- The final `sessions:` line is for us to track which past sessions you find\n useful:\n - one line, space-separated session ids after `sessions:`\n - session ids look like `ses_...` and appear in rollout summary files and\n MEMORY.md as `session_id:`\n - include unique ids only; do not repeat ids\n - omit the `sessions:` line if no session ids are available\n - do not include file paths or notes on this line\n - for every citation entry, try to find and cite the corresponding session id\n- Never include memory citations inside pull-request messages.\n- Never cite blank lines; double-check ranges.\n\n";
6
+ export declare const V2_CITATION_INSTRUCTIONS = "Memory citation requirements:\n\n- If ANY relevant memory files were used: append exactly one fenced code\n block with the language tag `memory-citation` as the VERY LAST content of\n the final reply. Answer first, then a blank line, then the block. The\n opening fence must be on its own line at column 0 \u2014 never glue it to the\n last sentence, or markdown clients treat the block as paragraph text and\n collapse its newlines. The host renders this block natively.\n- Use this exact structure for programmatic parsing (blank line before the\n fence is required):\n````\nanswer text ends here.\n\n```memory-citation\nMEMORY.md:234-236|note=build command for the api service\nrollout_summaries/2026-02-17T21-23-02-ln3m-example.md:10-12|note=weekly report format\nsessions: ses_abc123 ses_def456\n```\n````\n- Do not wrap it in `<memory-citation>` XML tags; the fenced form replaces\n that older format.\n- Citation entry lines are for rendering:\n - one citation entry per line\n - format: `<file>:<line_start>-<line_end>|note=<how memory was used>`\n - use file paths relative to the memory base path (for example, `MEMORY.md`,\n `rollout_summaries/...`, `skills/...`)\n - only cite files actually used under the memory base path (do not cite\n workspace files as memory citations)\n - if you used `MEMORY.md` and then a rollout summary/skill file, cite both\n - list entries in order of importance (most important first)\n - `note` should be short, single-line, and use simple characters only (avoid\n unusual symbols, no newlines)\n- The final `sessions:` line is for us to track which past sessions you find\n useful:\n - one line, space-separated session ids after `sessions:`\n - session ids look like `ses_...` and appear in rollout summary files and\n MEMORY.md as `session_id:`\n - include unique ids only; do not repeat ids\n - omit the `sessions:` line if no session ids are available\n - do not include file paths or notes on this line\n - for every citation entry, try to find and cite the corresponding session id\n- Never include memory citations inside pull-request messages.\n- Never cite blank lines; double-check ranges.\n\n";
7
7
  export declare function overlayV2CitationInstructions(prompt: string): string;
@@ -7,10 +7,15 @@ export const V2_CITATION_INSTRUCTIONS = `Memory citation requirements:
7
7
 
8
8
  - If ANY relevant memory files were used: append exactly one fenced code
9
9
  block with the language tag \`memory-citation\` as the VERY LAST content of
10
- the final reply. Normal responses should include the answer first, then
11
- append the block at the end. The host renders this block natively.
12
- - Use this exact structure for programmatic parsing:
10
+ the final reply. Answer first, then a blank line, then the block. The
11
+ opening fence must be on its own line at column 0 never glue it to the
12
+ last sentence, or markdown clients treat the block as paragraph text and
13
+ collapse its newlines. The host renders this block natively.
14
+ - Use this exact structure for programmatic parsing (blank line before the
15
+ fence is required):
13
16
  \`\`\`\`
17
+ answer text ends here.
18
+
14
19
  \`\`\`memory-citation
15
20
  MEMORY.md:234-236|note=build command for the api service
16
21
  rollout_summaries/2026-02-17T21-23-02-ln3m-example.md:10-12|note=weekly report format
@@ -25,6 +25,8 @@ import { runPhase1 } from "../phase1.js";
25
25
  import { runPhase2 } from "../phase2.js";
26
26
  import { setPluginInput, setSubSessionDirectory, cleanupOldSubSessions, isMemorySubSession, abortActiveSubSessions, } from "../llm.js";
27
27
  import { pluginOptions, clearConfigWarnings, resetPluginOptions } from "../options.js";
28
+ import { memoryDbPath } from "../paths.js";
29
+ import { closeDb } from "../db.js";
28
30
  import { beginPluginShutdown, isPluginShuttingDown, resetPluginLifecycle } from "../lifecycle.js";
29
31
  import { hostMcpStatus } from "../host-client.js";
30
32
  import { recordDiagnostic } from "../diagnostics.js";
@@ -278,8 +280,12 @@ export async function setup(ctx) {
278
280
  clearConfigWarnings();
279
281
  if (ctx.options)
280
282
  applyPluginOptions(ctx.options);
281
- else
283
+ else {
284
+ const previousDb = memoryDbPath();
282
285
  resetPluginOptions();
286
+ if (memoryDbPath() !== previousDb)
287
+ closeDb();
288
+ }
283
289
  await ensureV2Agents(ctx);
284
290
  const statusRegistration = await ctx.rpc.register(MemoryStatusRpc, {
285
291
  status: async (input) => {
@@ -1,7 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { tool } from "@opencode-ai/plugin";
4
- import { memoryRoot, memorySummaryPath } from "../src/paths.js";
4
+ import { memoryRoot, memorySummaryPath, dataRoot, memoryHomeSource } from "../src/paths.js";
5
5
  import { MemoryStore } from "../src/store.js";
6
6
  import { invalidateCache } from "../src/source.js";
7
7
  import { estimateTokens } from "../src/token.js";
@@ -42,6 +42,18 @@ function wipeMemoriesDir() {
42
42
  fs.unlinkSync(abs);
43
43
  }
44
44
  }
45
+ function homeSourceLabel(source) {
46
+ switch (source) {
47
+ case "test":
48
+ return "test root";
49
+ case "option":
50
+ return "option";
51
+ case "env":
52
+ return "OPENCODE_CODEX_MEMORY_HOME";
53
+ case "default":
54
+ return "OpenCode data dir";
55
+ }
56
+ }
45
57
  /**
46
58
  * Renders the effective (post-parse, post-clamp) plugin options plus any
47
59
  * problems recorded while applying them. The plugin never hard-fails on bad
@@ -65,6 +77,7 @@ function renderEffectiveConfig() {
65
77
  ` max_rollout_age_days: ${o.max_rollout_age_days}`,
66
78
  ` max_rollouts_per_startup: ${o.max_rollouts_per_startup}`,
67
79
  ` min_rollout_idle_hours: ${o.min_rollout_idle_hours}`,
80
+ ` home: ${dataRoot()} (${homeSourceLabel(memoryHomeSource())})`,
68
81
  ];
69
82
  const ci = o.codex_interop;
70
83
  if (!ci.import && !ci.export) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codex-memory",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "Persistent memory plugin for opencode — ports codex's two-phase memory system (extraction → consolidation → injection → citation feedback)",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",