Add OpenClaw memory-api plugin
Provides memory_store, memory_recall, memory_list, memory_delete tools as an OpenClaw plugin backed by the shared PostgreSQL memory API.
This commit is contained in:
parent
e08486f2bd
commit
66bb407bae
3 changed files with 259 additions and 0 deletions
238
openclaw-plugin/index.mjs
Normal file
238
openclaw-plugin/index.mjs
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/**
|
||||
* OpenClaw Memory API Plugin
|
||||
*
|
||||
* Provides memory_store, memory_recall, memory_list, memory_delete tools
|
||||
* backed by the shared PostgreSQL memory API (claude-memory service).
|
||||
*/
|
||||
|
||||
const PLUGIN_ID = "memory-api";
|
||||
|
||||
const memoryApiPlugin = {
|
||||
id: PLUGIN_ID,
|
||||
name: "Memory (API)",
|
||||
description: "PostgreSQL-backed shared memory via claude-memory API",
|
||||
kind: "memory",
|
||||
configSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
register(api) {
|
||||
const apiUrl =
|
||||
process.env.MEMORY_API_URL || "http://claude-memory.claude-memory.svc.cluster.local";
|
||||
const apiKey = process.env.MEMORY_API_KEY || "";
|
||||
|
||||
async function apiRequest(method, path, body) {
|
||||
const url = `${apiUrl}${path}`;
|
||||
const headers = {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
const options = { method, headers };
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
const resp = await fetch(url, options);
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text();
|
||||
throw new Error(`API error ${resp.status}: ${text}`);
|
||||
}
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
// memory_store
|
||||
api.registerTool({
|
||||
name: "memory_store",
|
||||
label: "Memory Store",
|
||||
description:
|
||||
"Store a fact or memory in persistent shared storage. Use to remember preferences, projects, decisions, or people.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
content: { type: "string", description: "The fact or memory to store" },
|
||||
category: {
|
||||
type: "string",
|
||||
enum: ["facts", "preferences", "projects", "people", "decisions"],
|
||||
description: "Category for organizing the memory",
|
||||
},
|
||||
tags: { type: "string", description: "Comma-separated tags" },
|
||||
importance: {
|
||||
type: "number",
|
||||
description: "Importance 0.0-1.0",
|
||||
minimum: 0.0,
|
||||
maximum: 1.0,
|
||||
},
|
||||
expanded_keywords: {
|
||||
type: "string",
|
||||
description:
|
||||
"REQUIRED. Space-separated semantically related search terms (MINIMUM 5 words).",
|
||||
},
|
||||
},
|
||||
required: ["content", "expanded_keywords"],
|
||||
},
|
||||
async execute(_toolCallId, params) {
|
||||
const result = await apiRequest("POST", "/api/memories", {
|
||||
content: params.content,
|
||||
category: params.category || "facts",
|
||||
tags: params.tags || "",
|
||||
expanded_keywords: params.expanded_keywords || "",
|
||||
importance: params.importance ?? 0.5,
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Stored memory #${result.id} in category '${result.category}' with importance ${Number(result.importance).toFixed(1)}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// memory_recall
|
||||
api.registerTool({
|
||||
name: "memory_recall",
|
||||
label: "Memory Recall",
|
||||
description:
|
||||
"Retrieve relevant memories based on context. Uses full-text search to find stored memories.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
context: {
|
||||
type: "string",
|
||||
description: "The context or topic to recall memories about",
|
||||
},
|
||||
expanded_query: {
|
||||
type: "string",
|
||||
description:
|
||||
"REQUIRED. Space-separated semantically related search terms (MINIMUM 5 words).",
|
||||
},
|
||||
category: {
|
||||
type: "string",
|
||||
enum: ["facts", "preferences", "projects", "people", "decisions"],
|
||||
description: "Optional: filter results to a specific category",
|
||||
},
|
||||
sort_by: {
|
||||
type: "string",
|
||||
enum: ["importance", "relevance"],
|
||||
description: "Sort order",
|
||||
},
|
||||
limit: { type: "integer", description: "Max results" },
|
||||
},
|
||||
required: ["context", "expanded_query"],
|
||||
},
|
||||
async execute(_toolCallId, params) {
|
||||
const result = await apiRequest("POST", "/api/memories/recall", {
|
||||
context: params.context,
|
||||
expanded_query: params.expanded_query || "",
|
||||
category: params.category || null,
|
||||
sort_by: params.sort_by || "importance",
|
||||
limit: params.limit || 10,
|
||||
});
|
||||
const rows = result.memories || [];
|
||||
if (!rows.length) {
|
||||
const filterDesc = params.category ? ` in category '${params.category}'` : "";
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: `No memories found matching: ${params.context}${filterDesc}` },
|
||||
],
|
||||
};
|
||||
}
|
||||
const sortDesc =
|
||||
params.sort_by === "relevance" ? "by relevance" : "by importance";
|
||||
const filterDesc = params.category ? ` in '${params.category}'` : "";
|
||||
const lines = rows.map(
|
||||
(r) =>
|
||||
`#${r.id} [${r.category}] (importance: ${Number(r.importance).toFixed(1)}) ${r.content}\n Tags: ${r.tags || "none"} | Stored: ${r.created_at}`,
|
||||
);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${rows.length} memories${filterDesc} (${sortDesc}):\n\n${lines.join("\n\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// memory_list
|
||||
api.registerTool({
|
||||
name: "memory_list",
|
||||
label: "Memory List",
|
||||
description: "List recent memories, optionally filtered by category.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
category: {
|
||||
type: "string",
|
||||
enum: ["facts", "preferences", "projects", "people", "decisions"],
|
||||
},
|
||||
limit: { type: "integer" },
|
||||
},
|
||||
},
|
||||
async execute(_toolCallId, params) {
|
||||
const limit = params.limit || 20;
|
||||
let path = `/api/memories?limit=${limit}`;
|
||||
if (params.category) {
|
||||
path += `&category=${params.category}`;
|
||||
}
|
||||
const result = await apiRequest("GET", path);
|
||||
const rows = result.memories || [];
|
||||
if (!rows.length) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: params.category
|
||||
? `No memories in category '${params.category}'`
|
||||
: "No memories stored yet",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
const lines = rows.map(
|
||||
(r) =>
|
||||
`#${r.id} [${r.category}] ${r.content}\n Importance: ${Number(r.importance).toFixed(1)} | Tags: ${r.tags || "none"} | Stored: ${r.created_at}`,
|
||||
);
|
||||
const header =
|
||||
"Recent memories" + (params.category ? ` in '${params.category}'` : "");
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `${header} (${rows.length} shown):\n\n${lines.join("\n\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// memory_delete
|
||||
api.registerTool({
|
||||
name: "memory_delete",
|
||||
label: "Memory Delete",
|
||||
description: "Delete a memory by ID.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "integer", description: "The ID of the memory to delete" },
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
async execute(_toolCallId, params) {
|
||||
const result = await apiRequest("DELETE", `/api/memories/${params.id}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Deleted memory #${result.deleted}: ${result.preview}...`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default memoryApiPlugin;
|
||||
9
openclaw-plugin/openclaw.plugin.json
Normal file
9
openclaw-plugin/openclaw.plugin.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"id": "memory-api",
|
||||
"kind": "memory",
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
12
openclaw-plugin/package.json
Normal file
12
openclaw-plugin/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "@openclaw/memory-api",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "PostgreSQL-backed shared memory plugin for OpenClaw",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue