From 9ab2667aa39a51af96ceac597598556a70f2b1f2 Mon Sep 17 00:00:00 2001 From: dtzp555 Date: Sun, 15 Mar 2026 14:11:14 +1000 Subject: [PATCH] feat: upgrade from skill to lifecycle plugin (v2.0.0) Replace prompt-based skill approach with OpenClaw lifecycle hooks (before_agent_start, agent_end, before_compaction, before_reset). State injection now happens automatically at the hook level, making it model-agnostic and reliable across all providers. - Add index.js with 5 lifecycle hooks - Add openclaw.plugin.json manifest - Rewrite post-install.sh to handle plugin installation - Update README with plugin architecture docs Co-Authored-By: Claude Opus 4.6 --- README.md | 241 +++++++++++++++-------------------- index.js | 269 ++++++++++++++++++++++++++++++++++++++++ openclaw.plugin.json | 32 +++++ scripts/post-install.sh | 143 +++++++++++++-------- 4 files changed, 495 insertions(+), 190 deletions(-) create mode 100644 index.js create mode 100644 openclaw.plugin.json diff --git a/README.md b/README.md index 764e13b..b8feb52 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,106 @@ # memory-continuity -**Current release:** `v0.3.0-probe` +**Current release:** `v2.0.0` -OpenClaw continuity package for **short-term working continuity** — currently shipped as: -- a **skill** (`SKILL.md`) for behavior contract / fallback recovery -- a **lifecycle plugin probe** (`plugin/lifecycle-prototype.ts`) for validating the primary runtime path - -Its goal is to let an agent recover structured in-flight work state after `/new`, reset, gateway interruption, model fallback, or compaction. +OpenClaw **lifecycle plugin** for short-term working continuity. Preserves structured in-flight work state across `/new`, reset, gateway restarts, model fallback, and context compaction. ## What problem does this solve? -OpenClaw already preserves a lot: -- transcripts -- compaction summaries -- memory files -- session memory search - -But those do not always answer the most operational question: +OpenClaw already preserves transcripts, compaction summaries, memory files, and session memory search. But those don't always answer the most operational question: > What were we doing right now, where did we stop, and what should happen next? -That is the problem this skill solves. +That is the problem this plugin solves. **One-line summary:** - long-term memory = what you know - memory continuity = what you are doing right now -## Current architecture stance +## How it works -> **Alpha support boundary (`v0.3.0-probe`)** -> -> Currently validated: -> - resident subagent startup continuity -> -> Not currently supported / not yet validated for reliable recovery: -> - Discord main/channel/thread continuity -> +The plugin uses OpenClaw lifecycle hooks to **automatically** save and restore working state — no model cooperation needed. -This repository should now be understood as a **continuity package**, not just a standalone skill. +| Hook | What it does | +|---|---| +| `before_agent_start` | Reads `memory/CURRENT_STATE.md` and injects it into the agent's system context | +| `before_compaction` | Injects state before compaction so it survives context compression | +| `before_reset` | Archives current state to `session_archive/` before `/new` | +| `agent_end` | Auto-extracts working state from conversation if no explicit state exists | +| `session_end` | Ensures `CURRENT_STATE.md` exists for future sessions | -### Included forms -- **Skill** = behavior contract / fallback implementation / human-readable protocol -- **Lifecycle plugin probe** = current runtime experiment for the primary architecture - -The intended primary runtime path is a **standard lifecycle plugin** that can -improve startup, `/new`, and compaction continuity **without consuming -OpenClaw’s exclusive `contextEngine` slot**. - -A ContextEngine implementation remains a **future option**, not the default -v1 direction. +Because state injection happens at the hook level (before the model sees anything), it works with **any model** — GPT-4o, MiniMax, Claude, etc. ## Quick Start ### Install ```bash -cd ~/.openclaw/workspace/main/skills/ +# Clone the plugin git clone https://github.com/dtzp555-max/memory-continuity.git + +# Run the installer cd memory-continuity bash scripts/post-install.sh ``` +The installer will: +1. Copy the plugin to `~/.openclaw/extensions/memory-continuity/` +2. Add the plugin entry to `~/.openclaw/openclaw.json` +3. Restart the gateway + No npm install, no API keys, no external database. -> **Why `post-install.sh`?** -> OpenClaw caches each session's skill list in a `skillsSnapshot`. If you -> install this skill while the gateway is stopped (or restart the gateway -> after cloning), existing sessions won't detect the new skill until their -> snapshot is cleared. The post-install script handles this automatically. -> New sessions created after install are unaffected. - -### Test the current skill version - -1. Start a multi-step task with your agent -2. Make a few concrete decisions -3. Check whether `memory/CURRENT_STATE.md` exists and reflects the work state -4. Trigger `/new` -5. Ask a recovery question like: - - “刚才我们说到哪了” - - “continue” - - “what were we doing” - -A good recovery should surface the current objective / step / next action, -not generic small talk. - -### Run the doctor +### Verify ```bash -python3 scripts/continuity_doctor.py --workspace ~/.openclaw/workspace +openclaw gateway restart 2>&1 | grep memory-continuity +# Should show: [memory-continuity] Plugin registered successfully ``` -## How the current skill version works +### Test -The skill defines a discipline around one file: -- `memory/CURRENT_STATE.md` +1. Tell your agent something memorable (e.g., "I'll tell you a secret: Ethan is super kid") +2. Send `/new` to reset the session +3. Ask "what was the secret?" or "我们刚才聊到哪了" +4. The agent should immediately surface the recovered state -That file is the short-term workbench for active work. It is: -- overwritten, not appended -- intentionally short -- structured for fast recovery +## Configuration -### The checkpoint shape +The plugin works with zero configuration. Optional settings in `openclaw.json`: + +```json +{ + "plugins": { + "entries": { + "memory-continuity": { + "enabled": true, + "hooks": { + "allowPromptInjection": true + }, + "config": { + "maxStateLines": 50, + "archiveOnNew": true, + "autoExtract": true + } + } + } + } +} +``` + +| Option | Default | Description | +|---|---|---| +| `maxStateLines` | `50` | Max lines for CURRENT_STATE.md | +| `archiveOnNew` | `true` | Archive state to `session_archive/` before `/new` | +| `autoExtract` | `true` | Auto-extract state from conversation at session end | + +## The checkpoint file + +The plugin maintains one file: `$WORKSPACE/memory/CURRENT_STATE.md` ```markdown # Current State -> Last updated: 2026-03-12T14:30:00Z +> Last updated: 2026-03-15T14:00:00Z ## Objective Build the user authentication module @@ -114,7 +110,6 @@ Completed JWT token generation, starting refresh endpoint ## Key Decisions - Using RS256 for token signing (user approved) -- Token expiry: 15 minutes access, 7 days refresh ## Next Action Implement POST /auth/refresh endpoint @@ -126,103 +121,67 @@ None None ``` -## Recovery rules +This file is: +- **Overwritten**, not appended (it's a checkpoint, not a journal) +- **Human-readable** plain markdown +- **Portable** — just copy the file to backup or migrate +- **Model-agnostic** — injected via hooks, not dependent on model behavior -In recovery scenarios, the skill expects the agent to prioritize: -- Objective -- Current Step -- Next Action -- Blockers -- Unsurfaced Results +## Backup & Migration -A generic greeting should **not** outrank recovery state when the checkpoint -contains active work. +```bash +# Backup +cp $WORKSPACE/memory/CURRENT_STATE.md /backup/ -## Relationship to native OpenClaw features +# Migrate to another machine +scp -r ~/.openclaw/extensions/memory-continuity/ newhost:~/.openclaw/extensions/ +scp $WORKSPACE/memory/CURRENT_STATE.md newhost:$WORKSPACE/memory/ +``` -### Native OpenClaw already handles -- transcript persistence -- compaction -- pre-compaction `memoryFlush` -- session memory search -- system prompt/bootstrap assembly +No database, no vector embeddings, no API keys to transfer. -### memory-continuity adds -- a **structured working-state checkpoint** -- explicit short-term recovery fields -- a deterministic place to look for active work state -- explicit handling for `Unsurfaced Results` +## Architecture: Plugin vs Skill -### Important boundary -Session memory search is useful for: -- “what did we discuss before?” -- “what decision was mentioned in a prior session?” +Previous versions (v0.x) shipped as a **skill** — a markdown file that asked the model to read/write `CURRENT_STATE.md`. This was unreliable because models could ignore the instructions. -Memory continuity is for: -- “what are we doing right now?” -- “where did we stop?” -- “what should happen next?” +v2.0 is a **lifecycle plugin** that uses OpenClaw hooks. The key difference: + +| | Skill (v0.x) | Plugin (v2.0) | +|---|---|---| +| State injection | Model must read the file | Hook injects automatically | +| State saving | Model must write the file | Hook saves automatically | +| Model dependency | Requires model cooperation | Model-agnostic | +| Reliability | Varies by model | Consistent | + +The skill (`SKILL.md`) is retained as documentation and fallback protocol. ## Repository layout ```text memory-continuity/ -├── SKILL.md # Behavior contract / skill definition -├── skill.json # Skill metadata for OpenClaw loader -├── _meta.json # Workspace skill registry metadata +├── openclaw.plugin.json # Plugin manifest +├── index.js # Plugin entry point (hooks) +├── SKILL.md # Behavior contract / protocol docs ├── README.md ├── LICENSE ├── plugin/ -│ └── lifecycle-prototype.ts # Phase 2 probe / not production yet +│ └── lifecycle-prototype.ts # Original prototype (reference) ├── references/ │ ├── template.md │ ├── doctor-spec.md │ └── phase2-hook-validation.md └── scripts/ - ├── post-install.sh # Clears stale skill snapshots - └── continuity_doctor.py -``` - -At runtime, the skill works primarily with: - -```text -$WORKSPACE/ -└── memory/ - ├── CURRENT_STATE.md - └── session_archive/ + ├── post-install.sh # Automated installer + └── continuity_doctor.py # Health check ``` ## Design principles -1. **Files are the source of truth** -2. **Structured checkpoint beats free-form recollection** -3. **Recovery must prefer truth over confident guessing** -4. **This complements native OpenClaw memory; it does not replace it** -5. **Read access is helpful, but should not be the only long-term path** -6. **The primary plugin direction should coexist with other ecosystem plugins such as `lossless-claw`** - -## Current roadmap - -### Phase 1 -Strengthen the current skill version: -- tighten recovery behavior -- tighten checkpoint discipline -- improve doctor and docs - -### Phase 2 -Build and validate a **standard lifecycle plugin** as the primary runtime path: -- startup recovery behavior -- `/new` checkpointing -- compaction-boundary checkpointing -- end-of-run safety writes -- hook validation in real resident subagent sessions - -### Future option -Evaluate a ContextEngine variant later only if the slot tradeoff is justified. - -## Release notes - -See `CHANGELOG.md` for the current packaged milestone history. +1. **Files are the source of truth** — plain markdown, no database +2. **Hooks over prompts** — don't rely on model behavior +3. **Zero external dependencies** — no API keys, no vector DB +4. **Portable and backupable** — `cp` is your backup tool +5. **Complements native OpenClaw memory** — does not replace it ## License diff --git a/index.js b/index.js new file mode 100644 index 0000000..ee42e47 --- /dev/null +++ b/index.js @@ -0,0 +1,269 @@ +import fs from "node:fs"; +import path from "node:path"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const PLACEHOLDER_VALUES = new Set([ + "", "none", "n/a", "na", "idle", + "[one sentence: what are we trying to accomplish]", + "[exactly what should happen next]", +]); + +const STATE_TEMPLATE = `# Current State +> Last updated: ${new Date().toISOString()} + +## Objective +None + +## Current Step +None + +## Key Decisions +- None + +## Next Action +None + +## Blockers +None + +## Unsurfaced Results +None +`; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function resolveStatePath(workspaceDir) { + if (!workspaceDir) return null; + return path.join(workspaceDir, "memory", "CURRENT_STATE.md"); +} + +function readFile(filePath) { + try { return fs.readFileSync(filePath, "utf8"); } catch { return null; } +} + +function writeFile(filePath, content) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content, "utf8"); +} + +function extractSection(md, heading) { + const re = new RegExp(`^##\\s+${heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`, "m"); + return (md.match(re)?.[1] ?? "").trim(); +} + +function isMeaningful(value) { + return !PLACEHOLDER_VALUES.has(value.trim().toLowerCase()); +} + +function buildSnapshot(md) { + const objective = extractSection(md, "Objective"); + if (!isMeaningful(objective)) return null; + + const fields = { + "Objective": objective, + "Current Step": extractSection(md, "Current Step") || "unknown", + "Key Decisions": extractSection(md, "Key Decisions") || "None", + "Next Action": extractSection(md, "Next Action") || "unknown", + "Blockers": extractSection(md, "Blockers") || "None", + "Unsurfaced Results": extractSection(md, "Unsurfaced Results") || "None", + }; + const updated = md.match(/^> Last updated:\s*(.+)$/m)?.[1]?.trim() ?? "unknown"; + + return [ + "=== CONTINUITY RECOVERY ===", + ...Object.entries(fields).map(([k, v]) => `${k}: ${v}`), + `Last Updated: ${updated}`, + "=== END RECOVERY ===", + ].join("\n"); +} + +function archiveState(workspaceDir, md) { + const now = new Date(); + const pad = (n) => String(n).padStart(2, "0"); + const stamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}`; + const archiveDir = path.join(workspaceDir, "memory", "session_archive"); + fs.mkdirSync(archiveDir, { recursive: true }); + fs.writeFileSync(path.join(archiveDir, `${stamp}.md`), md, "utf8"); +} + +function extractStateFromMessages(messages) { + if (!messages || messages.length === 0) return null; + + // Walk messages backwards to find the last meaningful exchange + const userMessages = []; + const assistantMessages = []; + + for (const msg of messages) { + const role = msg?.role; + const content = typeof msg?.content === "string" + ? msg.content + : Array.isArray(msg?.content) + ? msg.content.filter(b => b?.type === "text").map(b => b.text).join("\n") + : null; + if (!content) continue; + if (role === "user") userMessages.push(content); + if (role === "assistant") assistantMessages.push(content); + } + + if (userMessages.length === 0 && assistantMessages.length === 0) return null; + + // Build a simple state from the conversation tail + const lastUser = userMessages[userMessages.length - 1] || ""; + const lastAssistant = assistantMessages[assistantMessages.length - 1] || ""; + + // Truncate to keep it compact + const truncate = (s, max = 200) => s.length > max ? s.slice(0, max) + "..." : s; + + return `# Current State +> Last updated: ${new Date().toISOString()} + +## Objective +${truncate(lastUser, 300)} + +## Current Step +Conversation ended after ${messages.length} messages + +## Key Decisions +- Auto-extracted from conversation + +## Next Action +Continue from where we left off + +## Blockers +None + +## Unsurfaced Results +${truncate(lastAssistant, 500)} +`; +} + +// --------------------------------------------------------------------------- +// Plugin Definition +// --------------------------------------------------------------------------- + +const plugin = { + id: "memory-continuity", + name: "Memory Continuity", + + register(api) { + const log = api.logger || console; + const getWorkspace = () => api.runtime?.workspaceDir; + const getConfig = () => api.pluginConfig || {}; + + // ------------------------------------------------------------------ + // HOOK 1: before_agent_start — inject recovered state into context + // ------------------------------------------------------------------ + api.on("before_agent_start", async (_event, _ctx) => { + const ws = getWorkspace(); + const statePath = resolveStatePath(ws); + if (!statePath) return; + + const md = readFile(statePath); + if (!md) return; + + const snapshot = buildSnapshot(md); + if (!snapshot) return; + + log.info?.("[memory-continuity] Injecting recovered state into context"); + + return { + prependSystemContext: + snapshot + "\n\n" + + "IMPORTANT: The above is recovered working state from a previous session. " + + "If the user appears to be resuming work, surface this state immediately " + + "before any generic greeting. This is a continuity requirement, not optional.", + }; + }, { priority: 10 }); + + // ------------------------------------------------------------------ + // HOOK 2: before_compaction — inject state so it survives compaction + // ------------------------------------------------------------------ + api.on("before_compaction", async (_event, _ctx) => { + const ws = getWorkspace(); + const statePath = resolveStatePath(ws); + if (!statePath) return; + + const md = readFile(statePath); + if (!md) return; + + const snapshot = buildSnapshot(md); + if (!snapshot) return; + + log.info?.("[memory-continuity] Injecting state before compaction"); + + return { + prependSystemContext: snapshot, + }; + }, { priority: 10 }); + + // ------------------------------------------------------------------ + // HOOK 3: before_reset (/new) — archive current state + // ------------------------------------------------------------------ + api.on("before_reset", async (_event, _ctx) => { + const ws = getWorkspace(); + const config = getConfig(); + if (!ws || config.archiveOnNew === false) return; + + const statePath = resolveStatePath(ws); + if (!statePath) return; + + const md = readFile(statePath); + if (!md) return; + + if (buildSnapshot(md)) { + archiveState(ws, md); + log.info?.("[memory-continuity] Archived state before /new"); + } + }, { priority: 10 }); + + // ------------------------------------------------------------------ + // HOOK 4: agent_end — extract and save working state + // ------------------------------------------------------------------ + api.on("agent_end", async (event, _ctx) => { + const ws = getWorkspace(); + const config = getConfig(); + if (!ws || config.autoExtract === false) return; + + const statePath = resolveStatePath(ws); + if (!statePath) return; + + // If there's already a meaningful state file, don't overwrite with + // auto-extracted content (agent or user wrote it explicitly) + const existing = readFile(statePath); + if (existing && buildSnapshot(existing)) { + log.info?.("[memory-continuity] Existing state is meaningful, skipping auto-extract"); + return; + } + + // Extract from conversation messages + const newState = extractStateFromMessages(event?.messages); + if (!newState) return; + + writeFile(statePath, newState); + log.info?.("[memory-continuity] Auto-extracted state from conversation"); + }, { priority: 90 }); // low priority, run after other hooks + + // ------------------------------------------------------------------ + // HOOK 5: session_end — ensure state file exists + // ------------------------------------------------------------------ + api.on("session_end", async (_event, _ctx) => { + const ws = getWorkspace(); + const statePath = resolveStatePath(ws); + if (!statePath) return; + + if (!readFile(statePath)) { + writeFile(statePath, STATE_TEMPLATE); + log.info?.("[memory-continuity] Created initial CURRENT_STATE.md"); + } + }, { priority: 90 }); + + log.info?.("[memory-continuity] Plugin registered successfully"); + }, +}; + +export default plugin; diff --git a/openclaw.plugin.json b/openclaw.plugin.json new file mode 100644 index 0000000..9ff6ad9 --- /dev/null +++ b/openclaw.plugin.json @@ -0,0 +1,32 @@ +{ + "id": "memory-continuity", + "name": "Memory Continuity", + "description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.", + "version": "2.0.0", + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "maxStateLines": { + "type": "number", + "default": 50, + "description": "Max lines for CURRENT_STATE.md before auto-compress warning" + }, + "archiveOnNew": { + "type": "boolean", + "default": true, + "description": "Archive CURRENT_STATE.md to session_archive/ on /new" + }, + "autoExtract": { + "type": "boolean", + "default": true, + "description": "Auto-extract working state from conversation at agent_end" + } + } + }, + "uiHints": { + "maxStateLines": { "label": "Max state file lines", "help": "Keep checkpoint small for fast recovery" }, + "archiveOnNew": { "label": "Archive on /new", "help": "Save a snapshot before session reset" }, + "autoExtract": { "label": "Auto-extract state", "help": "Automatically save working state when conversation ends" } + } +} diff --git a/scripts/post-install.sh b/scripts/post-install.sh index 06f8a85..a1401a5 100755 --- a/scripts/post-install.sh +++ b/scripts/post-install.sh @@ -1,71 +1,116 @@ #!/usr/bin/env bash -# post-install.sh — Flush stale skill snapshots after installing memory-continuity +# post-install.sh — Install memory-continuity as an OpenClaw lifecycle plugin # -# OpenClaw caches a "skillsSnapshot" per session. If this skill was installed -# while the gateway was stopped (or after a gateway restart), the file watcher -# won't detect the new SKILL.md and existing sessions will never refresh. -# -# This script clears stale snapshots so every session rebuilds on next turn. +# This script: +# 1. Copies the plugin to ~/.openclaw/extensions/memory-continuity/ +# 2. Adds the plugin entry to openclaw.json (if not present) +# 3. Restarts the gateway # # Usage: -# bash scripts/post-install.sh [--agent-id ] -# -# Options: -# --agent-id Agent ID (default: main) +# bash scripts/post-install.sh # # Safe to run multiple times (idempotent). set -euo pipefail -AGENT_ID="main" -while [[ $# -gt 0 ]]; do - case "$1" in - --agent-id) AGENT_ID="$2"; shift 2 ;; - *) echo "Unknown option: $1" >&2; exit 1 ;; - esac -done - OPENCLAW_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}" -SESSIONS_FILE="$OPENCLAW_DIR/agents/$AGENT_ID/sessions/sessions.json" +EXTENSIONS_DIR="$OPENCLAW_DIR/extensions" +PLUGIN_DIR="$EXTENSIONS_DIR/memory-continuity" +CONFIG_FILE="$OPENCLAW_DIR/openclaw.json" -if [[ ! -f "$SESSIONS_FILE" ]]; then - echo "No sessions file found at $SESSIONS_FILE — nothing to do." - exit 0 +# Resolve the repo root (parent of scripts/) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +echo "=== memory-continuity plugin installer ===" +echo "" + +# Step 1: Copy plugin files to extensions directory +echo "[1/3] Installing plugin to $PLUGIN_DIR ..." +mkdir -p "$EXTENSIONS_DIR" + +# Remove old installation if exists +if [[ -d "$PLUGIN_DIR" || -L "$PLUGIN_DIR" ]]; then + rm -rf "$PLUGIN_DIR" + echo " Removed previous installation" fi -# Count and clear stale skillsSnapshot entries -BEFORE=$(python3 -c " +# Copy essential plugin files (not the entire repo) +mkdir -p "$PLUGIN_DIR" +for f in index.js openclaw.plugin.json SKILL.md; do + if [[ -f "$REPO_DIR/$f" ]]; then + cp "$REPO_DIR/$f" "$PLUGIN_DIR/" + fi +done +echo " Copied plugin files" + +# Step 2: Add plugin entry to openclaw.json +echo "[2/3] Configuring openclaw.json ..." +if [[ ! -f "$CONFIG_FILE" ]]; then + echo " WARNING: $CONFIG_FILE not found. Skipping config update." + echo " You'll need to manually add the plugin entry." +else + # Check if memory-continuity entry already exists + if python3 -c " import json, sys -with open('$SESSIONS_FILE') as f: +with open('$CONFIG_FILE') as f: data = json.load(f) -count = sum(1 for v in data.values() if isinstance(v, dict) and 'skillsSnapshot' in v) -print(count) -" 2>/dev/null || echo "0") - -if [[ "$BEFORE" == "0" ]]; then - echo "No stale skill snapshots found — all sessions will discover skills normally." - exit 0 -fi - -python3 -c " +entries = data.get('plugins', {}).get('entries', {}) +if 'memory-continuity' in entries: + print('exists') + sys.exit(0) +else: + sys.exit(1) +" 2>/dev/null; then + echo " Plugin entry already exists in config" + else + # Add the plugin entry + python3 -c " import json -path = '$SESSIONS_FILE' +path = '$CONFIG_FILE' with open(path) as f: data = json.load(f) -for key in data: - if isinstance(data[key], dict) and 'skillsSnapshot' in data[key]: - del data[key]['skillsSnapshot'] +if 'plugins' not in data: + data['plugins'] = {} +if 'entries' not in data['plugins']: + data['plugins']['entries'] = {} +data['plugins']['entries']['memory-continuity'] = { + 'enabled': True, + 'hooks': { + 'allowPromptInjection': True + }, + 'config': { + 'maxStateLines': 50, + 'archiveOnNew': True, + 'autoExtract': True + } +} with open(path, 'w') as f: json.dump(data, f, indent=2) -" - -echo "Cleared skillsSnapshot from $BEFORE session(s) in $SESSIONS_FILE" -echo "All sessions will rebuild their skill list on next turn." - -# Restart gateway if running, so file watcher picks up the new SKILL.md -if command -v openclaw &>/dev/null; then - if openclaw gateway status 2>&1 | grep -q "running"; then - echo "Restarting gateway to activate file watcher..." - openclaw gateway restart 2>/dev/null && echo "Gateway restarted." || echo "Gateway restart failed — try manually: openclaw gateway restart" +print(' Added plugin entry to config') +" 2>/dev/null || echo " WARNING: Could not update config automatically. Add manually." fi fi + +# Step 3: Restart gateway +echo "[3/3] Restarting gateway ..." +if command -v openclaw &>/dev/null; then + if openclaw gateway status 2>&1 | grep -q "running"; then + openclaw gateway restart 2>/dev/null && echo " Gateway restarted" || echo " Gateway restart failed — try: openclaw gateway restart" + else + echo " Gateway not running — start it with: openclaw gateway start" + fi +else + echo " openclaw command not found — install OpenClaw first" +fi + +echo "" +echo "=== Installation complete ===" +echo "" +echo "Verify with:" +echo " openclaw gateway restart 2>&1 | grep memory-continuity" +echo "" +echo "Test with:" +echo " 1. Tell your agent something memorable" +echo " 2. Send /new" +echo " 3. Ask what you told it"