opencode-claude-memory 1.7.6 → 1.7.8
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 +104 -213
- package/dist/agents.d.ts +12 -0
- package/dist/agents.js +69 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.js +79 -0
- package/dist/extraction/ExtractionCoordinator.d.ts +62 -0
- package/dist/extraction/ExtractionCoordinator.js +386 -0
- package/dist/extraction/autodream.d.ts +33 -0
- package/dist/extraction/autodream.js +85 -0
- package/dist/extraction/forkSession.d.ts +37 -0
- package/dist/extraction/forkSession.js +133 -0
- package/dist/extraction/lock.d.ts +29 -0
- package/dist/extraction/lock.js +191 -0
- package/dist/extraction/prompts.d.ts +5 -0
- package/dist/extraction/prompts.js +78 -0
- package/dist/extraction/state.d.ts +38 -0
- package/dist/extraction/state.js +159 -0
- package/dist/hooks/ignore.d.ts +6 -0
- package/dist/hooks/ignore.js +53 -0
- package/dist/hooks/messages.d.ts +14 -0
- package/dist/hooks/messages.js +96 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +50 -633
- package/dist/prompt/sections.d.ts +7 -0
- package/dist/{prompt.js → prompt/sections.js} +16 -72
- package/dist/prompt/systemPrompt.d.ts +7 -0
- package/dist/prompt/systemPrompt.js +64 -0
- package/dist/recall/RecallCoordinator.d.ts +36 -0
- package/dist/recall/RecallCoordinator.js +140 -0
- package/dist/recall/format.d.ts +17 -0
- package/dist/{recall.js → recall/format.js} +13 -36
- package/dist/{recallSelector.d.ts → recall/selector.d.ts} +26 -16
- package/dist/recall/selector.js +92 -0
- package/dist/sdk.d.ts +12 -0
- package/dist/sdk.js +8 -0
- package/dist/store/MemoryStore.d.ts +41 -0
- package/dist/store/MemoryStore.js +105 -0
- package/dist/store/frontmatter.d.ts +25 -0
- package/dist/store/frontmatter.js +61 -0
- package/dist/store/indexFile.d.ts +15 -0
- package/dist/store/indexFile.js +153 -0
- package/dist/{paths.d.ts → store/paths.d.ts} +7 -6
- package/dist/store/paths.js +187 -0
- package/dist/store/scan.d.ts +29 -0
- package/dist/store/scan.js +112 -0
- package/dist/tools.d.ts +9 -0
- package/dist/tools.js +142 -0
- package/dist/util/exclusiveFile.d.ts +11 -0
- package/dist/util/exclusiveFile.js +108 -0
- package/dist/util/legacyShellHook.d.ts +3 -0
- package/dist/util/legacyShellHook.js +23 -0
- package/dist/util/log.d.ts +6 -0
- package/dist/util/log.js +29 -0
- package/dist/util/ownedSessions.d.ts +7 -0
- package/dist/util/ownedSessions.js +39 -0
- package/dist/util/timeout.d.ts +5 -0
- package/dist/util/timeout.js +39 -0
- package/package.json +15 -8
- package/bin/opencode-memory +0 -1436
- package/dist/memory.d.ts +0 -25
- package/dist/memory.js +0 -203
- package/dist/memoryScan.d.ts +0 -20
- package/dist/memoryScan.js +0 -106
- package/dist/nativeExtraction.d.ts +0 -2
- package/dist/nativeExtraction.js +0 -38
- package/dist/paths.js +0 -141
- package/dist/prompt.d.ts +0 -4
- package/dist/recall.d.ts +0 -12
- package/dist/recallSelector.js +0 -154
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# 🧠 Claude Code-compatible memory for OpenCode
|
|
4
4
|
|
|
5
|
-
**Persistent, local-first shared memory for OpenCode and Claude Code —
|
|
5
|
+
**Persistent, local-first shared memory for OpenCode and Claude Code — one plugin, zero migration.**
|
|
6
6
|
|
|
7
|
-
This OpenCode
|
|
7
|
+
This OpenCode plugin lets OpenCode read and write Claude Code-compatible Markdown memory files, so both CLIs share the same project context.
|
|
8
8
|
|
|
9
9
|
Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Claude Code reads it.
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Clau
|
|
|
12
12
|
[](https://www.npmjs.com/package/opencode-claude-memory)
|
|
13
13
|
[](https://github.com/kuitos/opencode-claude-memory/blob/main/LICENSE)
|
|
14
14
|
|
|
15
|
-
[Quick Start](#-quick-start) • [
|
|
15
|
+
[Quick Start](#-quick-start) • [How it works](#-how-it-works) • [Configuration](#-configuration) • [Compatibility](#-compatibility-with-claude-code) • [Migrating from v1](#-migrating-from-v1) • [FAQ](#-faq)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -20,228 +20,95 @@ Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Clau
|
|
|
20
20
|
|
|
21
21
|
## ✨ At a glance
|
|
22
22
|
|
|
23
|
-
- **
|
|
24
|
-
|
|
25
|
-
- **
|
|
26
|
-
|
|
27
|
-
- **
|
|
28
|
-
|
|
29
|
-
- **Local-first, no migration**
|
|
30
|
-
Memory stays as local Markdown files in the same directory Claude Code already uses.
|
|
31
|
-
- **Auto-dream consolidation**
|
|
32
|
-
Periodically runs a background memory consolidation pass (Claude-style auto-dream gating).
|
|
23
|
+
- **Memory tools** — `memory_save` / `memory_delete` / `memory_list` / `memory_search` / `memory_read`, plus the Claude Code memory instructions injected into every system prompt.
|
|
24
|
+
- **LLM recall** — before each turn a hidden agent picks the memories relevant to the query; they appear in the *first* LLM call, including single-step questions.
|
|
25
|
+
- **Automatic extraction** — after a session goes idle, a sandboxed fork reviews only the *new* part of the conversation and saves what is worth keeping. Sessions closed before the fork ran are caught up at the next start.
|
|
26
|
+
- **Auto-dream** — periodic consolidation (merge / prune / rewrite) gated on time and session count, like Claude Code.
|
|
27
|
+
- **Claude Code-compatible** — same directory, same file format, same taxonomy, same worktree handling. `MEMORY.md` is edited line by line so hand-organised indexes stay intact.
|
|
28
|
+
- **Cross-platform, no shell hook** — everything runs inside the OpenCode process through the plugin SDK. No `python3`, no `jq`, no wrapper.
|
|
33
29
|
|
|
34
30
|
## 🚀 Quick Start
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `opencode`
|
|
39
|
-
- `python3` available in `PATH`
|
|
40
|
-
|
|
41
|
-
`python3` is a runtime dependency for the wrapper's scoped session detection and fork cleanup logic.
|
|
42
|
-
If it is missing or not executable, post-session maintenance becomes less reliable: session targeting can fall back to less precise heuristics, and fork cleanup is skipped for safety.
|
|
43
|
-
|
|
44
|
-
Common install commands:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# macOS (Homebrew)
|
|
48
|
-
brew install python
|
|
49
|
-
|
|
50
|
-
# Ubuntu / Debian
|
|
51
|
-
sudo apt-get update && sudo apt-get install -y python3
|
|
52
|
-
|
|
53
|
-
# Fedora
|
|
54
|
-
sudo dnf install -y python3
|
|
55
|
-
|
|
56
|
-
# Arch Linux
|
|
57
|
-
sudo pacman -S python
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### 1. Install
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npm install -g opencode-claude-memory
|
|
64
|
-
opencode-memory install # one-time: installs shell hook
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
This installs:
|
|
68
|
-
- The **plugin** — memory tools + system prompt injection
|
|
69
|
-
- The `opencode-memory` **CLI** — wraps opencode with automatic memory extraction + auto-dream consolidation
|
|
70
|
-
- A **shell hook** — defines an `opencode()` function in your `.zshrc`/`.bashrc` that delegates to `opencode-memory`
|
|
71
|
-
|
|
72
|
-
If `python3` is not installed yet, install it first using the commands above before enabling the shell hook.
|
|
73
|
-
|
|
74
|
-
### 2. Configure
|
|
32
|
+
Requires OpenCode **≥ 1.18**.
|
|
75
33
|
|
|
76
34
|
```jsonc
|
|
77
|
-
// opencode.json
|
|
35
|
+
// opencode.json (project) or ~/.config/opencode/opencode.json (global)
|
|
78
36
|
{
|
|
79
37
|
"plugin": ["opencode-claude-memory"]
|
|
80
38
|
}
|
|
81
39
|
```
|
|
82
40
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
opencode
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
That’s it. Memory extraction runs in the background after each session, and auto-dream consolidation is checked with time/session gates.
|
|
90
|
-
|
|
91
|
-
To uninstall:
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
opencode-memory uninstall # removes shell hook from .zshrc/.bashrc
|
|
95
|
-
npm uninstall -g opencode-claude-memory
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
To print the wrapper package version:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
opencode-memory self -v
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
This removes the shell hook, the CLI, and the plugin. Your saved memories in `~/.claude/projects/` are **not** deleted.
|
|
105
|
-
|
|
106
|
-
## 💡 Why this exists
|
|
107
|
-
|
|
108
|
-
If you use both Claude Code and OpenCode on the same repository, memory often ends up in separate silos.
|
|
109
|
-
|
|
110
|
-
This project solves that by making OpenCode read and write memory in Claude Code’s existing structure, so your context carries over naturally between both tools.
|
|
111
|
-
|
|
112
|
-
## 🧩 What makes this different
|
|
113
|
-
|
|
114
|
-
Most memory plugins introduce a new storage model or migration step.
|
|
115
|
-
|
|
116
|
-
This one is a **compatibility layer**, not a new memory system:
|
|
117
|
-
|
|
118
|
-
- same memory directory conventions as Claude Code
|
|
119
|
-
- same Markdown + frontmatter format
|
|
120
|
-
- same memory taxonomy (`user`, `feedback`, `project`, `reference`)
|
|
121
|
-
- same project/worktree resolution behavior
|
|
122
|
-
|
|
123
|
-
The outcome: **shared context across Claude Code and OpenCode without maintaining two memory systems.**
|
|
41
|
+
That's it. Start `opencode` and use it as usual. Memories live in `~/.claude/projects/<project>/memory/` (or under `$CLAUDE_CONFIG_DIR`), exactly where Claude Code keeps them.
|
|
124
42
|
|
|
125
43
|
## ⚙️ How it works
|
|
126
44
|
|
|
127
45
|
```mermaid
|
|
128
46
|
graph LR
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
47
|
+
U[User turn] --> R[Hidden recall agent<br/>selects relevant memories]
|
|
48
|
+
R --> S[System prompt: instructions + MEMORY.md + recalled memories]
|
|
49
|
+
S --> A[Main agent answers<br/>memory_* tools available]
|
|
50
|
+
A --> I[session.idle]
|
|
51
|
+
I --> E[Extraction fork<br/>new messages only]
|
|
52
|
+
E --> M[(~/.claude/projects/<project>/memory/)]
|
|
53
|
+
E --> D{Auto-dream gate}
|
|
54
|
+
D -->|24h & 5 sessions| C[Consolidation fork]
|
|
55
|
+
C --> M
|
|
137
56
|
```
|
|
138
57
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
4. After you exit, it checks whether the session already wrote memory files
|
|
145
|
-
5. If needed, it forks the session with a memory extraction prompt
|
|
146
|
-
6. It evaluates the auto-dream gate (default: at least 24h since last consolidation and 5 touched sessions)
|
|
147
|
-
7. If the gate passes, it runs a background consolidation pass to merge/prune memories
|
|
148
|
-
8. Maintenance runs **in the background** unless `OPENCODE_MEMORY_FOREGROUND=1`
|
|
149
|
-
9. Terminal maintenance logs are shown in foreground mode by default, or can be forced on/off with `OPENCODE_MEMORY_TERMINAL_LOG=1|0`
|
|
150
|
-
|
|
151
|
-
### Runtime dependencies
|
|
152
|
-
|
|
153
|
-
The wrapper expects `python3` to be available at runtime.
|
|
154
|
-
|
|
155
|
-
It is used for:
|
|
156
|
-
|
|
157
|
-
- scoped session selection from `opencode session list`
|
|
158
|
-
- parsing `opencode export` output to resolve session directories
|
|
159
|
-
- safely identifying and cleaning up forked extraction / auto-dream sessions
|
|
160
|
-
|
|
161
|
-
Without `python3`, the plugin tools still load, but wrapper maintenance is degraded and fork cleanup is intentionally skipped to avoid deleting the wrong session.
|
|
162
|
-
|
|
163
|
-
### Compatibility details
|
|
164
|
-
|
|
165
|
-
The implementation ports core logic from Claude Code for path hashing, git-root/worktree handling, memory format, and memory prompting behavior, so both tools can operate on the same files safely.
|
|
58
|
+
1. **Recall** — `experimental.chat.messages.transform` starts a selector prefetch for each new user turn (a hidden child session running `opencode-memory-recall`). `experimental.chat.system.transform` waits for it up to `recall.waitMs` (default 1.5 s) and injects the selected memories. Memories already in the conversation are not re-injected; after compaction they can surface again.
|
|
59
|
+
2. **Extraction** — every `session.idle` is debounced (`extract.debounceMs`). The plugin fetches the session's messages, slices them after the per-session watermark, and — only if there is a new user message — runs `opencode-memory-extract` in a child session restricted to `memory_save` / `memory_list` / `memory_read`. On success the watermark advances; if the main agent already saved memory in that stretch the fork is skipped.
|
|
60
|
+
3. **Catch-up** — on start-up the plugin lists the project's sessions and extracts the ones updated after their watermark (at most `extract.catchUpLimit`). This covers "answer, then quit immediately".
|
|
61
|
+
4. **Auto-dream** — after each extracted session the gate is evaluated (`autodream.minHours` since the last pass **and** `autodream.minSessions` extracted since). When it passes, `opencode-memory-dream` runs with all five memory tools. A lock file prevents two OpenCode processes from consolidating at once.
|
|
62
|
+
5. **Ignore memory** — "ignore memory" in a user message switches memory off for the rest of the session (no index, no recall); "use memory again" switches it back on.
|
|
166
63
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
| Module | Source | Purpose |
|
|
170
|
-
|---|---|---|
|
|
171
|
-
| `memoryScan.ts` | `memoryScan.ts` | Recursive directory scan + frontmatter header parsing |
|
|
172
|
-
| `recall.ts` + `recallSelector.ts` | `findRelevantMemories.ts` | LLM-selected memory recall + selected memory formatting |
|
|
173
|
-
| `prompt.ts` | `memoryTypes.ts` + `memdir.ts` | System prompt sections, type taxonomy, truncation |
|
|
174
|
-
| `memory.ts` | `memdir.ts` | `truncateEntrypoint()` aligned with `truncateEntrypointContent()` |
|
|
175
|
-
|
|
176
|
-
## 👥 Who this is for
|
|
177
|
-
|
|
178
|
-
- You use **both Claude Code and OpenCode**.
|
|
179
|
-
- You want **one shared memory context** across both tools.
|
|
180
|
-
- You prefer **file-based, local-first memory** you can inspect in Git/worktrees.
|
|
181
|
-
- You don’t want migration overhead or lock-in.
|
|
182
|
-
|
|
183
|
-
## ❓ FAQ
|
|
184
|
-
|
|
185
|
-
### Is this a new memory system?
|
|
186
|
-
|
|
187
|
-
No. It is a compatibility layer that lets OpenCode use Claude Code-compatible memory layout and conventions.
|
|
188
|
-
|
|
189
|
-
### Do I need to migrate existing memory?
|
|
190
|
-
|
|
191
|
-
No migration required. If you already have Claude Code memory files, OpenCode can work with them directly.
|
|
192
|
-
|
|
193
|
-
### Where is data stored?
|
|
194
|
-
|
|
195
|
-
In local files under Claude-style project memory directories (for example, under `~/.claude/projects/<project>/memory/`).
|
|
196
|
-
|
|
197
|
-
### Why file-based memory?
|
|
198
|
-
|
|
199
|
-
File-based memory is transparent, local-first, easy to inspect/diff/back up, and works naturally with existing developer workflows.
|
|
200
|
-
|
|
201
|
-
### Can I disable auto extraction?
|
|
202
|
-
|
|
203
|
-
Yes. Set `OPENCODE_MEMORY_EXTRACT=0`.
|
|
204
|
-
|
|
205
|
-
### Can I disable auto-dream?
|
|
206
|
-
|
|
207
|
-
Yes. Set `OPENCODE_MEMORY_AUTODREAM=0`. You can also tune gates with:
|
|
208
|
-
- `OPENCODE_MEMORY_AUTODREAM_MIN_HOURS`
|
|
209
|
-
- `OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS`
|
|
64
|
+
State that is private to the plugin (watermarks, auto-dream gate, lock) lives in `<CLAUDE_CONFIG_DIR>/opencode-memory/<project>/`, never inside the Claude Code project directory.
|
|
210
65
|
|
|
211
66
|
## 🔧 Configuration
|
|
212
67
|
|
|
213
|
-
|
|
68
|
+
All behaviour is configured through OpenCode's own configuration. There are no `OPENCODE_MEMORY_*` environment variables.
|
|
214
69
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
70
|
+
```jsonc
|
|
71
|
+
// opencode.json
|
|
72
|
+
{
|
|
73
|
+
"plugin": [
|
|
74
|
+
["opencode-claude-memory", {
|
|
75
|
+
"extract": { "enabled": true, "timeoutMs": 120000, "debounceMs": 10000, "maxConversationChars": 60000, "catchUpLimit": 5 },
|
|
76
|
+
"autodream": { "enabled": true, "minHours": 24, "minSessions": 5, "timeoutMs": 300000 },
|
|
77
|
+
"recall": { "enabled": true, "waitMs": 1500, "timeoutMs": 30000, "maxMemories": 5 }
|
|
78
|
+
}]
|
|
79
|
+
],
|
|
80
|
+
"agent": {
|
|
81
|
+
"opencode-memory-extract": { "model": "anthropic/claude-haiku-4-5", "steps": 20 },
|
|
82
|
+
"opencode-memory-recall": { "model": "anthropic/claude-haiku-4-5" },
|
|
83
|
+
"opencode-memory-dream": { "model": "anthropic/claude-sonnet-5" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
228
87
|
|
|
229
|
-
|
|
88
|
+
- Every option above is optional; the values shown are the defaults. Unknown keys are rejected when the plugin loads.
|
|
89
|
+
- When the same plugin is listed in both the global and the project `opencode.json`, OpenCode keeps the **last** declaration (project wins); options are not merged across files.
|
|
90
|
+
- The three agents are registered hidden with a memory-only tool sandbox. Override any field (`model`, `steps`, `temperature`, …) under `agent.<name>`; the plugin fills in the rest.
|
|
91
|
+
- `CLAUDE_CONFIG_DIR` is honoured exactly like Claude Code does, and is the only environment variable the plugin reads.
|
|
230
92
|
|
|
231
|
-
Logs
|
|
232
|
-
- `extract-*.log`: automatic memory extraction
|
|
233
|
-
- `dream-*.log`: auto-dream consolidation
|
|
93
|
+
Logs go to the OpenCode service log (`opencode` log directory, service `opencode-claude-memory`).
|
|
234
94
|
|
|
235
|
-
|
|
95
|
+
## 🤝 Compatibility with Claude Code
|
|
236
96
|
|
|
237
|
-
|
|
97
|
+
| Aspect | Claude Code | This plugin |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Memory directory | `~/.claude/projects/<sanitized canonical git root>/memory/` | identical (`sanitizePath`, worktree → main repo resolution ported byte for byte) |
|
|
100
|
+
| File format | Markdown + `name` / `description` / `type` frontmatter | identical; frontmatter parsed only within the first 30 lines, as in Claude Code |
|
|
101
|
+
| Taxonomy | `user`, `feedback`, `project`, `reference` | identical |
|
|
102
|
+
| `MEMORY.md` | one-line pointers, hand-organisable | read with the same truncation rules; written with minimal line-level edits |
|
|
103
|
+
| Sub-directories | `team/x.md` etc. | scanned, recalled and addressable from every tool |
|
|
104
|
+
| System prompt | memory instructions + index + recalled memories | ported sections (`memoryTypes.ts`, `memdir.ts`) |
|
|
105
|
+
| Recall | LLM side query | LLM side query in a hidden child session (`findRelevantMemories.ts` port) |
|
|
106
|
+
| Extraction / auto-dream | after session, gated | after `session.idle` + start-up catch-up, gated the same way |
|
|
238
107
|
|
|
239
|
-
|
|
108
|
+
Memory files written by either tool need no conversion in either direction.
|
|
240
109
|
|
|
241
110
|
## 📝 Memory format
|
|
242
111
|
|
|
243
|
-
Each memory is a Markdown file with YAML frontmatter:
|
|
244
|
-
|
|
245
112
|
```markdown
|
|
246
113
|
---
|
|
247
114
|
name: User prefers terse responses
|
|
@@ -255,32 +122,56 @@ Skip post-action summaries. User reads diffs directly.
|
|
|
255
122
|
**How to apply:** Don't summarize changes at the end of responses.
|
|
256
123
|
```
|
|
257
124
|
|
|
258
|
-
|
|
259
|
-
- `user`
|
|
260
|
-
- `feedback`
|
|
261
|
-
- `project`
|
|
262
|
-
- `reference`
|
|
125
|
+
## 🔁 Migrating from v1
|
|
263
126
|
|
|
264
|
-
|
|
127
|
+
v2 removes the shell wrapper, the `opencode-memory` CLI and every `OPENCODE_MEMORY_*` environment variable. Memory files are untouched and need no conversion.
|
|
265
128
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
-
|
|
269
|
-
|
|
270
|
-
- `memory_read`: read full memory content
|
|
129
|
+
```bash
|
|
130
|
+
# 1. remove the v1 shell hook, then the v1 package (v2 no longer needs a global install)
|
|
131
|
+
opencode-memory uninstall # or delete the ">>> opencode-memory auto-initialization >>>" block from your rc file
|
|
132
|
+
npm uninstall -g opencode-claude-memory
|
|
271
133
|
|
|
272
|
-
|
|
134
|
+
# 2. drop OPENCODE_MEMORY_* from your shell configuration
|
|
135
|
+
grep -n OPENCODE_MEMORY ~/.zshrc ~/.bashrc ~/.zshenv ~/.profile 2>/dev/null
|
|
136
|
+
```
|
|
273
137
|
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
|
|
138
|
+
```jsonc
|
|
139
|
+
// 3. pin the major in opencode.json — OpenCode caches npm plugins per specifier,
|
|
140
|
+
// so a bare "opencode-claude-memory" keeps serving the v1 it installed earlier
|
|
141
|
+
{
|
|
142
|
+
"plugin": ["opencode-claude-memory@2"]
|
|
143
|
+
}
|
|
144
|
+
```
|
|
277
145
|
|
|
278
|
-
|
|
279
|
-
bun run build
|
|
146
|
+
Everything the environment variables used to control now lives under `extract`, `autodream`, `recall` and `agent.opencode-memory-*` in `opencode.json` — see [Configuration](#-configuration). The v1 documentation, including the full list of environment variables, stays available in the [v1 README](https://github.com/kuitos/opencode-claude-memory/blob/v1.7.7/README.md).
|
|
280
147
|
|
|
281
|
-
|
|
148
|
+
## ❓ FAQ
|
|
149
|
+
|
|
150
|
+
**Is this a new memory system?** No. It is a compatibility layer around Claude Code's memory layout and conventions.
|
|
151
|
+
|
|
152
|
+
**Do I need to migrate existing memory?** No. Existing Claude Code memory files are used as they are.
|
|
153
|
+
|
|
154
|
+
**Where is data stored?** `~/.claude/projects/<project>/memory/` (or `$CLAUDE_CONFIG_DIR/projects/...`). Plugin state lives in `$CLAUDE_CONFIG_DIR/opencode-memory/<project>/`.
|
|
155
|
+
|
|
156
|
+
**Can I disable extraction, auto-dream or recall?** Yes — `extract.enabled`, `autodream.enabled`, `recall.enabled` in the plugin options.
|
|
157
|
+
|
|
158
|
+
**Why did my first answer take a moment longer?** The system prompt waits up to `recall.waitMs` for the selector. Set it to `0` to never wait (recalled memories then appear from the second LLM call of a turn onwards).
|
|
159
|
+
|
|
160
|
+
**Does the extraction fork see my whole conversation?** Only the messages after the last extraction, capped at `extract.maxConversationChars` (newest first). The fork can only call memory tools.
|
|
161
|
+
|
|
162
|
+
## 🧪 Development
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bun install
|
|
166
|
+
bun test # unit, integration and eval tests
|
|
167
|
+
bun run evals # readable task-eval report
|
|
168
|
+
bun run lint # biome
|
|
169
|
+
bun run typecheck
|
|
170
|
+
bun run build # emits dist/
|
|
282
171
|
```
|
|
283
172
|
|
|
173
|
+
Releases are cut by semantic-release on push to `main`.
|
|
174
|
+
|
|
284
175
|
## 📄 License
|
|
285
176
|
|
|
286
177
|
[MIT](LICENSE) © [kuitos](https://github.com/kuitos)
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MemoryAgents } from "./config.js";
|
|
2
|
+
import type { AgentConfig, PluginConfig } from "./sdk.js";
|
|
3
|
+
export declare const MEMORY_TOOL_NAMES: readonly ["memory_save", "memory_delete", "memory_list", "memory_search", "memory_read"];
|
|
4
|
+
export declare function buildAgentDefaults(agents: MemoryAgents): Record<string, AgentConfig>;
|
|
5
|
+
export declare function mergeAgentConfig(defaults: AgentConfig, user: AgentConfig | undefined): AgentConfig;
|
|
6
|
+
export declare class AgentRegistry {
|
|
7
|
+
private readonly defaults;
|
|
8
|
+
private readonly merged;
|
|
9
|
+
constructor(agents: MemoryAgents);
|
|
10
|
+
register(config: PluginConfig): void;
|
|
11
|
+
toolsFor(name: string): Record<string, boolean> | undefined;
|
|
12
|
+
}
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AUTODREAM_PROMPT, EXTRACT_PROMPT } from "./extraction/prompts.js";
|
|
2
|
+
import { SELECT_MEMORIES_SYSTEM_PROMPT } from "./recall/selector.js";
|
|
3
|
+
export const MEMORY_TOOL_NAMES = [
|
|
4
|
+
"memory_save",
|
|
5
|
+
"memory_delete",
|
|
6
|
+
"memory_list",
|
|
7
|
+
"memory_search",
|
|
8
|
+
"memory_read",
|
|
9
|
+
];
|
|
10
|
+
const ALL_MEMORY_TOOLS = Object.fromEntries(MEMORY_TOOL_NAMES.map((name) => [name, true]));
|
|
11
|
+
export function buildAgentDefaults(agents) {
|
|
12
|
+
return {
|
|
13
|
+
[agents.recall]: {
|
|
14
|
+
mode: "all",
|
|
15
|
+
hidden: true,
|
|
16
|
+
temperature: 0,
|
|
17
|
+
prompt: SELECT_MEMORIES_SYSTEM_PROMPT,
|
|
18
|
+
tools: { "*": false },
|
|
19
|
+
},
|
|
20
|
+
[agents.extract]: {
|
|
21
|
+
mode: "all",
|
|
22
|
+
hidden: true,
|
|
23
|
+
prompt: EXTRACT_PROMPT,
|
|
24
|
+
// A legitimate extraction is a handful of memory_save calls; the step cap terminates a model
|
|
25
|
+
// that keeps re-saving the same files instead of letting it spin until the timeout (#35).
|
|
26
|
+
steps: 30,
|
|
27
|
+
tools: { "*": false, memory_save: true, memory_list: true, memory_read: true },
|
|
28
|
+
},
|
|
29
|
+
[agents.dream]: {
|
|
30
|
+
mode: "all",
|
|
31
|
+
hidden: true,
|
|
32
|
+
prompt: AUTODREAM_PROMPT,
|
|
33
|
+
steps: 60,
|
|
34
|
+
tools: { "*": false, ...ALL_MEMORY_TOOLS },
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function mergeAgentConfig(defaults, user) {
|
|
39
|
+
if (!user)
|
|
40
|
+
return { ...defaults };
|
|
41
|
+
const merged = { ...defaults, ...user };
|
|
42
|
+
// `maxSteps` is the deprecated spelling of `steps`; a user who only set the old name must not be
|
|
43
|
+
// overridden by the default `steps`.
|
|
44
|
+
if (user.steps === undefined && user.maxSteps !== undefined)
|
|
45
|
+
delete merged.steps;
|
|
46
|
+
return merged;
|
|
47
|
+
}
|
|
48
|
+
export class AgentRegistry {
|
|
49
|
+
defaults;
|
|
50
|
+
merged;
|
|
51
|
+
constructor(agents) {
|
|
52
|
+
this.defaults = buildAgentDefaults(agents);
|
|
53
|
+
this.merged = { ...this.defaults };
|
|
54
|
+
}
|
|
55
|
+
// `config` hook: merge defaults under the user's own entries and remember the outcome so forks can
|
|
56
|
+
// pass the effective tool sandbox explicitly (defence in depth, see forkSession.ts).
|
|
57
|
+
register(config) {
|
|
58
|
+
config.agent ??= {};
|
|
59
|
+
const agent = config.agent;
|
|
60
|
+
for (const [name, defaults] of Object.entries(this.defaults)) {
|
|
61
|
+
const merged = mergeAgentConfig(defaults, agent[name]);
|
|
62
|
+
agent[name] = merged;
|
|
63
|
+
this.merged[name] = merged;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
toolsFor(name) {
|
|
67
|
+
return this.merged[name]?.tools;
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const MemoryOptionsSchema: z.ZodObject<{
|
|
3
|
+
extract: z.ZodDefault<z.ZodObject<{
|
|
4
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
5
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
6
|
+
debounceMs: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
maxConversationChars: z.ZodDefault<z.ZodNumber>;
|
|
8
|
+
catchUpLimit: z.ZodDefault<z.ZodNumber>;
|
|
9
|
+
}, "strict", z.ZodTypeAny, {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
debounceMs: number;
|
|
13
|
+
maxConversationChars: number;
|
|
14
|
+
catchUpLimit: number;
|
|
15
|
+
}, {
|
|
16
|
+
enabled?: boolean | undefined;
|
|
17
|
+
timeoutMs?: number | undefined;
|
|
18
|
+
debounceMs?: number | undefined;
|
|
19
|
+
maxConversationChars?: number | undefined;
|
|
20
|
+
catchUpLimit?: number | undefined;
|
|
21
|
+
}>>;
|
|
22
|
+
autodream: z.ZodDefault<z.ZodObject<{
|
|
23
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
minHours: z.ZodDefault<z.ZodNumber>;
|
|
25
|
+
minSessions: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
}, "strict", z.ZodTypeAny, {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
timeoutMs: number;
|
|
30
|
+
minHours: number;
|
|
31
|
+
minSessions: number;
|
|
32
|
+
}, {
|
|
33
|
+
enabled?: boolean | undefined;
|
|
34
|
+
timeoutMs?: number | undefined;
|
|
35
|
+
minHours?: number | undefined;
|
|
36
|
+
minSessions?: number | undefined;
|
|
37
|
+
}>>;
|
|
38
|
+
recall: z.ZodDefault<z.ZodObject<{
|
|
39
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
40
|
+
waitMs: z.ZodDefault<z.ZodNumber>;
|
|
41
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
42
|
+
maxMemories: z.ZodDefault<z.ZodNumber>;
|
|
43
|
+
}, "strict", z.ZodTypeAny, {
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
timeoutMs: number;
|
|
46
|
+
waitMs: number;
|
|
47
|
+
maxMemories: number;
|
|
48
|
+
}, {
|
|
49
|
+
enabled?: boolean | undefined;
|
|
50
|
+
timeoutMs?: number | undefined;
|
|
51
|
+
waitMs?: number | undefined;
|
|
52
|
+
maxMemories?: number | undefined;
|
|
53
|
+
}>>;
|
|
54
|
+
}, "strict", z.ZodTypeAny, {
|
|
55
|
+
extract: {
|
|
56
|
+
enabled: boolean;
|
|
57
|
+
timeoutMs: number;
|
|
58
|
+
debounceMs: number;
|
|
59
|
+
maxConversationChars: number;
|
|
60
|
+
catchUpLimit: number;
|
|
61
|
+
};
|
|
62
|
+
autodream: {
|
|
63
|
+
enabled: boolean;
|
|
64
|
+
timeoutMs: number;
|
|
65
|
+
minHours: number;
|
|
66
|
+
minSessions: number;
|
|
67
|
+
};
|
|
68
|
+
recall: {
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
timeoutMs: number;
|
|
71
|
+
waitMs: number;
|
|
72
|
+
maxMemories: number;
|
|
73
|
+
};
|
|
74
|
+
}, {
|
|
75
|
+
extract?: {
|
|
76
|
+
enabled?: boolean | undefined;
|
|
77
|
+
timeoutMs?: number | undefined;
|
|
78
|
+
debounceMs?: number | undefined;
|
|
79
|
+
maxConversationChars?: number | undefined;
|
|
80
|
+
catchUpLimit?: number | undefined;
|
|
81
|
+
} | undefined;
|
|
82
|
+
autodream?: {
|
|
83
|
+
enabled?: boolean | undefined;
|
|
84
|
+
timeoutMs?: number | undefined;
|
|
85
|
+
minHours?: number | undefined;
|
|
86
|
+
minSessions?: number | undefined;
|
|
87
|
+
} | undefined;
|
|
88
|
+
recall?: {
|
|
89
|
+
enabled?: boolean | undefined;
|
|
90
|
+
timeoutMs?: number | undefined;
|
|
91
|
+
waitMs?: number | undefined;
|
|
92
|
+
maxMemories?: number | undefined;
|
|
93
|
+
} | undefined;
|
|
94
|
+
}>;
|
|
95
|
+
export type MemoryOptions = z.infer<typeof MemoryOptionsSchema>;
|
|
96
|
+
export declare const MEMORY_AGENTS: {
|
|
97
|
+
readonly extract: "opencode-memory-extract";
|
|
98
|
+
readonly recall: "opencode-memory-recall";
|
|
99
|
+
readonly dream: "opencode-memory-dream";
|
|
100
|
+
};
|
|
101
|
+
export type MemoryAgents = typeof MEMORY_AGENTS;
|
|
102
|
+
export type MemoryConfig = MemoryOptions & {
|
|
103
|
+
claudeConfigDir: string;
|
|
104
|
+
homeDir: string;
|
|
105
|
+
agents: MemoryAgents;
|
|
106
|
+
};
|
|
107
|
+
export declare class MemoryConfigError extends Error {
|
|
108
|
+
constructor(message: string);
|
|
109
|
+
}
|
|
110
|
+
export declare function resolveClaudeConfigDir(env?: NodeJS.ProcessEnv): string;
|
|
111
|
+
export declare function parseMemoryOptions(options: unknown): MemoryOptions;
|
|
112
|
+
export declare function parseConfig(options: unknown, env?: NodeJS.ProcessEnv, homeDir?: string): MemoryConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
5
|
+
const timeoutMs = (fallback) => z.number().int().positive().max(MAX_TIMER_DELAY_MS).default(fallback);
|
|
6
|
+
// Plugin options come from `opencode.json`: `"plugin": [["opencode-claude-memory", { ... }]]`.
|
|
7
|
+
// The schema is strict at every level so a misspelled key fails at plugin load instead of silently
|
|
8
|
+
// falling back to defaults.
|
|
9
|
+
export const MemoryOptionsSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
extract: z
|
|
12
|
+
.object({
|
|
13
|
+
enabled: z.boolean().default(true),
|
|
14
|
+
timeoutMs: timeoutMs(120_000),
|
|
15
|
+
debounceMs: z.number().int().nonnegative().max(MAX_TIMER_DELAY_MS).default(10_000),
|
|
16
|
+
maxConversationChars: z.number().int().positive().default(60_000),
|
|
17
|
+
catchUpLimit: z.number().int().nonnegative().default(5),
|
|
18
|
+
})
|
|
19
|
+
.strict()
|
|
20
|
+
.default({}),
|
|
21
|
+
autodream: z
|
|
22
|
+
.object({
|
|
23
|
+
enabled: z.boolean().default(true),
|
|
24
|
+
minHours: z.number().positive().default(24),
|
|
25
|
+
minSessions: z.number().int().positive().default(5),
|
|
26
|
+
timeoutMs: timeoutMs(300_000),
|
|
27
|
+
})
|
|
28
|
+
.strict()
|
|
29
|
+
.default({}),
|
|
30
|
+
recall: z
|
|
31
|
+
.object({
|
|
32
|
+
enabled: z.boolean().default(true),
|
|
33
|
+
waitMs: z.number().int().nonnegative().max(MAX_TIMER_DELAY_MS).default(1_500),
|
|
34
|
+
timeoutMs: timeoutMs(30_000),
|
|
35
|
+
maxMemories: z.number().int().positive().max(20).default(5),
|
|
36
|
+
})
|
|
37
|
+
.strict()
|
|
38
|
+
.default({}),
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
41
|
+
// Agent names are fixed. Users customise the agents themselves (`agent.opencode-memory-extract.model`
|
|
42
|
+
// in opencode.json) instead of pointing the plugin at a differently named agent.
|
|
43
|
+
export const MEMORY_AGENTS = {
|
|
44
|
+
extract: "opencode-memory-extract",
|
|
45
|
+
recall: "opencode-memory-recall",
|
|
46
|
+
dream: "opencode-memory-dream",
|
|
47
|
+
};
|
|
48
|
+
export class MemoryConfigError extends Error {
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "MemoryConfigError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function formatIssue(issue) {
|
|
55
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
56
|
+
return `${path}: ${issue.message}`;
|
|
57
|
+
}
|
|
58
|
+
// `CLAUDE_CONFIG_DIR` is the only environment variable the plugin reads: it is shared with Claude Code
|
|
59
|
+
// and decides where both tools keep memory. Everything else is plugin options.
|
|
60
|
+
export function resolveClaudeConfigDir(env = process.env) {
|
|
61
|
+
const raw = env.CLAUDE_CONFIG_DIR?.trim();
|
|
62
|
+
return (raw ? raw : join(homedir(), ".claude")).normalize("NFC");
|
|
63
|
+
}
|
|
64
|
+
export function parseMemoryOptions(options) {
|
|
65
|
+
const result = MemoryOptionsSchema.safeParse(options ?? {});
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
const issues = result.error.issues.map(formatIssue).join("; ");
|
|
68
|
+
throw new MemoryConfigError(`opencode-claude-memory: invalid plugin options (${issues})`);
|
|
69
|
+
}
|
|
70
|
+
return result.data;
|
|
71
|
+
}
|
|
72
|
+
export function parseConfig(options, env = process.env, homeDir = homedir()) {
|
|
73
|
+
return {
|
|
74
|
+
...parseMemoryOptions(options),
|
|
75
|
+
claudeConfigDir: resolveClaudeConfigDir(env),
|
|
76
|
+
homeDir,
|
|
77
|
+
agents: MEMORY_AGENTS,
|
|
78
|
+
};
|
|
79
|
+
}
|