@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/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"