opencode-codex-memory 0.3.1 → 0.4.1

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
@@ -1,38 +1,39 @@
1
- # opencode-codex-memory
1
+ # OpenCode Codex Memory
2
2
 
3
- Persistent memory for [opencode](https://opencode.ai). Your agent remembers what
3
+ Persistent memory for [OpenCode](https://opencode.ai): your agent remembers what
4
4
  it learned in past sessions — your conventions, your projects, the decisions you
5
5
  made — and brings that context into new conversations automatically.
6
6
 
7
- It's a single plugin. No core changes, no MCP server, no separate process, no
8
- cloud service. Everything stays on your machine under
9
- `~/.local/share/opencode/`.
10
-
11
- Despite the name: **no codex subscription or OpenAI account is needed.** This
12
- project ports the memory *design* from OpenAI's codex to opencode. It works out
7
+ Despite the name: **no Codex subscription or OpenAI account is needed.** This
8
+ project is a faithful port of the memory system in OpenAI's Codex. It works out
13
9
  of the box with zero extra configuration and uses whatever models you already
14
- have set up in opencode.
10
+ have set up in OpenCode.
11
+
12
+ **Local-first by design.** Memory is plain markdown files plus a small SQLite
13
+ database on your own machine — no memory service to sign up for, no MCP server,
14
+ no separate process, no sync. Installing it is one line in your `opencode.json`;
15
+ from there everything lives under `~/.local/share/opencode/`, so you can read it,
16
+ grep it, edit it, or delete it like anything else you own. Nothing leaves your
17
+ machine beyond the model calls OpenCode already makes.
18
+
19
+ If you *do* also use the Codex CLI: the plugin can share memory with Codex in
20
+ both directions — what one assistant learns on your machine, the other picks
21
+ up. Off by default, one config flag per direction; see
22
+ [Sharing memory with the Codex CLI](#sharing-memory-with-the-codex-cli).
15
23
 
16
24
  ## Why
17
25
 
18
- By default every opencode session starts from zero. You re-explain your build
26
+ By default every OpenCode session starts from zero. You re-explain your build
19
27
  commands, your code style, and the quirks of each repo over and over.
20
28
 
21
- opencode-codex-memory closes that loop:
22
-
23
- - **It learns in the background.** Once a session has been idle for a while
24
- (default 6 h), a later background pass reviews the transcript and extracts
25
- durable facts preferences, project structure, what worked and what didn't.
26
- - **It consolidates.** Periodically it merges those notes into a compact,
27
- searchable memory, pruning what's stale.
28
- - **It remembers at the right time.** A short summary is injected into the system
29
- prompt, and the agent can search the full memory on demand when a task looks
30
- related to past work.
31
- - **It self-corrects.** When the agent actually uses a memory it cites the source,
32
- so useful memories rank higher over time and unused ones fade.
29
+ This plugin closes that loop. It reviews finished sessions in the
30
+ background, keeps what's durable — your preferences, how a repo is built, what
31
+ worked and what didn't and puts that context back in front of the agent in
32
+ later conversations. You don't manage any of it; OpenCode just gets more useful
33
+ the more you use it.
33
34
 
34
- The result: opencode gets more useful the more you use it, without you managing
35
- anything.
35
+ If you want the mental model before the details, jump to
36
+ [How it works](#how-it-works).
36
37
 
37
38
  ## Install
38
39
 
@@ -40,19 +41,37 @@ anything.
40
41
 
41
42
  ```json
42
43
  {
43
- "plugin": ["opencode-codex-memory"]
44
+ "plugin": ["opencode-codex-memory@0.4.1"]
44
45
  }
45
46
  ```
46
47
 
48
+ **Pin the version** (here and for any OpenCode plugin). OpenCode installs a
49
+ plugin spec once into its package cache and never re-resolves it, so a bare
50
+ `"opencode-codex-memory"` is not "always latest" — it freezes at whatever
51
+ was latest the first time OpenCode started. With a version tag you decide
52
+ which release runs, and bumping the tag installs the new one. Check
53
+ [npm](https://www.npmjs.com/package/opencode-codex-memory) for the current
54
+ version.
55
+
56
+ Since updating is then just editing version numbers, it's worth automating
57
+ with a custom command — one that reads your `opencode.json`, compares each
58
+ pin against npm, and rewrites the ones you confirm. If you'd rather not
59
+ write your own, this repo has one you can copy:
60
+ [`docs/commands/update-plugins.md`](./docs/commands/update-plugins.md) →
61
+ `~/.config/opencode/command/update-plugins.md`, then run `/update-plugins`.
62
+ It's documentation, not part of the package: nothing about it is installed
63
+ with the plugin, and it works for every pinned plugin in your config, not
64
+ just this one.
65
+
47
66
  (While developing locally, point it at an absolute path to your checkout
48
67
  instead of the package name.)
49
68
 
50
69
  2. That's it. The memory workspace is created on first use. Installing the
51
70
  plugin is the opt-in: background learning and summary injection are active
52
- immediately (codex ships the same system behind a default-off feature flag
71
+ immediately (Codex ships the same system behind a default-off feature flag
53
72
  with a consent prompt; a standalone memory plugin *is* the consent).
54
73
 
55
- Requires opencode 1.18 or newer (official release). Git is bundled
74
+ Requires OpenCode 1.18 or newer (official release). Git is bundled
56
75
  (`isomorphic-git`) — no `git` binary or any other external tool needed.
57
76
 
58
77
  The two restricted sub-agents that do the background learning (`memorize`,
@@ -66,11 +85,11 @@ override `memorize`, keep an `external_directory` allow for
66
85
  `~/.local/share/opencode/memories/*` (e.g.
67
86
  `"external_directory": { "$HOME/.local/share/opencode/memories/*": "allow" }`
68
87
  after the wildcard deny) — the memory folder lives outside your project, and
69
- without that grant opencode blocks the consolidator's file access.
88
+ without that grant OpenCode blocks the consolidator's file access.
70
89
 
71
90
  ## Try it
72
91
 
73
- Just use opencode normally. Sessions that have been idle for a few hours get
92
+ Just use OpenCode normally. Sessions that have been idle for a few hours get
74
93
  reviewed in the background and memory starts building up — you don't have to do
75
94
  anything. Come back the next day and ask something like *"what do you know about how I
76
95
  work?"* or *"what was I doing in this repo?"* and the agent draws on what it
@@ -91,11 +110,59 @@ echo 'I prefer TypeScript strict mode and 2-space indentation.' \
91
110
  > ~/.local/share/opencode/memories/memory_summary.md
92
111
  ```
93
112
 
113
+ ## How it works
114
+
115
+ You don't need to know any of this to use the plugin. The design is Codex's,
116
+ ported as-is, and it does what any memory system has to do: decide what's worth
117
+ keeping, write it down so it can be found again, surface the right piece at the
118
+ right moment, and forget what stopped being useful.
119
+
120
+ Think of it as three jobs: two background writers and one reader. **Nothing here
121
+ runs while you're waiting for a reply** — an assistant that stops to take notes
122
+ mid-answer would be slower and more expensive, so the learning happens after the
123
+ fact, on transcripts of conversations that are already over.
124
+
125
+ **Phase 1 — read one finished session, write notes about it.** Once a
126
+ conversation has been idle long enough that it's clearly done (default 6 h), the
127
+ plugin fetches that transcript, strips secrets out of it, and hands it to a
128
+ cheap model with one question: *what from this is worth keeping?* The answer
129
+ comes back as structured data — a detailed note plus a short recap of the
130
+ session — and lands in a local SQLite database. One session in, one record out.
131
+ Sessions are independent, so this part is easy to parallelize and to retry when
132
+ it fails.
133
+
134
+ **Phase 2 — merge all those notes into one memory.** Every few hours at most
135
+ (and only one run at a time across all your OpenCode windows), a second pass
136
+ takes the most relevant per-session notes and rewrites the actual memory files:
137
+ `MEMORY.md` as the full index, `memory_summary.md` as the short version, and
138
+ `skills/` for procedures worth repeating. This is where the interesting work
139
+ happens — ten similar observations collapse into one rule, contradictions get
140
+ resolved, and notes nothing ever used age out. Forgetting is a feature: memory
141
+ that only grows is memory that stops being useful.
142
+
143
+ The split exists because the two halves have opposite needs. Phase 1 is
144
+ per-session and can run many at once; phase 2 touches the single shared memory,
145
+ so it has to be serialized. Keeping them apart means one slow or failing session
146
+ extraction can't corrupt or block the shared store.
147
+
148
+ **The read path — actually remembering.** Every turn, the short summary is
149
+ appended to the system prompt (capped at ~2500 tokens, so the cost is small and
150
+ predictable). That's the always-on layer. When a task looks related to past
151
+ work, the agent goes further and searches the full memory itself with the
152
+ `memory_*` tools — the equivalent of "I've seen this before, let me look it up"
153
+ rather than carrying everything around all the time.
154
+
155
+ **The feedback loop.** When the agent uses a memory, it cites it. The citation
156
+ is recorded and then stripped before it reaches your screen, and those usage
157
+ counts feed back into phase 2's ranking. Memories that keep proving useful get
158
+ kept and sharpened; memories nothing has touched in a month drop out. The system
159
+ finds out which of its own notes were worth writing.
160
+
94
161
  ## Where your data lives
95
162
 
96
163
  ```
97
164
  ~/.local/share/opencode/
98
- ├── memory.db # the plugin's own database (opencode's data is only accessed via its API)
165
+ ├── memory.db # the plugin's own database (OpenCode's data is only accessed via its API)
99
166
  └── memories/
100
167
  ├── memory_summary.md # compact summary injected into the system prompt
101
168
  ├── MEMORY.md # searchable index of everything learned
@@ -104,7 +171,7 @@ echo 'I prefer TypeScript strict mode and 2-space indentation.' \
104
171
  └── extensions/ad_hoc/notes/ # things you explicitly asked it to remember
105
172
  ```
106
173
 
107
- The location follows opencode's own data directory — `$XDG_DATA_HOME/opencode`
174
+ The location follows OpenCode's own data directory — `$XDG_DATA_HOME/opencode`
108
175
  when that's set, otherwise `~/.local/share/opencode` (same resolution on macOS,
109
176
  Linux, and Windows).
110
177
 
@@ -115,8 +182,12 @@ those too.)
115
182
 
116
183
  ## Privacy & safety
117
184
 
118
- - **Local only.** Nothing is sent anywhere except through your existing opencode
119
- provider, using your existing credentials. The plugin holds no keys of its own.
185
+ - **Local only.** There is no remote storage option to enable, by accident or
186
+ otherwise: Codex keeps memory storage behind a backend interface whose only
187
+ implementation today is the local filesystem, and this port implements that
188
+ path and nothing else. Nothing is sent anywhere except through your existing
189
+ OpenCode provider, using your existing credentials; the plugin holds no keys
190
+ of its own.
120
191
  - **Secrets are redacted** (API keys, tokens, private keys, passwords) from
121
192
  session transcripts and extracted memories before anything is written or sent
122
193
  to a model. Notes you explicitly dictate ("remember that ...") are stored as
@@ -128,14 +199,14 @@ those too.)
128
199
  - **Reset is safe.** `memory_reset` refuses to run if the memory folder is a
129
200
  symlink, so it can't be tricked into deleting something else.
130
201
  - **Web/MCP sessions:** by default, sessions that used web search, fetch, or MCP
131
- tools are still eligible for memory (matching codex). If you'd rather exclude
202
+ tools are still eligible for memory (matching Codex). If you'd rather exclude
132
203
  them so scraped or external content can't enter your memory, set
133
204
  `disable_on_external_context: true`.
134
205
 
135
206
  ## Configuration
136
207
 
137
208
  Optional plugin options (all have sensible defaults). Names and defaults match
138
- codex's `[memories]` config so the two stay easy to compare:
209
+ Codex's `[memories]` config so the two stay easy to compare:
139
210
 
140
211
  | Option | Default | Meaning |
141
212
  |---|---|---|
@@ -143,84 +214,143 @@ codex's `[memories]` config so the two stay easy to compare:
143
214
  | `use_memories` | `true` | Inject the memory summary into the system prompt |
144
215
  | `dedicated_tools` | `true` | Expose the `memory_read`/`memory_search`/`memory_list`/`memory_add_note` tools |
145
216
  | `disable_on_external_context` | `false` | Exclude sessions that used web/MCP tools from memory |
146
- | `extract_model` | opencode `small_model`, else see below | Model used for per-session extraction |
147
- | `consolidation_model` | opencode `model`, else see below | Model used for consolidation |
217
+ | `extract_model` | OpenCode `small_model`, else see below | Model used for per-session extraction |
218
+ | `consolidation_model` | OpenCode `model`, else see below | Model used for consolidation |
148
219
  | `max_raw_memories_for_consolidation` | `256` | How many raw memories feed each consolidation pass |
149
220
  | `max_rollout_age_days` | `10` | Ignore sessions older than this for extraction |
150
221
  | `min_rollout_idle_hours` | `6` | How long a session must be idle before it's eligible |
151
222
  | `max_rollouts_per_startup` | `2` | Max sessions extracted per pass |
152
223
  | `max_unused_days` | `30` | Prune memories unused for this long |
224
+ | `codex_interop` | `{ "import": false, "export": false }` | Two-way memory exchange with a local Codex CLI (see below) |
153
225
 
154
226
  To set options, turn the plugin entry into a `[name, options]` pair:
155
227
 
156
228
  ```json
157
229
  {
158
230
  "plugin": [
159
- ["opencode-codex-memory", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
231
+ ["opencode-codex-memory@0.4.1", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
160
232
  ]
161
233
  }
162
234
  ```
163
235
 
164
- See the [opencode plugin docs](https://opencode.ai/docs/plugins/) for details.
236
+ See the [OpenCode plugin docs](https://opencode.ai/docs/plugins/) for details.
165
237
 
166
- Numeric options are clamped to codex's valid ranges; unknown option keys are
238
+ Numeric options are clamped to Codex's valid ranges; unknown option keys are
167
239
  ignored with a warning. Setting `use_memories: false` also hides the memory
168
- tools, matching codex's extension gating.
169
-
170
- Model selection mirrors codex's cheap-extraction / capable-consolidation
171
- split using opencode's own concepts: when `extract_model` is unset, the
172
- `small_model` from your `opencode.json` is used (codex uses `gpt-5.4-mini`);
173
- when `consolidation_model` is unset, your main `model` is used (codex uses
240
+ tools, matching Codex's extension gating.
241
+
242
+ **Verifying your configuration:** the plugin never hard-fails on bad options.
243
+ To check what actually took effect, ask the agent to run `memory_inspect` it
244
+ echoes the effective options (after parsing and clamping), lists warnings for
245
+ unknown or malformed keys (typos included), and shows the resolved Codex
246
+ interop state. A mistyped option shows up there twice: as a warning, and as
247
+ the default value appearing where you expected your setting.
248
+
249
+ Model selection mirrors Codex's cheap-extraction / capable-consolidation
250
+ split using OpenCode's own concepts: when `extract_model` is unset, the
251
+ `small_model` from your `opencode.json` is used (Codex uses `gpt-5.4-mini`);
252
+ when `consolidation_model` is unset, your main `model` is used (Codex uses
174
253
  `gpt-5.4`). If neither is configured, the learning sub-agents fall back to
175
254
  their own agent-level `model` (if you defined one), else the provider default.
176
- (opencode's *automatic* small-model pick is internal to opencode and not
255
+ (OpenCode's *automatic* small-model pick is internal to OpenCode and not
177
256
  exposed to plugins — set `small_model` explicitly to get the cheap extraction
178
257
  path.)
179
258
 
180
259
  The full precedence per phase: plugin option (`extract_model` /
181
- `consolidation_model`) → opencode config (`small_model` / `model`) → a `model`
260
+ `consolidation_model`) → OpenCode config (`small_model` / `model`) → a `model`
182
261
  on your own `memorize-extract`/`memorize` agent definition, if you overrode
183
262
  one → the provider's default model. Note that the first two pass the model
184
263
  explicitly, so they win over an agent-level `model`.
185
264
 
186
- > Note: `dedicated_tools` defaults to `true` here (codex defaults it to `false`).
265
+ > Note: `dedicated_tools` defaults to `true` here (Codex defaults it to `false`).
187
266
  > This is the one intentional default difference — the tools are a core part of a
188
- > standalone memory plugin. Everything else matches codex's defaults.
267
+ > standalone memory plugin. Everything else matches Codex's defaults.
189
268
  >
190
269
  > Turning `dedicated_tools` off keeps background learning, summary injection,
191
- > and citation tracking working. The injected guidance switches to codex's
270
+ > and citation tracking working. The injected guidance switches to Codex's
192
271
  > file-based mode — the agent reads the memory files with its normal file
193
272
  > tools and writes "remember this" notes directly into
194
273
  > `extensions/ad_hoc/notes/`. Caveat: the memory folder lives outside your
195
- > project, so opencode raises an `external_directory` permission prompt the
274
+ > project, so OpenCode raises an `external_directory` permission prompt the
196
275
  > first time an agent touches it (allow-always covers later access); agents
197
276
  > whose permissions deny that ask cannot use file-based mode. The dedicated
198
277
  > tools have no such friction — that's why they are the default. The
199
278
  > maintenance tools (`memory_reset`, `memory_inspect`, `memory_mode`) stay
200
279
  > available either way.
201
280
 
202
- ## Under the hood
281
+ ### Sharing memory with the Codex CLI
203
282
 
204
- opencode-codex-memory is a faithful port of the memory system from OpenAI's codex.
283
+ If you switch between OpenCode and OpenAI's Codex CLI on the same machine, the
284
+ plugin can exchange consolidated memories with Codex — in either or both
285
+ directions:
205
286
 
206
- One design choice is worth calling out, because it shapes everything else: **memory
207
- is global.** There's a single store for all your work, not one per project. That's
208
- not an accident of the port — it's codex's own hard-won shape. codex *started* with
209
- per-project memory (a separate bucket per directory, plus a user scope) and
210
- **deliberately removed it** in early 2026, collapsing everything into one global
211
- root for simplicity: one store, one lock, one consolidation pass. Project awareness
212
- didn't disappear — it moved out of storage and into the prompt, as soft "this looks
213
- like it belongs to that project" hints rather than hard partitions. This port
214
- mirrors that exactly.
287
+ ```json
288
+ {
289
+ "plugin": [
290
+ ["opencode-codex-memory@0.4.1", { "codex_interop": { "import": true, "export": true } }]
291
+ ]
292
+ }
293
+ ```
215
294
 
216
- If you want to understand the design, the trade-offs, or contribute, see
217
- [`ARCHITECTURE.md`](./ARCHITECTURE.md). Contributor guidance lives in
218
- [`CONTRIBUTING.md`](./CONTRIBUTING.md) and [`AGENTS.md`](./AGENTS.md) —
219
- in short: this repo exists to port codex's memory system to opencode, and
220
- PRs that break that parity will be rejected.
295
+ - `import` copies Codex's consolidated `MEMORY.md` / `memory_summary.md` into a
296
+ memory extension (`extensions/codex_import/`) before each consolidation pass.
297
+ The consolidator merges what's new, tagging it `[from codex]`.
298
+ - `export` copies this plugin's consolidated memory into Codex's memory
299
+ workspace as an extension (`extensions/opencode_import/`) after each
300
+ successful consolidation, together with instructions for Codex's own
301
+ consolidator. Codex picks it up on its next consolidation — no Codex
302
+ configuration needed. Nothing is exported until Codex's memory feature has
303
+ created `$CODEX_HOME/memories`, and Codex's own files are never modified.
304
+ (After a `memory_reset` here, the last export stays in Codex until your
305
+ next successful consolidation replaces it.)
306
+ - `codex_home` overrides where Codex lives (default: `$CODEX_HOME`, else
307
+ `~/.codex`).
308
+
309
+ Both sides mark imported content with a provenance tag (`[from codex]` /
310
+ `[from opencode]`) and skip content carrying the other side's tag, so memories
311
+ don't ping-pong between the two systems. This follows the same extension
312
+ mechanism Codex itself uses to import Claude memories.
313
+
314
+ ## Why one global memory?
315
+
316
+ There's a single store for everything you do, not one per project — and that's
317
+ the design choice most likely to surprise you, so it's worth explaining where it
318
+ came from.
319
+
320
+ Codex *started* with per-project memory: a separate bucket per directory, plus a
321
+ user-level scope on top. It **deliberately removed that** in early 2026 and
322
+ collapsed everything into one global root — one store, one lock, one
323
+ consolidation pass — for simplicity.
324
+
325
+ Simplicity is easy to underrate here — until you try to draw the boundaries
326
+ yourself. Scoping forces a question that often has no good answer: which project does
327
+ "prefers table-driven tests" belong to? Monorepos, worktrees, and sibling repos
328
+ of the same stack all blur the line, and the most valuable lessons — the ones
329
+ about how *you* work — belong to no project at all. Per-scope stores also each
330
+ consolidate over a thinner slice of evidence than the whole.
331
+
332
+ Project awareness didn't disappear; it moved out of storage and into the prompt.
333
+ Memories carry the project they came from, and the consolidator is told to keep
334
+ per-project detail separable — soft "this looks like it belongs to that project"
335
+ hints rather than hard partitions. You get the cross-project transfer (your
336
+ conventions follow you into a new repo on day one) while project-specific facts
337
+ stay recognizable as such.
338
+
339
+ The cost is real: with one store, an unrelated project's details can surface in
340
+ the summary. Codex judged that cheaper than the alternative, and this port
341
+ mirrors that decision rather than layering scoping back on top.
342
+
343
+ ## Contributing
344
+
345
+ The port follows Codex closely: same two-phase pipeline, same on-disk artifacts,
346
+ same prompts (adapted only where OpenCode differs). If you want the full design
347
+ and the trade-offs, see [`ARCHITECTURE.md`](./ARCHITECTURE.md); contributor
348
+ guidance lives in [`CONTRIBUTING.md`](./CONTRIBUTING.md) and
349
+ [`AGENTS.md`](./AGENTS.md) — in short: this repo exists to port Codex's memory
350
+ system to OpenCode, and PRs that break that parity will be rejected.
221
351
 
222
352
  ## License
223
353
 
224
354
  Apache 2.0 — the same license as [OpenAI Codex](https://github.com/openai/codex),
225
355
  whose memory system this project ports. See [`LICENSE`](./LICENSE) and
226
- [`NOTICE`](./NOTICE). Not affiliated with the codex project.
356
+ [`NOTICE`](./NOTICE). Not affiliated with the Codex project.
@@ -0,0 +1,38 @@
1
+ export declare const IMPORT_EXTENSION = "codex_import";
2
+ export declare const EXPORT_EXTENSION = "opencode_import";
3
+ export interface CodexInteropOptions {
4
+ import: boolean;
5
+ export: boolean;
6
+ codex_home?: string;
7
+ }
8
+ export interface ResolvedCodexInterop {
9
+ codexMemoryRoot: string;
10
+ importEnabled: boolean;
11
+ exportEnabled: boolean;
12
+ }
13
+ /**
14
+ * Resolves the Codex memory root and validates it against the plugin memory
15
+ * root. Precedence for the Codex home: explicit option > CODEX_HOME env >
16
+ * `~/.codex` (codex-rs find_codex_home). Overlapping roots would let one
17
+ * side's sync recurse into the other's workspace, so interop fails closed
18
+ * (returns null) with a warning.
19
+ */
20
+ export declare function resolveCodexInterop(opts: CodexInteropOptions): ResolvedCodexInterop | null;
21
+ /**
22
+ * Import direction: Codex consolidated memory -> our
23
+ * `extensions/codex_import/`. Call inside the claimed phase-2 job, after the
24
+ * git baseline exists (codex prepare_memory_workspace ordering) and before
25
+ * the workspace diff is captured, so copies are consolidated in the same run.
26
+ * Returns true when the plugin workspace changed.
27
+ */
28
+ export declare function syncCodexImport(codexMemoryRoot: string): boolean;
29
+ /**
30
+ * Export direction: our consolidated memory -> Codex's
31
+ * `extensions/opencode_import/`. Strictly additive: never bootstraps the
32
+ * Codex memory workspace (missing `<codex_home>/memories` means Codex's
33
+ * memory feature is not in use) and never touches Codex's state DB — Codex
34
+ * discovers the files through its own workspace diff on its next
35
+ * consolidation. Only valid consolidated artifacts are exported; the seeded
36
+ * placeholder MEMORY.md / empty summary would just be noise.
37
+ */
38
+ export declare function exportToCodexMemory(codexMemoryRoot: string): boolean;