Conversation
📝 WalkthroughWalkthroughThis PR updates integration registry versions across multiple language model platforms and implements automated thread capture via lifecycle hooks. It adds Stop-hook-based transcript capture for Codex and Claude Code, introduces a post-LLM-call fallback for Hermes, fixes configuration cascade issues in OpenClaw, normalizes SDK response shapes in OpenCode, and expands E2E test coverage to validate the new capture mechanisms across live-agent scenarios. ChangesAutomated Thread Capture & Integration Alignment
Sequence Diagram(s)sequenceDiagram
actor User as User / Agent
participant Claude as Claude Code
participant Hook as Stop Hook<br/>(nmem-hook-save.py)
participant CLI as nmem CLI
participant Mem as Nowledge Mem
User->>Claude: Interact (turn completed)
Claude->>Hook: Trigger Stop lifecycle event
Hook->>Hook: Read session_id & project path
Hook->>Hook: Resolve symlink (if macOS)
Hook->>CLI: Run nmem t save --from claude-code<br/>--json (with retries)
alt JSON supported
CLI-->>Hook: {results: [{action: "created"}]}
Hook->>Mem: ✓ Capture succeeded
else JSON unsupported
CLI-->>Hook: Error: unknown option --json
Hook->>Hook: Detect incompatibility
Hook->>CLI: Rerun without --json (legacy)
CLI-->>Hook: Exit 0 (non-JSON output)
Hook->>Mem: ✓ Capture succeeded (legacy)
end
Hook-->>Claude: Capture complete
sequenceDiagram
actor User as User
participant Codex as Codex Agent
participant Config as config.toml<br/>(codex_hooks=true)
participant HookRuntime as ~/.codex/hooks.json<br/>(Stop hook)
participant Script as nmem-stop-save.py
participant CLI as nmem CLI
participant Mem as Nowledge Mem
User->>User: Run codex setup
User->>Script: Install hooks via<br/>scripts/install_hooks.py
Script->>Config: Enable codex_hooks feature
Script->>HookRuntime: Merge/install Stop hook
Script-->>User: ✓ Setup complete
User->>Codex: Chat (turn completed)
Codex->>HookRuntime: Trigger on Stop event
HookRuntime->>Script: Execute nmem-stop-save.py
Script->>Script: Read session_id from payload
Script->>CLI: nmem t save --from codex<br/>--session-id <id> --json (with retry)
alt Saved result received
CLI-->>Script: {results: [{action: "created"}]}
Script->>Mem: ✓ Turn captured
else Session-id lookup miss
Script->>Script: Detect lookup miss marker
Script->>CLI: Retry without --session-id
CLI-->>Script: Success (latest session)
Script->>Mem: ✓ Turn captured (latest)
end
Script-->>Codex: Exit 0 (non-fatal)
sequenceDiagram
participant Hermes as Hermes Agent
participant Provider as NowledgeMemProvider<br/>(via plugin __init__)
participant Fallback as Fallback Hook<br/>(post_llm_call)
participant Client as NowledgeMemClient
participant Mem as Nowledge Mem
Hermes->>Hermes: Check plugin registration path
alt Full provider API available
Hermes->>Provider: Register memory_provider
Provider->>Client: Configured & ready
else Hook-only (older Hermes)
Hermes->>Fallback: Register post_llm_call hook
Fallback->>Provider: Lazy-initialize on first call
end
Hermes->>Hermes: LLM turn completed
alt Provider path
Hermes->>Provider: on_memory_write (or via lifecycle)
Provider->>Provider: sync_turn(user, assistant)
Provider->>Client: import_thread or append_thread
Client->>Mem: POST /threads
else Hook path
Hermes->>Fallback: post_llm_call hook fired
Fallback->>Provider: Extract session_id, user, assistant
Fallback->>Provider: sync_turn(...)
Provider->>Client: import_thread or append_thread
Client->>Mem: POST /threads
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
nowledge-mem-opencode-plugin/src/index.ts (1)
190-215: ⚡ Quick win
normalizeSessionMessageslooks robust; consider logging infetchSessionMessages's catch block.The
{ path: { id }, query }shape in the first attempt matches the current OpenCode SDK convention (the official docs showclient.session.get({ path: { id: "invalid-id" } })as the canonical pattern), so the first attempt should succeed on current builds and the second acts as a correct legacy fallback.However, the empty
catch {}at line 210 silently swallows every exception — including real failures like a dead server, auth errors, or a future SDK breaking change. When both attempts fail, the caller receives[]and returns"No messages found in current session", with no diagnostic signal about why. A singleconsole.warn(orclient.app.logif the client is in scope) would let users distinguish a genuine empty session from a transport/API failure without changing any behavior.🔍 Suggested change
- try { - const messages = normalizeSessionMessages(await client.session.messages(options as any)) - if (messages.length > 0) return messages - } catch { - // Keep compatibility across OpenCode SDK shapes. - } + try { + const messages = normalizeSessionMessages(await client.session.messages(options as any)) + if (messages.length > 0) return messages + } catch (err: any) { + // Keep compatibility across OpenCode SDK shapes; log for diagnosability. + console.warn("[nowledge-mem] session.messages attempt failed:", err?.message ?? err) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nowledge-mem-opencode-plugin/src/index.ts` around lines 190 - 215, The empty catch in fetchSessionMessages silently swallows errors; update the catch in fetchSessionMessages (the try around client.session.messages calls) to log the caught error details (e.g., using console.warn or client.app.log if client is available) along with context such as the options/attempt being used and the sessionID so transient transport/auth/SDK errors are visible while preserving the existing fallback behavior to the legacy attempt and the final return of [].nowledge-mem-hermes/tests/test_space_resolution.py (1)
283-336: ⚡ Quick winAdd a negative test to assert
space_idis absent when no space is configured.The updated test now covers the positive path, but there is no companion assertion confirming that
space_idis not included in the payload whenNowledgeMemClient()is constructed without aspaceargument. Without it, silently removing theif self._has_explicit_space and self._space:guard inclient.pywould go undetected.✅ Suggested companion test
def test_thread_append_omits_space_id_when_no_space_configured(self): captured: dict[str, object] = {} original_run = client_module.subprocess.run original_urlopen = client_module.urlrequest.urlopen previous_url = os.environ.get("NMEM_API_URL") previous_key = os.environ.get("NMEM_API_KEY") os.environ["NMEM_API_URL"] = "http://mem.test" os.environ["NMEM_API_KEY"] = "" def _bad_run(*_args, **_kwargs): raise AssertionError("thread append must not use subprocess") class _Response: def __enter__(self): return self def __exit__(self, *_): return False def read(self): return b'{"success": true, "messages_added": 1}' def _fake_urlopen(request, **_kwargs): captured["body"] = request.data.decode("utf-8") return _Response() try: client_module.subprocess.run = _bad_run client_module.urlrequest.urlopen = _fake_urlopen # No space argument — space_id must not appear in payload client = client_module.NowledgeMemClient() client.append_thread("s1", [{"role": "user", "content": "hello"}]) payload = json.loads(captured["body"]) self.assertNotIn("space_id", payload) finally: client_module.subprocess.run = original_run client_module.urlrequest.urlopen = original_urlopen if previous_url is None: os.environ.pop("NMEM_API_URL", None) else: os.environ["NMEM_API_URL"] = previous_url if previous_key is None: os.environ.pop("NMEM_API_KEY", None) else: os.environ["NMEM_API_KEY"] = previous_key🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nowledge-mem-hermes/tests/test_space_resolution.py` around lines 283 - 336, Add a negative test that constructs NowledgeMemClient() without the space argument and asserts that the outgoing append_thread payload does not include "space_id": copy the mocking pattern from test_thread_append_posts_payload_without_subprocess_argv (override client_module.subprocess.run to raise, monkeypatch client_module.urlrequest.urlopen to capture request.data in captured["body"], create a fake response that returns success), call client = client_module.NowledgeMemClient() then client.append_thread(...), parse json from captured["body"] and assert "space_id" is not in the payload; this will validate the guard around NowledgeMemClient._has_explicit_space / NowledgeMemClient._space in client.py remains effective.nowledge-mem-opencode-plugin/package.json (1)
31-33: Consider tightening the@opencode-ai/pluginpeer dependency lower bound to^1.3.7.The
{ path: { id } }call shape forclient.session.messages()was introduced in version 1.3.7 or earlier. The current peer dependency^1.3.0permits versions 1.3.0–1.3.6, which would not support the primary call pattern thatfetchSessionMessagestargets on line 208. While the code includes a fallback attempt with{ sessionID }syntax for broader compatibility, tightening the lower bound to^1.3.7would eliminate the risk of silent failures on outdated SDK versions and clarify the actual minimum SDK requirement for this plugin.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nowledge-mem-opencode-plugin/package.json` around lines 31 - 33, Update the package peer dependency for `@opencode-ai/plugin` from "^1.3.0" to "^1.3.7" to reflect the minimum SDK that implements the { path: { id } } call shape used by fetchSessionMessages; this ensures client.session.messages() supports the { path: { id } } signature (and avoids relying solely on the fallback using { sessionID }) and clearly documents the required minimum version for the plugin.nowledge-mem-codex-plugin/scripts/install_hooks.py (1)
139-148: 💤 Low valueTOML validation doesn't prevent writing if input is invalid.
_validate_toml_if_possibleparses the existing config but doesn't raise or warn if parsing fails. If the existingconfig.tomlis malformed, the script will proceed to modify it with string manipulation, potentially making it worse or creating a hybrid invalid file.Consider either warning the user or backing up invalid TOML before modification.
♻️ Proposed enhancement
def _validate_toml_if_possible(text: str) -> None: if not text.strip(): return try: import tomllib tomllib.loads(text) - except ModuleNotFoundError: + except ModuleNotFoundError: + return + except Exception as exc: + print(f"warning: existing config.toml may be invalid: {exc}", file=sys.stderr) return🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nowledge-mem-codex-plugin/scripts/install_hooks.py` around lines 139 - 148, The _validate_toml_if_possible function currently swallows TOML parse errors and does nothing, allowing the script to continue modifying an invalid config; update _validate_toml_if_possible to catch tomllib.TOMLDecodeError (or generic Exception if tomllib lacks that name) and either raise a descriptive exception or return a flag indicating invalid TOML, then update callers (e.g., the code that writes/edits config.toml) to abort or create a backup copy of the existing file before making changes and log a clear warning including the parse error; reference the function name _validate_toml_if_possible and ensure callers check its result or handle the raised exception to avoid in-place modifications of malformed TOML.nowledge-mem-hermes/__init__.py (1)
60-64: 💤 Low valueConsider logging shutdown failures for diagnostics.
The static analysis tool flagged the silent
try-except-passpattern. While swallowing shutdown errors is appropriate here (the provider is being replaced anyway), logging at debug level would help diagnose issues without affecting runtime.♻️ Proposed fix
if _fallback_provider is not None: try: _fallback_provider.shutdown() - except Exception: - pass + except Exception as exc: + logger.debug("Fallback provider shutdown error (ignored): %s", exc)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nowledge-mem-hermes/__init__.py` around lines 60 - 64, The silent except in the shutdown of _fallback_provider should log the exception at debug level for diagnostics: wrap the call to _fallback_provider.shutdown() in the existing try/except, capture the Exception as e, and call a module logger (e.g. logger = logging.getLogger(__name__)) with logger.debug("Fallback provider shutdown failed", exc_info=e) or logger.debug(..., exc_info=True) so the stacktrace is available; keep swallowing the exception after logging so behavior is unchanged and reference the symbol _fallback_provider.shutdown to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/plugin_e2e/test_key_plugins_e2e.py`:
- Around line 340-344: The test calls the install_hooks.py script which writes
to the real ~/.codex; modify the test to isolate Codex state by creating a
temporary directory and setting CODEX_HOME in a fresh env dict, pass that env to
_run (the invocation of install_hooks.py) and to any subsequent Codex-related
commands instead of reusing e2e_context.env, and ensure the temp directory is
cleaned up after the test; locate the call to _run in test_key_plugins_e2e.py
and update it to use the temp CODEX_HOME environment variable when invoking
install_hooks.py and later Codex operations.
---
Nitpick comments:
In `@nowledge-mem-codex-plugin/scripts/install_hooks.py`:
- Around line 139-148: The _validate_toml_if_possible function currently
swallows TOML parse errors and does nothing, allowing the script to continue
modifying an invalid config; update _validate_toml_if_possible to catch
tomllib.TOMLDecodeError (or generic Exception if tomllib lacks that name) and
either raise a descriptive exception or return a flag indicating invalid TOML,
then update callers (e.g., the code that writes/edits config.toml) to abort or
create a backup copy of the existing file before making changes and log a clear
warning including the parse error; reference the function name
_validate_toml_if_possible and ensure callers check its result or handle the
raised exception to avoid in-place modifications of malformed TOML.
In `@nowledge-mem-hermes/__init__.py`:
- Around line 60-64: The silent except in the shutdown of _fallback_provider
should log the exception at debug level for diagnostics: wrap the call to
_fallback_provider.shutdown() in the existing try/except, capture the Exception
as e, and call a module logger (e.g. logger = logging.getLogger(__name__)) with
logger.debug("Fallback provider shutdown failed", exc_info=e) or
logger.debug(..., exc_info=True) so the stacktrace is available; keep swallowing
the exception after logging so behavior is unchanged and reference the symbol
_fallback_provider.shutdown to locate the change.
In `@nowledge-mem-hermes/tests/test_space_resolution.py`:
- Around line 283-336: Add a negative test that constructs NowledgeMemClient()
without the space argument and asserts that the outgoing append_thread payload
does not include "space_id": copy the mocking pattern from
test_thread_append_posts_payload_without_subprocess_argv (override
client_module.subprocess.run to raise, monkeypatch
client_module.urlrequest.urlopen to capture request.data in captured["body"],
create a fake response that returns success), call client =
client_module.NowledgeMemClient() then client.append_thread(...), parse json
from captured["body"] and assert "space_id" is not in the payload; this will
validate the guard around NowledgeMemClient._has_explicit_space /
NowledgeMemClient._space in client.py remains effective.
In `@nowledge-mem-opencode-plugin/package.json`:
- Around line 31-33: Update the package peer dependency for `@opencode-ai/plugin`
from "^1.3.0" to "^1.3.7" to reflect the minimum SDK that implements the { path:
{ id } } call shape used by fetchSessionMessages; this ensures
client.session.messages() supports the { path: { id } } signature (and avoids
relying solely on the fallback using { sessionID }) and clearly documents the
required minimum version for the plugin.
In `@nowledge-mem-opencode-plugin/src/index.ts`:
- Around line 190-215: The empty catch in fetchSessionMessages silently swallows
errors; update the catch in fetchSessionMessages (the try around
client.session.messages calls) to log the caught error details (e.g., using
console.warn or client.app.log if client is available) along with context such
as the options/attempt being used and the sessionID so transient
transport/auth/SDK errors are visible while preserving the existing fallback
behavior to the legacy attempt and the final return of [].
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b8edbd26-750a-4ef4-89dd-7b1d67243f5f
⛔ Files ignored due to path filters (1)
nowledge-mem-openclaw-plugin/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (39)
integrations.jsonnowledge-mem-claude-code-plugin/.claude-plugin/plugin.jsonnowledge-mem-claude-code-plugin/CHANGELOG.mdnowledge-mem-claude-code-plugin/README.mdnowledge-mem-claude-code-plugin/hooks/hooks.jsonnowledge-mem-claude-code-plugin/scripts/nmem-hook-save.pynowledge-mem-claude-code-plugin/tests/test_nmem_hook_save.pynowledge-mem-codex-plugin/.codex-plugin/plugin.jsonnowledge-mem-codex-plugin/AGENTS.mdnowledge-mem-codex-plugin/CHANGELOG.mdnowledge-mem-codex-plugin/README.mdnowledge-mem-codex-plugin/codex.config.example.tomlnowledge-mem-codex-plugin/hooks/hooks.jsonnowledge-mem-codex-plugin/hooks/nmem-stop-save.pynowledge-mem-codex-plugin/scripts/install_hooks.pynowledge-mem-codex-plugin/scripts/validate-plugin.mjsnowledge-mem-codex-plugin/tests/test_codex_plugin.pynowledge-mem-hermes/CHANGELOG.mdnowledge-mem-hermes/README.mdnowledge-mem-hermes/__init__.pynowledge-mem-hermes/client.pynowledge-mem-hermes/plugin.yamlnowledge-mem-hermes/provider.pynowledge-mem-hermes/setup.shnowledge-mem-hermes/tests/test_session_sync.pynowledge-mem-hermes/tests/test_setup.shnowledge-mem-hermes/tests/test_space_resolution.pynowledge-mem-npx-skills/skills/check-integration/SKILL.mdnowledge-mem-openclaw-plugin/CHANGELOG.mdnowledge-mem-openclaw-plugin/openclaw.plugin.jsonnowledge-mem-openclaw-plugin/package.jsonnowledge-mem-openclaw-plugin/src/config.jsnowledge-mem-openclaw-plugin/tests/space-config.test.mjsnowledge-mem-opencode-plugin/CHANGELOG.mdnowledge-mem-opencode-plugin/README.mdnowledge-mem-opencode-plugin/package.jsonnowledge-mem-opencode-plugin/src/index.tstests/plugin_e2e/README.mdtests/plugin_e2e/test_key_plugins_e2e.py
Summary
Release-ready update for the major Nowledge Mem agent integrations.
NMEM_SPACEcan still scope release/test profiles.Validation
uv run --with pytest pytest tests/plugin_e2e -qnode nowledge-mem-codex-plugin/scripts/validate-plugin.mjsnpm testinnowledge-mem-openclaw-pluginnode scripts/validate-plugin.mjs && npm pack --dry-runinnowledge-mem-openclaw-pluginnpm pack --dry-runinnowledge-mem-opencode-pluginpython3 nowledge-mem-codex-plugin/tests/test_codex_plugin.pypython3 nowledge-mem-hermes/tests/test_space_resolution.pyRelease Notes
0.8.23is published to ClawHub.nmem-cliis already released.@nowledge/openclaw-nowledge-mem@0.8.23andopencode-nowledge-mem@0.3.3.