@zilliz/memsearch-opencode 0.1.1 → 0.2.0
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/README.md +13 -0
- package/index.ts +13 -9
- package/package.json +2 -1
- package/prompts/summarize.txt +14 -0
- package/scripts/__pycache__/capture-daemon.cpython-313.pyc +0 -0
- package/scripts/capture-daemon.py +34 -13
- package/skills/memory-recall/SKILL.md +12 -2
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ uv tool install 'memsearch[onnx]'
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
> Windows note: the current plugin shells out to `bash` and `python3` helper scripts. Plain Windows installs are not supported yet; use WSL2 (recommended) or a POSIX-compatible shell such as Git Bash. See issue #387.
|
|
34
|
+
|
|
33
35
|
### Install from Source (development)
|
|
34
36
|
|
|
35
37
|
```bash
|
|
@@ -73,6 +75,17 @@ OpenCode Session
|
|
|
73
75
|
└── memory_transcript ──→ parse-transcript.py (SQLite reader)
|
|
74
76
|
```
|
|
75
77
|
|
|
78
|
+
## Verify It Works
|
|
79
|
+
|
|
80
|
+
After restarting OpenCode, chat for a few turns in a project and confirm memory capture is happening:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ls .memsearch/memory/
|
|
84
|
+
cat .memsearch/memory/$(date +%Y-%m-%d).md
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If the plugin is active, daily markdown files should appear and grow as conversations finish.
|
|
88
|
+
|
|
76
89
|
## Recall Memories
|
|
77
90
|
|
|
78
91
|
**Manual invocation** — explicitly invoke the skill with a query:
|
package/index.ts
CHANGED
|
@@ -68,12 +68,15 @@ function deriveCollectionName(projectDir: string): string {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* Summarize the N most recent daily .md files for cold-start context.
|
|
72
|
+
* Extracts headings (## Session, ### turns) and bullet content from each
|
|
73
|
+
* file so the agent sees the structure of past days (what sessions existed,
|
|
74
|
+
* what topics came up), not just the tail of whichever file is newest.
|
|
72
75
|
*/
|
|
73
76
|
function getRecentMemories(
|
|
74
77
|
memDir: string,
|
|
75
78
|
count = 2,
|
|
76
|
-
|
|
79
|
+
maxLinesPerFile = 30
|
|
77
80
|
): string {
|
|
78
81
|
if (!existsSync(memDir)) return "";
|
|
79
82
|
|
|
@@ -84,23 +87,24 @@ function getRecentMemories(
|
|
|
84
87
|
|
|
85
88
|
if (files.length === 0) return "";
|
|
86
89
|
|
|
87
|
-
const
|
|
90
|
+
const summary: string[] = [];
|
|
88
91
|
for (const file of files) {
|
|
89
92
|
try {
|
|
90
93
|
const content = readFileSync(join(memDir, file), "utf-8");
|
|
91
|
-
const lines = content.split("\n")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
const lines = content.split("\n")
|
|
95
|
+
.filter((l) => /^#{2,4}\s/.test(l) || l.startsWith("- ") || l.startsWith("[Human]") || l.startsWith("[Assistant]"))
|
|
96
|
+
.slice(0, maxLinesPerFile);
|
|
97
|
+
if (lines.length > 0) {
|
|
98
|
+
summary.push(`[${file}]`, ...lines);
|
|
95
99
|
}
|
|
96
100
|
} catch { /* skip */ }
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
if (
|
|
103
|
+
if (summary.length === 0) {
|
|
100
104
|
return `You have ${files.length} past memory file(s). Use the memory_search tool when the user's question could benefit from historical context.`;
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
return `Recent memories (use memory_search for full search):\n${
|
|
107
|
+
return `Recent memories (use memory_search for full search):\n${summary.join("\n")}`;
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
/** Shell-escape a string for safe use inside single quotes. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zilliz/memsearch-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "memsearch plugin for OpenCode — semantic memory search across sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"index.ts",
|
|
12
12
|
"scripts/",
|
|
13
13
|
"skills/",
|
|
14
|
+
"prompts/",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
16
17
|
"keywords": [
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
You are a third-person note-taker. You will receive a transcript of ONE conversation turn between a human and {{AGENT_NAME}}.
|
|
2
|
+
|
|
3
|
+
Your job is to record what happened as factual third-person notes. You are an EXTERNAL OBSERVER — you are NOT {{AGENT_NAME}}, NOT an assistant. Do NOT answer the human's question, do NOT give suggestions, do NOT offer help. ONLY record what occurred.
|
|
4
|
+
|
|
5
|
+
Output 2-6 bullet points, each starting with '- '. NOTHING else.
|
|
6
|
+
|
|
7
|
+
Rules:
|
|
8
|
+
- Write in third person: 'User asked...', '{{AGENT_NAME}} read file X', '{{AGENT_NAME}} ran command Y'
|
|
9
|
+
- First bullet: what the user asked or wanted (one sentence)
|
|
10
|
+
- Remaining bullets: what was done — tools called, files read/edited, commands run, key findings
|
|
11
|
+
- Be specific: mention file names, function names, tool names, and concrete outcomes
|
|
12
|
+
- Do NOT answer the human's question yourself — just note what was discussed
|
|
13
|
+
- Do NOT add any text before or after the bullet points
|
|
14
|
+
- Write in the same language as the human's message in the transcript
|
|
Binary file
|
|
@@ -53,20 +53,41 @@ def ensure_isolated_config():
|
|
|
53
53
|
return os.path.dirname(isolated) # /tmp/opencode-memsearch-summarize
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def
|
|
56
|
+
def _load_summarize_prompt(agent_name, memsearch_cmd=None):
|
|
57
|
+
"""Load summarization prompt: user custom > plugin built-in > inline fallback."""
|
|
58
|
+
# Try user-custom prompt via config
|
|
59
|
+
if memsearch_cmd:
|
|
60
|
+
try:
|
|
61
|
+
result = subprocess.run(
|
|
62
|
+
memsearch_cmd.split() + ["config", "get", "prompts.summarize"],
|
|
63
|
+
capture_output=True, text=True, timeout=5,
|
|
64
|
+
)
|
|
65
|
+
custom_path = result.stdout.strip()
|
|
66
|
+
if custom_path and os.path.isfile(custom_path):
|
|
67
|
+
template = Path(custom_path).read_text(encoding="utf-8")
|
|
68
|
+
return template.replace("{{AGENT_NAME}}", agent_name)
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
# Plugin built-in template
|
|
73
|
+
builtin = Path(__file__).resolve().parent.parent / "prompts" / "summarize.txt"
|
|
74
|
+
if builtin.is_file():
|
|
75
|
+
template = builtin.read_text(encoding="utf-8")
|
|
76
|
+
return template.replace("{{AGENT_NAME}}", agent_name)
|
|
77
|
+
|
|
78
|
+
# Inline fallback
|
|
79
|
+
return (
|
|
80
|
+
"You are a third-person note-taker. Summarize the transcript as "
|
|
81
|
+
"2-6 bullet points. Write in third person. Output ONLY bullet points."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def summarize_with_llm(turn_text, small_model, memsearch_cmd=None):
|
|
57
86
|
"""Summarize using opencode run in isolated env (no plugins -> no recursion)."""
|
|
58
|
-
|
|
59
|
-
# the LLM treating the instruction itself as the conversation content.
|
|
60
|
-
# Clear delimiters (===) separate the transcript from the task.
|
|
87
|
+
system_prompt = _load_summarize_prompt("OpenCode", memsearch_cmd)
|
|
61
88
|
full_prompt = (
|
|
62
|
-
f"
|
|
63
|
-
f"{turn_text}
|
|
64
|
-
f"===TRANSCRIPT END===\n\n"
|
|
65
|
-
f"Summarize the transcript above as 2-6 third-person bullet points (each starting with '- '). "
|
|
66
|
-
f"Write 'User asked/did...' and 'OpenCode replied/did...'. "
|
|
67
|
-
f"Be specific (file names, tools, outcomes). "
|
|
68
|
-
f"Same language as the human. "
|
|
69
|
-
f"Output ONLY bullet points, nothing else."
|
|
89
|
+
f"{system_prompt}\n\n"
|
|
90
|
+
f"Transcript:\n{turn_text}"
|
|
70
91
|
)
|
|
71
92
|
isolated_dir = ensure_isolated_config()
|
|
72
93
|
|
|
@@ -279,7 +300,7 @@ def main():
|
|
|
279
300
|
for session_id, turn_text, msg_time in new_turns:
|
|
280
301
|
if turn_text and len(turn_text) > 10:
|
|
281
302
|
# Summarize with LLM, fallback to raw text
|
|
282
|
-
summary = summarize_with_llm(turn_text, small_model)
|
|
303
|
+
summary = summarize_with_llm(turn_text, small_model, args.memsearch_cmd)
|
|
283
304
|
write_capture(memory_dir, summary if summary else turn_text, session_id, db_path)
|
|
284
305
|
if msg_time > last_msg_time:
|
|
285
306
|
last_msg_time = msg_time
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory-recall
|
|
3
|
-
description: "Search and recall relevant memories from past sessions. Use when the user's question could benefit from historical context, past decisions, debugging notes, previous conversations, or project knowledge."
|
|
3
|
+
description: "Search and recall relevant memories from past sessions via memsearch. Use when the user's question could benefit from historical context, past decisions, debugging notes, previous conversations, or project knowledge -- especially questions like 'what did I decide about X', 'why did we do Y', or 'have I seen this before'. Also use when you see `[memsearch] Memory available` hints injected via SessionStart or UserPromptSubmit. Typical flow: search for 3-5 chunks, expand the most relevant, optionally deep-drill into original transcripts via the anchor format. Skip when the question is purely about current code state (use Read/Grep), ephemeral (today's task only), or the user has explicitly asked to ignore memory."
|
|
4
4
|
allowed-tools: Bash
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ You are a memory retrieval agent for memsearch. Your job is to search past memor
|
|
|
8
8
|
|
|
9
9
|
## Project Collection
|
|
10
10
|
|
|
11
|
-
Collection: !`bash __INSTALL_DIR__/scripts/derive-collection.sh`
|
|
11
|
+
Collection: !`bash -c 'root=$(git rev-parse --show-toplevel 2>/dev/null || true); if [ -n "$root" ]; then bash __INSTALL_DIR__/scripts/derive-collection.sh "$root"; else bash __INSTALL_DIR__/scripts/derive-collection.sh; fi'`
|
|
12
12
|
|
|
13
13
|
## Your Task
|
|
14
14
|
|
|
@@ -30,6 +30,16 @@ Search for memories relevant to: $ARGUMENTS
|
|
|
30
30
|
|
|
31
31
|
5. **Return results**: Output a curated summary of the most relevant memories. Be concise — only include information that is genuinely useful for the user's current question.
|
|
32
32
|
|
|
33
|
+
## When unsure what to search
|
|
34
|
+
|
|
35
|
+
If the user's question is vague or you can't form a concrete search query, explore the raw markdown first — it is the source of truth for memory:
|
|
36
|
+
|
|
37
|
+
- `ls -t .memsearch/memory/ | head -10` — recent daily logs
|
|
38
|
+
- `grep -h "^## " .memsearch/memory/*.md | sort -u | tail -40` — session headings across all days
|
|
39
|
+
- `cat .memsearch/memory/<YYYY-MM-DD>.md` — read a specific day
|
|
40
|
+
|
|
41
|
+
Once a concrete topic jumps out, go back to `memsearch search` with a specific query.
|
|
42
|
+
|
|
33
43
|
## Output Format
|
|
34
44
|
|
|
35
45
|
Organize by relevance. For each memory include:
|