@launchapp-dev/ao-memory-mcp 2.0.0 → 2.0.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/dist/db.d.ts +25 -0
- package/dist/db.js +90 -0
- package/dist/embeddings.d.ts +14 -0
- package/dist/embeddings.js +72 -0
- package/dist/schema.sql +194 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +56 -0
- package/dist/tools/context.d.ts +29 -0
- package/dist/tools/context.js +88 -0
- package/dist/tools/documents.d.ts +142 -0
- package/dist/tools/documents.js +201 -0
- package/dist/tools/episodes.d.ts +112 -0
- package/dist/tools/episodes.js +98 -0
- package/dist/tools/knowledge.d.ts +177 -0
- package/dist/tools/knowledge.js +235 -0
- package/dist/tools/recall.d.ts +153 -0
- package/dist/tools/recall.js +180 -0
- package/dist/tools/stats.d.ts +24 -0
- package/dist/tools/stats.js +50 -0
- package/dist/tools/store.d.ts +180 -0
- package/dist/tools/store.js +176 -0
- package/dist/tools/summarize.d.ts +74 -0
- package/dist/tools/summarize.js +92 -0
- package/package.json +9 -5
- package/migrate.ts +0 -250
- package/src/db.ts +0 -106
- package/src/embeddings.ts +0 -97
- package/src/server.ts +0 -70
- package/src/tools/context.ts +0 -106
- package/src/tools/documents.ts +0 -215
- package/src/tools/episodes.ts +0 -112
- package/src/tools/knowledge.ts +0 -248
- package/src/tools/recall.ts +0 -167
- package/src/tools/stats.ts +0 -51
- package/src/tools/store.ts +0 -168
- package/src/tools/summarize.ts +0 -114
- package/tsconfig.json +0 -12
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { jsonResult, errorResult, touchAccess } from "../db.js";
|
|
2
|
+
import { embed, hybridSearch } from "../embeddings.js";
|
|
3
|
+
export const recallTools = [
|
|
4
|
+
{
|
|
5
|
+
name: "memory.recall",
|
|
6
|
+
description: "Recall memories with structured filters. Supports filtering by type, scope, namespace, agent role, tags, date range. Returns entries sorted by date.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
memory_type: { type: "string", enum: ["semantic", "episodic", "procedural"], description: "Filter by type" },
|
|
11
|
+
scope: { type: "string", description: "Filter by scope" },
|
|
12
|
+
namespace: { type: "string", description: "Filter by namespace (project name, etc.)" },
|
|
13
|
+
agent_role: { type: "string", description: "Filter by agent role" },
|
|
14
|
+
task_id: { type: "string", description: "Filter by task ID" },
|
|
15
|
+
status: { type: "string", enum: ["active", "summarized", "archived"], description: "Default: active" },
|
|
16
|
+
date_from: { type: "string", description: "From ISO date" },
|
|
17
|
+
date_to: { type: "string", description: "To ISO date" },
|
|
18
|
+
tags: { type: "array", items: { type: "string" }, description: "Must have ALL specified tags" },
|
|
19
|
+
limit: { type: "number", description: "Max results (default 50)" },
|
|
20
|
+
offset: { type: "number", description: "Pagination offset" },
|
|
21
|
+
order: { type: "string", enum: ["newest", "oldest", "most_accessed", "highest_confidence"], description: "Sort order" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "memory.search",
|
|
27
|
+
description: "Hybrid semantic + keyword search across all memories. Uses vector similarity and FTS5 together for best results.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
query: { type: "string", description: "Natural language search query" },
|
|
32
|
+
memory_type: { type: "string", description: "Restrict to type" },
|
|
33
|
+
namespace: { type: "string", description: "Restrict to namespace" },
|
|
34
|
+
agent_role: { type: "string", description: "Restrict to agent role" },
|
|
35
|
+
status: { type: "string", description: "Restrict to status" },
|
|
36
|
+
limit: { type: "number", description: "Max results (default 10)" },
|
|
37
|
+
alpha: { type: "number", description: "Semantic vs keyword weight: 0=all keyword, 1=all semantic (default 0.5)" },
|
|
38
|
+
},
|
|
39
|
+
required: ["query"],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "memory.get",
|
|
44
|
+
description: "Get a single memory by ID. Updates access tracking.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
id: { type: "number", description: "Memory ID" },
|
|
49
|
+
},
|
|
50
|
+
required: ["id"],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
export function handleRecall(db, name, args) {
|
|
55
|
+
if (name === "memory.recall")
|
|
56
|
+
return memoryRecall(db, args);
|
|
57
|
+
if (name === "memory.search")
|
|
58
|
+
return memorySearch(db, args);
|
|
59
|
+
if (name === "memory.get")
|
|
60
|
+
return memoryGet(db, args);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function memoryRecall(db, args) {
|
|
64
|
+
const conditions = [];
|
|
65
|
+
const vals = [];
|
|
66
|
+
conditions.push("status = ?");
|
|
67
|
+
vals.push(args.status || "active");
|
|
68
|
+
if (args.memory_type) {
|
|
69
|
+
conditions.push("memory_type = ?");
|
|
70
|
+
vals.push(args.memory_type);
|
|
71
|
+
}
|
|
72
|
+
if (args.scope) {
|
|
73
|
+
conditions.push("scope = ?");
|
|
74
|
+
vals.push(args.scope);
|
|
75
|
+
}
|
|
76
|
+
if (args.namespace) {
|
|
77
|
+
conditions.push("namespace = ?");
|
|
78
|
+
vals.push(args.namespace);
|
|
79
|
+
}
|
|
80
|
+
if (args.agent_role) {
|
|
81
|
+
conditions.push("agent_role = ?");
|
|
82
|
+
vals.push(args.agent_role);
|
|
83
|
+
}
|
|
84
|
+
if (args.task_id) {
|
|
85
|
+
conditions.push("task_id = ?");
|
|
86
|
+
vals.push(args.task_id);
|
|
87
|
+
}
|
|
88
|
+
if (args.date_from) {
|
|
89
|
+
conditions.push("occurred_at >= ?");
|
|
90
|
+
vals.push(args.date_from);
|
|
91
|
+
}
|
|
92
|
+
if (args.date_to) {
|
|
93
|
+
conditions.push("occurred_at <= ?");
|
|
94
|
+
vals.push(args.date_to);
|
|
95
|
+
}
|
|
96
|
+
if (args.tags?.length) {
|
|
97
|
+
for (const tag of args.tags) {
|
|
98
|
+
conditions.push("EXISTS (SELECT 1 FROM json_each(tags) WHERE json_each.value = ?)");
|
|
99
|
+
vals.push(tag);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const orderMap = {
|
|
103
|
+
newest: "occurred_at DESC",
|
|
104
|
+
oldest: "occurred_at ASC",
|
|
105
|
+
most_accessed: "access_count DESC",
|
|
106
|
+
highest_confidence: "confidence DESC",
|
|
107
|
+
};
|
|
108
|
+
const order = orderMap[args.order || "newest"] || "occurred_at DESC";
|
|
109
|
+
const limit = args.limit || 50;
|
|
110
|
+
const offset = args.offset || 0;
|
|
111
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
112
|
+
const rows = db.prepare(`SELECT * FROM memories ${where} ORDER BY ${order} LIMIT ? OFFSET ?`).all(...vals, limit, offset);
|
|
113
|
+
return jsonResult({ memories: rows, count: rows.length });
|
|
114
|
+
}
|
|
115
|
+
async function memorySearch(db, args) {
|
|
116
|
+
const limit = args.limit || 10;
|
|
117
|
+
const alpha = args.alpha ?? 0.5;
|
|
118
|
+
// Get embedding for query
|
|
119
|
+
let queryEmbedding;
|
|
120
|
+
try {
|
|
121
|
+
queryEmbedding = await embed(args.query, true);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Fallback to FTS-only
|
|
125
|
+
return ftsOnlySearch(db, args);
|
|
126
|
+
}
|
|
127
|
+
const results = hybridSearch(db, "memories_fts", "vec_memories", args.query, queryEmbedding, limit * 3, alpha);
|
|
128
|
+
if (results.length === 0)
|
|
129
|
+
return jsonResult({ memories: [], count: 0 });
|
|
130
|
+
// Fetch full rows and apply filters
|
|
131
|
+
const ids = results.map(r => r.rowid);
|
|
132
|
+
const scoreMap = new Map(results.map(r => [r.rowid, r.score]));
|
|
133
|
+
const conditions = [`id IN (${ids.map(() => "?").join(",")})`];
|
|
134
|
+
const vals = [...ids];
|
|
135
|
+
if (args.memory_type) {
|
|
136
|
+
conditions.push("memory_type = ?");
|
|
137
|
+
vals.push(args.memory_type);
|
|
138
|
+
}
|
|
139
|
+
if (args.namespace) {
|
|
140
|
+
conditions.push("namespace = ?");
|
|
141
|
+
vals.push(args.namespace);
|
|
142
|
+
}
|
|
143
|
+
if (args.agent_role) {
|
|
144
|
+
conditions.push("agent_role = ?");
|
|
145
|
+
vals.push(args.agent_role);
|
|
146
|
+
}
|
|
147
|
+
if (args.status) {
|
|
148
|
+
conditions.push("status = ?");
|
|
149
|
+
vals.push(args.status);
|
|
150
|
+
}
|
|
151
|
+
const rows = db.prepare(`SELECT * FROM memories WHERE ${conditions.join(" AND ")}`).all(...vals);
|
|
152
|
+
// Sort by hybrid score and limit
|
|
153
|
+
const scored = rows
|
|
154
|
+
.map(r => ({ ...r, _score: scoreMap.get(r.id) || 0 }))
|
|
155
|
+
.sort((a, b) => b._score - a._score)
|
|
156
|
+
.slice(0, limit);
|
|
157
|
+
// Touch access for returned results
|
|
158
|
+
for (const row of scored)
|
|
159
|
+
touchAccess(db, row.id);
|
|
160
|
+
return jsonResult({ memories: scored, count: scored.length });
|
|
161
|
+
}
|
|
162
|
+
function ftsOnlySearch(db, args) {
|
|
163
|
+
const limit = args.limit || 10;
|
|
164
|
+
const rows = db.prepare(`
|
|
165
|
+
SELECT m.*, snippet(memories_fts, 0, '<mark>', '</mark>', '...', 32) as title_snippet,
|
|
166
|
+
snippet(memories_fts, 1, '<mark>', '</mark>', '...', 64) as content_snippet
|
|
167
|
+
FROM memories_fts f
|
|
168
|
+
JOIN memories m ON m.id = f.rowid
|
|
169
|
+
WHERE memories_fts MATCH ?
|
|
170
|
+
ORDER BY rank LIMIT ?
|
|
171
|
+
`).all(args.query, limit);
|
|
172
|
+
return jsonResult({ memories: rows, count: rows.length, mode: "keyword_only" });
|
|
173
|
+
}
|
|
174
|
+
function memoryGet(db, args) {
|
|
175
|
+
const entry = db.prepare("SELECT * FROM memories WHERE id = ?").get(args.id);
|
|
176
|
+
if (!entry)
|
|
177
|
+
return errorResult(`Memory ${args.id} not found`);
|
|
178
|
+
touchAccess(db, args.id);
|
|
179
|
+
return jsonResult(entry);
|
|
180
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
export declare const statsTools: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
namespace: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
agent_role: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}[];
|
|
19
|
+
export declare function handleStats(db: Database.Database, name: string, args: any): {
|
|
20
|
+
content: {
|
|
21
|
+
type: "text";
|
|
22
|
+
text: string;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsonResult } from "../db.js";
|
|
2
|
+
export const statsTools = [
|
|
3
|
+
{
|
|
4
|
+
name: "memory.stats",
|
|
5
|
+
description: "Get aggregate statistics across all memory types.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
namespace: { type: "string", description: "Filter by namespace" },
|
|
10
|
+
agent_role: { type: "string", description: "Filter by agent role" },
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
export function handleStats(db, name, args) {
|
|
16
|
+
if (name === "memory.stats")
|
|
17
|
+
return memoryStats(db, args);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function memoryStats(db, args) {
|
|
21
|
+
const conditions = [];
|
|
22
|
+
const vals = [];
|
|
23
|
+
if (args.namespace) {
|
|
24
|
+
conditions.push("namespace = ?");
|
|
25
|
+
vals.push(args.namespace);
|
|
26
|
+
}
|
|
27
|
+
if (args.agent_role) {
|
|
28
|
+
conditions.push("agent_role = ?");
|
|
29
|
+
vals.push(args.agent_role);
|
|
30
|
+
}
|
|
31
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
32
|
+
const totalMemories = db.prepare(`SELECT COUNT(*) as c FROM memories ${where}`).get(...vals).c;
|
|
33
|
+
const byType = db.prepare(`SELECT memory_type, COUNT(*) as count FROM memories ${where} GROUP BY memory_type`).all(...vals);
|
|
34
|
+
const byStatus = db.prepare(`SELECT status, COUNT(*) as count FROM memories ${where} GROUP BY status`).all(...vals);
|
|
35
|
+
const byScope = db.prepare(`SELECT scope, COUNT(*) as count FROM memories ${where} GROUP BY scope`).all(...vals);
|
|
36
|
+
const byRole = db.prepare(`SELECT agent_role, COUNT(*) as count FROM memories ${where} GROUP BY agent_role`).all(...vals);
|
|
37
|
+
const byNamespace = db.prepare(`SELECT namespace, COUNT(*) as count FROM memories ${where} GROUP BY namespace ORDER BY count DESC LIMIT 20`).all(...vals);
|
|
38
|
+
const dateRange = db.prepare(`SELECT MIN(occurred_at) as oldest, MAX(occurred_at) as newest FROM memories ${where}`).get(...vals);
|
|
39
|
+
const totalDocs = db.prepare("SELECT COUNT(*) as c FROM documents").get().c;
|
|
40
|
+
const totalChunks = db.prepare("SELECT COUNT(*) as c FROM chunks").get().c;
|
|
41
|
+
const totalEntities = db.prepare("SELECT COUNT(*) as c FROM entities").get().c;
|
|
42
|
+
const totalRelations = db.prepare("SELECT COUNT(*) as c FROM relations").get().c;
|
|
43
|
+
const totalEpisodes = db.prepare("SELECT COUNT(*) as c FROM episodes").get().c;
|
|
44
|
+
return jsonResult({
|
|
45
|
+
memories: { total: totalMemories, by_type: byType, by_status: byStatus, by_scope: byScope, by_role: byRole, by_namespace: byNamespace, oldest: dateRange?.oldest, newest: dateRange?.newest },
|
|
46
|
+
documents: { total: totalDocs, total_chunks: totalChunks },
|
|
47
|
+
knowledge_graph: { entities: totalEntities, relations: totalRelations },
|
|
48
|
+
episodes: { total: totalEpisodes },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
export declare const storeTools: ({
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
memory_type: {
|
|
9
|
+
type: string;
|
|
10
|
+
enum: string[];
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
title: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
content: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
scope: {
|
|
22
|
+
type: string;
|
|
23
|
+
enum: string[];
|
|
24
|
+
description: string;
|
|
25
|
+
};
|
|
26
|
+
namespace: {
|
|
27
|
+
type: string;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
agent_role: {
|
|
31
|
+
type: string;
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
task_id: {
|
|
35
|
+
type: string;
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
pr_number: {
|
|
39
|
+
type: string;
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
run_id: {
|
|
43
|
+
type: string;
|
|
44
|
+
description: string;
|
|
45
|
+
};
|
|
46
|
+
tags: {
|
|
47
|
+
type: string;
|
|
48
|
+
items: {
|
|
49
|
+
type: string;
|
|
50
|
+
};
|
|
51
|
+
description: string;
|
|
52
|
+
};
|
|
53
|
+
metadata: {
|
|
54
|
+
type: string;
|
|
55
|
+
description: string;
|
|
56
|
+
};
|
|
57
|
+
confidence: {
|
|
58
|
+
type: string;
|
|
59
|
+
description: string;
|
|
60
|
+
};
|
|
61
|
+
occurred_at: {
|
|
62
|
+
type: string;
|
|
63
|
+
description: string;
|
|
64
|
+
};
|
|
65
|
+
id?: undefined;
|
|
66
|
+
status?: undefined;
|
|
67
|
+
superseded_by?: undefined;
|
|
68
|
+
ids?: undefined;
|
|
69
|
+
before?: undefined;
|
|
70
|
+
};
|
|
71
|
+
required: string[];
|
|
72
|
+
};
|
|
73
|
+
} | {
|
|
74
|
+
name: string;
|
|
75
|
+
description: string;
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: "object";
|
|
78
|
+
properties: {
|
|
79
|
+
id: {
|
|
80
|
+
type: string;
|
|
81
|
+
description: string;
|
|
82
|
+
};
|
|
83
|
+
title: {
|
|
84
|
+
type: string;
|
|
85
|
+
description?: undefined;
|
|
86
|
+
};
|
|
87
|
+
content: {
|
|
88
|
+
type: string;
|
|
89
|
+
description?: undefined;
|
|
90
|
+
};
|
|
91
|
+
status: {
|
|
92
|
+
type: string;
|
|
93
|
+
enum: string[];
|
|
94
|
+
};
|
|
95
|
+
confidence: {
|
|
96
|
+
type: string;
|
|
97
|
+
description?: undefined;
|
|
98
|
+
};
|
|
99
|
+
tags: {
|
|
100
|
+
type: string;
|
|
101
|
+
items: {
|
|
102
|
+
type: string;
|
|
103
|
+
};
|
|
104
|
+
description?: undefined;
|
|
105
|
+
};
|
|
106
|
+
metadata: {
|
|
107
|
+
type: string;
|
|
108
|
+
description?: undefined;
|
|
109
|
+
};
|
|
110
|
+
superseded_by: {
|
|
111
|
+
type: string;
|
|
112
|
+
description: string;
|
|
113
|
+
};
|
|
114
|
+
memory_type?: undefined;
|
|
115
|
+
scope?: undefined;
|
|
116
|
+
namespace?: undefined;
|
|
117
|
+
agent_role?: undefined;
|
|
118
|
+
task_id?: undefined;
|
|
119
|
+
pr_number?: undefined;
|
|
120
|
+
run_id?: undefined;
|
|
121
|
+
occurred_at?: undefined;
|
|
122
|
+
ids?: undefined;
|
|
123
|
+
before?: undefined;
|
|
124
|
+
};
|
|
125
|
+
required: string[];
|
|
126
|
+
};
|
|
127
|
+
} | {
|
|
128
|
+
name: string;
|
|
129
|
+
description: string;
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object";
|
|
132
|
+
properties: {
|
|
133
|
+
ids: {
|
|
134
|
+
type: string;
|
|
135
|
+
items: {
|
|
136
|
+
type: string;
|
|
137
|
+
};
|
|
138
|
+
description: string;
|
|
139
|
+
};
|
|
140
|
+
agent_role: {
|
|
141
|
+
type: string;
|
|
142
|
+
description: string;
|
|
143
|
+
};
|
|
144
|
+
namespace: {
|
|
145
|
+
type: string;
|
|
146
|
+
description: string;
|
|
147
|
+
};
|
|
148
|
+
before: {
|
|
149
|
+
type: string;
|
|
150
|
+
description: string;
|
|
151
|
+
};
|
|
152
|
+
memory_type?: undefined;
|
|
153
|
+
title?: undefined;
|
|
154
|
+
content?: undefined;
|
|
155
|
+
scope?: undefined;
|
|
156
|
+
task_id?: undefined;
|
|
157
|
+
pr_number?: undefined;
|
|
158
|
+
run_id?: undefined;
|
|
159
|
+
tags?: undefined;
|
|
160
|
+
metadata?: undefined;
|
|
161
|
+
confidence?: undefined;
|
|
162
|
+
occurred_at?: undefined;
|
|
163
|
+
id?: undefined;
|
|
164
|
+
status?: undefined;
|
|
165
|
+
superseded_by?: undefined;
|
|
166
|
+
};
|
|
167
|
+
required?: undefined;
|
|
168
|
+
};
|
|
169
|
+
})[];
|
|
170
|
+
export declare function handleStore(db: Database.Database, name: string, args: any): {
|
|
171
|
+
content: {
|
|
172
|
+
type: "text";
|
|
173
|
+
text: string;
|
|
174
|
+
}[];
|
|
175
|
+
} | Promise<{
|
|
176
|
+
content: {
|
|
177
|
+
type: "text";
|
|
178
|
+
text: string;
|
|
179
|
+
}[];
|
|
180
|
+
}>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { contentHash, now, jsonResult, errorResult, touchAccess } from "../db.js";
|
|
2
|
+
import { embed, storeVector } from "../embeddings.js";
|
|
3
|
+
export const storeTools = [
|
|
4
|
+
{
|
|
5
|
+
name: "memory.remember",
|
|
6
|
+
description: "Store a new memory. Automatically embeds for semantic search. Deduplicates via content hash. Supports semantic (facts/knowledge), episodic (events/history), and procedural (how-to/workflows) types.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
memory_type: { type: "string", enum: ["semantic", "episodic", "procedural"], description: "Type: semantic (facts), episodic (events), procedural (how-to)" },
|
|
11
|
+
title: { type: "string", description: "Short summary" },
|
|
12
|
+
content: { type: "string", description: "Full content (markdown)" },
|
|
13
|
+
scope: { type: "string", enum: ["global", "user", "project", "session"], description: "Scope (default: project)" },
|
|
14
|
+
namespace: { type: "string", description: "Scope identifier — project name, user id, session id" },
|
|
15
|
+
agent_role: { type: "string", description: "Agent role storing this memory" },
|
|
16
|
+
task_id: { type: "string", description: "Task ID reference" },
|
|
17
|
+
pr_number: { type: "number", description: "PR number" },
|
|
18
|
+
run_id: { type: "string", description: "Run identifier" },
|
|
19
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags" },
|
|
20
|
+
metadata: { type: "object", description: "Custom metadata" },
|
|
21
|
+
confidence: { type: "number", description: "Confidence score 0.0-1.0 (default 1.0)" },
|
|
22
|
+
occurred_at: { type: "string", description: "ISO 8601 date (default: now)" },
|
|
23
|
+
},
|
|
24
|
+
required: ["memory_type", "title", "content"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "memory.update",
|
|
29
|
+
description: "Update an existing memory by ID.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
id: { type: "number", description: "Memory ID" },
|
|
34
|
+
title: { type: "string" },
|
|
35
|
+
content: { type: "string" },
|
|
36
|
+
status: { type: "string", enum: ["active", "summarized", "archived"] },
|
|
37
|
+
confidence: { type: "number" },
|
|
38
|
+
tags: { type: "array", items: { type: "string" } },
|
|
39
|
+
metadata: { type: "object" },
|
|
40
|
+
superseded_by: { type: "number", description: "ID of memory that replaces this one" },
|
|
41
|
+
},
|
|
42
|
+
required: ["id"],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "memory.forget",
|
|
47
|
+
description: "Archive memories. Soft delete — they remain queryable with status filter.",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
ids: { type: "array", items: { type: "number" }, description: "Specific IDs to archive" },
|
|
52
|
+
agent_role: { type: "string", description: "Archive all for this role" },
|
|
53
|
+
namespace: { type: "string", description: "Archive all in this namespace" },
|
|
54
|
+
before: { type: "string", description: "Archive entries before this ISO date" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
export function handleStore(db, name, args) {
|
|
60
|
+
if (name === "memory.remember")
|
|
61
|
+
return memoryRemember(db, args);
|
|
62
|
+
if (name === "memory.update")
|
|
63
|
+
return memoryUpdate(db, args);
|
|
64
|
+
if (name === "memory.forget")
|
|
65
|
+
return memoryForget(db, args);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
async function memoryRemember(db, args) {
|
|
69
|
+
const { memory_type, title, content } = args;
|
|
70
|
+
const scope = args.scope || "project";
|
|
71
|
+
const hash = contentHash(memory_type, scope, args.namespace || "", title, content);
|
|
72
|
+
const existing = db.prepare("SELECT id FROM memories WHERE content_hash = ?").get(hash);
|
|
73
|
+
if (existing) {
|
|
74
|
+
touchAccess(db, existing.id);
|
|
75
|
+
return jsonResult({ duplicate: true, existing_id: existing.id });
|
|
76
|
+
}
|
|
77
|
+
const ts = now();
|
|
78
|
+
const result = db.prepare(`
|
|
79
|
+
INSERT INTO memories (memory_type, scope, namespace, agent_role, title, content, task_id, pr_number, run_id, status, confidence, tags, metadata, created_at, occurred_at, updated_at, content_hash)
|
|
80
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?)
|
|
81
|
+
`).run(memory_type, scope, args.namespace || null, args.agent_role || null, title, content, args.task_id || null, args.pr_number || null, args.run_id || null, args.confidence ?? 1.0, JSON.stringify(args.tags || []), JSON.stringify(args.metadata || {}), ts, args.occurred_at || ts, ts, hash);
|
|
82
|
+
const id = Number(result.lastInsertRowid);
|
|
83
|
+
// Embed asynchronously
|
|
84
|
+
try {
|
|
85
|
+
const embedding = await embed(`${title}\n${content}`);
|
|
86
|
+
storeVector(db, "vec_memories", id, embedding);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
console.error(`[ao-memory] Embedding failed for memory ${id}:`, e);
|
|
90
|
+
}
|
|
91
|
+
return jsonResult({ id, created: true });
|
|
92
|
+
}
|
|
93
|
+
async function memoryUpdate(db, args) {
|
|
94
|
+
const { id, ...updates } = args;
|
|
95
|
+
const entry = db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
96
|
+
if (!entry)
|
|
97
|
+
return errorResult(`Memory ${id} not found`);
|
|
98
|
+
const sets = [];
|
|
99
|
+
const vals = [];
|
|
100
|
+
if (updates.title !== undefined) {
|
|
101
|
+
sets.push("title = ?");
|
|
102
|
+
vals.push(updates.title);
|
|
103
|
+
}
|
|
104
|
+
if (updates.content !== undefined) {
|
|
105
|
+
sets.push("content = ?");
|
|
106
|
+
vals.push(updates.content);
|
|
107
|
+
}
|
|
108
|
+
if (updates.status !== undefined) {
|
|
109
|
+
sets.push("status = ?");
|
|
110
|
+
vals.push(updates.status);
|
|
111
|
+
}
|
|
112
|
+
if (updates.confidence !== undefined) {
|
|
113
|
+
sets.push("confidence = ?");
|
|
114
|
+
vals.push(updates.confidence);
|
|
115
|
+
}
|
|
116
|
+
if (updates.superseded_by !== undefined) {
|
|
117
|
+
sets.push("superseded_by = ?");
|
|
118
|
+
vals.push(updates.superseded_by);
|
|
119
|
+
}
|
|
120
|
+
if (updates.tags !== undefined) {
|
|
121
|
+
sets.push("tags = ?");
|
|
122
|
+
vals.push(JSON.stringify(updates.tags));
|
|
123
|
+
}
|
|
124
|
+
if (updates.metadata !== undefined) {
|
|
125
|
+
const merged = { ...JSON.parse(entry.metadata), ...updates.metadata };
|
|
126
|
+
sets.push("metadata = ?");
|
|
127
|
+
vals.push(JSON.stringify(merged));
|
|
128
|
+
}
|
|
129
|
+
if (sets.length === 0)
|
|
130
|
+
return errorResult("No fields to update");
|
|
131
|
+
sets.push("updated_at = ?");
|
|
132
|
+
vals.push(now());
|
|
133
|
+
if (updates.title !== undefined || updates.content !== undefined) {
|
|
134
|
+
const newTitle = updates.title || entry.title;
|
|
135
|
+
const newContent = updates.content || entry.content;
|
|
136
|
+
const hash = contentHash(entry.memory_type, entry.scope, entry.namespace || "", newTitle, newContent);
|
|
137
|
+
sets.push("content_hash = ?");
|
|
138
|
+
vals.push(hash);
|
|
139
|
+
// Re-embed
|
|
140
|
+
try {
|
|
141
|
+
const embedding = await embed(`${newTitle}\n${newContent}`);
|
|
142
|
+
storeVector(db, "vec_memories", id, embedding);
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
145
|
+
}
|
|
146
|
+
vals.push(id);
|
|
147
|
+
db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...vals);
|
|
148
|
+
return jsonResult({ id, updated: true });
|
|
149
|
+
}
|
|
150
|
+
function memoryForget(db, args) {
|
|
151
|
+
const { ids, agent_role, namespace, before } = args;
|
|
152
|
+
if (!ids && !agent_role && !namespace && !before) {
|
|
153
|
+
return errorResult("At least one filter required");
|
|
154
|
+
}
|
|
155
|
+
const conditions = ["status = 'active'"];
|
|
156
|
+
const vals = [];
|
|
157
|
+
if (ids?.length) {
|
|
158
|
+
conditions.push(`id IN (${ids.map(() => "?").join(",")})`);
|
|
159
|
+
vals.push(...ids);
|
|
160
|
+
}
|
|
161
|
+
if (agent_role) {
|
|
162
|
+
conditions.push("agent_role = ?");
|
|
163
|
+
vals.push(agent_role);
|
|
164
|
+
}
|
|
165
|
+
if (namespace) {
|
|
166
|
+
conditions.push("namespace = ?");
|
|
167
|
+
vals.push(namespace);
|
|
168
|
+
}
|
|
169
|
+
if (before) {
|
|
170
|
+
conditions.push("occurred_at < ?");
|
|
171
|
+
vals.push(before);
|
|
172
|
+
}
|
|
173
|
+
const ts = now();
|
|
174
|
+
const result = db.prepare(`UPDATE memories SET status = 'archived', updated_at = ? WHERE ${conditions.join(" AND ")}`).run(ts, ...vals);
|
|
175
|
+
return jsonResult({ archived_count: result.changes });
|
|
176
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
export declare const summarizeTools: ({
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
namespace: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
agent_role: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
summary_title: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
summary_body: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
before: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
entry_ids: {
|
|
29
|
+
type: string;
|
|
30
|
+
items: {
|
|
31
|
+
type: string;
|
|
32
|
+
};
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
older_than_days?: undefined;
|
|
36
|
+
min_entries?: undefined;
|
|
37
|
+
dry_run?: undefined;
|
|
38
|
+
};
|
|
39
|
+
required: string[];
|
|
40
|
+
};
|
|
41
|
+
} | {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object";
|
|
46
|
+
properties: {
|
|
47
|
+
older_than_days: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
};
|
|
51
|
+
min_entries: {
|
|
52
|
+
type: string;
|
|
53
|
+
description: string;
|
|
54
|
+
};
|
|
55
|
+
dry_run: {
|
|
56
|
+
type: string;
|
|
57
|
+
description: string;
|
|
58
|
+
};
|
|
59
|
+
namespace?: undefined;
|
|
60
|
+
agent_role?: undefined;
|
|
61
|
+
summary_title?: undefined;
|
|
62
|
+
summary_body?: undefined;
|
|
63
|
+
before?: undefined;
|
|
64
|
+
entry_ids?: undefined;
|
|
65
|
+
};
|
|
66
|
+
required?: undefined;
|
|
67
|
+
};
|
|
68
|
+
})[];
|
|
69
|
+
export declare function handleSummarize(db: Database.Database, name: string, args: any): {
|
|
70
|
+
content: {
|
|
71
|
+
type: "text";
|
|
72
|
+
text: string;
|
|
73
|
+
}[];
|
|
74
|
+
};
|