@icex-labs/openclaw-memory-engine 3.3.1
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 +254 -0
- package/extras/auto-consolidation-crons.json +37 -0
- package/extras/memory-maintenance.sh +176 -0
- package/index.js +626 -0
- package/lib/archival.js +54 -0
- package/lib/backup.js +99 -0
- package/lib/consolidate.js +102 -0
- package/lib/core.js +76 -0
- package/lib/dashboard.js +235 -0
- package/lib/dedup.js +68 -0
- package/lib/embedding.js +70 -0
- package/lib/episodes.js +133 -0
- package/lib/graph.js +148 -0
- package/lib/paths.js +80 -0
- package/lib/reflection.js +188 -0
- package/lib/search.js +90 -0
- package/lib/store-sqlite.js +422 -0
- package/openclaw.plugin.json +23 -0
- package/package.json +40 -0
- package/setup.sh +368 -0
package/setup.sh
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# setup.sh β One-command setup for openclaw-memory-engine
|
|
3
|
+
# Usage: bash setup.sh [workspace_path]
|
|
4
|
+
# --non-interactive Skip prompts, use defaults
|
|
5
|
+
#
|
|
6
|
+
# Supports: macOS (LaunchAgent), Linux (systemd timer), Windows (manual)
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
WORKSPACE="${1:-${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}}"
|
|
11
|
+
OPENCLAW_DIR="${OPENCLAW_DIR:-$HOME/.openclaw}"
|
|
12
|
+
CONFIG="$OPENCLAW_DIR/openclaw.json"
|
|
13
|
+
MEMORY_DIR="$WORKSPACE/memory"
|
|
14
|
+
AGENTS_MD="$WORKSPACE/AGENTS.md"
|
|
15
|
+
PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
NON_INTERACTIVE=false
|
|
17
|
+
[[ "${2:-}" == "--non-interactive" || "${1:-}" == "--non-interactive" ]] && NON_INTERACTIVE=true
|
|
18
|
+
OS="$(uname -s)"
|
|
19
|
+
|
|
20
|
+
echo "π§ openclaw-memory-engine setup"
|
|
21
|
+
echo " Workspace: $WORKSPACE"
|
|
22
|
+
echo " Config: $CONFIG"
|
|
23
|
+
echo " Platform: $OS"
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
# --- Helper: prompt with default ---
|
|
27
|
+
ask() {
|
|
28
|
+
local prompt="$1" default="$2" var_name="$3"
|
|
29
|
+
if $NON_INTERACTIVE; then
|
|
30
|
+
eval "$var_name=\"$default\""
|
|
31
|
+
return
|
|
32
|
+
fi
|
|
33
|
+
printf "%s [%s]: " "$prompt" "$default"
|
|
34
|
+
read -r input
|
|
35
|
+
eval "$var_name=\"${input:-$default}\""
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# --- 1. Create memory directory ---
|
|
39
|
+
mkdir -p "$MEMORY_DIR"
|
|
40
|
+
echo "β
memory/ directory ready"
|
|
41
|
+
|
|
42
|
+
# --- 2. Create core.json (interactive or template) ---
|
|
43
|
+
if [ ! -f "$MEMORY_DIR/core.json" ]; then
|
|
44
|
+
echo ""
|
|
45
|
+
echo "π Let's set up your core memory (what your agent knows about you)."
|
|
46
|
+
echo " Press Enter to skip any field β you can update later via core_memory_replace."
|
|
47
|
+
echo ""
|
|
48
|
+
|
|
49
|
+
ask "Your name" "" USER_NAME
|
|
50
|
+
ask "Your location (city, timezone)" "" USER_LOCATION
|
|
51
|
+
ask "Language preference (e.g., 'English', 'δΈθ±ε―Ήη
§')" "English" USER_LANG
|
|
52
|
+
ask "Your job/role" "" USER_JOB
|
|
53
|
+
ask "Agent relationship (e.g., 'helpful assistant', 'intimate companion')" "helpful assistant" REL_DYNAMIC
|
|
54
|
+
|
|
55
|
+
python3 -c "
|
|
56
|
+
import json, datetime
|
|
57
|
+
core = {
|
|
58
|
+
'_meta': {
|
|
59
|
+
'version': 1,
|
|
60
|
+
'updated_at': datetime.datetime.utcnow().isoformat() + 'Z',
|
|
61
|
+
'description': 'Core memory block β always in context. Keep under 500 tokens.'
|
|
62
|
+
},
|
|
63
|
+
'user': {
|
|
64
|
+
'name': '''$USER_NAME''',
|
|
65
|
+
'location': '''$USER_LOCATION''',
|
|
66
|
+
'language': '''$USER_LANG''',
|
|
67
|
+
'job': '''$USER_JOB'''
|
|
68
|
+
},
|
|
69
|
+
'relationship': {
|
|
70
|
+
'dynamic': '''$REL_DYNAMIC''',
|
|
71
|
+
'trust': '',
|
|
72
|
+
'boundaries': ''
|
|
73
|
+
},
|
|
74
|
+
'preferences': {},
|
|
75
|
+
'current_focus': []
|
|
76
|
+
}
|
|
77
|
+
# Remove empty string values
|
|
78
|
+
for section in ['user', 'relationship']:
|
|
79
|
+
core[section] = {k: v for k, v in core[section].items() if v}
|
|
80
|
+
with open('$MEMORY_DIR/core.json', 'w') as f:
|
|
81
|
+
json.dump(core, f, indent=2, ensure_ascii=False)
|
|
82
|
+
print('β
core.json created with your info')
|
|
83
|
+
" 2>/dev/null || {
|
|
84
|
+
# Fallback if python3 fails
|
|
85
|
+
cat > "$MEMORY_DIR/core.json" <<'CORE'
|
|
86
|
+
{
|
|
87
|
+
"_meta": { "version": 1, "updated_at": "", "description": "Core memory block." },
|
|
88
|
+
"user": {},
|
|
89
|
+
"relationship": { "dynamic": "helpful assistant" },
|
|
90
|
+
"preferences": {},
|
|
91
|
+
"current_focus": []
|
|
92
|
+
}
|
|
93
|
+
CORE
|
|
94
|
+
echo "β
core.json created (edit manually to add your info)"
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
echo "βοΈ core.json already exists"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# --- 3. Create empty archival.jsonl (if not exists) ---
|
|
101
|
+
if [ ! -f "$MEMORY_DIR/archival.jsonl" ]; then
|
|
102
|
+
touch "$MEMORY_DIR/archival.jsonl"
|
|
103
|
+
echo "β
archival.jsonl created"
|
|
104
|
+
else
|
|
105
|
+
lines=$(wc -l < "$MEMORY_DIR/archival.jsonl" | tr -d ' ')
|
|
106
|
+
echo "βοΈ archival.jsonl already exists ($lines records)"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# --- 4. Install memory-maintenance.sh ---
|
|
110
|
+
SCRIPTS_DIR="$WORKSPACE/scripts"
|
|
111
|
+
mkdir -p "$SCRIPTS_DIR"
|
|
112
|
+
if [ ! -f "$SCRIPTS_DIR/memory-maintenance.sh" ]; then
|
|
113
|
+
cp "$PLUGIN_DIR/extras/memory-maintenance.sh" "$SCRIPTS_DIR/memory-maintenance.sh" 2>/dev/null || {
|
|
114
|
+
echo "β οΈ memory-maintenance.sh not found in extras/. Copy manually."
|
|
115
|
+
}
|
|
116
|
+
chmod +x "$SCRIPTS_DIR/memory-maintenance.sh" 2>/dev/null
|
|
117
|
+
echo "β
memory-maintenance.sh installed"
|
|
118
|
+
else
|
|
119
|
+
echo "βοΈ memory-maintenance.sh already exists"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# --- 5. Install platform-specific scheduler ---
|
|
123
|
+
install_scheduler() {
|
|
124
|
+
case "$OS" in
|
|
125
|
+
Darwin)
|
|
126
|
+
# macOS: LaunchAgent
|
|
127
|
+
PLIST="$HOME/Library/LaunchAgents/ai.openclaw.memory-maintenance.plist"
|
|
128
|
+
if [ ! -f "$PLIST" ]; then
|
|
129
|
+
cat > "$PLIST" <<PLIST
|
|
130
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
131
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
132
|
+
<plist version="1.0">
|
|
133
|
+
<dict>
|
|
134
|
+
<key>Label</key>
|
|
135
|
+
<string>ai.openclaw.memory-maintenance</string>
|
|
136
|
+
<key>ProgramArguments</key>
|
|
137
|
+
<array>
|
|
138
|
+
<string>/bin/bash</string>
|
|
139
|
+
<string>$SCRIPTS_DIR/memory-maintenance.sh</string>
|
|
140
|
+
</array>
|
|
141
|
+
<key>StartCalendarInterval</key>
|
|
142
|
+
<dict>
|
|
143
|
+
<key>Hour</key>
|
|
144
|
+
<integer>3</integer>
|
|
145
|
+
<key>Minute</key>
|
|
146
|
+
<integer>0</integer>
|
|
147
|
+
</dict>
|
|
148
|
+
<key>StandardOutPath</key>
|
|
149
|
+
<string>/tmp/memory-maintenance.log</string>
|
|
150
|
+
<key>StandardErrorPath</key>
|
|
151
|
+
<string>/tmp/memory-maintenance.log</string>
|
|
152
|
+
<key>RunAtLoad</key>
|
|
153
|
+
<false/>
|
|
154
|
+
</dict>
|
|
155
|
+
</plist>
|
|
156
|
+
PLIST
|
|
157
|
+
launchctl bootstrap gui/$(id -u) "$PLIST" 2>/dev/null || true
|
|
158
|
+
echo "β
macOS LaunchAgent installed (daily 3am)"
|
|
159
|
+
else
|
|
160
|
+
echo "βοΈ macOS LaunchAgent already exists"
|
|
161
|
+
fi
|
|
162
|
+
;;
|
|
163
|
+
|
|
164
|
+
Linux)
|
|
165
|
+
# Linux: systemd user timer
|
|
166
|
+
UNIT_DIR="$HOME/.config/systemd/user"
|
|
167
|
+
mkdir -p "$UNIT_DIR"
|
|
168
|
+
if [ ! -f "$UNIT_DIR/openclaw-memory-maintenance.timer" ]; then
|
|
169
|
+
cat > "$UNIT_DIR/openclaw-memory-maintenance.service" <<SVC
|
|
170
|
+
[Unit]
|
|
171
|
+
Description=OpenClaw Memory Maintenance
|
|
172
|
+
[Service]
|
|
173
|
+
Type=oneshot
|
|
174
|
+
ExecStart=/bin/bash $SCRIPTS_DIR/memory-maintenance.sh
|
|
175
|
+
SVC
|
|
176
|
+
cat > "$UNIT_DIR/openclaw-memory-maintenance.timer" <<TMR
|
|
177
|
+
[Unit]
|
|
178
|
+
Description=OpenClaw Memory Maintenance Timer
|
|
179
|
+
[Timer]
|
|
180
|
+
OnCalendar=*-*-* 03:00:00
|
|
181
|
+
Persistent=true
|
|
182
|
+
[Install]
|
|
183
|
+
WantedBy=timers.target
|
|
184
|
+
TMR
|
|
185
|
+
systemctl --user daemon-reload 2>/dev/null || true
|
|
186
|
+
systemctl --user enable --now openclaw-memory-maintenance.timer 2>/dev/null || true
|
|
187
|
+
echo "β
Linux systemd timer installed (daily 3am)"
|
|
188
|
+
else
|
|
189
|
+
echo "βοΈ Linux systemd timer already exists"
|
|
190
|
+
fi
|
|
191
|
+
;;
|
|
192
|
+
|
|
193
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
194
|
+
echo "β οΈ Windows detected. Add a scheduled task manually:"
|
|
195
|
+
echo " schtasks /create /tn \"OpenClaw Memory Maintenance\" /tr \"bash $SCRIPTS_DIR/memory-maintenance.sh\" /sc daily /st 03:00"
|
|
196
|
+
;;
|
|
197
|
+
|
|
198
|
+
*)
|
|
199
|
+
echo "β οΈ Unknown platform ($OS). Set up a daily cron manually:"
|
|
200
|
+
echo " 0 3 * * * /bin/bash $SCRIPTS_DIR/memory-maintenance.sh"
|
|
201
|
+
;;
|
|
202
|
+
esac
|
|
203
|
+
}
|
|
204
|
+
install_scheduler
|
|
205
|
+
|
|
206
|
+
# --- 6. Patch openclaw.json ---
|
|
207
|
+
if command -v python3 &>/dev/null && [ -f "$CONFIG" ]; then
|
|
208
|
+
python3 <<PYEOF
|
|
209
|
+
import json
|
|
210
|
+
|
|
211
|
+
with open("$CONFIG") as f:
|
|
212
|
+
cfg = json.load(f)
|
|
213
|
+
|
|
214
|
+
changed = False
|
|
215
|
+
|
|
216
|
+
allow = cfg.setdefault("plugins", {}).setdefault("allow", [])
|
|
217
|
+
if "memory-engine" not in allow:
|
|
218
|
+
allow.append("memory-engine")
|
|
219
|
+
changed = True
|
|
220
|
+
|
|
221
|
+
entries = cfg["plugins"].setdefault("entries", {})
|
|
222
|
+
if "memory-engine" not in entries:
|
|
223
|
+
entries["memory-engine"] = {
|
|
224
|
+
"enabled": True,
|
|
225
|
+
"config": {"workspace": "$WORKSPACE"}
|
|
226
|
+
}
|
|
227
|
+
changed = True
|
|
228
|
+
|
|
229
|
+
if changed:
|
|
230
|
+
with open("$CONFIG", "w") as f:
|
|
231
|
+
json.dump(cfg, f, indent=2)
|
|
232
|
+
print("β
openclaw.json updated")
|
|
233
|
+
else:
|
|
234
|
+
print("βοΈ openclaw.json already configured")
|
|
235
|
+
PYEOF
|
|
236
|
+
else
|
|
237
|
+
echo "β οΈ Could not patch openclaw.json. Add manually:"
|
|
238
|
+
echo ' "plugins": { "allow": ["memory-engine"], "entries": { "memory-engine": { "enabled": true } } }'
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# --- 7. Patch AGENTS.md ---
|
|
242
|
+
if [ -f "$AGENTS_MD" ]; then
|
|
243
|
+
if ! grep -q "core_memory_read" "$AGENTS_MD"; then
|
|
244
|
+
cat >> "$AGENTS_MD" <<'PATCH'
|
|
245
|
+
|
|
246
|
+
## Memory System β MemGPT Architecture
|
|
247
|
+
|
|
248
|
+
You have 19 memory tools. **Use them actively.**
|
|
249
|
+
|
|
250
|
+
### Core Memory (`core_memory_read` / `core_memory_replace` / `core_memory_append`)
|
|
251
|
+
- Call `core_memory_read` at **every session start**
|
|
252
|
+
- `core_memory_replace` when facts change
|
|
253
|
+
- `current_focus` max 5 items β update frequently
|
|
254
|
+
- Hard limit: 3KB β move details to archival
|
|
255
|
+
|
|
256
|
+
### Archival Memory (`archival_insert` / `archival_search` / `archival_update` / `archival_delete`)
|
|
257
|
+
- `archival_insert`: store facts with entity + tags + importance (1-10)
|
|
258
|
+
- `archival_search`: hybrid keyword + semantic search. Use before guessing.
|
|
259
|
+
- `archival_update`/`archival_delete`: correct or remove outdated facts
|
|
260
|
+
|
|
261
|
+
### Knowledge Graph (`graph_query` / `graph_add`)
|
|
262
|
+
- Auto-extracted from archival_insert (relations like has_doctor, owns, lives_in)
|
|
263
|
+
- `graph_query`: traverse from entity to find connections
|
|
264
|
+
- `graph_add`: manually add relations
|
|
265
|
+
|
|
266
|
+
### Episodic Memory (`episode_save` / `episode_recall`)
|
|
267
|
+
- `episode_save`: at end of meaningful conversations (summary, decisions, mood, topics)
|
|
268
|
+
- `episode_recall`: "what did we discuss about X last time?"
|
|
269
|
+
|
|
270
|
+
### Maintenance
|
|
271
|
+
- `memory_reflect`: analyze patterns (use during heartbeats)
|
|
272
|
+
- `archival_deduplicate`: clean near-duplicate facts
|
|
273
|
+
- `memory_consolidate`: extract facts from text blocks
|
|
274
|
+
- `memory_dashboard`: generate browsable HTML dashboard
|
|
275
|
+
|
|
276
|
+
### Memory Discipline
|
|
277
|
+
- If it matters β `archival_insert` it. "Mental notes" don't survive restarts.
|
|
278
|
+
- Don't guess β `archival_search` first.
|
|
279
|
+
- End of conversation β `episode_save`.
|
|
280
|
+
- Update core memory proactively.
|
|
281
|
+
PATCH
|
|
282
|
+
echo "β
AGENTS.md patched with memory instructions"
|
|
283
|
+
else
|
|
284
|
+
echo "βοΈ AGENTS.md already has memory instructions"
|
|
285
|
+
fi
|
|
286
|
+
else
|
|
287
|
+
echo "β οΈ AGENTS.md not found β create it with memory instructions"
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
# --- 8. Register cron jobs ---
|
|
291
|
+
if command -v openclaw &>/dev/null; then
|
|
292
|
+
EXISTING_CRONS=$(openclaw cron list --json 2>/dev/null | python3 -c "import sys,json; data=json.load(sys.stdin); print(' '.join(j.get('name','') for j in (data if isinstance(data,list) else data.get('jobs',[]))))" 2>/dev/null || echo "")
|
|
293
|
+
|
|
294
|
+
# Detect timezone
|
|
295
|
+
TZ_IANA=$(python3 -c "
|
|
296
|
+
try:
|
|
297
|
+
import subprocess
|
|
298
|
+
if '$(uname)' == 'Darwin':
|
|
299
|
+
tz = subprocess.check_output(['readlink', '/etc/localtime']).decode().strip().split('zoneinfo/')[-1]
|
|
300
|
+
else:
|
|
301
|
+
tz = open('/etc/timezone').read().strip()
|
|
302
|
+
print(tz)
|
|
303
|
+
except:
|
|
304
|
+
print('UTC')
|
|
305
|
+
" 2>/dev/null || echo "UTC")
|
|
306
|
+
|
|
307
|
+
register_cron() {
|
|
308
|
+
local name="$1" cron="$2" msg="$3" desc="$4" timeout="${5:-60000}"
|
|
309
|
+
if echo "$EXISTING_CRONS" | grep -q "$name"; then
|
|
310
|
+
echo "βοΈ Cron '$name' already exists"
|
|
311
|
+
return
|
|
312
|
+
fi
|
|
313
|
+
openclaw cron add \
|
|
314
|
+
--name "$name" \
|
|
315
|
+
--cron "$cron" \
|
|
316
|
+
--tz "$TZ_IANA" \
|
|
317
|
+
--agent main \
|
|
318
|
+
--session isolated \
|
|
319
|
+
--model "anthropic/claude-sonnet-4-6" \
|
|
320
|
+
--message "$msg" \
|
|
321
|
+
--description "$desc" \
|
|
322
|
+
--timeout "$timeout" \
|
|
323
|
+
>/dev/null 2>&1 && echo "β
Cron '$name' registered" || echo "β οΈ Cron '$name' failed (gateway not running?)"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
register_cron "memory-reflect-daily" "0 9 * * *" \
|
|
327
|
+
"Run memory_reflect with window_days=7. If you notice patterns, store via archival_insert with tags=['reflection']. Do NOT output to main chat." \
|
|
328
|
+
"Daily reflection: analyze memory patterns"
|
|
329
|
+
|
|
330
|
+
register_cron "memory-consolidate-6h" "0 */6 * * *" \
|
|
331
|
+
"Read today's daily log. If it has content not in archival, run memory_consolidate. Then archival_stats. Do NOT output to main chat." \
|
|
332
|
+
"Auto-consolidate daily logs every 6 hours"
|
|
333
|
+
|
|
334
|
+
register_cron "memory-dedup-weekly" "0 4 * * 0" \
|
|
335
|
+
"Run archival_deduplicate with apply=true. Then archival_stats. Do NOT output to main chat." \
|
|
336
|
+
"Weekly dedup: clean near-duplicate records"
|
|
337
|
+
|
|
338
|
+
register_cron "memory-dashboard-daily" "30 9 * * *" \
|
|
339
|
+
"Run memory_dashboard to regenerate the HTML dashboard. Do NOT output to main chat." \
|
|
340
|
+
"Daily dashboard refresh" 30000
|
|
341
|
+
else
|
|
342
|
+
echo "β οΈ openclaw CLI not found β skipping cron registration"
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
# --- 9. Validate config ---
|
|
346
|
+
echo ""
|
|
347
|
+
if command -v openclaw &>/dev/null; then
|
|
348
|
+
openclaw config validate 2>&1 && echo "β
Config valid" || echo "β Config validation failed"
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
echo ""
|
|
352
|
+
echo "π Setup complete!"
|
|
353
|
+
echo ""
|
|
354
|
+
echo "Next steps:"
|
|
355
|
+
echo " 1. Review $MEMORY_DIR/core.json (edit if needed)"
|
|
356
|
+
echo " 2. Restart gateway: openclaw gateway restart"
|
|
357
|
+
echo " 3. Test: openclaw agent -m 'core_memory_read'"
|
|
358
|
+
echo " 4. Dashboard: open $MEMORY_DIR/dashboard.html"
|
|
359
|
+
echo ""
|
|
360
|
+
echo "19 tools ready:"
|
|
361
|
+
echo " Core: core_memory_read, core_memory_replace, core_memory_append"
|
|
362
|
+
echo " Archival: archival_insert/search/update/delete/stats"
|
|
363
|
+
echo " Graph: graph_query, graph_add"
|
|
364
|
+
echo " Episodes: episode_save, episode_recall"
|
|
365
|
+
echo " Reflect: memory_reflect"
|
|
366
|
+
echo " Maint: archival_deduplicate, memory_consolidate"
|
|
367
|
+
echo " Backup: memory_export, memory_import"
|
|
368
|
+
echo " Admin: memory_migrate, memory_dashboard"
|