@riligar/agents-memories 1.0.0 → 1.1.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@riligar/agents-memories",
3
- "version": "1.0.0",
4
- "description": "RiLiGar Agents Memories - A persistent memories system for AI agents.",
3
+ "version": "1.1.0",
4
+ "description": "RiLiGar Agents Memories - A persistent memories system for AI agents (Modular SDK).",
5
5
  "module": "src/index.js",
6
6
  "main": "src/index.js",
7
7
  "type": "module",
@@ -23,10 +23,9 @@
23
23
  },
24
24
  "homepage": "https://github.com/riligar/agents-memories#readme",
25
25
  "scripts": {
26
- "dev": "bun --watch src/index.js",
27
- "start": "bun src/index.js",
28
- "test": "bun src/test.js",
29
- "test:sse": "bun src/test-sse.js"
26
+ "dev": "bun --watch src/server/index.js",
27
+ "start": "bun src/server/index.js",
28
+ "test": "bun src/test.js"
30
29
  },
31
30
  "dependencies": {
32
31
  "@huggingface/transformers": "^4.0.1",
@@ -0,0 +1,33 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import crypto from 'crypto'
4
+
5
+ /**
6
+ * Utilitário para gerenciar a identidade criptográfica do agente.
7
+ */
8
+ export function getSystemIdentity(dataDir) {
9
+ const identityPath = path.join(dataDir, 'identity.json')
10
+
11
+ if (fs.existsSync(identityPath)) {
12
+ const data = JSON.parse(fs.readFileSync(identityPath, 'utf8'))
13
+ return data.owner_id
14
+ }
15
+
16
+ if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
17
+
18
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519', {
19
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
20
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
21
+ })
22
+
23
+ const ownerId = crypto.createHash('sha256').update(publicKey).digest('hex').substring(0, 10)
24
+
25
+ fs.writeFileSync(identityPath, JSON.stringify({
26
+ owner_id: ownerId,
27
+ public_key: publicKey,
28
+ private_key: privateKey
29
+ }, null, 2), 'utf8')
30
+
31
+ console.error(`Generated new cryptographic identity: ${ownerId}`)
32
+ return ownerId
33
+ }
@@ -0,0 +1,62 @@
1
+ import { pipeline } from '@huggingface/transformers'
2
+
3
+ let extractor = null
4
+
5
+ /**
6
+ * Gera embeddings para o texto fornecido.
7
+ */
8
+ export async function getEmbedding(text) {
9
+ if (!extractor) {
10
+ extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2')
11
+ }
12
+ const output = await extractor(text, { pooling: 'mean', normalize: true })
13
+ return new Float32Array(output.data)
14
+ }
15
+
16
+ /**
17
+ * Infere a prioridade biológica/semântica com base na vizinhança.
18
+ */
19
+ export async function inferPriority(db, vector) {
20
+ const result = await db.execute({
21
+ sql: `SELECT priority, vector_distance_cos(embedding, ?) as distance FROM memories ORDER BY distance ASC LIMIT 5`,
22
+ args: [vector.buffer],
23
+ })
24
+ if (result.rows.length === 0) return 5
25
+ let weightedSum = 0,
26
+ totalWeight = 0
27
+ for (const row of result.rows) {
28
+ const similarity = 1 - row.distance
29
+ const weight = Math.pow(similarity, 2)
30
+ weightedSum += row.priority * weight
31
+ totalWeight += weight
32
+ }
33
+ return totalWeight > 0 ? Math.round(weightedSum / totalWeight) : 5
34
+ }
35
+
36
+ /**
37
+ * Propaga o impacto (ressonância) para memórias vizinhas.
38
+ */
39
+ export async function propagateRipple(db, vector, manualPriority) {
40
+ const result = await db.execute({
41
+ sql: `SELECT id, priority, vector_distance_cos(embedding, ?) as distance FROM memories ORDER BY distance ASC LIMIT 5`,
42
+ args: [vector.buffer],
43
+ })
44
+ for (const row of result.rows) {
45
+ const similarity = 1 - row.distance
46
+ const resonantPriority = Math.round(manualPriority * similarity)
47
+ if (resonantPriority > row.priority) {
48
+ await db.execute({ sql: `UPDATE memories SET priority = ? WHERE id = ?`, args: [resonantPriority, row.id] })
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Encontra o vizinho mais próximo para o Semantic Magnet.
55
+ */
56
+ export async function findClosestNeighbor(db, vector) {
57
+ const result = await db.execute({
58
+ sql: `SELECT id, path, vector_distance_cos(embedding, ?) as distance FROM memories ORDER BY distance ASC LIMIT 1`,
59
+ args: [vector.buffer],
60
+ })
61
+ return result.rows[0] || null
62
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Esquema de banco de dados do RiLiGar Agents Memories.
3
+ * Utiliza extensões de vetor nativas do LibSQL (vector_distance_cos).
4
+ */
5
+
6
+ export const SCHEMA_V1 = {
7
+ memories: `
8
+ CREATE TABLE IF NOT EXISTS memories (
9
+ id TEXT PRIMARY KEY DEFAULT (LOWER(HEX(RANDOMBLOB(5)))),
10
+ content TEXT NOT NULL,
11
+ path TEXT NOT NULL,
12
+ entities TEXT,
13
+ embedding F32_BLOB(384),
14
+ owner_id TEXT NOT NULL,
15
+ priority INTEGER DEFAULT 5,
16
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
17
+ );
18
+ `,
19
+ links: `
20
+ CREATE TABLE IF NOT EXISTS links (
21
+ source_id TEXT,
22
+ target_id TEXT,
23
+ relation_type TEXT,
24
+ PRIMARY KEY (source_id, target_id, relation_type)
25
+ );
26
+ `
27
+ }
28
+
29
+ export async function initializeSchema(db) {
30
+ await db.execute(SCHEMA_V1.memories)
31
+ await db.execute(SCHEMA_V1.links)
32
+ }
package/src/index.js CHANGED
@@ -1,344 +1,7 @@
1
- import { createClient } from '@libsql/client'
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js'
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
5
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
6
- import { pipeline } from '@huggingface/transformers'
7
- import express from 'express'
8
- import cors from 'cors'
9
- import fs from 'fs'
10
- import path from 'path'
11
- import crypto from 'crypto'
1
+ // Main Export File for @riligar/agents-memories SDK
12
2
 
13
- // ==========================================
14
- // 1. DATABASE & SETUP (Relational Knowledge Graph)
15
- // ==========================================
16
- const db = createClient({ url: 'file:data/memories.db' })
17
- const DATA_DIR = 'data'
18
- const IDENTITY_JSON_PATH = path.join(DATA_DIR, 'identity.json')
19
-
20
- if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR)
21
-
22
- function getSystemIdentity() {
23
- if (fs.existsSync(IDENTITY_JSON_PATH)) {
24
- const data = JSON.parse(fs.readFileSync(IDENTITY_JSON_PATH, 'utf8'))
25
- return data.owner_id
26
- }
27
-
28
- const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519', {
29
- publicKeyEncoding: { type: 'spki', format: 'pem' },
30
- privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
31
- })
32
-
33
- const ownerId = crypto.createHash('sha256').update(publicKey).digest('hex').substring(0, 10)
34
-
35
- fs.writeFileSync(IDENTITY_JSON_PATH, JSON.stringify({
36
- owner_id: ownerId,
37
- public_key: publicKey,
38
- private_key: privateKey
39
- }, null, 2), 'utf8')
40
-
41
- console.error(`Generated new cryptographic identity: ${ownerId}`)
42
- return ownerId
43
- }
44
-
45
- const SYSTEM_OWNER_ID = getSystemIdentity()
46
- console.error(`System Identity initialized: ${SYSTEM_OWNER_ID}`)
47
-
48
- await db.execute(`
49
- CREATE TABLE IF NOT EXISTS memories (
50
- id TEXT PRIMARY KEY DEFAULT (LOWER(HEX(RANDOMBLOB(5)))),
51
- content TEXT NOT NULL,
52
- path TEXT NOT NULL,
53
- entities TEXT,
54
- embedding F32_BLOB(384),
55
- owner_id TEXT NOT NULL,
56
- priority INTEGER DEFAULT 5,
57
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
58
- );
59
- `)
60
-
61
- await db.execute(`
62
- CREATE TABLE IF NOT EXISTS links (
63
- source_id TEXT,
64
- target_id TEXT,
65
- relation_type TEXT,
66
- PRIMARY KEY (source_id, target_id, relation_type)
67
- );
68
- `)
69
-
70
- let extractor = null
71
- async function getEmbedding(text) {
72
- if (!extractor) {
73
- extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2')
74
- }
75
- const output = await extractor(text, { pooling: 'mean', normalize: true })
76
- return new Float32Array(output.data)
77
- }
78
-
79
- // ==========================================
80
- // 2. COGNITIVE LOGIC (Inference, Ripple & Graph)
81
- // ==========================================
82
-
83
- async function inferPriority(vector) {
84
- const result = await db.execute({
85
- sql: `SELECT priority, vector_distance_cos(embedding, ?) as distance FROM memories ORDER BY distance ASC LIMIT 5`,
86
- args: [vector.buffer],
87
- })
88
- if (result.rows.length === 0) return 5
89
- let weightedSum = 0,
90
- totalWeight = 0
91
- for (const row of result.rows) {
92
- const similarity = 1 - row.distance
93
- const weight = Math.pow(similarity, 2)
94
- weightedSum += row.priority * weight
95
- totalWeight += weight
96
- }
97
- return totalWeight > 0 ? Math.round(weightedSum / totalWeight) : 5
98
- }
99
-
100
- async function propagateRipple(vector, manualPriority) {
101
- const result = await db.execute({
102
- sql: `SELECT id, priority, vector_distance_cos(embedding, ?) as distance FROM memories ORDER BY distance ASC LIMIT 5`,
103
- args: [vector.buffer],
104
- })
105
- for (const row of result.rows) {
106
- const similarity = 1 - row.distance
107
- const resonantPriority = Math.round(manualPriority * similarity)
108
- if (resonantPriority > row.priority) {
109
- await db.execute({ sql: `UPDATE memories SET priority = ? WHERE id = ?`, args: [resonantPriority, row.id] })
110
- }
111
- }
112
- }
113
-
114
- async function findClosestNeighbor(vector) {
115
- const result = await db.execute({
116
- sql: `SELECT id, path, vector_distance_cos(embedding, ?) as distance FROM memories ORDER BY distance ASC LIMIT 1`,
117
- args: [vector.buffer],
118
- })
119
- return result.rows[0] || null
120
- }
121
-
122
- // ==========================================
123
- // 3. MCP SERVER FACTORY
124
- // ==========================================
125
-
126
- function createMcpServer() {
127
- const server = new Server({ name: 'RiLiGar Agents Memories', version: '4.0.0' }, { capabilities: { tools: {} } })
128
-
129
- server.setRequestHandler(ListToolsRequestSchema, async () => {
130
- return {
131
- tools: [
132
- {
133
- name: 'save',
134
- description: 'Saves info with Resonance and Auto-Bridging (Semantic Magnet).',
135
- inputSchema: {
136
- type: 'object',
137
- properties: {
138
- content: {
139
- oneOf: [{ type: 'string' }, { type: 'array', items: { anyOf: [{ type: 'string' }, { type: 'object', properties: { content: { type: 'string' }, path: { type: 'string' }, owner_id: { type: 'string' }, priority: { type: 'integer' } }, required: ['content'] }] } }],
140
- },
141
- path: { type: 'string' },
142
- owner_id: { type: 'string' },
143
- priority: { type: 'integer' },
144
- },
145
- required: ['content'],
146
- },
147
- },
148
- {
149
- name: 'bridge',
150
- description: 'Manually connects two memory paths.',
151
- inputSchema: {
152
- type: 'object',
153
- properties: {
154
- source_path: { type: 'string' },
155
- target_path: { type: 'string' },
156
- relation: { type: 'string', default: 'related_to' },
157
- },
158
- required: ['source_path', 'target_path'],
159
- },
160
- },
161
- {
162
- name: 'search',
163
- description: 'Semantic search with Relational Awareness.',
164
- inputSchema: {
165
- type: 'object',
166
- properties: {
167
- query: { type: 'string' },
168
- path_filter: { type: 'string' },
169
- },
170
- required: ['query'],
171
- },
172
- },
173
- {
174
- name: 'inspect',
175
- description: 'Visualizes the Knowledge Graph in Mermaid.js syntax.',
176
- inputSchema: { type: 'object', properties: {} },
177
- },
178
- {
179
- name: 'list',
180
- description: 'Lists all memory paths.',
181
- inputSchema: { type: 'object', properties: {} },
182
- },
183
- ],
184
- }
185
- })
186
-
187
- server.setRequestHandler(CallToolRequestSchema, async request => {
188
- const { name, arguments: args } = request.params
189
-
190
- try {
191
- if (name === 'save') {
192
- const results = []
193
- const rawContent = args.content
194
- const defaultPath = args.path || 'General'
195
- const memoriesToSave = Array.isArray(rawContent) ? rawContent.map(m => (typeof m === 'string' ? { content: m } : m)) : [{ content: rawContent, path: args.path, owner_id: args.owner_id, priority: args.priority }]
196
-
197
- for (const mem of memoriesToSave) {
198
- if (!mem.content) continue
199
- const vector = await getEmbedding(mem.content)
200
- const path = mem.path || defaultPath
201
-
202
- const neighbor = await findClosestNeighbor(vector)
203
- const isManualPriority = typeof mem.priority === 'number'
204
- const finalPriority = isManualPriority ? mem.priority : await inferPriority(vector)
205
-
206
- const insertRes = await db.execute({
207
- sql: `INSERT INTO memories (content, path, embedding, owner_id, priority) VALUES (?, ?, ?, ?, ?) RETURNING id`,
208
- args: [mem.content, path, vector.buffer, mem.owner_id || args.owner_id || SYSTEM_OWNER_ID, finalPriority],
209
- })
210
- const newId = insertRes.rows[0].id
211
-
212
- if (neighbor && 1 - neighbor.distance > 0.9) {
213
- await db.execute({
214
- sql: `INSERT OR IGNORE INTO links (source_id, target_id, relation_type) VALUES (?, ?, ?)`,
215
- args: [newId, neighbor.id, 'similar_to'],
216
- })
217
- }
218
-
219
- if (isManualPriority) await propagateRipple(vector, finalPriority)
220
- results.push(path)
221
- }
222
- return { content: [{ type: 'text', text: `Saved ${results.length} memories. Semantic Magnet applied.` }] }
223
- }
224
-
225
- if (name === 'bridge') {
226
- const src = await db.execute({ sql: `SELECT id FROM memories WHERE path = ? LIMIT 1`, args: [args.source_path] })
227
- const tgt = await db.execute({ sql: `SELECT id FROM memories WHERE path = ? LIMIT 1`, args: [args.target_path] })
228
- if (!src.rows[0] || !tgt.rows[0]) throw new Error('Path(s) not found.')
229
-
230
- await db.execute({
231
- sql: `INSERT OR IGNORE INTO links (source_id, target_id, relation_type) VALUES (?, ?, ?)`,
232
- args: [src.rows[0].id, tgt.rows[0].id, args.relation || 'related_to'],
233
- })
234
- return { content: [{ type: 'text', text: `Bridge created: [${args.source_path}] --(${args.relation})--> [${args.target_path}]` }] }
235
- }
236
-
237
- if (name === 'search') {
238
- const vector = await getEmbedding(args.query)
239
- let sql = `SELECT id, content, path, priority, created_at, vector_distance_cos(embedding, ?) as distance FROM memories`
240
- const queryArgs = [vector.buffer],
241
- filters = []
242
- if (args.path_filter) {
243
- filters.push(`path LIKE ?`)
244
- queryArgs.push(args.path_filter)
245
- }
246
- if (filters.length > 0) sql += ` WHERE ` + filters.join(' AND ')
247
-
248
- const result = await db.execute({ sql, args: queryArgs })
249
- const rows = result.rows
250
- .map(r => ({ ...r, score: (1 - r.distance) * 0.6 + (1 / (1 + (new Date() - new Date(r.created_at)) / 86400000)) * 0.4 }))
251
- .sort((a, b) => b.score - a.score)
252
- .slice(0, 5)
253
-
254
- const formattedResults = []
255
- for (const r of rows) {
256
- const links = await db.execute({
257
- sql: `SELECT m.path, l.relation_type FROM links l JOIN memories m ON l.target_id = m.id WHERE l.source_id = ?`,
258
- args: [r.id],
259
- })
260
- const linkText = links.rows.length > 0 ? `\n🔗 Related: ${links.rows.map(l => `${l.path} (${l.relation_type})`).join(', ')}` : ''
261
- formattedResults.push(`[${r.path} | P:${r.priority}] ${r.content}${linkText}`)
262
- }
263
- return { content: [{ type: 'text', text: formattedResults.join('\n\n') || 'No results.' }] }
264
- }
265
-
266
- if (name === 'inspect') {
267
- const result = await db.execute(`
268
- SELECT m1.path as src, m2.path as tgt, l.relation_type as rel
269
- FROM links l
270
- JOIN memories m1 ON l.source_id = m1.id
271
- JOIN memories m2 ON l.target_id = m2.id
272
- `)
273
- let mermaid = 'graph TD\n'
274
- result.rows.forEach(r => {
275
- mermaid += ` ${r.src.replace(/\./g, '_')}["${r.src}"] -- "${r.rel}" --> ${r.tgt.replace(/\./g, '_')}["${r.tgt}"]\n`
276
- })
277
- return { content: [{ type: 'text', text: `### Knowledge Graph\n\n\`\`\`mermaid\n${mermaid}\`\`\`` }] }
278
- }
279
-
280
- if (name === 'list') {
281
- const result = await db.execute(`SELECT DISTINCT path FROM memories ORDER BY path`)
282
- return { content: [{ type: 'text', text: result.rows.map(r => `- ${r.path}`).join('\n') || 'Empty.' }] }
283
- }
284
-
285
- throw new Error('Unknown tool.')
286
- } catch (error) {
287
- return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
288
- }
289
- })
290
-
291
- return server
292
- }
293
-
294
- // ==========================================
295
- // 4. CONNECTION HANDLING (STDIO + HTTP)
296
- // ==========================================
297
-
298
- // 4.1. STDIO Connection
299
- const stdioServer = createMcpServer()
300
- const stdioTransport = new StdioServerTransport()
301
- stdioServer.connect(stdioTransport).catch(err => console.error('STDIO Error:', err))
302
- console.error('RiLiGar Agents Memories running via STDIO.')
303
-
304
- // 4.2. Express Server with SDK SSE Transport
305
- const app = express()
306
- app.use(cors())
307
- // Note: We do NOT use express.json() because SSEServerTransport handles body parsing manually via getRawBody.
308
-
309
- const sseSessions = new Map()
310
-
311
- app.get('/health', (req, res) => {
312
- res.status(200).send('OK')
313
- })
314
-
315
- app.get('/sse', async (req, res) => {
316
- const transport = new SSEServerTransport('/messages', res)
317
- const sessionServer = createMcpServer()
318
- await sessionServer.connect(transport)
319
-
320
- const sessionId = transport.sessionId
321
- sseSessions.set(sessionId, transport)
322
- console.error(`SSE session started: ${sessionId}`)
323
-
324
- res.on('close', () => {
325
- console.error(`SSE session closed: ${sessionId}`)
326
- sseSessions.delete(sessionId)
327
- })
328
- })
329
-
330
- app.post('/messages', async (req, res) => {
331
- const sessionId = req.query.sessionId
332
- const transport = sseSessions.get(sessionId)
333
-
334
- if (!transport) {
335
- return res.status(404).send('Session not found')
336
- }
337
-
338
- await transport.handlePostMessage(req, res)
339
- })
340
-
341
- const PORT = 3000
342
- app.listen(PORT, () => {
343
- console.error(`RiLiGar Agents Memories (Express) running at http://localhost:${PORT}`)
344
- })
3
+ export { MemorySystem } from './sdk/memory-system.js'
4
+ export { getSystemIdentity } from './core/identity.js'
5
+ export * from './core/logic.js'
6
+ export * from './database/schema.js'
7
+ export { createMcpServer } from './server/mcp-server.js'
@@ -0,0 +1,143 @@
1
+ import crypto from 'crypto'
2
+ import { getEmbedding, inferPriority, propagateRipple, findClosestNeighbor } from '../core/logic.js'
3
+ import { initializeSchema } from '../database/schema.js'
4
+
5
+ /**
6
+ * MemorySystem: A interface programática do RiLiGar Agents Memories.
7
+ * Desacoplada de transporte (SSE/Stdio) e orquestração.
8
+ */
9
+ export class MemorySystem {
10
+ constructor(db, ownerId = null) {
11
+ this.db = db
12
+ this.ownerId = ownerId
13
+ }
14
+
15
+ /**
16
+ * Inicializa o sistema (Schema).
17
+ */
18
+ async init() {
19
+ await initializeSchema(this.db)
20
+ }
21
+
22
+ /**
23
+ * Salva uma memória com Ressonância e Magnetismo Semântico.
24
+ */
25
+ async save(args) {
26
+ const rawContent = args.content
27
+ const defaultPath = args.path || 'General'
28
+ const memoriesToSave = Array.isArray(rawContent)
29
+ ? rawContent.map(m => (typeof m === 'string' ? { content: m } : m))
30
+ : [{ content: rawContent, path: args.path, owner_id: args.owner_id, priority: args.priority }]
31
+
32
+ const results = []
33
+ for (const mem of memoriesToSave) {
34
+ if (!mem.content) continue
35
+ const vector = await getEmbedding(mem.content)
36
+ const path = mem.path || defaultPath
37
+ const ownerId = mem.owner_id || args.owner_id || this.ownerId
38
+
39
+ const neighbor = await findClosestNeighbor(this.db, vector)
40
+ const isManualPriority = typeof mem.priority === 'number'
41
+ const finalPriority = isManualPriority ? mem.priority : await inferPriority(this.db, vector)
42
+
43
+ const insertRes = await this.db.execute({
44
+ sql: `INSERT INTO memories (content, path, embedding, owner_id, priority) VALUES (?, ?, ?, ?, ?) RETURNING id`,
45
+ args: [mem.content, path, vector.buffer, ownerId, finalPriority],
46
+ })
47
+ const newId = insertRes.rows[0].id
48
+
49
+ // Semantic Magnet: Auto-bridge near-identical facts
50
+ if (neighbor && 1 - neighbor.distance > 0.9) {
51
+ await this.db.execute({
52
+ sql: `INSERT OR IGNORE INTO links (source_id, target_id, relation_type) VALUES (?, ?, ?)`,
53
+ args: [newId, neighbor.id, 'similar_to'],
54
+ })
55
+ }
56
+
57
+ if (isManualPriority) await propagateRipple(this.db, vector, finalPriority)
58
+ results.push(path)
59
+ }
60
+
61
+ return { results, count: results.length }
62
+ }
63
+
64
+ /**
65
+ * Busca semântica relacional.
66
+ */
67
+ async search(args) {
68
+ const vector = await getEmbedding(args.query)
69
+ let sql = `SELECT id, content, path, priority, created_at, vector_distance_cos(embedding, ?) as distance FROM memories`
70
+ const queryArgs = [vector.buffer], filters = []
71
+
72
+ if (args.path_filter) {
73
+ filters.push(`path LIKE ?`)
74
+ queryArgs.push(args.path_filter)
75
+ }
76
+
77
+ if (filters.length > 0) sql += ` WHERE ` + filters.join(' AND ')
78
+
79
+ const result = await this.db.execute({ sql, args: queryArgs })
80
+ const rows = result.rows
81
+ .map(r => ({
82
+ ...r,
83
+ score: (1 - r.distance) * 0.6 + (1 / (1 + (new Date() - new Date(r.created_at)) / 86400000)) * 0.4
84
+ }))
85
+ .sort((a, b) => b.score - a.score)
86
+ .slice(0, 5)
87
+
88
+ const formattedResults = []
89
+ for (const r of rows) {
90
+ const links = await this.db.execute({
91
+ sql: `SELECT m.path, l.relation_type FROM links l JOIN memories m ON l.target_id = m.id WHERE l.source_id = ?`,
92
+ args: [r.id],
93
+ })
94
+ formattedResults.push({
95
+ ...r,
96
+ links: links.rows
97
+ })
98
+ }
99
+ return formattedResults
100
+ }
101
+
102
+ /**
103
+ * Cria uma ponte manual entre dois caminhos.
104
+ */
105
+ async bridge(args) {
106
+ const src = await this.db.execute({ sql: `SELECT id FROM memories WHERE path = ? LIMIT 1`, args: [args.source_path] })
107
+ const tgt = await this.db.execute({ sql: `SELECT id FROM memories WHERE path = ? LIMIT 1`, args: [args.target_path] })
108
+
109
+ if (!src.rows[0] || !tgt.rows[0]) throw new Error('Path(s) not found.')
110
+
111
+ await this.db.execute({
112
+ sql: `INSERT OR IGNORE INTO links (source_id, target_id, relation_type) VALUES (?, ?, ?)`,
113
+ args: [src.rows[0].id, tgt.rows[0].id, args.relation || 'related_to'],
114
+ })
115
+
116
+ return true
117
+ }
118
+
119
+ /**
120
+ * Gera o Knowledge Graph em formato Mermaid.
121
+ */
122
+ async inspect() {
123
+ const result = await this.db.execute(`
124
+ SELECT m1.path as src, m2.path as tgt, l.relation_type as rel
125
+ FROM links l
126
+ JOIN memories m1 ON l.source_id = m1.id
127
+ JOIN memories m2 ON l.target_id = m2.id
128
+ `)
129
+ let mermaid = 'graph TD\n'
130
+ result.rows.forEach(r => {
131
+ mermaid += ` ${r.src.replace(/\./g, '_')}["${r.src}"] -- "${r.rel}" --> ${r.tgt.replace(/\./g, '_')}["${r.tgt}"]\n`
132
+ })
133
+ return mermaid
134
+ }
135
+
136
+ /**
137
+ * Lista caminhos únicos.
138
+ */
139
+ async list() {
140
+ const result = await this.db.execute(`SELECT DISTINCT path FROM memories ORDER BY path`)
141
+ return result.rows.map(r => r.path)
142
+ }
143
+ }
@@ -0,0 +1,56 @@
1
+ import { createClient } from '@libsql/client'
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
4
+ import express from 'express'
5
+ import cors from 'cors'
6
+ import path from 'path'
7
+
8
+ import { MemorySystem, getSystemIdentity, createMcpServer } from '../index.js'
9
+
10
+ // 1. SETUP
11
+ const DATA_DIR = 'data'
12
+ const SYSTEM_OWNER_ID = getSystemIdentity(DATA_DIR)
13
+ const db = createClient({ url: `file:${path.join(DATA_DIR, 'memories.db')}` })
14
+
15
+ const memorySystem = new MemorySystem(db, SYSTEM_OWNER_ID)
16
+ await memorySystem.init()
17
+
18
+ console.error(`RiLiGar Agents Memories initialized for Owner: ${SYSTEM_OWNER_ID}`)
19
+
20
+ // 2. TRANSPORTS
21
+ // 2.1. STDIO
22
+ const mcpServer = createMcpServer(memorySystem)
23
+ const stdioTransport = new StdioServerTransport()
24
+ mcpServer.connect(stdioTransport).catch(err => console.error('STDIO Error:', err))
25
+
26
+ // 2.2. SSE (Express)
27
+ const app = express()
28
+ app.use(cors())
29
+ const sseSessions = new Map()
30
+
31
+ app.get('/sse', async (req, res) => {
32
+ const transport = new SSEServerTransport('/messages', res)
33
+ const sessionServer = createMcpServer(memorySystem)
34
+ await sessionServer.connect(transport)
35
+
36
+ const sessionId = transport.sessionId
37
+ sseSessions.set(sessionId, transport)
38
+ console.error(`SSE session started: ${sessionId}`)
39
+
40
+ res.on('close', () => {
41
+ sseSessions.delete(sessionId)
42
+ console.error(`SSE session closed: ${sessionId}`)
43
+ })
44
+ })
45
+
46
+ app.post('/messages', async (req, res) => {
47
+ const sessionId = req.query.sessionId
48
+ const transport = sseSessions.get(sessionId)
49
+ if (!transport) return res.status(404).send('Session not found')
50
+ await transport.handlePostMessage(req, res)
51
+ })
52
+
53
+ const PORT = process.env.PORT || 3000
54
+ app.listen(PORT, () => {
55
+ console.error(`RiLiGar Agents Memories Standalone running at http://localhost:${PORT}`)
56
+ })
@@ -0,0 +1,124 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
2
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
3
+
4
+ /**
5
+ * Factory para criar o servidor MCP usando o SDK do RiLiGar.
6
+ */
7
+ export function createMcpServer(memorySystem) {
8
+ const server = new Server(
9
+ { name: 'RiLiGar Agents Memories', version: '4.1.0' },
10
+ { capabilities: { tools: {} } }
11
+ )
12
+
13
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
14
+ return {
15
+ tools: [
16
+ {
17
+ name: 'save',
18
+ description: 'Saves info with Resonance and Auto-Bridging (Semantic Magnet).',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ content: {
23
+ oneOf: [
24
+ { type: 'string' },
25
+ { type: 'array', items: { anyOf: [
26
+ { type: 'string' },
27
+ { type: 'object', properties: {
28
+ content: { type: 'string' },
29
+ path: { type: 'string' },
30
+ owner_id: { type: 'string' },
31
+ priority: { type: 'integer' }
32
+ }, required: ['content'] }
33
+ ] } }
34
+ ],
35
+ },
36
+ path: { type: 'string' },
37
+ owner_id: { type: 'string' },
38
+ priority: { type: 'integer' },
39
+ },
40
+ required: ['content'],
41
+ },
42
+ },
43
+ {
44
+ name: 'bridge',
45
+ description: 'Manually connects two memory paths.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ source_path: { type: 'string' },
50
+ target_path: { type: 'string' },
51
+ relation: { type: 'string', default: 'related_to' },
52
+ },
53
+ required: ['source_path', 'target_path'],
54
+ },
55
+ },
56
+ {
57
+ name: 'search',
58
+ description: 'Semantic search with Relational Awareness.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ query: { type: 'string' },
63
+ path_filter: { type: 'string' },
64
+ },
65
+ required: ['query'],
66
+ },
67
+ },
68
+ {
69
+ name: 'inspect',
70
+ description: 'Visualizes the Knowledge Graph in Mermaid.js syntax.',
71
+ inputSchema: { type: 'object', properties: {} },
72
+ },
73
+ {
74
+ name: 'list',
75
+ description: 'Lists all memory paths.',
76
+ inputSchema: { type: 'object', properties: {} },
77
+ },
78
+ ],
79
+ }
80
+ })
81
+
82
+ server.setRequestHandler(CallToolRequestSchema, async request => {
83
+ const { name, arguments: args } = request.params
84
+
85
+ try {
86
+ if (name === 'save') {
87
+ const res = await memorySystem.save(args)
88
+ return { content: [{ type: 'text', text: `Saved ${res.count} memories. Semantic Magnet applied.` }] }
89
+ }
90
+
91
+ if (name === 'bridge') {
92
+ await memorySystem.bridge(args)
93
+ return { content: [{ type: 'text', text: `Bridge created: [${args.source_path}] --(${args.relation})--> [${args.target_path}]` }] }
94
+ }
95
+
96
+ if (name === 'search') {
97
+ const rows = await memorySystem.search(args)
98
+ const formattedResults = rows.map(r => {
99
+ const linkText = r.links.length > 0
100
+ ? `\n🔗 Related: ${r.links.map(l => `${l.path} (${l.relation_type})`).join(', ')}`
101
+ : ''
102
+ return `[${r.path} | P:${r.priority}] ${r.content}${linkText}`
103
+ })
104
+ return { content: [{ type: 'text', text: formattedResults.join('\n\n') || 'No results.' }] }
105
+ }
106
+
107
+ if (name === 'inspect') {
108
+ const mermaid = await memorySystem.inspect()
109
+ return { content: [{ type: 'text', text: `### Knowledge Graph\n\n\`\`\`mermaid\n${mermaid}\`\`\`` }] }
110
+ }
111
+
112
+ if (name === 'list') {
113
+ const paths = await memorySystem.list()
114
+ return { content: [{ type: 'text', text: paths.map(p => `- ${p}`).join('\n') || 'Empty.' }] }
115
+ }
116
+
117
+ throw new Error('Unknown tool.')
118
+ } catch (error) {
119
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
120
+ }
121
+ })
122
+
123
+ return server
124
+ }
package/src/test.js CHANGED
@@ -10,7 +10,7 @@ async function runTest() {
10
10
 
11
11
  const transport = new StdioClientTransport({
12
12
  command: process.platform === 'win32' ? 'bun.exe' : (process.env.HOME + '/.bun/bin/bun'),
13
- args: ['src/index.js']
13
+ args: ['src/server/index.js']
14
14
  })
15
15
 
16
16
  const client = new Client(
@@ -16,7 +16,7 @@ async function runIdentityTest() {
16
16
 
17
17
  const transport = new StdioClientTransport({
18
18
  command: (process.env.HOME + '/.bun/bin/bun'),
19
- args: ['src/index.js']
19
+ args: ['src/server/index.js']
20
20
  })
21
21
 
22
22
  const client = new Client(