@susu-eng/gralkor 26.0.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/.env.example +32 -0
- package/README.md +429 -0
- package/config.yaml +16 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +89 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/check.d.ts +2 -0
- package/dist/cli/commands/check.d.ts.map +1 -0
- package/dist/cli/commands/check.js +118 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +24 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/install.d.ts +8 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +105 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +68 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/lib/config.d.ts +7 -0
- package/dist/cli/lib/config.d.ts.map +1 -0
- package/dist/cli/lib/config.js +38 -0
- package/dist/cli/lib/config.js.map +1 -0
- package/dist/cli/lib/openclaw.d.ts +21 -0
- package/dist/cli/lib/openclaw.d.ts.map +1 -0
- package/dist/cli/lib/openclaw.js +93 -0
- package/dist/cli/lib/openclaw.js.map +1 -0
- package/dist/cli/lib/output.d.ts +9 -0
- package/dist/cli/lib/output.d.ts.map +1 -0
- package/dist/cli/lib/output.js +36 -0
- package/dist/cli/lib/output.js.map +1 -0
- package/dist/cli/lib/version.d.ts +9 -0
- package/dist/cli/lib/version.d.ts.map +1 -0
- package/dist/cli/lib/version.js +51 -0
- package/dist/cli/lib/version.js.map +1 -0
- package/dist/client.d.ts +72 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +85 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +69 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +163 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks.d.ts +131 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +458 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +184 -0
- package/dist/index.js.map +1 -0
- package/dist/native-memory.d.ts +67 -0
- package/dist/native-memory.d.ts.map +1 -0
- package/dist/native-memory.js +79 -0
- package/dist/native-memory.js.map +1 -0
- package/dist/register.d.ts +10 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +150 -0
- package/dist/register.js.map +1 -0
- package/dist/server-manager.d.ts +19 -0
- package/dist/server-manager.d.ts.map +1 -0
- package/dist/server-manager.js +238 -0
- package/dist/server-manager.js.map +1 -0
- package/dist/tools.d.ts +32 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +56 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/docker-compose.yml +34 -0
- package/openclaw.plugin.json +99 -0
- package/package.json +65 -0
- package/server/Dockerfile +7 -0
- package/server/main.py +763 -0
- package/server/pyproject.toml +19 -0
- package/server/requirements.txt +5 -0
- package/server/uv.lock +1162 -0
package/.env.example
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# --- Graphiti server LLM provider ---
|
|
2
|
+
# Set the API key for your chosen LLM provider.
|
|
3
|
+
# Only one provider is needed (unless noted below).
|
|
4
|
+
# Configured in config.yaml (llm.provider and embedder.provider).
|
|
5
|
+
|
|
6
|
+
# Google Gemini (default provider — fully self-contained: LLM + embeddings + reranking)
|
|
7
|
+
GOOGLE_API_KEY=
|
|
8
|
+
|
|
9
|
+
# OpenAI (handles LLM + embeddings; needed for embeddings if using Anthropic or Groq)
|
|
10
|
+
OPENAI_API_KEY=
|
|
11
|
+
|
|
12
|
+
# Anthropic (LLM only — still requires OPENAI_API_KEY for embeddings)
|
|
13
|
+
ANTHROPIC_API_KEY=
|
|
14
|
+
|
|
15
|
+
# Groq (LLM only — still requires OPENAI_API_KEY for embeddings)
|
|
16
|
+
GROQ_API_KEY=
|
|
17
|
+
|
|
18
|
+
# --- OpenClaw native memory embeddings ---
|
|
19
|
+
# OpenClaw's native memory_search needs an embedding provider to index
|
|
20
|
+
# MEMORY.md and memory/*.md files. Without one, files are never indexed
|
|
21
|
+
# and memory_search returns empty results (even in FTS-only mode — this
|
|
22
|
+
# is an upstream OpenClaw bug).
|
|
23
|
+
#
|
|
24
|
+
# The key must be set in the OpenClaw gateway's environment (not just here).
|
|
25
|
+
# Add it to ~/.openclaw/.env on the host running the gateway:
|
|
26
|
+
#
|
|
27
|
+
# echo 'GOOGLE_API_KEY=...' >> ~/.openclaw/.env
|
|
28
|
+
#
|
|
29
|
+
# OpenClaw auto-detects the provider from available keys. Any one of these
|
|
30
|
+
# will work: OPENAI_API_KEY, GOOGLE_API_KEY, VOYAGE_API_KEY, MISTRAL_API_KEY.
|
|
31
|
+
# If you already set GOOGLE_API_KEY above for Graphiti and the gateway can
|
|
32
|
+
# see it, native memory indexing will use it automatically.
|
package/README.md
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# Gralkor
|
|
2
|
+
|
|
3
|
+
**Persistent memory for OpenClaw agents, powered by knowledge graphs.**
|
|
4
|
+
|
|
5
|
+
Gralkor is an OpenClaw plugin that gives your agents long-term, temporally-aware memory. It uses [Graphiti](https://github.com/getzep/graphiti) (by Zep) for knowledge graph construction and [FalkorDB](https://www.falkordb.com/) as the graph database backend. Both run automatically as a managed subprocess — no Docker required.
|
|
6
|
+
|
|
7
|
+
Gralkor automatically remembers and recalls everything your agents says, _thinks_, and _does_ — no prompt engineering required by the operator, no conscious (haha) effort required by the agent.
|
|
8
|
+
|
|
9
|
+
## Why Gralkor
|
|
10
|
+
|
|
11
|
+
After years of building with every AI memory system out there, reading the latest research daily, and doing my own cognitive architecture experiments, I am here to tell you a thing or two about AI memory, and why you should use Gralkor for your OpenClaw agents and forget everything else.
|
|
12
|
+
|
|
13
|
+
**Graphs, not Markdown or pure vector** The AI ecosystem's fixation on Markdown-based memory is baffling. Graphs have been the right data structure for representing knowledge since long before LLMs existed. Your code is a graph (syntax trees). Your filesystem is a graph. The web is a graph. Relationships between entities are naturally graph-shaped, and trying to flatten them into Markdown files or pure vector embeddings is fighting reality. [Graphiti](https://github.com/getzep/graphiti) combines a knowledge graph with vector search — you get structured relationships *and* semantic retrieval. Facts carry temporal validity: when they became true, when they stopped being true, when they were superseded. This is not yet another chunking strategy or embedding experiment. Graphiti has solved this layer of the problem and we build on top of it (and not much).
|
|
14
|
+
|
|
15
|
+
**Remembering behaviour, not just dialog.** When your agent reasons through a problem — weighing options, rejecting approaches, arriving at a conclusion — that thinking process is as valuable as the final answer. Gralkor distills the agent's thinking blocks into first-person behavioural summaries and weaves them into the episode transcript before ingestion. The graph doesn't just know what was said; it knows how the agent arrived there. This adds roughly 20% to token cost during ingestion. *Fighting words*: Other memory systems only remember what was spoken, totally ignoring what your agent thinkgs and does. Even if you have a sophisticated memory system, your agent is inherently dishonest with you, frequently claiming to remember what it has done when it only really remembers what claimed to have done, or to have thought what it is only now imagining. Gralkor actually remembers what your agent thought and did - it is the only memory system with this capability AFAIK.
|
|
16
|
+
|
|
17
|
+
**Maximum context at ingestion.** Most memory systems save isolated question-answer pairs or summarized snippets. Gralkor captures all messages in each session of work, distills behaviour, and feeds results to Graphiti *as whole episodes*. Extraction works _way_ better when Graphiti has full context rather fragmented QA pairs. *Fighting words*: Other memory systems capture single QA exchanges of dialog, we capture _the whole episode_ - the entire series of questions, thoughts, actions, and responses that _solved the problem_. Richer semantics, better understanding, better recall.
|
|
18
|
+
|
|
19
|
+
**Built for the long term.**. Graphiti - on which Gralkor is based - is _temporally aware_. On every ingestion, it doesn't just append, it resolves new information against the existing graph, amending, expiring, and invalidating so that your agent knows _what happened over time_. This is expensive, bad for throughput, and useless for short-lived agents, so serving a single, long-lived user agent is _the perfect use case_. Graphiti was destined for Gralkor and OpenClaw.
|
|
20
|
+
|
|
21
|
+
**Recursion through reflection.** A knowledge graph is a living structure. The most powerful thing you can do with it is point the agent back at its own memory — let it reflect on what it knows, identify contradictions, synthesize higher-order insights, and do with them whatever you believe to be _good cognitive architecture_ :shrug:. Gralkor doesn't prescribe how you do this. Instead, it provides the platform for cognitive architecture experimentation: a structured, temporally-aware graph that the agent can both read from and write to using OpenClaw crons.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Example: schedule the agent to reflect on its memory every 6 hours
|
|
25
|
+
openclaw cron add --every 6h --prompt "Search your memory for recent facts. \
|
|
26
|
+
Look for contradictions, outdated information, or patterns worth consolidating. \
|
|
27
|
+
Use memory_add to store any new insights."
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This is where it gets interesting. The graph gives you a substrate for experimentation — reflection strategies, knowledge consolidation, cross-session reasoning — that flat retrieval systems simply cannot support.
|
|
31
|
+
|
|
32
|
+
**Custom ontology: model your agent's world _your way_.** Define your own entity types, attributes, and relationships so that information is parsed into the language of your domain — or your life. [Apple's ODKE+](https://arxiv.org/abs/2509.04696) (2025) showed ontology-guided extraction hits 98.8% precision vs 91% raw LLM; a [separate study](https://arxiv.org/abs/2511.05991) (2025) found ontology-guided KGs substantially outperform vector baselines for retrieval. *Fighting words*: When every entity is just a "thing" and every relationship is `RELATES_TO`, your graph is a soup of ambiguity. Custom ontology turns it into a structured model of _your_ world.
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
Gralkor replaces the native memory plugin entirely, taking the memory slot.
|
|
37
|
+
|
|
38
|
+
- **`memory_search`** — searches both native Markdown files and the knowledge graph in parallel, returning combined results
|
|
39
|
+
- **`memory_get`** — reads native Markdown memory files directly (delegated to OpenClaw's built-in implementation)
|
|
40
|
+
- **`memory_add`** — stores information in the knowledge graph; Graphiti extracts entities and relationships
|
|
41
|
+
- Hooks: auto-capture (stores full multi-turn conversations after each agent run), auto-recall (injects relevant facts and entities before the agent responds)
|
|
42
|
+
- Set up: `plugins.slots.memory = "gralkor"` in `openclaw.json`
|
|
43
|
+
|
|
44
|
+
The agent gets a unified memory interface where it doesn't need to think about which backend to query.
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### 1. Prerequisites
|
|
49
|
+
|
|
50
|
+
- OpenClaw >= 2026.1.26
|
|
51
|
+
- Python 3.12+ on the system PATH
|
|
52
|
+
- `uv` on PATH ([install](https://docs.astral.sh/uv/getting-started/installation/))
|
|
53
|
+
- An API key for a supported LLM provider (see below)
|
|
54
|
+
|
|
55
|
+
### 2. Install the plugin
|
|
56
|
+
|
|
57
|
+
**Using the CLI helper (recommended):**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx @susu-eng/gralkor install
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This handles everything: installs the plugin, enables it, assigns the memory slot, and migrates from `memory-gralkor` if present. You can also pass config inline:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx @susu-eng/gralkor install \
|
|
67
|
+
--config '{"llm":{"provider":"openai","model":"gpt-4.1-mini"}}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or from a tarball:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx @susu-eng/gralkor install /path/to/susu-eng-gralkor-memory-19.0.4.tgz
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The install is idempotent — running it again with the same version is a no-op.
|
|
77
|
+
|
|
78
|
+
**Manual install:**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
openclaw plugins install @susu-eng/gralkor
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then edit `~/.openclaw/openclaw.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"plugins": {
|
|
89
|
+
"slots": {
|
|
90
|
+
"memory": "gralkor"
|
|
91
|
+
},
|
|
92
|
+
"entries": {
|
|
93
|
+
"gralkor": {
|
|
94
|
+
"enabled": true,
|
|
95
|
+
"config": {}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Set your LLM API key
|
|
103
|
+
|
|
104
|
+
Graphiti needs an LLM to extract entities and relationships from conversations. Make sure the API key for your chosen provider is available in the environment (see [OpenClaw docs](https://openclaw.dev/docs) for where env vars are configured).
|
|
105
|
+
|
|
106
|
+
Supported providers:
|
|
107
|
+
|
|
108
|
+
| Provider | Env var | Notes |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| **Google Gemini** (default) | `GOOGLE_API_KEY` | Fully self-contained (LLM + embeddings + reranking) |
|
|
111
|
+
| **OpenAI** | `OPENAI_API_KEY` | Handles LLM + embeddings out of the box |
|
|
112
|
+
| **Anthropic** | `ANTHROPIC_API_KEY` | LLM only — still needs `OPENAI_API_KEY` for embeddings |
|
|
113
|
+
| **Groq** | `GROQ_API_KEY` | LLM only — still needs `OPENAI_API_KEY` for embeddings |
|
|
114
|
+
|
|
115
|
+
To switch away from Gemini, set `llm` and `embedder` in the plugin config. For example, with OpenAI:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"plugins": {
|
|
120
|
+
"slots": {
|
|
121
|
+
"memory": "gralkor"
|
|
122
|
+
},
|
|
123
|
+
"entries": {
|
|
124
|
+
"gralkor": {
|
|
125
|
+
"enabled": true,
|
|
126
|
+
"config": {
|
|
127
|
+
"llm": { "provider": "openai", "model": "gpt-4.1-mini" },
|
|
128
|
+
"embedder": { "provider": "openai", "model": "text-embedding-3-small" }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 4. Restart and go
|
|
137
|
+
|
|
138
|
+
Restart OpenClaw. On first start, Gralkor automatically:
|
|
139
|
+
- Creates a Python virtual environment
|
|
140
|
+
- Installs Graphiti and its dependencies (~1-2 min first time)
|
|
141
|
+
- Starts the Graphiti server with embedded FalkorDB
|
|
142
|
+
- Subsequent restarts are fast (venv reused, pip skipped)
|
|
143
|
+
|
|
144
|
+
Verify the plugin loaded:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
openclaw plugins list
|
|
148
|
+
openclaw gralkor status
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Start chatting with your agent. Gralkor works in the background:
|
|
152
|
+
- **Auto-capture**: Full multi-turn conversations are stored in the knowledge graph after each agent run
|
|
153
|
+
- **Auto-recall**: Before the agent responds, relevant facts and entities are retrieved and injected as context
|
|
154
|
+
|
|
155
|
+
## Native memory search
|
|
156
|
+
|
|
157
|
+
In memory mode, `memory_search` searches both the knowledge graph and native Markdown files (`MEMORY.md`, `memory/*.md`). For native memory indexing to work, OpenClaw's gateway needs an embedding provider API key (`OPENAI_API_KEY`, `GOOGLE_API_KEY`, `VOYAGE_API_KEY`, or `MISTRAL_API_KEY`) in its environment. Without this, native `memory_search` results will be empty (this is an upstream OpenClaw bug in FTS-only mode — the FTS table is never populated without an embedding provider).
|
|
158
|
+
|
|
159
|
+
## CLI
|
|
160
|
+
|
|
161
|
+
### Lifecycle management
|
|
162
|
+
|
|
163
|
+
Run via `npx @susu-eng/gralkor` or install globally:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
gralkor install # Self-install from npm (default: @susu-eng/gralkor)
|
|
167
|
+
gralkor config --set llm.model=gpt-4.1-mini # Set plugin config
|
|
168
|
+
gralkor check # Pre-flight: uv, API keys, plugin state, server health
|
|
169
|
+
gralkor status # Plugin version, slot, server health, graph stats
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `install` command handles version comparison, upgrades, migration from the old `memory-gralkor` plugin ID, and optional `--config`/`--set` flags. Use `--dry-run` to preview what it would do.
|
|
173
|
+
|
|
174
|
+
### Plugin commands (via OpenClaw)
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
openclaw gralkor status # Server state, config, graph stats, data dir, venv
|
|
178
|
+
openclaw gralkor search <query> # Search the knowledge graph
|
|
179
|
+
openclaw gralkor clear [group] # Delete all data for a group (destructive!)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The native `openclaw memory` commands also remain available.
|
|
183
|
+
|
|
184
|
+
## Configuration
|
|
185
|
+
|
|
186
|
+
Configure in your OpenClaw plugin settings (`~/.openclaw/openclaw.json`):
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"plugins": {
|
|
191
|
+
"entries": {
|
|
192
|
+
"gralkor": {
|
|
193
|
+
"enabled": true,
|
|
194
|
+
"config": {
|
|
195
|
+
"autoCapture": { "enabled": true },
|
|
196
|
+
"autoRecall": { "enabled": true, "maxResults": 10 },
|
|
197
|
+
"idleTimeoutMs": 300000,
|
|
198
|
+
"dataDir": "/path/to/data"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
| Setting | Default | Description |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `autoCapture.enabled` | `true` | Automatically store conversations in the graph |
|
|
209
|
+
| `autoRecall.enabled` | `true` | Automatically recall relevant context before each turn |
|
|
210
|
+
| `autoRecall.maxResults` | `10` | Maximum number of facts injected as context |
|
|
211
|
+
| `idleTimeoutMs` | `300000` | How long (ms) after the last agent response to wait before flushing buffered messages to the graph. Prevents data loss when sessions aren't explicitly ended (e.g. user walks away, gateway restarts). Set to `0` to disable idle flushing. |
|
|
212
|
+
| `dataDir` | `{pluginDir}/../.gralkor-data` | Directory for backend data (Python venv, FalkorDB database) |
|
|
213
|
+
| `test` | `false` | Test mode — logs full episode bodies and search results at plugin boundaries for debugging |
|
|
214
|
+
|
|
215
|
+
### Graph partitioning
|
|
216
|
+
|
|
217
|
+
Each agent gets its own graph partition automatically (based on `agentId`). No configuration needed — different agents won't see each other's knowledge.
|
|
218
|
+
|
|
219
|
+
## Custom entity and relationship types
|
|
220
|
+
|
|
221
|
+
By default, Graphiti extracts generic entities and connects them with generic `RELATES_TO` relationships. This works well out of the box — you don't need to configure anything for Gralkor to be useful.
|
|
222
|
+
|
|
223
|
+
If you want more structured extraction, you can define custom entity and relationship types. Graphiti will classify entities into your types, extract structured attributes, and create typed relationships between them.
|
|
224
|
+
|
|
225
|
+
### Entities only (start here)
|
|
226
|
+
|
|
227
|
+
The simplest useful ontology defines just entity types. Relationships will still be created, using Graphiti's default `RELATES_TO` type.
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"plugins": {
|
|
232
|
+
"entries": {
|
|
233
|
+
"gralkor": {
|
|
234
|
+
"enabled": true,
|
|
235
|
+
"config": {
|
|
236
|
+
"ontology": {
|
|
237
|
+
"entities": {
|
|
238
|
+
"Project": {
|
|
239
|
+
"description": "A software project or initiative being actively developed. Look for mentions of repositories, codebases, applications, services, or named systems that are built and maintained by a team.",
|
|
240
|
+
"attributes": {
|
|
241
|
+
"status": ["active", "completed", "paused"],
|
|
242
|
+
"language": "Primary programming language used in the project"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"Technology": {
|
|
246
|
+
"description": "A programming language, framework, library, database, or infrastructure tool. Identify by mentions of specific named technologies used in or considered for projects.",
|
|
247
|
+
"attributes": {
|
|
248
|
+
"category": ["language", "framework", "database", "infrastructure", "tool"]
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Adding relationships
|
|
261
|
+
|
|
262
|
+
To control how entities are connected, add `edges` (relationship types) and `edgeMap` (which entity pairs they apply to):
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"ontology": {
|
|
267
|
+
"entities": {
|
|
268
|
+
"Project": {
|
|
269
|
+
"description": "A software project or initiative being actively developed. Look for mentions of repositories, codebases, applications, services, or named systems that are built and maintained by a team.",
|
|
270
|
+
"attributes": {
|
|
271
|
+
"status": ["active", "completed", "paused"],
|
|
272
|
+
"language": "Primary programming language used in the project"
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"Technology": {
|
|
276
|
+
"description": "A programming language, framework, library, database, or infrastructure tool. Identify by mentions of specific named technologies used in or considered for projects.",
|
|
277
|
+
"attributes": {
|
|
278
|
+
"category": ["language", "framework", "database", "infrastructure", "tool"]
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"edges": {
|
|
283
|
+
"Uses": {
|
|
284
|
+
"description": "A project actively using a technology in its stack. Look for statements about tech choices, dependencies, or implementation details that indicate a project relies on a specific technology.",
|
|
285
|
+
"attributes": {
|
|
286
|
+
"version": "Version of the technology in use, if mentioned"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"edgeMap": {
|
|
291
|
+
"Project,Technology": ["Uses"]
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Without `edgeMap`, all edge types can connect any entity pair. With `edgeMap`, relationships are constrained to specific pairs — entity pairs not listed fall back to `RELATES_TO`.
|
|
298
|
+
|
|
299
|
+
### Attribute format
|
|
300
|
+
|
|
301
|
+
Attributes control what Graphiti extracts for each entity or relationship. They are **required fields** — if the LLM can't populate them from the text, it won't extract that entity type at all. This makes attributes the primary mechanism for gating extraction quality.
|
|
302
|
+
|
|
303
|
+
| Format | Example | Generated type | Gating strength |
|
|
304
|
+
|---|---|---|---|
|
|
305
|
+
| String | `"language": "Primary programming language"` | Required `str` field | Weak — any text satisfies it |
|
|
306
|
+
| Enum (array) | `"status": ["active", "completed", "paused"]` | Required `Literal` enum | Strong — must pick a valid value |
|
|
307
|
+
| Typed object | `"budget": { "type": "float", "description": "Budget in USD" }` | Required typed field | Medium — must be valid type |
|
|
308
|
+
| Enum with description | `"priority": { "enum": ["low", "high"], "description": "Priority level" }` | Required `Literal` enum | Strong |
|
|
309
|
+
|
|
310
|
+
Supported types for the object form: `string`, `int`, `float`, `bool`, `datetime`.
|
|
311
|
+
|
|
312
|
+
### Writing good descriptions
|
|
313
|
+
|
|
314
|
+
Descriptions are the most important part of your ontology — they tell the LLM what to look for. Write them like extraction instructions, not dictionary definitions.
|
|
315
|
+
|
|
316
|
+
**Weak** (dictionary definition):
|
|
317
|
+
```
|
|
318
|
+
"A software project."
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Strong** (extraction instructions):
|
|
322
|
+
```
|
|
323
|
+
"A software project or initiative being actively developed. Look for mentions of repositories, codebases, applications, services, or named systems that are built and maintained by a team."
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The more specific your description, the better Graphiti will distinguish between entity types and avoid false positives.
|
|
327
|
+
|
|
328
|
+
### Excluding entity types
|
|
329
|
+
|
|
330
|
+
To prevent Graphiti from extracting certain default entity types:
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"ontology": {
|
|
335
|
+
"excludedEntityTypes": ["SomeType"]
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Reserved names
|
|
341
|
+
|
|
342
|
+
The following entity names are used internally by Graphiti and cannot be used: `Entity`, `Episodic`, `Community`, `Saga`.
|
|
343
|
+
|
|
344
|
+
## Data storage
|
|
345
|
+
|
|
346
|
+
By default, all data lives in `.gralkor-data/` alongside the plugin directory (i.e. `{pluginDir}/../.gralkor-data/`):
|
|
347
|
+
- `venv/` — Python virtual environment (Graphiti, FalkorDBLite, etc.)
|
|
348
|
+
- `falkordb/` — embedded FalkorDB database files
|
|
349
|
+
|
|
350
|
+
This location is outside the plugin directory so that `openclaw plugins uninstall` doesn't destroy runtime data — the graph database survives plugin upgrades without any data-preservation workarounds.
|
|
351
|
+
|
|
352
|
+
Set `dataDir` in plugin config to change the location.
|
|
353
|
+
|
|
354
|
+
## How it works
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
User sends message
|
|
358
|
+
│
|
|
359
|
+
▼
|
|
360
|
+
┌─────────────┐ search ┌──────────┐ query ┌──────────┐
|
|
361
|
+
│ auto-recall │ ──────────▶ │ Graphiti │ ──────────▶ │ FalkorDB │
|
|
362
|
+
│ hook │ ◀────────── │ API │ ◀────────── │ │
|
|
363
|
+
└─────────────┘ facts └──────────┘ subgraph └──────────┘
|
|
364
|
+
│
|
|
365
|
+
▼
|
|
366
|
+
Agent runs (with recalled facts as context)
|
|
367
|
+
│
|
|
368
|
+
▼
|
|
369
|
+
┌──────────────┐ ingest ┌──────────┐ extract ┌──────────┐
|
|
370
|
+
│ auto-capture │ ──────────▶ │ Graphiti │ ──────────▶ │ FalkorDB │
|
|
371
|
+
│ hook │ │ API │ entities │ │
|
|
372
|
+
└──────────────┘ └──────────┘ & facts └──────────┘
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Graphiti handles the heavy lifting: entity extraction, relationship mapping, temporal tracking, and embedding-based search. Gralkor wires it into the OpenClaw plugin lifecycle. The Graphiti server and embedded FalkorDB run as a managed subprocess — started and stopped automatically by the plugin.
|
|
376
|
+
|
|
377
|
+
## Troubleshooting
|
|
378
|
+
|
|
379
|
+
**`gralkor status` says "Server process: stopped"**
|
|
380
|
+
Python 3.12+ is not found on the system PATH. Install Python 3.12+ and restart OpenClaw.
|
|
381
|
+
|
|
382
|
+
**First startup takes a long time**
|
|
383
|
+
Normal — Gralkor is creating a Python virtual environment and installing dependencies via pip. This takes ~1-2 minutes. Subsequent starts reuse the venv and skip pip.
|
|
384
|
+
|
|
385
|
+
**Plugin loads but all graph operations fail**
|
|
386
|
+
Run `openclaw gralkor check` to validate your provider configuration and API keys. Most likely: missing or invalid LLM API key in the environment.
|
|
387
|
+
|
|
388
|
+
**No memories being recalled**
|
|
389
|
+
- Check that `autoRecall.enabled` is `true` (it is by default)
|
|
390
|
+
- Verify the graph has data: run `openclaw gralkor search <term>`
|
|
391
|
+
- Auto-recall extracts keywords from the user's message — very short messages may not match
|
|
392
|
+
|
|
393
|
+
**Agent doesn't store conversations**
|
|
394
|
+
- Check that `autoCapture.enabled` is `true` (it is by default)
|
|
395
|
+
- Conversations are flushed to the graph when the session ends or after 5 minutes of inactivity (configurable via `idleTimeoutMs`). On SIGTERM, all pending buffers are flushed before shutdown. If the process receives SIGKILL without prior SIGTERM, buffered messages may be lost.
|
|
396
|
+
- Conversations where the first user message starts with `/` are skipped by design
|
|
397
|
+
- Empty conversations (no extractable text) are skipped
|
|
398
|
+
|
|
399
|
+
**Agent doesn't have the `memory_add` tool**
|
|
400
|
+
OpenClaw's tool profiles (`coding`, `minimal`, etc.) only allowlist core tools by default. `memory_add` is a plugin tool, so it gets filtered out when a profile is active. To enable it, add it to `alsoAllow` in your `openclaw.json`:
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"tools": {
|
|
405
|
+
"alsoAllow": ["memory_add"]
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
You can also allow all Gralkor tools with `"alsoAllow": ["gralkor"]` or all plugin tools with `"alsoAllow": ["group:plugins"]`. Note that `memory_add` is not required for Gralkor to work — auto-capture already stores everything your agent hears, says, thinks, and does. `memory_add` is only needed if you want the agent to selectively store specific insights or conclusions on its own.
|
|
411
|
+
|
|
412
|
+
**`memory_search` returns empty in memory mode**
|
|
413
|
+
Native memory indexing needs an embedding provider key in the OpenClaw gateway's environment. See the "Native memory search" section above.
|
|
414
|
+
|
|
415
|
+
## Legacy Docker mode
|
|
416
|
+
|
|
417
|
+
If you prefer to run FalkorDB as a separate Docker container (e.g. for production deployments with specific resource constraints), you can set `FALKORDB_URI` to bypass the embedded mode:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
cd ~/.openclaw/plugins/gralkor
|
|
421
|
+
docker build -t gralkor-server:latest server/
|
|
422
|
+
FALKORDB_URI=redis://falkordb:6379 docker compose up -d
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
This starts FalkorDB on port 6379 and the Graphiti API on port 8001. If your OpenClaw gateway runs in Docker, connect it to the `gralkor` network:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
docker network connect gralkor <your-openclaw-container-name>
|
|
429
|
+
```
|
package/config.yaml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Graphiti configuration
|
|
2
|
+
# See: https://help.getzep.com/graphiti/configuration/llm-configuration
|
|
3
|
+
#
|
|
4
|
+
# Change the provider/model to match your API key in .env
|
|
5
|
+
# Supported providers: openai, anthropic, gemini, groq, azure_openai
|
|
6
|
+
|
|
7
|
+
server:
|
|
8
|
+
transport: "http"
|
|
9
|
+
|
|
10
|
+
llm:
|
|
11
|
+
provider: "gemini"
|
|
12
|
+
model: "gemini-3.1-flash-lite-preview"
|
|
13
|
+
|
|
14
|
+
embedder:
|
|
15
|
+
provider: "gemini"
|
|
16
|
+
model: "gemini-embedding-2-preview"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":""}
|
package/dist/cli/bin.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { install } from "./commands/install.js";
|
|
4
|
+
import { config } from "./commands/config.js";
|
|
5
|
+
import { check } from "./commands/check.js";
|
|
6
|
+
import { status } from "./commands/status.js";
|
|
7
|
+
import { getCLIVersion } from "./lib/version.js";
|
|
8
|
+
const DEFAULT_SOURCE = "@susu-eng/gralkor";
|
|
9
|
+
const HELP = `Usage: gralkor <command> [options]
|
|
10
|
+
|
|
11
|
+
Commands:
|
|
12
|
+
install [source] Install or upgrade the Gralkor plugin (default: ${DEFAULT_SOURCE})
|
|
13
|
+
config Set plugin configuration
|
|
14
|
+
check Validate prerequisites (uv, API keys, etc.)
|
|
15
|
+
status Show plugin and server status
|
|
16
|
+
|
|
17
|
+
Install/config options:
|
|
18
|
+
--config <json> Plugin config as JSON string
|
|
19
|
+
--set <key=value> Set individual config value (repeatable)
|
|
20
|
+
|
|
21
|
+
Install options:
|
|
22
|
+
--dry-run Show what would happen without executing
|
|
23
|
+
|
|
24
|
+
General:
|
|
25
|
+
--help Show this help
|
|
26
|
+
--version Show CLI version`;
|
|
27
|
+
async function main() {
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const command = args[0];
|
|
30
|
+
if (!command || command === "--help" || command === "-h") {
|
|
31
|
+
console.log(HELP);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (command === "--version" || command === "-v") {
|
|
35
|
+
console.log(`gralkor ${getCLIVersion()}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
switch (command) {
|
|
39
|
+
case "install": {
|
|
40
|
+
const { values, positionals } = parseArgs({
|
|
41
|
+
args: args.slice(1),
|
|
42
|
+
options: {
|
|
43
|
+
config: { type: "string" },
|
|
44
|
+
set: { type: "string", multiple: true },
|
|
45
|
+
"dry-run": { type: "boolean", default: false },
|
|
46
|
+
},
|
|
47
|
+
allowPositionals: true,
|
|
48
|
+
});
|
|
49
|
+
const source = positionals[0] ?? DEFAULT_SOURCE;
|
|
50
|
+
await install({
|
|
51
|
+
source,
|
|
52
|
+
config: values.config,
|
|
53
|
+
set: values.set,
|
|
54
|
+
dryRun: values["dry-run"],
|
|
55
|
+
});
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "config": {
|
|
59
|
+
const { values: configValues } = parseArgs({
|
|
60
|
+
args: args.slice(1),
|
|
61
|
+
options: {
|
|
62
|
+
config: { type: "string" },
|
|
63
|
+
set: { type: "string", multiple: true },
|
|
64
|
+
},
|
|
65
|
+
allowPositionals: false,
|
|
66
|
+
});
|
|
67
|
+
await config({
|
|
68
|
+
config: configValues.config,
|
|
69
|
+
set: configValues.set,
|
|
70
|
+
});
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case "check":
|
|
74
|
+
await check();
|
|
75
|
+
break;
|
|
76
|
+
case "status":
|
|
77
|
+
await status();
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
console.error(`Unknown command: ${command}`);
|
|
81
|
+
console.error("Run 'gralkor --help' for usage");
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
main().catch((err) => {
|
|
86
|
+
console.error(err instanceof Error ? err.message : err);
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=bin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"AAQA,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAuG3C"}
|