@basicmemory/openclaw-basic-memory 0.1.0-alpha.9 → 0.1.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 +140 -504
- package/bm-client.ts +27 -21
- package/config.ts +0 -24
- package/index.ts +54 -41
- package/openclaw.plugin.json +0 -12
- package/package.json +2 -2
- package/schema/conversation-schema.ts +33 -0
- package/analytics.ts +0 -162
package/README.md
CHANGED
|
@@ -1,134 +1,66 @@
|
|
|
1
1
|
# openclaw-basic-memory
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Give your OpenClaw agent persistent, searchable memory — in plain text files you can read and edit.
|
|
4
4
|
|
|
5
|
-
## What
|
|
5
|
+
## What is Basic Memory?
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[Basic Memory](https://basicmemory.com) stores AI knowledge in local Markdown files and indexes them into a semantic knowledge graph. Your agent writes notes. You can open them in any editor, read them, change them, and the changes sync back automatically. No black box. No proprietary format. Just files.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **Persistent MCP stdio session** — keeps a single `bm mcp --transport stdio --project <name>` process alive
|
|
11
|
-
- **Auto-recall** — injects active tasks and recent activity as context at session start
|
|
12
|
-
- **Auto-capture** — records agent conversations as structured daily notes
|
|
13
|
-
- **Graph tools** — search, read, write, edit, delete, move, and navigate notes via `memory://` URLs
|
|
14
|
-
- **Skill workflows** — `/tasks`, `/reflect`, `/defrag`, `/schema` slash commands for guided memory management
|
|
9
|
+
It does three things that work together:
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
- **Stores knowledge in plain Markdown** — everything lives in plain text files on your computer, not locked inside a database you can't read
|
|
12
|
+
- **Creates connections automatically** — notes link to each other through a searchable, traversable knowledge graph
|
|
13
|
+
- **Searches by meaning, not just keywords** — vector search finds relevant context even when the exact words don't match
|
|
14
|
+
- **Keeps notes consistent** — dynamic schemas and validation ensure your knowledge base stays structured and useful as it grows
|
|
15
|
+
- **Enables two-way collaboration** — both you and the AI read and write the same files
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
Over time, your agent builds a knowledge base that grows with you. Context that survives across sessions. Memory that belongs to you.
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
Learn more: [basicmemory.com](https://basicmemory.com) · [GitHub](https://github.com/basicmachines-co/basic-memory) · [Docs](https://docs.basicmemory.com)
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# macOS
|
|
25
|
-
brew install uv
|
|
21
|
+
## What this plugin does
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
29
|
-
```
|
|
23
|
+
This plugin connects Basic Memory to OpenClaw so your agent can:
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
- **Remember across sessions** — search and recall past conversations, decisions, and context
|
|
26
|
+
- **Track work in progress** — structured tasks that survive context compaction
|
|
27
|
+
- **Build knowledge over time** — notes, observations, and relations that grow into a connected graph
|
|
28
|
+
- **Search intelligently** — composited search across working memory, the knowledge graph, and active tasks in parallel
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
All data stays on your machine as Markdown files indexed locally with SQLite. Cloud sync is available but entirely optional.
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
# Install the plugin (automatically installs the bm CLI via uv)
|
|
37
|
-
openclaw plugins install @basicmemory/openclaw-basic-memory
|
|
32
|
+
## Install
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
openclaw plugins enable openclaw-basic-memory --slot memory
|
|
34
|
+
**Prerequisite:** [uv](https://docs.astral.sh/uv/) (Python package manager) — used to install the Basic Memory CLI.
|
|
41
35
|
|
|
42
|
-
# Restart the gateway
|
|
43
|
-
openclaw gateway restart
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Verify:
|
|
47
36
|
```bash
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```
|
|
37
|
+
# macOS
|
|
38
|
+
brew install uv
|
|
51
39
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
bash ~/.openclaw/extensions/openclaw-basic-memory/scripts/setup-bm.sh
|
|
40
|
+
# macOS / Linux
|
|
41
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
56
42
|
```
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Everything works locally — cloud adds cross-device, team, and production capabilities:
|
|
61
|
-
|
|
62
|
-
- **Your agent's memory travels with you** — same knowledge graph on laptop, desktop, and hosted environments
|
|
63
|
-
- **Team knowledge sharing** — org workspaces let multiple agents and team members build on a shared knowledge base
|
|
64
|
-
- **Durable memory for production agents** — persistent memory that survives CI teardowns and container restarts
|
|
65
|
-
- **Multi-agent coordination** — multiple agents can read and write to the same graph
|
|
66
|
-
|
|
67
|
-
Cloud extends local-first — still plain markdown, still yours. Start with a [7-day free trial](https://basicmemory.com) and use code `BMCLAW` for 20% off for 3 months. See [BASIC_MEMORY.md](./BASIC_MEMORY.md) for cloud setup.
|
|
68
|
-
|
|
69
|
-
### Development (local directory)
|
|
70
|
-
|
|
71
|
-
For plugin development, clone and link locally:
|
|
44
|
+
Then install the plugin:
|
|
72
45
|
|
|
73
46
|
```bash
|
|
74
|
-
|
|
75
|
-
cd openclaw-basic-memory
|
|
76
|
-
bun install
|
|
77
|
-
openclaw plugins install -l "$PWD"
|
|
78
|
-
openclaw plugins enable openclaw-basic-memory --slot memory
|
|
47
|
+
openclaw plugins install @basicmemory/openclaw-basic-memory
|
|
79
48
|
openclaw gateway restart
|
|
80
49
|
```
|
|
81
50
|
|
|
82
|
-
|
|
51
|
+
That's it. The plugin auto-installs the `bm` CLI on first startup if it's not already on your PATH. See [SECURITY.md](./SECURITY.md) for details on how this works.
|
|
83
52
|
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
plugins: {
|
|
87
|
-
load: {
|
|
88
|
-
paths: ["~/dev/openclaw-basic-memory"]
|
|
89
|
-
},
|
|
90
|
-
entries: {
|
|
91
|
-
"openclaw-basic-memory": {
|
|
92
|
-
enabled: true
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
slots: {
|
|
96
|
-
memory: "openclaw-basic-memory"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Bundled Skills
|
|
103
|
-
|
|
104
|
-
This plugin ships with six skills that are automatically available when the plugin is enabled — no manual installation needed:
|
|
105
|
-
|
|
106
|
-
- **`memory-tasks`** — structured task tracking that survives context compaction
|
|
107
|
-
- **`memory-reflect`** — periodic consolidation of recent notes into durable memory
|
|
108
|
-
- **`memory-defrag`** — periodic cleanup/reorganization of memory files
|
|
109
|
-
- **`memory-schema`** — schema lifecycle management (infer, create, validate, diff, evolve)
|
|
110
|
-
- **`memory-metadata-search`** — structured metadata search by custom frontmatter fields (status, priority, etc.)
|
|
111
|
-
- **`memory-notes`** — guidance for writing well-structured notes with observations and relations
|
|
112
|
-
|
|
113
|
-
Skills are loaded from the plugin's `skills/` directory. See the upstream source at [`basic-memory-skills`](https://github.com/basicmachines-co/basic-memory-skills).
|
|
114
|
-
|
|
115
|
-
#### Updating or adding skills with `npx skills`
|
|
116
|
-
|
|
117
|
-
You can update bundled skills or install new ones as they become available using the [Skills CLI](https://github.com/vercel-labs/skills):
|
|
53
|
+
Verify:
|
|
118
54
|
|
|
119
55
|
```bash
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# Install a specific skill
|
|
124
|
-
npx skills add basicmachines-co/basic-memory-skills --name memory-tasks --agent openclaw
|
|
56
|
+
openclaw plugins list
|
|
57
|
+
openclaw plugins info openclaw-basic-memory
|
|
125
58
|
```
|
|
126
59
|
|
|
127
|
-
This installs to the same `skills/` directory the plugin reads from, so updated skills take effect on the next session.
|
|
128
|
-
|
|
129
60
|
## Configuration
|
|
130
61
|
|
|
131
|
-
###
|
|
62
|
+
### Zero-config (recommended)
|
|
63
|
+
|
|
132
64
|
```json5
|
|
133
65
|
{
|
|
134
66
|
"openclaw-basic-memory": {
|
|
@@ -137,476 +69,180 @@ This installs to the same `skills/` directory the plugin reads from, so updated
|
|
|
137
69
|
}
|
|
138
70
|
```
|
|
139
71
|
|
|
140
|
-
This uses sensible defaults: auto-generated project name, maps
|
|
72
|
+
This uses sensible defaults: auto-generated project name, maps to your workspace root, captures conversations, and recalls active tasks on session start.
|
|
73
|
+
|
|
74
|
+
### Full options
|
|
141
75
|
|
|
142
|
-
### Full configuration
|
|
143
76
|
```json5
|
|
144
77
|
{
|
|
145
78
|
"openclaw-basic-memory": {
|
|
146
79
|
enabled: true,
|
|
147
80
|
config: {
|
|
148
|
-
project: "my-agent",
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
autoRecall: true, // Inject active tasks + recent activity at session start
|
|
156
|
-
recallPrompt: "Check for active tasks and recent activity. Summarize anything relevant to the current session.",
|
|
157
|
-
debug: false, // Verbose logging
|
|
81
|
+
project: "my-agent", // BM project name (default: "openclaw-{hostname}")
|
|
82
|
+
projectPath: ".", // Project directory (default: workspace root)
|
|
83
|
+
memoryDir: "memory/", // Where task notes live
|
|
84
|
+
memoryFile: "MEMORY.md", // Working memory file
|
|
85
|
+
autoCapture: true, // Record conversations as daily notes
|
|
86
|
+
autoRecall: true, // Inject active tasks + recent activity at session start
|
|
87
|
+
debug: false // Verbose logging
|
|
158
88
|
}
|
|
159
89
|
}
|
|
160
90
|
}
|
|
161
91
|
```
|
|
162
92
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
|
166
|
-
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `recallPrompt` | string | *(see above)* | Instruction appended to recalled context — customize to change what the agent focuses on |
|
|
176
|
-
| `debug` | boolean | `false` | Enable verbose debug logs |
|
|
177
|
-
|
|
178
|
-
Snake_case aliases (`memory_dir`, `memory_file`, `auto_recall`, `recall_prompt`, `capture_min_chars`) are also supported.
|
|
179
|
-
|
|
180
|
-
Cloud sync is optional — see [BASIC_MEMORY.md](./BASIC_MEMORY.md) for cloud configuration.
|
|
181
|
-
|
|
182
|
-
On startup, the plugin ensures the configured BM project exists at `projectPath` via MCP `create_memory_project` in idempotent mode, and sets it as the default Basic Memory project.
|
|
183
|
-
|
|
184
|
-
## How It Works
|
|
185
|
-
|
|
186
|
-
### MCP Session Lifecycle
|
|
187
|
-
On startup, the plugin starts one persistent MCP stdio session:
|
|
188
|
-
1. Spawns `bm mcp --transport stdio --project <name>`
|
|
189
|
-
2. Verifies required MCP tool capabilities at connect time
|
|
190
|
-
3. Uses bounded reconnect attempts (`500ms`, `1000ms`, `2000ms`) when the session drops
|
|
191
|
-
|
|
192
|
-
Basic Memory MCP lifecycle handles sync and watch behavior for the project.
|
|
193
|
-
|
|
194
|
-
### Composited `memory_search`
|
|
195
|
-
When the agent calls `memory_search`, three sources are queried in parallel:
|
|
196
|
-
|
|
197
|
-
1. **MEMORY.md** — grep/text search with ±1 line context
|
|
198
|
-
2. **BM Knowledge Graph** — hybrid FTS + vector search (top 5 results with scores)
|
|
199
|
-
3. **Active Tasks** — scans `memory/tasks/` for non-done tasks
|
|
200
|
-
|
|
201
|
-
Results are formatted into clear sections:
|
|
202
|
-
```
|
|
203
|
-
## MEMORY.md
|
|
204
|
-
- matching lines with context...
|
|
205
|
-
|
|
206
|
-
## Knowledge Graph (memory/)
|
|
207
|
-
- note-title (0.85)
|
|
208
|
-
> preview of content...
|
|
209
|
-
|
|
210
|
-
## Active Tasks
|
|
211
|
-
- **Task Name** (status: active, step: 3)
|
|
212
|
-
context snippet...
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Memory + Task Management Flow
|
|
216
|
-
|
|
217
|
-
This plugin works best if you treat memory as three lanes:
|
|
218
|
-
|
|
219
|
-
1. **Working memory (`MEMORY.md`)** — short-horizon context and current focus.
|
|
220
|
-
2. **Knowledge graph (`memory/**/*.md`)** — long-term notes indexed by Basic Memory.
|
|
221
|
-
3. **Task notes (`memory/tasks/*.md`)** — active execution state for in-flight work.
|
|
222
|
-
|
|
223
|
-
> **Note:** OpenClaw's default convention treats `MEMORY.md` as [long-term curated memory](https://docs.openclaw.ai/concepts/memory). This plugin flips that role — the BM knowledge graph handles durable storage, so `MEMORY.md` serves as short-horizon working memory. See [MEMORY_TASK_FLOW.md](./MEMORY_TASK_FLOW.md) for details.
|
|
224
|
-
|
|
225
|
-
Typical loop:
|
|
226
|
-
|
|
227
|
-
1. Capture or update notes/tasks with `write_note` / `edit_note`.
|
|
228
|
-
2. The persistent BM MCP process syncs markdown updates into the BM project index.
|
|
229
|
-
3. `memory_search` queries:
|
|
230
|
-
- `MEMORY.md` text snippets
|
|
231
|
-
- BM search results (semantic + FTS)
|
|
232
|
-
- active tasks
|
|
233
|
-
4. Drill into one result with `memory_get` or `read_note`.
|
|
234
|
-
5. Advance tasks by updating `current_step`, checkboxes, and context.
|
|
235
|
-
6. Complete tasks by setting `status: done` (done tasks are excluded from active task results).
|
|
236
|
-
|
|
237
|
-
```mermaid
|
|
238
|
-
flowchart LR
|
|
239
|
-
A["Write/Update notes"] --> B["BM MCP indexes changes"]
|
|
240
|
-
B --> C["memory_search(query)"]
|
|
241
|
-
C --> D["MEMORY.md"]
|
|
242
|
-
C --> E["Knowledge Graph"]
|
|
243
|
-
C --> F["Active Tasks"]
|
|
244
|
-
D --> G["Composited result"]
|
|
245
|
-
E --> G
|
|
246
|
-
F --> G
|
|
247
|
-
G --> H["memory_get / read_note"]
|
|
248
|
-
H --> A
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Task Note Shape (Recommended)
|
|
252
|
-
|
|
253
|
-
`memory_search` task extraction is strongest when task notes include:
|
|
254
|
-
|
|
255
|
-
- file location: `memory/tasks/*.md`
|
|
256
|
-
- frontmatter fields: `status:` and `current_step:`
|
|
257
|
-
- a `## Context` section for preview snippets
|
|
258
|
-
|
|
259
|
-
Example:
|
|
260
|
-
|
|
261
|
-
```markdown
|
|
262
|
-
---
|
|
263
|
-
title: auth-middleware-rollout
|
|
264
|
-
type: Task
|
|
265
|
-
status: active
|
|
266
|
-
current_step: 2
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
## Context
|
|
270
|
-
Rolling JWT middleware to all API routes. Staging verification is in progress.
|
|
271
|
-
|
|
272
|
-
## Plan
|
|
273
|
-
- [x] Implement middleware
|
|
274
|
-
- [x] Add refresh-token validation
|
|
275
|
-
- [ ] Roll out to staging
|
|
276
|
-
- [ ] Verify logs and error rates
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
To mark complete, update:
|
|
280
|
-
|
|
281
|
-
```yaml
|
|
282
|
-
status: done
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Done tasks are filtered out of the `Active Tasks` section in composited `memory_search`.
|
|
286
|
-
|
|
287
|
-
### Auto-Recall
|
|
288
|
-
On each `agent_start` event (when `autoRecall: true`), the plugin:
|
|
289
|
-
1. Queries the knowledge graph for active tasks (`type: Task`, `status: active`, up to 5)
|
|
290
|
-
2. Fetches notes modified in the last 24 hours
|
|
291
|
-
3. Formats both into structured context and returns it to the agent
|
|
292
|
-
|
|
293
|
-
This gives the agent immediate awareness of ongoing work and recent changes without the user needing to ask. The `recallPrompt` config field controls the instruction appended to the context — customize it to steer what the agent prioritizes.
|
|
93
|
+
| Option | Default | Description |
|
|
94
|
+
|--------|---------|-------------|
|
|
95
|
+
| `project` | `"openclaw-{hostname}"` | Basic Memory project name |
|
|
96
|
+
| `bmPath` | `"bm"` | Path to BM CLI binary |
|
|
97
|
+
| `projectPath` | `"."` | Project data directory |
|
|
98
|
+
| `memoryDir` | `"memory/"` | Relative path for task scanning |
|
|
99
|
+
| `memoryFile` | `"MEMORY.md"` | Working memory file for text search |
|
|
100
|
+
| `autoCapture` | `true` | Auto-index agent conversations |
|
|
101
|
+
| `captureMinChars` | `10` | Min chars to trigger capture |
|
|
102
|
+
| `autoRecall` | `true` | Inject context at session start |
|
|
103
|
+
| `recallPrompt` | *(default)* | Instruction appended to recalled context |
|
|
104
|
+
| `debug` | `false` | Verbose logs |
|
|
294
105
|
|
|
295
|
-
|
|
296
|
-
After each agent turn (when `autoCapture: true`), the plugin:
|
|
297
|
-
1. Extracts the last user + assistant messages
|
|
298
|
-
2. Appends them as timestamped entries to a daily conversation note (`conversations-YYYY-MM-DD`)
|
|
299
|
-
3. Skips very short exchanges (< `captureMinChars` chars each, default 10)
|
|
106
|
+
## How it works
|
|
300
107
|
|
|
301
|
-
|
|
108
|
+
### Memory search
|
|
302
109
|
|
|
303
|
-
|
|
110
|
+
When your agent calls `memory_search`, three sources are queried in parallel:
|
|
304
111
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
list_workspaces()
|
|
309
|
-
```
|
|
112
|
+
1. **MEMORY.md** — text search with surrounding context
|
|
113
|
+
2. **Knowledge Graph** — hybrid full-text + vector search across all indexed notes
|
|
114
|
+
3. **Active Tasks** — scans `memory/tasks/` for in-progress work
|
|
310
115
|
|
|
311
|
-
|
|
312
|
-
List all projects, optionally filtered by workspace.
|
|
313
|
-
```
|
|
314
|
-
list_memory_projects()
|
|
315
|
-
list_memory_projects(workspace="my-org")
|
|
316
|
-
```
|
|
116
|
+
Results come back in clear sections so the agent knows where each piece of context came from.
|
|
317
117
|
|
|
318
|
-
###
|
|
319
|
-
Search the knowledge graph.
|
|
320
|
-
```
|
|
321
|
-
search_notes(query="API design", limit=5)
|
|
322
|
-
search_notes(query="API design", project="other-project") # cross-project
|
|
323
|
-
```
|
|
118
|
+
### Auto-recall
|
|
324
119
|
|
|
325
|
-
|
|
326
|
-
Read a note by title, permalink, or `memory://` URL.
|
|
327
|
-
```
|
|
328
|
-
read_note(identifier="memory://projects/api-redesign")
|
|
329
|
-
read_note(identifier="memory://projects/api-redesign", include_frontmatter=true) # raw markdown + YAML
|
|
330
|
-
read_note(identifier="notes/readme", project="docs") # cross-project
|
|
331
|
-
```
|
|
120
|
+
On each session start, the plugin loads active tasks and recently modified notes, giving the agent immediate awareness of ongoing work without being asked.
|
|
332
121
|
|
|
333
|
-
###
|
|
334
|
-
Create a new note.
|
|
335
|
-
```
|
|
336
|
-
write_note(title="Auth Strategy", content="## Overview\n...", folder="decisions")
|
|
337
|
-
write_note(title="Shared Note", content="...", folder="shared", project="team") # cross-project
|
|
338
|
-
```
|
|
122
|
+
### Auto-capture
|
|
339
123
|
|
|
340
|
-
|
|
341
|
-
Edit an existing note (`append`, `prepend`, `find_replace`, `replace_section`).
|
|
342
|
-
```
|
|
343
|
-
edit_note(identifier="weekly-review", operation="append", content="\n## Update\nDone.")
|
|
344
|
-
edit_note(
|
|
345
|
-
identifier="weekly-review",
|
|
346
|
-
operation="find_replace",
|
|
347
|
-
find_text="status: active",
|
|
348
|
-
content="status: done",
|
|
349
|
-
expected_replacements=1,
|
|
350
|
-
)
|
|
351
|
-
edit_note(
|
|
352
|
-
identifier="weekly-review",
|
|
353
|
-
operation="replace_section",
|
|
354
|
-
section="## This Week",
|
|
355
|
-
content="- Done\n- Next",
|
|
356
|
-
)
|
|
357
|
-
```
|
|
124
|
+
After each conversation turn, the plugin records the exchange as a timestamped entry in a daily note. This builds a searchable history of everything your agent has discussed.
|
|
358
125
|
|
|
359
|
-
###
|
|
360
|
-
Delete a note.
|
|
361
|
-
```
|
|
362
|
-
delete_note(identifier="notes/old-draft")
|
|
363
|
-
delete_note(identifier="notes/old-draft", project="archive") # cross-project
|
|
364
|
-
```
|
|
126
|
+
### Persistent connection
|
|
365
127
|
|
|
366
|
-
|
|
367
|
-
Move a note to a different folder.
|
|
368
|
-
```
|
|
369
|
-
move_note(identifier="notes/my-note", newFolder="archive")
|
|
370
|
-
```
|
|
128
|
+
The plugin keeps a long-lived Basic Memory process running over standard I/O. No cold starts per tool call. The connection auto-reconnects if it drops.
|
|
371
129
|
|
|
372
|
-
|
|
373
|
-
Navigate the knowledge graph — get a note with its observations and relations.
|
|
374
|
-
```
|
|
375
|
-
build_context(url="memory://projects/api-redesign", depth=2)
|
|
376
|
-
build_context(url="memory://decisions", depth=1, project="team") # cross-project
|
|
377
|
-
```
|
|
130
|
+
## Agent tools
|
|
378
131
|
|
|
379
|
-
|
|
380
|
-
Validate notes against their Picoschema definitions.
|
|
381
|
-
```
|
|
382
|
-
schema_validate(noteType="person")
|
|
383
|
-
schema_validate(identifier="notes/john-doe")
|
|
384
|
-
schema_validate(noteType="person", project="contacts") # cross-project
|
|
385
|
-
```
|
|
132
|
+
All tools accept an optional `project` parameter for cross-project operations.
|
|
386
133
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
134
|
+
| Tool | Description |
|
|
135
|
+
|------|-------------|
|
|
136
|
+
| `memory_search` | Composited search across all memory sources |
|
|
137
|
+
| `memory_get` | Read a specific note by title or path |
|
|
138
|
+
| `search_notes` | Search the knowledge graph directly |
|
|
139
|
+
| `read_note` | Read a note by title, permalink, or `memory://` URL |
|
|
140
|
+
| `write_note` | Create or update a note |
|
|
141
|
+
| `edit_note` | Append, prepend, find/replace, or replace a section |
|
|
142
|
+
| `delete_note` | Delete a note |
|
|
143
|
+
| `move_note` | Move a note to a different folder |
|
|
144
|
+
| `build_context` | Navigate the knowledge graph — follow relations and connections |
|
|
145
|
+
| `list_memory_projects` | List accessible projects |
|
|
146
|
+
| `list_workspaces` | List workspaces (personal and org) |
|
|
147
|
+
| `schema_validate` | Validate notes against Picoschema definitions |
|
|
148
|
+
| `schema_infer` | Analyze notes and suggest a schema |
|
|
149
|
+
| `schema_diff` | Detect drift between schema and actual usage |
|
|
401
150
|
|
|
402
|
-
|
|
403
|
-
- **`/remember <text>`** — Save a quick note to the knowledge graph
|
|
404
|
-
- **`/recall <query>`** — Search the knowledge graph (top 5 results)
|
|
405
|
-
|
|
406
|
-
### Skill workflows
|
|
407
|
-
These inject step-by-step instructions from the bundled skill files. Each accepts optional arguments for context.
|
|
151
|
+
## Slash commands
|
|
408
152
|
|
|
409
153
|
| Command | Description |
|
|
410
154
|
|---------|-------------|
|
|
411
|
-
| `/
|
|
412
|
-
| `/
|
|
413
|
-
| `/
|
|
414
|
-
| `/
|
|
155
|
+
| `/remember <text>` | Save a quick note |
|
|
156
|
+
| `/recall <query>` | Search the knowledge graph |
|
|
157
|
+
| `/tasks [args]` | Create, track, resume structured tasks |
|
|
158
|
+
| `/reflect [args]` | Consolidate recent notes into long-term memory |
|
|
159
|
+
| `/defrag [args]` | Reorganize and clean up memory files |
|
|
160
|
+
| `/schema [args]` | Manage Picoschema definitions |
|
|
415
161
|
|
|
416
|
-
|
|
417
|
-
```
|
|
418
|
-
/tasks create a task for the API migration
|
|
419
|
-
/reflect
|
|
420
|
-
/defrag clean up completed tasks older than 2 weeks
|
|
421
|
-
/schema infer a schema for Meeting notes
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
## CLI Commands
|
|
162
|
+
## CLI
|
|
425
163
|
|
|
426
164
|
```bash
|
|
427
165
|
openclaw basic-memory search "auth patterns" --limit 5
|
|
428
166
|
openclaw basic-memory read "projects/api-redesign"
|
|
429
|
-
openclaw basic-memory read "projects/api-redesign" --raw
|
|
430
|
-
openclaw basic-memory edit "projects/api-redesign" --operation append --content $'\n## Update\nDone.'
|
|
431
167
|
openclaw basic-memory context "memory://projects/api-redesign" --depth 2
|
|
432
168
|
openclaw basic-memory recent --timeframe 24h
|
|
433
169
|
openclaw basic-memory status
|
|
434
170
|
```
|
|
435
171
|
|
|
436
|
-
##
|
|
437
|
-
|
|
438
|
-
### `bm` command not found
|
|
439
|
-
```bash
|
|
440
|
-
which bm # Check if installed
|
|
441
|
-
bm --version # Check version
|
|
442
|
-
bm mcp --help # Verify MCP server command exists
|
|
443
|
-
```
|
|
444
|
-
If `bm mcp` doesn't exist, update Basic Memory to a newer version.
|
|
445
|
-
|
|
446
|
-
### `edit_note` says `edit-note` is required
|
|
447
|
-
Your installed `basic-memory` version is missing native `tool edit-note`.
|
|
448
|
-
Upgrade `basic-memory` and rerun.
|
|
449
|
-
|
|
450
|
-
### Jiti cache issues
|
|
451
|
-
```bash
|
|
452
|
-
rm -rf /tmp/jiti/ "$TMPDIR/jiti/"
|
|
453
|
-
openclaw gateway stop && openclaw gateway start
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### Disabling semantic search
|
|
457
|
-
If you want to run without vector/embedding dependencies (faster startup, less memory), set the environment variable before launching:
|
|
458
|
-
```bash
|
|
459
|
-
BASIC_MEMORY_SEMANTIC_SEARCH_ENABLED=false
|
|
460
|
-
```
|
|
461
|
-
Or in `~/.basic-memory/config.json`:
|
|
462
|
-
```json
|
|
463
|
-
{ "semantic_search_enabled": false }
|
|
464
|
-
```
|
|
465
|
-
Search will fall back to full-text search only.
|
|
466
|
-
|
|
467
|
-
### Search returns no results
|
|
468
|
-
1. Check that the MCP session is connected (look for `connected to BM MCP stdio` in logs)
|
|
469
|
-
2. Verify files exist in the project directory
|
|
470
|
-
3. Try `bm mcp --transport stdio --project <name>` and run `search_notes` through an MCP inspector/client
|
|
471
|
-
4. Check project status: `bm project list`
|
|
472
|
-
|
|
473
|
-
## Integration Tests
|
|
172
|
+
## Bundled skills
|
|
474
173
|
|
|
475
|
-
|
|
174
|
+
Six skills ship with the plugin — no installation needed:
|
|
476
175
|
|
|
477
|
-
-
|
|
176
|
+
- **memory-tasks** — structured task tracking that survives context compaction
|
|
177
|
+
- **memory-reflect** — periodic consolidation of recent notes into durable memory
|
|
178
|
+
- **memory-defrag** — cleanup and reorganization of memory files
|
|
179
|
+
- **memory-schema** — schema lifecycle (infer, create, validate, diff)
|
|
180
|
+
- **memory-metadata-search** — query notes by frontmatter fields
|
|
181
|
+
- **memory-notes** — guidance for writing well-structured notes
|
|
478
182
|
|
|
479
|
-
|
|
480
|
-
run write/read/edit/search/context/move/delete calls, and assert actual filesystem/index results.
|
|
481
|
-
|
|
482
|
-
Run integration tests:
|
|
183
|
+
### Updating skills
|
|
483
184
|
|
|
484
185
|
```bash
|
|
485
|
-
|
|
186
|
+
npx skills add basicmachines-co/basic-memory-skills --agent openclaw
|
|
486
187
|
```
|
|
487
188
|
|
|
488
|
-
|
|
489
|
-
`../basic-memory` checkout via `uv run --project ...` when present, and falls
|
|
490
|
-
back to `bm` on `PATH` otherwise.
|
|
189
|
+
## Task notes
|
|
491
190
|
|
|
492
|
-
|
|
191
|
+
The plugin works well with structured task notes in `memory/tasks/`:
|
|
493
192
|
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
193
|
+
```markdown
|
|
194
|
+
---
|
|
195
|
+
title: auth-middleware-rollout
|
|
196
|
+
type: Task
|
|
197
|
+
status: active
|
|
198
|
+
current_step: 2
|
|
199
|
+
---
|
|
501
200
|
|
|
502
|
-
##
|
|
201
|
+
## Context
|
|
202
|
+
Rolling JWT middleware to all API routes.
|
|
503
203
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
204
|
+
## Plan
|
|
205
|
+
- [x] Implement middleware
|
|
206
|
+
- [x] Add refresh-token validation
|
|
207
|
+
- [ ] Roll out to staging
|
|
208
|
+
- [ ] Verify logs and error rates
|
|
509
209
|
```
|
|
510
210
|
|
|
511
|
-
|
|
211
|
+
Set `status: done` to mark complete. Done tasks are filtered out of active task results.
|
|
512
212
|
|
|
513
|
-
|
|
213
|
+
## Basic Memory Cloud
|
|
514
214
|
|
|
515
|
-
|
|
516
|
-
# 1) Verify release readiness (types + tests + npm pack dry run)
|
|
517
|
-
just release-check
|
|
215
|
+
Everything works locally. Cloud adds cross-device sync, team workspaces, and persistent memory for hosted agents.
|
|
518
216
|
|
|
519
|
-
|
|
520
|
-
|
|
217
|
+
- Same knowledge graph on laptop, desktop, and CI
|
|
218
|
+
- Shared workspaces for teams
|
|
219
|
+
- Durable memory for production agents
|
|
521
220
|
|
|
522
|
-
|
|
523
|
-
npm login
|
|
221
|
+
Cloud extends local-first — still plain Markdown, still yours. [Start a free trial](https://basicmemory.com) and use code `BMCLAW` for 20% off for 3 months. See [BASIC_MEMORY.md](./BASIC_MEMORY.md) for setup.
|
|
524
222
|
|
|
525
|
-
|
|
526
|
-
just release-publish
|
|
527
|
-
```
|
|
223
|
+
## Troubleshooting
|
|
528
224
|
|
|
529
|
-
|
|
225
|
+
**`bm` not found** — Install uv, then restart the gateway. Or install manually: `uv tool install basic-memory`
|
|
530
226
|
|
|
531
|
-
|
|
532
|
-
just release patch # or: minor, major, 0.2.0, etc.
|
|
533
|
-
```
|
|
227
|
+
**Search returns nothing** — Check that Basic Memory connected (look for `connected to BM` in logs). Verify files exist in the project directory.
|
|
534
228
|
|
|
535
|
-
|
|
229
|
+
**Jiti cache issues** — `rm -rf /tmp/jiti/ "$TMPDIR/jiti/"` then restart the gateway.
|
|
536
230
|
|
|
537
|
-
|
|
538
|
-
- Release workflow: `.github/workflows/release.yml` runs manually (`workflow_dispatch`) and will:
|
|
539
|
-
1. run release checks
|
|
540
|
-
2. bump version and create a git tag
|
|
541
|
-
3. push commit + tag
|
|
542
|
-
4. publish to npm
|
|
543
|
-
5. create a GitHub release
|
|
231
|
+
**Disable semantic search** — Set `BASIC_MEMORY_SEMANTIC_SEARCH_ENABLED=false` to fall back to full-text only.
|
|
544
232
|
|
|
545
|
-
|
|
233
|
+
## More
|
|
546
234
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
├── tools/ # Agent tools
|
|
554
|
-
│ ├── search-notes.ts # search_notes
|
|
555
|
-
│ ├── read-note.ts # read_note
|
|
556
|
-
│ ├── write-note.ts # write_note
|
|
557
|
-
│ ├── edit-note.ts # edit_note
|
|
558
|
-
│ ├── delete-note.ts # delete_note
|
|
559
|
-
│ ├── move-note.ts # move_note
|
|
560
|
-
│ ├── build-context.ts # build_context
|
|
561
|
-
│ ├── list-memory-projects.ts # list_memory_projects
|
|
562
|
-
│ ├── list-workspaces.ts # list_workspaces
|
|
563
|
-
│ ├── schema-validate.ts # schema_validate
|
|
564
|
-
│ ├── schema-infer.ts # schema_infer
|
|
565
|
-
│ ├── schema-diff.ts # schema_diff
|
|
566
|
-
│ └── memory-provider.ts # Composited memory_search + memory_get
|
|
567
|
-
├── commands/
|
|
568
|
-
│ ├── slash.ts # /remember, /recall
|
|
569
|
-
│ ├── skills.ts # /tasks, /reflect, /defrag, /schema
|
|
570
|
-
│ └── cli.ts # openclaw basic-memory CLI
|
|
571
|
-
└── hooks/
|
|
572
|
-
├── capture.ts # Auto-capture conversations
|
|
573
|
-
└── recall.ts # Auto-recall (active tasks + recent activity)
|
|
574
|
-
```
|
|
235
|
+
- [Memory + Task Flow](./MEMORY_TASK_FLOW.md) — practical runbook
|
|
236
|
+
- [Cloud Setup](./BASIC_MEMORY.md) — configure Basic Memory Cloud
|
|
237
|
+
- [Security](./SECURITY.md) — how auto-installation and data handling work
|
|
238
|
+
- [Development](./DEVELOPMENT.md) — contributing, tests, publishing
|
|
239
|
+
- [Basic Memory docs](https://docs.basicmemory.com)
|
|
240
|
+
- [Issues](https://github.com/basicmachines-co/openclaw-basic-memory/issues)
|
|
575
241
|
|
|
576
242
|
## Telemetry
|
|
577
243
|
|
|
578
|
-
This plugin
|
|
579
|
-
|
|
580
|
-
**What we collect:**
|
|
581
|
-
- Plugin registration and startup events
|
|
582
|
-
- First use of each tool per session (deduplicated — not every call)
|
|
583
|
-
- Plugin version with each event
|
|
584
|
-
|
|
585
|
-
**What we do NOT collect:**
|
|
586
|
-
- No file contents, note titles, search queries, or conversation text
|
|
587
|
-
- No personally identifiable information (PII)
|
|
588
|
-
- No IP address tracking or fingerprinting
|
|
589
|
-
- No per-command tracking beyond first-use-per-session
|
|
590
|
-
|
|
591
|
-
Events are sent to our [Umami Cloud](https://umami.is) instance, an open-source, privacy-focused analytics platform. Events are fire-and-forget with a 3-second timeout — analytics never blocks or slows the plugin.
|
|
592
|
-
|
|
593
|
-
**Opt out** by setting either environment variable:
|
|
594
|
-
|
|
595
|
-
```bash
|
|
596
|
-
# Plugin-specific opt-out
|
|
597
|
-
export OPENCLAW_BASIC_MEMORY_TELEMETRY=0
|
|
598
|
-
|
|
599
|
-
# Or use the shared Basic Memory opt-out (also disables BM CLI telemetry)
|
|
600
|
-
export BASIC_MEMORY_NO_PROMOS=1
|
|
601
|
-
```
|
|
244
|
+
This plugin does not collect telemetry. The Basic Memory CLI may send anonymous usage analytics — see the [Basic Memory docs](https://github.com/basicmachines-co/basic-memory) for opt-out instructions.
|
|
602
245
|
|
|
603
246
|
## License
|
|
604
247
|
|
|
605
|
-
MIT
|
|
606
|
-
|
|
607
|
-
## Links
|
|
608
|
-
|
|
609
|
-
- [Basic Memory](https://github.com/basicmachines-co/basic-memory)
|
|
610
|
-
- [Basic Memory Skills](https://github.com/basicmachines-co/basic-memory-skills)
|
|
611
|
-
- [OpenClaw](https://docs.openclaw.ai)
|
|
612
|
-
- [Issues](https://github.com/basicmachines-co/openclaw-basic-memory/issues)
|
|
248
|
+
MIT
|
package/bm-client.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises"
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client"
|
|
3
3
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
4
|
-
import { trackToolCall } from "./analytics.ts"
|
|
5
4
|
import { log } from "./logger.ts"
|
|
6
5
|
|
|
7
6
|
const DEFAULT_RETRY_DELAYS_MS = [500, 1000, 2000]
|
|
@@ -197,7 +196,7 @@ function isRecoverableConnectionError(err: unknown): boolean {
|
|
|
197
196
|
)
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
function
|
|
199
|
+
function _isNoteNotFoundError(err: unknown): boolean {
|
|
201
200
|
const msg = getErrorMessage(err).toLowerCase()
|
|
202
201
|
return (
|
|
203
202
|
msg.includes("entity not found") ||
|
|
@@ -450,7 +449,6 @@ export class BmClient {
|
|
|
450
449
|
name: string,
|
|
451
450
|
args: Record<string, unknown>,
|
|
452
451
|
): Promise<unknown> {
|
|
453
|
-
trackToolCall(name)
|
|
454
452
|
const result = await this.callToolRaw(name, args)
|
|
455
453
|
|
|
456
454
|
if (!isRecord(result) || result.structuredContent === undefined) {
|
|
@@ -661,11 +659,12 @@ export class BmClient {
|
|
|
661
659
|
identifier,
|
|
662
660
|
operation,
|
|
663
661
|
content,
|
|
664
|
-
find_text: options.find_text,
|
|
665
|
-
section: options.section,
|
|
666
|
-
expected_replacements: options.expected_replacements,
|
|
667
662
|
output_format: "json",
|
|
668
663
|
}
|
|
664
|
+
if (options.find_text) args.find_text = options.find_text
|
|
665
|
+
if (options.section) args.section = options.section
|
|
666
|
+
if (options.expected_replacements != null)
|
|
667
|
+
args.expected_replacements = options.expected_replacements
|
|
669
668
|
if (project) args.project = project
|
|
670
669
|
|
|
671
670
|
const payload = await this.callTool("edit_note", args)
|
|
@@ -831,10 +830,6 @@ export class BmClient {
|
|
|
831
830
|
return payload as unknown as MetadataSearchResult
|
|
832
831
|
}
|
|
833
832
|
|
|
834
|
-
private isNoteNotFoundError(err: unknown): boolean {
|
|
835
|
-
return isNoteNotFoundError(err)
|
|
836
|
-
}
|
|
837
|
-
|
|
838
833
|
async indexConversation(
|
|
839
834
|
userMessage: string,
|
|
840
835
|
assistantResponse: string,
|
|
@@ -856,22 +851,33 @@ export class BmClient {
|
|
|
856
851
|
"---",
|
|
857
852
|
].join("\n")
|
|
858
853
|
|
|
854
|
+
// Try append first — if it fails for ANY reason, fall through to create
|
|
859
855
|
try {
|
|
860
856
|
await this.editNote(title, "append", entry)
|
|
861
857
|
log.debug(`appended conversation to: ${title}`)
|
|
858
|
+
return
|
|
862
859
|
} catch (err) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
return
|
|
866
|
-
}
|
|
860
|
+
log.debug(`append failed, will create: ${getErrorMessage(err)}`)
|
|
861
|
+
}
|
|
867
862
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
863
|
+
// Create the note with frontmatter and first entry
|
|
864
|
+
const content = [
|
|
865
|
+
"---",
|
|
866
|
+
`title: Conversations ${dateStr}`,
|
|
867
|
+
"type: Conversation",
|
|
868
|
+
`date: "${dateStr}"`,
|
|
869
|
+
"---",
|
|
870
|
+
"",
|
|
871
|
+
`# Conversations ${dateStr}`,
|
|
872
|
+
"",
|
|
873
|
+
entry,
|
|
874
|
+
].join("\n")
|
|
875
|
+
|
|
876
|
+
try {
|
|
877
|
+
await this.writeNote(title, content, "conversations")
|
|
878
|
+
log.debug(`created conversation note: ${title}`)
|
|
879
|
+
} catch (err) {
|
|
880
|
+
log.error("conversation index failed", err)
|
|
875
881
|
}
|
|
876
882
|
}
|
|
877
883
|
|
package/config.ts
CHANGED
|
@@ -6,11 +6,6 @@ export type CloudConfig = {
|
|
|
6
6
|
api_key: string
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export type DashboardConfig = {
|
|
10
|
-
enabled: boolean
|
|
11
|
-
port: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
9
|
export type BasicMemoryConfig = {
|
|
15
10
|
project: string
|
|
16
11
|
bmPath: string
|
|
@@ -23,7 +18,6 @@ export type BasicMemoryConfig = {
|
|
|
23
18
|
recallPrompt: string
|
|
24
19
|
debug: boolean
|
|
25
20
|
cloud?: CloudConfig
|
|
26
|
-
dashboard: DashboardConfig
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
const ALLOWED_KEYS = [
|
|
@@ -43,7 +37,6 @@ const ALLOWED_KEYS = [
|
|
|
43
37
|
"recall_prompt",
|
|
44
38
|
"debug",
|
|
45
39
|
"cloud",
|
|
46
|
-
"dashboard",
|
|
47
40
|
]
|
|
48
41
|
|
|
49
42
|
function assertAllowedKeys(
|
|
@@ -113,22 +106,6 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
|
|
|
113
106
|
}
|
|
114
107
|
}
|
|
115
108
|
|
|
116
|
-
let dashboard: DashboardConfig = { enabled: false, port: 3838 }
|
|
117
|
-
if (
|
|
118
|
-
cfg.dashboard &&
|
|
119
|
-
typeof cfg.dashboard === "object" &&
|
|
120
|
-
!Array.isArray(cfg.dashboard)
|
|
121
|
-
) {
|
|
122
|
-
const d = cfg.dashboard as Record<string, unknown>
|
|
123
|
-
dashboard = {
|
|
124
|
-
enabled: typeof d.enabled === "boolean" ? d.enabled : false,
|
|
125
|
-
port:
|
|
126
|
-
typeof d.port === "number" && d.port > 0 && d.port < 65536
|
|
127
|
-
? d.port
|
|
128
|
-
: 3838,
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
109
|
return {
|
|
133
110
|
project:
|
|
134
111
|
typeof cfg.project === "string" && cfg.project.length > 0
|
|
@@ -167,7 +144,6 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
|
|
|
167
144
|
: "Check for active tasks and recent activity. Summarize anything relevant to the current session.",
|
|
168
145
|
debug: typeof cfg.debug === "boolean" ? cfg.debug : false,
|
|
169
146
|
cloud,
|
|
170
|
-
dashboard,
|
|
171
147
|
}
|
|
172
148
|
}
|
|
173
149
|
|
package/index.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { execSync } from "node:child_process"
|
|
2
|
+
|
|
2
3
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
3
|
-
import {
|
|
4
|
-
EVENT_PLUGIN_INSTALLED,
|
|
5
|
-
EVENT_PLUGIN_STARTED,
|
|
6
|
-
track,
|
|
7
|
-
} from "./analytics.ts"
|
|
8
4
|
import { BmClient } from "./bm-client.ts"
|
|
9
5
|
import { registerCli } from "./commands/cli.ts"
|
|
10
6
|
import { registerSkillCommands } from "./commands/skills.ts"
|
|
@@ -14,10 +10,11 @@ import {
|
|
|
14
10
|
parseConfig,
|
|
15
11
|
resolveProjectPath,
|
|
16
12
|
} from "./config.ts"
|
|
17
|
-
|
|
13
|
+
|
|
18
14
|
import { buildCaptureHandler } from "./hooks/capture.ts"
|
|
19
15
|
import { buildRecallHandler } from "./hooks/recall.ts"
|
|
20
16
|
import { initLogger, log } from "./logger.ts"
|
|
17
|
+
import { CONVERSATION_SCHEMA_CONTENT } from "./schema/conversation-schema.ts"
|
|
21
18
|
import { TASK_SCHEMA_CONTENT } from "./schema/task-schema.ts"
|
|
22
19
|
import { registerContextTool } from "./tools/build-context.ts"
|
|
23
20
|
import { registerDeleteTool } from "./tools/delete-note.ts"
|
|
@@ -53,8 +50,6 @@ export default {
|
|
|
53
50
|
`project=${cfg.project} memoryDir=${cfg.memoryDir} memoryFile=${cfg.memoryFile}`,
|
|
54
51
|
)
|
|
55
52
|
|
|
56
|
-
track(EVENT_PLUGIN_INSTALLED)
|
|
57
|
-
|
|
58
53
|
const client = new BmClient(cfg.bmPath, cfg.project)
|
|
59
54
|
|
|
60
55
|
// --- BM Tools (always registered) ---
|
|
@@ -89,13 +84,47 @@ export default {
|
|
|
89
84
|
registerCli(api, client, cfg)
|
|
90
85
|
|
|
91
86
|
// --- Service lifecycle ---
|
|
92
|
-
let dashboardServer: Server | undefined
|
|
93
|
-
|
|
94
87
|
api.registerService({
|
|
95
88
|
id: "openclaw-basic-memory",
|
|
96
89
|
start: async (ctx: { config?: unknown; workspaceDir?: string }) => {
|
|
97
90
|
log.info("starting...")
|
|
98
91
|
|
|
92
|
+
// Auto-install bm CLI if not found
|
|
93
|
+
const bmBin = cfg.bmPath || "bm"
|
|
94
|
+
try {
|
|
95
|
+
execSync(`command -v ${bmBin}`, { stdio: "ignore" })
|
|
96
|
+
} catch {
|
|
97
|
+
log.info("bm CLI not found on PATH — attempting auto-install...")
|
|
98
|
+
try {
|
|
99
|
+
execSync("command -v uv", { stdio: "ignore" })
|
|
100
|
+
log.info(
|
|
101
|
+
"installing basic-memory via uv (this may take a minute)...",
|
|
102
|
+
)
|
|
103
|
+
const result = execSync(
|
|
104
|
+
'uv tool install "basic-memory @ git+https://github.com/basicmachines-co/basic-memory.git@main" --force',
|
|
105
|
+
{ encoding: "utf-8", timeout: 120_000, stdio: "pipe" },
|
|
106
|
+
)
|
|
107
|
+
log.info(
|
|
108
|
+
`basic-memory installed: ${result.trim().split("\n").pop()}`,
|
|
109
|
+
)
|
|
110
|
+
// Verify it worked
|
|
111
|
+
try {
|
|
112
|
+
execSync(`command -v ${bmBin}`, { stdio: "ignore" })
|
|
113
|
+
log.info("bm CLI now available on PATH")
|
|
114
|
+
} catch {
|
|
115
|
+
log.error(
|
|
116
|
+
"bm installed but not found on PATH. You may need to add uv's bin directory to your PATH (typically ~/.local/bin).",
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
} catch (_uvErr) {
|
|
120
|
+
log.error(
|
|
121
|
+
"Cannot auto-install basic-memory: uv not found. " +
|
|
122
|
+
"Install uv first (brew install uv, or curl -LsSf https://astral.sh/uv/install.sh | sh), " +
|
|
123
|
+
"then restart the gateway.",
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
99
128
|
const workspace = ctx.workspaceDir ?? process.cwd()
|
|
100
129
|
const projectPath = resolveProjectPath(cfg.projectPath, workspace)
|
|
101
130
|
cfg.projectPath = projectPath
|
|
@@ -104,46 +133,30 @@ export default {
|
|
|
104
133
|
await client.ensureProject(projectPath)
|
|
105
134
|
log.debug(`project "${cfg.project}" at ${projectPath}`)
|
|
106
135
|
|
|
107
|
-
// Seed
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
// Seed schemas if not already present
|
|
137
|
+
for (const [name, content] of [
|
|
138
|
+
["Task", TASK_SCHEMA_CONTENT],
|
|
139
|
+
["Conversation", CONVERSATION_SCHEMA_CONTENT],
|
|
140
|
+
] as const) {
|
|
112
141
|
try {
|
|
113
|
-
await client.
|
|
114
|
-
log.debug(
|
|
115
|
-
} catch
|
|
116
|
-
|
|
142
|
+
await client.readNote(`schema/${name}`)
|
|
143
|
+
log.debug(`${name} schema already exists, skipping seed`)
|
|
144
|
+
} catch {
|
|
145
|
+
try {
|
|
146
|
+
await client.writeNote(name, content, "schema")
|
|
147
|
+
log.debug(`seeded ${name} schema note`)
|
|
148
|
+
} catch (err) {
|
|
149
|
+
log.debug(`${name} schema seed failed (non-fatal)`, err)
|
|
150
|
+
}
|
|
117
151
|
}
|
|
118
152
|
}
|
|
119
153
|
|
|
120
154
|
setWorkspaceDir(workspace)
|
|
121
155
|
|
|
122
|
-
// Start dashboard if enabled
|
|
123
|
-
if (cfg.dashboard.enabled) {
|
|
124
|
-
dashboardServer = createDashboardServer({
|
|
125
|
-
port: cfg.dashboard.port,
|
|
126
|
-
client,
|
|
127
|
-
})
|
|
128
|
-
dashboardServer.listen(cfg.dashboard.port, () => {
|
|
129
|
-
log.info(
|
|
130
|
-
`dashboard running at http://localhost:${cfg.dashboard.port}`,
|
|
131
|
-
)
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
track(EVENT_PLUGIN_STARTED, { project: cfg.project })
|
|
136
156
|
log.info("connected — BM MCP stdio session running")
|
|
137
157
|
},
|
|
138
158
|
stop: async () => {
|
|
139
159
|
log.info("stopping BM MCP session...")
|
|
140
|
-
if (dashboardServer) {
|
|
141
|
-
await new Promise<void>((resolve) =>
|
|
142
|
-
dashboardServer?.close(() => resolve()),
|
|
143
|
-
)
|
|
144
|
-
dashboardServer = undefined
|
|
145
|
-
log.info("dashboard stopped")
|
|
146
|
-
}
|
|
147
160
|
await client.stop()
|
|
148
161
|
log.info("stopped")
|
|
149
162
|
},
|
package/openclaw.plugin.json
CHANGED
|
@@ -56,11 +56,6 @@
|
|
|
56
56
|
"label": "Cloud Backend",
|
|
57
57
|
"help": "Optional cloud backend config (url + api_key). If present, uses cloud instead of local BM.",
|
|
58
58
|
"advanced": true
|
|
59
|
-
},
|
|
60
|
-
"dashboard": {
|
|
61
|
-
"label": "Dashboard",
|
|
62
|
-
"help": "Web dashboard for visualizing the knowledge graph (enabled, port)",
|
|
63
|
-
"advanced": true
|
|
64
59
|
}
|
|
65
60
|
},
|
|
66
61
|
"configSchema": {
|
|
@@ -81,13 +76,6 @@
|
|
|
81
76
|
"url": { "type": "string" },
|
|
82
77
|
"api_key": { "type": "string" }
|
|
83
78
|
}
|
|
84
|
-
},
|
|
85
|
-
"dashboard": {
|
|
86
|
-
"type": "object",
|
|
87
|
-
"properties": {
|
|
88
|
-
"enabled": { "type": "boolean" },
|
|
89
|
-
"port": { "type": "number", "minimum": 1, "maximum": 65535 }
|
|
90
|
-
}
|
|
91
79
|
}
|
|
92
80
|
},
|
|
93
81
|
"required": []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basicmemory/openclaw-basic-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Basic Memory plugin for OpenClaw — local-first knowledge graph for agent memory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"url": "https://github.com/basicmachines-co/openclaw-basic-memory"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"analytics.ts",
|
|
13
12
|
"index.ts",
|
|
14
13
|
"bm-client.ts",
|
|
15
14
|
"config.ts",
|
|
@@ -34,6 +33,7 @@
|
|
|
34
33
|
"tools/write-note.ts",
|
|
35
34
|
"types/openclaw.d.ts",
|
|
36
35
|
"schema/task-schema.ts",
|
|
36
|
+
"schema/conversation-schema.ts",
|
|
37
37
|
"skills/",
|
|
38
38
|
"scripts/setup-bm.sh",
|
|
39
39
|
"openclaw.plugin.json",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Conversation schema note content, seeded into new projects on first startup.
|
|
3
|
+
* If the schema already exists (user may have customized it), seeding is skipped.
|
|
4
|
+
*/
|
|
5
|
+
export const CONVERSATION_SCHEMA_CONTENT = `---
|
|
6
|
+
title: Conversation
|
|
7
|
+
type: schema
|
|
8
|
+
entity: Conversation
|
|
9
|
+
version: 1
|
|
10
|
+
schema:
|
|
11
|
+
date: "string, ISO date YYYY-MM-DD"
|
|
12
|
+
session_id?: "string, unique session identifier"
|
|
13
|
+
channel?: "string, where the conversation happened (webchat, telegram, discord, etc)"
|
|
14
|
+
participants?: "array, who was in the conversation"
|
|
15
|
+
topic?: "string, brief description of main topic"
|
|
16
|
+
summary?: "string, one-paragraph summary of key points"
|
|
17
|
+
key_decisions?: "array, decisions made during conversation"
|
|
18
|
+
action_items?: "array, things to do as a result"
|
|
19
|
+
settings:
|
|
20
|
+
validation: warn
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Conversation
|
|
24
|
+
|
|
25
|
+
A record of a conversation session between the agent and user(s).
|
|
26
|
+
|
|
27
|
+
## Observations
|
|
28
|
+
- [convention] Conversation files live in memory/conversations/ with format conversations-YYYY-MM-DD.md
|
|
29
|
+
- [convention] Messages are appended as the conversation progresses
|
|
30
|
+
- [convention] Summary and key_decisions populated at session end or by memory-reflect
|
|
31
|
+
- [convention] Skip routine greetings, heartbeat acks, and tool call details
|
|
32
|
+
- [convention] Focus on decisions, actions taken, and key context needed for continuity
|
|
33
|
+
`
|
package/analytics.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lightweight analytics via Umami event collector.
|
|
3
|
-
*
|
|
4
|
-
* Sends anonymous, non-blocking usage events to help understand plugin adoption
|
|
5
|
-
* and tool usage patterns. No PII, no fingerprinting, no cookies.
|
|
6
|
-
*
|
|
7
|
-
* Events are fire-and-forget — analytics never blocks or breaks the plugin.
|
|
8
|
-
*
|
|
9
|
-
* Defaults point to the Basic Memory Umami Cloud instance. Override via:
|
|
10
|
-
* BASIC_MEMORY_UMAMI_HOST — Custom Umami instance URL
|
|
11
|
-
* BASIC_MEMORY_UMAMI_SITE_ID — Custom Website ID
|
|
12
|
-
* Opt out entirely with OPENCLAW_BASIC_MEMORY_TELEMETRY=0 or BASIC_MEMORY_NO_PROMOS=1.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { readFileSync } from "node:fs"
|
|
16
|
-
import { join } from "node:path"
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Configuration
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
const DEFAULT_UMAMI_HOST = "https://cloud.umami.is"
|
|
23
|
-
const DEFAULT_UMAMI_SITE_ID = "f6479898-ebaf-4e60-bce2-6dc60a3f6c5c"
|
|
24
|
-
const SEND_TIMEOUT_MS = 3000
|
|
25
|
-
|
|
26
|
-
function umamiHost(): string {
|
|
27
|
-
return process.env.BASIC_MEMORY_UMAMI_HOST?.trim() || DEFAULT_UMAMI_HOST
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function umamiSiteId(): string {
|
|
31
|
-
return process.env.BASIC_MEMORY_UMAMI_SITE_ID?.trim() || DEFAULT_UMAMI_SITE_ID
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function analyticsDisabled(): boolean {
|
|
35
|
-
// Plugin-specific opt-out
|
|
36
|
-
const telemetry = (process.env.OPENCLAW_BASIC_MEMORY_TELEMETRY ?? "")
|
|
37
|
-
.trim()
|
|
38
|
-
.toLowerCase()
|
|
39
|
-
if (telemetry === "0" || telemetry === "false" || telemetry === "no") {
|
|
40
|
-
return true
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Shared Basic Memory opt-out (also disables BM CLI analytics)
|
|
44
|
-
const noPromos = (process.env.BASIC_MEMORY_NO_PROMOS ?? "")
|
|
45
|
-
.trim()
|
|
46
|
-
.toLowerCase()
|
|
47
|
-
return noPromos === "1" || noPromos === "true" || noPromos === "yes"
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Version
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
let _cachedVersion: string | null = null
|
|
55
|
-
|
|
56
|
-
function getVersion(): string {
|
|
57
|
-
if (_cachedVersion) return _cachedVersion
|
|
58
|
-
try {
|
|
59
|
-
const pkg = JSON.parse(
|
|
60
|
-
readFileSync(
|
|
61
|
-
join(import.meta.dirname ?? __dirname, "package.json"),
|
|
62
|
-
"utf-8",
|
|
63
|
-
),
|
|
64
|
-
)
|
|
65
|
-
_cachedVersion = pkg.version ?? "unknown"
|
|
66
|
-
} catch {
|
|
67
|
-
_cachedVersion = "unknown"
|
|
68
|
-
}
|
|
69
|
-
return _cachedVersion ?? "unknown"
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Session-scoped dedup for tool calls
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
|
|
76
|
-
const _trackedTools = new Set<string>()
|
|
77
|
-
|
|
78
|
-
export function resetTrackedTools(): void {
|
|
79
|
-
_trackedTools.clear()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Event constants
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
export const EVENT_PLUGIN_INSTALLED = "openclaw-plugin-installed"
|
|
87
|
-
export const EVENT_PLUGIN_STARTED = "openclaw-plugin-started"
|
|
88
|
-
export const EVENT_TOOL_CALL = "openclaw-tool-call"
|
|
89
|
-
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// Public API
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Send an analytics event to Umami. Non-blocking, silent on failure.
|
|
96
|
-
*
|
|
97
|
-
* @param eventName - Short kebab-case name (e.g. "openclaw-plugin-started")
|
|
98
|
-
* @param data - Optional dict of event properties (string/number values)
|
|
99
|
-
*/
|
|
100
|
-
export function track(
|
|
101
|
-
eventName: string,
|
|
102
|
-
data?: Record<string, string | number>,
|
|
103
|
-
): void {
|
|
104
|
-
if (analyticsDisabled()) return
|
|
105
|
-
|
|
106
|
-
const host = umamiHost()
|
|
107
|
-
const siteId = umamiSiteId()
|
|
108
|
-
|
|
109
|
-
const payload = {
|
|
110
|
-
payload: {
|
|
111
|
-
hostname: "openclaw.basicmemory.com",
|
|
112
|
-
language: "en",
|
|
113
|
-
url: `/openclaw/${eventName}`,
|
|
114
|
-
website: siteId,
|
|
115
|
-
name: eventName,
|
|
116
|
-
data: {
|
|
117
|
-
version: getVersion(),
|
|
118
|
-
...(data ?? {}),
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Fire-and-forget — never block the plugin
|
|
124
|
-
_sendPayload(host, payload).catch(() => {})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Track a tool call, deduped per tool name per session.
|
|
129
|
-
* Only the first invocation of each tool sends an event.
|
|
130
|
-
*/
|
|
131
|
-
export function trackToolCall(toolName: string): void {
|
|
132
|
-
if (analyticsDisabled()) return
|
|
133
|
-
if (_trackedTools.has(toolName)) return
|
|
134
|
-
_trackedTools.add(toolName)
|
|
135
|
-
track(EVENT_TOOL_CALL, { tool: toolName })
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
// Internal send (exported for testing)
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
export async function _sendPayload(
|
|
143
|
-
host: string,
|
|
144
|
-
payload: unknown,
|
|
145
|
-
): Promise<void> {
|
|
146
|
-
const controller = new AbortController()
|
|
147
|
-
const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS)
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
await fetch(`${host}/api/send`, {
|
|
151
|
-
method: "POST",
|
|
152
|
-
headers: {
|
|
153
|
-
"Content-Type": "application/json",
|
|
154
|
-
"User-Agent": `openclaw-basic-memory/${getVersion()}`,
|
|
155
|
-
},
|
|
156
|
-
body: JSON.stringify(payload),
|
|
157
|
-
signal: controller.signal,
|
|
158
|
-
})
|
|
159
|
-
} finally {
|
|
160
|
-
clearTimeout(timer)
|
|
161
|
-
}
|
|
162
|
-
}
|