@basicmemory/openclaw-basic-memory 0.1.4 → 0.2.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.
package/bm-client.ts
CHANGED
|
@@ -5,6 +5,15 @@ import { log } from "./logger.ts"
|
|
|
5
5
|
|
|
6
6
|
const DEFAULT_RETRY_DELAYS_MS = [500, 1000, 2000]
|
|
7
7
|
|
|
8
|
+
export class NoteAlreadyExistsError extends Error {
|
|
9
|
+
readonly permalink: string
|
|
10
|
+
constructor(title: string, permalink: string) {
|
|
11
|
+
super(`Note already exists: "${title}" (${permalink})`)
|
|
12
|
+
this.name = "NoteAlreadyExistsError"
|
|
13
|
+
this.permalink = permalink
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
const REQUIRED_TOOLS = [
|
|
9
18
|
"search_notes",
|
|
10
19
|
"read_note",
|
|
@@ -569,6 +578,7 @@ export class BmClient {
|
|
|
569
578
|
content: string,
|
|
570
579
|
folder: string,
|
|
571
580
|
project?: string,
|
|
581
|
+
overwrite?: boolean,
|
|
572
582
|
): Promise<NoteResult> {
|
|
573
583
|
const args: Record<string, unknown> = {
|
|
574
584
|
title,
|
|
@@ -577,6 +587,7 @@ export class BmClient {
|
|
|
577
587
|
output_format: "json",
|
|
578
588
|
}
|
|
579
589
|
if (project) args.project = project
|
|
590
|
+
if (overwrite !== undefined) args.overwrite = overwrite
|
|
580
591
|
|
|
581
592
|
const payload = await this.callTool("write_note", args)
|
|
582
593
|
|
|
@@ -584,6 +595,13 @@ export class BmClient {
|
|
|
584
595
|
throw new Error("invalid write_note response")
|
|
585
596
|
}
|
|
586
597
|
|
|
598
|
+
if (payload.error === "NOTE_ALREADY_EXISTS") {
|
|
599
|
+
throw new NoteAlreadyExistsError(
|
|
600
|
+
asString(payload.title) ?? title,
|
|
601
|
+
asString(payload.permalink) ?? "",
|
|
602
|
+
)
|
|
603
|
+
}
|
|
604
|
+
|
|
587
605
|
const resultTitle = asString(payload.title)
|
|
588
606
|
const permalink = asString(payload.permalink)
|
|
589
607
|
const filePath = asString(payload.file_path)
|
|
@@ -850,7 +868,7 @@ export class BmClient {
|
|
|
850
868
|
].join("\n")
|
|
851
869
|
|
|
852
870
|
try {
|
|
853
|
-
await this.writeNote(title, content, "conversations")
|
|
871
|
+
await this.writeNote(title, content, "conversations", undefined, true)
|
|
854
872
|
log.debug(`created conversation note: ${title}`)
|
|
855
873
|
} catch (err) {
|
|
856
874
|
log.error("conversation index failed", err)
|
package/package.json
CHANGED
package/scripts/setup-bm.sh
CHANGED
package/skills/manifest.json
CHANGED
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"name": "memory-lifecycle",
|
|
15
15
|
"description": "Manage entity status transitions in Basic Memory: archive completed work, move notes between status folders, update frontmatter, and handle edge cases. Use when marking items complete, archiving old entities, or managing any folder-based status workflow."
|
|
16
16
|
},
|
|
17
|
+
{
|
|
18
|
+
"dir": "memory-literary-analysis",
|
|
19
|
+
"name": "memory-literary-analysis",
|
|
20
|
+
"description": "Analyze a complete literary work into a structured Basic Memory knowledge graph. Covers schema design, entity seeding, chapter-by-chapter processing, cross-referencing, validation, and visualization."
|
|
21
|
+
},
|
|
17
22
|
{
|
|
18
23
|
"dir": "memory-metadata-search",
|
|
19
24
|
"name": "memory-metadata-search",
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory-literary-analysis
|
|
3
|
+
description: "Analyze a complete literary work into a structured Basic Memory knowledge graph. Covers schema design, entity seeding, chapter-by-chapter processing, cross-referencing, validation, and visualization."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Memory Literary Analysis
|
|
7
|
+
|
|
8
|
+
Transform a complete literary work into a structured knowledge graph. Characters, themes, chapters, locations, symbols, and literary devices become interconnected notes — searchable, validatable, and visualizable.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Analyzing a novel, play, poem, or non-fiction book end-to-end
|
|
13
|
+
- Building a teaching or study resource for a literary text
|
|
14
|
+
- Creating a book club companion knowledge base
|
|
15
|
+
- Research projects requiring structured close reading
|
|
16
|
+
- Stress-testing Basic Memory at scale (~200+ notes, 1000+ relations)
|
|
17
|
+
|
|
18
|
+
## Pipeline Overview
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Phase 0: Setup → project, schemas, directory structure
|
|
22
|
+
Phase 1: Seed → stub notes for known major entities
|
|
23
|
+
Phase 2: Process → chapter-by-chapter notes in batches
|
|
24
|
+
Phase 3: Cross-ref → enrich arcs, add parallels, write analysis
|
|
25
|
+
Phase 4: Validate → schema checks, drift detection, consistency
|
|
26
|
+
Phase 5: Visualize → canvas files for character webs, timelines
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Phase 0: Setup
|
|
30
|
+
|
|
31
|
+
### Create the Project
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
create_memory_project(name="<work-name>", path="~/basic-memory/<work-name>")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use a kebab-case slug of the work's title (e.g., `great-gatsby`, `hamlet`, `beloved`).
|
|
38
|
+
|
|
39
|
+
### Define Schemas
|
|
40
|
+
|
|
41
|
+
Write 6 schema notes to `schema/`. Each schema defines the entity type's fields, observation categories, and relation types. Adapt fields to fit the work — the schemas below are starting points, not rigid templates.
|
|
42
|
+
|
|
43
|
+
#### Character Schema
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
write_note(
|
|
47
|
+
title="Character",
|
|
48
|
+
directory="schema",
|
|
49
|
+
note_type="schema",
|
|
50
|
+
metadata={
|
|
51
|
+
"entity": "Character",
|
|
52
|
+
"version": 1,
|
|
53
|
+
"schema": {
|
|
54
|
+
"role(enum)": "[protagonist, antagonist, supporting, minor], character's narrative role",
|
|
55
|
+
"description": "string, brief character description",
|
|
56
|
+
"first_appearance?": "string, chapter or scene of first appearance",
|
|
57
|
+
"status?(enum)": "[alive, dead, unknown, transformed], character status at end of work"
|
|
58
|
+
},
|
|
59
|
+
"settings": {"validation": "warn"}
|
|
60
|
+
},
|
|
61
|
+
content="""# Character
|
|
62
|
+
|
|
63
|
+
Schema for character entity notes.
|
|
64
|
+
|
|
65
|
+
## Observations
|
|
66
|
+
- [convention] Major characters in characters/major/, minor in characters/minor/
|
|
67
|
+
- [convention] Observation categories: trait, motivation, arc, quote, appearance, relationship, symbolism, fate
|
|
68
|
+
- [convention] Relations: appears_in, contrasts_with, allied_with, commands, symbolizes, associated_with"""
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Add work-specific fields as needed — e.g., `rank` for military fiction, `house` for family sagas, `species` for fantasy.
|
|
73
|
+
|
|
74
|
+
#### Theme Schema
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
write_note(
|
|
78
|
+
title="Theme",
|
|
79
|
+
directory="schema",
|
|
80
|
+
note_type="schema",
|
|
81
|
+
metadata={
|
|
82
|
+
"entity": "Theme",
|
|
83
|
+
"version": 1,
|
|
84
|
+
"schema": {
|
|
85
|
+
"description": "string, what this theme explores",
|
|
86
|
+
"prevalence(enum)": "[major, minor], how central to the work",
|
|
87
|
+
"first_introduced?": "string, where theme first appears"
|
|
88
|
+
},
|
|
89
|
+
"settings": {"validation": "warn"}
|
|
90
|
+
},
|
|
91
|
+
content="""# Theme
|
|
92
|
+
|
|
93
|
+
Schema for thematic analysis notes.
|
|
94
|
+
|
|
95
|
+
## Observations
|
|
96
|
+
- [convention] Observation categories: definition, manifestation, evolution, counterpoint, quote, interpretation
|
|
97
|
+
- [convention] Relations: embodied_by, contrasts_with, reinforced_by, explored_in, expressed_through"""
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Chapter Schema
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
write_note(
|
|
105
|
+
title="Chapter",
|
|
106
|
+
directory="schema",
|
|
107
|
+
note_type="schema",
|
|
108
|
+
metadata={
|
|
109
|
+
"entity": "Chapter",
|
|
110
|
+
"version": 1,
|
|
111
|
+
"schema": {
|
|
112
|
+
"chapter_number": "integer, sequential chapter number",
|
|
113
|
+
"pov?": "string, point-of-view character or narrator mode",
|
|
114
|
+
"setting?": "string, primary location",
|
|
115
|
+
"narrative_mode?(enum)": "[dramatic, expository, reflective, epistolary, mixed], chapter's primary mode"
|
|
116
|
+
},
|
|
117
|
+
"settings": {"validation": "warn"}
|
|
118
|
+
},
|
|
119
|
+
content="""# Chapter
|
|
120
|
+
|
|
121
|
+
Schema for chapter-level analysis notes.
|
|
122
|
+
|
|
123
|
+
## Observations
|
|
124
|
+
- [convention] Chapters stored in chapters/ directory
|
|
125
|
+
- [convention] Observation categories: summary, event, tone, technique, quote, significance, foreshadowing
|
|
126
|
+
- [convention] Relations: features, set_in, explores, contains, employs, follows, precedes, parallels"""
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Location Schema
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
write_note(
|
|
134
|
+
title="Location",
|
|
135
|
+
directory="schema",
|
|
136
|
+
note_type="schema",
|
|
137
|
+
metadata={
|
|
138
|
+
"entity": "Location",
|
|
139
|
+
"version": 1,
|
|
140
|
+
"schema": {
|
|
141
|
+
"description": "string, what this place is",
|
|
142
|
+
"location_type(enum)": "[city, building, landscape, body_of_water, region, fictional, vehicle], type of place",
|
|
143
|
+
"real_or_fictional(enum)": "[real, fictional, both], whether the place exists"
|
|
144
|
+
},
|
|
145
|
+
"settings": {"validation": "warn"}
|
|
146
|
+
},
|
|
147
|
+
content="""# Location
|
|
148
|
+
|
|
149
|
+
Schema for location and setting notes.
|
|
150
|
+
|
|
151
|
+
## Observations
|
|
152
|
+
- [convention] Observation categories: description, atmosphere, symbolism, significance, geography
|
|
153
|
+
- [convention] Relations: setting_for, associated_with, symbolizes, contains, part_of"""
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Symbol Schema
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
write_note(
|
|
161
|
+
title="Symbol",
|
|
162
|
+
directory="schema",
|
|
163
|
+
note_type="schema",
|
|
164
|
+
metadata={
|
|
165
|
+
"entity": "Symbol",
|
|
166
|
+
"version": 1,
|
|
167
|
+
"schema": {
|
|
168
|
+
"description": "string, what the symbol is literally",
|
|
169
|
+
"symbol_type(enum)": "[object, animal, color, action, natural_phenomenon, body_part], category of symbol",
|
|
170
|
+
"primary_meaning": "string, most common interpretation"
|
|
171
|
+
},
|
|
172
|
+
"settings": {"validation": "warn"}
|
|
173
|
+
},
|
|
174
|
+
content="""# Symbol
|
|
175
|
+
|
|
176
|
+
Schema for symbolic element notes.
|
|
177
|
+
|
|
178
|
+
## Observations
|
|
179
|
+
- [convention] Observation categories: meaning, appearance, ambiguity, interpretation, quote, evolution
|
|
180
|
+
- [convention] Relations: represents, associated_with, appears_in, contrasts_with, located_at"""
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### LiteraryDevice Schema
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
write_note(
|
|
188
|
+
title="LiteraryDevice",
|
|
189
|
+
directory="schema",
|
|
190
|
+
note_type="schema",
|
|
191
|
+
metadata={
|
|
192
|
+
"entity": "LiteraryDevice",
|
|
193
|
+
"version": 1,
|
|
194
|
+
"schema": {
|
|
195
|
+
"description": "string, what the device is",
|
|
196
|
+
"device_type(enum)": "[rhetorical, structural, figurative, narrative, dramatic], category",
|
|
197
|
+
"frequency(enum)": "[pervasive, frequent, occasional, rare], how often used"
|
|
198
|
+
},
|
|
199
|
+
"settings": {"validation": "warn"}
|
|
200
|
+
},
|
|
201
|
+
content="""# LiteraryDevice
|
|
202
|
+
|
|
203
|
+
Schema for literary technique and device notes.
|
|
204
|
+
|
|
205
|
+
## Observations
|
|
206
|
+
- [convention] Observation categories: definition, usage, effect, example, significance
|
|
207
|
+
- [convention] Relations: used_in, characterizes, expresses, related_to"""
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Directory Structure
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
<project>/
|
|
215
|
+
schema/ # 6 schema definitions
|
|
216
|
+
chapters/ # one note per chapter/section + prologue/epilogue
|
|
217
|
+
characters/
|
|
218
|
+
major/ # protagonist, antagonist, key supporting
|
|
219
|
+
minor/ # named characters with limited roles
|
|
220
|
+
themes/ # thematic analysis notes
|
|
221
|
+
locations/ # settings and places
|
|
222
|
+
symbols/ # symbolic elements
|
|
223
|
+
literary-devices/ # techniques and devices
|
|
224
|
+
analysis/ # cross-cutting synthesis
|
|
225
|
+
tasks/ # processing tracker
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Phase 1: Seed Entities
|
|
229
|
+
|
|
230
|
+
Before processing chapters, create stub notes for major entities so `[[wiki-links]]` resolve from the start.
|
|
231
|
+
|
|
232
|
+
### Characters (major)
|
|
233
|
+
|
|
234
|
+
For each major character, create a stub with known metadata:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
write_note(
|
|
238
|
+
title="<Character Name>",
|
|
239
|
+
directory="characters/major",
|
|
240
|
+
note_type="Character",
|
|
241
|
+
tags=["character", "major", "<role>"],
|
|
242
|
+
metadata={"role": "<role>", "description": "<brief description>"},
|
|
243
|
+
content="""# <Character Name>
|
|
244
|
+
|
|
245
|
+
## Observations
|
|
246
|
+
- [role] <Character's role in the work>
|
|
247
|
+
- [appearance] <Key physical description>
|
|
248
|
+
|
|
249
|
+
## Relations
|
|
250
|
+
- associated_with [[<Related Character>]]
|
|
251
|
+
- appears_in [[<Key Location>]]"""
|
|
252
|
+
)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Seed Checklist
|
|
256
|
+
|
|
257
|
+
Identify the work's major entities before you start reading. A good starting inventory:
|
|
258
|
+
|
|
259
|
+
| Type | Typical Count | What to Include |
|
|
260
|
+
|------|--------------|-----------------|
|
|
261
|
+
| Characters (major) | 8-20 | Protagonist, antagonist, key supporting cast |
|
|
262
|
+
| Themes | 5-12 | Central concerns the work explores |
|
|
263
|
+
| Locations | 4-10 | Primary settings, symbolically significant places |
|
|
264
|
+
| Symbols | 4-10 | Recurring objects, images, or motifs with layered meaning |
|
|
265
|
+
|
|
266
|
+
Stubs don't need to be complete — they give `[[wiki-link]]` targets and will be enriched during chapter processing.
|
|
267
|
+
|
|
268
|
+
## Phase 2: Chapter Processing
|
|
269
|
+
|
|
270
|
+
### Source Text Preparation
|
|
271
|
+
|
|
272
|
+
Obtain the full text and identify chapter/section boundaries. For public domain works, Project Gutenberg is a good source. For copyrighted works, work from a physical or licensed digital copy.
|
|
273
|
+
|
|
274
|
+
### Batching Strategy
|
|
275
|
+
|
|
276
|
+
Process ~10 chapters per batch to balance depth with progress. Group by narrative arc or thematic focus:
|
|
277
|
+
|
|
278
|
+
| Batch | Typical Content |
|
|
279
|
+
|-------|----------------|
|
|
280
|
+
| 1 | Opening: setting, character introductions, world-building |
|
|
281
|
+
| 2-3 | Rising action: conflicts established, relationships develop |
|
|
282
|
+
| 4-6 | Middle: complications, turning points, thematic deepening |
|
|
283
|
+
| 7-8 | Climax approach: escalation, revelations, crises |
|
|
284
|
+
| Final | Climax, resolution, epilogue |
|
|
285
|
+
|
|
286
|
+
Adjust batch size based on chapter length and density. Short, action-heavy chapters can be batched in larger groups; long, philosophically dense chapters may need smaller batches.
|
|
287
|
+
|
|
288
|
+
### Per-Chapter Workflow
|
|
289
|
+
|
|
290
|
+
For each chapter:
|
|
291
|
+
|
|
292
|
+
**1. Read the chapter carefully.** If working from a source text file, read the relevant section.
|
|
293
|
+
|
|
294
|
+
**2. Create the chapter note:**
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
write_note(
|
|
298
|
+
title="Chapter <N> - <Title>",
|
|
299
|
+
directory="chapters",
|
|
300
|
+
note_type="Chapter",
|
|
301
|
+
tags=["chapter", "<arc-phase>"],
|
|
302
|
+
metadata={
|
|
303
|
+
"chapter_number": <N>,
|
|
304
|
+
"pov": "<narrator or POV character>",
|
|
305
|
+
"setting": "<primary location>",
|
|
306
|
+
"narrative_mode": "<mode>"
|
|
307
|
+
},
|
|
308
|
+
content="""# Chapter <N> - <Title>
|
|
309
|
+
|
|
310
|
+
## Observations
|
|
311
|
+
- [summary] <1-2 sentence synopsis>
|
|
312
|
+
- [event] <Key plot events>
|
|
313
|
+
- [tone] <Emotional and stylistic atmosphere>
|
|
314
|
+
- [technique] <Notable narrative techniques>
|
|
315
|
+
- [quote] "<Significant passage>"
|
|
316
|
+
- [significance] <Why this chapter matters to the whole>
|
|
317
|
+
- [foreshadowing] <Hints at future events>
|
|
318
|
+
|
|
319
|
+
## Relations
|
|
320
|
+
- features [[<Character>]]
|
|
321
|
+
- set_in [[<Location>]]
|
|
322
|
+
- explores [[<Theme>]]
|
|
323
|
+
- contains [[<Symbol>]]
|
|
324
|
+
- employs [[<Literary Device>]]
|
|
325
|
+
- follows [[Chapter <N-1> - <Previous Title>]]
|
|
326
|
+
- precedes [[Chapter <N+1> - <Next Title>]]"""
|
|
327
|
+
)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**3. Enrich related entities:**
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
edit_note(
|
|
334
|
+
identifier="characters/major/<character-slug>",
|
|
335
|
+
operation="append",
|
|
336
|
+
heading="Observations",
|
|
337
|
+
content="""- [arc] Ch.<N>: <What happens to this character>
|
|
338
|
+
- [quote] "<Attributed quote>" (Ch.<N>)"""
|
|
339
|
+
)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**4. Track progress** using the memory-tasks skill to create a processing task that survives context compaction.
|
|
343
|
+
|
|
344
|
+
### What to Capture Per Chapter
|
|
345
|
+
|
|
346
|
+
| Category | What to Look For |
|
|
347
|
+
|----------|-----------------|
|
|
348
|
+
| `[summary]` | 1-2 sentence chapter synopsis |
|
|
349
|
+
| `[event]` | Key plot events (actions, revelations, arrivals) |
|
|
350
|
+
| `[tone]` | Emotional and stylistic atmosphere |
|
|
351
|
+
| `[technique]` | Narrative innovations (POV shifts, structural experiments, genre blending) |
|
|
352
|
+
| `[quote]` | Memorable or thematically significant passages |
|
|
353
|
+
| `[significance]` | Why this chapter matters to the whole |
|
|
354
|
+
| `[foreshadowing]` | Hints at future events |
|
|
355
|
+
|
|
356
|
+
### Entity Enrichment Per Chapter
|
|
357
|
+
|
|
358
|
+
As each chapter is processed, append observations to relevant entities:
|
|
359
|
+
- **Characters**: `[arc]` moments, new `[trait]` revelations, `[quote]` attributions
|
|
360
|
+
- **Themes**: `[manifestation]` in this chapter, `[evolution]` shifts
|
|
361
|
+
- **Symbols**: `[appearance]` with context, new `[interpretation]` angles
|
|
362
|
+
- **Locations**: `[atmosphere]` as described, `[significance]` in scene
|
|
363
|
+
- **Literary devices**: `[example]` from this chapter
|
|
364
|
+
|
|
365
|
+
### Adding Prose and Interpretation
|
|
366
|
+
|
|
367
|
+
After the structured observations are in place, consider adding interpretive prose to major entity notes. Prepend 2-4 paragraphs of critical essay before the Observations section using `edit_note(operation="prepend")`. This prose should:
|
|
368
|
+
|
|
369
|
+
- Argue for a reading of the character, theme, or symbol — not just describe it
|
|
370
|
+
- Connect the entity to the work's larger concerns and to literary tradition
|
|
371
|
+
- Include subjective opinions clearly marked as such ("In my reading...", "I find...")
|
|
372
|
+
- Ground claims in textual evidence cited by chapter number
|
|
373
|
+
|
|
374
|
+
The prose adds the interpretive texture that structured observations alone cannot capture.
|
|
375
|
+
|
|
376
|
+
## Phase 3: Cross-Referencing
|
|
377
|
+
|
|
378
|
+
After all chapters are processed:
|
|
379
|
+
|
|
380
|
+
### Character Arcs
|
|
381
|
+
For each major character, write a full `[arc]` summary observation covering their trajectory across the work.
|
|
382
|
+
|
|
383
|
+
### Theme Evolution
|
|
384
|
+
For each theme, add `[evolution]` observations tracing how it develops from introduction to resolution.
|
|
385
|
+
|
|
386
|
+
### Chapter Parallels
|
|
387
|
+
Add `parallels` and `contrasts_with` relations between structurally similar chapters (e.g., mirrored scenes, repeated settings, thematic echoes).
|
|
388
|
+
|
|
389
|
+
### Analysis Notes
|
|
390
|
+
Create synthesis notes in `analysis/`:
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
write_note(
|
|
394
|
+
title="Narrative Structure",
|
|
395
|
+
directory="analysis",
|
|
396
|
+
note_type="note",
|
|
397
|
+
tags=["analysis", "structure"],
|
|
398
|
+
content="""# Narrative Structure
|
|
399
|
+
|
|
400
|
+
Analysis of the work's narrative architecture.
|
|
401
|
+
|
|
402
|
+
## Observations
|
|
403
|
+
- [structure] <Overall arc description>
|
|
404
|
+
- [technique] <Key narrative strategies>
|
|
405
|
+
...
|
|
406
|
+
|
|
407
|
+
## Relations
|
|
408
|
+
- analyzes [[<Protagonist>]]
|
|
409
|
+
- analyzes [[<Key Character>]]
|
|
410
|
+
- explores [[<Central Theme>]]
|
|
411
|
+
..."""
|
|
412
|
+
)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Recommended analysis notes:
|
|
416
|
+
- **Narrative Structure** — overall architecture and pacing
|
|
417
|
+
- **Work Overview** — synthesis of the complete work (summary, thesis, legacy)
|
|
418
|
+
- **Critical Reception** — historical and contemporary interpretations
|
|
419
|
+
|
|
420
|
+
### Discover Emergent Entities
|
|
421
|
+
During chapter processing, new minor characters, locations, and symbols will emerge. Create notes for any that appear in 3+ chapters or carry thematic weight.
|
|
422
|
+
|
|
423
|
+
## Phase 4: Validation
|
|
424
|
+
|
|
425
|
+
### Schema Validation
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
# Validate each entity type
|
|
429
|
+
schema_validate(noteType="Character")
|
|
430
|
+
schema_validate(noteType="Theme")
|
|
431
|
+
schema_validate(noteType="Chapter")
|
|
432
|
+
schema_validate(noteType="Location")
|
|
433
|
+
schema_validate(noteType="Symbol")
|
|
434
|
+
schema_validate(noteType="LiteraryDevice")
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Drift Detection
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
schema_diff(noteType="Character")
|
|
441
|
+
# ... for each type
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Fix issues found — common fixes:
|
|
445
|
+
- Missing required observation categories → add them via `edit_note`
|
|
446
|
+
- Enum values outside allowed set → correct metadata
|
|
447
|
+
- Fields in notes but not schema → add as optional to schema if legitimate
|
|
448
|
+
|
|
449
|
+
### Relation Consistency
|
|
450
|
+
Spot-check bidirectional relations: if Chapter X `features [[Character]]`, does Character have observations referencing Chapter X? Fix gaps.
|
|
451
|
+
|
|
452
|
+
## Phase 5: Visualization
|
|
453
|
+
|
|
454
|
+
Generate canvas files for visual exploration:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
# Character relationship web
|
|
458
|
+
canvas(query="type:Character AND role:protagonist OR role:antagonist OR role:supporting")
|
|
459
|
+
|
|
460
|
+
# Theme connections
|
|
461
|
+
canvas(query="type:Theme")
|
|
462
|
+
|
|
463
|
+
# Chapter timeline with key events
|
|
464
|
+
canvas(query="type:Chapter", layout="timeline")
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Adapting to Other Genres
|
|
468
|
+
|
|
469
|
+
This pipeline works for any literary text. Adjust schemas for genre:
|
|
470
|
+
|
|
471
|
+
| Genre | Schema Adjustments |
|
|
472
|
+
|-------|-------------------|
|
|
473
|
+
| **Novel** | Base schemas work as-is; add genre-specific Character fields as needed |
|
|
474
|
+
| **Play** | Add `Act` and `Scene` schemas; Character gets `speaking_lines` field |
|
|
475
|
+
| **Poetry collection** | Replace Chapter with `Poem`; add `form`, `meter`, `rhyme_scheme` fields |
|
|
476
|
+
| **Non-fiction** | Replace Chapter with `Section`; add `Argument`, `Evidence` schemas |
|
|
477
|
+
| **Short story collection** | Add `Story` schema with `narrator`, `setting`, `word_count` |
|
|
478
|
+
| **Epic/myth** | Add `Deity`, `Prophecy` schemas; Location gets `mythological_significance` |
|
|
479
|
+
| **Memoir** | Character schema gets `relationship_to_narrator`; add `Memory` schema |
|
|
480
|
+
|
|
481
|
+
### Scaling Guidance
|
|
482
|
+
|
|
483
|
+
| Work Length | Batch Size | Estimated Notes |
|
|
484
|
+
|-------------|-----------|----------------|
|
|
485
|
+
| Novella (~40K words) | 5-10 chapters | ~50-80 |
|
|
486
|
+
| Novel (~80K words) | 8-12 chapters | ~100-150 |
|
|
487
|
+
| Long novel (~200K+ words) | 10-15 chapters | ~200-300 |
|
|
488
|
+
| Series (multiple volumes) | 1 volume at a time | ~200+ per volume |
|
|
489
|
+
|
|
490
|
+
## Related Skills
|
|
491
|
+
|
|
492
|
+
- **memory-schema** — Schema creation, validation, and drift detection
|
|
493
|
+
- **memory-tasks** — Track chapter processing progress across context compaction
|
|
494
|
+
- **memory-notes** — Note writing patterns, observation categories, wiki-links
|
|
495
|
+
- **memory-ingest** — Processing external input into structured entities
|
|
496
|
+
- **memory-metadata-search** — Querying notes by frontmatter fields
|
|
497
|
+
- **memory-lifecycle** — Archiving completed analysis phases
|
|
498
|
+
|
|
499
|
+
## Guidelines
|
|
500
|
+
|
|
501
|
+
- **Seed before processing.** Create entity stubs first so wiki-links resolve immediately during chapter processing.
|
|
502
|
+
- **Batch for sanity.** Processing ~10 chapters at a time balances depth with momentum. Track progress with a Task note.
|
|
503
|
+
- **Read the source text.** Don't rely on memory or summaries. Read (or re-read) the actual text for each batch before creating notes. Textual evidence is everything.
|
|
504
|
+
- **Observations are your index.** The knowledge graph's value comes from categorized observations. Be generous with categories and specific with content.
|
|
505
|
+
- **Relations are your web.** Every chapter should link to characters, themes, locations, and devices. Every entity should link back to chapters where it appears.
|
|
506
|
+
- **Enrich iteratively.** Entity notes grow richer with each chapter. Don't try to write the perfect character note upfront — append as you go.
|
|
507
|
+
- **Add prose for depth.** After structured data is in place, add interpretive essays to major notes. The prose captures what observations cannot: argument, nuance, opinion, and voice.
|
|
508
|
+
- **Validate periodically.** Run `schema_validate` after each batch, not just at the end. Catch drift early.
|
|
509
|
+
- **Quote generously.** Literary analysis lives on textual evidence. Include significant quotes as `[quote]` observations with chapter attribution.
|
|
510
|
+
- **Review and revise.** After completing all chapters, review the full graph from an external perspective. Look for thin notes, missing connections, and gaps in coverage. The first pass is never the last.
|
|
511
|
+
- **Analysis comes last.** Synthesis notes in `analysis/` should be written after all chapters are processed, when you have the full picture.
|
|
@@ -80,6 +80,8 @@ Numeric values use numeric comparison; strings use lexicographic comparison.
|
|
|
80
80
|
- `$in` and array-contains require non-empty lists
|
|
81
81
|
- `$between` requires exactly `[min, max]`
|
|
82
82
|
|
|
83
|
+
> **Warning:** Operators MUST include the `$` prefix — write `$gte`, not `gte`. Without the prefix the filter is treated as an exact-match key and will silently return no results. Correct: `{"confidence": {"$gte": 0.7}}`. Wrong: `{"confidence": {"gte": 0.7}}`.
|
|
84
|
+
|
|
83
85
|
## Using `search_notes` with Metadata
|
|
84
86
|
|
|
85
87
|
Pass `metadata_filters`, `tags`, or `status` to `search_notes`. Omit `query` for filter-only searches, or combine text and filters together.
|
package/tools/write-note.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox"
|
|
2
2
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
3
3
|
import type { BmClient } from "../bm-client.ts"
|
|
4
|
+
import { NoteAlreadyExistsError } from "../bm-client.ts"
|
|
4
5
|
import { log } from "../logger.ts"
|
|
5
6
|
|
|
6
7
|
export function registerWriteTool(
|
|
@@ -12,9 +13,9 @@ export function registerWriteTool(
|
|
|
12
13
|
name: "write_note",
|
|
13
14
|
label: "Write Note",
|
|
14
15
|
description:
|
|
15
|
-
"Create
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"Create a note in the Basic Memory knowledge graph. " +
|
|
17
|
+
"If the note already exists, returns an error by default. " +
|
|
18
|
+
"Pass overwrite=true to replace, or use edit_note for incremental updates.",
|
|
18
19
|
parameters: Type.Object({
|
|
19
20
|
title: Type.String({ description: "Note title" }),
|
|
20
21
|
content: Type.String({
|
|
@@ -26,6 +27,12 @@ export function registerWriteTool(
|
|
|
26
27
|
description: "Target project name (defaults to current project)",
|
|
27
28
|
}),
|
|
28
29
|
),
|
|
30
|
+
overwrite: Type.Optional(
|
|
31
|
+
Type.Boolean({
|
|
32
|
+
description:
|
|
33
|
+
"Set to true to replace an existing note. Defaults to false.",
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
29
36
|
}),
|
|
30
37
|
async execute(
|
|
31
38
|
_toolCallId: string,
|
|
@@ -34,6 +41,7 @@ export function registerWriteTool(
|
|
|
34
41
|
content: string
|
|
35
42
|
folder: string
|
|
36
43
|
project?: string
|
|
44
|
+
overwrite?: boolean
|
|
37
45
|
},
|
|
38
46
|
) {
|
|
39
47
|
log.debug(`write_note: title=${params.title} folder=${params.folder}`)
|
|
@@ -44,6 +52,7 @@ export function registerWriteTool(
|
|
|
44
52
|
params.content,
|
|
45
53
|
params.folder,
|
|
46
54
|
params.project,
|
|
55
|
+
params.overwrite,
|
|
47
56
|
)
|
|
48
57
|
|
|
49
58
|
const msg = `Note saved: ${note.title} (${note.permalink})`
|
|
@@ -61,6 +70,22 @@ export function registerWriteTool(
|
|
|
61
70
|
},
|
|
62
71
|
}
|
|
63
72
|
} catch (err) {
|
|
73
|
+
if (err instanceof NoteAlreadyExistsError) {
|
|
74
|
+
const hint = [
|
|
75
|
+
`Note already exists: "${params.title}" (${err.permalink})`,
|
|
76
|
+
"",
|
|
77
|
+
"To update this note, use one of:",
|
|
78
|
+
` - edit_note("${err.permalink}", operation="append", content="...")`,
|
|
79
|
+
` - edit_note("${err.permalink}", operation="replace_section", section="...", content="...")`,
|
|
80
|
+
` - write_note("${params.title}", ..., overwrite=true) to fully replace`,
|
|
81
|
+
` - read_note("${err.permalink}") to inspect current content first`,
|
|
82
|
+
].join("\n")
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text" as const, text: hint }],
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
log.error("write_note failed", err)
|
|
65
90
|
return {
|
|
66
91
|
content: [
|