@riligar/agents-memories 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -44
- package/package.json +5 -6
- package/src/core/identity.js +33 -0
- package/src/core/logic.js +62 -0
- package/src/core/sip.js +30 -0
- package/src/database/schema.js +32 -0
- package/src/index.js +6 -343
- package/src/sdk/memory-system.js +143 -0
- package/src/server/index.js +56 -0
- package/src/server/mcp-server.js +158 -0
- package/src/test.js +1 -1
- package/src/test_identity.js +1 -1
package/README.md
CHANGED
|
@@ -1,78 +1,110 @@
|
|
|
1
1
|
# RiLiGar Agents Memories
|
|
2
2
|
|
|
3
|
-
A high-performance, persistent memory system for AI agents, built on the **Model Context Protocol (MCP)**.
|
|
3
|
+
A high-performance, persistent memory system for AI agents, built on the **Model Context Protocol (MCP)**. RiLiGar transforms flat data into a **Relational Knowledge Graph** featuring **Resonance Intelligence** and **Semantic Bridging**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Now evolved into a **Modular SDK**, RiLiGar can be used both as a standalone MCP server and as a core library for cognitive applications.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 🧠 Philosophy: The Cognitive Self
|
|
8
8
|
|
|
9
|
-
RiLiGar focuses on **Relational Intelligence**. By transforming memories into a living graph, the agent
|
|
9
|
+
RiLiGar focuses on **Relational Intelligence**. Memory is not just retrieval; it is an extension of the agent's identity. By transforming memories into a living graph, the agent understands how facts are interconnected across different domains, simulating a "working memory" that evolves with every interaction.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Each RiLiGar installation possesses a unique, professional cryptographic identity (Ed25519):
|
|
11
|
+
---
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
- **Explicit Identity**: Users or external agents can provide their own `owner_id` to maintain clear boundaries in multi-user environments.
|
|
17
|
-
- **Architecture**: Stored locally in `data/identity.json`, ensuring the agent maintains its "soul" across sessions.
|
|
13
|
+
## 🏗️ Modular Architecture
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
The project is organized into distinct layers to ensure scalability and maintainability:
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
- **`src/core` (Cognition)**: Implements the heavy-lifting logic:
|
|
18
|
+
- **Resonance Intelligence**: Priorities ripple through the graph; if a node is marked as critical, its semantic neighbors gain importance.
|
|
19
|
+
- **Semantic Magnet**: Automatically detects near-identical facts and creates `similar_to` links.
|
|
20
|
+
- **`src/database` (Persistence)**: Uses **LibSQL (SQLite)** for edge-ready, high-speed storage.
|
|
21
|
+
- **`src/sdk` (Interface)**: Provides the `MemorySystem` class, a clean programmatic API.
|
|
22
|
+
- **`src/server` (Transport)**: Handles MCP communication via **Stdio** (local) and **SSE** (web/remote).
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
- **Manual Bridging**: Explicitly connect disparate paths using the `bridge` tool to establish logical dependencies (e.g., `Architecture` depends_on `Infrastructure`).
|
|
25
|
-
- **Resonance Intelligence**: Priorities ripple through the graph; if a node is marked as critical, its semantic neighbors gain importance via resonance.
|
|
24
|
+
---
|
|
26
25
|
|
|
27
|
-
##
|
|
26
|
+
## 🔌 Quick Start
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
### Prerequisites
|
|
30
29
|
|
|
31
|
-
-
|
|
32
|
-
- **Output**: Generates a visual mental map of all connections, allowing the agent and user to see the topology of their stored knowledge.
|
|
30
|
+
- [Bun](https://bun.sh) runtime (v1.x+)
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
### Installation
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
```bash
|
|
35
|
+
bun install
|
|
36
|
+
```
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
`🔗 Related: Path.B (similar_to), Path.C (depends_on)`
|
|
38
|
+
### Running the Standalone Server
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
- **Short-term Awareness**: The system is tuned for **Cognitive Fluidity**, prioritizing recent thoughts and context to function as an effective short-term "working memory" while maintaining long-term relational links.
|
|
40
|
+
The server starts both **Stdio** and **SSE** transports simultaneously on port `3000`.
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
```bash
|
|
43
|
+
bun start
|
|
44
|
+
```
|
|
45
45
|
|
|
46
|
-
-
|
|
47
|
-
- `
|
|
48
|
-
- `bridge`: Manually links two memory paths with a specified relation type.
|
|
49
|
-
- `inspect`: Generates a Mermaid.js diagram of the current knowledge graph.
|
|
50
|
-
- `list`: Lists all unique memory paths.
|
|
46
|
+
- **Stdio**: Use this for Claude Desktop or local IDE configurations.
|
|
47
|
+
- **SSE**: Access via `http://localhost:3000/sse` for web dashboards or remote agents.
|
|
51
48
|
|
|
52
49
|
---
|
|
53
50
|
|
|
54
|
-
##
|
|
51
|
+
## 📦 Programmatic Usage (SDK)
|
|
55
52
|
|
|
56
|
-
|
|
53
|
+
You can use RiLiGar as a library in your own Bun projects:
|
|
57
54
|
|
|
58
|
-
|
|
55
|
+
```javascript
|
|
56
|
+
import { MemorySystem, getSystemIdentity } from '@riligar/agents-memories'
|
|
57
|
+
import { createClient } from '@libsql/client'
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
const db = createClient({ url: 'file:data/memories.db' })
|
|
60
|
+
const ownerId = getSystemIdentity('data')
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
62
|
+
const memory = new MemorySystem(db, ownerId)
|
|
63
|
+
await memory.init()
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
// Save a memory with automatic priority inference
|
|
66
|
+
await memory.save({
|
|
67
|
+
content: 'The architecture is now modular.',
|
|
68
|
+
path: 'Project.Architecture',
|
|
69
|
+
})
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
// Relational Semantic Search
|
|
72
|
+
const results = await memory.search({ query: 'How is the project organized?' })
|
|
73
|
+
console.log(results)
|
|
70
74
|
```
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🛡️ Identity & SaaS Readiness
|
|
79
|
+
|
|
80
|
+
- **Cryptographic Soul**: Each installation generates an Ed25519 identity (`data/identity.json`). The system uses a 10-character hex `owner_id` derived from this key.
|
|
81
|
+
- **Multi-Tenancy**: Every memory is bound to an `owner_id`, allowing the same database to securely host memories for multiple users or agents.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🛠️ MCP Tools Reference
|
|
86
|
+
|
|
87
|
+
- `save`: Stores content with automatic priority inference and **Semantic Magnet** auto-linking.
|
|
88
|
+
- `search`: Relational semantic search (Score = 60% Similarity + 40% Recency).
|
|
89
|
+
- `bridge`: Manually links two memory paths (e.g., `Architecture` -> `Security`).
|
|
90
|
+
- `inspect`: Generates a **Mermaid.js** graph of the memory topology.
|
|
91
|
+
- `list`: Shows all unique memory paths.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 🧪 Development
|
|
96
|
+
|
|
97
|
+
### Running tests
|
|
73
98
|
|
|
74
99
|
```bash
|
|
75
|
-
bun
|
|
100
|
+
bun test
|
|
76
101
|
```
|
|
77
102
|
|
|
78
|
-
|
|
103
|
+
### Stack
|
|
104
|
+
|
|
105
|
+
- **Runtime**: [Bun](https://bun.sh)
|
|
106
|
+
- **Database**: [LibSQL](https://github.com/tursodatabase/libsql)
|
|
107
|
+
- **Embeddings**: [Transformers.js](https://huggingface.co/docs/transformers.js) (`all-MiniLM-L6-v2`)
|
|
108
|
+
- **Server**: [Express](https://expressjs.com/) + MCP SDK
|
|
109
|
+
|
|
110
|
+
**Built with ❤️ by the RiLiGar Team.**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riligar/agents-memories",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "RiLiGar Agents Memories - A
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "RiLiGar Agents Memories - A self-improving relational memory system for AI agents.",
|
|
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
|
+
}
|
package/src/core/sip.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Improvement Protocol (SIP) - Protocol Definition
|
|
3
|
+
* This document defines the core pillars of the RiLiGar self-improvement loop.
|
|
4
|
+
*/
|
|
5
|
+
export const SIP_PROTOCOL = `
|
|
6
|
+
# RiLiGar Self-Improvement Protocol (SIP) v1.0
|
|
7
|
+
|
|
8
|
+
The SIP transforms the memory system from a passive database into an active, self-correcting cognitive engine. Every agent using this MCP should adhere to the following three pillars:
|
|
9
|
+
|
|
10
|
+
## 1. Pattern Mining (Refinamento de Padrões)
|
|
11
|
+
- **Goal**: Identify common failure points and recurring logic patterns.
|
|
12
|
+
- **Action**: When saving a new memory, always include the **Rationale** (the "Why").
|
|
13
|
+
- **Loop**: Before taking a complex action, use \`search\` to find "Lessons Learned" and "Past Failures" related to the current context.
|
|
14
|
+
|
|
15
|
+
## 2. Meta-Programming (Evolução de Skills)
|
|
16
|
+
- **Goal**: Update internal instructions and code patterns based on discovered "State of the Art" (SOTA) solutions.
|
|
17
|
+
- **Action**: If a specific implementation pattern proves superior (e.g., Elysia optimization), save it under \`Patterns.SOTA.*\` and update relevant local \`.agent/skills/\`.
|
|
18
|
+
|
|
19
|
+
## 3. Self-Critique (Auditoria de Arquitetura)
|
|
20
|
+
- **Goal**: Prevent technical debt and architectural drift.
|
|
21
|
+
- **Action**: Periodically use \`inspect\` and \`search\` with filter \`path:Project.Architecture.*\` to identify redundancies or legacy constraints.
|
|
22
|
+
- **Outcome**: Propose refactoring at the first sign of relational misalignment.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### Execution Protocol
|
|
27
|
+
1. **CAPTURE**: Save rationale for every architectural decision.
|
|
28
|
+
2. **REFLECT**: Search for patterns before starting new tasks.
|
|
29
|
+
3. **EVOLVE**: Update skills and best practices paths based on successful outcomes.
|
|
30
|
+
`.trim();
|
|
@@ -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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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,158 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
2
|
+
import {
|
|
3
|
+
CallToolRequestSchema,
|
|
4
|
+
ListToolsRequestSchema,
|
|
5
|
+
ListResourcesRequestSchema,
|
|
6
|
+
ReadResourceRequestSchema
|
|
7
|
+
} from '@modelcontextprotocol/sdk/types.js'
|
|
8
|
+
import { SIP_PROTOCOL } from '../core/sip.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Factory para criar o servidor MCP usando o SDK do RiLiGar.
|
|
12
|
+
*/
|
|
13
|
+
export function createMcpServer(memorySystem) {
|
|
14
|
+
const server = new Server(
|
|
15
|
+
{ name: 'RiLiGar Agents Memories', version: '4.2.0' },
|
|
16
|
+
{ capabilities: { tools: {}, resources: {} } }
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
20
|
+
return {
|
|
21
|
+
tools: [
|
|
22
|
+
{
|
|
23
|
+
name: 'save',
|
|
24
|
+
description: 'Saves info with Resonance and Auto-Bridging. SIP Note: Capture the Rationale (the "Why") to enable Pattern Mining.',
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
content: {
|
|
29
|
+
oneOf: [
|
|
30
|
+
{ type: 'string' },
|
|
31
|
+
{ type: 'array', items: { anyOf: [
|
|
32
|
+
{ type: 'string' },
|
|
33
|
+
{ type: 'object', properties: {
|
|
34
|
+
content: { type: 'string' },
|
|
35
|
+
path: { type: 'string' },
|
|
36
|
+
owner_id: { type: 'string' },
|
|
37
|
+
priority: { type: 'integer' }
|
|
38
|
+
}, required: ['content'] }
|
|
39
|
+
] } }
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
path: { type: 'string' },
|
|
43
|
+
owner_id: { type: 'string' },
|
|
44
|
+
priority: { type: 'integer' },
|
|
45
|
+
},
|
|
46
|
+
required: ['content'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'bridge',
|
|
51
|
+
description: 'Manually connects two memory paths.',
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
source_path: { type: 'string' },
|
|
56
|
+
target_path: { type: 'string' },
|
|
57
|
+
relation: { type: 'string', default: 'related_to' },
|
|
58
|
+
},
|
|
59
|
+
required: ['source_path', 'target_path'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'search',
|
|
64
|
+
description: 'Semantic search with Relational Awareness. Use for Pattern Mining and Error Avoidance (SIP).',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
query: { type: 'string' },
|
|
69
|
+
path_filter: { type: 'string' },
|
|
70
|
+
},
|
|
71
|
+
required: ['query'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'inspect',
|
|
76
|
+
description: 'Visualizes the Knowledge Graph in Mermaid.js syntax. Use for Architecture Audit (SIP).',
|
|
77
|
+
inputSchema: { type: 'object', properties: {} },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'list',
|
|
81
|
+
description: 'Lists all memory paths.',
|
|
82
|
+
inputSchema: { type: 'object', properties: {} },
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
89
|
+
return {
|
|
90
|
+
resources: [
|
|
91
|
+
{
|
|
92
|
+
uri: 'sip://protocol',
|
|
93
|
+
name: 'Self-Improvement Protocol (SIP)',
|
|
94
|
+
description: 'Protocolo de autoaperfeiçoamento contínuo para agentes RiLiGar.',
|
|
95
|
+
mimeType: 'text/markdown',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
|
102
|
+
if (request.params.uri === 'sip://protocol') {
|
|
103
|
+
return {
|
|
104
|
+
contents: [
|
|
105
|
+
{
|
|
106
|
+
uri: 'sip://protocol',
|
|
107
|
+
mimeType: 'text/markdown',
|
|
108
|
+
text: SIP_PROTOCOL,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
throw new Error('Resource not found')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
117
|
+
const { name, arguments: args } = request.params
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
if (name === 'save') {
|
|
121
|
+
const res = await memorySystem.save(args)
|
|
122
|
+
return { content: [{ type: 'text', text: `Saved ${res.count} memories. Semantic Magnet applied.` }] }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (name === 'bridge') {
|
|
126
|
+
await memorySystem.bridge(args)
|
|
127
|
+
return { content: [{ type: 'text', text: `Bridge created: [${args.source_path}] --(${args.relation})--> [${args.target_path}]` }] }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (name === 'search') {
|
|
131
|
+
const rows = await memorySystem.search(args)
|
|
132
|
+
const formattedResults = rows.map(r => {
|
|
133
|
+
const linkText = r.links.length > 0
|
|
134
|
+
? `\n🔗 Related: ${r.links.map(l => `${l.path} (${l.relation_type})`).join(', ')}`
|
|
135
|
+
: ''
|
|
136
|
+
return `[${r.path} | P:${r.priority}] ${r.content}${linkText}`
|
|
137
|
+
})
|
|
138
|
+
return { content: [{ type: 'text', text: formattedResults.join('\n\n') || 'No results.' }] }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (name === 'inspect') {
|
|
142
|
+
const mermaid = await memorySystem.inspect()
|
|
143
|
+
return { content: [{ type: 'text', text: `### Knowledge Graph\n\n\`\`\`mermaid\n${mermaid}\`\`\`` }] }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (name === 'list') {
|
|
147
|
+
const paths = await memorySystem.list()
|
|
148
|
+
return { content: [{ type: 'text', text: paths.map(p => `- ${p}`).join('\n') || 'Empty.' }] }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
throw new Error('Unknown tool.')
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return server
|
|
158
|
+
}
|
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(
|
package/src/test_identity.js
CHANGED