@nowledge/openclaw-nowledge-mem 0.6.5 → 0.6.6

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/CHANGELOG.md CHANGED
@@ -2,14 +2,42 @@
2
2
 
3
3
  All notable changes to the Nowledge Mem OpenClaw plugin will be documented in this file.
4
4
 
5
+ ## [0.6.6] - 2026-02-27
6
+
7
+ ### Changed - Config file + env var cascade
8
+
9
+ **Config file at `~/.nowledge-mem/openclaw.json`**
10
+
11
+ The plugin now reads configuration from a dedicated config file, independent of OpenClaw's `openclaw.json`. The file is auto-created on first startup with sensible defaults. If you had settings in OpenClaw's plugin config, those values are automatically migrated into the file on first run. No manual steps needed.
12
+
13
+ This ensures your settings survive OpenClaw version upgrades that may strip custom plugin config fields (observed in OpenClaw >= 2026.2.25).
14
+
15
+ **Config cascade** (4 layers, highest to lowest priority):
16
+ 1. `api.pluginConfig` - from OpenClaw's plugin config (when supported)
17
+ 2. `~/.nowledge-mem/openclaw.json` - dedicated config file
18
+ 3. Environment variables - `NMEM_SESSION_CONTEXT`, `NMEM_SESSION_DIGEST`, etc.
19
+ 4. Built-in defaults
20
+
21
+ ### Fixed
22
+
23
+ - **Conversation text unbounded**: `buildConversationText()` now caps per-message content at 2000 chars and total at 30K chars, preventing oversized LLM API payloads from long coding sessions
24
+ - **Config strict mode**: unknown config keys throw a descriptive error listing all valid keys
25
+ - **apiKey not written to config file**: API keys are intentionally excluded from the auto-created config file. They should stay in env vars or OpenClaw's secure config
26
+
27
+ ### Added
28
+
29
+ - Environment variable support for all config keys (not just `apiUrl`/`apiKey`)
30
+ - Migration: first-run config file seeded from existing `pluginConfig` values
31
+ - `isDefaultApiUrl()` helper exported for local-vs-remote detection
32
+
5
33
  ## [0.6.5] - 2026-02-26
6
34
 
7
- ### Added — Thread tools + sourceThreadId linkage
35
+ ### Added - Thread tools + sourceThreadId linkage
8
36
 
9
37
  **Two new tools for conversation retrieval.** The plugin now provides 9 tools total.
10
38
 
11
- - `nowledge_mem_thread_search` — Search past conversations by keyword. Returns matched threads with message snippets and relevance scores. Use when the user asks about a past discussion or wants to find a specific conversation.
12
- - `nowledge_mem_thread_fetch` — Fetch full messages from a specific thread with pagination support. Use to progressively retrieve long conversations. Accepts `offset` and `limit` for paginated retrieval.
39
+ - `nowledge_mem_thread_search` - search past conversations by keyword. Returns matched threads with message snippets and relevance scores. Use when the user asks about a past discussion or wants to find a specific conversation.
40
+ - `nowledge_mem_thread_fetch` - fetch full messages from a specific thread with pagination support. Use to progressively retrieve long conversations. Accepts `offset` and `limit` for paginated retrieval.
13
41
 
14
42
  **Memories now link back to their source conversations.**
15
43
 
@@ -17,28 +45,28 @@ Every memory distilled from a conversation now includes `sourceThreadId` in sear
17
45
 
18
46
  **New client methods:**
19
47
 
20
- - `client.searchThreadsFull(query, { limit, source })` — Full-featured thread search (throws on error, supports source filter). CLI-first with API fallback.
21
- - `client.fetchThread(threadId, { offset, limit })` — Fetch messages from a thread with pagination. CLI-first with API fallback.
48
+ - `client.searchThreadsFull(query, { limit, source })` - full-featured thread search (throws on error, supports source filter). CLI-first with API fallback.
49
+ - `client.fetchThread(threadId, { offset, limit })` - fetch messages from a thread with pagination. CLI-first with API fallback.
22
50
 
23
51
  **Save deduplication.**
24
52
 
25
- Before saving, the plugin checks for near-identical existing memories (≥90% similarity). If a match is found, the save is skipped and the existing memory is returned — preventing obvious duplicates at the plugin level. Deeper semantic dedup is handled by the Knowledge Agent's EVOLVES chains in the background.
53
+ Before saving, the plugin checks for near-identical existing memories (>=90% similarity). If a match is found, the save is skipped and the existing memory is returned, preventing obvious duplicates at the plugin level. Deeper semantic dedup is handled by the Knowledge Agent's EVOLVES chains in the background.
26
54
 
27
55
  **Context-aware behavioral guidance.**
28
56
 
29
- The always-on hook now tells the agent about `nowledge_mem_thread_fetch` for following up on `sourceThreadId` links. When `sessionContext` is enabled, the guidance adjusts to note that relevant memories have already been injected — reducing redundant `memory_search` calls.
57
+ The always-on hook now tells the agent about `nowledge_mem_thread_fetch` for following up on `sourceThreadId` links. When `sessionContext` is enabled, the guidance adjusts to note that relevant memories have already been injected, reducing redundant `memory_search` calls.
30
58
 
31
59
  ## [0.6.4] - 2026-02-26
32
60
 
33
- ### Changed — Thread search enrichment + sessionDigest default on
61
+ ### Changed - Thread search enrichment + sessionDigest default on
34
62
 
35
63
  **`memory_search` now includes relevant conversation threads.**
36
64
 
37
- When you search memories, the response now also includes `relatedThreads` — snippets from past conversations that match the query. This uses the backend's BM25 thread search (`GET /threads/search`). Thread search is best-effort: if it fails or returns nothing, the memory results are returned as before. No new tool for the agent to learn — thread context surfaces automatically alongside memories.
65
+ When you search memories, the response now also includes `relatedThreads` - snippets from past conversations that match the query. This uses the backend's BM25 thread search (`GET /threads/search`). Thread search is best-effort: if it fails or returns nothing, the memory results are returned as before. No new tool for the agent to learn. Thread context surfaces automatically alongside memories.
38
66
 
39
67
  **`sessionDigest` now defaults to `true`.**
40
68
 
41
- Previously, session digest (thread capture + LLM distillation at session end) was off by default. The cost is negligible — one lightweight triage call per session end, full distillation only when worthwhile. With this change, conversations are captured and distilled automatically out of the box. Users who explicitly set `sessionDigest: false` are unaffected.
69
+ Previously, session digest (thread capture + LLM distillation at session end) was off by default. The cost is negligible: one lightweight triage call per session end, full distillation only when worthwhile. Conversations are now captured and distilled automatically out of the box. Users who explicitly set `sessionDigest: false` are unaffected.
42
70
 
43
71
  **Behavioral hook updated.**
44
72
 
@@ -46,12 +74,12 @@ The always-on guidance now mentions that `memory_search` returns conversation sn
46
74
 
47
75
  ### Added
48
76
 
49
- - `client.searchThreads(query, limit)` — calls `GET /threads/search` API. Returns top matching threads with message snippets and relevance scores. Best-effort: never throws.
77
+ - `client.searchThreads(query, limit)` - calls `GET /threads/search` API. Returns top matching threads with message snippets and relevance scores. Best-effort: never throws.
50
78
  - Version bumped to 0.6.4.
51
79
 
52
80
  ## [0.4.0] - 2026-02-26
53
81
 
54
- ### Changed — Rename config + always-on behavioral guidance
82
+ ### Changed - Rename config + always-on behavioral guidance
55
83
 
56
84
  **Renamed config options** (old names still work as silent aliases):
57
85
 
@@ -62,30 +90,30 @@ The always-on guidance now mentions that `memory_search` returns conversation sn
62
90
  | `captureMinInterval` | `digestMinInterval` |
63
91
  | `maxRecallResults` | `maxContextResults` |
64
92
 
65
- Existing configs with old names continue to work — aliases are resolved transparently in `parseConfig()`. New names take precedence if both are present.
93
+ Existing configs with old names continue to work. Aliases are resolved transparently in `parseConfig()`. New names take precedence if both are present.
66
94
 
67
95
  **Added: Always-on behavioral hook**
68
96
 
69
- The agent now receives brief behavioral guidance (~50 tokens) on every turn via `before_prompt_build`, telling it to proactively save and search. This fires in ALL modes, including tool-only (default). Previously, without `sessionContext` enabled, the agent had no system-level instruction to use memory tools — most LLMs ignored the "call proactively" hints in tool descriptions. This is the single most impactful change for memory adoption.
97
+ The agent now receives brief behavioral guidance (~50 tokens) on every turn via `before_prompt_build`, telling it to proactively save and search. This fires in ALL modes, including tool-only (default). Previously, without `sessionContext` enabled, the agent had no system-level instruction to use memory tools. Most LLMs ignored the "call proactively" hints in tool descriptions. This is the single most impactful change for memory adoption.
70
98
 
71
99
  **Migrated: `before_prompt_build` for session context**
72
100
 
73
- Session context injection (formerly "autoRecall") now uses the modern `before_prompt_build` hook instead of legacy `before_agent_start`. Both the behavioral hook and session context coexist — OpenClaw concatenates multiple `prependContext` values with `\n\n`.
101
+ Session context injection (formerly "autoRecall") now uses the modern `before_prompt_build` hook instead of legacy `before_agent_start`. Both the behavioral hook and session context coexist. OpenClaw concatenates multiple `prependContext` values with `\n\n`.
74
102
 
75
103
  ## [0.3.0] - 2026-02-23
76
104
 
77
- ### Changed — Architecture overhaul: tool-first, LLM-based capture
105
+ ### Changed - Architecture overhaul: tool-first, LLM-based capture
78
106
 
79
107
  **Breaking: `autoRecall` now defaults to `false`**
80
108
 
81
- The agent has full access to all 7 tools regardless of this setting. Tool-only mode (both `autoRecall` and `autoCapture` off) is now the recommended default. The agent calls `memory_search`, `nowledge_mem_save`, etc. on demand — no tokens wasted on irrelevant context injection.
109
+ The agent has full access to all 7 tools regardless of this setting. Tool-only mode (both `autoRecall` and `autoCapture` off) is now the recommended default. The agent calls `memory_search`, `nowledge_mem_save`, etc. on demand. No tokens wasted on irrelevant context injection.
82
110
 
83
111
  Users who explicitly set `autoRecall: true` are unaffected.
84
112
 
85
113
  **Removed: English-only heuristic capture**
86
114
 
87
115
  The entire rule-based capture pipeline has been removed:
88
- - `shouldCaptureAsMemory()` — English-only regex patterns (`/\bi (like|prefer|hate)\b/i`)
116
+ - `shouldCaptureAsMemory()` - English-only regex patterns (`/\bi (like|prefer|hate)\b/i`)
89
117
  - `MEMORY_TRIGGER_PATTERNS`, `PROMPT_INJECTION_PATTERNS`
90
118
  - `looksLikeQuestion()`, `hasMemoryTrigger()`, `looksLikePromptInjection()`
91
119
  - `fingerprint()`, per-session dedup map
@@ -96,9 +124,9 @@ These were fundamentally broken for non-English users (~95% of the world) and vi
96
124
 
97
125
  Replaced heuristics with a proper LLM-based pipeline:
98
126
 
99
- 1. **Thread capture** (unconditional, unchanged) — full conversation appended to persistent thread
100
- 2. **Triage** (cheap, fast) — lightweight LLM call (~50 output tokens) determines if conversation contains save-worthy content. Language-agnostic. New backend endpoint: `POST /memories/distill/triage`
101
- 3. **Distillation** (only when worthwhile) — full LLM extraction via existing `POST /memories/distill`, creating structured memories with proper unit_type, labels, and temporal data
127
+ 1. **Thread capture** (unconditional, unchanged) - full conversation appended to persistent thread
128
+ 2. **Triage** (cheap, fast) - lightweight LLM call (~50 output tokens) determines if conversation contains save-worthy content. Language-agnostic. New backend endpoint: `POST /memories/distill/triage`
129
+ 3. **Distillation** (only when worthwhile) - full LLM extraction via existing `POST /memories/distill`, creating structured memories with proper unit_type, labels, and temporal data
102
130
 
103
131
  Cost: negligible for conversations without save-worthy content (triage only). Moderate for rich conversations (triage + distill). Works in any language.
104
132
 
@@ -108,17 +136,17 @@ More directive description for tool-only mode: tells the agent when to proactive
108
136
 
109
137
  **Migrated: `before_prompt_build` hook**
110
138
 
111
- Auto-recall hook migrated from legacy `before_agent_start` to modern `before_prompt_build` API. Trimmed verbose tool guidance — the agent already sees tool descriptions in its tool list.
139
+ Auto-recall hook migrated from legacy `before_agent_start` to modern `before_prompt_build` API. Trimmed verbose tool guidance. The agent already sees tool descriptions in its tool list.
112
140
 
113
141
  ### Added
114
142
 
115
- - `client.triageConversation(content)` — calls `POST /memories/distill/triage`
116
- - `client.distillThread({ threadId, title, content })` — calls `POST /memories/distill`
143
+ - `client.triageConversation(content)` - calls `POST /memories/distill/triage`
144
+ - `client.distillThread({ threadId, title, content })` - calls `POST /memories/distill`
117
145
  - Backend `POST /memories/distill/triage` endpoint with lightweight LLM triage prompt
118
146
 
119
147
  ## [0.2.7] - 2026-02-18
120
148
 
121
- ### Added — Gap closures: date range, EVOLVES CLI, WM section edit
149
+ ### Added - Gap closures: date range, EVOLVES CLI, WM section edit
122
150
 
123
151
  **Feed date range filtering**
124
152
 
@@ -129,82 +157,82 @@ Auto-recall hook migrated from legacy `before_agent_start` to modern `before_pro
129
157
 
130
158
  **EVOLVES version chain via CLI**
131
159
 
132
- - `nmem g evolves <id>` — new CLI command showing the full EVOLVES chain for a memory (replaces/enriches/confirms/challenges relations).
133
- - `client.graphEvolves()` — new client method using `nmem g evolves`; falls back to `GET /agent/evolves?memory_id=<id>`.
160
+ - `nmem g evolves <id>` - new CLI command showing the full EVOLVES chain for a memory (replaces/enriches/confirms/challenges relations).
161
+ - `client.graphEvolves()` - new client method using `nmem g evolves`; falls back to `GET /agent/evolves?memory_id=<id>`.
134
162
  - `connections.js` now uses `client.graphEvolves()` instead of a raw API call. "Knowledge evolution" section now correctly shows direction (→ newer / ← older) relative to the queried memory.
135
163
  - Backend `GET /agent/evolves` now accepts `memory_id` query param to filter edges for a specific memory.
136
164
 
137
165
  **Working Memory section-level edit**
138
166
 
139
- - `nmem wm patch --heading "## Notes" --content/--append` — new CLI subcommand. Does client-side read-modify-write: reads WM, patches just the target section, writes the full doc back.
140
- - `client.patchWorkingMemory(heading, { content, append })` — new client method.
167
+ - `nmem wm patch --heading "## Notes" --content/--append` - new CLI subcommand. Does client-side read-modify-write: reads WM, patches just the target section, writes the full doc back.
168
+ - `client.patchWorkingMemory(heading, { content, append })` - new client method.
141
169
  - `nowledge_mem_context` now supports optional `patch_section` + `patch_content`/`patch_append` parameters. An agent can now update one section of Working Memory without destroying the rest.
142
170
  - Includes a JS `patchWmSection()` helper in `client.js` for the API fallback path (for CLI versions that predate `wm patch`).
143
171
 
144
172
  ## [0.2.6] - 2026-02-18
145
173
 
146
- ### Added — Rich save: labels, event_start, temporal_context, unit_type fixed
174
+ ### Added - Rich save: labels, event_start, temporal_context, unit_type fixed
147
175
 
148
176
  ## [0.2.5] - 2026-02-18
149
177
 
150
- ### Added — Remote mode configuration
178
+ ### Added - Remote mode configuration
151
179
 
152
180
  - `apiUrl` config option: set to your remote server URL to use Nowledge Mem across devices or in a team. Leave empty for local mode (default: `http://127.0.0.1:14242`).
153
- - `apiKey` config option: API key for remote access. Marked `"secret": true` in uiHints so OpenClaw can mask it in the UI. **Never logged, never passed as a CLI argument** — injected as `NMEM_API_KEY` env var into child processes only.
181
+ - `apiKey` config option: API key for remote access. Marked `"secret": true` in uiHints so OpenClaw can mask it in the UI. **Never logged, never passed as a CLI argument.** Injected as `NMEM_API_KEY` env var into child processes only.
154
182
  - `NowledgeMemClient` now accepts `{ apiUrl, apiKey }` credentials at construction time. Both config values and `NMEM_API_URL` / `NMEM_API_KEY` env vars are supported; plugin config wins over env vars.
155
183
  - Initialization log now shows `mode=remote → https://...` vs `mode=local` (key never appears in logs).
156
184
  - `_spawnEnv()` helper: builds per-spawn env with credentials injected; `_apiUrlArgs()` adds `--api-url` flag only when not local.
157
185
 
158
186
  ## [0.2.4] - 2026-02-18
159
187
 
160
- ### Changed — CLI-first architecture
188
+ ### Changed - CLI-first architecture
161
189
 
162
190
  All operations now go through the `nmem` CLI instead of direct API calls.
163
191
  This is a structural alignment that makes every feature work in remote mode
164
192
  (`NMEM_API_URL` + `NMEM_API_KEY`) without any plugin changes.
165
193
 
166
- - `client.graphExpand()` — uses `nmem g expand <id>`; falls back to API on older CLI
167
- - `client.feedEvents()` — uses `nmem f`; falls back to API on older CLI
168
- - `client.search()` — CLI now returns `relevance_reason`, `importance`, `labels`,
194
+ - `client.graphExpand()` - uses `nmem g expand <id>`; falls back to API on older CLI
195
+ - `client.feedEvents()` - uses `nmem f`; falls back to API on older CLI
196
+ - `client.search()` - CLI now returns `relevance_reason`, `importance`, `labels`,
169
197
  temporal fields natively; `searchRich()` is now an alias for `search()`
170
- - `client.searchTemporal()` — uses `nmem m search --event-from/--recorded-from`
198
+ - `client.searchTemporal()` - uses `nmem m search --event-from/--recorded-from`
171
199
  CLI args; falls back to API if CLI is pre-bi-temporal-update
172
- - `client._normalizeMemory()` — canonical memory shape shared across all search paths
200
+ - `client._normalizeMemory()` - canonical memory shape shared across all search paths
173
201
  - Removed `client.apiJson()` usage in `connections.js` and `timeline.js`
174
202
  - `connections.js`: `client.apiJson()` retained only for the `/agent/evolves` chain
175
- (no CLI command for this yet — tracked as future improvement)
203
+ (no CLI command for this yet, tracked as future improvement)
176
204
 
177
205
  ## [0.2.3] - 2026-02-18
178
206
 
179
- ### Changed — Graph Transparency & Scoring Visibility
207
+ ### Changed - Graph Transparency & Scoring Visibility
180
208
 
181
209
  - `nowledge_mem_connections`: completely rewritten output format
182
- - Edges are now JOIN-ed to their target nodes — each connection shows its relationship type and strength
210
+ - Edges are now JOIN-ed to their target nodes. Each connection shows its relationship type and strength
183
211
  - Sections organized by edge type: "Synthesized from N source memories" (CRYSTALLIZED_FROM), "Knowledge evolution" (EVOLVES), "Sourced from document" (SOURCED_FROM), "Entities mentioned" (MENTIONS)
184
212
  - EVOLVES sub-relations are labeled: supersedes, enriches, confirms, challenges
185
213
  - Each connected memory includes its `id` for direct follow-up with `memory_get` or `nowledge_mem_connections`
186
- - `memory_search`: now always uses API path (`searchRich`) — returns `matchedVia` with scoring breakdown
214
+ - `memory_search`: now always uses API path (`searchRich`), returns `matchedVia` with scoring breakdown
187
215
  - e.g. `"matchedVia": "Text Match (100%) + Semantic Match (69%) | decay[imp:high]"`
188
216
  - Also returns `importance` per result
189
217
  - Response `mode` field updated to `"multi-signal"` to reflect actual behavior
190
- - `nowledge_mem_timeline`: timeline entries now include `(id: <memoryId>)` hint for events with linked memories — enables immediate chaining to `nowledge_mem_connections`
218
+ - `nowledge_mem_timeline`: timeline entries now include `(id: <memoryId>)` hint for events with linked memories, enables immediate chaining to `nowledge_mem_connections`
191
219
  - `nowledge_mem_timeline`: `event_type` filter values documented in tool description for model discoverability
192
- - Auto-recall hook: uses `searchRich` instead of CLI search — shows scoring breakdown in recalled context
220
+ - Auto-recall hook: uses `searchRich` instead of CLI search, shows scoring breakdown in recalled context
193
221
 
194
222
  ### Added
195
223
 
196
- - `client.searchRich()` — convenience wrapper for `searchTemporal` without temporal filters; always returns `relevanceReason`
224
+ - `client.searchRich()` - convenience wrapper for `searchTemporal` without temporal filters; always returns `relevanceReason`
197
225
 
198
226
  ## [0.2.2] - 2026-02-18
199
227
 
200
228
  ### Added
201
229
 
202
230
  - `memory_search` now supports bi-temporal filtering:
203
- - `event_date_from` / `event_date_to` — when the fact/event happened (YYYY, YYYY-MM, YYYY-MM-DD)
204
- - `recorded_date_from` / `recorded_date_to` — when the memory was saved to Nowledge Mem
231
+ - `event_date_from` / `event_date_to` - when the fact/event happened (YYYY, YYYY-MM, YYYY-MM-DD)
232
+ - `recorded_date_from` / `recorded_date_to` - when the memory was saved to Nowledge Mem
205
233
  - Uses API-direct path (`client.searchTemporal`) so it works regardless of installed CLI version
206
234
  - Results include `eventStart`, `eventEnd`, `temporalContext` when available
207
- - `client.searchTemporal()` — new method wrapping `/memories/search` bi-temporal API directly
235
+ - `client.searchTemporal()` - new method wrapping `/memories/search` bi-temporal API directly
208
236
 
209
237
  ## [0.2.1] - 2026-02-18
210
238
 
@@ -216,7 +244,7 @@ This is a structural alignment that makes every feature work in remote mode
216
244
 
217
245
  ## [0.2.0] - 2026-02-18
218
246
 
219
- ### Changed — Tool Set Redesign
247
+ ### Changed - Tool Set Redesign
220
248
 
221
249
  Redesigned from first principles around Nowledge Mem's v0.6 architecture.
222
250
  This version reflects our genuine strengths: knowledge graph, structured types,
@@ -224,9 +252,9 @@ Working Memory, and cross-AI continuity.
224
252
 
225
253
  ### Added
226
254
 
227
- - `nowledge_mem_save` tool: structured knowledge capture with `unit_type` parameter (fact, preference, decision, plan, procedure, learning, context, event) — replaces generic `nowledge_mem_store`
228
- - `nowledge_mem_context` tool: read today's Working Memory daily briefing — replaces `nowledge_mem_working_memory` with clearer naming
229
- - `nowledge_mem_connections` tool: explore knowledge graph around a topic — returns connected memories, EVOLVES chains, related entities, and Source document provenance (SOURCED_FROM edges from Library). This is our graph-native differentiator.
255
+ - `nowledge_mem_save` tool: structured knowledge capture with `unit_type` parameter (fact, preference, decision, plan, procedure, learning, context, event). Replaces generic `nowledge_mem_store`
256
+ - `nowledge_mem_context` tool: read today's Working Memory daily briefing. Replaces `nowledge_mem_working_memory` with clearer naming
257
+ - `nowledge_mem_connections` tool: explore knowledge graph around a topic. Returns connected memories, EVOLVES chains, related entities, and Source document provenance (SOURCED_FROM edges from Library). This is our graph-native differentiator.
230
258
  - `nowledge_mem_forget` tool: delete memories by ID or search query with confirmation flow
231
259
  - `/forget` slash command: quick memory deletion from chat
232
260
  - Capture quality gate: prompt injection detection, question filtering, memory-trigger pattern matching
package/CLAUDE.md CHANGED
@@ -1,4 +1,4 @@
1
- # CLAUDE.md — Nowledge Mem OpenClaw Plugin
1
+ # CLAUDE.md - Nowledge Mem OpenClaw Plugin
2
2
 
3
3
  Continuation guide for `community/nowledge-mem-openclaw-plugin`.
4
4
 
@@ -7,86 +7,94 @@ Continuation guide for `community/nowledge-mem-openclaw-plugin`.
7
7
  - Plugin target: OpenClaw plugin runtime (memory slot provider)
8
8
  - Runtime: JS ESM modules under `src/`, no TS build pipeline
9
9
  - Memory backend: `nmem` CLI (fallback: `uvx --from nmem-cli nmem`)
10
- - Architecture: **CLI-first** — all operations go through the nmem CLI, not direct API calls
11
- - Remote mode: set `NMEM_API_URL` + `NMEM_API_KEY` env vars or plugin config `apiUrl` + `apiKey`
10
+ - Architecture: **CLI-first** - all operations go through the nmem CLI, not direct API calls
11
+ - Remote mode: set `NMEM_API_URL` + `NMEM_API_KEY` env vars or config file `apiUrl` + `apiKey`
12
12
 
13
13
  ## Design Philosophy
14
14
 
15
15
  Reflects Nowledge Mem's genuine v0.6 strengths:
16
16
 
17
- 1. **Knowledge graph** — EVOLVES chains, entity relationships, typed memory connections
18
- 2. **Source provenance** — Library ingests docs/URLs; `SOURCED_FROM` edges trace knowledge origin
19
- 3. **Structured types** — 8 unit types: `fact | preference | decision | plan | procedure | learning | context | event`
20
- 4. **Working Memory** — daily evolving briefing; agents can patch sections without overwriting the whole document
21
- 5. **Cross-AI continuity** — "Your AI tools forget. We remember. Everywhere."
22
- 6. **Hybrid search** — BM25 + semantic + graph + decay, not vector-only
23
- 7. **Transparent scoring** — `relevance_reason` in every search result
17
+ 1. **Knowledge graph** - EVOLVES chains, entity relationships, typed memory connections
18
+ 2. **Source provenance** - Library ingests docs/URLs; `SOURCED_FROM` edges trace knowledge origin
19
+ 3. **Structured types** - 8 unit types: `fact | preference | decision | plan | procedure | learning | context | event`
20
+ 4. **Working Memory** - daily evolving briefing; agents can patch sections without overwriting the whole document
21
+ 5. **Cross-AI continuity** - "Your AI tools forget. We remember. Everywhere."
22
+ 6. **Hybrid search** - BM25 + semantic + graph + decay, not vector-only
23
+ 7. **Transparent scoring** - `relevance_reason` in every search result
24
24
 
25
25
  ## Files That Matter
26
26
 
27
27
  ```
28
28
  src/
29
- index.js — plugin registration (tools, hooks, commands, CLI)
30
- client.js — CLI wrapper with API fallback; credential handling
31
- config.js — strict config parsing (apiUrl, apiKey, sessionContext, etc.)
29
+ index.js - plugin registration (tools, hooks, commands, CLI)
30
+ client.js - CLI wrapper with API fallback; credential handling
31
+ config.js - config cascade: pluginConfig > ~/.nowledge-mem/openclaw.json > env vars > defaults
32
32
  hooks/
33
- behavioral.js — always-on behavioral guidance (~50 tokens/turn)
34
- recall.js — before_prompt_build: inject Working Memory + recalled memories
35
- capture.js — thread capture + LLM triage/distillation at session lifecycle events
33
+ behavioral.js - always-on behavioral guidance (~50 tokens/turn)
34
+ recall.js - before_prompt_build: inject Working Memory + recalled memories
35
+ capture.js - thread capture + LLM triage/distillation at session lifecycle events
36
36
  tools/
37
- memory-search.js — OpenClaw compat; multi-signal; bi-temporal; relevance_reason; sourceThreadId
38
- memory-get.js — OpenClaw compat; supports MEMORY.md alias; sourceThreadId
39
- save.js — structured capture: unit_type, labels, temporal, importance; pre-save dedup check
40
- context.js — Working Memory daily briefing (read + section-level patch)
41
- connections.js — graph exploration: edge types, relationship strength, provenance
42
- timeline.js — activity feed: daily grouping, event_type filter, memoryId hints
43
- forget.js — memory deletion by ID or search
44
- thread-search.js — search past conversations by keyword
45
- thread-fetch.js — fetch full messages from a thread with pagination
46
- openclaw.plugin.json — manifest + config schema (version, uiHints, configSchema)
37
+ memory-search.js - OpenClaw compat; multi-signal; bi-temporal; relevance_reason; sourceThreadId
38
+ memory-get.js - OpenClaw compat; supports MEMORY.md alias; sourceThreadId
39
+ save.js - structured capture: unit_type, labels, temporal, importance; pre-save dedup check
40
+ context.js - Working Memory daily briefing (read + section-level patch)
41
+ connections.js - graph exploration: edge types, relationship strength, provenance
42
+ timeline.js - activity feed: daily grouping, event_type filter, memoryId hints
43
+ forget.js - memory deletion by ID or search
44
+ thread-search.js - search past conversations by keyword
45
+ thread-fetch.js - fetch full messages from a thread with pagination
46
+ openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema)
47
+ ~/.nowledge-mem/openclaw.json - user config file (auto-created on first run)
47
48
  ```
48
49
 
49
50
  ## Tool Surface (9 tools)
50
51
 
51
52
  ### OpenClaw Memory Slot (required for system prompt activation)
52
- - `memory_search` — multi-signal: BM25 + embedding + label + graph + decay. Returns `matchedVia` ("Text Match 100% + Semantic 69%"), `importance`, bi-temporal filters (`event_date_from/to`, `recorded_date_from/to`). Also returns `relatedThreads` (past conversation snippets matching the query) and `sourceThreadId` (link to source conversation). Mode: `"multi-signal"`.
53
- - `memory_get` — retrieve by `nowledgemem://memory/<id>` path or bare ID. `MEMORY.md` → Working Memory. Returns `sourceThreadId` when available.
53
+ - `memory_search` - multi-signal: BM25 + embedding + label + graph + decay. Returns `matchedVia` ("Text Match 100% + Semantic 69%"), `importance`, bi-temporal filters (`event_date_from/to`, `recorded_date_from/to`). Also returns `relatedThreads` (past conversation snippets matching the query) and `sourceThreadId` (link to source conversation). Mode: `"multi-signal"`.
54
+ - `memory_get` - retrieve by `nowledgemem://memory/<id>` path or bare ID. `MEMORY.md` → Working Memory. Returns `sourceThreadId` when available.
54
55
 
55
56
  ### Nowledge Mem Native (differentiators)
56
- - `nowledge_mem_save` — structured capture: `unit_type`, `labels[]`, `event_start`, `event_end`, `temporal_context`, `importance`. All fields wired to CLI and API.
57
- - `nowledge_mem_context` — Working Memory daily briefing. Read-only by default; supports section-level patch via `patch_section` + `patch_content`/`patch_append` params (uses `nmem wm patch` client-side read-modify-write).
58
- - `nowledge_mem_connections` — graph exploration. Edges JOIN-ed to nodes by type: CRYSTALLIZED_FROM (crystal → source memories), EVOLVES (with sub-relations: supersedes/enriches/confirms/challenges), SOURCED_FROM (document provenance), MENTIONS (entities). Each connection shows strength % and memoryId.
59
- - `nowledge_mem_timeline` — activity feed via `nmem f`. Groups by day. `event_type` filter. Exact date range via `date_from`/`date_to` (YYYY-MM-DD). Entries include `(id: <memoryId>)` for chaining to connections.
60
- - `nowledge_mem_forget` — delete by ID or fuzzy query.
57
+ - `nowledge_mem_save` - structured capture: `unit_type`, `labels[]`, `event_start`, `event_end`, `temporal_context`, `importance`. All fields wired to CLI and API.
58
+ - `nowledge_mem_context` - Working Memory daily briefing. Read-only by default; supports section-level patch via `patch_section` + `patch_content`/`patch_append` params (uses `nmem wm patch` client-side read-modify-write).
59
+ - `nowledge_mem_connections` - graph exploration. Edges JOIN-ed to nodes by type: CRYSTALLIZED_FROM (crystal → source memories), EVOLVES (with sub-relations: supersedes/enriches/confirms/challenges), SOURCED_FROM (document provenance), MENTIONS (entities). Each connection shows strength % and memoryId.
60
+ - `nowledge_mem_timeline` - activity feed via `nmem f`. Groups by day. `event_type` filter. Exact date range via `date_from`/`date_to` (YYYY-MM-DD). Entries include `(id: <memoryId>)` for chaining to connections.
61
+ - `nowledge_mem_forget` - delete by ID or fuzzy query.
61
62
 
62
63
  ### Thread Tools (progressive conversation retrieval)
63
- - `nowledge_mem_thread_search` — search past conversations by keyword. Returns threads with matched message snippets, relevance scores, and message counts. Supports `source` filter.
64
- - `nowledge_mem_thread_fetch` — fetch full messages from a specific thread. Supports pagination via `offset` + `limit` for progressive retrieval of long conversations.
64
+ - `nowledge_mem_thread_search` - search past conversations by keyword. Returns threads with matched message snippets, relevance scores, and message counts. Supports `source` filter.
65
+ - `nowledge_mem_thread_fetch` - fetch full messages from a specific thread. Supports pagination via `offset` + `limit` for progressive retrieval of long conversations.
65
66
 
66
67
  ## Hook Surface
67
68
 
68
- - `before_prompt_build` (always-on) — behavioral guidance: brief note telling agent to save/search proactively. Adjusts when sessionContext is on to avoid redundant searches.
69
- - `before_prompt_build` (sessionContext) — session context: Working Memory + `searchRich()` with `relevanceReason` in context
70
- - `agent_end` — thread capture + LLM triage/distillation (requires `sessionDigest: true`)
71
- - `after_compaction` — thread append
72
- - `before_reset` — thread append
69
+ - `before_prompt_build` (always-on) - behavioral guidance: brief note telling agent to save/search proactively. Adjusts when sessionContext is on to avoid redundant searches.
70
+ - `before_prompt_build` (sessionContext) - session context: Working Memory + `searchRich()` with `relevanceReason` in context
71
+ - `agent_end` - thread capture + LLM triage/distillation (requires `sessionDigest: true`)
72
+ - `after_compaction` - thread append
73
+ - `before_reset` - thread append
73
74
 
74
75
  ## Slash Commands
75
76
 
76
- - `/remember` — quick save
77
- - `/recall` — quick search
78
- - `/forget` — quick delete
77
+ - `/remember` - quick save
78
+ - `/recall` - quick search
79
+ - `/forget` - quick delete
79
80
 
80
81
  ## Config Keys
81
82
 
82
- | Key | Type | Default | Description |
83
- |-----|------|---------|-------------|
84
- | `sessionContext` | boolean | `false` | Inject Working Memory + relevant memories at prompt time |
85
- | `sessionDigest` | boolean | `true` | Thread capture + LLM distillation at session end |
86
- | `maxContextResults` | integer 1–20 | `5` | How many memories to inject at prompt time |
87
- | `digestMinInterval` | integer | `300` | Minimum seconds between session digests |
88
- | `apiUrl` | string | `""` | Remote server URL. Empty = local (127.0.0.1:14242) |
89
- | `apiKey` | string | `""` | API key. Injected as `NMEM_API_KEY` env var only. Never logged. |
83
+ Config file at `~/.nowledge-mem/openclaw.json` (auto-created on first run). Also supports env vars and pluginConfig.
84
+
85
+ | Key | Type | Default | Env Var | Description |
86
+ |-----|------|---------|---------|-------------|
87
+ | `sessionContext` | boolean | `false` | `NMEM_SESSION_CONTEXT` | Inject Working Memory + relevant memories at prompt time |
88
+ | `sessionDigest` | boolean | `true` | `NMEM_SESSION_DIGEST` | Thread capture + LLM distillation at session end |
89
+ | `digestMinInterval` | integer 0-86400 | `300` | `NMEM_DIGEST_MIN_INTERVAL` | Minimum seconds between session digests |
90
+ | `maxContextResults` | integer 1-20 | `5` | `NMEM_MAX_CONTEXT_RESULTS` | How many memories to inject at prompt time |
91
+ | `apiUrl` | string | `""` | `NMEM_API_URL` | Remote server URL. Empty = local (127.0.0.1:14242) |
92
+ | `apiKey` | string | `""` | `NMEM_API_KEY` | API key. Never logged. |
93
+
94
+ **Priority**: pluginConfig > config file > env var > default.
95
+
96
+ Legacy aliases accepted silently in all sources for backward compat:
97
+ `autoRecall`→`sessionContext`, `autoCapture`→`sessionDigest`, `captureMinInterval`→`digestMinInterval`, `maxRecallResults`→`maxContextResults`.
90
98
 
91
99
  ### Credential Handling Rules
92
100
  - `apiKey` → ONLY via child process env (`NMEM_API_KEY`). Never CLI arg, never logged.
@@ -136,15 +144,15 @@ NMEM_API_URL=https://your-server NMEM_API_KEY=key nmem status
136
144
 
137
145
  ## Known Gaps / Accepted Limitations
138
146
 
139
- 1. **Feed API `date_from`/`date_to`** — supported. Backend filters events by YYYY-MM-DD range. CLI: `nmem f --from/--to`. Plugin: `nowledge_mem_timeline` accepts `date_from`/`date_to`.
140
- 2. **Working Memory section edit** — supported. `nmem wm patch --heading "## X" --content/--append` does client-side read-modify-write. Plugin: `nowledge_mem_context` accepts `patch_section` + `patch_content`/`patch_append`.
141
- 3. **EVOLVES chain CLI** — supported. `nmem g evolves <id>` calls `/agent/evolves?memory_id=<id>`. Plugin: `nowledge_mem_connections` uses `client.graphEvolves()`.
142
- 4. **`unit_type` requires rebuilt backend** — `MemoryCreateRequest` includes `unit_type` (fixed). Restart backend after rebuild.
143
- 5. **Working Memory full-overwrite only via API** — the API (`PUT /agent/working-memory`) still takes full content. The section-level patch is implemented purely client-side. This is acceptable; the Knowledge Agent regenerates WM each morning anyway.
147
+ 1. **Feed API `date_from`/`date_to`** - supported. Backend filters events by YYYY-MM-DD range. CLI: `nmem f --from/--to`. Plugin: `nowledge_mem_timeline` accepts `date_from`/`date_to`.
148
+ 2. **Working Memory section edit** - supported. `nmem wm patch --heading "## X" --content/--append` does client-side read-modify-write. Plugin: `nowledge_mem_context` accepts `patch_section` + `patch_content`/`patch_append`.
149
+ 3. **EVOLVES chain CLI** - supported. `nmem g evolves <id>` calls `/agent/evolves?memory_id=<id>`. Plugin: `nowledge_mem_connections` uses `client.graphEvolves()`.
150
+ 4. **`unit_type` requires rebuilt backend** - `MemoryCreateRequest` includes `unit_type` (fixed). Restart backend after rebuild.
151
+ 5. **Working Memory full-overwrite only via API** - the API (`PUT /agent/working-memory`) still takes full content. The section-level patch is implemented purely client-side. This is acceptable; the Knowledge Agent regenerates WM each morning anyway.
144
152
 
145
153
  ## Non-Goals
146
154
 
147
- - Do NOT add `nowledge_mem_search` — `memory_search` covers it.
148
- - Do NOT expose full WM overwrite from agents — section-level patch is the right granularity.
155
+ - Do NOT add `nowledge_mem_search` - `memory_search` covers it.
156
+ - Do NOT expose full WM overwrite from agents - section-level patch is the right granularity.
149
157
  - Do NOT add cloud dependencies to the core path.
150
158
  - Do NOT accept unknown config keys (strict parser in `config.js`).
package/README.md CHANGED
@@ -17,7 +17,7 @@ openclaw plugins install @nowledge/openclaw-nowledge-mem
17
17
 
18
18
  ### Local mode (default)
19
19
 
20
- Start Nowledge Mem desktop app or run `nmem serve`, then configure:
20
+ Start Nowledge Mem desktop app or run `nmem serve`, then enable the plugin:
21
21
 
22
22
  ```json
23
23
  {
@@ -36,26 +36,18 @@ That's it. The agent gets 9 tools and calls them on demand. No extra tokens wast
36
36
 
37
37
  ### Remote mode
38
38
 
39
- Connect to a Nowledge Mem server running elsewhere — on a VPS, a home server, or shared team instance. See [remote access guide](https://mem.nowledge.co/docs/remote-access) for server setup.
39
+ Connect to a Nowledge Mem server running elsewhere (a VPS, a home server, or a shared team instance). See [remote access guide](https://mem.nowledge.co/docs/remote-access) for server setup.
40
+
41
+ Enable the plugin the same way as local mode, then set your remote server in `~/.nowledge-mem/openclaw.json`:
40
42
 
41
43
  ```json
42
44
  {
43
- "plugins": {
44
- "slots": { "memory": "openclaw-nowledge-mem" },
45
- "entries": {
46
- "openclaw-nowledge-mem": {
47
- "enabled": true,
48
- "config": {
49
- "apiUrl": "https://nowledge.example.com",
50
- "apiKey": "your-api-key-here"
51
- }
52
- }
53
- }
54
- }
45
+ "apiUrl": "https://nowledge.example.com",
46
+ "apiKey": "your-api-key-here"
55
47
  }
56
48
  ```
57
49
 
58
- The `apiKey` is injected as `NMEM_API_KEY` into the nmem CLI process — never passed as a CLI argument, never logged.
50
+ The `apiKey` is injected as `NMEM_API_KEY` into the nmem CLI process. Never passed as a CLI argument, never logged.
59
51
 
60
52
  ## How It Works
61
53
 
@@ -114,7 +106,7 @@ flowchart TD
114
106
 
115
107
  C --> F{"sessionDigest enabled+ enough messages+ cooldown passed?"}
116
108
  F -->|"Yes"| G["LLM triage (~100 tokens):worth distilling?"]
117
- F -->|"No"| H["Done — thread saved"]
109
+ F -->|"No"| H["Done - thread saved"]
118
110
  G -->|"Decisions, insights,preferences found"| I["Full distillation ->structured memories(with sourceThreadId)"]
119
111
  G -->|"Routine chat"| H
120
112
 
@@ -123,7 +115,7 @@ flowchart TD
123
115
  ```
124
116
 
125
117
  **Key points:**
126
- - Thread capture is **unconditional** — every conversation is saved and searchable via `nowledge_mem_thread_search`
118
+ - Thread capture is unconditional: every conversation is saved and searchable via `nowledge_mem_thread_search`
127
119
  - LLM distillation only runs at `agent_end`, not during compaction/reset checkpoints
128
120
  - Distilled memories carry `sourceThreadId`, linking them back to the source conversation
129
121
  - Cooldown (`digestMinInterval`, default 300s) prevents burst distillation
@@ -151,8 +143,8 @@ flowchart LR
151
143
  ```
152
144
 
153
145
  Two entry points:
154
- 1. **From a memory** — `memory_search` or `memory_get` returns `sourceThreadId` -> fetch the source conversation
155
- 2. **Direct search** — `nowledge_mem_thread_search` finds conversations by keyword -> fetch any result
146
+ 1. **From a memory**: `memory_search` or `memory_get` returns `sourceThreadId`, then fetch the source conversation
147
+ 2. **Direct search**: `nowledge_mem_thread_search` finds conversations by keyword, then fetch any result
156
148
 
157
149
  ### Three Modes at a Glance
158
150
 
@@ -186,15 +178,15 @@ flowchart LR
186
178
 
187
179
  These satisfy the OpenClaw memory slot contract and activate the "Memory Recall" section in OpenClaw's system prompt.
188
180
 
189
- **memory_search** — Multi-signal recall using embedding, BM25, label match, graph signals, and recency decay. Returns structured source paths (`nowledgemem://memory/<id>`) for follow-up with `memory_get` or `nowledge_mem_connections`. Also returns relevant past conversation snippets (`relatedThreads`) and `sourceThreadId` (link to the conversation the memory was distilled from) when available.
181
+ **memory_search** - Multi-signal recall using embedding, BM25, label match, graph signals, and recency decay. Returns structured source paths (`nowledgemem://memory/<id>`) for follow-up with `memory_get` or `nowledge_mem_connections`. Also returns relevant past conversation snippets (`relatedThreads`) and `sourceThreadId` (link to the conversation the memory was distilled from) when available.
190
182
 
191
- **memory_get** — Read a specific memory by ID or path. Supports `MEMORY.md` alias for Working Memory. Returns `sourceThreadId` when the memory was distilled from a conversation.
183
+ **memory_get** - Read a specific memory by ID or path. Supports `MEMORY.md` alias for Working Memory. Returns `sourceThreadId` when the memory was distilled from a conversation.
192
184
 
193
185
  ### Nowledge Mem Native
194
186
 
195
187
  These reflect capabilities unique to Nowledge Mem's knowledge graph architecture.
196
188
 
197
- **nowledge_mem_save** — Capture structured knowledge with type classification, labels, and temporal context.
189
+ **nowledge_mem_save** - Capture structured knowledge with type classification, labels, and temporal context.
198
190
 
199
191
  ```
200
192
  text: "We decided to use PostgreSQL with JSONB for the task events table"
@@ -204,18 +196,18 @@ importance: 0.8
204
196
  labels: ["backend", "architecture"]
205
197
  event_start: 2024-03
206
198
  temporal_context: past
207
- → Saved: Task events database choice [decision] (id: mem_abc) · labels: backend, architecture · event: 2024-03
199
+ > Saved: Task events database choice [decision] (id: mem_abc) - labels: backend, architecture - event: 2024-03
208
200
  ```
209
201
 
210
- Eight memory types: `fact`, `preference`, `decision`, `plan`, `procedure`, `learning`, `context`, `event` — each becomes a typed node in the knowledge graph. Labels enable filtering in `memory_search`. `event_start` records *when* something happened, not just when you saved it — powering bi-temporal search.
202
+ Eight memory types: `fact`, `preference`, `decision`, `plan`, `procedure`, `learning`, `context`, `event`. Each becomes a typed node in the knowledge graph. Labels enable filtering in `memory_search`. `event_start` records *when* something happened, not just when you saved it, powering bi-temporal search.
211
203
 
212
- **nowledge_mem_context** — Read today's Working Memory: focus areas, priorities, unresolved flags, and recent activity. Generated by the Knowledge Agent each morning, updated throughout the day.
204
+ **nowledge_mem_context** - Read today's Working Memory: focus areas, priorities, unresolved flags, and recent activity. Generated by the Knowledge Agent each morning, updated throughout the day.
213
205
 
214
- **nowledge_mem_connections** — Explore the knowledge graph around a topic or memory. Returns connected memories, EVOLVES version chains (how understanding has grown), related entities, and source document provenance (which files or URLs knowledge was extracted from).
206
+ **nowledge_mem_connections** - Explore the knowledge graph around a topic or memory. Returns connected memories, EVOLVES version chains (how understanding has grown), related entities, and source document provenance (which files or URLs knowledge was extracted from).
215
207
 
216
208
  ```
217
209
  memoryId: "mem_abc"
218
- → Connected memories:
210
+ > Connected memories:
219
211
  - PostgreSQL optimization patterns: Use JSONB GIN indexes for...
220
212
  - Redis caching layer decision: For frequently accessed task lists...
221
213
  Source documents (provenance):
@@ -227,25 +219,25 @@ memoryId: "mem_abc"
227
219
  - superseded by newer understanding (version chain)
228
220
  ```
229
221
 
230
- **nowledge_mem_timeline** — Browse your knowledge history chronologically. Use for questions like "what was I working on last week?" or "what happened yesterday?". Groups activity by day: memories saved, documents ingested, insights generated, and more.
222
+ **nowledge_mem_timeline** - Browse your knowledge history chronologically. Use for questions like "what was I working on last week?" or "what happened yesterday?". Groups activity by day: memories saved, documents ingested, insights generated, and more.
231
223
 
232
224
  ```
233
225
  last_n_days: 7
234
- → 2026-02-18:
235
- - [Memory saved] UV guide — Python toolchain setup
226
+ > 2026-02-18:
227
+ - [Memory saved] UV guide - Python toolchain setup
236
228
  - [Knowledge extracted from document] api-spec.pdf
237
229
  2026-02-17:
238
230
  - [Daily briefing] Focus: NebulaGraph, AI biotech...
239
231
  - [Insight] Connection between Redis caching and...
240
232
  ```
241
233
 
242
- **nowledge_mem_forget** — Delete a memory by ID or search query. Supports user confirmation when multiple matches are found.
234
+ **nowledge_mem_forget** - Delete a memory by ID or search query. Supports user confirmation when multiple matches are found.
243
235
 
244
236
  ### Thread Tools
245
237
 
246
- **nowledge_mem_thread_search** — Search past conversations by keyword. Returns matched threads with message snippets and relevance scores. Use when the user asks about a past discussion or wants to find a conversation from a specific time.
238
+ **nowledge_mem_thread_search** - Search past conversations by keyword. Returns matched threads with message snippets and relevance scores. Use when the user asks about a past discussion or wants to find a conversation from a specific time.
247
239
 
248
- **nowledge_mem_thread_fetch** — Fetch full messages from a conversation thread with pagination. Pass a `sourceThreadId` from memory results or a `threadId` from thread search. Supports `offset` and `limit` for progressive retrieval of long conversations.
240
+ **nowledge_mem_thread_fetch** - Fetch full messages from a conversation thread with pagination. Pass a `sourceThreadId` from memory results or a `threadId` from thread search. Supports `offset` and `limit` for progressive retrieval of long conversations.
249
241
 
250
242
  ```
251
243
  threadId: "openclaw-db-arch-a1b2c3"
@@ -262,19 +254,19 @@ The plugin supports three modes. Choose based on how much you want to guarantee
262
254
 
263
255
  | Mode | Config | Behavior | Tradeoff |
264
256
  |------|--------|----------|----------|
265
- | **Default** (recommended) | `sessionContext: false, sessionDigest: true` | Agent calls 9 tools on demand. Behavioral guidance every turn (~50 tokens). Conversations captured + distilled at session end. | Lowest cost. Agent decides when to search — usually smart, occasionally forgets. |
257
+ | **Default** (recommended) | `sessionContext: false, sessionDigest: true` | Agent calls 9 tools on demand. Behavioral guidance every turn (~50 tokens). Conversations captured + distilled at session end. | Lowest cost. Agent decides when to search, usually smart, occasionally forgets. |
266
258
  | **Session context** | `sessionContext: true` | Working Memory + relevant memories injected at prompt time, plus all 9 tools still available. | ~1-2 KB/turn. Guarantees context is always present. Best for short sessions or critical workflows. |
267
259
  | **Minimal** | `sessionDigest: false` | Tool-only, no automatic capture. | Zero overhead beyond guidance. Use when you handle memory manually. |
268
260
 
269
261
  **Which mode should you use?**
270
262
 
271
263
  - **Most users**: start with default. The agent gets behavioral guidance nudging it to search before answering and save after deciding. It works well for most conversations.
272
- - **Short sessions or critical accuracy**: enable `sessionContext`. This guarantees relevant memories are present from the first turn — the agent doesn't need to decide whether to search. The tradeoff is ~1-2 KB of context per turn.
264
+ - **Short sessions or critical accuracy**: enable `sessionContext`. This guarantees relevant memories are present from the first turn. The agent doesn't need to decide whether to search. The tradeoff is ~1-2 KB of context per turn.
273
265
  - **Full manual control**: set `sessionDigest: false`. You control what gets saved (via `/remember` or `nowledge_mem_save`) and nothing is captured automatically.
274
266
 
275
267
  ### Session Context (`sessionContext`, default: false)
276
268
 
277
- When enabled, the plugin injects Working Memory and relevant search results at prompt time. The behavioral guidance automatically adjusts to tell the agent that context is already present — use `memory_search` only for specific follow-up queries, not broad recall. This prevents the agent from redundantly searching for the same context that was just injected.
269
+ When enabled, the plugin injects Working Memory and relevant search results at prompt time. The behavioral guidance automatically adjusts to tell the agent that context is already present, so `memory_search` should only be used for specific follow-up queries, not broad recall. This prevents the agent from redundantly searching for the same context that was just injected.
278
270
 
279
271
  Useful for giving the agent immediate context without waiting for it to search proactively.
280
272
 
@@ -284,7 +276,7 @@ When enabled, two things happen at `agent_end`, `after_compaction`, and `before_
284
276
 
285
277
  **1. Thread capture (always).** The full conversation is appended to a persistent thread. Unconditional, idempotent by message ID.
286
278
 
287
- **2. LLM distillation (when worthwhile).** A lightweight LLM triage determines if the conversation has save-worthy content. If yes, a full distillation pass creates structured memories with types, labels, and temporal data. Language-agnostic — works in any language.
279
+ **2. LLM distillation (when worthwhile).** A lightweight LLM triage determines if the conversation has save-worthy content. If yes, a full distillation pass creates structured memories with types, labels, and temporal data. Language-agnostic, works in any language.
288
280
 
289
281
  ## Design Decisions
290
282
 
@@ -292,23 +284,23 @@ Honest answers to common questions about how the memory system works.
292
284
 
293
285
  **Does the agent always search before answering?**
294
286
 
295
- No. The behavioral guidance nudges the agent to search, but doesn't force it. This is a deliberate tradeoff — forcing a search on every turn would add latency and cost for messages that don't need past context (like "hello" or "thanks"). In practice, modern LLMs follow behavioral guidance reliably for knowledge-related questions. If guaranteed recall matters for your use case, enable `sessionContext: true` — that injects relevant memories at prompt time, before the agent even processes your message.
287
+ No. The behavioral guidance nudges the agent to search, but doesn't force it. This is a deliberate tradeoff: forcing a search on every turn would add latency and cost for messages that don't need past context (like "hello" or "thanks"). In practice, modern LLMs follow behavioral guidance reliably for knowledge-related questions. If guaranteed recall matters for your use case, enable `sessionContext: true`. That injects relevant memories at prompt time, before the agent even processes your message.
296
288
 
297
289
  **What stops the agent from saving duplicate memories?**
298
290
 
299
- Two layers. First, the plugin checks for near-identical existing memories before every save. If a memory with ≥90% similarity already exists, the save is skipped and the existing memory is returned instead. Second, Nowledge Mem's Knowledge Agent runs in the background and handles deeper deduplication — it identifies semantic overlap across memories and links them via EVOLVES chains (replaces, enriches, confirms, or challenges). The plugin handles obvious duplicates; the Knowledge Agent handles subtle ones.
291
+ Two layers. First, the plugin checks for near-identical existing memories before every save. If a memory with >=90% similarity already exists, the save is skipped and the existing memory is returned instead. Second, Nowledge Mem's Knowledge Agent runs in the background and handles deeper deduplication, identifying semantic overlap across memories and linking them via EVOLVES chains (replaces, enriches, confirms, or challenges). The plugin handles obvious duplicates; the Knowledge Agent handles subtle ones.
300
292
 
301
293
  **When `sessionContext` is on, will the agent search again and get duplicate context?**
302
294
 
303
- The behavioral guidance adjusts when `sessionContext` is enabled. Instead of "search with memory_search when prior context would help," it tells the agent "relevant memories have already been injected — use memory_search only for specific follow-up queries." This reduces redundant searches. The injected context and manual search results are in different XML blocks, so even if overlap occurs, it doesn't confuse the agent — it just sees the same fact confirmed from two sources.
295
+ The behavioral guidance adjusts when `sessionContext` is enabled. Instead of "search with memory_search when prior context would help," it tells the agent "relevant memories have already been injected, use memory_search only for specific follow-up queries." This reduces redundant searches. The injected context and manual search results are in different XML blocks, so even if overlap occurs, it doesn't confuse the agent.
304
296
 
305
297
  **What happens to conversations I don't explicitly save?**
306
298
 
307
- With `sessionDigest` enabled (the default), every conversation is saved as a searchable thread — you can find it later with `nowledge_mem_thread_search`. On top of that, a lightweight LLM triage checks if the conversation contained decisions, insights, or preferences worth keeping as structured memories. If yes, they're extracted with proper types, labels, and temporal context. If the conversation was routine ("fix this typo"), nothing extra is saved.
299
+ With `sessionDigest` enabled (the default), every conversation is saved as a searchable thread. You can find it later with `nowledge_mem_thread_search`. On top of that, a lightweight LLM triage checks if the conversation contained decisions, insights, or preferences worth keeping as structured memories. If yes, they're extracted with proper types, labels, and temporal context. If the conversation was routine ("fix this typo"), nothing extra is saved.
308
300
 
309
301
  **Can memories be wrong or outdated?**
310
302
 
311
- Yes. Memories are what you or the AI saved at a point in time. Nowledge Mem's EVOLVES chains track how understanding changes — a newer memory can supersede, enrich, or challenge an older one. The Knowledge Agent identifies these relationships automatically. When you search, the relevance scoring considers recency, so newer memories rank higher by default.
303
+ Yes. Memories are what you or the AI saved at a point in time. Nowledge Mem's EVOLVES chains track how understanding changes: a newer memory can supersede, enrich, or challenge an older one. The Knowledge Agent identifies these relationships automatically. When you search, the relevance scoring considers recency, so newer memories rank higher by default.
312
304
 
313
305
  ## Slash Commands
314
306
 
@@ -327,24 +319,54 @@ openclaw nowledge-mem status
327
319
 
328
320
  ## Configuration
329
321
 
322
+ On first startup, the plugin creates a config file at `~/.nowledge-mem/openclaw.json` with defaults. Edit it to customize:
323
+
324
+ ```json
325
+ {
326
+ "sessionContext": false,
327
+ "sessionDigest": true,
328
+ "digestMinInterval": 300,
329
+ "maxContextResults": 5,
330
+ "apiUrl": "",
331
+ "apiKey": ""
332
+ }
333
+ ```
334
+
335
+ Edit the file and restart OpenClaw. The plugin reads it on every startup.
336
+
330
337
  | Key | Type | Default | Description |
331
338
  |-----|------|---------|-------------|
332
339
  | `sessionContext` | boolean | `false` | Inject Working Memory + relevant memories at prompt time |
333
340
  | `sessionDigest` | boolean | `true` | Thread capture + LLM distillation at session end |
334
- | `maxContextResults` | integer | `5` | Max memories to inject at prompt time (1–20, only used when sessionContext is enabled) |
335
- | `digestMinInterval` | integer | `300` | Minimum seconds between session digests for the same thread (0–86400) |
341
+ | `digestMinInterval` | integer | `300` | Minimum seconds between session digests for the same thread (0-86400) |
342
+ | `maxContextResults` | integer | `5` | Max memories to inject at prompt time (1-20, only used when sessionContext is enabled) |
336
343
  | `apiUrl` | string | `""` | Remote server URL. Empty = local (`http://127.0.0.1:14242`) |
337
- | `apiKey` | string | `""` | API key for remote access. Injected as `NMEM_API_KEY` env var — never logged |
344
+ | `apiKey` | string | `""` | API key for remote access. Injected as `NMEM_API_KEY` env var, never logged |
345
+
346
+ ### Advanced: environment variables
347
+
348
+ For CI, scripts, or Docker deployments, all options also support environment variables:
349
+
350
+ ```bash
351
+ NMEM_SESSION_CONTEXT=true
352
+ NMEM_SESSION_DIGEST=true
353
+ NMEM_DIGEST_MIN_INTERVAL=300
354
+ NMEM_MAX_CONTEXT_RESULTS=5
355
+ NMEM_API_URL=https://...
356
+ NMEM_API_KEY=your-key
357
+ ```
358
+
359
+ **Priority**: OpenClaw plugin config > config file > env vars > defaults.
338
360
 
339
361
  ## What Makes This Different
340
362
 
341
363
  - **Local-first**: no API key, no cloud account. Your knowledge stays on your machine.
342
364
  - **Knowledge graph**: memories are connected nodes, not isolated vectors. EVOLVES edges track how understanding grows over time.
343
- - **Source provenance**: the Library ingests PDFs, DOCX, URLs — extracted knowledge links back to the exact document section it came from.
344
- - **Working Memory**: an AI-generated daily briefing that evolves — not a static user profile.
365
+ - **Source provenance**: the Library ingests PDFs, DOCX, URLs. Extracted knowledge links back to the exact document section it came from.
366
+ - **Working Memory**: an AI-generated daily briefing that evolves, not a static user profile.
345
367
  - **Cross-AI continuity**: knowledge captured in any tool (Cursor, Claude, ChatGPT) flows to OpenClaw and back.
346
- - **Typed memories**: 8 knowledge types mapped to graph node properties — structured understanding, not text blobs.
347
- - **Multi-signal search**: not just semantic similarity — combines embedding, BM25 keyword, label match, graph & community signals, and recency/importance decay. See [Search & Relevance](https://mem.nowledge.co/docs/search-relevance).
368
+ - **Typed memories**: 8 knowledge types mapped to graph node properties. Structured understanding, not text blobs.
369
+ - **Multi-signal search**: not just semantic similarity. Combines embedding, BM25 keyword, label match, graph & community signals, and recency/importance decay. See [Search & Relevance](https://mem.nowledge.co/docs/search-relevance).
348
370
 
349
371
  ## License
350
372
 
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "id": "openclaw-nowledge-mem",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "kind": "memory",
5
5
  "uiHints": {
6
6
  "sessionContext": {
7
7
  "label": "Session context injection",
8
- "help": "Inject Working Memory and relevant memories at prompt time. Off by default — the agent can call memory_search on demand instead."
8
+ "help": "Inject Working Memory and relevant memories at prompt time. Off by default \u2014 the agent can call memory_search on demand instead."
9
9
  },
10
10
  "sessionDigest": {
11
11
  "label": "Session digest at end",
12
- "help": "Capture conversation threads and distill key memories via LLM at session end. On by default — negligible cost, one triage call per session."
12
+ "help": "Capture conversation threads and distill key memories via LLM at session end. On by default \u2014 negligible cost, one triage call per session."
13
13
  },
14
14
  "digestMinInterval": {
15
15
  "label": "Minimum digest interval (seconds)",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "maxContextResults": {
19
19
  "label": "Max context results",
20
- "help": "How many memories to inject for each context cycle (1–20)"
20
+ "help": "How many memories to inject for each context cycle (1\u201320)"
21
21
  },
22
22
  "apiUrl": {
23
23
  "label": "Server URL (remote mode)",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "apiKey": {
27
27
  "label": "API key (remote mode)",
28
- "help": "Required when connecting to a remote server. Never logged or passed as a CLI argument — injected as NMEM_API_KEY env var only.",
28
+ "help": "Required when connecting to a remote server. Never logged or passed as a CLI argument \u2014 injected as NMEM_API_KEY env var only.",
29
29
  "secret": true
30
30
  }
31
31
  },
@@ -35,7 +35,7 @@
35
35
  "sessionContext": {
36
36
  "type": "boolean",
37
37
  "default": false,
38
- "description": "Inject Working Memory and recalled memories at prompt time. Off by default — the agent queries memory on demand via tools."
38
+ "description": "Inject Working Memory and recalled memories at prompt time. Off by default \u2014 the agent queries memory on demand via tools."
39
39
  },
40
40
  "sessionDigest": {
41
41
  "type": "boolean",
@@ -64,7 +64,7 @@
64
64
  "apiKey": {
65
65
  "type": "string",
66
66
  "default": "",
67
- "description": "API key for remote access. Stored in plugin config, injected as NMEM_API_KEY env var — never logged."
67
+ "description": "API key for remote access. Stored in plugin config, injected as NMEM_API_KEY env var. Never logged."
68
68
  }
69
69
  },
70
70
  "additionalProperties": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowledge/openclaw-nowledge-mem",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "type": "module",
5
5
  "description": "Nowledge Mem memory plugin for OpenClaw, local-first personal knowledge base",
6
6
  "author": {
package/src/config.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+
1
5
  export const API_DEFAULT_URL = "http://127.0.0.1:14242";
2
6
 
3
7
  /**
@@ -17,6 +21,11 @@ const ALLOWED_KEYS = new Set([
17
21
  "maxContextResults",
18
22
  "apiUrl",
19
23
  "apiKey",
24
+ // Legacy aliases — accepted but not advertised
25
+ "autoRecall",
26
+ "autoCapture",
27
+ "captureMinInterval",
28
+ "maxRecallResults",
20
29
  ]);
21
30
 
22
31
  // Backward-compatible aliases (old names → new names).
@@ -28,11 +37,123 @@ const ALIAS_KEYS = {
28
37
  maxRecallResults: "maxContextResults",
29
38
  };
30
39
 
31
- export function parseConfig(raw) {
32
- const obj = raw && typeof raw === "object" ? raw : {};
40
+ // --- config file ---------------------------------------------------------
41
+
42
+ const CONFIG_DIR = join(homedir(), ".nowledge-mem");
43
+ const CONFIG_PATH = join(CONFIG_DIR, "openclaw.json");
44
+
45
+ const DEFAULT_CONFIG = {
46
+ sessionContext: false,
47
+ sessionDigest: true,
48
+ digestMinInterval: 300,
49
+ maxContextResults: 5,
50
+ apiUrl: "",
51
+ apiKey: "",
52
+ };
53
+
54
+ /**
55
+ * Read ~/.nowledge-mem/openclaw.json.
56
+ * On first run, creates the file seeded with values from pluginConfig
57
+ * (if any) — this migrates settings before OpenClaw strips them.
58
+ * Returns {} on any error — never crashes.
59
+ */
60
+ function readConfigFile(logger, pluginCfg) {
61
+ try {
62
+ if (!existsSync(CONFIG_PATH)) {
63
+ // Seed from pluginConfig + env vars for migration.
64
+ // Users on older OpenClaw get their values written to the file
65
+ // BEFORE a future OpenClaw update can strip them.
66
+ const initial = { ...DEFAULT_CONFIG };
67
+ if (pluginCfg && typeof pluginCfg === "object") {
68
+ // Resolve aliases in pluginConfig for seeding
69
+ const resolved = { ...pluginCfg };
70
+ for (const [oldKey, newKey] of Object.entries(ALIAS_KEYS)) {
71
+ if (oldKey in resolved) {
72
+ if (!(newKey in resolved)) {
73
+ resolved[newKey] = resolved[oldKey];
74
+ }
75
+ delete resolved[oldKey];
76
+ }
77
+ }
33
78
 
34
- // Resolve aliases: copy old-name values to new-name slots
35
- // (new name takes precedence if both are present).
79
+ if (typeof resolved.sessionContext === "boolean")
80
+ initial.sessionContext = resolved.sessionContext;
81
+ if (typeof resolved.sessionDigest === "boolean")
82
+ initial.sessionDigest = resolved.sessionDigest;
83
+ if (
84
+ typeof resolved.digestMinInterval === "number" &&
85
+ Number.isFinite(resolved.digestMinInterval)
86
+ )
87
+ initial.digestMinInterval = resolved.digestMinInterval;
88
+ if (
89
+ typeof resolved.maxContextResults === "number" &&
90
+ Number.isFinite(resolved.maxContextResults)
91
+ )
92
+ initial.maxContextResults = resolved.maxContextResults;
93
+ if (typeof resolved.apiUrl === "string" && resolved.apiUrl.trim())
94
+ initial.apiUrl = resolved.apiUrl.trim();
95
+ // apiKey intentionally NOT written to disk — keep in env/pluginConfig only
96
+ }
97
+
98
+ mkdirSync(CONFIG_DIR, { recursive: true });
99
+ writeFileSync(
100
+ CONFIG_PATH,
101
+ JSON.stringify(initial, null, 2) + "\n",
102
+ "utf-8",
103
+ );
104
+ logger?.info?.(
105
+ `nowledge-mem: created config at ${CONFIG_PATH} — edit to customize`,
106
+ );
107
+ return { ...initial };
108
+ }
109
+ const content = readFileSync(CONFIG_PATH, "utf-8").trim();
110
+ if (!content) return {};
111
+ return JSON.parse(content);
112
+ } catch (err) {
113
+ logger?.warn?.(
114
+ `nowledge-mem: could not read ${CONFIG_PATH}: ${err instanceof Error ? err.message : err}`,
115
+ );
116
+ return {};
117
+ }
118
+ }
119
+
120
+ /** Exported for tests / CLI tooling. */
121
+ export { CONFIG_PATH };
122
+
123
+ // --- env var helpers -----------------------------------------------------
124
+
125
+ function envStr(name) {
126
+ const v = process.env[name];
127
+ return typeof v === "string" ? v.trim() : undefined;
128
+ }
129
+
130
+ function envBool(name, fallbackName) {
131
+ const v = envStr(name) ?? envStr(fallbackName);
132
+ if (v === undefined) return undefined;
133
+ return v === "true" || v === "1" || v === "yes";
134
+ }
135
+
136
+ function envInt(name) {
137
+ const v = envStr(name);
138
+ if (v === undefined) return undefined;
139
+ const n = Number.parseInt(v, 10);
140
+ return Number.isFinite(n) ? n : undefined;
141
+ }
142
+
143
+ // --- helpers to read a boolean/number from resolved sources --------------
144
+
145
+ function pickBool(obj, key) {
146
+ if (typeof obj[key] === "boolean") return obj[key];
147
+ return undefined;
148
+ }
149
+
150
+ function pickNum(obj, key) {
151
+ if (typeof obj[key] === "number" && Number.isFinite(obj[key])) return obj[key];
152
+ return undefined;
153
+ }
154
+
155
+ /** Resolve legacy aliases in an object: old names → new names. */
156
+ function resolveAliases(obj) {
36
157
  const resolved = { ...obj };
37
158
  for (const [oldKey, newKey] of Object.entries(ALIAS_KEYS)) {
38
159
  if (oldKey in resolved) {
@@ -42,49 +163,107 @@ export function parseConfig(raw) {
42
163
  delete resolved[oldKey];
43
164
  }
44
165
  }
166
+ return resolved;
167
+ }
45
168
 
46
- // Strict: reject unknown keys so users catch typos immediately.
47
- const unknownKeys = Object.keys(resolved).filter((k) => !ALLOWED_KEYS.has(k));
169
+ // -------------------------------------------------------------------------
170
+
171
+ /**
172
+ * Parse plugin config with cascade:
173
+ * pluginConfig > ~/.nowledge-mem/openclaw.json > env vars > defaults
174
+ *
175
+ * Canonical keys: sessionContext, sessionDigest, digestMinInterval,
176
+ * maxContextResults, apiUrl, apiKey
177
+ *
178
+ * Legacy aliases (accepted from all sources; never shown in docs):
179
+ * autoRecall → sessionContext
180
+ * autoCapture → sessionDigest
181
+ * captureMinInterval → digestMinInterval
182
+ * maxRecallResults → maxContextResults
183
+ *
184
+ * Environment variables (all optional):
185
+ * NMEM_SESSION_CONTEXT — true/1/yes to enable (alias: NMEM_AUTO_RECALL)
186
+ * NMEM_SESSION_DIGEST — true/1/yes to enable (alias: NMEM_AUTO_CAPTURE)
187
+ * NMEM_DIGEST_MIN_INTERVAL — seconds (0–86400)
188
+ * NMEM_MAX_CONTEXT_RESULTS — integer (1–20)
189
+ * NMEM_API_URL — remote server URL
190
+ * NMEM_API_KEY — API key (never logged)
191
+ */
192
+ export function parseConfig(raw, logger) {
193
+ const pluginCfg = raw && typeof raw === "object" ? raw : {};
194
+ const fileCfg = readConfigFile(logger, pluginCfg);
195
+
196
+ // Resolve aliases in both sources
197
+ const resolvedPlugin = resolveAliases(pluginCfg);
198
+ const resolvedFile = resolveAliases(fileCfg);
199
+
200
+ // Strict: reject unknown keys in pluginConfig (typo catcher).
201
+ // File config is our own — validated by shape, no strict check needed.
202
+ const unknownKeys = Object.keys(resolvedPlugin).filter(
203
+ (k) => !ALLOWED_KEYS.has(k),
204
+ );
48
205
  if (unknownKeys.length > 0) {
49
206
  throw new Error(
50
207
  `nowledge-mem: unknown config key${unknownKeys.length > 1 ? "s" : ""}: ${unknownKeys.join(", ")}. ` +
51
- `Allowed keys: ${[...ALLOWED_KEYS].join(", ")}`,
208
+ `Allowed keys: ${[...ALLOWED_KEYS].filter((k) => !Object.keys(ALIAS_KEYS).includes(k)).join(", ")}`,
52
209
  );
53
210
  }
54
211
 
55
- // apiUrl: config wins, then env var, then local default
212
+ // --- sessionContext ---
213
+ const sessionContext =
214
+ pickBool(resolvedPlugin, "sessionContext") ??
215
+ pickBool(resolvedFile, "sessionContext") ??
216
+ envBool("NMEM_SESSION_CONTEXT", "NMEM_AUTO_RECALL") ??
217
+ false;
218
+
219
+ // --- sessionDigest ---
220
+ const sessionDigest =
221
+ pickBool(resolvedPlugin, "sessionDigest") ??
222
+ pickBool(resolvedFile, "sessionDigest") ??
223
+ envBool("NMEM_SESSION_DIGEST", "NMEM_AUTO_CAPTURE") ??
224
+ true;
225
+
226
+ // --- digestMinInterval ---
227
+ const rawInterval =
228
+ pickNum(resolvedPlugin, "digestMinInterval") ??
229
+ pickNum(resolvedFile, "digestMinInterval") ??
230
+ envInt("NMEM_DIGEST_MIN_INTERVAL") ??
231
+ envInt("NMEM_CAPTURE_MIN_INTERVAL");
232
+ const digestMinInterval =
233
+ rawInterval !== undefined
234
+ ? Math.min(86400, Math.max(0, Math.trunc(rawInterval)))
235
+ : 300;
236
+
237
+ // --- maxContextResults ---
238
+ const rawMax =
239
+ pickNum(resolvedPlugin, "maxContextResults") ??
240
+ pickNum(resolvedFile, "maxContextResults") ??
241
+ envInt("NMEM_MAX_CONTEXT_RESULTS") ??
242
+ envInt("NMEM_MAX_RECALL_RESULTS");
243
+ const maxContextResults =
244
+ rawMax !== undefined
245
+ ? Math.min(20, Math.max(1, Math.trunc(rawMax)))
246
+ : 5;
247
+
248
+ // --- apiUrl: pluginConfig > file > env > "" ---
56
249
  const apiUrl =
57
- (typeof resolved.apiUrl === "string" && resolved.apiUrl.trim()) ||
58
- (typeof process.env.NMEM_API_URL === "string" &&
59
- process.env.NMEM_API_URL.trim()) ||
250
+ (typeof resolvedPlugin.apiUrl === "string" && resolvedPlugin.apiUrl.trim()) ||
251
+ (typeof resolvedFile.apiUrl === "string" && resolvedFile.apiUrl.trim()) ||
252
+ envStr("NMEM_API_URL") ||
60
253
  "";
61
254
 
62
- // apiKey: config wins, then env var — never logged, never in CLI args
255
+ // --- apiKey: pluginConfig > file > env > "" ---
63
256
  const apiKey =
64
- (typeof resolved.apiKey === "string" && resolved.apiKey.trim()) ||
65
- (typeof process.env.NMEM_API_KEY === "string" &&
66
- process.env.NMEM_API_KEY.trim()) ||
257
+ (typeof resolvedPlugin.apiKey === "string" && resolvedPlugin.apiKey.trim()) ||
258
+ (typeof resolvedFile.apiKey === "string" && resolvedFile.apiKey.trim()) ||
259
+ envStr("NMEM_API_KEY") ||
67
260
  "";
68
261
 
69
262
  return {
70
- sessionContext:
71
- typeof resolved.sessionContext === "boolean"
72
- ? resolved.sessionContext
73
- : false,
74
- sessionDigest:
75
- typeof resolved.sessionDigest === "boolean"
76
- ? resolved.sessionDigest
77
- : true,
78
- digestMinInterval:
79
- typeof resolved.digestMinInterval === "number" &&
80
- Number.isFinite(resolved.digestMinInterval)
81
- ? Math.min(86400, Math.max(0, Math.trunc(resolved.digestMinInterval)))
82
- : 300,
83
- maxContextResults:
84
- typeof resolved.maxContextResults === "number" &&
85
- Number.isFinite(resolved.maxContextResults)
86
- ? Math.min(20, Math.max(1, Math.trunc(resolved.maxContextResults)))
87
- : 5,
263
+ sessionContext,
264
+ sessionDigest,
265
+ digestMinInterval,
266
+ maxContextResults,
88
267
  apiUrl,
89
268
  apiKey,
90
269
  };
@@ -291,7 +291,7 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
291
291
  });
292
292
 
293
293
  // 2. Triage + distill: language-agnostic LLM-based capture.
294
- // Defensive guard — registration in index.js already gates on autoCapture,
294
+ // Defensive guard - registration in index.js already gates on sessionDigest,
295
295
  // but check here too so the handler is safe if called directly.
296
296
  if (!cfg.sessionDigest) return;
297
297
 
package/src/index.js CHANGED
@@ -30,8 +30,8 @@ export default {
30
30
  kind: "memory",
31
31
 
32
32
  register(api) {
33
- const cfg = parseConfig(api.pluginConfig);
34
33
  const logger = api.logger;
34
+ const cfg = parseConfig(api.pluginConfig, logger);
35
35
  const client = new NowledgeMemClient(logger, {
36
36
  apiUrl: cfg.apiUrl,
37
37
  apiKey: cfg.apiKey,