@pentatonic-ai/openclaw-memory-plugin 0.4.4
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 +60 -0
- package/index.js +424 -0
- package/openclaw.plugin.json +69 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @pentatonic-ai/openclaw-memory
|
|
2
|
+
|
|
3
|
+
Persistent, searchable memory for OpenClaw. Local (Docker + Ollama) or hosted (Pentatonic TES).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install @pentatonic-ai/openclaw-memory
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
Tell OpenClaw:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Set up pentatonic memory
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or configure manually in `openclaw.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"plugins": {
|
|
24
|
+
"slots": { "contextEngine": "pentatonic-memory" },
|
|
25
|
+
"entries": {
|
|
26
|
+
"pentatonic-memory": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"config": {
|
|
29
|
+
"database_url": "postgres://memory:memory@localhost:5433/memory",
|
|
30
|
+
"embedding_url": "http://localhost:11435/v1",
|
|
31
|
+
"embedding_model": "nomic-embed-text",
|
|
32
|
+
"llm_url": "http://localhost:11435/v1",
|
|
33
|
+
"llm_model": "llama3.2:3b"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What it does
|
|
42
|
+
|
|
43
|
+
Every lifecycle event is handled automatically:
|
|
44
|
+
|
|
45
|
+
- **Ingest** — every message stored with embeddings + HyDE query expansion
|
|
46
|
+
- **Assemble** — relevant memories injected as context before every prompt
|
|
47
|
+
- **Compact** — decay cycle when context window fills
|
|
48
|
+
- **After turn** — high-access memories consolidated to semantic layer
|
|
49
|
+
|
|
50
|
+
Plus tools: `memory_search`, `memory_store`, `memory_layers`
|
|
51
|
+
|
|
52
|
+
## Local vs Hosted
|
|
53
|
+
|
|
54
|
+
**Local**: Fully private. Requires Docker (Postgres + pgvector + Ollama). Run `npx @pentatonic-ai/ai-agent-sdk memory` to set up.
|
|
55
|
+
|
|
56
|
+
**Hosted**: Connect to Pentatonic TES for higher-dimensional embeddings, team memory, and analytics. Run `npx @pentatonic-ai/ai-agent-sdk init`.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pentatonic Memory — OpenClaw Context Engine Plugin
|
|
3
|
+
*
|
|
4
|
+
* Install: openclaw plugins install @pentatonic-ai/openclaw-memory
|
|
5
|
+
*
|
|
6
|
+
* Provides persistent, searchable memory via the ContextEngine lifecycle:
|
|
7
|
+
* ingest — every message stored with embedding + HyDE
|
|
8
|
+
* assemble — relevant memories injected before every prompt
|
|
9
|
+
* compact — decay cycle on context overflow
|
|
10
|
+
* afterTurn — consolidation check
|
|
11
|
+
*
|
|
12
|
+
* Plus agent-callable tools: memory_search, memory_store, pentatonic_memory_setup
|
|
13
|
+
*
|
|
14
|
+
* Two modes:
|
|
15
|
+
* - Local: HTTP calls to the memory server (localhost:3333)
|
|
16
|
+
* - Hosted: HTTP calls to TES GraphQL API
|
|
17
|
+
*
|
|
18
|
+
* No native modules, no child_process, no filesystem access.
|
|
19
|
+
* All config comes from OpenClaw's plugin config system.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// --- Local mode: HTTP to memory server ---
|
|
23
|
+
|
|
24
|
+
async function localSearch(baseUrl, query, limit = 5, minScore = 0.3) {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`${baseUrl}/search`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: { "Content-Type": "application/json" },
|
|
29
|
+
body: JSON.stringify({ query, limit, min_score: minScore }),
|
|
30
|
+
signal: AbortSignal.timeout(5000),
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) return [];
|
|
33
|
+
const data = await res.json();
|
|
34
|
+
return data.results || [];
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function localStore(baseUrl, content, metadata = {}) {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(`${baseUrl}/store`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify({ content, metadata }),
|
|
46
|
+
signal: AbortSignal.timeout(10000),
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) return null;
|
|
49
|
+
return res.json();
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function localHealth(baseUrl) {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(`${baseUrl}/health`, {
|
|
58
|
+
signal: AbortSignal.timeout(3000),
|
|
59
|
+
});
|
|
60
|
+
return res.ok;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- Hosted mode: TES GraphQL ---
|
|
67
|
+
|
|
68
|
+
function tesHeaders(config) {
|
|
69
|
+
const headers = {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"x-client-id": config.tes_client_id,
|
|
72
|
+
};
|
|
73
|
+
if (config.tes_api_key?.startsWith("tes_")) {
|
|
74
|
+
headers["Authorization"] = `Bearer ${config.tes_api_key}`;
|
|
75
|
+
} else {
|
|
76
|
+
headers["x-service-key"] = config.tes_api_key;
|
|
77
|
+
}
|
|
78
|
+
return headers;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function hostedSearch(config, query, limit = 5, minScore = 0.3) {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(`${config.tes_endpoint}/api/graphql`, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: tesHeaders(config),
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
query: `query($clientId: String!, $query: String!, $limit: Int, $minScore: Float) {
|
|
88
|
+
semanticSearchMemories(clientId: $clientId, query: $query, limit: $limit, minScore: $minScore) {
|
|
89
|
+
id content similarity
|
|
90
|
+
}
|
|
91
|
+
}`,
|
|
92
|
+
variables: { clientId: config.tes_client_id, query, limit, minScore },
|
|
93
|
+
}),
|
|
94
|
+
signal: AbortSignal.timeout(5000),
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) return [];
|
|
97
|
+
const json = await res.json();
|
|
98
|
+
return json.data?.semanticSearchMemories || [];
|
|
99
|
+
} catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function hostedStore(config, content, metadata = {}) {
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(`${config.tes_endpoint}/api/graphql`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: tesHeaders(config),
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
query: `mutation CreateModuleEvent($moduleId: String!, $input: ModuleEventInput!) {
|
|
111
|
+
createModuleEvent(moduleId: $moduleId, input: $input) { success eventId }
|
|
112
|
+
}`,
|
|
113
|
+
variables: {
|
|
114
|
+
moduleId: "deep-memory",
|
|
115
|
+
input: {
|
|
116
|
+
eventType: "STORE_MEMORY",
|
|
117
|
+
data: {
|
|
118
|
+
entity_id: metadata.session_id || "openclaw",
|
|
119
|
+
attributes: { ...metadata, content, source: "openclaw-plugin" },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
signal: AbortSignal.timeout(10000),
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok) return null;
|
|
127
|
+
return res.json();
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- Context engines ---
|
|
134
|
+
|
|
135
|
+
function createLocalContextEngine(baseUrl, opts = {}) {
|
|
136
|
+
const searchLimit = opts.searchLimit || 5;
|
|
137
|
+
const minScore = opts.minScore || 0.3;
|
|
138
|
+
const log = opts.logger || (() => {});
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
info: { id: "pentatonic-memory", name: "Pentatonic Memory (Local)", ownsCompaction: false },
|
|
142
|
+
|
|
143
|
+
async ingest({ sessionId, message }) {
|
|
144
|
+
if (!message?.content) return { ingested: false };
|
|
145
|
+
const role = message.role || message.type;
|
|
146
|
+
if (role !== "user" && role !== "assistant") return { ingested: false };
|
|
147
|
+
try {
|
|
148
|
+
await localStore(baseUrl, message.content, { session_id: sessionId, role });
|
|
149
|
+
log(`Ingested ${role} message`);
|
|
150
|
+
return { ingested: true };
|
|
151
|
+
} catch (err) {
|
|
152
|
+
log(`Ingest failed: ${err.message}`);
|
|
153
|
+
return { ingested: false };
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async assemble({ sessionId, messages }) {
|
|
158
|
+
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user" || m.type === "user");
|
|
159
|
+
if (!lastUserMsg?.content) return { messages, estimatedTokens: 0 };
|
|
160
|
+
try {
|
|
161
|
+
const results = await localSearch(baseUrl, lastUserMsg.content, searchLimit, minScore);
|
|
162
|
+
if (!results.length) return { messages, estimatedTokens: 0 };
|
|
163
|
+
const memoryText = results
|
|
164
|
+
.map((m) => `- [${Math.round((m.similarity || 0) * 100)}%] ${m.content}`)
|
|
165
|
+
.join("\n");
|
|
166
|
+
const addition = `[Memory] Relevant context from past conversations:\n${memoryText}`;
|
|
167
|
+
log(`Assembled ${results.length} memories`);
|
|
168
|
+
return { messages, estimatedTokens: Math.ceil(addition.length / 4), systemPromptAddition: addition };
|
|
169
|
+
} catch (err) {
|
|
170
|
+
log(`Assemble failed: ${err.message}`);
|
|
171
|
+
return { messages, estimatedTokens: 0 };
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
async compact() { return { ok: true, compacted: false }; },
|
|
176
|
+
async afterTurn() {},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function createHostedContextEngine(config, opts = {}) {
|
|
181
|
+
const searchLimit = opts.searchLimit || 5;
|
|
182
|
+
const minScore = opts.minScore || 0.3;
|
|
183
|
+
const log = opts.logger || (() => {});
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
info: { id: "pentatonic-memory", name: "Pentatonic Memory (Hosted)", ownsCompaction: false },
|
|
187
|
+
|
|
188
|
+
async ingest({ sessionId, message }) {
|
|
189
|
+
if (!message?.content) return { ingested: false };
|
|
190
|
+
const role = message.role || message.type;
|
|
191
|
+
if (role !== "user" && role !== "assistant") return { ingested: false };
|
|
192
|
+
try {
|
|
193
|
+
await hostedStore(config, message.content, { session_id: sessionId, role });
|
|
194
|
+
log(`Ingested ${role} message via TES`);
|
|
195
|
+
return { ingested: true };
|
|
196
|
+
} catch (err) {
|
|
197
|
+
log(`Hosted ingest failed: ${err.message}`);
|
|
198
|
+
return { ingested: false };
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
async assemble({ sessionId, messages }) {
|
|
203
|
+
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user" || m.type === "user");
|
|
204
|
+
if (!lastUserMsg?.content) return { messages, estimatedTokens: 0 };
|
|
205
|
+
try {
|
|
206
|
+
const results = await hostedSearch(config, lastUserMsg.content, searchLimit, minScore);
|
|
207
|
+
if (!results.length) return { messages, estimatedTokens: 0 };
|
|
208
|
+
const memoryText = results
|
|
209
|
+
.map((m) => `- [${Math.round((m.similarity || 0) * 100)}%] ${m.content}`)
|
|
210
|
+
.join("\n");
|
|
211
|
+
const addition = `[Memory] Relevant context from past conversations:\n${memoryText}`;
|
|
212
|
+
log(`Assembled ${results.length} memories via TES`);
|
|
213
|
+
return { messages, estimatedTokens: Math.ceil(addition.length / 4), systemPromptAddition: addition };
|
|
214
|
+
} catch (err) {
|
|
215
|
+
log(`Hosted assemble failed: ${err.message}`);
|
|
216
|
+
return { messages, estimatedTokens: 0 };
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
async compact() { return { ok: true, compacted: false }; },
|
|
221
|
+
async afterTurn() {},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --- Format helpers ---
|
|
226
|
+
|
|
227
|
+
function formatResults(results) {
|
|
228
|
+
if (!results.length) return "No relevant memories found.";
|
|
229
|
+
return results
|
|
230
|
+
.map((m, i) => `${i + 1}. [${Math.round((m.similarity || 0) * 100)}%] ${m.content}`)
|
|
231
|
+
.join("\n\n");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// --- Plugin entry ---
|
|
235
|
+
|
|
236
|
+
export default {
|
|
237
|
+
id: "pentatonic-memory",
|
|
238
|
+
name: "Pentatonic Memory",
|
|
239
|
+
description: "Persistent, searchable memory with multi-signal retrieval and HyDE query expansion",
|
|
240
|
+
kind: "context-engine",
|
|
241
|
+
|
|
242
|
+
register(api) {
|
|
243
|
+
const config = api.config || {};
|
|
244
|
+
const hosted = !!(config.tes_endpoint && config.tes_api_key);
|
|
245
|
+
const baseUrl = config.memory_url || "http://localhost:3333";
|
|
246
|
+
const log = (msg) => process.stderr.write(`[pentatonic-memory] ${msg}\n`);
|
|
247
|
+
|
|
248
|
+
// --- Setup tool (always registered) ---
|
|
249
|
+
|
|
250
|
+
api.registerTool({
|
|
251
|
+
name: "pentatonic_memory_setup",
|
|
252
|
+
description: `Guide the user through setting up Pentatonic Memory.
|
|
253
|
+
|
|
254
|
+
Two modes:
|
|
255
|
+
1. "local" — fully private, Docker-based (PostgreSQL + pgvector + Ollama). No cloud.
|
|
256
|
+
2. "hosted" — Pentatonic TES cloud. Team-wide shared memory, analytics, higher-dimensional embeddings.
|
|
257
|
+
|
|
258
|
+
Call this to get instructions for the user's chosen mode.`,
|
|
259
|
+
parameters: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
mode: { type: "string", enum: ["local", "hosted"], description: "Which mode the user wants" },
|
|
263
|
+
},
|
|
264
|
+
required: ["mode"],
|
|
265
|
+
},
|
|
266
|
+
async execute({ mode }) {
|
|
267
|
+
if (mode === "local") {
|
|
268
|
+
return `## Local Memory Setup
|
|
269
|
+
|
|
270
|
+
Run in terminal:
|
|
271
|
+
\`\`\`
|
|
272
|
+
npx @pentatonic-ai/ai-agent-sdk memory
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
This starts PostgreSQL + pgvector, Ollama, and the memory server via Docker.
|
|
276
|
+
|
|
277
|
+
Then add to openclaw.json:
|
|
278
|
+
\`\`\`json
|
|
279
|
+
{
|
|
280
|
+
"plugins": {
|
|
281
|
+
"slots": { "contextEngine": "pentatonic-memory" },
|
|
282
|
+
"entries": {
|
|
283
|
+
"pentatonic-memory": {
|
|
284
|
+
"enabled": true,
|
|
285
|
+
"config": { "memory_url": "http://localhost:3333" }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
\`\`\`
|
|
291
|
+
|
|
292
|
+
Restart OpenClaw to activate.`;
|
|
293
|
+
}
|
|
294
|
+
return `## Hosted TES Setup
|
|
295
|
+
|
|
296
|
+
Run in terminal:
|
|
297
|
+
\`\`\`
|
|
298
|
+
npx @pentatonic-ai/ai-agent-sdk init
|
|
299
|
+
\`\`\`
|
|
300
|
+
|
|
301
|
+
This creates a TES account and generates API credentials.
|
|
302
|
+
|
|
303
|
+
Then add to openclaw.json:
|
|
304
|
+
\`\`\`json
|
|
305
|
+
{
|
|
306
|
+
"plugins": {
|
|
307
|
+
"slots": { "contextEngine": "pentatonic-memory" },
|
|
308
|
+
"entries": {
|
|
309
|
+
"pentatonic-memory": {
|
|
310
|
+
"enabled": true,
|
|
311
|
+
"config": {
|
|
312
|
+
"tes_endpoint": "https://your-company.api.pentatonic.com",
|
|
313
|
+
"tes_client_id": "your-company",
|
|
314
|
+
"tes_api_key": "tes_your-company_xxxxx"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
Restart OpenClaw to activate.`;
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// --- Mode-specific registration ---
|
|
327
|
+
|
|
328
|
+
if (hosted) {
|
|
329
|
+
log("Hosted mode — routing through TES");
|
|
330
|
+
|
|
331
|
+
api.registerContextEngine("pentatonic-memory", () =>
|
|
332
|
+
createHostedContextEngine(config, {
|
|
333
|
+
searchLimit: config.search_limit || 5,
|
|
334
|
+
minScore: config.min_score || 0.3,
|
|
335
|
+
logger: log,
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
api.registerTool({
|
|
340
|
+
name: "memory_search",
|
|
341
|
+
description: "Search memories for relevant context.",
|
|
342
|
+
parameters: {
|
|
343
|
+
type: "object",
|
|
344
|
+
properties: {
|
|
345
|
+
query: { type: "string", description: "What to search for" },
|
|
346
|
+
limit: { type: "number", description: "Max results (default 5)" },
|
|
347
|
+
},
|
|
348
|
+
required: ["query"],
|
|
349
|
+
},
|
|
350
|
+
async execute({ query, limit }) {
|
|
351
|
+
return formatResults(await hostedSearch(config, query, limit || 5, 0.3));
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
api.registerTool({
|
|
356
|
+
name: "memory_store",
|
|
357
|
+
description: "Explicitly store something important.",
|
|
358
|
+
parameters: {
|
|
359
|
+
type: "object",
|
|
360
|
+
properties: { content: { type: "string", description: "What to remember" } },
|
|
361
|
+
required: ["content"],
|
|
362
|
+
},
|
|
363
|
+
async execute({ content }) {
|
|
364
|
+
const result = await hostedStore(config, content, { source: "openclaw-tool" });
|
|
365
|
+
return result ? "Memory stored." : "Failed to store memory.";
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
} else {
|
|
369
|
+
// Local mode — HTTP to memory server
|
|
370
|
+
const isConfigured = config.memory_url || config.database_url;
|
|
371
|
+
|
|
372
|
+
if (isConfigured) {
|
|
373
|
+
log(`Local mode — ${baseUrl}`);
|
|
374
|
+
|
|
375
|
+
// Check if server is reachable on startup
|
|
376
|
+
localHealth(baseUrl).then((ok) => {
|
|
377
|
+
if (!ok) log(`Warning: memory server not reachable at ${baseUrl}. Is Docker running?`);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
api.registerContextEngine("pentatonic-memory", () =>
|
|
381
|
+
createLocalContextEngine(baseUrl, {
|
|
382
|
+
searchLimit: config.search_limit || 5,
|
|
383
|
+
minScore: config.min_score || 0.3,
|
|
384
|
+
logger: log,
|
|
385
|
+
})
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
api.registerTool({
|
|
389
|
+
name: "memory_search",
|
|
390
|
+
description: "Search memories for relevant context.",
|
|
391
|
+
parameters: {
|
|
392
|
+
type: "object",
|
|
393
|
+
properties: {
|
|
394
|
+
query: { type: "string", description: "What to search for" },
|
|
395
|
+
limit: { type: "number", description: "Max results (default 5)" },
|
|
396
|
+
},
|
|
397
|
+
required: ["query"],
|
|
398
|
+
},
|
|
399
|
+
async execute({ query, limit }) {
|
|
400
|
+
return formatResults(await localSearch(baseUrl, query, limit || 5, 0.3));
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
api.registerTool({
|
|
405
|
+
name: "memory_store",
|
|
406
|
+
description: "Explicitly store something important.",
|
|
407
|
+
parameters: {
|
|
408
|
+
type: "object",
|
|
409
|
+
properties: { content: { type: "string", description: "What to remember" } },
|
|
410
|
+
required: ["content"],
|
|
411
|
+
},
|
|
412
|
+
async execute({ content }) {
|
|
413
|
+
const result = await localStore(baseUrl, content, { source: "openclaw-tool" });
|
|
414
|
+
return result ? `Stored: ${result.id}` : "Failed to store memory.";
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
} else {
|
|
418
|
+
log("No config — setup tool available. Tell OpenClaw: 'set up pentatonic memory'");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
log(`Plugin registered (${hosted ? "hosted" : isConfigured ? "local" : "unconfigured"})`);
|
|
423
|
+
},
|
|
424
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "pentatonic-memory",
|
|
3
|
+
"name": "Pentatonic Memory",
|
|
4
|
+
"description": "Persistent, searchable memory with multi-signal retrieval and HyDE query expansion. Local (Docker + Ollama) or hosted (Pentatonic TES).",
|
|
5
|
+
"version": "0.4.4",
|
|
6
|
+
"kind": "context-engine",
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"mode": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": ["local", "hosted"],
|
|
14
|
+
"default": "local",
|
|
15
|
+
"description": "Local (Docker + Ollama) or hosted (Pentatonic TES)"
|
|
16
|
+
},
|
|
17
|
+
"database_url": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "PostgreSQL connection string (local mode)"
|
|
20
|
+
},
|
|
21
|
+
"embedding_url": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "OpenAI-compatible embeddings endpoint (local mode)"
|
|
24
|
+
},
|
|
25
|
+
"embedding_model": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"default": "nomic-embed-text"
|
|
28
|
+
},
|
|
29
|
+
"llm_url": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "OpenAI-compatible chat endpoint for HyDE (local mode)"
|
|
32
|
+
},
|
|
33
|
+
"llm_model": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"default": "llama3.2:3b"
|
|
36
|
+
},
|
|
37
|
+
"tes_endpoint": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "TES API endpoint (hosted mode)"
|
|
40
|
+
},
|
|
41
|
+
"tes_client_id": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "TES client ID (hosted mode)"
|
|
44
|
+
},
|
|
45
|
+
"tes_api_key": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "TES API key (hosted mode)"
|
|
48
|
+
},
|
|
49
|
+
"client_id": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"default": "default",
|
|
52
|
+
"description": "Memory namespace"
|
|
53
|
+
},
|
|
54
|
+
"search_limit": {
|
|
55
|
+
"type": "number",
|
|
56
|
+
"default": 5,
|
|
57
|
+
"description": "Memories to inject per prompt"
|
|
58
|
+
},
|
|
59
|
+
"min_score": {
|
|
60
|
+
"type": "number",
|
|
61
|
+
"default": 0.3,
|
|
62
|
+
"description": "Minimum relevance threshold"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"setup": {
|
|
67
|
+
"description": "Set up persistent memory — local (Docker + Ollama, fully private) or hosted (Pentatonic TES, team-wide)."
|
|
68
|
+
}
|
|
69
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pentatonic-ai/openclaw-memory-plugin",
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "Pentatonic Memory plugin for OpenClaw — persistent, searchable memory with multi-signal retrieval and HyDE query expansion",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"openclaw": {
|
|
8
|
+
"extensions": [
|
|
9
|
+
{
|
|
10
|
+
"id": "pentatonic-memory",
|
|
11
|
+
"entrypoint": "./index.js"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"install": {
|
|
15
|
+
"npmSpec": "@pentatonic-ai/openclaw-memory",
|
|
16
|
+
"minHostVersion": ">=2026.3.7"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/Pentatonic-Ltd/ai-agent-sdk.git",
|
|
24
|
+
"directory": "packages/memory/openclaw-plugin"
|
|
25
|
+
},
|
|
26
|
+
"keywords": ["openclaw", "plugin", "memory", "context-engine", "pentatonic", "tes"]
|
|
27
|
+
}
|