Add Proma integration plugin for Nowledge Mem#232
Conversation
Proma is a desktop AI agent built on Claude Agent SDK. This plugin provides the three-layer integration pattern: - MCP server config (mcp.json with "servers" top-level key) - Lifecycle hooks (Stop: auto-capture sessions, SessionStart: inject WM) - Companion skill (nmem: /nmem-save, /nmem-search, /nmem-status) Sessions are parsed from Proma's JSONL format (~/.proma/agent-sessions/<id>.jsonl) and uploaded to nmem via REST API. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
635dcaa to
9d1c4c1
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
integrations.json (2)
605-605: ⚖️ Poor tradeoffConsider updating docsUrl to follow the established pattern.
Most integrations with a
directoryfield use the pattern"/docs/integrations/<id>"for theirdocsUrl. For example:
- Cursor:
"/docs/integrations/cursor"- Codex:
"/docs/integrations/codex-cli"External repo links are typically reserved for integrations without a
directoryfield. Since Proma has"directory": "nowledge-mem-proma-plugin", consider using"/docs/integrations/proma"and creating the corresponding docs page.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@integrations.json` at line 605, The current integration entry uses an external repo URL in the "docsUrl" field despite having "directory": "nowledge-mem-proma-plugin"; update the "docsUrl" value to the canonical internal docs path "/docs/integrations/proma" and ensure a corresponding docs page is created under that route so the integration follows the established pattern; modify the integrations.json entry (the "docsUrl" property for the Proma integration) to the new path and add the docs page for the "proma" integration.
583-586: ⚡ Quick winConsider using "plugin-capture" instead of "hook" for threadSave.method.
The value
"hook"appears to be new and inconsistent with existing patterns. The Copilot CLI integration (line 70) uses"plugin-capture"with a similar approach: "Python script reads Copilot CLI transcript events from Stop, PreCompact, and SessionEnd hooks, creates threads via nmem t import."The Proma implementation follows the same pattern (hook script reads session data and uploads). For consistency with the existing registry schema, consider using
"plugin-capture".Proposed fix
"threadSave": { - "method": "hook", + "method": "plugin-capture", "note": "Stop hook runs save-to-nmem.py which parses Proma session JSONL from ~/.proma/agent-sessions/ and uploads messages to nmem via REST API. SessionStart hook injects Working Memory via read-working-memory.py." },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@integrations.json` around lines 583 - 586, The "threadSave" integration entry uses method: "hook" which is inconsistent with the existing registry pattern; update the threadSave object's "method" property from "hook" to "plugin-capture" so it matches similar integrations (e.g., the Copilot CLI entry) and preserves the intent that a Python script reads Proma session JSONL and uploads threads via nmem; ensure the surrounding metadata/note remains unchanged and validate the JSON after the edit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@integrations.json`:
- Around line 587-591: Update the autonomy settings for the "autonomy"
integration entry so that the "recall" and "distill" fields use "guided" instead
of "automatic" (i.e., change the values for the "recall" and "distill" keys
inside the "autonomy" object) to match the other MCP-transport integrations'
pattern.
In `@nowledge-mem-proma-plugin/hooks/hooks.json`:
- Line 8: Clarify in the README that the hooks path can be resolved two ways:
either set the PROMA_HOME environment variable (export
PROMA_HOME=/path/to/proma) so entries like "${PROMA_HOME}/hooks/save-to-nmem.py"
are expanded, or replace "${PROMA_HOME}" with a literal path (e.g.,
"~/.proma/hooks/...") in hooks.json; explicitly mention that the Python scripts
save-to-nmem.py and read-working-memory.py default to "~/.proma" when PROMA_HOME
is unset and provide one clear example for each approach so users know which
option to follow.
In `@nowledge-mem-proma-plugin/hooks/save-to-nmem.py`:
- Around line 204-210: Add a brief clarifying comment above the assistant
message filter (the block starting with elif msg_type == "assistant") explaining
why we skip entries where role == "user" (e.g., whether this handles a known
Proma format quirk, is a defensive check for malformed records, or prevents
duplicate processing), and reference the relevant symbols in the comment
(msg_type, role, extract_text_from_content, and messages) so future maintainers
understand the intent and won't remove the check.
---
Nitpick comments:
In `@integrations.json`:
- Line 605: The current integration entry uses an external repo URL in the
"docsUrl" field despite having "directory": "nowledge-mem-proma-plugin"; update
the "docsUrl" value to the canonical internal docs path
"/docs/integrations/proma" and ensure a corresponding docs page is created under
that route so the integration follows the established pattern; modify the
integrations.json entry (the "docsUrl" property for the Proma integration) to
the new path and add the docs page for the "proma" integration.
- Around line 583-586: The "threadSave" integration entry uses method: "hook"
which is inconsistent with the existing registry pattern; update the threadSave
object's "method" property from "hook" to "plugin-capture" so it matches similar
integrations (e.g., the Copilot CLI entry) and preserves the intent that a
Python script reads Proma session JSONL and uploads threads via nmem; ensure the
surrounding metadata/note remains unchanged and validate the JSON after the
edit.
🪄 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: 3dd31d8b-95cf-4245-9202-001c96494d82
📒 Files selected for processing (8)
integrations.jsonnowledge-mem-proma-plugin/.claude-plugin/plugin.jsonnowledge-mem-proma-plugin/CHANGELOG.mdnowledge-mem-proma-plugin/README.mdnowledge-mem-proma-plugin/hooks/hooks.jsonnowledge-mem-proma-plugin/hooks/read-working-memory.pynowledge-mem-proma-plugin/hooks/save-to-nmem.pynowledge-mem-proma-plugin/skills/nmem/SKILL.md
| "autonomy": { | ||
| "bootstrap": "automatic", | ||
| "recall": "automatic", | ||
| "distill": "automatic", | ||
| "threads": "automatic-capture", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Compare autonomy settings for MCP-transport integrations
jq '.integrations[] | select(.transport == "mcp" or .transport == "cli+mcp" or .transport == "hybrid-cli+mcp") | {id, transport, recall: .autonomy.recall, distill: .autonomy.distill}' integrations.jsonRepository: nowledge-co/community
Length of output: 625
🏁 Script executed:
fd -t f "SKILL.md" nowledge-mem-proma-pluginRepository: nowledge-co/community
Length of output: 112
🏁 Script executed:
cat -n nowledge-mem-proma-plugin/skills/nmem/SKILL.md | head -40Repository: nowledge-co/community
Length of output: 2296
Change proma's autonomy levels to match other MCP integrations.
The skill documentation confirms that recall and distill require agent interpretation:
- Recall ("When user says 'before', 'last time'...") is reactive to user language cues, not truly automatic
- Distill ("After key decisions...") requires the agent to recognize when a key decision occurs
All other MCP-transport integrations (cursor, claude-desktop, mcp-direct, gemini-cli, codex-cli) use "guided" for both recall and distill. Proma should follow this pattern:
Proposed fix
"autonomy": {
"bootstrap": "automatic",
- "recall": "automatic",
- "distill": "automatic",
+ "recall": "guided",
+ "distill": "guided",
"threads": "automatic-capture",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "autonomy": { | |
| "bootstrap": "automatic", | |
| "recall": "automatic", | |
| "distill": "automatic", | |
| "threads": "automatic-capture", | |
| "autonomy": { | |
| "bootstrap": "automatic", | |
| "recall": "guided", | |
| "distill": "guided", | |
| "threads": "automatic-capture", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@integrations.json` around lines 587 - 591, Update the autonomy settings for
the "autonomy" integration entry so that the "recall" and "distill" fields use
"guided" instead of "automatic" (i.e., change the values for the "recall" and
"distill" keys inside the "autonomy" object) to match the other MCP-transport
integrations' pattern.
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "python \"${PROMA_HOME}/hooks/save-to-nmem.py\"", |
There was a problem hiding this comment.
Clarify PROMA_HOME variable usage vs hardcoded path.
The hooks configuration uses "${PROMA_HOME}/hooks/save-to-nmem.py" and "${PROMA_HOME}/hooks/read-working-memory.py", but the README (lines 60, 73) instructs users to replace <proma-home> with "your actual Proma home path."
This creates ambiguity:
- Should users set the
PROMA_HOMEenvironment variable? - Should users replace
${PROMA_HOME}with a hardcoded path like~/.proma?
The Python scripts default to ~/.proma if PROMA_HOME is unset (e.g., save-to-nmem.py line 55), so the hooks should work either way, but the installation instructions should clarify:
- Either explicitly tell users to use
~/.proma/hooks/...(most straightforward) - Or explain that
${PROMA_HOME}must be set as an environment variable for Proma to expand
Also applies to: 21-21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nowledge-mem-proma-plugin/hooks/hooks.json` at line 8, Clarify in the README
that the hooks path can be resolved two ways: either set the PROMA_HOME
environment variable (export PROMA_HOME=/path/to/proma) so entries like
"${PROMA_HOME}/hooks/save-to-nmem.py" are expanded, or replace "${PROMA_HOME}"
with a literal path (e.g., "~/.proma/hooks/...") in hooks.json; explicitly
mention that the Python scripts save-to-nmem.py and read-working-memory.py
default to "~/.proma" when PROMA_HOME is unset and provide one clear example for
each approach so users know which option to follow.
| elif msg_type == "assistant": | ||
| role = message.get("role", "assistant") | ||
| if role == "user": | ||
| continue | ||
| text = extract_text_from_content(content) | ||
| if text: | ||
| messages.append({"role": "assistant", "content": text}) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for entries with type="assistant" but message.role="user"
# This would validate the edge case being filtered
cd ~/.proma/agent-sessions/ 2>/dev/null || exit 1
for f in *.jsonl; do
echo "Checking $f..."
jq -c 'select(.type == "assistant" and .message.role == "user")' "$f" 2>/dev/null | head -5
doneRepository: nowledge-co/community
Length of output: 47
🏁 Script executed:
# First, find the file being reviewed
find . -name "save-to-nmem.py" -type fRepository: nowledge-co/community
Length of output: 115
🏁 Script executed:
# Search for test files or example JSONL data that might show the message format
find . -name "*.jsonl" -o -name "*test*" -o -name "*fixture*" | head -20Repository: nowledge-co/community
Length of output: 1061
🏁 Script executed:
# Look for documentation or comments about Proma message structure
rg -i "proma.*format|message.*role|assistant.*role" --type py -B 2 -A 2Repository: nowledge-co/community
Length of output: 6463
🏁 Script executed:
# Check if there's any documentation about this specific pattern
rg -i "type.*assistant|role.*user" --type py -B 3 -A 3Repository: nowledge-co/community
Length of output: 20780
Add clarifying comment on assistant message role filter.
Lines 205-207 skip assistant-type entries where role == "user". This suggests entries can have type == "assistant" but message.role == "user" in Proma JSONL format. Without a comment explaining this edge case, the intent is unclear. Add a comment documenting whether this:
- Handles a known Proma format quirk
- Filters malformed entries defensively
- Prevents duplicate message processing
This will help maintainers understand the design rationale.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nowledge-mem-proma-plugin/hooks/save-to-nmem.py` around lines 204 - 210, Add
a brief clarifying comment above the assistant message filter (the block
starting with elif msg_type == "assistant") explaining why we skip entries where
role == "user" (e.g., whether this handles a known Proma format quirk, is a
defensive check for malformed records, or prevents duplicate processing), and
reference the relevant symbols in the comment (msg_type, role,
extract_text_from_content, and messages) so future maintainers understand the
intent and won't remove the check.
- Split unified 'nmem' skill into 5 standard skills: read-working-memory, search-memory, distill-memory, save-thread, status - Add uvx fallback for nmem CLI discovery in read-working-memory.py - Add static contract tests (tests/plugin_e2e/test_proma_plugin.py) - Add Proma project description and GitHub link to README - Update integrations.json skills array to standard names Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7a2b3138d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| result = api_request("POST", "/threads", body) | ||
| return result is not None |
There was a problem hiding this comment.
Append to existing thread when create conflicts
The Stop hook calls POST /threads on every run using the same thread_id (proma-{session_id}), but upload_thread treats any non-2xx create response as a hard failure and only retries the same create call. In environments where the thread already exists (a common case after the first successful save), later turns will never be persisted, so automatic capture effectively stops after the initial upload. Other integrations in this repo already handle this by falling back to /threads/{id}/append with deduplication (for example nowledge-mem-opencode-plugin/src/index.ts).
Useful? React with 👍 / 👎.
| if nmem.endswith("uvx") or nmem.endswith("uv"): | ||
| cmd = [nmem, "--from", "nmem-cli", "nmem", "--json", "wm", "read"] |
There was a problem hiding this comment.
Build uv fallback command correctly for nmem discovery
The fallback path returns either uvx or uv, but command construction assumes both accept --from directly ([nmem, "--from", ...]). --from is a uvx/uv tool run option, not a top-level uv option, so the fallback fails when only uv is installed. In addition, the suffix check misses executables like uvx.exe, causing the script to run uvx.exe --json wm read without the required tool name. This breaks Working Memory bootstrap exactly in the "nmem not on PATH" scenario the fallback is supposed to cover.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a new Proma host integration package (nowledge-mem-proma-plugin) so Proma agents can use Nowledge Mem via MCP tools plus lifecycle hooks (Stop capture + SessionStart Working Memory injection), and registers Proma in the repo’s integrations.json catalog.
Changes:
- Introduces the Proma plugin package with hook scripts (
save-to-nmem.py,read-working-memory.py), hook config, and user-facing documentation (README/CHANGELOG). - Adds Proma-focused skills documentation under
skills/and an e2e/static contract test suite for the plugin layout. - Registers Proma in
integrations.jsonwith capabilities, install guidance, and listed skills/commands.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/plugin_e2e/test_proma_plugin.py | Adds static contract tests validating Proma plugin structure and registry entry. |
| nowledge-mem-proma-plugin/skills/status/SKILL.md | Adds “status” skill guidance for Proma usage. |
| nowledge-mem-proma-plugin/skills/search-memory/SKILL.md | Adds “search-memory” skill guidance for Proma usage. |
| nowledge-mem-proma-plugin/skills/save-thread/SKILL.md | Adds “save-thread” skill guidance for Proma usage. |
| nowledge-mem-proma-plugin/skills/read-working-memory/SKILL.md | Adds “read-working-memory” skill guidance for Proma usage. |
| nowledge-mem-proma-plugin/skills/distill-memory/SKILL.md | Adds “distill-memory” skill guidance for Proma usage. |
| nowledge-mem-proma-plugin/README.md | Documents installation, configuration, and troubleshooting for Proma integration. |
| nowledge-mem-proma-plugin/hooks/save-to-nmem.py | Implements Stop-hook thread capture by parsing Proma JSONL and POSTing to /threads. |
| nowledge-mem-proma-plugin/hooks/read-working-memory.py | Implements SessionStart-hook Working Memory retrieval via nmem --json wm read with uvx fallback. |
| nowledge-mem-proma-plugin/hooks/hooks.json | Provides Proma hook configuration referencing the two hook scripts. |
| nowledge-mem-proma-plugin/CHANGELOG.md | Adds initial 0.1.0 release notes for the new plugin. |
| nowledge-mem-proma-plugin/.claude-plugin/plugin.json | Adds plugin manifest metadata for marketplace/discovery. |
| integrations.json | Adds “proma” integration registry entry (capabilities, install steps, skills, commands). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # uvx fallback (per plugin development guide) | ||
| uvx = shutil.which("uvx") or shutil.which("uv") | ||
| if uvx: | ||
| return uvx # caller will prepend "uvx --from nmem-cli nmem" args | ||
| return None |
| # If _find_nmem returned uvx, prepend the nmem package args | ||
| if nmem.endswith("uvx") or nmem.endswith("uv"): | ||
| cmd = [nmem, "--from", "nmem-cli", "nmem", "--json", "wm", "read"] | ||
| else: | ||
| cmd = [nmem, "--json", "wm", "read"] |
| log("skip: no API key configured (set NMEM_API_KEY or ~/.nowledge-mem/config.json)") | ||
| return 0 |
| or _nmem_cfg.get("apiUrl") | ||
| or "http://127.0.0.1:14242" | ||
| ).rstrip("/") | ||
| API_KEY = ( | ||
| os.environ.get("NMEM_API_KEY") | ||
| or _nmem_cfg.get("apiKey") |
| "Add Nowledge Mem MCP server to ~/.proma/agent-workspaces/default/mcp.json (key: servers)", | ||
| "Copy hook scripts to ~/.proma/hooks/", | ||
| "Add Stop and SessionStart hooks to ~/.proma/settings.json", | ||
| "Copy nmem skill to ~/.proma/agent-workspaces/default/skills/nmem/", |
| # Status | ||
|
|
||
| Check Nowledge Mem connection status and configuration for Proma. | ||
|
|
| # Search Memory | ||
|
|
||
| Search your Nowledge Mem knowledge base proactively when past insights would improve the response. | ||
|
|
| # Save Thread | ||
|
|
||
| Save the current Proma session to Nowledge Mem on explicit user request. | ||
|
|
| # Read Working Memory | ||
|
|
||
| Read your daily Working Memory briefing to understand current context for Proma sessions. | ||
|
|
| # Distill Memory | ||
|
|
||
| Save durable insights to Nowledge Mem autonomously. Don't wait to be asked — save proactively when the conversation produces lasting value. | ||
|
|
Summary
nowledge-mem-proma-plugin) providing persistent cross-session memory through MCP tools, lifecycle hooks, and a companion skillintegrations.jsonregistryPlugin Structure
How It Works
mcp.json(key:servers) connects to nmem MCP endpoint, giving the agentmcp__nowledge-mem__*toolssave-to-nmem.pyparses Proma session JSONL (~/.proma/agent-sessions/<id>.jsonl), deduplicates by UUID, extracts text from content blocks, and uploads to nmem via REST APIread-working-memory.pycallsnmem --json wm readand outputs the briefing for context injection/nmem-save,/nmem-search,/nmem-statusslash commands as fallbackTest Plan
Proma is unique among the integrations because it uses
"servers"(not"mcpServers") as the MCP config key and stores sessions as JSONL in~/.proma/agent-sessions/.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation