@basicmemory/openclaw-basic-memory 0.24.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +24 -12
  2. package/bm-client.ts +302 -112
  3. package/commands/cli.ts +5 -6
  4. package/commands/slash.ts +4 -4
  5. package/context-engine/basic-memory-context-engine.ts +74 -9
  6. package/context-engine/types.ts +21 -0
  7. package/dist/bm-client.d.ts +61 -12
  8. package/dist/bm-client.js +250 -101
  9. package/dist/commands/cli.d.ts +1 -1
  10. package/dist/commands/cli.js +5 -5
  11. package/dist/commands/slash.d.ts +1 -1
  12. package/dist/commands/slash.js +4 -3
  13. package/dist/context-engine/basic-memory-context-engine.d.ts +20 -1
  14. package/dist/context-engine/basic-memory-context-engine.js +50 -2
  15. package/dist/context-engine/types.d.ts +9 -0
  16. package/dist/context-engine/types.js +1 -0
  17. package/dist/index.d.ts +10 -4
  18. package/dist/index.js +79 -69
  19. package/dist/memory/basic-memory-capability.d.ts +19 -0
  20. package/dist/memory/basic-memory-capability.js +190 -0
  21. package/dist/tools/build-context.d.ts +1 -1
  22. package/dist/tools/build-context.js +12 -4
  23. package/dist/tools/delete-note.d.ts +1 -1
  24. package/dist/tools/delete-note.js +2 -1
  25. package/dist/tools/edit-note.d.ts +1 -1
  26. package/dist/tools/edit-note.js +22 -12
  27. package/dist/tools/list-memory-projects.d.ts +1 -1
  28. package/dist/tools/list-memory-projects.js +2 -1
  29. package/dist/tools/list-workspaces.d.ts +1 -1
  30. package/dist/tools/list-workspaces.js +4 -4
  31. package/dist/tools/move-note.d.ts +1 -1
  32. package/dist/tools/move-note.js +2 -1
  33. package/dist/tools/read-note.d.ts +1 -1
  34. package/dist/tools/read-note.js +2 -1
  35. package/dist/tools/schema-diff.d.ts +1 -1
  36. package/dist/tools/schema-diff.js +2 -1
  37. package/dist/tools/schema-infer.d.ts +1 -1
  38. package/dist/tools/schema-infer.js +2 -1
  39. package/dist/tools/schema-validate.d.ts +1 -1
  40. package/dist/tools/schema-validate.js +2 -1
  41. package/dist/tools/search-notes.d.ts +1 -1
  42. package/dist/tools/search-notes.js +13 -6
  43. package/dist/tools/write-note.d.ts +1 -1
  44. package/dist/tools/write-note.js +11 -12
  45. package/index.ts +93 -84
  46. package/memory/basic-memory-capability.ts +253 -0
  47. package/openclaw.plugin.json +0 -2
  48. package/package.json +6 -5
  49. package/skills/manifest.json +5 -0
  50. package/skills/memory-agents/SKILL.md +23 -10
  51. package/skills/memory-capture/SKILL.md +29 -18
  52. package/skills/memory-comark/SKILL.md +17 -6
  53. package/skills/memory-continue/SKILL.md +47 -27
  54. package/skills/memory-curate/SKILL.md +40 -24
  55. package/skills/memory-defrag/SKILL.md +18 -7
  56. package/skills/memory-doctor/SKILL.md +93 -0
  57. package/skills/memory-ingest/SKILL.md +33 -17
  58. package/skills/memory-lifecycle/SKILL.md +35 -22
  59. package/skills/memory-literary-analysis/SKILL.md +112 -75
  60. package/skills/memory-metadata-search/SKILL.md +39 -28
  61. package/skills/memory-notes/SKILL.md +75 -50
  62. package/skills/memory-okf/SKILL.md +27 -15
  63. package/skills/memory-onboarding/SKILL.md +20 -9
  64. package/skills/memory-onboarding/references/assistant-setup.md +1 -1
  65. package/skills/memory-onboarding/references/conventions.md +4 -4
  66. package/skills/memory-onboarding/references/domain-playbooks.md +1 -1
  67. package/skills/memory-onboarding/references/schema-guide.md +9 -8
  68. package/skills/memory-quest/SKILL.md +18 -7
  69. package/skills/memory-reflect/SKILL.md +25 -13
  70. package/skills/memory-research/SKILL.md +28 -13
  71. package/skills/memory-schema/SKILL.md +36 -24
  72. package/skills/memory-tasks/SKILL.md +34 -22
  73. package/tools/build-context.ts +14 -5
  74. package/tools/delete-note.ts +2 -2
  75. package/tools/edit-note.ts +27 -14
  76. package/tools/list-memory-projects.ts +6 -2
  77. package/tools/list-workspaces.ts +8 -5
  78. package/tools/move-note.ts +2 -2
  79. package/tools/read-note.ts +2 -2
  80. package/tools/schema-diff.ts +2 -2
  81. package/tools/schema-infer.ts +2 -2
  82. package/tools/schema-validate.ts +2 -2
  83. package/tools/search-notes.ts +18 -7
  84. package/tools/write-note.ts +12 -12
  85. package/dist/tools/memory-provider.d.ts +0 -15
  86. package/dist/tools/memory-provider.js +0 -275
  87. package/tools/memory-provider.ts +0 -336
package/index.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { execFileSync } from "node:child_process"
1
+ import { execFile } from "node:child_process"
2
+ import { mkdir } from "node:fs/promises"
3
+ import { promisify } from "node:util"
2
4
 
3
5
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"
4
6
  import { BmClient } from "./bm-client.ts"
@@ -12,6 +14,7 @@ import {
12
14
  } from "./config.ts"
13
15
  import { BasicMemoryContextEngine } from "./context-engine/basic-memory-context-engine.ts"
14
16
  import { initLogger, log } from "./logger.ts"
17
+ import { buildBasicMemoryCapability } from "./memory/basic-memory-capability.ts"
15
18
  import { CONVERSATION_SCHEMA_CONTENT } from "./schema/conversation-schema.ts"
16
19
  import { TASK_SCHEMA_CONTENT } from "./schema/task-schema.ts"
17
20
  import { registerContextTool } from "./tools/build-context.ts"
@@ -19,10 +22,6 @@ import { registerDeleteTool } from "./tools/delete-note.ts"
19
22
  import { registerEditTool } from "./tools/edit-note.ts"
20
23
  import { registerProjectListTool } from "./tools/list-memory-projects.ts"
21
24
  import { registerWorkspaceListTool } from "./tools/list-workspaces.ts"
22
- import {
23
- registerMemoryProvider,
24
- setWorkspaceDir,
25
- } from "./tools/memory-provider.ts"
26
25
  import { registerMoveTool } from "./tools/move-note.ts"
27
26
  import { registerReadTool } from "./tools/read-note.ts"
28
27
  import { registerSchemaDiffTool } from "./tools/schema-diff.ts"
@@ -31,20 +30,71 @@ import { registerSchemaValidateTool } from "./tools/schema-validate.ts"
31
30
  import { registerSearchTool } from "./tools/search-notes.ts"
32
31
  import { registerWriteTool } from "./tools/write-note.ts"
33
32
 
34
- export function isCommandAvailable(command: string): boolean {
33
+ const execFileAsync = promisify(execFile)
34
+
35
+ // Async so a probe or an install never blocks OpenClaw's event loop during
36
+ // plugin startup.
37
+ export async function isCommandAvailable(command: string): Promise<boolean> {
35
38
  try {
36
- execFileSync(command, ["--version"], { stdio: "ignore" })
39
+ await execFileAsync(command, ["--version"])
37
40
  return true
38
41
  } catch {
39
42
  return false
40
43
  }
41
44
  }
42
45
 
46
+ async function installBasicMemoryIfMissing(bmBin: string): Promise<void> {
47
+ if (await isCommandAvailable(bmBin)) return
48
+
49
+ log.info("bm CLI not found on PATH — attempting auto-install...")
50
+ if (!(await isCommandAvailable("uv"))) {
51
+ throw new Error(
52
+ "Cannot auto-install basic-memory: uv not found. " +
53
+ "Install uv first (brew install uv, or curl -LsSf https://astral.sh/uv/install.sh | sh), " +
54
+ "then restart the gateway.",
55
+ )
56
+ }
57
+
58
+ log.info("installing basic-memory via uv (this may take a minute)...")
59
+ // basic-memory pins a FastMCP pre-release; without this flag uv silently
60
+ // installs the last release that had none (#1338).
61
+ const { stdout } = await execFileAsync(
62
+ "uv",
63
+ ["tool", "install", "basic-memory", "--prerelease=allow", "--force"],
64
+ { encoding: "utf-8", timeout: 120_000 },
65
+ )
66
+ log.info(`basic-memory installed: ${stdout.trim().split("\n").pop()}`)
67
+ if (!(await isCommandAvailable(bmBin))) {
68
+ throw new Error(
69
+ "bm installed but not found on PATH. You may need to add uv's bin directory to your PATH (typically ~/.local/bin).",
70
+ )
71
+ }
72
+ }
73
+
74
+ async function seedSchemas(client: BmClient): Promise<void> {
75
+ for (const [name, content] of [
76
+ ["Task", TASK_SCHEMA_CONTENT],
77
+ ["Conversation", CONVERSATION_SCHEMA_CONTENT],
78
+ ] as const) {
79
+ try {
80
+ await client.readNote(`schema/${name}`)
81
+ log.debug(`${name} schema already exists, skipping seed`)
82
+ } catch {
83
+ try {
84
+ await client.writeNote(name, content, "schema")
85
+ log.debug(`seeded ${name} schema note`)
86
+ } catch (err) {
87
+ log.debug(`${name} schema seed failed (non-fatal)`, err)
88
+ }
89
+ }
90
+ }
91
+ }
92
+
43
93
  export default definePluginEntry({
44
94
  id: "openclaw-basic-memory",
45
95
  name: "Basic Memory",
46
96
  description:
47
- "Local-first knowledge graph for OpenClaw — persistent memory with graph search and composited memory_search",
97
+ "Local-first knowledge graph for OpenClaw — persistent memory with graph search, recall and conversation capture",
48
98
  configSchema: basicMemoryConfigSchema,
49
99
 
50
100
  register(api) {
@@ -72,9 +122,14 @@ export default definePluginEntry({
72
122
  registerSchemaInferTool(api, client)
73
123
  registerSchemaDiffTool(api, client)
74
124
 
75
- // --- Composited memory_search + memory_get (always registered) ---
76
- registerMemoryProvider(api, client, cfg)
77
- log.info("registered composited memory_search + memory_get")
125
+ // --- Memory capability ---
126
+ // OpenClaw 2026.10 owns the memory_search and memory_get tool names
127
+ // (memory-core registers them even when another plugin holds the memory
128
+ // slot), so the plugin serves the host through registerMemoryCapability:
129
+ // gateway memory search, status and doctor, Active Memory recall, and the
130
+ // pre-compaction flush.
131
+ api.registerMemoryCapability(buildBasicMemoryCapability(client))
132
+ log.info("registered Basic Memory memory capability")
78
133
  api.registerContextEngine(
79
134
  "openclaw-basic-memory",
80
135
  () => new BasicMemoryContextEngine(client, cfg),
@@ -92,83 +147,37 @@ export default definePluginEntry({
92
147
  start: async (ctx: { config?: unknown; workspaceDir?: string }) => {
93
148
  log.info("starting...")
94
149
 
95
- // Auto-install bm CLI if not found
96
- const bmBin = cfg.bmPath || "bm"
97
- if (!isCommandAvailable(bmBin)) {
98
- log.info("bm CLI not found on PATH — attempting auto-install...")
99
- try {
100
- if (!isCommandAvailable("uv")) {
101
- throw new Error("uv not found")
102
- }
103
- log.info(
104
- "installing basic-memory via uv (this may take a minute)...",
105
- )
106
- // basic-memory pins a FastMCP pre-release; without this flag uv
107
- // silently installs the last release that had none (#1338).
108
- const result = execFileSync(
109
- "uv",
110
- [
111
- "tool",
112
- "install",
113
- "basic-memory",
114
- "--prerelease=allow",
115
- "--force",
116
- ],
117
- {
118
- encoding: "utf-8",
119
- timeout: 120_000,
120
- stdio: ["ignore", "pipe", "pipe"],
121
- },
122
- )
123
- log.info(
124
- `basic-memory installed: ${result.trim().split("\n").pop()}`,
125
- )
126
- // Verify it worked
127
- if (isCommandAvailable(bmBin)) {
128
- log.info("bm CLI now available on PATH")
129
- } else {
130
- log.error(
131
- "bm installed but not found on PATH. You may need to add uv's bin directory to your PATH (typically ~/.local/bin).",
132
- )
133
- }
134
- } catch (_uvErr) {
135
- log.error(
136
- "Cannot auto-install basic-memory: uv not found. " +
137
- "Install uv first (brew install uv, or curl -LsSf https://astral.sh/uv/install.sh | sh), " +
138
- "then restart the gateway.",
139
- )
140
- }
141
- }
142
-
143
150
  const workspace = ctx.workspaceDir ?? process.cwd()
144
151
  const projectPath = resolveProjectPath(cfg.projectPath, workspace)
145
152
  cfg.projectPath = projectPath
146
153
 
147
- await client.start({ cwd: workspace })
148
- await client.ensureProject(projectPath)
149
- log.debug(`project "${cfg.project}" at ${projectPath}`)
150
-
151
- // Seed schemas if not already present
152
- for (const [name, content] of [
153
- ["Task", TASK_SCHEMA_CONTENT],
154
- ["Conversation", CONVERSATION_SCHEMA_CONTENT],
155
- ] as const) {
156
- try {
157
- await client.readNote(`schema/${name}`)
158
- log.debug(`${name} schema already exists, skipping seed`)
159
- } catch {
160
- try {
161
- await client.writeNote(name, content, "schema")
162
- log.debug(`seeded ${name} schema note`)
163
- } catch (err) {
164
- log.debug(`${name} schema seed failed (non-fatal)`, err)
165
- }
166
- }
167
- }
168
-
169
- setWorkspaceDir(workspace)
170
-
171
- log.info("connected — BM MCP stdio session running")
154
+ // bm runs with the workspace as its working directory. OpenClaw
155
+ // creates its default workspace only on the first agent run, and
156
+ // spawning into a missing directory fails with an ENOENT that names
157
+ // the bm binary instead of the directory.
158
+ await mkdir(workspace, { recursive: true })
159
+
160
+ // OpenClaw gives a service about 5 s to start. A first start (bm
161
+ // install, cold imports, first indexing) takes far longer, and a
162
+ // timed-out start fails the plugin. So startup runs in the
163
+ // background; tool calls wait for it inside BmClient, and a failed
164
+ // startup is retried by the next tool call instead of failing here.
165
+ client
166
+ .start({
167
+ cwd: workspace,
168
+ projectPath,
169
+ prepare: () => installBasicMemoryIfMissing(cfg.bmPath || "bm"),
170
+ })
171
+ .then(async () => {
172
+ log.info("connected — BM MCP stdio session running")
173
+ await seedSchemas(client)
174
+ })
175
+ .catch((err) => {
176
+ log.error(
177
+ "Basic Memory startup failed; the next tool call will retry",
178
+ err,
179
+ )
180
+ })
172
181
  },
173
182
  stop: async () => {
174
183
  log.info("stopping BM MCP session...")
@@ -0,0 +1,253 @@
1
+ import type { MemoryPluginCapability } from "openclaw/plugin-sdk/memory-host-core"
2
+ import {
3
+ type BmClient,
4
+ isNoteNotFoundError,
5
+ searchResultText,
6
+ } from "../bm-client.ts"
7
+ import { log } from "../logger.ts"
8
+
9
+ // OpenClaw documents deriving the provider types from the public capability
10
+ // type rather than importing its private memory-host-search facade.
11
+ type MemoryProviderRuntime = NonNullable<
12
+ MemoryPluginCapability["providerRuntime"]
13
+ >
14
+ type MemoryProviderHandle = NonNullable<
15
+ Awaited<ReturnType<MemoryProviderRuntime["open"]>>["provider"]
16
+ >
17
+ type MemorySearchRequest = Parameters<MemoryProviderHandle["search"]>[0]
18
+ type MemorySearchPage = Awaited<ReturnType<MemoryProviderHandle["search"]>>
19
+ type MemoryGetRequest = Parameters<MemoryProviderHandle["get"]>[0]
20
+ type MemoryGetResult = Awaited<ReturnType<MemoryProviderHandle["get"]>>
21
+ type MemoryHealth = Awaited<ReturnType<MemoryProviderHandle["health"]>>
22
+ type MemoryCallerContext = Parameters<
23
+ MemoryProviderRuntime["open"]
24
+ >[0]["context"]
25
+ type MemoryProviderFlushPlanResolver = NonNullable<
26
+ MemoryPluginCapability["providerFlushPlanResolver"]
27
+ >
28
+
29
+ export const MEMORY_PROVIDER_ID = "openclaw-basic-memory"
30
+
31
+ // Agent tools this plugin registers that Active Memory may use for deep
32
+ // recall, and that the pre-compaction flush may use to look before writing.
33
+ export const RECALL_TOOL_NAMES = [
34
+ "search_notes",
35
+ "read_note",
36
+ "build_context",
37
+ ] as const
38
+ const FLUSH_PERSISTENCE_TOOL_NAMES = ["write_note", "edit_note"] as const
39
+ const FLUSH_LOOKUP_TOOL_NAMES = ["search_notes", "read_note"] as const
40
+
41
+ const DEFAULT_MAX_RESULTS = 10
42
+ const MAX_EXCERPT_CHARS = 700
43
+ // memory-core's flush prompts use the same token for "nothing to save".
44
+ const SILENT_REPLY_TOKEN = "NO_REPLY"
45
+
46
+ // --- Search and retrieval ---
47
+
48
+ /**
49
+ * Basic Memory fuses keyword and vector scores into [0, 1.3] (see
50
+ * search_reader.py FUSION_BONUS). OpenClaw compares provider scores against a
51
+ * caller's minScore on a 0–1 scale, so a fused score above 1 is capped at 1.
52
+ */
53
+ function hostScore(score: number | undefined): number | undefined {
54
+ return score === undefined ? undefined : Math.min(score, 1)
55
+ }
56
+
57
+ function excerptOf(content: string): string {
58
+ return content.length > MAX_EXCERPT_CHARS
59
+ ? `${content.slice(0, MAX_EXCERPT_CHARS)}…`
60
+ : content
61
+ }
62
+
63
+ /**
64
+ * Apply the host's optional line window to a note body. `from` is 1-based,
65
+ * matching the host's memory_get contract.
66
+ */
67
+ function sliceLines(
68
+ text: string,
69
+ from: number | undefined,
70
+ lines: number | undefined,
71
+ ): Pick<
72
+ Extract<MemoryGetResult, { status: "ok" }>,
73
+ "text" | "truncated" | "from" | "lines" | "nextFrom"
74
+ > {
75
+ if (from === undefined && lines === undefined) return { text }
76
+
77
+ const all = text.split("\n")
78
+ const start = Math.max(1, from ?? 1)
79
+ const count = lines ?? all.length - start + 1
80
+ const window = all.slice(start - 1, start - 1 + count)
81
+ const end = start - 1 + window.length
82
+ const truncated = end < all.length
83
+ return {
84
+ text: window.join("\n"),
85
+ from: start,
86
+ lines: window.length,
87
+ truncated,
88
+ ...(truncated ? { nextFrom: end + 1 } : {}),
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Open a caller-bound reader over the plugin's shared BM MCP session.
94
+ *
95
+ * The host rechecks the caller's authority around every provider call;
96
+ * context.assertCurrent() is also called before and after each BM request so
97
+ * a revoked caller is refused rather than served. Every note in the project
98
+ * is visible to every caller, which matches what the plugin's own
99
+ * search_notes and read_note tools already expose.
100
+ */
101
+ export function openBasicMemoryReader(
102
+ client: BmClient,
103
+ context: MemoryCallerContext,
104
+ ): MemoryProviderHandle {
105
+ const guarded = async <T>(run: () => Promise<T>): Promise<T> => {
106
+ context.assertCurrent()
107
+ const result = await run()
108
+ context.assertCurrent()
109
+ return result
110
+ }
111
+
112
+ // Trigger: the reader opens while the plugin service is not running, as in
113
+ // one-shot CLI commands like `openclaw memory status`.
114
+ // Why: the first BM call then spawns a `bm mcp` child that nothing else
115
+ // will stop, and its open pipes keep the CLI process from exiting.
116
+ // Outcome: this reader owns that connection and stops it on close().
117
+ const ownsConnection = !client.isServiceRunning()
118
+
119
+ return {
120
+ capabilities: {
121
+ sources: ["memory"],
122
+ pagination: false,
123
+ candidates: [],
124
+ projectFilter: false,
125
+ },
126
+
127
+ async search(request: MemorySearchRequest): Promise<MemorySearchPage> {
128
+ const results = await guarded(() =>
129
+ client.search(request.query, request.maxResults ?? DEFAULT_MAX_RESULTS),
130
+ )
131
+
132
+ const hits = results
133
+ .map((result) => ({ result, score: hostScore(result.score) }))
134
+ // Trigger: the caller set a score floor.
135
+ // Why: the host contract requires every provider to enforce minScore.
136
+ // Outcome: unscored or lower-scored hits are dropped.
137
+ .filter(
138
+ ({ score }) =>
139
+ request.minScore === undefined ||
140
+ (score !== undefined && score >= request.minScore),
141
+ )
142
+ .map(({ result, score }) => ({
143
+ reference: { providerId: MEMORY_PROVIDER_ID, id: result.permalink },
144
+ excerpt: excerptOf(searchResultText(result)),
145
+ source: result.file_path,
146
+ ...(score === undefined ? {} : { score }),
147
+ }))
148
+
149
+ return { hits }
150
+ },
151
+
152
+ async get(request: MemoryGetRequest): Promise<MemoryGetResult> {
153
+ try {
154
+ const note = await guarded(() => client.readNote(request.reference.id))
155
+ return {
156
+ status: "ok",
157
+ reference: { providerId: MEMORY_PROVIDER_ID, id: note.permalink },
158
+ ...sliceLines(note.content, request.from, request.lines),
159
+ }
160
+ } catch (err) {
161
+ // A missing note is an answer; any other failure must surface so the
162
+ // host does not report it as an empty success.
163
+ if (isNoteNotFoundError(err)) return { status: "not_found" }
164
+ throw err
165
+ }
166
+ },
167
+
168
+ async health(): Promise<MemoryHealth> {
169
+ // Listing projects proves the BM MCP session answers without reading
170
+ // any note content, so it is safe for status-only callers.
171
+ try {
172
+ await guarded(() => client.listProjects())
173
+ return { status: "ready" }
174
+ } catch (err) {
175
+ log.warn(
176
+ `memory health check failed: ${err instanceof Error ? err.message : String(err)}`,
177
+ )
178
+ return {
179
+ status: "unavailable",
180
+ message: "Basic Memory is not reachable",
181
+ }
182
+ }
183
+ },
184
+
185
+ // In the gateway the BM MCP session is shared by the whole plugin and
186
+ // owned by its service, so closing one caller's reader releases nothing.
187
+ async close(): Promise<void> {
188
+ if (ownsConnection) await client.stop()
189
+ },
190
+ }
191
+ }
192
+
193
+ // --- Pre-compaction flush ---
194
+
195
+ const FLUSH_PROMPT = [
196
+ "Pre-compaction memory flush.",
197
+ "Save durable facts, decisions and open tasks from this session to Basic Memory.",
198
+ "Use search_notes or read_note first to find an existing note, then edit_note to update it;",
199
+ "use write_note only for a new topic.",
200
+ `If nothing is worth saving, reply with ${SILENT_REPLY_TOKEN}.`,
201
+ ].join(" ")
202
+
203
+ const FLUSH_SYSTEM_PROMPT = [
204
+ "Pre-compaction memory flush turn.",
205
+ "The session is near auto-compaction; capture durable memories in Basic Memory with write_note or edit_note.",
206
+ `You may reply, but usually ${SILENT_REPLY_TOKEN} is correct.`,
207
+ ].join(" ")
208
+
209
+ // Returning a tools-arm plan makes the host persist through this plugin's own
210
+ // tools instead of appending to a workspace memory file. The host has already
211
+ // honored agents.defaults.compaction.memoryFlush.enabled before calling this.
212
+ const resolveFlushPlan: MemoryProviderFlushPlanResolver = () => ({
213
+ prompt: FLUSH_PROMPT,
214
+ systemPrompt: FLUSH_SYSTEM_PROMPT,
215
+ persistenceToolNames: FLUSH_PERSISTENCE_TOOL_NAMES,
216
+ lookupToolNames: FLUSH_LOOKUP_TOOL_NAMES,
217
+ })
218
+
219
+ // --- Prompt guidance ---
220
+
221
+ /**
222
+ * OpenClaw fills any capability field the slot owner leaves empty from other
223
+ * memory registrations, including memory-core's prompt section, which points
224
+ * agents at its file-only memory_search. Supplying our own section keeps the
225
+ * agent's guidance on the Basic Memory tools.
226
+ */
227
+ function buildPromptSection(params: { availableTools: Set<string> }): string[] {
228
+ if (!params.availableTools.has("search_notes")) return []
229
+ return [
230
+ "## Basic Memory",
231
+ "Long-term memory lives in Basic Memory, a knowledge graph of Markdown notes.",
232
+ "Before answering questions about prior work, decisions, people or preferences, search it with search_notes and read the best match with read_note.",
233
+ "Earlier conversations are saved automatically in daily conversation notes.",
234
+ "memory_search and memory_get cover only the workspace MEMORY.md and memory/ files, not Basic Memory.",
235
+ ]
236
+ }
237
+
238
+ // --- Capability ---
239
+
240
+ export function buildBasicMemoryCapability(
241
+ client: BmClient,
242
+ ): MemoryPluginCapability {
243
+ return {
244
+ providerRuntime: {
245
+ async open({ context }) {
246
+ return { provider: openBasicMemoryReader(client, context) }
247
+ },
248
+ },
249
+ recallToolNames: RECALL_TOOL_NAMES,
250
+ providerFlushPlanResolver: resolveFlushPlan,
251
+ promptBuilder: buildPromptSection,
252
+ }
253
+ }
@@ -8,8 +8,6 @@
8
8
  "edit_note",
9
9
  "list_memory_projects",
10
10
  "list_workspaces",
11
- "memory_get",
12
- "memory_search",
13
11
  "move_note",
14
12
  "read_note",
15
13
  "schema_diff",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basicmemory/openclaw-basic-memory",
3
- "version": "0.24.1",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -15,6 +15,8 @@
15
15
  "index.ts",
16
16
  "bm-client.ts",
17
17
  "context-engine/basic-memory-context-engine.ts",
18
+ "context-engine/types.ts",
19
+ "memory/basic-memory-capability.ts",
18
20
  "config.ts",
19
21
  "logger.ts",
20
22
  "commands/cli.ts",
@@ -27,7 +29,6 @@
27
29
  "tools/edit-note.ts",
28
30
  "tools/list-memory-projects.ts",
29
31
  "tools/list-workspaces.ts",
30
- "tools/memory-provider.ts",
31
32
  "tools/move-note.ts",
32
33
  "tools/read-note.ts",
33
34
  "tools/schema-diff.ts",
@@ -80,8 +81,8 @@
80
81
  "minGatewayVersion": "2026.5.2"
81
82
  },
82
83
  "build": {
83
- "openclawVersion": "2026.5.4",
84
- "pluginSdkVersion": "2026.5.4"
84
+ "openclawVersion": "2026.10.9",
85
+ "pluginSdkVersion": "2026.10.9"
85
86
  },
86
87
  "install": {
87
88
  "minHostVersion": ">=2026.5.2"
@@ -90,7 +91,7 @@
90
91
  "devDependencies": {
91
92
  "@biomejs/biome": "^2.4.6",
92
93
  "@types/node": "^20.0.0",
93
- "openclaw": "^2026.5.4",
94
+ "openclaw": "^2026.10.6",
94
95
  "typescript": "^5.9.3"
95
96
  }
96
97
  }
@@ -29,6 +29,11 @@
29
29
  "name": "memory-defrag",
30
30
  "description": "Defragment a Basic Memory project: split bloated notes, consolidate scattered facts, resolve contradictions, retire stale content. Plans first; deletes only when told. Run weekly or on demand."
31
31
  },
32
+ {
33
+ "dir": "memory-doctor",
34
+ "name": "memory-doctor",
35
+ "description": "Check local Basic Memory projects with bm doctor, explain the findings, and run the suggested fixes once the user agrees. Use when search misses or repeats notes, or a save looks lost."
36
+ },
32
37
  {
33
38
  "dir": "memory-ingest",
34
39
  "name": "memory-ingest",
@@ -11,7 +11,19 @@ short-lived agent in Cloud that reads the target, works through the Basic Memory
11
11
  tools, and changes notes as its instructions say.
12
12
 
13
13
  Agents run in Basic Memory Cloud only, on plans that include them. A local
14
- Basic Memory has no agent tools.
14
+ Basic Memory has no agent tools. Confirm the connected server exposes `bm_agent`
15
+ before starting this workflow.
16
+
17
+ ## Project scope
18
+
19
+ If the project is unknown, discover it with `bm_project(command="list")`.
20
+ Pass a nonempty `project` on every note, folder, context and schema call, even
21
+ with one project or a qualified `memory://` URL. Use the chosen project's name,
22
+ `workspace/project`, or external ID; `<project>` below is a placeholder for it.
23
+ Recent activity also requires `project`. Search accepts `project` for one
24
+ project, `projects` for selected projects, or `all_projects=True` explicitly.
25
+ Discovery and bundled manuals need no project. Keep the same scope when following
26
+ returned paths, paginating, or recovering from a refusal.
15
27
 
16
28
  ## When to Use
17
29
 
@@ -24,12 +36,13 @@ Basic Memory has no agent tools.
24
36
 
25
37
  | Tool | Does |
26
38
  |---|---|
27
- | `list_agents` | The project's agents and how their recent runs went |
28
- | `run_agent` | Starts an agent on a note; returns the run id |
29
- | `get_agent_run` | One run: status, notes changed, and the agent's last reply |
30
- | `list_agent_runs` | Recent runs, newest first, optionally for one agent |
39
+ | `bm_agent(command="list", project="<project>")` | The project's agents and how their recent runs went |
40
+ | `bm_agent(command="run", project="<project>")` | Starts an agent on a note; returns the run id |
41
+ | `bm_agent(command="get", project="<project>")` | One run: status, notes changed, and the agent's last reply |
42
+ | `bm_agent(command="runs", project="<project>")` | Recent runs, newest first, optionally for one agent |
43
+ | `bm_agent(command="mark", project="<project>")` | Mark a run `kept` or `dismissed` with `run_id` and `decision` |
31
44
 
32
- Create and edit agents with `write_note` and `edit_note`. There is no separate
45
+ Create and edit agents with `bm_write` and `bm_edit`. There is no separate
33
46
  agent tool for that.
34
47
 
35
48
  ## Write an Agent
@@ -112,10 +125,10 @@ Point the user to these before writing an agent from scratch.
112
125
  ## Run an Agent
113
126
 
114
127
  ```python
115
- list_agents(project="work")
116
- run_agent(agent="Curator", note="projects/Launch", project="work")
128
+ bm_agent(command="list", project="work")
129
+ bm_agent(command="run", agent="Curator", identifier="projects/Launch", project="work")
117
130
  # -> run id. Runs usually take one to a few minutes.
118
- get_agent_run(run_id="<run id>", project="work")
131
+ bm_agent(command="get", run_id="<run id>", project="work")
119
132
  ```
120
133
 
121
134
  An agent changes the user's notes. Ask before running one, unless the user has
@@ -128,7 +141,7 @@ on the version the person ended with. Notes written by agents, imports, or
128
141
 
129
142
  ## Check a Run
130
143
 
131
- `get_agent_run` shows the status, the notes the run created, edited, moved, or
144
+ `bm_agent(command="get", project="<project>")` shows the status, the notes the run created, edited, moved, or
132
145
  deleted, and the agent's last reply. Every reply is also kept in a run log note, `agents/runs/<agent> run <id>`. Read it for the whole
133
146
  conversation; do not edit it.
134
147