@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 +80 -52
- package/CLAUDE.md +66 -58
- package/README.md +71 -49
- package/openclaw.plugin.json +7 -7
- package/package.json +1 -1
- package/src/config.js +212 -33
- package/src/hooks/capture.js +1 -1
- package/src/index.js +1 -1
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
|
|
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`
|
|
12
|
-
- `nowledge_mem_thread_fetch`
|
|
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 })`
|
|
21
|
-
- `client.fetchThread(threadId, { offset, limit })`
|
|
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 (
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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)`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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()`
|
|
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)
|
|
100
|
-
2. **Triage** (cheap, fast)
|
|
101
|
-
3. **Distillation** (only when worthwhile)
|
|
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
|
|
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)`
|
|
116
|
-
- `client.distillThread({ threadId, title, content })`
|
|
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
|
|
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>`
|
|
133
|
-
- `client.graphEvolves()`
|
|
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`
|
|
140
|
-
- `client.patchWorkingMemory(heading, { content, append })`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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()`
|
|
167
|
-
- `client.feedEvents()`
|
|
168
|
-
- `client.search()`
|
|
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()`
|
|
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()`
|
|
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
|
|
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
|
|
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
|
|
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`)
|
|
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
|
|
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
|
|
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()`
|
|
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`
|
|
204
|
-
- `recorded_date_from` / `recorded_date_to`
|
|
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()`
|
|
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
|
|
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)
|
|
228
|
-
- `nowledge_mem_context` tool: read today's Working Memory daily briefing
|
|
229
|
-
- `nowledge_mem_connections` tool: explore knowledge graph around a topic
|
|
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
|
|
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**
|
|
11
|
-
- Remote mode: set `NMEM_API_URL` + `NMEM_API_KEY` env vars or
|
|
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**
|
|
18
|
-
2. **Source provenance**
|
|
19
|
-
3. **Structured types**
|
|
20
|
-
4. **Working Memory**
|
|
21
|
-
5. **Cross-AI continuity**
|
|
22
|
-
6. **Hybrid search**
|
|
23
|
-
7. **Transparent scoring**
|
|
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
|
|
30
|
-
client.js
|
|
31
|
-
config.js
|
|
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
|
|
34
|
-
recall.js
|
|
35
|
-
capture.js
|
|
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
|
|
38
|
-
memory-get.js
|
|
39
|
-
save.js
|
|
40
|
-
context.js
|
|
41
|
-
connections.js
|
|
42
|
-
timeline.js
|
|
43
|
-
forget.js
|
|
44
|
-
thread-search.js
|
|
45
|
-
thread-fetch.js
|
|
46
|
-
openclaw.plugin.json
|
|
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`
|
|
53
|
-
- `memory_get`
|
|
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`
|
|
57
|
-
- `nowledge_mem_context`
|
|
58
|
-
- `nowledge_mem_connections`
|
|
59
|
-
- `nowledge_mem_timeline`
|
|
60
|
-
- `nowledge_mem_forget`
|
|
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`
|
|
64
|
-
- `nowledge_mem_thread_fetch`
|
|
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)
|
|
69
|
-
- `before_prompt_build` (sessionContext)
|
|
70
|
-
- `agent_end`
|
|
71
|
-
- `after_compaction`
|
|
72
|
-
- `before_reset`
|
|
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`
|
|
77
|
-
- `/recall`
|
|
78
|
-
- `/forget`
|
|
77
|
+
- `/remember` - quick save
|
|
78
|
+
- `/recall` - quick search
|
|
79
|
+
- `/forget` - quick delete
|
|
79
80
|
|
|
80
81
|
## Config Keys
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
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`**
|
|
140
|
-
2. **Working Memory section edit**
|
|
141
|
-
3. **EVOLVES chain CLI**
|
|
142
|
-
4. **`unit_type` requires rebuilt backend**
|
|
143
|
-
5. **Working Memory full-overwrite only via API**
|
|
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`
|
|
148
|
-
- Do NOT expose full WM overwrite from agents
|
|
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
|
|
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
|
|
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
|
-
"
|
|
44
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
155
|
-
2. **Direct search
|
|
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**
|
|
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**
|
|
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**
|
|
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
|
-
|
|
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
|
|
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**
|
|
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**
|
|
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
|
-
|
|
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**
|
|
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
|
-
|
|
235
|
-
- [Memory saved] UV guide
|
|
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**
|
|
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**
|
|
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**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
| `
|
|
335
|
-
| `
|
|
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
|
|
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
|
|
344
|
-
- **Working Memory**: an AI-generated daily briefing that evolves
|
|
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
|
|
347
|
-
- **Multi-signal search**: not just semantic similarity
|
|
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
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-nowledge-mem",
|
|
3
|
-
"version": "0.6.
|
|
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
|
|
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
|
|
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
|
+
"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
|
|
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
|
|
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
|
|
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
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
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
|
|
58
|
-
(typeof
|
|
59
|
-
|
|
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:
|
|
255
|
+
// --- apiKey: pluginConfig > file > env > "" ---
|
|
63
256
|
const apiKey =
|
|
64
|
-
(typeof
|
|
65
|
-
(typeof
|
|
66
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
};
|
package/src/hooks/capture.js
CHANGED
|
@@ -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
|
|
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,
|