@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
@@ -7,6 +7,17 @@ description: "Capture a working thread's current state into one coherent Basic M
7
7
 
8
8
  Capture the gist of a working thread — the decisions made, insights surfaced, and context built — into a single coherent Basic Memory note that reflects where the thread has landed.
9
9
 
10
+ ## Project scope
11
+
12
+ If the project is unknown, discover it with `bm_project(command="list")`.
13
+ Pass a nonempty `project` on every note, folder, context and schema call, even
14
+ with one project or a qualified `memory://` URL. Use the chosen project's name,
15
+ `workspace/project`, or external ID; `<project>` below is a placeholder for it.
16
+ Recent activity also requires `project`. Search accepts `project` for one
17
+ project, `projects` for selected projects, or `all_projects=True` explicitly.
18
+ Discovery and bundled manuals need no project. Keep the same scope when following
19
+ returned paths, paginating, or recovering from a refusal.
20
+
10
21
  ## Purpose
11
22
 
12
23
  A thread has a beginning, middle, and end. Things change as the conversation progresses: an early decision gets revised, a problem looks different in light of new information, a trade-off is settled differently than it first seemed. When this skill is invoked, capture the **current state of understanding**, not the history of how it got there.
@@ -33,31 +44,31 @@ To rewrite the same note on re-capture instead of duplicating, key the note to a
33
44
 
34
45
  > **Example (hosts with a JSONL transcript):** some agents write a per-session transcript whose filename is a stable session UUID. If yours does, you can derive the id from the most-recently-modified transcript file and use it as `thread_id`. This is optional — only do it if your host actually exposes such a transcript.
35
46
 
36
- **If no stable id is available**, match the existing note by title/topic instead: search for a note covering the same thread (`search_notes(query="<topic>")`), and if you find the one this thread already produced, rewrite it. Omit `thread_id` and rely on a consistent title.
47
+ **If no stable id is available**, match the existing note by title/topic instead: search for a note covering the same thread (`bm_search(query="<topic>", project="<project>")`), and if you find the one this thread already produced, rewrite it. Omit `thread_id` and rely on a consistent title.
37
48
 
38
49
  ## Decision Flow
39
50
 
40
51
  1. **Determine the thread key.** Use a stable session/thread id if your agent exposes one; otherwise plan to match by title/topic.
41
52
  2. **Search Basic Memory** for the existing thread note.
42
- - With a thread id, use `metadata_filters` (not `query`) — full-text query doesn't reliably match YAML frontmatter custom fields:
53
+ - With a thread id, use `filters` (not `query`) — full-text query doesn't reliably match YAML frontmatter custom fields:
43
54
  ```python
44
- search_notes(
45
- metadata_filters={"thread_id": "<thread-id>"},
55
+ bm_search(
56
+ filters={"thread_id": "<thread-id>"},
46
57
  project="<project>"
47
58
  )
48
59
  ```
49
60
  - Without one, search by topic and identify the note this thread already produced:
50
61
  ```python
51
- search_notes(query="<thread topic>", project="<project>")
62
+ bm_search(query="<thread topic>", project="<project>")
52
63
  ```
53
64
  3. **If a match is found:**
54
65
  - Read the existing note as JSON (use the full permalink returned by search) and keep its `checksum`
55
66
  - Synthesize a new version that integrates the latest understanding from the conversation
56
- - Overwrite via `write_note` with `overwrite=True` and `expected_checksum` set to that checksum (same title, same `thread_id` if used, same directory). A `NOTE_REVISION_CONFLICT` error means another session changed the note since you read it: read it again and re-synthesize
67
+ - Overwrite via `bm_write` with `overwrite=True` and `expected_checksum` set to that checksum using the returned `file_path` as `identifier` (same `thread_id` if used). A `REVISION_CONFLICT` error means another session changed the note since you read it: read it again and re-synthesize
57
68
  4. **If no match is found:**
58
69
  - Synthesize the note from the conversation
59
- - If you have a thread id, pass `metadata={"thread_id": "<thread-id>"}` to `write_note` (it surfaces as a custom frontmatter field)
60
- - Save it. If `write_note` lists similar existing notes, check whether one of them is this thread's note before keeping the new one
70
+ - If you have a thread id, pass `metadata={"thread_id": "<thread-id>"}` to `bm_write` (it surfaces as a custom frontmatter field)
71
+ - Save it. If `bm_write` lists similar existing notes, check whether one of them is this thread's note before keeping the new one
61
72
 
62
73
  ## Synthesis Rules
63
74
 
@@ -125,23 +136,23 @@ The title should reflect the thread's topic. On update, the title can be refined
125
136
  ## MCP Tools Used
126
137
 
127
138
  ```python
128
- # Find existing thread note by thread id (use metadata_filters, not query)
129
- search_notes(
130
- metadata_filters={"thread_id": "<thread-id>"},
139
+ # Find existing thread note by thread id (use filters, not query)
140
+ bm_search(
141
+ filters={"thread_id": "<thread-id>"},
131
142
  project="<project>"
132
143
  )
133
144
 
134
145
  # Or, without a thread id, find it by topic
135
- search_notes(query="<thread topic>", project="<project>")
146
+ bm_search(query="<thread topic>", project="<project>")
136
147
 
137
148
  # Read existing thread note (use the full permalink from search results)
138
- read_note(
149
+ bm_read(
139
150
  identifier="<full-permalink>",
140
151
  project="<project>"
141
152
  )
142
153
 
143
154
  # Create
144
- write_note(
155
+ bm_write(
145
156
  title="<title>",
146
157
  content="<markdown body — frontmatter is generated from title/tags/metadata>",
147
158
  directory="<folder>",
@@ -151,11 +162,11 @@ write_note(
151
162
  )
152
163
 
153
164
  # Overwrite an existing note (same path), only the revision you read
154
- existing = read_note(identifier="<full-permalink>", output_format="json", project="<project>")
155
- write_note(
156
- title="<same title>",
165
+ existing = bm_read(identifier="<full-permalink>", output_format="json", project="<project>")
166
+ bm_write(
167
+ identifier=existing["file_path"],
168
+ title="<updated title>",
157
169
  content="<new content>",
158
- directory="<same folder>",
159
170
  tags=["..."],
160
171
  metadata={"thread_id": "<same thread-id>"}, # omit if no stable id
161
172
  overwrite=True,
@@ -19,6 +19,17 @@ The full vocabulary, every prop, and every data grammar are in
19
19
  [references/components.md](references/components.md). Read it before using a
20
20
  component you have not used before.
21
21
 
22
+ ## Project scope
23
+
24
+ If the project is unknown, discover it with `bm_project(command="list")`.
25
+ Pass a nonempty `project` on every note, folder, context and schema call, even
26
+ with one project or a qualified `memory://` URL. Use the chosen project's name,
27
+ `workspace/project`, or external ID; `<project>` below is a placeholder for it.
28
+ Recent activity also requires `project`. Search accepts `project` for one
29
+ project, `projects` for selected projects, or `all_projects=True` explicitly.
30
+ Discovery and bundled manuals need no project. Keep the same scope when following
31
+ returned paths, paginating, or recovering from a refusal.
32
+
22
33
  ## Pick a component
23
34
 
24
35
  Most notes need only these. The reference has the rest (39 in total).
@@ -138,13 +149,13 @@ Semantics to state honestly in the surrounding prose:
138
149
 
139
150
  ### Check the rendered result before you quote it
140
151
 
141
- In Basic Memory Cloud, `read_note(view="text")` returns the note as a reader
152
+ In Basic Memory Cloud, `bm_read(identifier="<note>", view="text", project="<project>")` returns the note as a reader
142
153
  sees it: every component as plain Markdown, with live queries run now. Read it
143
154
  back after writing a live view and confirm the view has rows before you state a
144
155
  number in prose. A query over a field no note has renders an empty view.
145
156
 
146
157
  Elsewhere, only the browser runs the query, so run the equivalent read yourself
147
- (for example `search_notes` with metadata filters; see the
158
+ (for example `bm_search` with metadata filters; see the
148
159
  memory-metadata-search skill).
149
160
 
150
161
  A live chart's body is its **snapshot**: the rows readers see when live data
@@ -154,7 +165,7 @@ as a snapshot in prose and keep them in the chart's row grammar.
154
165
 
155
166
  ## Edit a note that already has components
156
167
 
157
- - Change one component with `edit_note` (`find_replace` on its directive line or
168
+ - Change one component with `bm_edit` (`find_replace` on its directive line or
158
169
  rows). Do not rewrite the whole note to change one value.
159
170
  - The rich editor normalizes formatting when a person edits the note: attribute
160
171
  order changes and YAML blocks come back as JSON. Values survive; do not fight
@@ -168,9 +179,9 @@ as a snapshot in prose and keep them in the chart's row grammar.
168
179
  ## When a component shows an error
169
180
 
170
181
  A broken component renders a coded problem and a fix inside its own frame; the
171
- rest of the note still renders. In Basic Memory Cloud, `write_note` and
172
- `edit_note` list these problems under **Component problems** in their result
173
- (they never block the write), so fix them right away with `edit_note`. `comark(7)` in the manual
182
+ rest of the note still renders. In Basic Memory Cloud, `bm_write` and
183
+ `bm_edit` list these problems under **Component problems** in their result
184
+ (they never block the write), so fix them right away with `bm_edit`. `comark(7)` in the manual
174
185
  (`memory://man/comark(7)`) is a quick reference to the syntax and codes. The
175
186
  codes you will meet most:
176
187
 
@@ -7,6 +7,17 @@ description: "Resume prior work by rebuilding context from the Basic Memory grap
7
7
 
8
8
  Resume previous work by reconstructing context from the Basic Memory knowledge graph, so the assistant can pick up across sessions instead of starting cold.
9
9
 
10
+ ## Project scope
11
+
12
+ If the project is unknown, discover it with `bm_project(command="list")`.
13
+ Pass a nonempty `project` on every note, folder, context and schema call, even
14
+ with one project or a qualified `memory://` URL. Use the chosen project's name,
15
+ `workspace/project`, or external ID; `<project>` below is a placeholder for it.
16
+ Recent activity also requires `project`. Search accepts `project` for one
17
+ project, `projects` for selected projects, or `all_projects=True` explicitly.
18
+ Discovery and bundled manuals need no project. Keep the same scope when following
19
+ returned paths, paginating, or recovering from a refusal.
20
+
10
21
  ## When to Use
11
22
 
12
23
  - Starting a new session and you need to pick up where you left off
@@ -21,31 +32,32 @@ Resume previous work by reconstructing context from the Basic Memory knowledge g
21
32
 
22
33
  If it's unclear, ask:
23
34
  - What topic or project should you resume?
24
- - What timeframe matters?
35
+ - What since matters?
25
36
  - Any specific aspect to focus on?
26
37
 
27
38
  ### 2. Gather Context with MCP Tools
28
39
 
29
- **Known topic — use `build_context`.** Navigate the graph from a starting point, following relations outward:
40
+ **Known topic — use `bm_context`.** Navigate the graph from a starting point, following relations outward:
30
41
 
31
42
  ```python
32
- build_context(
33
- url="memory://topic-or-note-name",
43
+ bm_context(
44
+ identifier="memory://topic-or-note-name",
34
45
  depth=2, # how many relation hops to follow
35
- timeframe="7d", # bias toward recent changes
46
+ since="7d", # only include changes in this window
47
+ project="<project>",
36
48
  )
37
49
  ```
38
50
 
39
- **No clear starting point — use `recent_activity`.** See what's changed and let it surface the thread:
51
+ **No clear starting point — use `bm_project(command="recent", project="<project>")`.** See what's changed and let it surface the thread:
40
52
 
41
53
  ```python
42
- recent_activity(timeframe="3d", depth=1)
54
+ bm_project(command="recent", since="3d", project="<project>")
43
55
  ```
44
56
 
45
- **Looking for something specific — use `search_notes`.** Find candidate notes by keyword:
57
+ **Looking for something specific — use `bm_search`.** Find candidate notes by keyword:
46
58
 
47
59
  ```python
48
- search_notes(query="async client refactor", page_size=10)
60
+ bm_search(query="async client refactor", page_size=10, project="<project>")
49
61
  ```
50
62
 
51
63
  ### 3. Read the Key Notes
@@ -53,17 +65,17 @@ search_notes(query="async client refactor", page_size=10)
53
65
  Once you've identified the relevant notes, read them in full:
54
66
 
55
67
  ```python
56
- read_note(identifier="note-title-or-permalink")
68
+ bm_read(identifier="note-title-or-permalink", project="<project>")
57
69
  ```
58
70
 
59
71
  For long notes, pick before you pay for bodies. `compact=True` drops bodies from
60
- search results (and observations from `build_context`), and a line range or a
72
+ search results (and observations from `bm_context`), and a line range or a
61
73
  section reads only the part you need:
62
74
 
63
75
  ```python
64
- search_notes(query="async client refactor", compact=True)
65
- read_note(identifier="note-title-or-permalink", start_line=120, end_line=180)
66
- cat(identifier="note-title-or-permalink", section="Status")
76
+ bm_search(query="async client refactor", compact=True, project="<project>")
77
+ bm_read(identifier="note-title-or-permalink", start_line=120, end_line=180, project="<project>")
78
+ bm_read(identifier="note-title-or-permalink", section="Status", project="<project>")
67
79
  ```
68
80
 
69
81
  ### 4. Present Context to the User
@@ -76,7 +88,7 @@ Summarize what you found, incrementally:
76
88
 
77
89
  ## Memory URL Reference
78
90
 
79
- `build_context` and `read_note` both accept `memory://` URLs, which address notes by permalink and support wildcards for gathering groups of notes.
91
+ `bm_context` and `bm_read` both accept `memory://` URLs. `bm_read` resolves one exact note; use `bm_context` for wildcard patterns that gather groups of notes.
80
92
 
81
93
  ```
82
94
  memory://note-title # a single note by permalink
@@ -89,7 +101,7 @@ Use a specific note URL to anchor on one starting point; use a wildcard to pull
89
101
 
90
102
  ## Timeframe Reference
91
103
 
92
- `build_context` and `recent_activity` accept natural-language timeframes:
104
+ `bm_context` and `bm_project(command="recent", project="<project>")` accept natural-language timeframes:
93
105
 
94
106
  | Timeframe | Meaning |
95
107
  |-----------|---------|
@@ -106,10 +118,10 @@ Use a specific note URL to anchor on one starting point; use a wildcard to pull
106
118
 
107
119
  ```python
108
120
  # 1. Read the spec / project note
109
- read_note(identifier="SPEC-24: Postgres Database Migration")
121
+ bm_read(identifier="SPEC-24: Postgres Database Migration", project="<project>")
110
122
 
111
123
  # 2. Pull in related context and recent changes via the graph
112
- build_context(url="memory://SPEC-24*", timeframe="7d")
124
+ bm_context(identifier="memory://SPEC-24*", since="7d", project="<project>")
113
125
  ```
114
126
 
115
127
  Then summarize: the goals, what's completed, what's pending, and any blockers or open decisions.
@@ -118,10 +130,10 @@ Then summarize: the goals, what's completed, what's pending, and any blockers or
118
130
 
119
131
  ```python
120
132
  # 1. Check recent activity
121
- recent_activity(timeframe="3d")
133
+ bm_project(command="recent", since="3d", project="<project>")
122
134
 
123
135
  # 2. Read notes from the recent sessions it surfaces
124
- read_note(identifier="relevant-note")
136
+ bm_read(identifier="relevant-note", project="<project>")
125
137
  ```
126
138
 
127
139
  Then list the modified notes with brief descriptions and ask which thread to dive into.
@@ -130,10 +142,10 @@ Then list the modified notes with brief descriptions and ask which thread to div
130
142
 
131
143
  ```python
132
144
  # 1. Find the topic
133
- search_notes(query="topic keywords")
145
+ bm_search(query="topic keywords", project="<project>")
134
146
 
135
147
  # 2. Build context from the best match, following its relations
136
- build_context(url="memory://found-note-permalink", depth=2)
148
+ bm_context(identifier="memory://found-note-permalink", depth=2, project="<project>")
137
149
  ```
138
150
 
139
151
  Then present the full picture — the note plus its connected context.
@@ -143,16 +155,24 @@ Then present the full picture — the note plus its connected context.
143
155
  Project names are user-specific. To discover what's available before scoping a search or `memory://` URL:
144
156
 
145
157
  ```python
146
- list_memory_projects()
158
+ bm_project(command="list")
147
159
  ```
148
160
 
149
- In multi-project setups, prefix a `memory://` URL with the project name (e.g. `memory://research/papers/crdt`) to scope it.
161
+ A qualified `memory://` URL (e.g. `memory://research/papers/crdt`) must agree
162
+ with `project`; the prefix does not replace that required parameter.
163
+
164
+ For an explicitly broader search, use one of these scopes instead:
165
+
166
+ ```python
167
+ bm_search(query="topic keywords", projects=["research", "team/engineering"])
168
+ bm_search(query="topic keywords", all_projects=True)
169
+ ```
150
170
 
151
171
  ## Guidelines
152
172
 
153
- 1. **Start broad, then narrow.** Get an overview with `recent_activity` or a wildcard `build_context`, then drill into specific notes.
173
+ 1. **Start broad, then narrow.** Get an overview with `bm_project(command="recent", project="<project>")` or a wildcard `bm_context`, then drill into specific notes.
154
174
  2. **Present incrementally.** Share what you find as you go rather than holding everything until the end.
155
- 3. **Follow relations.** The graph's connections are the point — `build_context` with `depth` surfaces context you wouldn't find by reading one note.
156
- 4. **Check multiple projects.** Specs may live separately from implementation notes; discover projects with `list_memory_projects`.
175
+ 3. **Follow relations.** The graph's connections are the point — `bm_context` with `depth` surfaces context you wouldn't find by reading one note.
176
+ 4. **Check multiple projects.** Specs may live separately from implementation notes; discover projects with `bm_project(command="list")`.
157
177
  5. **Confirm understanding.** Verify the reconstructed context is what the user actually needs before acting on it.
158
178
  6. **Capture new progress.** As the resumed work advances, write it back to the graph (see the **memory-notes** skill) so the next session can continue too.
@@ -9,6 +9,17 @@ Maintain a healthy, well-connected knowledge graph. As notes accumulate, it pays
9
9
 
10
10
  This skill curates the **knowledge graph** — the notes, relations, and tags that make up the knowledge base. (For the notes' content — splitting bloated notes, consolidating scattered facts, retiring stale entries — see **memory-defrag**.)
11
11
 
12
+ ## Project scope
13
+
14
+ If the project is unknown, discover it with `bm_project(command="list")`.
15
+ Pass a nonempty `project` on every note, folder, context and schema call, even
16
+ with one project or a qualified `memory://` URL. Use the chosen project's name,
17
+ `workspace/project`, or external ID; `<project>` below is a placeholder for it.
18
+ Recent activity also requires `project`. Search accepts `project` for one
19
+ project, `projects` for selected projects, or `all_projects=True` explicitly.
20
+ Discovery and bundled manuals need no project. Keep the same scope when following
21
+ returned paths, paginating, or recovering from a refusal.
22
+
12
23
  ## When to Use
13
24
 
14
25
  - Asked to organize, clean up, or improve the knowledge base
@@ -26,12 +37,12 @@ Orphans have no relations to other notes — they're islands in the graph.
26
37
 
27
38
  ```python
28
39
  # List notes, then read each to inspect its Relations section
29
- search_notes(query="*", search_type="text", page_size=50)
30
- read_note(identifier="note-to-check")
40
+ bm_search(query="*", mode="text", page_size=50, project="<project>")
41
+ bm_read(identifier="note-to-check", project="<project>")
31
42
  # Orphans have an empty (or missing) Relations section
32
43
  ```
33
44
 
34
- `query="*"` lists everything only with `search_type="text"`. A note whose body
45
+ `query="*"` lists everything only with `mode="text"`. A note whose body
35
46
  links to other notes, with wikilinks or ordinary Markdown links, is not an
36
47
  orphan even without a Relations section.
37
48
 
@@ -47,9 +58,9 @@ If the `bm` CLI is available, `bm orphans` lists notes with no relations in one
47
58
  Analyze a note's content and propose meaningful connections.
48
59
 
49
60
  ```python
50
- read_note(identifier="note-to-analyze")
61
+ bm_read(identifier="note-to-analyze", project="<project>")
51
62
  # Pull out key terms, then search for related notes
52
- search_notes(query="key terms from the note")
63
+ bm_search(query="key terms from the note", project="<project>")
53
64
  ```
54
65
 
55
66
  Suggest relations based on shared topics, complementary content (problem/solution,
@@ -67,14 +78,15 @@ question/answer), sequence (part 1 → part 2), or hierarchy (parent concept →
67
78
 
68
79
  Custom relation types are fine — use whatever verb is descriptive.
69
80
 
70
- Add a confirmed relation with `edit_note`:
81
+ Add a confirmed relation with `bm_edit`:
71
82
 
72
83
  ```python
73
- edit_note(
84
+ bm_edit(
74
85
  identifier="API Design Decisions",
75
- operation="insert_after_section",
86
+ command="insert_after_section",
76
87
  section="Relations",
77
88
  content="- depends_on [[Rate Limiter]]",
89
+ project="<project>",
78
90
  )
79
91
  ```
80
92
 
@@ -82,10 +94,11 @@ edit_note(
82
94
  `## Relations`. Then append the section instead:
83
95
 
84
96
  ```python
85
- edit_note(
97
+ bm_edit(
86
98
  identifier="API Design Decisions",
87
- operation="append",
99
+ command="append",
88
100
  content="\n## Relations\n- depends_on [[Rate Limiter]]",
101
+ project="<project>",
89
102
  )
90
103
  ```
91
104
 
@@ -94,7 +107,7 @@ edit_note(
94
107
  Find notes that may cover the same ground.
95
108
 
96
109
  ```python
97
- search_notes(query="topic keywords")
110
+ bm_search(query="topic keywords", project="<project>")
98
111
  # Compare results for: similar titles, overlapping observations,
99
112
  # shared tags, close-together timestamps
100
113
  ```
@@ -107,36 +120,38 @@ search_notes(query="topic keywords")
107
120
 
108
121
  ```python
109
122
  # Point an older note at the one that replaces it
110
- edit_note(
123
+ bm_edit(
111
124
  identifier="DB Schema v1",
112
- operation="insert_after_section",
125
+ command="insert_after_section",
113
126
  section="Relations",
114
127
  content="- updates [[DB Schema v2]]",
128
+ project="<project>",
115
129
  )
116
130
  ```
117
131
 
118
132
  ### 4. Folder Organization Review
119
133
 
120
134
  ```python
121
- list_directory(dir_name="/", depth=3)
135
+ bm_find(path="/", depth=3, project="<project>")
122
136
  ```
123
137
 
124
138
  Look for overcrowded folders, single-note folders, inconsistent naming, and notes
125
139
  that belong elsewhere. Suggest grouping related notes into topic folders, adding
126
140
  subfolders for large categories, and a consistent naming convention. Move misplaced
127
- notes with `move_note`. By default the permalink stays the same after a move, so links keep resolving. Projects with `update_permalinks_on_move` enabled rewrite it from the new path.
141
+ notes with `bm_move`. By default the permalink stays the same after a move, so links keep resolving. Projects with `update_permalinks_on_move` enabled rewrite it from the new path.
128
142
 
129
143
  ```python
130
- move_note(
144
+ bm_move(
131
145
  identifier="API Design Decisions",
132
- destination_path="architecture/api-design-decisions.md",
146
+ destination="architecture/api-design-decisions.md",
147
+ project="<project>",
133
148
  )
134
149
  ```
135
150
 
136
151
  ### 5. Tag Consistency
137
152
 
138
153
  ```python
139
- search_notes(query="*", search_type="text", page_size=100)
154
+ bm_search(query="*", mode="text", page_size=100, project="<project>")
140
155
  # Inspect tag patterns across results
141
156
  ```
142
157
 
@@ -151,7 +166,7 @@ Look for:
151
166
  After finding a cluster of related notes, build a navigation hub.
152
167
 
153
168
  ```python
154
- write_note(
169
+ bm_write(
155
170
  title="Architecture Decisions Index",
156
171
  directory="indexes",
157
172
  tags=["architecture", "index"],
@@ -174,6 +189,7 @@ A hub linking architecture-related decisions and patterns.
174
189
 
175
190
  ## Relations
176
191
  - indexes [[Architecture]]""",
192
+ project="<project>",
177
193
  )
178
194
  ```
179
195
 
@@ -182,19 +198,19 @@ A hub linking architecture-related decisions and patterns.
182
198
  Find notes lacking structure and fill them in.
183
199
 
184
200
  ```python
185
- read_note(identifier="sparse-note")
201
+ bm_read(identifier="sparse-note", project="<project>")
186
202
  ```
187
203
 
188
204
  If the note is missing an Observations section, suggest categories. If it has no
189
205
  Relations, suggest links. If it has no tags, suggest relevant ones. If it lacks
190
- context, suggest adding background. Apply with `edit_note`.
206
+ context, suggest adding background. Apply with `bm_edit`.
191
207
 
192
208
  ## Curation Workflows
193
209
 
194
210
  Scale the pass to the request. A health check reports orphans, likely duplicates,
195
- recent activity (`recent_activity`), and folder distribution. A deep session works
211
+ recent activity (`bm_project(command="recent", project="<project>")`), and folder distribution. A deep session works
196
212
  through the capabilities above in whatever order the findings suggest. A topic pass
197
- maps one subject with `search_notes` and `build_context`, fills the gaps it finds, and
213
+ maps one subject with `bm_search` and `bm_context`, fills the gaps it finds, and
198
214
  ends with a hub note.
199
215
 
200
216
  ## Best Practices
@@ -204,7 +220,7 @@ ends with a hub note.
204
220
  3. **Preserve permalinks.** Changing a permalink breaks inbound `memory://` links. Moves keep the permalink by default (unless the project enables `update_permalinks_on_move`).
205
221
  4. **Explain suggestions.** Say *why* a relation or merge makes sense.
206
222
  5. **Respect the existing system.** Enhance the user's organization — don't impose a new taxonomy.
207
- 6. **Show the graph.** Use `build_context` to help the user see how notes connect.
223
+ 6. **Show the graph.** Use `bm_context` to help the user see how notes connect.
208
224
 
209
225
  ## Example Conversations
210
226
 
@@ -10,6 +10,17 @@ time notes grow past their topic, the same fact lands in several places, old
10
10
  facts contradict new ones, and finished work lingers. Defrag fixes the content;
11
11
  **memory-curate** handles links, relations, and tags.
12
12
 
13
+ ## Project scope
14
+
15
+ If the project is unknown, discover it with `bm_project(command="list")`.
16
+ Pass a nonempty `project` on every note, folder, context and schema call, even
17
+ with one project or a qualified `memory://` URL. Use the chosen project's name,
18
+ `workspace/project`, or external ID; `<project>` below is a placeholder for it.
19
+ Recent activity also requires `project`. Search accepts `project` for one
20
+ project, `projects` for selected projects, or `all_projects=True` explicitly.
21
+ Discovery and bundled manuals need no project. Keep the same scope when following
22
+ returned paths, paginating, or recovering from a refusal.
23
+
13
24
  ## When to Run
14
25
 
15
26
  - **Periodic**: weekly or every other week
@@ -25,18 +36,18 @@ Walk the whole project, not just recent changes. Stale content is usually in
25
36
  notes nobody touched lately.
26
37
 
27
38
  ```python
28
- # Every folder and note. Ask for page=2, 3, ... until a page returns fewer
29
- # than page_size entries.
30
- list_directory(dir_name="/", depth=10, page_size=200)
39
+ # Every folder and note within depth 10. Page until has_more is false;
40
+ # descend into deeper folders separately if the project has them.
41
+ bm_find(path="/", depth=10, page_size=100, project="<project>")
31
42
 
32
43
  # Recently busy notes, to look at first
33
- recent_activity(timeframe="30d", output_format="json", page_size=50)
44
+ bm_project(command="recent", since="30d", output_format="json", page_size=50, project="<project>")
34
45
 
35
46
  # Notes on one topic, to spot overlap
36
- search_notes(query="launch plan")
47
+ bm_search(query="launch plan", project="<project>")
37
48
 
38
49
  # Commitments due on or before today: finished work that lingers, or overdue items
39
- search_notes(time_kind="due", valid_overlaps="(,2026-10-14]")
50
+ bm_search(time_kind="due", valid_overlaps="(,2026-10-14]", project="<project>")
40
51
  ```
41
52
 
42
53
  Read every note you might change, in full. For each, note its topic, size, last
@@ -105,7 +116,7 @@ status: proposed
105
116
  - Make one change at a time. Read each note as JSON before you edit it and pass
106
117
  its checksum as `expected_checksum`, so the edit applies only to the revision
107
118
  you read; on a checksum conflict, read again and recheck the plan line. Keep
108
- relations pointing at the right note after a split or move (`move_note` keeps
119
+ relations pointing at the right note after a split or move (`bm_move` keeps
109
120
  links working; a new note needs links added).
110
121
 
111
122
  ### 5. Record what was done
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: memory-doctor
3
+ 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."
4
+ ---
5
+
6
+ # Memory Doctor
7
+
8
+ `bm doctor` checks the user's local Basic Memory projects and reports what is
9
+ wrong. Each problem comes with the command that fixes it, when one exists.
10
+ Doctor's checks only read; they never change a project's notes or index. The
11
+ fixes it suggests do change things, so run them only after the user agrees.
12
+
13
+ This skill needs a shell with the `bm` (or `basic-memory`) command.
14
+
15
+ ## When to Use
16
+
17
+ - Search misses a note the user knows exists, or returns the same note twice
18
+ - A note was saved but its file did not change, or the reverse
19
+ - The user asks whether Basic Memory is working, or to "check" or "fix" it
20
+ - After a crash, an interrupted import, or moving a project's folder
21
+
22
+ ## Run It
23
+
24
+ Start with the report alone. It is fast and has a stable JSON shape:
25
+
26
+ ```bash
27
+ bm doctor --skip-self-test --json
28
+ ```
29
+
30
+ Add `--project NAME` to check one project. Without `--skip-self-test`, doctor
31
+ also runs a self-test: it writes, indexes and searches a note in a throwaway
32
+ sandbox, which takes a few seconds and proves the install itself works. Run it
33
+ when the report is clean but something still seems broken.
34
+
35
+ ## Read the Report
36
+
37
+ ```json
38
+ {
39
+ "projects": [
40
+ {
41
+ "name": "work",
42
+ "findings": [
43
+ {
44
+ "check": "search_index",
45
+ "severity": "error",
46
+ "summary": "1 search row does not match a current note, observation or relation.",
47
+ "evidence": ["entity id=99, entity_id=99"],
48
+ "action": {"kind": "reindex_search", "full": true},
49
+ "command": "bm reindex --search --full --project work"
50
+ }
51
+ ],
52
+ "has_errors": true
53
+ }
54
+ ],
55
+ "skipped_cloud_projects": ["research"],
56
+ "self_test": {"outcome": "skipped"},
57
+ "has_errors": true
58
+ }
59
+ ```
60
+
61
+ | Severity | Meaning | What to do |
62
+ |---|---|---|
63
+ | `error` | Will not go away without a fix | Offer the `command`; if it is null, explain the evidence |
64
+ | `warning` | Needs attention, may recover on its own | Offer the `command` if there is one |
65
+ | `info` | Expected while work is in progress | Mention it; a running server finishes it, and the `command` finishes it now |
66
+
67
+ Indexing catches up in the background. A duplicate, missing or extra search row
68
+ is a `warning` while file indexing is still running, because the pass may clear
69
+ it, and becomes an `error` once indexing has settled. If the report has only `info` findings, nothing is wrong. When no
70
+ Basic Memory server is running, nothing indexes in the background, so offer the
71
+ `command` on an `info` finding too.
72
+
73
+ Cloud projects are listed under `skipped_cloud_projects`: they are indexed on the
74
+ server, and local commands cannot fix them.
75
+
76
+ ## Fix
77
+
78
+ 1. Summarize the findings for the user in plain words: which project, what is
79
+ wrong, and what the fix does. Group findings that share a command.
80
+ 2. Show each `command` exactly as doctor printed it and ask before running it.
81
+ `bm reindex --search --full` re-reads every file in the project and can take
82
+ a while on a large one.
83
+ 3. Run the commands the user approves.
84
+ 4. Run `bm doctor --skip-self-test --json` again and confirm the findings are gone.
85
+
86
+ When a finding has no `command`, do not invent one. Say what the evidence shows
87
+ (for example, which files changed on disk while a newer save was waiting) and
88
+ let the user decide.
89
+
90
+ ## Exit Codes
91
+
92
+ `bm doctor` exits 0 when there are no errors and the self-test passed (or was
93
+ skipped), and 1 otherwise. Warnings and info never fail it.