@basicmemory/openclaw-basic-memory 0.1.3 → 0.1.4

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.
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: memory-lifecycle
3
+ 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."
4
+ ---
5
+
6
+ # Memory Lifecycle
7
+
8
+ Manage how entities move through status stages in Basic Memory. The core principle: **archive, never delete.** Completed work is valuable context — move it out of the active view, but keep it in the knowledge graph.
9
+
10
+ ## When to Use
11
+
12
+ - User says something is "done", "finished", "completed", "submitted", "missed", or "cancelled"
13
+ - Moving entities between status folders (active → archive, pipeline → active, etc.)
14
+ - Reverting a mistaken completion
15
+ - Periodic cleanup of stale active items
16
+
17
+ ## Core Principle: Archive, Never Delete
18
+
19
+ Deleting a note removes it from the knowledge graph — all its observations, relations, and history disappear. Archiving preserves everything while signaling the entity is no longer active.
20
+
21
+ ```
22
+ # Good — entity stays in the knowledge graph
23
+ move_note → active/ to archive/
24
+
25
+ # Bad — knowledge is lost
26
+ delete_note
27
+ ```
28
+
29
+ The only exception: notes created by mistake (typos, true duplicates) can be deleted.
30
+
31
+ ## Folder Conventions
32
+
33
+ Organize entities by status using folders. The exact folder names depend on your domain, but follow a consistent pattern:
34
+
35
+ ```
36
+ entities/
37
+ active/ # Currently relevant, in-progress
38
+ archive/ # Completed, no longer active, but worth keeping
39
+ pipeline/ # Future items, not yet started
40
+ ```
41
+
42
+ For tasks specifically:
43
+
44
+ ```
45
+ tasks/
46
+ active/ # Work in progress
47
+ completed/ # Finished work
48
+ ```
49
+
50
+ For any entity type with a clear lifecycle:
51
+
52
+ ```
53
+ [type]/
54
+ active/ # Current
55
+ [end-state]/ # Whatever "done" means for this type
56
+ ```
57
+
58
+ Pick folder names that match your domain. The pattern matters more than the specific names.
59
+
60
+ ## Status Detection
61
+
62
+ When the user mentions completion or status change, extract the intent:
63
+
64
+ | Signal | Status | Action |
65
+ |--------|--------|--------|
66
+ | "finished", "done", "completed", "shipped" | Complete | Move to archive/completed folder |
67
+ | "submitted", "sent", "delivered" | Complete | Move to archive/completed folder |
68
+ | "missed", "passed", "skipped", "expired" | Missed | Move to archive or missed folder |
69
+ | "cancelled", "abandoned", "killed" | Cancelled | Move to archive folder |
70
+ | "paused", "on hold", "deferred" | Paused | Update frontmatter status, keep in place |
71
+ | "restarting", "reopening", "reviving" | Reactivate | Move back to active folder |
72
+
73
+ ## Workflow
74
+
75
+ ### 1. Find the Entity
76
+
77
+ Search Basic Memory with multiple variations to locate the entity:
78
+
79
+ ```python
80
+ search_notes(query="quarterly report")
81
+ search_notes(query="Q1 report")
82
+ ```
83
+
84
+ If multiple matches come back, present options and ask which one.
85
+
86
+ If no match is found, ask for clarification — don't guess.
87
+
88
+ ### 2. Move the File
89
+
90
+ Use `move_note` to relocate the entity to the appropriate status folder:
91
+
92
+ ```python
93
+ move_note(
94
+ identifier="tasks/active/quarterly-report",
95
+ destination_path="tasks/completed/quarterly-report.md"
96
+ )
97
+ ```
98
+
99
+ The permalink stays the same, so all existing `[[wiki-links]]` and `memory://` URLs continue to resolve.
100
+
101
+ ### 3. Update Frontmatter
102
+
103
+ After moving, update the status in frontmatter to match:
104
+
105
+ ```python
106
+ edit_note(
107
+ identifier="quarterly-report",
108
+ operation="find_replace",
109
+ find_text="status: active",
110
+ content="status: completed"
111
+ )
112
+ ```
113
+
114
+ If there's a completion date field, set it:
115
+
116
+ ```python
117
+ edit_note(
118
+ identifier="quarterly-report",
119
+ operation="find_replace",
120
+ find_text="completed:",
121
+ content="completed: 2026-02-22"
122
+ )
123
+ ```
124
+
125
+ ### 4. Confirm
126
+
127
+ Report what was done concisely:
128
+
129
+ ```
130
+ Marked complete: Quarterly Report
131
+ Moved to: tasks/completed/quarterly-report.md
132
+ Status: completed
133
+ ```
134
+
135
+ ## Edge Cases
136
+
137
+ ### Already Archived
138
+
139
+ If the entity is already in an archive/completed folder, notify the user:
140
+
141
+ > "Quarterly Report is already in tasks/completed/. Want me to update anything on it?"
142
+
143
+ ### Partial Completion
144
+
145
+ Sometimes only part of an entity is done. Don't move it — instead, update observations or status notes within the entity to reflect partial progress.
146
+
147
+ ### Revert / Reactivate
148
+
149
+ If something was archived by mistake, move it back:
150
+
151
+ ```python
152
+ move_note(
153
+ identifier="tasks/completed/quarterly-report",
154
+ destination_path="tasks/active/quarterly-report.md"
155
+ )
156
+
157
+ edit_note(
158
+ identifier="quarterly-report",
159
+ operation="find_replace",
160
+ find_text="status: completed",
161
+ content="status: active"
162
+ )
163
+ ```
164
+
165
+ ### Status Without Movement
166
+
167
+ Some status changes don't require a folder move — "paused" or "blocked" items often stay in `active/` with just a frontmatter update. Reserve folder moves for terminal or major state transitions.
168
+
169
+ ## Relationship to Other Skills
170
+
171
+ - **memory-tasks**: Tasks are a specific lifecycle case. This skill covers the general pattern; memory-tasks covers task-specific fields (steps, current_step, context).
172
+ - **memory-notes**: Use search-before-create (from memory-notes) to find the entity before transitioning it.
173
+ - **memory-defrag**: Periodic defrag can identify stale active items that should be archived.
174
+
175
+ ## Guidelines
176
+
177
+ - **Archive, never delete.** The knowledge graph benefits from historical context.
178
+ - **Move first, then update frontmatter.** This order ensures the file is in the right place even if the edit step fails.
179
+ - **Permalinks survive moves.** Links to the entity keep working after a `move_note`.
180
+ - **Be concise in confirmations.** The user knows their system — just report what changed.
181
+ - **Ask when ambiguous.** If multiple entities match or the target folder isn't clear, ask rather than guess.
182
+ - **Batch operations are fine.** If the user says "archive all completed tasks", find them all, confirm the list, then move them in sequence.
@@ -16,16 +16,9 @@ Find notes by their structured frontmatter fields instead of (or in addition to)
16
16
  - **Tag-based filtering** — find notes tagged with specific frontmatter tags
17
17
  - **Schema-aware queries** — filter by nested schema fields using dot notation
18
18
 
19
- ## Tool
19
+ ## The Tool
20
20
 
21
- Use the `search_notes` tool with the optional `metadata_filters`, `tags`, and `status` parameters.
22
-
23
- | Parameter | Type | Description |
24
- |-----------|------|-------------|
25
- | `query` | string | Text search query (can be empty for filter-only searches) |
26
- | `metadata_filters` | object | Filter by frontmatter fields (see filter syntax below) |
27
- | `tags` | string[] | Filter by frontmatter tags (all must match) |
28
- | `status` | string | Filter by frontmatter status field |
21
+ All metadata searching uses `search_notes`. Pass filters via `metadata_filters`, or use the `tags` and `status` convenience shortcuts. Omit `query` (or pass `None`) for filter-only searches.
29
22
 
30
23
  ## Filter Syntax
31
24
 
@@ -87,71 +80,52 @@ Numeric values use numeric comparison; strings use lexicographic comparison.
87
80
  - `$in` and array-contains require non-empty lists
88
81
  - `$between` requires exactly `[min, max]`
89
82
 
90
- ## Examples
91
-
92
- ### Metadata-only search (empty query)
93
-
94
- ```
95
- search_notes(query="", metadata_filters={"status": "in-progress", "type": "spec"})
96
- ```
97
-
98
- ### Text search narrowed by metadata
83
+ ## Using `search_notes` with Metadata
99
84
 
100
- ```
101
- search_notes(query="authentication", metadata_filters={"status": "draft"})
102
- ```
85
+ Pass `metadata_filters`, `tags`, or `status` to `search_notes`. Omit `query` for filter-only searches, or combine text and filters together.
103
86
 
104
- ### Filter by tags
87
+ ```python
88
+ # Filter-only — find all notes with a given status
89
+ search_notes(metadata_filters={"status": "in-progress"})
105
90
 
106
- ```
107
- search_notes(query="", tags=["security", "oauth"])
108
- ```
91
+ # Filter-only — high-priority specs in a specific project
92
+ search_notes(
93
+ metadata_filters={"type": "spec", "priority": {"$in": ["high", "critical"]}},
94
+ project="research",
95
+ page_size=10,
96
+ )
109
97
 
110
- ### Filter by status shortcut
98
+ # Filter-only — notes with confidence above a threshold
99
+ search_notes(metadata_filters={"confidence": {"$gt": 0.7}})
111
100
 
112
- ```
113
- search_notes(query="planning", status="active")
114
- ```
101
+ # Convenience shortcuts for tags and status
102
+ search_notes(status="active")
103
+ search_notes(tags=["security", "oauth"])
115
104
 
116
- ### Combined text + metadata + tags
105
+ # Text search narrowed by metadata
106
+ search_notes("authentication", metadata_filters={"status": "draft"})
117
107
 
118
- ```
108
+ # Mix text, tag shortcut, and advanced filter
119
109
  search_notes(
120
- query="oauth flow",
121
- metadata_filters={"confidence": {"$gt": 0.7}},
110
+ "oauth flow",
122
111
  tags=["security"],
123
- status="in-progress",
124
- )
125
- ```
126
-
127
- ### High-priority notes in a specific project
128
-
129
- ```
130
- search_notes(
131
- query="",
132
- metadata_filters={"priority": {"$in": ["high", "critical"]}},
133
- project="research",
134
- limit=10,
112
+ metadata_filters={"confidence": {"$gt": 0.7}},
135
113
  )
136
114
  ```
137
115
 
138
- ### Numeric range query
139
-
140
- ```
141
- search_notes(query="", metadata_filters={"score": {"$between": [0.3, 0.8]}})
142
- ```
116
+ **Merging rules:** `tags` and `status` are convenience shortcuts merged into `metadata_filters` via `setdefault`. If the same key exists in `metadata_filters`, the explicit filter wins.
143
117
 
144
118
  ## Tag Search Shorthand
145
119
 
146
120
  The `tag:` prefix in a query converts to a tag filter automatically:
147
121
 
148
- ```
122
+ ```python
149
123
  # These are equivalent:
150
- search_notes(query="tag:tier1")
151
- search_notes(query="", tags=["tier1"])
124
+ search_notes("tag:tier1")
125
+ search_notes("", tags=["tier1"])
152
126
 
153
127
  # Multiple tags (comma or space separated) — all must match:
154
- search_notes(query="tag:tier1,alpha")
128
+ search_notes("tag:tier1,alpha")
155
129
  ```
156
130
 
157
131
  ## Example: Custom Frontmatter in Practice
@@ -180,29 +154,29 @@ confidence: 0.85
180
154
 
181
155
  Queries that find it:
182
156
 
183
- ```
157
+ ```python
184
158
  # By status and type
185
- search_notes(query="", metadata_filters={"status": "in-progress", "type": "spec"})
159
+ search_notes(metadata_filters={"status": "in-progress", "type": "spec"})
186
160
 
187
161
  # By numeric threshold
188
- search_notes(query="", metadata_filters={"confidence": {"$gt": 0.7}})
162
+ search_notes(metadata_filters={"confidence": {"$gt": 0.7}})
189
163
 
190
164
  # By priority set
191
- search_notes(query="", metadata_filters={"priority": {"$in": ["high", "critical"]}})
165
+ search_notes(metadata_filters={"priority": {"$in": ["high", "critical"]}})
192
166
 
193
167
  # By tag shorthand
194
- search_notes(query="tag:security")
168
+ search_notes("tag:security")
195
169
 
196
170
  # Combined text + metadata
197
- search_notes(query="OAuth", metadata_filters={"status": "in-progress"})
171
+ search_notes("OAuth", metadata_filters={"status": "in-progress"})
198
172
  ```
199
173
 
200
174
  ## Guidelines
201
175
 
202
- - **Use metadata filters for structured queries.** If you're looking for notes by a known field value (status, priority, type), metadata filters are more precise than text search.
176
+ - **Use metadata search for structured queries.** If you're looking for notes by a known field value (status, priority, type), metadata filters are more precise than text search.
203
177
  - **Use text search for content queries.** If you're looking for notes *about* something, text search is better. Combine both when you need precision.
204
178
  - **Custom fields are free.** Any YAML key you put in frontmatter becomes queryable — no schema or configuration required.
205
179
  - **Multiple filters are AND.** `{"status": "active", "priority": "high"}` requires both conditions.
206
- - **Use empty query for filter-only searches.** Pass `query=""` with `metadata_filters` when you only need structured filtering.
180
+ - **Omit `query` for filter-only searches.** `search_notes(metadata_filters={"status": "active"})` works without a text query.
207
181
  - **Dot notation for nesting.** Access nested YAML structures with dots: `{"schema.version": "2"}` queries the `version` key inside a `schema` object.
208
- - **Tags and status are convenient shortcuts.** Use the dedicated `tags` and `status` parameters for common fields, or `metadata_filters` for anything else.
182
+ - **Tags shortcut is convenient but limited.** `tags` and `status` are sugar for common fields. For anything else, use `metadata_filters` directly.
@@ -17,7 +17,10 @@ tags: [api, architecture, decisions]
17
17
 
18
18
  # API Design Decisions
19
19
 
20
- Background context explaining why this note exists and what it covers.
20
+ The API team evaluated multiple approaches for the public API during Q1. After
21
+ prototyping both REST and GraphQL, the team chose REST due to broader ecosystem
22
+ support and simpler caching semantics. This note captures the key decisions and
23
+ their rationale, along with open questions still to resolve.
21
24
 
22
25
  ## Observations
23
26
  - [decision] Use REST over GraphQL for simplicity #api
@@ -45,17 +48,20 @@ permalink: custom-path # optional — auto-generated from title if omitted
45
48
 
46
49
  - The `title` must match the `# Heading` in the body
47
50
  - Tags are searchable and help with discovery
48
- - Custom `type` values (Task, Meeting, Person, etc.) work with the schema system
49
- - The `permalink` is auto-generated from the title (e.g., "API Design Decisions" → `api-design-decisions`). It stays stable across file moves and is the basis for `memory://` URLs. You rarely need to set it manually.
51
+ - Custom `type` values (Task, Meeting, Person, etc.) work with the schema system. See the **memory-schema** skill for defining schemas, validating notes against them, and detecting drift.
52
+ - The `permalink` is auto-generated from the `title` and `directory`. For example, title "API Design Decisions" in directory "specs" produces permalink `specs/api-design-decisions` and memory URL `memory://specs/api-design-decisions`. If no directory is specified, the permalink is just the kebab-cased title. Permalinks stay stable across file moves. You rarely need to set one manually.
53
+
54
+ > **Note:** When using `write_note`, you don't write frontmatter yourself. The `title`, `tags`, `note_type`, and `metadata` are separate parameters — Basic Memory generates the frontmatter automatically. Your `content` parameter is just the markdown body starting with `# Heading`.
50
55
 
51
56
  ### Body / Context
52
57
 
53
- Free-form markdown between the heading and the Observations section. Use this for:
54
- - Background and motivation
55
- - Summary of the topic
56
- - Any prose that doesn't fit neatly into categorized observations
58
+ Free-form markdown between the heading and the Observations section. This is the heart of the note — write generously here:
59
+ - Background, motivation, and history
60
+ - Detailed explanation of what happened and why it matters
61
+ - Analysis, reasoning, and trade-offs considered
62
+ - Context that someone (or an AI) needs to understand this note later
57
63
 
58
- This content is indexed for full-text search but isn't parsed into structured entities.
64
+ Write complete, substantive prose. Basic Memory's search retrieves relevant chunks from note bodies, so longer, richer context makes notes more discoverable and more useful when found. Don't reduce everything to bullet points — tell the story.
59
65
 
60
66
  ## Observations
61
67
 
@@ -173,33 +179,88 @@ The first path segment is matched against known project names. If it matches, it
173
179
 
174
180
  Memory URLs work with `build_context` to assemble related knowledge by traversing relations:
175
181
 
176
- ```
177
- // Get a note and its connected context
182
+ ```python
183
+ # Get a note and its connected context
178
184
  build_context(url="memory://api-design-decisions")
179
185
 
180
- // Wildcard — gather all docs
186
+ # Wildcard — gather all docs
181
187
  build_context(url="memory://docs/*")
182
188
 
183
- // Direct read by permalink
189
+ # Direct read by permalink
184
190
  read_note(identifier="memory://api-design-decisions")
185
191
  ```
186
192
 
193
+ ## Before Creating a Note
194
+
195
+ Always search Basic Memory before creating a new note. Duplicates fragment your knowledge graph — updating an existing note is almost always better than creating a second one.
196
+
197
+ ### Search with Multiple Variations
198
+
199
+ A single search often misses. Try the full name, abbreviations, acronyms, and keywords:
200
+
201
+ ```python
202
+ # Searching for an entity that might already exist
203
+ search_notes(query="Kubernetes Migration")
204
+ search_notes(query="k8s migration")
205
+ search_notes(query="container migration")
206
+ ```
207
+
208
+ For people, try full name and last name. For organizations, try the full name and common abbreviations.
209
+
210
+ ### Decision Tree
211
+
212
+ - **Entity exists** → Update it with `edit_note` (append observations, add relations, find-and-replace outdated info)
213
+ - **Entity doesn't exist** → Create it with `write_note`
214
+ - **Unsure if it's the same entity** → Read the existing note first, then decide
215
+
216
+ ### Granular Updates with `edit_note`
217
+
218
+ When a note already exists, make targeted edits instead of rewriting the whole file:
219
+
220
+ ```python
221
+ # Append a new observation to an existing note
222
+ edit_note(
223
+ identifier="API Design Decisions",
224
+ operation="append",
225
+ section="Observations",
226
+ content="- [decision] Switched to OpenAPI 3.1 for spec generation #api"
227
+ )
228
+
229
+ # Fix outdated information
230
+ edit_note(
231
+ identifier="API Design Decisions",
232
+ operation="find_replace",
233
+ find_text="- [status] draft",
234
+ content="- [status] approved"
235
+ )
236
+
237
+ # Add a new relation
238
+ edit_note(
239
+ identifier="API Design Decisions",
240
+ operation="append",
241
+ section="Relations",
242
+ content="- depends_on [[Rate Limiter]]"
243
+ )
244
+ ```
245
+
246
+ This preserves existing content and keeps the edit history clean.
247
+
187
248
  ## Writing Notes with Tools
188
249
 
189
250
  ### Creating a Note
190
251
 
191
- ```
252
+ ```python
192
253
  write_note(
193
- title="API Design Decisions",
194
- folder="architecture", // optional — organizes files on disk
195
- content="---
196
- title: API Design Decisions
197
- tags: [api, architecture]
198
- ---
199
-
200
- # API Design Decisions
254
+ title="API Design Decisions",
255
+ directory="architecture",
256
+ tags=["api", "architecture"],
257
+ content="""# API Design Decisions
201
258
 
202
- Context about the API design process.
259
+ The API team evaluated REST and GraphQL during Q1 planning. After prototyping
260
+ both approaches, we chose REST for the public API — broader ecosystem support,
261
+ simpler caching with HTTP semantics, and a lower learning curve for external
262
+ consumers. GraphQL remains an option for internal services where query
263
+ flexibility matters more.
203
264
 
204
265
  ## Observations
205
266
  - [decision] Use REST for public API #api
@@ -207,37 +268,52 @@ Context about the API design process.
207
268
 
208
269
  ## Relations
209
270
  - implements [[API Specification]]
210
- - relates_to [[Backend Architecture]]",
271
+ - relates_to [[Backend Architecture]]"""
211
272
  )
212
273
  ```
213
274
 
275
+ Basic Memory auto-generates frontmatter (including the permalink and memory URL) from the parameters. This note would get permalink `architecture/api-design-decisions` and be addressable at `memory://architecture/api-design-decisions`.
276
+
214
277
  ### Editing an Existing Note
215
278
 
216
279
  Use `edit_note` to append, prepend, or find-and-replace within a note:
217
280
 
218
- ```
219
- // Append new observations
281
+ ```python
282
+ # Append new observations
220
283
  edit_note(
221
- identifier="API Design Decisions",
222
- operation="append",
223
- heading="Observations",
224
- content="- [decision] Use OpenAPI 3.1 for spec generation #api",
284
+ identifier="API Design Decisions",
285
+ operation="append",
286
+ section="Observations",
287
+ content="- [decision] Use OpenAPI 3.1 for spec generation #api"
225
288
  )
226
289
 
227
- // Add a new relation
290
+ # Add a new relation
228
291
  edit_note(
229
- identifier="API Design Decisions",
230
- operation="append",
231
- heading="Relations",
232
- content="- depends_on [[Rate Limiter]]",
292
+ identifier="API Design Decisions",
293
+ operation="append",
294
+ section="Relations",
295
+ content="- depends_on [[Rate Limiter]]"
233
296
  )
234
297
  ```
235
298
 
299
+ ### Moving a Note
300
+
301
+ Use `move_note` to reorganize notes into different directories:
302
+
303
+ ```python
304
+ move_note(
305
+ identifier="API Design Decisions",
306
+ destination_path="archive/api-design-decisions.md"
307
+ )
308
+ ```
309
+
310
+ The permalink stays the same after a move, so all `[[wiki-links]]` and `memory://` URLs continue to resolve.
311
+
236
312
  ## Best Practices
237
313
 
238
314
  1. **Start with context.** Before listing observations, explain *why* this note exists. Future-you (or your AI collaborator) will thank you.
239
315
 
240
- 2. **Observations over prose.** Categorized observations are searchable and structured. A paragraph of text is not. Prefer `[decision] X because Y` over burying the same fact in a paragraph.
316
+ 2. **Favor completeness.** Write rich, substantive notes. Basic Memory's search pulls relevant chunks from note bodies, so longer notes with more context are *more* discoverable, not less. Use prose in the body to tell the full story — the background, the reasoning, the nuance. Then distill key facts into `[category] content` observations for structured queries. Both matter: prose gives meaning, observations give precision.
241
317
 
242
318
  3. **Build incrementally.** Add to existing notes rather than creating duplicates. Use `edit_note` to append new observations or relations as you learn more.
243
319
 
@@ -19,10 +19,20 @@ Inspired by sleep-time compute — the idea that memory formation happens best *
19
19
 
20
20
  ### 1. Gather Recent Material
21
21
 
22
- Read the last 2-3 days of daily notes from `memory/YYYY-MM-DD.md`. Also check:
23
- - Recent conversation transcripts (if accessible)
24
- - Any files modified in `memory/` in the last 48 hours
25
- - Active tasks in `memory/tasks/`
22
+ Find what changed recently, then read the relevant files:
23
+
24
+ ```python
25
+ # Find recently modified notes — use json format for the complete list
26
+ # (text format truncates to ~5 items in the summary)
27
+ recent_activity(timeframe="2d", output_format="json")
28
+
29
+ # Read specific daily notes
30
+ read_note(identifier="memory/2026-02-27")
31
+ read_note(identifier="memory/2026-02-26")
32
+
33
+ # Check active tasks
34
+ search_notes(note_types=["task"], status="active")
35
+ ```
26
36
 
27
37
  ### 2. Evaluate What Matters
28
38