@contextableai/openclaw-memory-graphiti 0.2.6 → 0.2.7
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/graphiti.ts +1 -1
- package/index.ts +24 -7
- package/package.json +1 -1
- package/scripts/dev-start.sh +43 -14
package/graphiti.ts
CHANGED
|
@@ -80,7 +80,7 @@ export class GraphitiClient {
|
|
|
80
80
|
/** Polling interval (ms) for UUID resolution after addEpisode. */
|
|
81
81
|
uuidPollIntervalMs = 3000;
|
|
82
82
|
/** Max polling attempts for UUID resolution (total wait = interval * attempts). */
|
|
83
|
-
uuidPollMaxAttempts =
|
|
83
|
+
uuidPollMaxAttempts = 80;
|
|
84
84
|
|
|
85
85
|
constructor(private readonly endpoint: string) {}
|
|
86
86
|
|
package/index.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
14
14
|
import { Type } from "@sinclair/typebox";
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
15
16
|
import { readFileSync } from "node:fs";
|
|
16
17
|
import { join, dirname } from "node:path";
|
|
17
18
|
import { fileURLToPath } from "node:url";
|
|
@@ -117,9 +118,9 @@ const memoryGraphitiPlugin = {
|
|
|
117
118
|
name: "memory_recall",
|
|
118
119
|
label: "Memory Recall",
|
|
119
120
|
description:
|
|
120
|
-
"Search through memories using the knowledge graph. Returns entities and facts the current user is authorized to see. Supports session, long-term, or combined scope.",
|
|
121
|
+
"Search through memories using the knowledge graph. Returns entities and facts the current user is authorized to see. Supports session, long-term, or combined scope. REQUIRES a search query.",
|
|
121
122
|
parameters: Type.Object({
|
|
122
|
-
query: Type.String({ description: "Search query" }),
|
|
123
|
+
query: Type.String({ description: "REQUIRED: Search query for semantic matching" }),
|
|
123
124
|
limit: Type.Optional(Type.Number({ description: "Max results (default: 10)" })),
|
|
124
125
|
scope: Type.Optional(
|
|
125
126
|
Type.Union(
|
|
@@ -249,7 +250,7 @@ const memoryGraphitiPlugin = {
|
|
|
249
250
|
Type.Array(Type.String(), { description: "Person/agent IDs involved in this memory" }),
|
|
250
251
|
),
|
|
251
252
|
group_id: Type.Optional(
|
|
252
|
-
Type.String({ description: "Target group
|
|
253
|
+
Type.String({ description: "Target group ID (optional, uses your default group if omitted)" }),
|
|
253
254
|
),
|
|
254
255
|
longTerm: Type.Optional(
|
|
255
256
|
Type.Boolean({ description: "Store as long-term memory (default: true). Set to false for session-scoped." }),
|
|
@@ -270,10 +271,22 @@ const memoryGraphitiPlugin = {
|
|
|
270
271
|
longTerm?: boolean;
|
|
271
272
|
};
|
|
272
273
|
|
|
274
|
+
// Sanitize group_id: SpiceDB requires alphanumeric + _|\\-=+ only (no spaces!)
|
|
275
|
+
const sanitizeGroupId = (id?: string): string | undefined => {
|
|
276
|
+
if (!id) return undefined;
|
|
277
|
+
const trimmed = id.trim();
|
|
278
|
+
// If it looks like a description rather than an ID, ignore it
|
|
279
|
+
if (trimmed.includes(' ') || trimmed.toLowerCase().includes('configured')) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
return trimmed;
|
|
283
|
+
};
|
|
284
|
+
|
|
273
285
|
// Resolve target group: explicit > longTerm flag > default
|
|
274
286
|
let targetGroupId: string;
|
|
275
|
-
|
|
276
|
-
|
|
287
|
+
const sanitizedGroupId = sanitizeGroupId(group_id);
|
|
288
|
+
if (sanitizedGroupId) {
|
|
289
|
+
targetGroupId = sanitizedGroupId;
|
|
277
290
|
} else if (!longTerm && currentSessionId) {
|
|
278
291
|
targetGroupId = sessionGroupId(currentSessionId);
|
|
279
292
|
} else {
|
|
@@ -312,8 +325,10 @@ const memoryGraphitiPlugin = {
|
|
|
312
325
|
}
|
|
313
326
|
|
|
314
327
|
// 1. Add episode to Graphiti
|
|
328
|
+
// Generate unique episode name to avoid collisions
|
|
329
|
+
const episodeName = `memory_${randomUUID()}`;
|
|
315
330
|
const result = await graphiti.addEpisode({
|
|
316
|
-
name:
|
|
331
|
+
name: episodeName,
|
|
317
332
|
episode_body: content,
|
|
318
333
|
source_description,
|
|
319
334
|
group_id: targetGroupId,
|
|
@@ -682,8 +697,10 @@ const memoryGraphitiPlugin = {
|
|
|
682
697
|
}
|
|
683
698
|
}
|
|
684
699
|
|
|
700
|
+
// Generate unique episode name to avoid collisions
|
|
701
|
+
const episodeName = `auto_capture_${randomUUID()}`;
|
|
685
702
|
const result = await graphiti.addEpisode({
|
|
686
|
-
name:
|
|
703
|
+
name: episodeName,
|
|
687
704
|
episode_body: episodeBody,
|
|
688
705
|
source_description: "auto-captured conversation",
|
|
689
706
|
group_id: targetGroupId,
|
package/package.json
CHANGED
package/scripts/dev-start.sh
CHANGED
|
@@ -5,15 +5,25 @@
|
|
|
5
5
|
# Services run in the background; logs go to .dev/logs/.
|
|
6
6
|
# PID files are written to .dev/pids/.
|
|
7
7
|
#
|
|
8
|
-
# Environment variables
|
|
9
|
-
# OPENAI_API_KEY Required by Graphiti for entity extraction
|
|
8
|
+
# Environment variables:
|
|
9
|
+
# OPENAI_API_KEY Required by Graphiti for entity extraction (unless using local models)
|
|
10
10
|
# SPICEDB_TOKEN Pre-shared key (default: dev_token)
|
|
11
11
|
# SPICEDB_PORT gRPC port (default: 50051)
|
|
12
12
|
# SPICEDB_DATASTORE "memory" (default) or "postgres"
|
|
13
13
|
# SPICEDB_DB_URI Postgres connection URI (when SPICEDB_DATASTORE=postgres)
|
|
14
14
|
# FALKORDB_PORT Redis port (default: 6379)
|
|
15
15
|
# GRAPHITI_PORT HTTP port (default: 8000)
|
|
16
|
+
# GRAPHITI_PATH Path to Graphiti git clone (default: ../graphiti)
|
|
16
17
|
# EPISODE_ID_PREFIX Prefix for Graphiti episode UUIDs (default: epi-)
|
|
18
|
+
#
|
|
19
|
+
# Optional: Use local models instead of OpenAI
|
|
20
|
+
# LLM_PROVIDER "openai_generic" (enables custom LLM endpoint)
|
|
21
|
+
# LLM_MODEL Model name (e.g., "nvidia/Qwen3-Next-80B-A3B-Instruct-NVFP4")
|
|
22
|
+
# LLM_API_URL Custom LLM endpoint (e.g., "http://dgx-spark:8000/v1")
|
|
23
|
+
# EMBEDDER_MODEL Embedding model (e.g., "nomic-embed-text:v1.5")
|
|
24
|
+
# EMBEDDER_DIMENSIONS Embedding dimensions (e.g., 768 for nomic, 1536 for OpenAI)
|
|
25
|
+
# EMBEDDER_API_URL Custom embedder endpoint (e.g., "http://minisforum:11434/v1")
|
|
26
|
+
# RERANKER_PROVIDER Reranker provider: "openai", "gemini", or "bge" (default: bge)
|
|
17
27
|
# -------------------------------------------------------------------
|
|
18
28
|
set -euo pipefail
|
|
19
29
|
|
|
@@ -35,6 +45,17 @@ SPICEDB_DATASTORE="${SPICEDB_DATASTORE:-memory}"
|
|
|
35
45
|
SPICEDB_DB_URI="${SPICEDB_DB_URI:-postgres://spicedb:spicedb_dev@127.0.0.1:5432/spicedb?sslmode=disable}"
|
|
36
46
|
FALKORDB_PORT="${FALKORDB_PORT:-6379}"
|
|
37
47
|
GRAPHITI_PORT="${GRAPHITI_PORT:-8000}"
|
|
48
|
+
GRAPHITI_PATH="${GRAPHITI_PATH:-$PROJECT_DIR/../graphiti}"
|
|
49
|
+
|
|
50
|
+
# Local model endpoints (for your dev environment)
|
|
51
|
+
LLM_PROVIDER="${LLM_PROVIDER:-openai_generic}"
|
|
52
|
+
LLM_MODEL="${LLM_MODEL:-nvidia/Qwen3-Next-80B-A3B-Instruct-NVFP4}"
|
|
53
|
+
LLM_API_URL="${LLM_API_URL:-http://dgx-spark:8000/v1}"
|
|
54
|
+
LLM_MAX_TOKENS="${LLM_MAX_TOKENS:-8192}"
|
|
55
|
+
EMBEDDER_MODEL="${EMBEDDER_MODEL:-nomic-embed-text:v1.5}"
|
|
56
|
+
EMBEDDER_DIMENSIONS="${EMBEDDER_DIMENSIONS:-768}"
|
|
57
|
+
EMBEDDER_API_URL="${EMBEDDER_API_URL:-http://minisforum:11434/v1}"
|
|
58
|
+
RERANKER_PROVIDER="${RERANKER_PROVIDER:-bge}"
|
|
38
59
|
|
|
39
60
|
mkdir -p "$DEV_DIR/logs" "$DEV_DIR/pids" "$DEV_DIR/data/falkordb"
|
|
40
61
|
|
|
@@ -71,8 +92,9 @@ else
|
|
|
71
92
|
--port "$FALKORDB_PORT" \
|
|
72
93
|
--dir "$DEV_DIR/data/falkordb" \
|
|
73
94
|
--daemonize no \
|
|
74
|
-
--save "" \
|
|
75
|
-
--appendonly
|
|
95
|
+
--save "300 10 60 1000" \
|
|
96
|
+
--appendonly yes \
|
|
97
|
+
--appendfsync everysec \
|
|
76
98
|
--loglevel notice \
|
|
77
99
|
> "$DEV_DIR/logs/falkordb.log" 2>&1 &
|
|
78
100
|
echo $! > "$DEV_DIR/pids/falkordb.pid"
|
|
@@ -178,23 +200,30 @@ fi
|
|
|
178
200
|
if is_running "$DEV_DIR/pids/graphiti.pid"; then
|
|
179
201
|
echo "==> Graphiti MCP server already running (pid $(cat "$DEV_DIR/pids/graphiti.pid"))"
|
|
180
202
|
else
|
|
181
|
-
if [ -z "${OPENAI_API_KEY:-}" ]; then
|
|
182
|
-
echo ""
|
|
183
|
-
echo "WARNING: OPENAI_API_KEY is not set."
|
|
184
|
-
echo "Graphiti needs it for entity extraction and embeddings."
|
|
185
|
-
echo "Set it in .env or export it before running this script."
|
|
186
|
-
echo ""
|
|
187
|
-
fi
|
|
188
|
-
|
|
189
203
|
echo "==> Starting Graphiti MCP server on port $GRAPHITI_PORT..."
|
|
204
|
+
echo " LLM: $LLM_PROVIDER / $LLM_MODEL"
|
|
205
|
+
echo " LLM endpoint: $LLM_API_URL"
|
|
206
|
+
echo " Embedder: $EMBEDDER_MODEL (${EMBEDDER_DIMENSIONS}d)"
|
|
207
|
+
echo " Embedder endpoint: $EMBEDDER_API_URL"
|
|
208
|
+
echo " Reranker: $RERANKER_PROVIDER"
|
|
190
209
|
|
|
191
|
-
GRAPHITI_DIR="$
|
|
210
|
+
GRAPHITI_DIR="$GRAPHITI_PATH/mcp_server"
|
|
192
211
|
|
|
193
212
|
# Set environment for Graphiti
|
|
194
|
-
export OPENAI_API_KEY="${OPENAI_API_KEY:-}"
|
|
213
|
+
export OPENAI_API_KEY="${OPENAI_API_KEY:-not-needed}"
|
|
195
214
|
export FALKORDB_URI="redis://localhost:$FALKORDB_PORT"
|
|
196
215
|
export EPISODE_ID_PREFIX="${EPISODE_ID_PREFIX:-epi-}"
|
|
197
216
|
|
|
217
|
+
# Export local model configuration
|
|
218
|
+
export LLM_PROVIDER
|
|
219
|
+
export LLM_MODEL
|
|
220
|
+
export LLM_API_URL
|
|
221
|
+
export LLM_MAX_TOKENS
|
|
222
|
+
export EMBEDDER_MODEL
|
|
223
|
+
export EMBEDDER_DIMENSIONS
|
|
224
|
+
export EMBEDDER_API_URL
|
|
225
|
+
export RERANKER_PROVIDER
|
|
226
|
+
|
|
198
227
|
cd "$GRAPHITI_DIR"
|
|
199
228
|
uv run main.py \
|
|
200
229
|
--transport http \
|