@jungjaehoon/clawdbot-mama 0.1.3 → 0.2.1

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.
Files changed (2) hide show
  1. package/index.ts +178 -7
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -3,6 +3,11 @@
3
3
  *
4
4
  * NO HTTP/REST - MAMA 로직을 Gateway에 직접 임베드
5
5
  * better-sqlite3 + sqlite-vec로 벡터 검색
6
+ *
7
+ * Features:
8
+ * - 4 native tools: mama_search, mama_save, mama_load_checkpoint, mama_update
9
+ * - Auto-recall: 에이전트 시작 시 유저 프롬프트 기반 시맨틱 검색
10
+ * - Auto-capture: 에이전트 종료 시 중요 결정 자동 저장
6
11
  */
7
12
 
8
13
  import { Type } from "@sinclair/typebox";
@@ -18,6 +23,29 @@ const MAMA_MODULE_PATH = require.resolve("@jungjaehoon/mama-server/src/mama/mama
18
23
  let initialized = false;
19
24
  let mama: any = null;
20
25
 
26
+ /**
27
+ * Format reasoning with link extraction
28
+ * Shows truncated reasoning + preserves builds_on/debates/synthesizes links
29
+ */
30
+ function formatReasoning(reasoning: string, maxLen: number = 80): string {
31
+ if (!reasoning) return "";
32
+
33
+ // Extract link patterns
34
+ const linkMatch = reasoning.match(/(builds_on|debates|synthesizes):\s*[\w\[\],\s_-]+/i);
35
+
36
+ // Truncate main reasoning
37
+ const truncated = reasoning.length > maxLen
38
+ ? reasoning.substring(0, maxLen) + "..."
39
+ : reasoning;
40
+
41
+ // Add link info if found and not already in truncated part
42
+ if (linkMatch && !truncated.includes(linkMatch[0])) {
43
+ return `${truncated}\n 🔗 ${linkMatch[0]}`;
44
+ }
45
+
46
+ return truncated;
47
+ }
48
+
21
49
  /**
22
50
  * Initialize MAMA (lazy, once)
23
51
  */
@@ -53,6 +81,142 @@ const mamaPlugin = {
53
81
 
54
82
  register(api: ClawdbotPluginApi) {
55
83
 
84
+ // =====================================================
85
+ // Auto-recall: 유저 프롬프트 기반 시맨틱 검색
86
+ // =====================================================
87
+ api.on("before_agent_start", async (event: any) => {
88
+ try {
89
+ await initMAMA();
90
+
91
+ const userPrompt = event.prompt || "";
92
+
93
+ // 1. 유저 프롬프트가 있으면 시맨틱 검색 수행
94
+ let semanticResults: any[] = [];
95
+ if (userPrompt && userPrompt.length >= 5) {
96
+ try {
97
+ const searchResult = await mama.suggest(userPrompt, { limit: 3, threshold: 0.5 });
98
+ semanticResults = searchResult?.results || [];
99
+ } catch (searchErr: any) {
100
+ console.error("[MAMA] Semantic search error:", searchErr.message);
101
+ }
102
+ }
103
+
104
+ // 2. 최근 체크포인트 로드
105
+ const checkpoint = await mama.loadCheckpoint();
106
+
107
+ // 3. 최근 결정들 로드 (시맨틱 검색 결과가 없을 때만)
108
+ let recentDecisions: any[] = [];
109
+ if (semanticResults.length === 0) {
110
+ const recentResult = await mama.list({ limit: 3 });
111
+ recentDecisions = recentResult?.decisions || [];
112
+ }
113
+
114
+ // 4. 컨텍스트가 있으면 주입
115
+ if (checkpoint || semanticResults.length > 0 || recentDecisions.length > 0) {
116
+ let content = "<relevant-memories>\n";
117
+ content += "# MAMA Memory Context\n\n";
118
+
119
+ if (semanticResults.length > 0) {
120
+ content += "## Relevant Decisions (semantic match)\n\n";
121
+ semanticResults.forEach((r: any) => {
122
+ const pct = Math.round((r.similarity || 0) * 100);
123
+ content += `- **${r.topic}** [${pct}%]: ${r.decision}`;
124
+ if (r.outcome) content += ` (${r.outcome})`;
125
+ content += `\n _${formatReasoning(r.reasoning, 100)}_\n`;
126
+ content += ` ID: \`${r.id}\`\n`;
127
+ });
128
+ content += "\n";
129
+ }
130
+
131
+ if (checkpoint) {
132
+ content += `## Last Checkpoint (${new Date(checkpoint.timestamp).toLocaleString()})\n\n`;
133
+ content += `**Summary:** ${checkpoint.summary}\n\n`;
134
+ if (checkpoint.next_steps) {
135
+ content += `**Next Steps:** ${checkpoint.next_steps}\n\n`;
136
+ }
137
+ }
138
+
139
+ if (recentDecisions.length > 0) {
140
+ content += "## Recent Decisions\n\n";
141
+ recentDecisions.forEach((d: any) => {
142
+ content += `- **${d.topic}**: ${d.decision}`;
143
+ if (d.outcome) content += ` (${d.outcome})`;
144
+ content += "\n";
145
+ });
146
+ content += "\n";
147
+ }
148
+
149
+ content += "</relevant-memories>";
150
+
151
+ console.log(`[MAMA] Auto-recall: ${semanticResults.length} semantic matches, ${recentDecisions.length} recent, checkpoint: ${!!checkpoint}`);
152
+
153
+ return {
154
+ prependContext: content,
155
+ };
156
+ }
157
+ } catch (err: any) {
158
+ console.error("[MAMA] Auto-recall error:", err.message);
159
+ }
160
+ });
161
+
162
+ // =====================================================
163
+ // Auto-capture: 에이전트 종료 시 결정 자동 저장
164
+ // =====================================================
165
+ api.on("agent_end", async (event: any) => {
166
+ if (!event.success || !event.messages || event.messages.length === 0) {
167
+ return;
168
+ }
169
+
170
+ try {
171
+ await initMAMA();
172
+
173
+ // 메시지에서 텍스트 추출
174
+ const texts: string[] = [];
175
+ for (const msg of event.messages) {
176
+ if (!msg || typeof msg !== "object") continue;
177
+
178
+ const role = msg.role;
179
+ if (role !== "user" && role !== "assistant") continue;
180
+
181
+ const content = msg.content;
182
+ if (typeof content === "string") {
183
+ texts.push(content);
184
+ } else if (Array.isArray(content)) {
185
+ for (const block of content) {
186
+ if (block?.type === "text" && typeof block.text === "string") {
187
+ texts.push(block.text);
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ // 결정 패턴 감지
194
+ const decisionPatterns = [
195
+ /decided|결정|선택|chose|use.*instead|going with/i,
196
+ /will use|사용할|approach|방식|strategy/i,
197
+ /remember|기억|learned|배웠|lesson/i,
198
+ ];
199
+
200
+ for (const text of texts) {
201
+ // Skip short or injected content
202
+ if (text.length < 20 || text.length > 500) continue;
203
+ if (text.includes("<relevant-memories>")) continue;
204
+ if (text.startsWith("<") && text.includes("</")) continue;
205
+
206
+ // Check if it matches decision patterns
207
+ const isDecision = decisionPatterns.some(p => p.test(text));
208
+ if (!isDecision) continue;
209
+
210
+ // Auto-save detected decision (logged only, not actually saved without explicit topic)
211
+ console.log(`[MAMA] Auto-capture candidate: ${text.substring(0, 50)}...`);
212
+ // Note: 실제 저장은 명시적 topic이 필요하므로 로그만 남김
213
+ // 향후 LLM을 통한 topic 추출 기능 추가 가능
214
+ }
215
+ } catch (err: any) {
216
+ console.error("[MAMA] Auto-capture error:", err.message);
217
+ }
218
+ });
219
+
56
220
  // =====================================================
57
221
  // mama_search - 시맨틱 메모리 검색
58
222
  // =====================================================
@@ -60,17 +224,19 @@ const mamaPlugin = {
60
224
  name: "mama_search",
61
225
  description: `Search semantic memory for relevant past decisions.
62
226
 
63
- **ALWAYS use BEFORE making architectural decisions:**
64
- - Find if this problem was solved before
65
- - Recall reasoning and lessons learned
66
- - Avoid repeating past mistakes
67
- - Check for related decisions to link
227
+ ⚠️ **TRIGGERS - Call this BEFORE:**
228
+ Making architectural choices (check prior art)
229
+ Calling mama_save (find links first!)
230
+ Debugging (find past failures on similar issues)
231
+ Starting work on a topic (load context)
68
232
 
69
233
  **Returns:** Decisions ranked by semantic similarity with:
70
234
  - Topic, decision, reasoning
71
235
  - Similarity score (0-100%)
72
236
  - Decision ID (for linking/updating)
73
237
 
238
+ **High similarity (>80%) = MUST link with builds_on/debates/synthesizes**
239
+
74
240
  **Example queries:** "authentication", "database choice", "error handling"`,
75
241
 
76
242
  parameters: Type.Object({
@@ -108,7 +274,7 @@ const mamaPlugin = {
108
274
  const pct = Math.round((r.similarity || 0) * 100);
109
275
  output += `**${idx + 1}. ${r.topic}** [${pct}% match]\n`;
110
276
  output += ` Decision: ${r.decision}\n`;
111
- output += ` Reasoning: ${(r.reasoning || "").substring(0, 150)}...\n`;
277
+ output += ` Reasoning: ${formatReasoning(r.reasoning, 150)}\n`;
112
278
  output += ` ID: \`${r.id}\` | Outcome: ${r.outcome || "pending"}\n\n`;
113
279
  });
114
280
 
@@ -126,6 +292,11 @@ const mamaPlugin = {
126
292
  name: "mama_save",
127
293
  description: `Save a decision or checkpoint to semantic memory.
128
294
 
295
+ ⚠️ **REQUIRED WORKFLOW (Don't create orphans!):**
296
+ 1. Call mama_search FIRST to find related decisions
297
+ 2. Check if same topic exists (yours will supersede it)
298
+ 3. MUST include link in reasoning/summary field
299
+
129
300
  **DECISION - Use when:**
130
301
  - Making architectural choices
131
302
  - Learning a lesson (success or failure)
@@ -137,7 +308,7 @@ const mamaPlugin = {
137
308
  - Reaching a milestone
138
309
  - Before switching tasks
139
310
 
140
- **Link decisions:** Add "builds_on: decision_xxx" in reasoning to create graph edges.`,
311
+ **Link decisions:** End reasoning with 'builds_on: <id>' or 'debates: <id>' or 'synthesizes: [id1, id2]'`,
141
312
 
142
313
  parameters: Type.Object({
143
314
  type: Type.Union([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungjaehoon/clawdbot-mama",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "MAMA Memory Plugin for Clawdbot - Semantic decision memory with reasoning graph",
5
5
  "type": "module",
6
6
  "main": "index.ts",