@basicmemory/openclaw-basic-memory 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +576 -0
- package/bm-client.ts +879 -0
- package/commands/cli.ts +176 -0
- package/commands/skills.ts +52 -0
- package/commands/slash.ts +73 -0
- package/config.ts +152 -0
- package/hooks/capture.ts +95 -0
- package/hooks/recall.ts +66 -0
- package/index.ts +120 -0
- package/logger.ts +47 -0
- package/openclaw.plugin.json +83 -0
- package/package.json +68 -0
- package/schema/task-schema.ts +34 -0
- package/scripts/setup-bm.sh +32 -0
- package/skills/memory-defrag/SKILL.md +87 -0
- package/skills/memory-metadata-search/SKILL.md +208 -0
- package/skills/memory-notes/SKILL.md +250 -0
- package/skills/memory-reflect/SKILL.md +63 -0
- package/skills/memory-schema/SKILL.md +237 -0
- package/skills/memory-tasks/SKILL.md +162 -0
- package/tools/build-context.ts +123 -0
- package/tools/delete-note.ts +67 -0
- package/tools/edit-note.ts +118 -0
- package/tools/list-memory-projects.ts +94 -0
- package/tools/list-workspaces.ts +75 -0
- package/tools/memory-provider.ts +327 -0
- package/tools/move-note.ts +74 -0
- package/tools/read-note.ts +79 -0
- package/tools/schema-diff.ts +104 -0
- package/tools/schema-infer.ts +103 -0
- package/tools/schema-validate.ts +100 -0
- package/tools/search-notes.ts +130 -0
- package/tools/write-note.ts +78 -0
- package/types/openclaw.d.ts +24 -0
package/index.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
2
|
+
import { BmClient } from "./bm-client.ts"
|
|
3
|
+
import { registerCli } from "./commands/cli.ts"
|
|
4
|
+
import { registerSkillCommands } from "./commands/skills.ts"
|
|
5
|
+
import { registerCommands } from "./commands/slash.ts"
|
|
6
|
+
import {
|
|
7
|
+
basicMemoryConfigSchema,
|
|
8
|
+
parseConfig,
|
|
9
|
+
resolveProjectPath,
|
|
10
|
+
} from "./config.ts"
|
|
11
|
+
import { buildCaptureHandler } from "./hooks/capture.ts"
|
|
12
|
+
import { buildRecallHandler } from "./hooks/recall.ts"
|
|
13
|
+
import { initLogger, log } from "./logger.ts"
|
|
14
|
+
import { TASK_SCHEMA_CONTENT } from "./schema/task-schema.ts"
|
|
15
|
+
import { registerContextTool } from "./tools/build-context.ts"
|
|
16
|
+
import { registerDeleteTool } from "./tools/delete-note.ts"
|
|
17
|
+
import { registerEditTool } from "./tools/edit-note.ts"
|
|
18
|
+
import { registerProjectListTool } from "./tools/list-memory-projects.ts"
|
|
19
|
+
import { registerWorkspaceListTool } from "./tools/list-workspaces.ts"
|
|
20
|
+
import {
|
|
21
|
+
registerMemoryProvider,
|
|
22
|
+
setWorkspaceDir,
|
|
23
|
+
} from "./tools/memory-provider.ts"
|
|
24
|
+
import { registerMoveTool } from "./tools/move-note.ts"
|
|
25
|
+
import { registerReadTool } from "./tools/read-note.ts"
|
|
26
|
+
import { registerSchemaDiffTool } from "./tools/schema-diff.ts"
|
|
27
|
+
import { registerSchemaInferTool } from "./tools/schema-infer.ts"
|
|
28
|
+
import { registerSchemaValidateTool } from "./tools/schema-validate.ts"
|
|
29
|
+
import { registerSearchTool } from "./tools/search-notes.ts"
|
|
30
|
+
import { registerWriteTool } from "./tools/write-note.ts"
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
id: "basic-memory",
|
|
34
|
+
name: "Basic Memory",
|
|
35
|
+
description:
|
|
36
|
+
"Local-first knowledge graph for OpenClaw — persistent memory with graph search and composited memory_search",
|
|
37
|
+
kind: "memory" as const,
|
|
38
|
+
configSchema: basicMemoryConfigSchema,
|
|
39
|
+
|
|
40
|
+
register(api: OpenClawPluginApi) {
|
|
41
|
+
const cfg = parseConfig(api.pluginConfig)
|
|
42
|
+
|
|
43
|
+
initLogger(api.logger, cfg.debug)
|
|
44
|
+
|
|
45
|
+
log.info(
|
|
46
|
+
`project=${cfg.project} memoryDir=${cfg.memoryDir} memoryFile=${cfg.memoryFile}`,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const client = new BmClient(cfg.bmPath, cfg.project)
|
|
50
|
+
|
|
51
|
+
// --- BM Tools (always registered) ---
|
|
52
|
+
registerSearchTool(api, client)
|
|
53
|
+
registerProjectListTool(api, client)
|
|
54
|
+
registerWorkspaceListTool(api, client)
|
|
55
|
+
registerReadTool(api, client)
|
|
56
|
+
registerWriteTool(api, client)
|
|
57
|
+
registerEditTool(api, client)
|
|
58
|
+
registerContextTool(api, client)
|
|
59
|
+
registerDeleteTool(api, client)
|
|
60
|
+
registerMoveTool(api, client)
|
|
61
|
+
registerSchemaValidateTool(api, client)
|
|
62
|
+
registerSchemaInferTool(api, client)
|
|
63
|
+
registerSchemaDiffTool(api, client)
|
|
64
|
+
|
|
65
|
+
// --- Composited memory_search + memory_get (always registered) ---
|
|
66
|
+
registerMemoryProvider(api, client, cfg)
|
|
67
|
+
log.info("registered composited memory_search + memory_get")
|
|
68
|
+
|
|
69
|
+
if (cfg.autoCapture) {
|
|
70
|
+
api.on("agent_end", buildCaptureHandler(client, cfg))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (cfg.autoRecall) {
|
|
74
|
+
api.on("agent_start", buildRecallHandler(client, cfg))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Commands ---
|
|
78
|
+
registerCommands(api, client)
|
|
79
|
+
registerSkillCommands(api)
|
|
80
|
+
registerCli(api, client, cfg)
|
|
81
|
+
|
|
82
|
+
// --- Service lifecycle ---
|
|
83
|
+
api.registerService({
|
|
84
|
+
id: "basic-memory",
|
|
85
|
+
start: async (ctx: { config?: unknown; workspaceDir?: string }) => {
|
|
86
|
+
log.info("starting...")
|
|
87
|
+
|
|
88
|
+
const workspace = ctx.workspaceDir ?? process.cwd()
|
|
89
|
+
const projectPath = resolveProjectPath(cfg.projectPath, workspace)
|
|
90
|
+
cfg.projectPath = projectPath
|
|
91
|
+
|
|
92
|
+
await client.start({ cwd: workspace })
|
|
93
|
+
await client.ensureProject(projectPath)
|
|
94
|
+
log.debug(`project "${cfg.project}" at ${projectPath}`)
|
|
95
|
+
|
|
96
|
+
// Seed Task schema if not already present
|
|
97
|
+
try {
|
|
98
|
+
await client.readNote("schema/Task")
|
|
99
|
+
log.debug("Task schema already exists, skipping seed")
|
|
100
|
+
} catch {
|
|
101
|
+
try {
|
|
102
|
+
await client.writeNote("Task", TASK_SCHEMA_CONTENT, "schema")
|
|
103
|
+
log.debug("seeded Task schema note")
|
|
104
|
+
} catch (err) {
|
|
105
|
+
log.debug("Task schema seed failed (non-fatal)", err)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setWorkspaceDir(workspace)
|
|
110
|
+
|
|
111
|
+
log.info("connected — BM MCP stdio session running")
|
|
112
|
+
},
|
|
113
|
+
stop: async () => {
|
|
114
|
+
log.info("stopping BM MCP session...")
|
|
115
|
+
await client.stop()
|
|
116
|
+
log.info("stopped")
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
}
|
package/logger.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type LoggerBackend = {
|
|
2
|
+
info(msg: string, ...args: unknown[]): void
|
|
3
|
+
warn(msg: string, ...args: unknown[]): void
|
|
4
|
+
error(msg: string, ...args: unknown[]): void
|
|
5
|
+
debug?(msg: string, ...args: unknown[]): void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const NOOP_LOGGER: LoggerBackend = {
|
|
9
|
+
info() {},
|
|
10
|
+
warn() {},
|
|
11
|
+
error() {},
|
|
12
|
+
debug() {},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let _backend: LoggerBackend = NOOP_LOGGER
|
|
16
|
+
let _debug = false
|
|
17
|
+
|
|
18
|
+
export function initLogger(backend: LoggerBackend, debug: boolean): void {
|
|
19
|
+
_backend = backend
|
|
20
|
+
_debug = debug
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const log = {
|
|
24
|
+
info(msg: string, ...args: unknown[]): void {
|
|
25
|
+
_backend.info(`basic-memory: ${msg}`, ...args)
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
warn(msg: string, ...args: unknown[]): void {
|
|
29
|
+
_backend.warn(`basic-memory: ${msg}`, ...args)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
error(msg: string, err?: unknown): void {
|
|
33
|
+
const detail =
|
|
34
|
+
err instanceof Error
|
|
35
|
+
? err.message
|
|
36
|
+
: err !== undefined && err !== ""
|
|
37
|
+
? String(err)
|
|
38
|
+
: ""
|
|
39
|
+
_backend.error(`basic-memory: ${msg}${detail ? ` — ${detail}` : ""}`)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
debug(msg: string, ...args: unknown[]): void {
|
|
43
|
+
if (!_debug) return
|
|
44
|
+
const fn = _backend.debug ?? _backend.info
|
|
45
|
+
fn(`basic-memory [debug]: ${msg}`, ...args)
|
|
46
|
+
},
|
|
47
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "basic-memory",
|
|
3
|
+
"kind": "memory",
|
|
4
|
+
"skills": [
|
|
5
|
+
"skills/memory-tasks",
|
|
6
|
+
"skills/memory-reflect",
|
|
7
|
+
"skills/memory-defrag",
|
|
8
|
+
"skills/memory-schema",
|
|
9
|
+
"skills/memory-metadata-search",
|
|
10
|
+
"skills/memory-notes"
|
|
11
|
+
],
|
|
12
|
+
"uiHints": {
|
|
13
|
+
"project": {
|
|
14
|
+
"label": "Project Name",
|
|
15
|
+
"placeholder": "my-agent",
|
|
16
|
+
"help": "Basic Memory project name. Created automatically if it doesn't exist."
|
|
17
|
+
},
|
|
18
|
+
"memoryDir": {
|
|
19
|
+
"label": "Memory Directory",
|
|
20
|
+
"placeholder": "memory/",
|
|
21
|
+
"help": "Directory to index into the knowledge graph (relative to workspace)"
|
|
22
|
+
},
|
|
23
|
+
"memoryFile": {
|
|
24
|
+
"label": "Memory File",
|
|
25
|
+
"placeholder": "MEMORY.md",
|
|
26
|
+
"help": "Working memory file (searched but not indexed into BM)"
|
|
27
|
+
},
|
|
28
|
+
"bmPath": {
|
|
29
|
+
"label": "BM CLI Path",
|
|
30
|
+
"placeholder": "bm",
|
|
31
|
+
"help": "Path to the basic-memory CLI binary (default: 'bm')",
|
|
32
|
+
"advanced": true
|
|
33
|
+
},
|
|
34
|
+
"indexInterval": {
|
|
35
|
+
"label": "Index Interval (seconds)",
|
|
36
|
+
"placeholder": "300",
|
|
37
|
+
"help": "Seconds between periodic re-indexing sweeps",
|
|
38
|
+
"advanced": true
|
|
39
|
+
},
|
|
40
|
+
"autoCapture": {
|
|
41
|
+
"label": "Auto-Capture",
|
|
42
|
+
"help": "Automatically index conversation content after each turn"
|
|
43
|
+
},
|
|
44
|
+
"debug": {
|
|
45
|
+
"label": "Debug Logging",
|
|
46
|
+
"help": "Enable verbose debug logs",
|
|
47
|
+
"advanced": true
|
|
48
|
+
},
|
|
49
|
+
"projectPath": {
|
|
50
|
+
"label": "Project Path",
|
|
51
|
+
"placeholder": "~/.openclaw/workspace/memory/",
|
|
52
|
+
"help": "Filesystem path for Basic Memory project data (relative paths resolve from workspace); created automatically if missing",
|
|
53
|
+
"advanced": true
|
|
54
|
+
},
|
|
55
|
+
"cloud": {
|
|
56
|
+
"label": "Cloud Backend",
|
|
57
|
+
"help": "Optional cloud backend config (url + api_key). If present, uses cloud instead of local BM.",
|
|
58
|
+
"advanced": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"configSchema": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"properties": {
|
|
65
|
+
"project": { "type": "string" },
|
|
66
|
+
"memoryDir": { "type": "string" },
|
|
67
|
+
"memoryFile": { "type": "string" },
|
|
68
|
+
"bmPath": { "type": "string" },
|
|
69
|
+
"indexInterval": { "type": "number", "minimum": 30, "maximum": 3600 },
|
|
70
|
+
"autoCapture": { "type": "boolean" },
|
|
71
|
+
"debug": { "type": "boolean" },
|
|
72
|
+
"projectPath": { "type": "string" },
|
|
73
|
+
"cloud": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {
|
|
76
|
+
"url": { "type": "string" },
|
|
77
|
+
"api_key": { "type": "string" }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"required": []
|
|
82
|
+
}
|
|
83
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@basicmemory/openclaw-basic-memory",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Basic Memory plugin for OpenClaw — local-first knowledge graph for agent memory",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.ts",
|
|
9
|
+
"bm-client.ts",
|
|
10
|
+
"config.ts",
|
|
11
|
+
"logger.ts",
|
|
12
|
+
"commands/cli.ts",
|
|
13
|
+
"commands/skills.ts",
|
|
14
|
+
"commands/slash.ts",
|
|
15
|
+
"hooks/capture.ts",
|
|
16
|
+
"hooks/recall.ts",
|
|
17
|
+
"tools/build-context.ts",
|
|
18
|
+
"tools/delete-note.ts",
|
|
19
|
+
"tools/edit-note.ts",
|
|
20
|
+
"tools/list-memory-projects.ts",
|
|
21
|
+
"tools/list-workspaces.ts",
|
|
22
|
+
"tools/memory-provider.ts",
|
|
23
|
+
"tools/move-note.ts",
|
|
24
|
+
"tools/read-note.ts",
|
|
25
|
+
"tools/schema-diff.ts",
|
|
26
|
+
"tools/schema-infer.ts",
|
|
27
|
+
"tools/schema-validate.ts",
|
|
28
|
+
"tools/search-notes.ts",
|
|
29
|
+
"tools/write-note.ts",
|
|
30
|
+
"types/openclaw.d.ts",
|
|
31
|
+
"schema/task-schema.ts",
|
|
32
|
+
"skills/",
|
|
33
|
+
"scripts/setup-bm.sh",
|
|
34
|
+
"openclaw.plugin.json",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
43
|
+
"@sinclair/typebox": "0.34.47"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"openclaw": ">=2026.1.29"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"postinstall": "bash scripts/setup-bm.sh || true",
|
|
50
|
+
"check-types": "tsc --noEmit",
|
|
51
|
+
"lint": "bunx @biomejs/biome ci .",
|
|
52
|
+
"lint:fix": "bunx @biomejs/biome check --write .",
|
|
53
|
+
"test": "bun test",
|
|
54
|
+
"test:int": "BM_INTEGRATION=1 BM_BIN=${BM_BIN:-./scripts/bm-local.sh} bun test integration --timeout 120000",
|
|
55
|
+
"test:coverage": "bun test --coverage",
|
|
56
|
+
"release:check": "bun run check-types && bun test && npm pack --dry-run"
|
|
57
|
+
},
|
|
58
|
+
"openclaw": {
|
|
59
|
+
"extensions": [
|
|
60
|
+
"./index.ts"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@biomejs/biome": "^2.3.8",
|
|
65
|
+
"@types/node": "^20.0.0",
|
|
66
|
+
"typescript": "^5.9.3"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Task schema note content, seeded into new projects on first startup.
|
|
3
|
+
* If the schema already exists (user may have customized it), seeding is skipped.
|
|
4
|
+
*/
|
|
5
|
+
export const TASK_SCHEMA_CONTENT = `---
|
|
6
|
+
title: Task
|
|
7
|
+
type: schema
|
|
8
|
+
entity: Task
|
|
9
|
+
version: 1
|
|
10
|
+
schema:
|
|
11
|
+
description: string, what needs to be done
|
|
12
|
+
status?(enum): [active, blocked, done, abandoned], current state
|
|
13
|
+
assigned_to?: string, who is working on this
|
|
14
|
+
steps?(array): string, ordered steps to complete
|
|
15
|
+
current_step?: integer, which step number we're on (1-indexed)
|
|
16
|
+
context?: string, key context needed to resume after memory loss
|
|
17
|
+
started?: string, when work began
|
|
18
|
+
completed?: string, when work finished
|
|
19
|
+
blockers?(array): string, what's preventing progress
|
|
20
|
+
parent_task?: Task, parent task if this is a subtask
|
|
21
|
+
settings:
|
|
22
|
+
validation: warn
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Task
|
|
26
|
+
|
|
27
|
+
Structured task note for tracking multi-step work that survives context compaction.
|
|
28
|
+
|
|
29
|
+
## Observations
|
|
30
|
+
- [convention] Task files live in memory/tasks/ with format YYYY-MM-DD-short-name.md
|
|
31
|
+
- [convention] Status transitions: active → blocked → done or abandoned
|
|
32
|
+
- [convention] Include a ## Context section for resumption after memory loss
|
|
33
|
+
- [convention] Include a ## Steps section with numbered checkbox items for step tracking
|
|
34
|
+
`
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install basic-memory CLI via uv from the latest main branch.
|
|
3
|
+
# Idempotent — safe to re-run. Fails gracefully when uv is absent.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
BM_REPO="https://github.com/basicmachines-co/basic-memory.git"
|
|
7
|
+
BM_REF="${BM_REF:-main}"
|
|
8
|
+
|
|
9
|
+
# ── check for uv ──────────────────────────────────────────────────
|
|
10
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
11
|
+
echo "⚠ uv not found — skipping basic-memory install."
|
|
12
|
+
echo " Install uv: brew install uv"
|
|
13
|
+
echo " or: curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
14
|
+
echo " Then re-run: bash scripts/setup-bm.sh"
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# ── install basic-memory[semantic] ────────────────────────────────
|
|
19
|
+
echo "Installing basic-memory from ${BM_REPO}@${BM_REF} ..."
|
|
20
|
+
uv tool install \
|
|
21
|
+
"basic-memory[semantic] @ git+${BM_REPO}@${BM_REF}" \
|
|
22
|
+
--with 'onnxruntime<1.24; platform_system == "Darwin" and platform_machine == "x86_64"' \
|
|
23
|
+
--force
|
|
24
|
+
|
|
25
|
+
# ── verify ────────────────────────────────────────────────────────
|
|
26
|
+
if ! command -v bm >/dev/null 2>&1; then
|
|
27
|
+
echo "❌ bm binary not found on PATH after install."
|
|
28
|
+
echo " You may need to add uv's bin directory to your PATH."
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "✅ $(bm --version)"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory-defrag
|
|
3
|
+
description: "Defragment and reorganize agent memory files: split bloated files, merge duplicates, remove stale information, and restructure the memory hierarchy. Use when memory files have grown unwieldy, contain redundancies, or need reorganization. Run periodically (weekly) or on demand."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Memory Defrag
|
|
7
|
+
|
|
8
|
+
Reorganize memory files for clarity, efficiency, and relevance. Like filesystem defragmentation but for knowledge.
|
|
9
|
+
|
|
10
|
+
## When to Run
|
|
11
|
+
|
|
12
|
+
- **Periodic**: Weekly or biweekly via cron (recommended)
|
|
13
|
+
- **On demand**: User asks to clean up, reorganize, or defrag memory
|
|
14
|
+
- **Threshold**: When MEMORY.md exceeds ~500 lines or daily notes accumulate without consolidation
|
|
15
|
+
|
|
16
|
+
## Process
|
|
17
|
+
|
|
18
|
+
### 1. Audit Current State
|
|
19
|
+
|
|
20
|
+
Inventory all memory files:
|
|
21
|
+
```
|
|
22
|
+
MEMORY.md — long-term memory
|
|
23
|
+
memory/ — daily notes, tasks, topical files
|
|
24
|
+
memory/tasks/ — active and completed tasks
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For each file, note: line count, last modified, topic coverage, staleness.
|
|
28
|
+
|
|
29
|
+
### 2. Identify Problems
|
|
30
|
+
|
|
31
|
+
Look for these common issues:
|
|
32
|
+
|
|
33
|
+
| Problem | Signal | Fix |
|
|
34
|
+
|---------|--------|-----|
|
|
35
|
+
| **Bloated file** | >300 lines, covers many topics | Split into focused files |
|
|
36
|
+
| **Duplicate info** | Same fact in multiple places | Consolidate to one location |
|
|
37
|
+
| **Stale entries** | References to completed work, old dates, resolved issues | Remove or archive |
|
|
38
|
+
| **Orphan files** | Files in memory/ never referenced or updated | Review, merge, or remove |
|
|
39
|
+
| **Inconsistencies** | Contradictory information across files | Resolve to ground truth |
|
|
40
|
+
| **Poor organization** | Related info scattered across files | Restructure by topic |
|
|
41
|
+
| **Recursive nesting** | `memory/memory/memory/...` directories | Delete nested dirs (indexer bug artifact) |
|
|
42
|
+
|
|
43
|
+
### 3. Plan Changes
|
|
44
|
+
|
|
45
|
+
Before making edits, write a brief plan:
|
|
46
|
+
```markdown
|
|
47
|
+
## Defrag Plan
|
|
48
|
+
- [ ] Split MEMORY.md "Key People" section → memory/people.md
|
|
49
|
+
- [ ] Remove completed tasks older than 30 days from memory/tasks/
|
|
50
|
+
- [ ] Merge memory/bm-marketing-ideas.md into memory/competitive/
|
|
51
|
+
- [ ] Update stale project status entries in MEMORY.md
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 4. Execute
|
|
55
|
+
|
|
56
|
+
Apply changes one at a time:
|
|
57
|
+
- **Split**: Extract sections from large files into focused topical files
|
|
58
|
+
- **Merge**: Combine related small files into coherent documents
|
|
59
|
+
- **Prune**: Remove information that is no longer relevant or accurate
|
|
60
|
+
- **Restructure**: Move files to appropriate directories, rename for clarity
|
|
61
|
+
- **Update**: Fix outdated facts, dates, statuses
|
|
62
|
+
|
|
63
|
+
### 5. Verify & Log
|
|
64
|
+
|
|
65
|
+
After changes:
|
|
66
|
+
- Verify no information was lost (compare before/after)
|
|
67
|
+
- Update any cross-references between files
|
|
68
|
+
- Log what was done in today's daily note:
|
|
69
|
+
|
|
70
|
+
```markdown
|
|
71
|
+
## Memory Defrag (HH:MM)
|
|
72
|
+
- Files reviewed: N
|
|
73
|
+
- Split: [list]
|
|
74
|
+
- Merged: [list]
|
|
75
|
+
- Pruned: [list]
|
|
76
|
+
- Net result: X files, Y total lines (was Z lines)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Guidelines
|
|
80
|
+
|
|
81
|
+
- **Back up first.** If git is available, commit before defragging. If not, note the pre-defrag state.
|
|
82
|
+
- **Preserve raw daily notes.** Don't delete or modify `memory/YYYY-MM-DD.md` files — they're the audit trail.
|
|
83
|
+
- **Target 15-25 focused files.** Too few means bloated files; too many means fragmentation. Aim for the sweet spot.
|
|
84
|
+
- **File names should be scannable.** Use descriptive names: `people.md`, `project-status.md`, `competitive-landscape.md` — not `notes-2.md`.
|
|
85
|
+
- **Don't over-organize.** One level of directories is usually enough. `memory/tasks/` and `memory/competitive/` are fine; `memory/work/projects/active/basic-memory/notes/` is not.
|
|
86
|
+
- **Completed tasks**: Tasks with `status: done` older than 14 days can be removed. Their insights should already be in MEMORY.md via reflection.
|
|
87
|
+
- **Ask before destructive changes.** If uncertain whether information is still relevant, keep it with a `(review needed)` tag rather than deleting.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory-metadata-search
|
|
3
|
+
description: "Structured metadata search for Basic Memory: query notes by custom frontmatter fields using equality, range, array, and nested filters. Use when finding notes by status, priority, confidence, or any custom YAML field rather than free-text content."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Memory Metadata Search
|
|
7
|
+
|
|
8
|
+
Find notes by their structured frontmatter fields instead of (or in addition to) free-text content. Any custom YAML key in a note's frontmatter beyond the standard set (`title`, `type`, `tags`, `permalink`, `schema`) is automatically indexed as `entity_metadata` and becomes queryable.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- **Filtering by status or priority** — find all notes with `status: draft` or `priority: high`
|
|
13
|
+
- **Querying custom fields** — any frontmatter key you invent is searchable
|
|
14
|
+
- **Range queries** — find notes with `confidence > 0.7` or `score between 0.3 and 0.8`
|
|
15
|
+
- **Combining text + metadata** — narrow a text search with structured constraints
|
|
16
|
+
- **Tag-based filtering** — find notes tagged with specific frontmatter tags
|
|
17
|
+
- **Schema-aware queries** — filter by nested schema fields using dot notation
|
|
18
|
+
|
|
19
|
+
## Tool
|
|
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 |
|
|
29
|
+
|
|
30
|
+
## Filter Syntax
|
|
31
|
+
|
|
32
|
+
Filters are a JSON dictionary. Each key targets a frontmatter field; the value specifies the match condition. Multiple keys combine with **AND** logic.
|
|
33
|
+
|
|
34
|
+
### Equality
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{"status": "active"}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Array Contains (all listed values must be present)
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{"tags": ["security", "oauth"]}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `$in` (match any value in list)
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{"priority": {"$in": ["high", "critical"]}}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Comparisons (`$gt`, `$gte`, `$lt`, `$lte`)
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{"confidence": {"$gt": 0.7}}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Numeric values use numeric comparison; strings use lexicographic comparison.
|
|
59
|
+
|
|
60
|
+
### `$between` (inclusive range)
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{"score": {"$between": [0.3, 0.8]}}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Nested Access (dot notation)
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{"schema.version": "2"}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Quick Reference
|
|
73
|
+
|
|
74
|
+
| Operator | Syntax | Example |
|
|
75
|
+
|----------|--------|---------|
|
|
76
|
+
| Equality | `{"field": "value"}` | `{"status": "active"}` |
|
|
77
|
+
| Array contains | `{"field": ["a", "b"]}` | `{"tags": ["security", "oauth"]}` |
|
|
78
|
+
| `$in` | `{"field": {"$in": [...]}}` | `{"priority": {"$in": ["high", "critical"]}}` |
|
|
79
|
+
| `$gt` / `$gte` | `{"field": {"$gt": N}}` | `{"confidence": {"$gt": 0.7}}` |
|
|
80
|
+
| `$lt` / `$lte` | `{"field": {"$lt": N}}` | `{"score": {"$lt": 0.5}}` |
|
|
81
|
+
| `$between` | `{"field": {"$between": [lo, hi]}}` | `{"score": {"$between": [0.3, 0.8]}}` |
|
|
82
|
+
| Nested | `{"a.b": "value"}` | `{"schema.version": "2"}` |
|
|
83
|
+
|
|
84
|
+
**Rules:**
|
|
85
|
+
- Keys must match `[A-Za-z0-9_-]+` (dots separate nesting levels)
|
|
86
|
+
- Operator dicts must contain exactly one operator
|
|
87
|
+
- `$in` and array-contains require non-empty lists
|
|
88
|
+
- `$between` requires exactly `[min, max]`
|
|
89
|
+
|
|
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
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
search_notes(query="authentication", metadata_filters={"status": "draft"})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Filter by tags
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
search_notes(query="", tags=["security", "oauth"])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Filter by status shortcut
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
search_notes(query="planning", status="active")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Combined text + metadata + tags
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
search_notes(
|
|
120
|
+
query="oauth flow",
|
|
121
|
+
metadata_filters={"confidence": {"$gt": 0.7}},
|
|
122
|
+
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,
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Numeric range query
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
search_notes(query="", metadata_filters={"score": {"$between": [0.3, 0.8]}})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Tag Search Shorthand
|
|
145
|
+
|
|
146
|
+
The `tag:` prefix in a query converts to a tag filter automatically:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
# These are equivalent:
|
|
150
|
+
search_notes(query="tag:tier1")
|
|
151
|
+
search_notes(query="", tags=["tier1"])
|
|
152
|
+
|
|
153
|
+
# Multiple tags (comma or space separated) — all must match:
|
|
154
|
+
search_notes(query="tag:tier1,alpha")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Example: Custom Frontmatter in Practice
|
|
158
|
+
|
|
159
|
+
A note with custom fields:
|
|
160
|
+
|
|
161
|
+
```markdown
|
|
162
|
+
---
|
|
163
|
+
title: Auth Design
|
|
164
|
+
type: spec
|
|
165
|
+
tags: [security, oauth]
|
|
166
|
+
status: in-progress
|
|
167
|
+
priority: high
|
|
168
|
+
confidence: 0.85
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# Auth Design
|
|
172
|
+
|
|
173
|
+
## Observations
|
|
174
|
+
- [decision] Use OAuth 2.1 with PKCE for all client types #security
|
|
175
|
+
- [requirement] Token refresh must be transparent to the user
|
|
176
|
+
|
|
177
|
+
## Relations
|
|
178
|
+
- implements [[Security Requirements]]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Queries that find it:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
# By status and type
|
|
185
|
+
search_notes(query="", metadata_filters={"status": "in-progress", "type": "spec"})
|
|
186
|
+
|
|
187
|
+
# By numeric threshold
|
|
188
|
+
search_notes(query="", metadata_filters={"confidence": {"$gt": 0.7}})
|
|
189
|
+
|
|
190
|
+
# By priority set
|
|
191
|
+
search_notes(query="", metadata_filters={"priority": {"$in": ["high", "critical"]}})
|
|
192
|
+
|
|
193
|
+
# By tag shorthand
|
|
194
|
+
search_notes(query="tag:security")
|
|
195
|
+
|
|
196
|
+
# Combined text + metadata
|
|
197
|
+
search_notes(query="OAuth", metadata_filters={"status": "in-progress"})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Guidelines
|
|
201
|
+
|
|
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.
|
|
203
|
+
- **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
|
+
- **Custom fields are free.** Any YAML key you put in frontmatter becomes queryable — no schema or configuration required.
|
|
205
|
+
- **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.
|
|
207
|
+
- **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.
|