@nowledge/openclaw-nowledge-mem 0.6.9 → 0.6.13

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,6 +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.13] - 2026-03-17
6
+
7
+ ### Fixed
8
+
9
+ - **Dashboard config: API key field now masked correctly.** The manifest `uiHints` for `apiKey` used `"secret": true` but OpenClaw's `PluginConfigUiHint` type expects `"sensitive": true`. The field now renders as a password input in the OpenClaw dashboard.
10
+
11
+ ## [0.6.12] - 2026-03-15
12
+
13
+ ### Added
14
+
15
+ - **Agent behavioral skill**: the plugin now registers a `skills/memory-guide/SKILL.md` skill via the OpenClaw manifest `skills[]` array. OpenClaw auto-discovers it and presents it to the agent as an available skill. The skill teaches the agent when and how to search, save, explore connections, browse timelines, fetch past conversations, and use Working Memory. This is the single biggest change for memory recall quality: agents now receive structured guidance before their first memory interaction, rather than relying on tool descriptions alone.
16
+
17
+ ### Changed
18
+
19
+ - **Behavioral guidance is now directive**: the always-on system hint now says "Before answering questions about prior work, decisions, dates, people, preferences, or plans: search with memory_search" instead of the previous "When prior context would improve your response, search." This matches the imperative pattern that LLMs follow most reliably, and explicitly notes "natural language queries, not file paths" to prevent confusion with OpenClaw's built-in file-based memory guidance.
20
+
21
+ ## [0.6.11] - 2026-03-15
22
+
23
+ ### Changed
24
+
25
+ - **Release-cycle alignment**: the OpenClaw plugin version now tracks the main Nowledge Mem `0.6.11` release line, so the published plugin and app-facing changelog stay in sync for this release.
26
+ - **Agent-assisted onboarding added**: the plugin now ships with a bundled `SKILL.md` playbook so AI agents can install, configure, verify, and explain the integration without collapsing the human docs into an agent script.
27
+ - **Aligned command execution with OpenClaw runtime**: the plugin now runs `nmem` through OpenClaw's runtime command runner instead of importing `child_process` directly. This removes gateway-blocking synchronous process calls from the plugin surface and matches current OpenClaw plugin abstractions.
28
+ - **Static guidance moved into system-prompt space**: the always-on behavioral hint now uses system-context injection instead of user-prompt prepend text, matching OpenClaw's newer `prependSystemContext` / `appendSystemContext` guidance for cacheable stable instructions.
29
+ - **Minimum supported OpenClaw version is now `2026.3.7`**: this is the first release with the system-context fields the plugin now relies on.
30
+ - **Package publishing is now explicit**: npm contents are pinned through the package manifest instead of relying on `.gitignore` fallback behavior, making releases more deterministic.
31
+
32
+ ### Fixed
33
+
34
+ - **Health checks now keep the JSON contract**: the internal `status` probe now uses `--json`, matching the rest of the plugin's parseable CLI surface.
35
+ - **Resolved config now overrides inherited `NMEM_*` values cleanly**: child-process env construction now clears inherited `NMEM_API_URL` and `NMEM_API_KEY` before applying the effective plugin config, so local mode cannot accidentally inherit a stale remote backend.
36
+ - **Failure logs no longer echo full CLI argv**: plugin logs now record a sanitized command label instead of raw `nmem` arguments, avoiding retention of user queries or message batches in log files.
37
+ - **Thread CLI flag drift**: thread search and thread fetch now use `-n`, matching the current `nmem` thread CLI surface and the plugin's own internal docs.
38
+ - **Install and trust docs now match current OpenClaw behavior**: the docs now explain that `openclaw plugins install` enables the plugin and selects the memory slot automatically, and they document the recommended `plugins.allow` hardening path for non-bundled plugins.
39
+ - **Default-mode docs no longer over-promise auto-injected context**: Working Memory and recalled memories are now described as opt-in `sessionContext` behavior, not something every default install gets automatically.
40
+
5
41
  ## [0.6.9] - 2026-03-05
6
42
 
7
43
  ### Fixed
package/CLAUDE.md CHANGED
@@ -7,7 +7,8 @@ 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
10
+ - OpenClaw minimum: `2026.3.7` (`appendSystemContext` / system-context guidance required)
11
+ - Architecture: **CLI-first via OpenClaw runtime** - all CLI execution goes through `api.runtime.system.runCommandWithTimeout`, not direct `child_process`
11
12
  - Remote mode: set `NMEM_API_URL` + `NMEM_API_KEY` env vars or config file `apiUrl` + `apiKey`
12
13
 
13
14
  ## Design Philosophy
@@ -27,7 +28,8 @@ Reflects Nowledge Mem's genuine v0.6 strengths:
27
28
  ```
28
29
  src/
29
30
  index.js - plugin registration (tools, hooks, commands, CLI)
30
- client.js - CLI wrapper with API fallback; credential handling
31
+ client.js - CLI wrapper with API fallback; async runtime command execution; credential handling
32
+ spawn-env.js - env-only credential injection for the nmem runner
31
33
  config.js - config cascade: ~/.nowledge-mem/openclaw.json > pluginConfig > env vars > defaults
32
34
  hooks/
33
35
  behavioral.js - always-on behavioral guidance (~50 tokens/turn)
@@ -43,7 +45,10 @@ src/
43
45
  forget.js - memory deletion by ID or search
44
46
  thread-search.js - search past conversations by keyword
45
47
  thread-fetch.js - fetch full messages from a thread with pagination
46
- openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema)
48
+ skills/
49
+ memory-guide/
50
+ SKILL.md - agent behavioral skill: when/how to search, save, explore memory (auto-discovered by OpenClaw)
51
+ openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema, skills)
47
52
  ~/.nowledge-mem/openclaw.json - optional user config file (overrides OpenClaw settings when present)
48
53
  ```
49
54
 
@@ -62,15 +67,19 @@ openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema)
62
67
 
63
68
  ### Thread Tools (progressive conversation retrieval)
64
69
  - `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.
70
+ - `nowledge_mem_thread_fetch` - fetch messages from a specific thread. Start with a small page, then use `offset` + `limit` for progressive retrieval only when more context is needed.
66
71
 
67
72
  ### Diagnostics
68
73
  - `nowledge_mem_status` - show effective config (mode, apiUrl, apiKey set, sessionContext, sessionDigest, etc.), backend connectivity, and version. No parameters.
69
74
 
75
+ ## Skill Surface
76
+
77
+ - `skills/memory-guide/SKILL.md` - agent behavioral skill auto-discovered by OpenClaw via manifest `skills[]`. Teaches the agent when/how to search, save, explore connections, fetch threads, and use Working Memory. Read on-demand when the agent identifies a memory-related task.
78
+
70
79
  ## Hook Surface
71
80
 
72
- - `before_prompt_build` (always-on) - behavioral guidance: brief note telling agent to save/search proactively. Adjusts when sessionContext is on to avoid redundant searches.
73
- - `before_prompt_build` (sessionContext) - session context: Working Memory + `searchRich()` with `relevanceReason` in context
81
+ - `before_prompt_build` (always-on) - directive behavioral guidance in system-prompt space: tells agent to search before answering questions about prior work/decisions/preferences, and to save decisions/learnings proactively. Explicitly notes semantic search (not file paths) to counter OpenClaw's hardcoded memory section. Adjusts when sessionContext is on to avoid redundant searches.
82
+ - `before_prompt_build` (sessionContext) - session context: Working Memory + `searchRich()` memories with `relevanceReason`. Note: does NOT inject thread snippets — threads are available via `memory_search` tool and `nowledge_mem_thread_fetch`.
74
83
  - `agent_end` - thread capture + LLM triage/distillation (requires `sessionDigest: true`)
75
84
  - `after_compaction` - thread append
76
85
  - `before_reset` - thread append
@@ -121,8 +130,8 @@ Legacy aliases accepted silently in all sources for backward compat:
121
130
  | `nmem --json wm patch --heading "## S" --content/--append` | `client.patchWorkingMemory()` | Section-level WM update |
122
131
  | `nmem --json m delete <id>` | `client.execJson()` in forget.js | Delete |
123
132
  | `GET /threads/search?query=&limit=` | `client.searchThreads()` | Thread search enrichment (best-effort, API-only) |
124
- | `nmem --json t search <q> --limit N` | `client.searchThreadsFull()` | Full thread search (CLI-first, API fallback) |
125
- | `nmem --json t show <id> --limit N --offset O` | `client.fetchThread()` | Fetch thread messages with pagination |
133
+ | `nmem --json t search <q> -n N` | `client.searchThreadsFull()` | Full thread search (CLI-first, API fallback) |
134
+ | `nmem --json t show <id> -n N --offset O` | `client.fetchThread()` | Fetch thread messages with pagination |
126
135
 
127
136
  All commands have API fallback for older CLI versions.
128
137
 
package/README.md CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  Local-first knowledge graph memory for [OpenClaw](https://openclaw.ai) agents, powered by [Nowledge Mem](https://mem.nowledge.co).
4
4
 
5
- Your AI tools forget. We remember. Everywhere. This plugin gives your OpenClaw agents persistent, graph-connected memory across WhatsApp, Telegram, Discord, Slack, and every channel OpenClaw supports. All data stays on your machine.
5
+ Your AI tools forget. We remember. Everywhere. This plugin gives OpenClaw both lossless session memory and the same graph-connected memory layer your other AI tools can use too. Every OpenClaw conversation is captured as a searchable thread, important moments can distill into linked memories with `sourceThreadId`, and knowledge from Claude, Cursor, web chats, and imported threads stays searchable inside OpenClaw. Local-first by default, with remote mode when you want a shared server.
6
+
7
+ This is not a flat note store. Nowledge Mem links related knowledge into a graph, tracks how ideas evolve, deduplicates repeated context, and can run background intelligence to produce daily briefings, contradiction checks, and crystals from converging evidence. OpenClaw can search that same growing memory layer instead of starting from scratch every time.
6
8
 
7
9
  ## Requirements
8
10
 
9
11
  - [Nowledge Mem](https://mem.nowledge.co) desktop app **or** `nmem` CLI
10
- - [OpenClaw](https://openclaw.ai) >= 2026.1.29
12
+ - [OpenClaw](https://openclaw.ai) >= 2026.3.7
11
13
 
12
14
  ## Installation
13
15
 
@@ -15,9 +17,37 @@ Your AI tools forget. We remember. Everywhere. This plugin gives your OpenClaw a
15
17
  openclaw plugins install @nowledge/openclaw-nowledge-mem
16
18
  ```
17
19
 
18
- ### Local mode (default)
20
+ OpenClaw's installer writes the install record, enables the plugin, and switches the `memory` slot to `openclaw-nowledge-mem`. Restart OpenClaw after install to load it.
21
+
22
+ ### Agent-assisted setup
23
+
24
+ If another AI agent is helping you install this plugin, point it at the bundled [SKILL.md](./SKILL.md).
25
+
26
+ Copyable instruction:
27
+
28
+ ```text
29
+ Read https://raw.githubusercontent.com/nowledge-co/community/main/nowledge-mem-openclaw-plugin/SKILL.md and follow it to install, configure, verify, and explain Nowledge Mem for OpenClaw.
30
+ ```
31
+
32
+ That guide is written for AI agents, not for humans. It handles local vs remote mode, optional API auth, trust pinning, restart, verification, and next steps.
33
+
34
+ ### Recommended hardening
35
+
36
+ If you keep non-bundled plugins on an explicit trust allowlist, add:
37
+
38
+ ```json
39
+ {
40
+ "plugins": {
41
+ "allow": ["openclaw-nowledge-mem"]
42
+ }
43
+ }
44
+ ```
45
+
46
+ `plugins.allow` is id-based, not source-based. If you also use `plugins.load.paths` or `openclaw plugins install --link`, review those paths too. An allowlist trusts the active plugin with that id; it does not pin where it came from.
47
+
48
+ ### Manual config or verification
19
49
 
20
- Start Nowledge Mem desktop app or run `nmem serve`, then enable the plugin:
50
+ If you manage config manually, or you want to verify what the installer selected, this is the expected shape:
21
51
 
22
52
  ```json
23
53
  {
@@ -32,7 +62,7 @@ Start Nowledge Mem desktop app or run `nmem serve`, then enable the plugin:
32
62
  }
33
63
  ```
34
64
 
35
- That's it. The agent gets 10 tools and calls them on demand. No extra tokens wasted.
65
+ By default the agent gets 10 tools on demand, a behavioral skill that teaches it when to search and save, a short always-on system hint, and end-of-session capture. Working Memory injection is optional and controlled by `sessionContext`.
36
66
 
37
67
  ### Remote mode
38
68
 
@@ -66,7 +96,7 @@ Every user message triggers hooks before the agent sees it, then the agent decid
66
96
  flowchart TD
67
97
  A["User sends message"] --> B["before_prompt_build hook"]
68
98
 
69
- B --> C["Behavioral guidance(always, ~110 tokens)"]
99
+ B --> C["Behavioral guidance(cacheable system hint)"]
70
100
  B -.->|"sessionContext: true"| D["Working Memory +relevant memories (~1-2 KB)"]
71
101
 
72
102
  C --> E["Agent processes message(10 tools available)"]
@@ -85,7 +115,7 @@ flowchart TD
85
115
 
86
116
  ### When Each Tool Gets Called
87
117
 
88
- The behavioral hook nudges the agent to **search before answering** and **save after deciding**. Here's when each tool fires:
118
+ The behavioral skill and always-on hook nudge the agent to **search before answering** and **save after deciding**. Here's when each tool fires:
89
119
 
90
120
  | Scenario | Tool | What happens |
91
121
  |----------|------|--------------|
@@ -160,7 +190,7 @@ Two entry points:
160
190
  flowchart LR
161
191
  subgraph "default" ["Default (recommended)"]
162
192
  direction TB
163
- D1["Every turn: behavioral guidance (~50 tokens)"]
193
+ D1["Every turn: short system hint"]
164
194
  D2["Agent calls 10 tools on demand"]
165
195
  D3["Session end: thread capture + LLM distillation"]
166
196
  end
@@ -174,7 +204,7 @@ flowchart LR
174
204
 
175
205
  subgraph minimal ["Minimal"]
176
206
  direction TB
177
- M1["Every turn: behavioral guidance (~50 tokens)"]
207
+ M1["Every turn: short system hint"]
178
208
  M2["Agent calls 10 tools on demand"]
179
209
  M3["No automatic capture"]
180
210
  end
@@ -262,14 +292,14 @@ The plugin supports three modes. Choose based on how much you want to guarantee
262
292
 
263
293
  | Mode | Config | Behavior | Tradeoff |
264
294
  |------|--------|----------|----------|
265
- | **Default** (recommended) | `sessionContext: false, sessionDigest: true` | Agent calls 10 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
- | **Session context** | `sessionContext: true` | Working Memory + relevant memories injected at prompt time, plus all 10 tools still available. | ~1-2 KB/turn. Guarantees context is always present. Best for short sessions or critical workflows. |
267
- | **Minimal** | `sessionDigest: false` | Tool-only, no automatic capture. | Zero overhead beyond guidance. Use when you handle memory manually. |
295
+ | **Default** (recommended) | `sessionContext: false, sessionDigest: true` | Agent calls 10 tools on demand. A short system hint stays on. Conversations captured + distilled at session end. | Lowest overhead. Agent decides when to search, usually smart, occasionally forgets. |
296
+ | **Session context** | `sessionContext: true` | Working Memory + relevant memories injected at prompt time, plus all 10 tools still available. | Higher per-turn context cost. Guarantees context is present from the first turn. |
297
+ | **Minimal** | `sessionDigest: false` | Tool-only, no automatic capture. | Small overhead from the always-on hint only. Use when you handle memory manually. |
268
298
 
269
299
  **Which mode should you use?**
270
300
 
271
301
  - **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.
302
+ - **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 a larger per-turn prompt.
273
303
  - **Full manual control**: set `sessionDigest: false`. You control what gets saved (via `/remember` or `nowledge_mem_save`) and nothing is captured automatically.
274
304
 
275
305
  ### Session Context (`sessionContext`, default: false)
@@ -292,7 +322,7 @@ Honest answers to common questions about how the memory system works.
292
322
 
293
323
  **Does the agent always search before answering?**
294
324
 
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.
325
+ The plugin uses two layers to drive recall. First, a behavioral skill (auto-discovered by OpenClaw) teaches the agent when and how to use memory tools. Second, a short always-on system hint reminds it to "search before answering questions about prior work, decisions, dates, people, preferences, or plans." In practice, modern LLMs follow this directive guidance reliably for knowledge-related questions. For messages that don't need past context (like "hello" or "thanks"), the agent skips the search, which is the right tradeoff. 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
326
 
297
327
  **What stops the agent from saving duplicate memories?**
298
328
 
@@ -327,7 +357,7 @@ openclaw nowledge-mem status
327
357
 
328
358
  ## Configuration
329
359
 
330
- No config needed to get started. The defaults work for local mode.
360
+ No config is required for a normal npm install. The defaults work for local mode, and `openclaw plugins install` already enables the plugin and selects the memory slot.
331
361
 
332
362
  To change settings, use the OpenClaw plugin settings UI. Changes take effect on restart.
333
363
 
@@ -381,6 +411,20 @@ config file > OpenClaw settings > env vars > defaults
381
411
 
382
412
  Use `nowledge_mem_status` (or `openclaw nowledge-mem status`) to see where each value comes from.
383
413
 
414
+ ### Trust warning after install
415
+
416
+ If OpenClaw logs a warning like `plugins.allow is empty; discovered non-bundled plugins may auto-load`, the plugin is installed correctly. OpenClaw is warning that you have not pinned trust for non-bundled plugins yet. Add:
417
+
418
+ ```json
419
+ {
420
+ "plugins": {
421
+ "allow": ["openclaw-nowledge-mem"]
422
+ }
423
+ }
424
+ ```
425
+
426
+ If you also use linked or workspace copies, review `plugins.load.paths` before relying on an allowlist. OpenClaw allowlists plugin ids, not install provenance.
427
+
384
428
  ## What Makes This Different
385
429
 
386
430
  - **Local-first**: no API key, no cloud account. Your knowledge stays on your machine.
package/SKILL.md ADDED
@@ -0,0 +1,245 @@
1
+ # Nowledge Mem for OpenClaw
2
+
3
+ Use this guide when an AI agent is helping a user install, configure, verify, or explain the Nowledge Mem OpenClaw plugin.
4
+
5
+ Nowledge Mem is not just "memory for OpenClaw." It is the user's shared memory layer across OpenClaw, Claude Code, Cursor, browser capture, imported threads, and documents. OpenClaw joins that context exchange.
6
+
7
+ ## Use This When
8
+
9
+ - The user wants to install or configure the OpenClaw plugin
10
+ - The user wants OpenClaw to connect to existing Nowledge Mem data
11
+ - The user wants local-first memory on this machine
12
+ - The user wants remote or multi-machine memory through a remote `apiUrl`
13
+ - The user wants to verify that OpenClaw can recall, save, and search memory
14
+
15
+ ## Do Not Use This When
16
+
17
+ - The user only wants temporary context in the current OpenClaw session
18
+ - The user is troubleshooting unrelated OpenClaw issues
19
+ - The user only wants direct MCP setup for another client
20
+
21
+ ## Product Framing
22
+
23
+ When talking to users:
24
+
25
+ - Say "Nowledge Mem gives OpenClaw the same shared memory your other AI tools can use."
26
+ - Lead with cross-tool continuity, not with plugin internals.
27
+ - For local mode, say "No API key is needed for local mode."
28
+ - For remote mode, say "Set `apiUrl`, and add `apiKey` when the server requires auth."
29
+ - Do not imply that OpenClaw is the only source of truth. It is one client of the user's larger memory system.
30
+ - Do not imply that cloud backup is the default. Our default story is local-first.
31
+
32
+ Good user-facing explanation:
33
+
34
+ > OpenClaw can now read the same memory you already built in your other AI tools, and what happens in OpenClaw can enrich that shared memory too.
35
+
36
+ ## Definition of Done
37
+
38
+ This task is not complete until all of the following are true:
39
+
40
+ 1. `nmem` is available
41
+ 2. The intended backend is verified reachable with `nmem`
42
+ 3. The OpenClaw plugin is installed
43
+ 4. OpenClaw is using the Nowledge Mem memory slot
44
+ 5. Remote config is written only if the user chose remote mode
45
+ 6. OpenClaw is restarted
46
+ 7. `openclaw nowledge-mem status` succeeds
47
+ 8. The user receives a short handoff covering:
48
+ - one real first question to ask
49
+ - how to bring in older history
50
+ - local vs remote expectations
51
+ - trust pinning if they see `plugins.allow` warnings
52
+
53
+ ## Setup Flow
54
+
55
+ ### Step 0 — Choose Local or Remote
56
+
57
+ Ask the user which setup they want:
58
+
59
+ 1. Local Nowledge Mem already running on this machine
60
+ 2. Existing remote Nowledge Mem server
61
+
62
+ Explain the difference plainly:
63
+
64
+ - Local mode: OpenClaw talks to the local Nowledge Mem app/server through `nmem`
65
+ - Remote mode: OpenClaw talks to a remote Nowledge Mem server through `nmem --api-url ...`; add `apiKey` when that server has auth enabled
66
+
67
+ ### Step 1 — Verify `nmem`
68
+
69
+ ```bash
70
+ nmem --version
71
+ ```
72
+
73
+ If this fails:
74
+
75
+ - For users with the desktop app, direct them to **Settings > Preferences > Developer Tools > Install CLI**
76
+ - Otherwise suggest `pip install nmem-cli`
77
+
78
+ ### Step 2 — Verify the Backend Before Plugin Setup
79
+
80
+ Local mode:
81
+
82
+ ```bash
83
+ nmem --json status
84
+ ```
85
+
86
+ Remote mode without auth:
87
+
88
+ ```bash
89
+ nmem --json --api-url "$API_URL" status
90
+ ```
91
+
92
+ Remote mode with auth:
93
+
94
+ ```bash
95
+ NMEM_API_KEY="$API_KEY" nmem --json --api-url "$API_URL" status
96
+ ```
97
+
98
+ Success means the backend responds correctly through `nmem`. Do not skip this step and assume the URL/auth is right.
99
+
100
+ ### Step 3 — Install the Plugin
101
+
102
+ Default install:
103
+
104
+ ```bash
105
+ openclaw plugins install @nowledge/openclaw-nowledge-mem
106
+ ```
107
+
108
+ If the user is on mainland China or npmjs is unusually slow, prefer a one-shot mirror override instead of changing global npm config:
109
+
110
+ ```bash
111
+ NPMJS_TIME="$(curl -o /dev/null -sS --connect-timeout 2 --max-time 6 -w '%{time_total}' https://registry.npmjs.org/@nowledge%2fopenclaw-nowledge-mem || echo timeout)"
112
+ MIRROR_TIME="$(curl -o /dev/null -sS --connect-timeout 2 --max-time 6 -w '%{time_total}' https://registry.npmmirror.com/@nowledge%2fopenclaw-nowledge-mem || echo timeout)"
113
+
114
+ if [ "$MIRROR_TIME" != "timeout" ] && { [ "$NPMJS_TIME" = "timeout" ] || awk "BEGIN { exit !($NPMJS_TIME > 2 && $MIRROR_TIME < $NPMJS_TIME) }"; }; then
115
+ NPM_CONFIG_REGISTRY=https://registry.npmmirror.com openclaw plugins install @nowledge/openclaw-nowledge-mem
116
+ else
117
+ openclaw plugins install @nowledge/openclaw-nowledge-mem
118
+ fi
119
+ ```
120
+
121
+ Important facts:
122
+
123
+ - The installer already writes the install record
124
+ - The installer already enables the plugin
125
+ - The installer already switches the `memory` slot to `openclaw-nowledge-mem`
126
+
127
+ Do not tell the user to hand-edit those settings unless they are using a manual/path setup.
128
+
129
+ ### Step 4 — Configure Remote Only When Needed
130
+
131
+ Local mode:
132
+
133
+ - No extra config is required
134
+
135
+ Remote mode:
136
+
137
+ - Prefer setting `apiUrl` and `apiKey` in the OpenClaw plugin settings
138
+ - Use `~/.nowledge-mem/openclaw.json` only when the user wants a stable machine-local override or scripted setup
139
+
140
+ `~/.nowledge-mem/openclaw.json` example:
141
+
142
+ ```json
143
+ {
144
+ "apiUrl": "https://nowledge.example.com",
145
+ "apiKey": "your-api-key-here"
146
+ }
147
+ ```
148
+
149
+ Important precedence rule:
150
+
151
+ - `~/.nowledge-mem/openclaw.json` overrides OpenClaw plugin settings
152
+ - Then plugin settings override inherited environment variables
153
+ - Do not assume stale `NMEM_API_URL` or `NMEM_API_KEY` env vars should keep winning
154
+
155
+ If the remote server does not require auth, omit `apiKey`.
156
+
157
+ ### Step 5 — Pin Trust When Appropriate
158
+
159
+ If OpenClaw warns that `plugins.allow` is empty, or the user wants explicit trust for non-bundled plugins, add:
160
+
161
+ ```json
162
+ {
163
+ "plugins": {
164
+ "allow": ["openclaw-nowledge-mem"]
165
+ }
166
+ }
167
+ ```
168
+
169
+ Explain this correctly:
170
+
171
+ - `plugins.allow` trusts the plugin id
172
+ - It does not pin the plugin source path
173
+ - If the user also has linked or workspace copies in `plugins.load.paths`, they should review those too
174
+
175
+ ### Step 6 — Restart OpenClaw
176
+
177
+ Restart OpenClaw so the plugin and hooks are active.
178
+
179
+ If an agent is doing the restart automatically, tell the user before triggering it.
180
+
181
+ ### Step 7 — Verify the Plugin
182
+
183
+ First check the plugin status:
184
+
185
+ ```bash
186
+ openclaw nowledge-mem status
187
+ ```
188
+
189
+ Then verify in chat.
190
+
191
+ For a clean synthetic check:
192
+
193
+ 1. `/remember We chose PostgreSQL for task events`
194
+ 2. `/recall PostgreSQL`
195
+ 3. `/new`
196
+ 4. Ask `What database did we choose for task events?`
197
+
198
+ If the user already has real memory in Nowledge Mem, prefer a real question over a synthetic demo. Good examples:
199
+
200
+ - `What was I working on this week?`
201
+ - `What did we already decide about the task API?`
202
+ - `Find the conversation where we discussed Redis caching`
203
+
204
+ ## Handoff Message
205
+
206
+ After setup succeeds, send a short handoff that covers all four points:
207
+
208
+ 1. OpenClaw is now connected
209
+ 2. The best next question to ask
210
+ 3. How to bring in older history
211
+ 4. What the local/remote trust model is
212
+
213
+ Good structure:
214
+
215
+ ```text
216
+ OpenClaw is now connected to Nowledge Mem.
217
+
218
+ Try this next:
219
+ - Ask OpenClaw: "What was I working on this week?"
220
+
221
+ If you want more history:
222
+ - Import old AI threads or browser-captured chats into Nowledge Mem so OpenClaw can search them too.
223
+
224
+ What to expect:
225
+ - Local mode needs no API key.
226
+ - Remote mode uses the same apiUrl and, when auth is enabled, the same apiKey you already verified with nmem.
227
+ - If OpenClaw warns that plugins.allow is empty, add openclaw-nowledge-mem to the allowlist to pin trust explicitly.
228
+ ```
229
+
230
+ ## Troubleshooting
231
+
232
+ | Symptom | What to do |
233
+ | --- | --- |
234
+ | `nmem --json status` fails in local mode | Start Nowledge Mem locally first |
235
+ | Remote mode fails | Re-run `nmem --json --api-url ... status` with the exact URL and auth you plan to use |
236
+ | `openclaw nowledge-mem status` fails after install | Restart OpenClaw and confirm the plugin was installed successfully |
237
+ | `plugins.allow is empty` warning | Add `openclaw-nowledge-mem` to `plugins.allow` if the user wants explicit trust |
238
+ | Remote config seems ignored | Check whether `~/.nowledge-mem/openclaw.json` is overriding plugin settings |
239
+ | Local mode unexpectedly talks to a remote server | Check for stale `NMEM_API_URL` / `NMEM_API_KEY` in the environment or an overriding `~/.nowledge-mem/openclaw.json` |
240
+
241
+ ## Notes for Agents
242
+
243
+ - Human docs and AI setup instructions are different things. Keep the human explanation short and concrete.
244
+ - Do not dump the whole configuration model on the user up front.
245
+ - Do not reduce Nowledge Mem to "OpenClaw memory." The real value is shared context across tools and growth of knowledge over time.
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "id": "openclaw-nowledge-mem",
3
- "version": "0.6.9",
3
+ "version": "0.6.13",
4
4
  "kind": "memory",
5
+ "skills": ["skills/memory-guide"],
5
6
  "uiHints": {
6
7
  "sessionContext": {
7
8
  "label": "Session context injection",
@@ -30,7 +31,7 @@
30
31
  "apiKey": {
31
32
  "label": "API key (remote mode)",
32
33
  "help": "Required when connecting to a remote server. Never logged or passed as a CLI argument. Injected as NMEM_API_KEY env var only.",
33
- "secret": true
34
+ "sensitive": true
34
35
  }
35
36
  },
36
37
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowledge/openclaw-nowledge-mem",
3
- "version": "0.6.9",
3
+ "version": "0.6.13",
4
4
  "type": "module",
5
5
  "description": "Nowledge Mem memory plugin for OpenClaw, local-first personal knowledge base",
6
6
  "author": {
@@ -14,6 +14,15 @@
14
14
  "url": "https://github.com/nowledge-co/community.git",
15
15
  "directory": "nowledge-mem-openclaw-plugin"
16
16
  },
17
+ "files": [
18
+ "src",
19
+ "skills",
20
+ "README.md",
21
+ "CHANGELOG.md",
22
+ "CLAUDE.md",
23
+ "SKILL.md",
24
+ "openclaw.plugin.json"
25
+ ],
17
26
  "openclaw": {
18
27
  "extensions": ["./src/index.js"]
19
28
  },
@@ -23,7 +32,7 @@
23
32
  "format": "biome format --write ."
24
33
  },
25
34
  "peerDependencies": {
26
- "openclaw": ">=2026.1.29"
35
+ "openclaw": ">=2026.3.7"
27
36
  },
28
37
  "devDependencies": {
29
38
  "@biomejs/biome": "^1.9.0"
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: nowledge-mem-guide
3
+ description: >
4
+ Cross-AI personal knowledge graph memory (Nowledge Mem). Stores memories from this tool,
5
+ Claude Code, Cursor, browser capture, imported documents, and more. Use when: (1) the user
6
+ asks about prior work, decisions, preferences, people, dates, plans, or todos, (2) the user
7
+ references something discussed before or says "remind me" / "what did we decide" / "what was
8
+ I working on", (3) the conversation produces a decision, preference, plan, or learning worth
9
+ keeping, (4) the user wants to browse recent activity or explore how ideas connect, (5) the
10
+ user asks about a past conversation or wants to find a specific thread.
11
+ ---
12
+
13
+ # Nowledge Mem
14
+
15
+ ## Search (memory_search)
16
+
17
+ Use natural language queries. This is semantic search, not file search.
18
+
19
+ - "database choice for task events" (not "MEMORY.md" or file paths)
20
+ - "meeting with Sarah about API redesign"
21
+ - "deployment procedure for production"
22
+
23
+ Results include `matchedVia` (scoring breakdown), `importance`, `labels`, `sourceThreadId`.
24
+
25
+ When results include `relatedThreads`, use `nowledge_mem_thread_fetch` with the `threadId` for full conversation context. Start with a small page; fetch more via `offset` + `limit` only when needed.
26
+
27
+ Bi-temporal filters narrow results by time:
28
+
29
+ - `event_date_from` / `event_date_to` — when the fact happened
30
+ - `recorded_date_from` / `recorded_date_to` — when it was saved
31
+
32
+ ## Save (nowledge_mem_save)
33
+
34
+ Save proactively when the conversation produces a decision, preference, plan, procedure, learning, or important context. Do not wait to be asked.
35
+
36
+ Structure saves:
37
+
38
+ - `unit_type`: fact | preference | decision | plan | procedure | learning | context | event
39
+ - `labels`: lowercase-hyphenated topic tags
40
+ - `importance`: 0.0-1.0 (most: 0.5; key decisions/preferences: 0.7-0.9)
41
+ - `event_start` / `event_end`: ISO dates when temporal
42
+
43
+ Skip trivial exchanges, greetings, and meta-conversation about memory itself.
44
+
45
+ ## Connections (nowledge_mem_connections)
46
+
47
+ Pass a `memoryId` from search results to explore related memories, evolution chains, and source documents. Edge types: EVOLVES, CRYSTALLIZED_FROM, SOURCED_FROM, MENTIONS.
48
+
49
+ ## Timeline (nowledge_mem_timeline)
50
+
51
+ Browse recent activity grouped by day. Filter by `event_type` or exact `date_from` / `date_to` range.
52
+
53
+ ## Threads (nowledge_mem_thread_search + nowledge_mem_thread_fetch)
54
+
55
+ Find past conversations by keyword, then progressively fetch messages. Threads span all sources: this tool, other AI tools, browser capture, imports.
56
+
57
+ ## Working Memory (nowledge_mem_context)
58
+
59
+ Read the user's daily briefing (priorities, active projects, flags). Patch a specific section with `patch_section` + `patch_content`/`patch_append` when the conversation updates a priority or resolves a flag.
60
+
61
+ ## Important
62
+
63
+ - Search uses natural language queries, not file paths. Never search for "MEMORY.md" or "memory/*.md".
64
+ - When `relatedThreads` appear in results, they often contain the most useful context.
65
+ - Use tools directly; do not tell the user to "check your memory."
package/src/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { buildNmemSpawnEnv } from "./spawn-env.js";
2
2
 
3
3
  /**
4
4
  * Patch a single markdown section in a Working Memory document.
@@ -66,18 +66,21 @@ function patchWmSection(currentContent, heading, { content, append } = {}) {
66
66
  *
67
67
  * Credential rules:
68
68
  * - apiUrl: passed to CLI via --api-url flag (not a secret)
69
- * - apiKey: injected into the child process env as NMEM_API_KEY ONLY
69
+ * - apiKey: injected into the command runner env as NMEM_API_KEY ONLY
70
70
  * (never passed as a CLI arg to avoid exposure in `ps aux`)
71
71
  * (never logged, even at debug level)
72
72
  */
73
73
  export class NowledgeMemClient {
74
74
  /**
75
75
  * @param {object} logger
76
+ * @param {object} runtimeSystem
76
77
  * @param {{ apiUrl?: string; apiKey?: string }} [credentials]
77
78
  */
78
- constructor(logger, credentials = {}) {
79
+ constructor(logger, runtimeSystem, credentials = {}) {
79
80
  this.logger = logger;
81
+ this.runtimeSystem = runtimeSystem;
80
82
  this.nmemCmd = null;
83
+ this.nmemCmdPromise = null;
81
84
  // Resolved once from config + env (config wins over env, both win over default)
82
85
  this._apiUrl =
83
86
  (credentials.apiUrl || "").trim() || "http://127.0.0.1:14242";
@@ -138,28 +141,42 @@ export class NowledgeMemClient {
138
141
 
139
142
  // ── CLI helpers ────────────────────────────────────────────────────────────
140
143
 
141
- resolveCommand() {
144
+ async resolveCommand() {
142
145
  if (this.nmemCmd) return this.nmemCmd;
146
+ if (this.nmemCmdPromise) return this.nmemCmdPromise;
143
147
 
144
148
  const candidates = [["nmem"], ["uvx", "--from", "nmem-cli", "nmem"]];
145
149
 
146
- for (const cmd of candidates) {
147
- const [bin, ...baseArgs] = cmd;
148
- const result = spawnSync(bin, [...baseArgs, "--version"], {
149
- stdio: ["ignore", "pipe", "pipe"],
150
- timeout: 10_000,
151
- encoding: "utf-8",
152
- });
153
- if (result.status === 0) {
154
- this.nmemCmd = cmd;
155
- this.logger.info(`nmem resolved: ${cmd.join(" ")}`);
156
- return cmd;
150
+ this.nmemCmdPromise = (async () => {
151
+ for (const cmd of candidates) {
152
+ try {
153
+ const result = await this.runtimeSystem.runCommandWithTimeout(
154
+ [...cmd, "--version"],
155
+ {
156
+ timeoutMs: 10_000,
157
+ env: this._spawnEnv(),
158
+ },
159
+ );
160
+ if ((result.code ?? 1) === 0) {
161
+ this.nmemCmd = cmd;
162
+ this.logger.info(`nmem resolved: ${cmd.join(" ")}`);
163
+ return cmd;
164
+ }
165
+ } catch {
166
+ // Try the next candidate.
167
+ }
157
168
  }
158
- }
159
169
 
160
- throw new Error(
161
- "nmem CLI not found. Install with: pip install nmem-cli (or use uvx)",
162
- );
170
+ throw new Error(
171
+ "nmem CLI not found. Install with: pip install nmem-cli (or use uvx)",
172
+ );
173
+ })();
174
+
175
+ try {
176
+ return await this.nmemCmdPromise;
177
+ } finally {
178
+ this.nmemCmdPromise = null;
179
+ }
163
180
  }
164
181
 
165
182
  /**
@@ -167,15 +184,10 @@ export class NowledgeMemClient {
167
184
  * apiKey is injected here — NEVER via CLI args.
168
185
  */
169
186
  _spawnEnv() {
170
- const env = { ...process.env };
171
- // Explicit config wins over any existing env var
172
- if (this._apiUrl !== "http://127.0.0.1:14242") {
173
- env.NMEM_API_URL = this._apiUrl;
174
- }
175
- if (this._apiKey) {
176
- env.NMEM_API_KEY = this._apiKey;
177
- }
178
- return env;
187
+ return buildNmemSpawnEnv({
188
+ apiUrl: this._apiUrl,
189
+ apiKey: this._apiKey,
190
+ });
179
191
  }
180
192
 
181
193
  /**
@@ -188,41 +200,68 @@ export class NowledgeMemClient {
188
200
  : [];
189
201
  }
190
202
 
191
- exec(args, timeout = 30_000) {
192
- const cmd = this.resolveCommand();
193
- const [bin, ...baseArgs] = cmd;
203
+ _commandLabel(cmd, args) {
204
+ let i = cmd.length;
205
+ const argv = [...cmd, ...this._apiUrlArgs(), ...args];
206
+ while (i < argv.length) {
207
+ const token = argv[i];
208
+ if (token === "--api-url") {
209
+ i += 2;
210
+ continue;
211
+ }
212
+ break;
213
+ }
214
+ const route = argv
215
+ .slice(i, i + 2)
216
+ .filter(Boolean)
217
+ .join(" ");
218
+ return route ? `nmem ${route}` : cmd.join(" ");
219
+ }
220
+
221
+ _summarizeExecError(message) {
222
+ if (!message) return "failed";
223
+ if (message.includes("timed out")) return "timed out";
224
+ const codeMatch = message.match(/exited with code\s+(\d+)/i);
225
+ if (codeMatch) return `exited with code ${codeMatch[1]}`;
226
+ return "failed";
227
+ }
228
+
229
+ async exec(args, timeout = 30_000) {
230
+ const cmd = await this.resolveCommand();
231
+ const argv = [...cmd, ...this._apiUrlArgs(), ...args];
232
+ const commandLabel = this._commandLabel(cmd, args);
194
233
  try {
195
- const result = spawnSync(
196
- bin,
197
- [...baseArgs, ...this._apiUrlArgs(), ...args],
198
- {
199
- stdio: ["ignore", "pipe", "pipe"],
200
- timeout,
201
- encoding: "utf-8",
202
- env: this._spawnEnv(),
203
- },
204
- );
205
- if (result.error) {
206
- throw result.error;
234
+ const result = await this.runtimeSystem.runCommandWithTimeout(argv, {
235
+ timeoutMs: timeout,
236
+ env: this._spawnEnv(),
237
+ });
238
+ if (
239
+ result.termination === "timeout" ||
240
+ result.termination === "no-output-timeout"
241
+ ) {
242
+ const message =
243
+ (result.stderr || result.stdout || "").trim() ||
244
+ `nmem timed out after ${timeout}ms`;
245
+ throw new Error(message);
207
246
  }
208
- if (result.status !== 0) {
247
+ if ((result.code ?? 1) !== 0) {
209
248
  const message = (result.stderr || result.stdout || "").trim();
210
249
  throw new Error(
211
- message || `nmem exited with code ${String(result.status)}`,
250
+ message || `nmem exited with code ${String(result.code)}`,
212
251
  );
213
252
  }
214
253
  return String(result.stdout ?? "").trim();
215
254
  } catch (err) {
216
255
  const message = err instanceof Error ? err.message : String(err);
217
256
  this.logger.error(
218
- `nmem command failed: ${cmd.join(" ")} ${args.join(" ")} — ${message}`,
257
+ `nmem command failed (${commandLabel}): ${this._summarizeExecError(message)}`,
219
258
  );
220
259
  throw err;
221
260
  }
222
261
  }
223
262
 
224
- execJson(args, timeout = 30_000) {
225
- const raw = this.exec(args, timeout);
263
+ async execJson(args, timeout = 30_000) {
264
+ const raw = await this.exec(args, timeout);
226
265
  return JSON.parse(raw);
227
266
  }
228
267
 
@@ -237,7 +276,7 @@ export class NowledgeMemClient {
237
276
  100,
238
277
  Math.max(1, Math.trunc(Number(limit) || 5)),
239
278
  );
240
- const data = this.execJson([
279
+ const data = await this.execJson([
241
280
  "--json",
242
281
  "m",
243
282
  "search",
@@ -296,7 +335,7 @@ export class NowledgeMemClient {
296
335
 
297
336
  let data;
298
337
  try {
299
- data = this.execJson(args);
338
+ data = await this.execJson(args);
300
339
  } catch (err) {
301
340
  // Graceful fallback: CLI doesn't know --event-from yet → use API directly
302
341
  const message = err instanceof Error ? err.message : String(err);
@@ -388,7 +427,7 @@ export class NowledgeMemClient {
388
427
  ];
389
428
 
390
429
  try {
391
- return this.execJson(args);
430
+ return await this.execJson(args);
392
431
  } catch (err) {
393
432
  const message = err instanceof Error ? err.message : String(err);
394
433
  const needsFallback =
@@ -422,7 +461,7 @@ export class NowledgeMemClient {
422
461
  String(limit),
423
462
  ];
424
463
  try {
425
- return this.execJson(args);
464
+ return await this.execJson(args);
426
465
  } catch (err) {
427
466
  const message = err instanceof Error ? err.message : String(err);
428
467
  const needsFallback =
@@ -472,7 +511,7 @@ export class NowledgeMemClient {
472
511
  if (dateTo) args.push("--to", String(dateTo));
473
512
 
474
513
  try {
475
- const data = this.execJson(args);
514
+ const data = await this.execJson(args);
476
515
  return Array.isArray(data) ? data : (data.events ?? []);
477
516
  } catch (err) {
478
517
  const message = err instanceof Error ? err.message : String(err);
@@ -506,7 +545,7 @@ export class NowledgeMemClient {
506
545
  if (importance !== undefined && Number.isFinite(Number(importance))) {
507
546
  args.push("-i", String(importance));
508
547
  }
509
- const data = this.execJson(args);
548
+ const data = await this.execJson(args);
510
549
  return String(data.id ?? data.memory?.id ?? data.memory_id ?? "created");
511
550
  }
512
551
 
@@ -535,7 +574,7 @@ export class NowledgeMemClient {
535
574
  if (threadId) {
536
575
  args.push("--id", String(threadId));
537
576
  }
538
- data = this.execJson(args);
577
+ data = await this.execJson(args);
539
578
  } catch (err) {
540
579
  const message = err instanceof Error ? err.message : String(err);
541
580
  const needsApiFallback =
@@ -588,7 +627,7 @@ export class NowledgeMemClient {
588
627
  if (idempotencyKey) {
589
628
  args.push("--idempotency-key", String(idempotencyKey));
590
629
  }
591
- const data = this.execJson(args);
630
+ const data = await this.execJson(args);
592
631
  return {
593
632
  messagesAdded: Number(data.messages_added ?? 0),
594
633
  totalMessages: Number(data.total_messages ?? 0),
@@ -627,7 +666,7 @@ export class NowledgeMemClient {
627
666
  if (!id) {
628
667
  throw new Error("getMemory requires memoryId");
629
668
  }
630
- return this.execJson(["--json", "m", "show", id]);
669
+ return await this.execJson(["--json", "m", "show", id]);
631
670
  }
632
671
 
633
672
  isThreadNotFoundError(err) {
@@ -643,7 +682,7 @@ export class NowledgeMemClient {
643
682
 
644
683
  async readWorkingMemory() {
645
684
  try {
646
- const data = this.execJson(["--json", "wm", "read"], 15_000);
685
+ const data = await this.execJson(["--json", "wm", "read"], 15_000);
647
686
  const content = String(data.content ?? "").trim();
648
687
  const exists = Boolean(data.exists);
649
688
  return { content, available: exists && content.length > 0 };
@@ -667,7 +706,7 @@ export class NowledgeMemClient {
667
706
  if (append !== undefined) args.push("--append", String(append));
668
707
 
669
708
  try {
670
- return this.execJson(args, 20_000);
709
+ return await this.execJson(args, 20_000);
671
710
  } catch (err) {
672
711
  const message = err instanceof Error ? err.message : String(err);
673
712
  const notSupported =
@@ -772,7 +811,7 @@ export class NowledgeMemClient {
772
811
  * Unlike searchThreads() (best-effort, swallows errors), this throws on failure
773
812
  * and supports additional parameters.
774
813
  *
775
- * Uses CLI: `nmem --json t search "<query>" --limit N`
814
+ * Uses CLI: `nmem --json t search "<query>" -n N`
776
815
  * Falls back to API: GET /threads/search
777
816
  *
778
817
  * @param {string} query Search keywords
@@ -789,13 +828,13 @@ export class NowledgeMemClient {
789
828
  "t",
790
829
  "search",
791
830
  String(query || ""),
792
- "--limit",
831
+ "-n",
793
832
  String(normalizedLimit),
794
833
  ];
795
834
  if (source) args.push("--source", String(source));
796
835
 
797
836
  try {
798
- const data = this.execJson(args);
837
+ const data = await this.execJson(args);
799
838
  return {
800
839
  threads: data.threads ?? [],
801
840
  totalFound: Number(data.total_found ?? data.total ?? 0),
@@ -830,7 +869,7 @@ export class NowledgeMemClient {
830
869
  * Fetch full messages from a specific thread.
831
870
  * Supports pagination for progressive retrieval of long conversations.
832
871
  *
833
- * Uses CLI: `nmem --json t show <id> --limit N --offset O`
872
+ * Uses CLI: `nmem --json t show <id> -n N --offset O`
834
873
  * Falls back to API: GET /threads/{id}
835
874
  *
836
875
  * @param {string} threadId Thread ID
@@ -846,20 +885,13 @@ export class NowledgeMemClient {
846
885
  );
847
886
  const normalizedOffset = Math.max(0, Math.trunc(Number(offset) || 0));
848
887
 
849
- const args = [
850
- "--json",
851
- "t",
852
- "show",
853
- id,
854
- "--limit",
855
- String(normalizedLimit),
856
- ];
888
+ const args = ["--json", "t", "show", id, "-n", String(normalizedLimit)];
857
889
  if (normalizedOffset > 0) {
858
890
  args.push("--offset", String(normalizedOffset));
859
891
  }
860
892
 
861
893
  try {
862
- const data = this.execJson(args);
894
+ const data = await this.execJson(args);
863
895
  return {
864
896
  threadId: data.thread_id ?? data.id ?? id,
865
897
  title: data.title ?? "(untitled)",
@@ -906,7 +938,7 @@ export class NowledgeMemClient {
906
938
 
907
939
  async checkHealth() {
908
940
  try {
909
- this.exec(["status"]);
941
+ await this.execJson(["--json", "status"]);
910
942
  return true;
911
943
  } catch {
912
944
  return false;
@@ -38,7 +38,7 @@ export function createForgetCommand(client, logger) {
38
38
 
39
39
  try {
40
40
  if (isLikelyId) {
41
- client.exec(["--json", "m", "delete", "-f", text]);
41
+ await client.exec(["--json", "m", "delete", "-f", text]);
42
42
  logger.info(`/forget: deleted memory ${text}`);
43
43
  return { text: `Forgotten: memory ${text} deleted.` };
44
44
  }
@@ -50,7 +50,7 @@ export function createForgetCommand(client, logger) {
50
50
 
51
51
  if (results[0].score >= 0.85) {
52
52
  const target = results[0];
53
- client.exec(["--json", "m", "delete", "-f", target.id]);
53
+ await client.exec(["--json", "m", "delete", "-f", target.id]);
54
54
  logger.info(`/forget: deleted memory ${target.id}`);
55
55
  const preview = target.title || target.content.slice(0, 60);
56
56
  return {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * This is the single most impactful change for memory adoption:
5
5
  * most LLMs ignore "call this proactively" in tool descriptions,
6
- * but reliably follow behavioral guidance in prepended context.
6
+ * but reliably follow behavioral guidance in system-prompt space.
7
7
  *
8
8
  * Cost: ~50 tokens per turn. Negligible.
9
9
  *
@@ -15,11 +15,11 @@
15
15
  const BASE_GUIDANCE = [
16
16
  "<nowledge-mem-guidance>",
17
17
  "You have access to the user's personal knowledge graph (Nowledge Mem).",
18
- "When the conversation produces something worth keeping — a decision made, a preference stated,",
19
- "something learned, a plan formed, a useful discovery — save it with nowledge_mem_save. Don't wait to be asked.",
20
- "When prior context would improve your response, search with memory_search.",
21
- "memory_search also returns relevant past conversation snippets alongside memories.",
22
- "When a memory has a sourceThreadId, fetch the full conversation with nowledge_mem_thread_fetch.",
18
+ "Before answering questions about prior work, decisions, dates, people, preferences, or plans:",
19
+ "search with memory_search using natural language queries (not file paths).",
20
+ "memory_search returns memories with scores, labels, and relevant past conversation snippets.",
21
+ "When results include a sourceThreadId, use nowledge_mem_thread_fetch for full conversation context.",
22
+ "When the conversation produces a decision, preference, plan, or learning, save it with nowledge_mem_save.",
23
23
  "</nowledge-mem-guidance>",
24
24
  ].join("\n");
25
25
 
@@ -27,10 +27,10 @@ const SESSION_CONTEXT_GUIDANCE = [
27
27
  "<nowledge-mem-guidance>",
28
28
  "You have access to the user's personal knowledge graph (Nowledge Mem).",
29
29
  "Relevant memories and your Working Memory have already been injected into this prompt.",
30
- "Use memory_search only when you need something specific beyond what was auto-recalled.",
31
- "When the conversation produces something worth keeping — a decision made, a preference stated,",
32
- "something learned, a plan formed, a useful discovery — save it with nowledge_mem_save. Don't wait to be asked.",
33
- "When a memory has a sourceThreadId, fetch the full conversation with nowledge_mem_thread_fetch.",
30
+ "Use memory_search when you need something specific beyond what was auto-recalled (natural language queries, not file paths).",
31
+ "memory_search also returns relevant past conversation snippets alongside memories.",
32
+ "When results include a sourceThreadId, use nowledge_mem_thread_fetch for full conversation context.",
33
+ "When the conversation produces a decision, preference, plan, or learning, save it with nowledge_mem_save.",
34
34
  "</nowledge-mem-guidance>",
35
35
  ].join("\n");
36
36
 
@@ -38,6 +38,6 @@ export function buildBehavioralHook(logger, { sessionContext = false } = {}) {
38
38
  const guidance = sessionContext ? SESSION_CONTEXT_GUIDANCE : BASE_GUIDANCE;
39
39
  return (_event, _ctx) => {
40
40
  logger.debug?.("behavioral: injecting guidance");
41
- return { prependContext: guidance };
41
+ return { appendSystemContext: guidance };
42
42
  };
43
43
  }
package/src/index.js CHANGED
@@ -33,7 +33,7 @@ export default {
33
33
  register(api) {
34
34
  const logger = api.logger;
35
35
  const cfg = parseConfig(api.pluginConfig, logger);
36
- const client = new NowledgeMemClient(logger, {
36
+ const client = new NowledgeMemClient(logger, api.runtime.system, {
37
37
  apiUrl: cfg.apiUrl,
38
38
  apiKey: cfg.apiKey,
39
39
  });
@@ -0,0 +1,19 @@
1
+ const LOCAL_API_URL = "http://127.0.0.1:14242";
2
+
3
+ /**
4
+ * Build the child env for nmem CLI execution.
5
+ * apiKey stays in env only and is never passed as a CLI flag.
6
+ * Clear inherited NMEM_* values first so the resolved plugin config wins.
7
+ */
8
+ export function buildNmemSpawnEnv({ apiUrl, apiKey } = {}) {
9
+ const env = { ...process.env };
10
+ env.NMEM_API_URL = undefined;
11
+ env.NMEM_API_KEY = undefined;
12
+ if (apiUrl && apiUrl !== LOCAL_API_URL) {
13
+ env.NMEM_API_URL = apiUrl;
14
+ }
15
+ if (apiKey) {
16
+ env.NMEM_API_KEY = apiKey;
17
+ }
18
+ return env;
19
+ }
@@ -46,7 +46,7 @@ export function createForgetTool(client, logger) {
46
46
  // Mode 1: Direct delete by ID
47
47
  if (memoryId) {
48
48
  try {
49
- client.exec(["--json", "m", "delete", "-f", memoryId]);
49
+ await client.exec(["--json", "m", "delete", "-f", memoryId]);
50
50
  logger.info(`forget: deleted memory ${memoryId}`);
51
51
  return {
52
52
  content: [
@@ -92,7 +92,7 @@ export function createForgetTool(client, logger) {
92
92
  if (results.length === 1 && results[0].score >= 0.85) {
93
93
  const target = results[0];
94
94
  try {
95
- client.exec(["--json", "m", "delete", "-f", target.id]);
95
+ await client.exec(["--json", "m", "delete", "-f", target.id]);
96
96
  logger.info(`forget: deleted memory ${target.id} via search`);
97
97
  return {
98
98
  content: [
package/src/tools/save.js CHANGED
@@ -169,7 +169,7 @@ export function createSaveTool(client, logger) {
169
169
  if (eventEnd) args.push("--event-end", eventEnd);
170
170
  if (temporalContext) args.push("--when", temporalContext);
171
171
 
172
- const data = client.execJson(args);
172
+ const data = await client.execJson(args);
173
173
  const id = String(
174
174
  data.id ?? data.memory?.id ?? data.memory_id ?? "created",
175
175
  );
@@ -37,7 +37,7 @@ export function createStatusTool(client, _logger, cfg) {
37
37
 
38
38
  // 2. CLI resolution
39
39
  try {
40
- const cmd = client.resolveCommand();
40
+ const cmd = await client.resolveCommand();
41
41
  details.cli = cmd.join(" ");
42
42
  lines.push(`CLI: ${cmd.join(" ")}`);
43
43
  } catch {