@jungjaehoon/mama-server 1.9.3 → 1.10.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.
- package/README.md +18 -11
- package/package.json +2 -2
- package/src/db/migrations/001-initial-decision-graph.sql +1 -1
- package/src/db/migrations/002-add-error-patterns.sql +1 -1
- package/src/mama/transparency-banner.js +1 -1
- package/src/server.js +58 -7
- package/src/tools/checkpoint-tools.js +2 -7
- package/src/tools/index.js +4 -0
- package/src/tools/ingest-conversation.js +99 -0
- package/src/tools/list-decisions.js +15 -5
- package/src/tools/recall-decision.js +68 -23
- package/src/tools/save-decision.js +33 -0
- package/src/tools/suggest-decision.js +15 -3
package/README.md
CHANGED
|
@@ -75,16 +75,23 @@ Any MCP-compatible client can use MAMA with:
|
|
|
75
75
|
npx -y @jungjaehoon/mama-server
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
## Available Tools (v1.
|
|
79
|
-
|
|
80
|
-
The MCP server exposes
|
|
81
|
-
|
|
82
|
-
| Tool
|
|
83
|
-
|
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
78
|
+
## Available Tools (v1.9)
|
|
79
|
+
|
|
80
|
+
The MCP server exposes 11 tools:
|
|
81
|
+
|
|
82
|
+
| Tool | Description |
|
|
83
|
+
| ------------------------- | ----------------------------------------------------------------------- |
|
|
84
|
+
| `save_decision` | Save decision with optional scopes and event_date for temporal tracking |
|
|
85
|
+
| `recall_decision` | Recall decision history by topic, scope-filtered via recallMemory v2 |
|
|
86
|
+
| `suggest_decision` | Semantic search for relevant past decisions, scope-aware |
|
|
87
|
+
| `list_decisions` | List recent decisions, scope-filterable |
|
|
88
|
+
| `update_outcome` | Update decision outcome (case-insensitive: success/failed/partial) |
|
|
89
|
+
| `search_narrative` | Narrative search with link expansion (depth 0-2) |
|
|
90
|
+
| `ingest_conversation` | Ingest conversation messages into memory with optional LLM extraction |
|
|
91
|
+
| `save_checkpoint` | Save session checkpoint for later resumption |
|
|
92
|
+
| `load_checkpoint` | Resume previous session |
|
|
93
|
+
| `generate_quality_report` | Quality metrics and observability report |
|
|
94
|
+
| `get_restart_metrics` | Restart success rate and latency monitoring |
|
|
88
95
|
|
|
89
96
|
### Edge Types
|
|
90
97
|
|
|
@@ -224,7 +231,7 @@ Interactive visualization of your reasoning graph.
|
|
|
224
231
|
## Technical Details
|
|
225
232
|
|
|
226
233
|
- **Database:** SQLite + pure-TS cosine similarity
|
|
227
|
-
- **Embeddings:** Transformers.js (Xenova/multilingual-e5-
|
|
234
|
+
- **Embeddings:** Transformers.js (Xenova/multilingual-e5-large, 1024-dim)
|
|
228
235
|
- **Transport:** stdio-based MCP protocol (default) + optional HTTP embedding server (port 3849)
|
|
229
236
|
- **Storage:** ~/.claude/mama-memory.db (configurable via MAMA_DB_PATH)
|
|
230
237
|
- **Port File:** ~/.mama-embedding-port (for client discovery)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungjaehoon/mama-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"description": "MAMA MCP Server - Memory-Augmented MCP Assistant for Claude Code & Desktop",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=22.13.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jungjaehoon/mama-core": "^1.
|
|
42
|
+
"@jungjaehoon/mama-core": "^1.4.0",
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.0.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
@@ -119,7 +119,7 @@ CREATE INDEX IF NOT EXISTS idx_sessions_last_active ON sessions(last_active_at);
|
|
|
119
119
|
-- because sqlite-vss requires extension loading first
|
|
120
120
|
--
|
|
121
121
|
-- CREATE VIRTUAL TABLE vss_memories USING vss0(
|
|
122
|
-
-- embedding(
|
|
122
|
+
-- embedding(1024) -- multilingual-e5-large embeddings
|
|
123
123
|
-- );
|
|
124
124
|
|
|
125
125
|
-- ══════════════════════════════════════════════════════════════
|
|
@@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS error_patterns (
|
|
|
33
33
|
first_seen INTEGER NOT NULL, -- First occurrence timestamp
|
|
34
34
|
|
|
35
35
|
-- Vector Embedding (for semantic matching)
|
|
36
|
-
embedding BLOB, --
|
|
36
|
+
embedding BLOB, -- 1024-dim embedding (multilingual-e5-large)
|
|
37
37
|
|
|
38
38
|
-- Metadata
|
|
39
39
|
created_at INTEGER DEFAULT (unixepoch()),
|
|
@@ -56,7 +56,7 @@ const FIX_INSTRUCTIONS = {
|
|
|
56
56
|
steps: [
|
|
57
57
|
'1. Install Transformers.js: npm install @xenova/transformers',
|
|
58
58
|
'2. Configure model in ~/.mama/config.json:',
|
|
59
|
-
' { "embeddingModel": "Xenova/multilingual-e5-
|
|
59
|
+
' { "embeddingModel": "Xenova/multilingual-e5-large" }',
|
|
60
60
|
'3. Restart Claude Code to reload configuration',
|
|
61
61
|
],
|
|
62
62
|
impact: 'Without embeddings, MAMA falls back to keyword search (30% less accurate)',
|
package/src/server.js
CHANGED
|
@@ -248,6 +248,28 @@ type='checkpoint': session state for resumption (ALSO requires search first!)`,
|
|
|
248
248
|
minimum: 0,
|
|
249
249
|
maximum: 1,
|
|
250
250
|
},
|
|
251
|
+
// Scope & temporal fields
|
|
252
|
+
scopes: {
|
|
253
|
+
type: 'array',
|
|
254
|
+
items: {
|
|
255
|
+
type: 'object',
|
|
256
|
+
properties: {
|
|
257
|
+
kind: {
|
|
258
|
+
type: 'string',
|
|
259
|
+
enum: ['global', 'user', 'channel', 'project'],
|
|
260
|
+
},
|
|
261
|
+
id: { type: 'string' },
|
|
262
|
+
},
|
|
263
|
+
required: ['kind', 'id'],
|
|
264
|
+
},
|
|
265
|
+
description:
|
|
266
|
+
'[Decision] Memory scopes for isolation. Example: [{"kind": "project", "id": "/path/to/project"}]',
|
|
267
|
+
},
|
|
268
|
+
event_date: {
|
|
269
|
+
type: 'string',
|
|
270
|
+
description:
|
|
271
|
+
'[Decision] ISO 8601 date when the event occurred (e.g., "2024-01-15"). Defaults to now.',
|
|
272
|
+
},
|
|
251
273
|
// Checkpoint fields
|
|
252
274
|
summary: {
|
|
253
275
|
type: 'string',
|
|
@@ -311,6 +333,21 @@ When presenting search results to the user or agent, include a brief **Reasoning
|
|
|
311
333
|
type: 'number',
|
|
312
334
|
description: 'Maximum results. Default: 10',
|
|
313
335
|
},
|
|
336
|
+
scopes: {
|
|
337
|
+
type: 'array',
|
|
338
|
+
items: {
|
|
339
|
+
type: 'object',
|
|
340
|
+
properties: {
|
|
341
|
+
kind: {
|
|
342
|
+
type: 'string',
|
|
343
|
+
enum: ['global', 'user', 'channel', 'project'],
|
|
344
|
+
},
|
|
345
|
+
id: { type: 'string' },
|
|
346
|
+
},
|
|
347
|
+
required: ['kind', 'id'],
|
|
348
|
+
},
|
|
349
|
+
description: 'Filter search results by scope.',
|
|
350
|
+
},
|
|
314
351
|
},
|
|
315
352
|
},
|
|
316
353
|
},
|
|
@@ -415,6 +452,8 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
|
|
|
415
452
|
// Handle tool execution - 4 core tools only
|
|
416
453
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
417
454
|
const { name, arguments: args } = request.params;
|
|
455
|
+
const toolStart = Date.now();
|
|
456
|
+
console.error(`[MAMA MCP] Tool start: ${name}`);
|
|
418
457
|
|
|
419
458
|
try {
|
|
420
459
|
let result;
|
|
@@ -446,6 +485,8 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
|
|
|
446
485
|
this.legacyNoticeEmittedInToolResponse = true;
|
|
447
486
|
}
|
|
448
487
|
|
|
488
|
+
console.error(`[MAMA MCP] Tool done: ${name} (${Date.now() - toolStart}ms)`);
|
|
489
|
+
|
|
449
490
|
return {
|
|
450
491
|
content: [
|
|
451
492
|
{
|
|
@@ -464,6 +505,7 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
|
|
|
464
505
|
],
|
|
465
506
|
};
|
|
466
507
|
} catch (error) {
|
|
508
|
+
console.error(`[MAMA MCP] Tool failed: ${name} (${Date.now() - toolStart}ms)`);
|
|
467
509
|
console.error('[MAMA MCP] Tool execution error:', error);
|
|
468
510
|
return {
|
|
469
511
|
content: [
|
|
@@ -485,11 +527,19 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
|
|
|
485
527
|
const { type } = args;
|
|
486
528
|
|
|
487
529
|
if (type === 'decision') {
|
|
488
|
-
const { topic, decision, reasoning, confidence = 0.5 } = args;
|
|
530
|
+
const { topic, decision, reasoning, confidence = 0.5, scopes, event_date } = args;
|
|
489
531
|
if (!topic || !decision || !reasoning) {
|
|
490
532
|
return { success: false, message: '❌ Decision requires: topic, decision, reasoning' };
|
|
491
533
|
}
|
|
492
|
-
const id = await mama.save({
|
|
534
|
+
const id = await mama.save({
|
|
535
|
+
type: 'user_decision',
|
|
536
|
+
topic,
|
|
537
|
+
decision,
|
|
538
|
+
reasoning,
|
|
539
|
+
confidence,
|
|
540
|
+
...(scopes && { scopes }),
|
|
541
|
+
...(event_date && { event_date }),
|
|
542
|
+
});
|
|
493
543
|
return {
|
|
494
544
|
success: true,
|
|
495
545
|
id,
|
|
@@ -519,7 +569,7 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
|
|
|
519
569
|
* Handle unified search (decisions + checkpoints)
|
|
520
570
|
*/
|
|
521
571
|
async handleSearch(args) {
|
|
522
|
-
const { query, type = 'all', limit = 10 } = args;
|
|
572
|
+
const { query, type = 'all', limit = 10, scopes } = args;
|
|
523
573
|
|
|
524
574
|
const results = [];
|
|
525
575
|
|
|
@@ -527,12 +577,13 @@ Returns: summary (4-section), next_steps (DoD + commands), open_files
|
|
|
527
577
|
if (type === 'all' || type === 'decision') {
|
|
528
578
|
let decisions;
|
|
529
579
|
if (query) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
580
|
+
const suggestResult = await mama.suggest(query, {
|
|
581
|
+
limit,
|
|
582
|
+
...(scopes && { scopes }),
|
|
583
|
+
});
|
|
533
584
|
decisions = suggestResult?.results || [];
|
|
534
585
|
} else {
|
|
535
|
-
decisions = await mama.list(limit);
|
|
586
|
+
decisions = await mama.list({ limit, ...(scopes && { scopes }) });
|
|
536
587
|
}
|
|
537
588
|
// Ensure decisions is an array
|
|
538
589
|
if (Array.isArray(decisions)) {
|
|
@@ -235,10 +235,6 @@ const loadCheckpointTool = {
|
|
|
235
235
|
inputSchema: {
|
|
236
236
|
type: 'object',
|
|
237
237
|
properties: {
|
|
238
|
-
session_id: {
|
|
239
|
-
type: 'string',
|
|
240
|
-
description: 'Optional session ID. If not provided, loads the most recent checkpoint.',
|
|
241
|
-
},
|
|
242
238
|
include_narrative: {
|
|
243
239
|
type: 'boolean',
|
|
244
240
|
description: 'Include related narrative/decisions (default: true)',
|
|
@@ -258,8 +254,7 @@ const loadCheckpointTool = {
|
|
|
258
254
|
},
|
|
259
255
|
handler: async (args = {}) => {
|
|
260
256
|
const start = Date.now();
|
|
261
|
-
|
|
262
|
-
const { session_id, include_narrative = true, include_links = true, link_depth = 1 } = args;
|
|
257
|
+
const { include_narrative = true, include_links = true, link_depth = 1 } = args;
|
|
263
258
|
|
|
264
259
|
try {
|
|
265
260
|
const adapter = getAdapter();
|
|
@@ -338,7 +333,7 @@ const loadCheckpointTool = {
|
|
|
338
333
|
const formattedResponse = formatRestart(checkpoint, narrative, links);
|
|
339
334
|
|
|
340
335
|
// BMad Workflow Integration: Add Story context
|
|
341
|
-
const currentStory = inferCurrentStory(checkpoint.summary);
|
|
336
|
+
const currentStory = include_narrative ? inferCurrentStory(checkpoint.summary) : null;
|
|
342
337
|
let bmadWorkflowContext = '';
|
|
343
338
|
|
|
344
339
|
if (currentStory && currentStory.details) {
|
package/src/tools/index.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* - search_narrative: Semantic search with link expansion ✅
|
|
16
16
|
* - generate_quality_report: Generate coverage and quality metrics report ✅
|
|
17
17
|
* - get_restart_metrics: Get restart success rate and latency metrics ✅
|
|
18
|
+
* - ingest_conversation: Ingest conversation messages into memory ✅
|
|
18
19
|
*
|
|
19
20
|
* @module tools
|
|
20
21
|
*/
|
|
@@ -27,6 +28,7 @@ const { updateOutcomeTool } = require('./update-outcome.js');
|
|
|
27
28
|
const { saveCheckpointTool, loadCheckpointTool } = require('./checkpoint-tools.js');
|
|
28
29
|
const { searchNarrativeTool } = require('./search-narrative.js');
|
|
29
30
|
const { generateQualityReportTool, getRestartMetricsTool } = require('./quality-metrics-tools.js');
|
|
31
|
+
const { ingestConversationTool } = require('./ingest-conversation.js');
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Create all MAMA memory tools
|
|
@@ -47,6 +49,7 @@ function createMemoryTools() {
|
|
|
47
49
|
search_narrative: searchNarrativeTool,
|
|
48
50
|
generate_quality_report: generateQualityReportTool,
|
|
49
51
|
get_restart_metrics: getRestartMetricsTool,
|
|
52
|
+
ingest_conversation: ingestConversationTool,
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -63,4 +66,5 @@ module.exports = {
|
|
|
63
66
|
searchNarrativeTool,
|
|
64
67
|
generateQualityReportTool,
|
|
65
68
|
getRestartMetricsTool,
|
|
69
|
+
ingestConversationTool,
|
|
66
70
|
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: ingest_conversation
|
|
3
|
+
*
|
|
4
|
+
* Ingests conversation messages into MAMA memory with optional extraction.
|
|
5
|
+
*
|
|
6
|
+
* @module ingest-conversation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { ingestConversation } = require('@jungjaehoon/mama-core');
|
|
10
|
+
|
|
11
|
+
const createIngestConversationTool = (mamaApi) => ({
|
|
12
|
+
name: 'ingest_conversation',
|
|
13
|
+
description:
|
|
14
|
+
"Ingest a conversation into MAMA's memory. Stores the raw conversation and optionally extracts structured memories (decisions, facts, preferences) using LLM. Use this to import past conversations or chat logs into memory.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
messages: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
items: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
role: { type: 'string', enum: ['user', 'assistant', 'system'] },
|
|
24
|
+
content: { type: 'string' },
|
|
25
|
+
},
|
|
26
|
+
required: ['role', 'content'],
|
|
27
|
+
},
|
|
28
|
+
description: 'Conversation messages to ingest.',
|
|
29
|
+
},
|
|
30
|
+
scopes: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
|
|
36
|
+
id: { type: 'string' },
|
|
37
|
+
},
|
|
38
|
+
required: ['kind', 'id'],
|
|
39
|
+
},
|
|
40
|
+
description: 'Memory scopes for isolation.',
|
|
41
|
+
},
|
|
42
|
+
session_date: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'ISO 8601 date when the conversation occurred (e.g., "2024-01-15").',
|
|
45
|
+
},
|
|
46
|
+
extract: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description: 'Whether to extract structured memories from the conversation. Default: false',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['messages'],
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async handler(params, _context) {
|
|
55
|
+
const { messages, scopes, session_date, extract = false } = params || {};
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
message: '❌ Validation error: messages must be a non-empty array',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ingestFn = mamaApi.ingestConversation || ingestConversation;
|
|
66
|
+
const result = await ingestFn({
|
|
67
|
+
messages,
|
|
68
|
+
scopes: scopes || [],
|
|
69
|
+
source: {
|
|
70
|
+
package: 'mcp-server',
|
|
71
|
+
source_type: 'mcp_ingest_conversation',
|
|
72
|
+
},
|
|
73
|
+
...(session_date && { sessionDate: session_date }),
|
|
74
|
+
...(extract && { extract: { enabled: true } }),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
raw_id: result.rawId,
|
|
80
|
+
extracted_memories: result.extractedMemories || [],
|
|
81
|
+
message: `✅ Conversation ingested (ID: ${result.rawId})${
|
|
82
|
+
result.extractedMemories?.length
|
|
83
|
+
? `, extracted ${result.extractedMemories.length} memories`
|
|
84
|
+
: ''
|
|
85
|
+
}`,
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
message: `❌ Failed to ingest conversation: ${errorMessage}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const ingestConversationTool = createIngestConversationTool({});
|
|
98
|
+
|
|
99
|
+
module.exports = { ingestConversationTool, createIngestConversationTool };
|
|
@@ -26,7 +26,7 @@ const mama = require('@jungjaehoon/mama-core/mama-api');
|
|
|
26
26
|
const listDecisionsTool = {
|
|
27
27
|
name: 'list_decisions',
|
|
28
28
|
description:
|
|
29
|
-
'List recent decisions in chronological order. Returns formatted list showing time, type (user/assistant), topic, preview, confidence, and status. Use this to see recent activity or find decisions by browsing.',
|
|
29
|
+
'List recent decisions in chronological order. Returns formatted list showing time, type (user/assistant), topic, preview, confidence, and status. Use this to see recent activity or find decisions by browsing. Use scopes to filter by project/channel.',
|
|
30
30
|
inputSchema: {
|
|
31
31
|
type: 'object',
|
|
32
32
|
properties: {
|
|
@@ -36,12 +36,24 @@ const listDecisionsTool = {
|
|
|
36
36
|
minimum: 1,
|
|
37
37
|
maximum: 100,
|
|
38
38
|
},
|
|
39
|
+
scopes: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
items: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
|
|
45
|
+
id: { type: 'string' },
|
|
46
|
+
},
|
|
47
|
+
required: ['kind', 'id'],
|
|
48
|
+
},
|
|
49
|
+
description: 'Filter list by scope. If omitted, lists all scopes.',
|
|
50
|
+
},
|
|
39
51
|
},
|
|
40
52
|
required: [],
|
|
41
53
|
},
|
|
42
54
|
|
|
43
55
|
async handler(params, _context) {
|
|
44
|
-
const { limit = 20 } = params || {};
|
|
56
|
+
const { limit = 20, scopes } = params || {};
|
|
45
57
|
|
|
46
58
|
try {
|
|
47
59
|
// Validation: Limit range check
|
|
@@ -52,9 +64,7 @@ const listDecisionsTool = {
|
|
|
52
64
|
};
|
|
53
65
|
}
|
|
54
66
|
|
|
55
|
-
|
|
56
|
-
// mama.list() defaults to JSON (LLM-first), but we need markdown for user display
|
|
57
|
-
const list = await mama.list({ limit, format: 'markdown' });
|
|
67
|
+
const list = await mama.list({ limit, format: 'markdown', ...(scopes && { scopes }) });
|
|
58
68
|
|
|
59
69
|
// Return success response with formatted list
|
|
60
70
|
return {
|
|
@@ -4,15 +4,8 @@
|
|
|
4
4
|
* Story M1.3: MCP Tool - recall_decision (ported from mcp-server)
|
|
5
5
|
* Priority: P1 (Core Feature)
|
|
6
6
|
*
|
|
7
|
-
* Recalls
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Flow:
|
|
11
|
-
* 1. User (via Claude Desktop): "Recall my decision about auth strategy"
|
|
12
|
-
* 2. Claude: Calls recall_decision MCP tool
|
|
13
|
-
* 3. Tool: Validates input, calls mama.recall()
|
|
14
|
-
* 4. mama.recall(): Queries decision history + formats as markdown
|
|
15
|
-
* 5. Tool: Returns formatted markdown response
|
|
7
|
+
* Recalls decision history for a specific topic using v2 recallMemory API.
|
|
8
|
+
* Supports scope-based filtering.
|
|
16
9
|
*
|
|
17
10
|
* @module recall-decision
|
|
18
11
|
*/
|
|
@@ -20,26 +13,51 @@
|
|
|
20
13
|
const mama = require('@jungjaehoon/mama-core/mama-api');
|
|
21
14
|
|
|
22
15
|
/**
|
|
23
|
-
*
|
|
16
|
+
* Create recall decision tool with dependencies
|
|
17
|
+
* @param {Object} mamaApi - MAMA API instance
|
|
24
18
|
*/
|
|
25
|
-
const
|
|
19
|
+
const createRecallDecisionTool = (mamaApi) => ({
|
|
26
20
|
name: 'recall_decision',
|
|
27
21
|
description:
|
|
28
|
-
'Recall
|
|
22
|
+
'Recall decision history for a topic using semantic search. Returns past decisions filtered by scope if provided. Use this when you need to review previous decisions, understand decision evolution, or check current position on a topic.\n\n⚡ GRAPH TRAVERSAL: When the same topic is reused across multiple decisions, this tool automatically shows the decision evolution chain (supersedes graph), enabling Learn/Unlearn/Relearn workflows.',
|
|
29
23
|
inputSchema: {
|
|
30
24
|
type: 'object',
|
|
31
25
|
properties: {
|
|
32
26
|
topic: {
|
|
33
27
|
type: 'string',
|
|
34
28
|
description:
|
|
35
|
-
"Decision topic to recall (e.g., 'auth_strategy', 'mesh_detail_choice'). Use the EXACT SAME topic name used in save_decision to see full decision evolution graph.
|
|
29
|
+
"Decision topic to recall (e.g., 'auth_strategy', 'mesh_detail_choice'). Use the EXACT SAME topic name used in save_decision to see full decision evolution graph.",
|
|
30
|
+
},
|
|
31
|
+
format: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
enum: ['markdown', 'json'],
|
|
34
|
+
description: "Output format. Default: 'markdown'",
|
|
35
|
+
},
|
|
36
|
+
scopes: {
|
|
37
|
+
type: 'array',
|
|
38
|
+
items: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
kind: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
enum: ['global', 'user', 'channel', 'project'],
|
|
44
|
+
description: 'Scope type',
|
|
45
|
+
},
|
|
46
|
+
id: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Scope identifier (e.g., project path, channel ID)',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['kind', 'id'],
|
|
52
|
+
},
|
|
53
|
+
description: 'Filter recall results by scope. If omitted, returns all scopes.',
|
|
36
54
|
},
|
|
37
55
|
},
|
|
38
56
|
required: ['topic'],
|
|
39
57
|
},
|
|
40
58
|
|
|
41
59
|
async handler(params, _context) {
|
|
42
|
-
const { topic } = params || {};
|
|
60
|
+
const { topic, format = 'markdown', scopes } = params || {};
|
|
43
61
|
|
|
44
62
|
try {
|
|
45
63
|
// Validation: Non-empty string check
|
|
@@ -50,26 +68,53 @@ const recallDecisionTool = {
|
|
|
50
68
|
};
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
if (scopes && scopes.length > 0) {
|
|
72
|
+
// Use v2 recallMemory for scope-aware semantic recall
|
|
73
|
+
const bundle = await mamaApi.recallMemory(topic, {
|
|
74
|
+
scopes,
|
|
75
|
+
includeHistory: true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (format === 'json') {
|
|
79
|
+
return { success: true, history: bundle, message: bundle };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const memories = bundle.memories || [];
|
|
83
|
+
let md = `🧠 **Recall: ${topic}** (${memories.length} results)\n\n`;
|
|
84
|
+
for (const m of memories) {
|
|
85
|
+
md += `### ${m.topic}\n`;
|
|
86
|
+
md += `${m.summary}\n`;
|
|
87
|
+
if (m.details && m.details !== m.summary) {
|
|
88
|
+
md += `> ${m.details}\n`;
|
|
89
|
+
}
|
|
90
|
+
md += `- Confidence: ${m.confidence} | Status: ${m.status}`;
|
|
91
|
+
if (m.event_date) {
|
|
92
|
+
md += ` | Event: ${m.event_date}`;
|
|
93
|
+
}
|
|
94
|
+
md += '\n\n';
|
|
95
|
+
}
|
|
96
|
+
return { success: true, history: md, message: md };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Legacy path: topic-exact-match recall (no scopes)
|
|
100
|
+
const history = await mamaApi.recall(topic, { format });
|
|
56
101
|
|
|
57
|
-
// Return success response with formatted history
|
|
58
102
|
return {
|
|
59
103
|
success: true,
|
|
60
104
|
history,
|
|
61
|
-
message: history,
|
|
105
|
+
message: history,
|
|
62
106
|
};
|
|
63
107
|
} catch (error) {
|
|
64
|
-
// Error handling: Return user-friendly message
|
|
65
108
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
66
|
-
|
|
67
109
|
return {
|
|
68
110
|
success: false,
|
|
69
111
|
message: `❌ Failed to recall decisions: ${errorMessage}`,
|
|
70
112
|
};
|
|
71
113
|
}
|
|
72
114
|
},
|
|
73
|
-
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Default instance with real dependency
|
|
118
|
+
const recallDecisionTool = createRecallDecisionTool(mama);
|
|
74
119
|
|
|
75
|
-
module.exports = { recallDecisionTool };
|
|
120
|
+
module.exports = { recallDecisionTool, createRecallDecisionTool };
|
|
@@ -149,6 +149,10 @@ When you find similar past decisions (returned in similar_decisions), choose you
|
|
|
149
149
|
- **debate**: Present a counter-argument with evidence. Explain why the prior decision may be wrong.
|
|
150
150
|
- **synthesize**: Merge multiple decisions into a new unified approach.
|
|
151
151
|
|
|
152
|
+
**SCOPES & TEMPORAL:**
|
|
153
|
+
- Use 'scopes' to isolate decisions per project/channel/user (e.g., [{"kind": "project", "id": "/my/app"}])
|
|
154
|
+
- Use 'event_date' (ISO 8601) to record when events actually happened vs. when they were saved
|
|
155
|
+
|
|
152
156
|
**5-LAYER REASONING (CoT Guide):**
|
|
153
157
|
Structure your reasoning with these layers for maximum value:
|
|
154
158
|
1. **Context**: What problem/situation prompted this decision?
|
|
@@ -211,6 +215,31 @@ Structure your reasoning with these layers for maximum value:
|
|
|
211
215
|
type: 'string',
|
|
212
216
|
description: 'Potential risks or downsides (Tension layer).',
|
|
213
217
|
},
|
|
218
|
+
scopes: {
|
|
219
|
+
type: 'array',
|
|
220
|
+
items: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
kind: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
enum: ['global', 'user', 'channel', 'project'],
|
|
226
|
+
description: 'Scope type',
|
|
227
|
+
},
|
|
228
|
+
id: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
description: 'Scope identifier (e.g., project path, channel ID)',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
required: ['kind', 'id'],
|
|
234
|
+
},
|
|
235
|
+
description:
|
|
236
|
+
'Memory scopes for isolation. Decisions are stored per-scope. If omitted, decision is unscoped (global). Example: [{"kind": "project", "id": "/path/to/project"}]',
|
|
237
|
+
},
|
|
238
|
+
event_date: {
|
|
239
|
+
type: 'string',
|
|
240
|
+
description:
|
|
241
|
+
'ISO 8601 date when the event actually occurred (e.g., "2024-01-15"). If omitted, defaults to current time. Use this when recording decisions about past events.',
|
|
242
|
+
},
|
|
214
243
|
},
|
|
215
244
|
required: ['topic', 'decision', 'reasoning'],
|
|
216
245
|
},
|
|
@@ -226,6 +255,8 @@ Structure your reasoning with these layers for maximum value:
|
|
|
226
255
|
evidence,
|
|
227
256
|
alternatives,
|
|
228
257
|
risks,
|
|
258
|
+
scopes,
|
|
259
|
+
event_date,
|
|
229
260
|
} = params || {};
|
|
230
261
|
|
|
231
262
|
try {
|
|
@@ -305,6 +336,8 @@ Structure your reasoning with these layers for maximum value:
|
|
|
305
336
|
alternatives,
|
|
306
337
|
risks,
|
|
307
338
|
trust_context: trustContext,
|
|
339
|
+
...(scopes && { scopes }),
|
|
340
|
+
...(event_date && { event_date }),
|
|
308
341
|
});
|
|
309
342
|
|
|
310
343
|
// Story 1.2: Return enhanced response with collaborative fields
|
|
@@ -17,7 +17,7 @@ const mama = require('@jungjaehoon/mama-core/mama-api');
|
|
|
17
17
|
const suggestDecisionTool = {
|
|
18
18
|
name: 'suggest_decision',
|
|
19
19
|
description:
|
|
20
|
-
"Auto-suggest relevant past decisions based on user's question. Uses semantic search to find decisions related to the current context. Returns null if no relevant decisions found. Supports multilingual queries (English, Korean, etc.).",
|
|
20
|
+
"Auto-suggest relevant past decisions based on user's question. Uses semantic search to find decisions related to the current context. Returns null if no relevant decisions found. Supports multilingual queries (English, Korean, etc.). Use 'scopes' to filter by project/channel.",
|
|
21
21
|
inputSchema: {
|
|
22
22
|
type: 'object',
|
|
23
23
|
properties: {
|
|
@@ -48,12 +48,24 @@ const suggestDecisionTool = {
|
|
|
48
48
|
description: 'Optional: Disable recency weighting entirely. Default: false',
|
|
49
49
|
default: false,
|
|
50
50
|
},
|
|
51
|
+
scopes: {
|
|
52
|
+
type: 'array',
|
|
53
|
+
items: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
kind: { type: 'string', enum: ['global', 'user', 'channel', 'project'] },
|
|
57
|
+
id: { type: 'string' },
|
|
58
|
+
},
|
|
59
|
+
required: ['kind', 'id'],
|
|
60
|
+
},
|
|
61
|
+
description: 'Filter suggestions by scope. If omitted, searches all scopes.',
|
|
62
|
+
},
|
|
51
63
|
},
|
|
52
64
|
required: ['userQuestion'],
|
|
53
65
|
},
|
|
54
66
|
|
|
55
67
|
async handler(params, _context) {
|
|
56
|
-
const { userQuestion, recencyWeight, recencyScale, recencyDecay, disableRecency } =
|
|
68
|
+
const { userQuestion, recencyWeight, recencyScale, recencyDecay, disableRecency, scopes } =
|
|
57
69
|
params || {};
|
|
58
70
|
|
|
59
71
|
try {
|
|
@@ -65,13 +77,13 @@ const suggestDecisionTool = {
|
|
|
65
77
|
};
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
// Call MAMA API with markdown format
|
|
69
80
|
const suggestions = await mama.suggest(userQuestion, {
|
|
70
81
|
format: 'markdown',
|
|
71
82
|
recencyWeight,
|
|
72
83
|
recencyScale,
|
|
73
84
|
recencyDecay,
|
|
74
85
|
disableRecency,
|
|
86
|
+
...(scopes && { scopes }),
|
|
75
87
|
});
|
|
76
88
|
|
|
77
89
|
if (!suggestions) {
|