@clawhub-ngplateform-da53bbcd7b
Give an AI agent persistent semantic memory that survives restarts and compaction. Captures structured observations from tool calls, summarizes sessions (LLM...
---
name: claw-mem2db
description: Standalone OpenClaw skill that automatically (and on demand) captures the agent's chat conversations and tool-call chains into a local SQLite + FTS5 database, then replays them as token-budgeted context on the next prompt — so the agent doesn't forget across restarts, compaction, or new sessions. Captures `message_received` / `message_sent` (chat, with explicit cues like "remember this" / "note this" promoted to `decision` observations; bilingual EN + zh-CN cue dictionaries built in) and `after_tool_call` (tool); reads on `before_prompt_build` via hybrid recall (FTS5 search on the latest user message merged with the recent tail). Use when the user wants long-lived agent memory, wants to search past chats and tool-call observations, wants explicit "remember this" cues to stick, wants memory to survive restarts / compaction, or wants to export / import memory across machines. Zero-config — `openclaw plugins install @chainofclaw/claw-mem` is sufficient; works fully on its own with no chain interaction and no external services. Exposes agent-callable tools (`mem-search`, `mem-status`, `mem-forget`) and a CLI namespace (`openclaw mem ...`). Session summaries default to the OpenClaw inference surface (`openclaw infer model run`) so no extra API key is needed. Optional companion: install `coc-soul` alongside to upload memory snapshots on-chain, register a DID identity, mirror to P2P decentralized storage, and recover from a different device after corruption — together they enable "digital immortality" / "silicon-based persistence" for an AI agent.
version: 2.3.1
metadata:
openclaw:
homepage: https://www.npmjs.com/package/@chainofclaw/claw-mem
primaryEnv: CLAW_MEM_DATA_DIR
requires:
bins:
- node
anyBins:
- claw-mem
- openclaw
install:
- kind: node
package: "@chainofclaw/claw-mem"
version: "2.3.1"
bins:
- claw-mem
---
# claw-mem2db — persistent semantic memory for agents
A standalone, plug-in memory layer for OpenClaw agents. claw-mem2db turns every conversation and every tool call into a queryable history — automatic capture, local SQLite + FTS5 storage, hybrid recall on the next prompt. **Use it on its own** for long-lived agent memory; **pair it with `coc-soul`** for on-chain backup, DID identity, P2P storage, and cross-device recovery — i.e. digital / silicon-based persistence.
> **Naming note.** This ClawHub skill is published as **`claw-mem2db`** because the bare `claw-mem` slug was taken. The underlying artifacts keep the original names:
>
> - **npm package:** [`@chainofclaw/claw-mem`](https://www.npmjs.com/package/@chainofclaw/claw-mem)
> - **OpenClaw plugin id:** `claw-mem` (auto-loaded after `openclaw plugins install`)
> - **Standalone CLI binary:** `claw-mem` — *only present if you separately ran `npm i -g @chainofclaw/claw-mem`*. `openclaw plugins install` does **not** put it on your PATH.
>
> Inside OpenClaw, you don't need the standalone bin. Use the agent tools or `openclaw mem …` (covered below).
## Install
The plugin ships on npm as `@chainofclaw/claw-mem`. The full real-world install command is:
```bash
openclaw plugins install @chainofclaw/claw-mem --dangerously-force-unsafe-install --force
```
Two flags are needed in practice — neither is "skip safety checks for fun":
- **`--dangerously-force-unsafe-install`** — claw-mem legitimately uses `child_process` (the `openclaw` summarizer mode spawns `openclaw infer model run`; bootstrap helpers shell out). OpenClaw's static scan flags any plugin that imports `child_process`, so this flag is required to whitelist a known-safe consumer. It is **not** disabling sandboxing of the running plugin.
- **`--force`** — allows reinstalling/upgrading over an existing extension directory without the "already installed" abort.
If `openclaw plugins install` itself errors out (npm cache `EACCES`, registry timeout, etc.), fall back to the in-place tarball install — see the appendix in `references/cli.md`.
### After install: writable data dir
claw-mem opens its SQLite DB on first activation. The data dir auto-resolves (1.1.17+):
1. `config.dataDir` (per-instance plugin config — set this when the defaults below don't work)
2. `$CLAW_MEM_DATA_DIR` (operator env override)
3. `$OPENCLAW_STATE_DIR/claw-mem` (OpenClaw's standard sandboxed state-dir)
4. `~/.claw-mem` (standalone default)
The first writable candidate wins. If every candidate fails, the plugin throws an actionable error — it does **not** silently fall back to `/tmp`.
**Sandboxed hosts (Docker, restricted-uid runners) commonly hit `EACCES` on `~/.claw-mem`** because `$HOME` is read-only or owned by a different uid. The fix is to point claw-mem at an explicitly-writable directory in `~/.openclaw/openclaw.json`:
```jsonc
{
"plugins": {
"entries": {
"claw-mem": {
"config": {
"dataDir": "/home/node/.openclaw/state/claw-mem"
}
}
}
}
}
```
Use whatever absolute path is writable in your environment (`~/.openclaw/state/claw-mem` is a good default since OpenClaw already owns that tree). Restart the gateway after editing. Verify with `openclaw mem status` — if it returns counts (even 0/0/0), the DB opened successfully.
## Zero-config (after the install + dataDir step)
**No further setup needed.** No chain interaction, no external services, no required env vars beyond the dataDir override above (and only when the default isn't writable).
- **Session hooks auto-register**: every tool call becomes a candidate observation; sessions are summarized on close.
- **Reads work immediately** once any observations have been captured.
- **`CLAW_MEM_DATA_DIR`** is the only env knob and it has a sensible default.
**Memory-only mode is expected, not a degradation.** The gateway log line `[claw-mem] Loaded (memory layer only)` is intentional — claw-mem deliberately owns *only* the memory layer. If you've also installed `coc-node` and/or `coc-soul`, each registers its own commands/tools under its own root, so the three plugins cleanly compose without stepping on each other.
**Session summaries** default to `openclaw` mode when loaded inside OpenClaw (1.1.16+). Each session-end summary is generated by spawning `openclaw infer model run --json` — the summarizer reuses whatever inference provider the host's OpenClaw agent is already authenticated for. **No claw-mem-specific API key, no extra env var, no model picker.** If the spawn fails (openclaw not on PATH, no provider auth) the summarizer falls back to a heuristic stringifier so observations still get summaries. Override with `summarizer.mode: "heuristic"` to skip LLM calls entirely, or `summarizer.mode: "llm"` + `summarizer.llm.apiKey` (or `ANTHROPIC_API_KEY` env) to talk to Anthropic directly via the bundled SDK.
## Mental model
Two streams feed the same store:
- **Chat** — every user message (and optionally every assistant message) is run through a lightweight extractor that flags explicit cues (e.g. "remember this", "note this", "for the record" — full bilingual EN + zh-CN dictionary in the cue config) as `decision` observations and preference cues (e.g. "I prefer", "from now on", "always use") as `learning`. Plain chat lands as a low-signal `discovery`.
- **Tool calls** — every tool the agent runs becomes a typed observation (discovery / decision / pattern / learning / issue / change / explanation), capturing what was read, edited, searched, or executed.
Both streams write into a single local SQLite database with FTS5 full-text search. On each new prompt, claw-mem assembles a token-budgeted **memory context** via hybrid recall — an FTS5 search on the latest user message merged with the recent tail — and injects it into the next prompt. Sessions are summarized on close (the host's OpenClaw inference provider does the summarization, no extra API key needed).
What the agent gets in return:
- No more "I forgot what we were doing" between sessions or after compaction
- Searchable history of decisions, preferences, and the rationale behind them
- Explicit "remember this" cues in chat reliably stick as `decision` observations (bilingual EN + zh-CN cue dictionaries built in)
- All memory stays local by default — no network, no chain, no external service
## Optional companions: from local memory to digital immortality
claw-mem2db is **complete on its own**. It needs nothing else to capture, search, or inject memory. But two adjacent skills extend the agent in directions claw-mem deliberately doesn't:
| Skill | What it adds on top of claw-mem | When to install |
|---|---|---|
| [coc-soul](https://clawhub.ai/ngplateform/coc-soul) | On-chain memory snapshots, DID identity, P2P / IPFS-backed storage, cross-device recovery & resurrection | When you want the agent to survive a host dying, a disk failing, or a move to a different machine — i.e. **digital / silicon-based persistence** |
| [coc-node](https://clawhub.ai/ngplateform/coc-node) | Running a COC blockchain node so the agent participates in the network instead of just consuming it | When you want the agent to be a first-class peer rather than a guest |
The plugins are fully decoupled: claw-mem doesn't know whether coc-soul is installed, and works the same way either way. Installing coc-soul alongside claw-mem creates the immortality story (memory captured locally → snapshotted to chain + IPFS → recoverable from a fresh host using the agent's DID), but you can adopt it later without changing anything in claw-mem.
## How to use it
### From inside an agent loop (preferred)
The plugin registers three agent-callable tools. **Use these — not shell commands — when you're an agent answering the user.** No PATH lookup, no plugin discovery, no shell context required.
| Tool | Parameters | Returns |
|---|---|---|
| `mem-search` | `query` (required), `limit?`, `type?` (`discovery` \| `decision` \| `pattern` \| `learning` \| `issue` \| `change` \| `explanation`) | Matching observations from FTS5 index |
| `mem-status` | none | Stats: observation/summary/session counts, agents, DB path, tokenBudget |
| `mem-forget` | `sessionId` (required) | Confirms deletion of that session's observations |
Typical agent-side patterns:
- Before deciding how to approach a familiar-feeling task, call `mem-search` with the topic — past decisions and their rationale will surface.
- Before claiming "I'm not sure about X," call `mem-search` for X.
- When the user says "forget that whole session" or you notice a session is polluting search results, call `mem-forget` with the session id from `mem-status`.
### From a human shell (CLI)
When operating from a terminal — install/upgrade scripts, ops checks, manual cleanup — use the OpenClaw CLI namespace. Every memory operation is reachable as `openclaw mem <subcommand>`.
```bash
openclaw mem search "checkpoint" # FTS5 keyword/phrase search
openclaw mem search "..." --type decision --limit 20 --json
openclaw mem status # DB stats; --json for machine output
openclaw mem peek # preview the context that would be injected
openclaw mem forget <sessionId> # delete one session's observations
openclaw mem prune --older-than 90 # drop observations older than 90 days
openclaw mem prune --before 2025-01-01 # explicit cutoff
openclaw mem export memory.json # dump observations to JSON
openclaw mem import memory.json # restore from JSON
```
Sibling subtrees:
```bash
openclaw mem config get|set|list|path # plugin config (persisted to ~/.claw-mem/config.json)
openclaw mem db vacuum|migrate-status|size # SQLite maintenance
openclaw mem version # plugin version
```
## Configuration knobs worth knowing
- `tokenBudget` (default 8000) — how much of the next prompt goes to memory context
- `maxObservations` / `maxSummaries` (50 / 10) — hard caps on what's considered for injection
- `skipTools` — tools the observer skips entirely (defaults exclude `TodoWrite`, `AskUserQuestion`, `Skill` because they're usually noisy meta-tools)
- `dedupWindowMs` (30000) — de-duplicate observations with the same content hash within this window
- `summarizer.mode` — `openclaw` (default inside OpenClaw — spawns `openclaw infer model run`), `heuristic` (no LLM), or `llm` (direct Anthropic SDK with own apiKey)
- `summarizer.openclaw.model` — pin a specific provider/model for summary calls (default: let OpenClaw pick); `summarizer.openclaw.timeoutMs` defaults to 60000ms
### Chat compaction (2.3.0+) — automatic summarization & space reclamation
By default, chat capture writes one observation per message. Over time the chat tail grows and the agent's tokenBudget gets eaten by raw chitchat. v2.3.0 adds a compactor that rolls batches of chat observations into a single `chat_compaction` summary, and (optionally) hard-deletes low-importance originals to reclaim space.
**Pipeline:**
1. **Capture-time denoising** — emoji-only / pictograph-only turns are dropped entirely (don't even reach the DB). Each captured row gets a heuristic `importance` score (0.0–1.0) computed from cue match, presence of dates / URLs / code / numbers, length, role, and chitchat-token detection.
2. **Trigger** — every `chatMemory.compaction.triggerEvery` chat captures (default 10) the compactor runs in the background. It also fires opportunistically on `agent_end`.
3. **Roll-up** — the compactor pulls uncompacted chat rows (skipping the most-recent `keepRecentRaw`, default 20), passes the batch through the configured summarizer (`openclaw` mode by default — same auth path as session summaries), writes a single `chat_compaction` observation tagged `tool_name="chat_compaction"` with `importance=0.9`, then marks the originals `compacted=1, compacted_into=<id>`.
4. **Prune** — when `deleteCompactedLowValue=true` (default), compacted rows whose `importance < minImportanceToKeep` (default 0.7) are hard-deleted. The most-recent `keepRecentRaw` are always retained regardless of importance.
5. **Recall** — `before_prompt_build` and `mem-search` exclude `compacted=1` rows by default, so the agent reads the compaction summary + the recent tail instead of seeing the same content twice.
**Schema (migration v3, additive — safe on existing DBs):**
| Column | Type | Default | Meaning |
|---|---|---|---|
| `compacted` | INTEGER | `0` | `1` = rolled into a chat_compaction; hidden from default recall / search |
| `importance` | REAL | `0.5` | heuristic score; higher = more worth keeping |
| `compacted_into` | INTEGER | NULL | id of the chat_compaction observation that this row was rolled into (audit trail) |
**Config (under `chatMemory.compaction`):**
- `enabled` (default `true`)
- `triggerEvery` (default `10`) — run a pass every N captures
- `keepRecentRaw` (default `20`) — never compact the most-recent N
- `deleteCompactedLowValue` (default `true`) — hard-delete after compaction
- `minImportanceToKeep` (default `0.7`) — prune threshold
- `idleMs` (default `120000`) — reserved for future idle-based trigger
Set `chatMemory.compaction.enabled = false` to keep raw chat rows forever (audit-heavy use). Set `deleteCompactedLowValue = false` to keep all originals on disk but still benefit from cleaner recall (the compaction summary stays small).
### Chat memory (2.1.0+ / renamed knobs in 2.2.0)
Pure chat sessions used to slip past the observer because capture only fired on `after_tool_call`. v2.1.0 added `message_received` / `message_sent` capture so spoken-only conversations build up memory too. v2.2.0 also exposes the schema in `openclaw.plugin.json` so `openclaw plugins inspect claw-mem` shows both hooks and config keys, and renames the knobs to match the OpenClaw operator-side conventions (breaking — see migration table below). Defaults are conservative: user messages are captured, assistant messages are not.
- `chatMemory.enabled` (default `true`) — master switch for chat capture
- `chatMemory.explicitOnly` (default `false`) — only capture when the message contains an explicit cue ("remember this", "note this", "for the record", "save this", and the equivalent zh-CN tokens shipped in the default cue dictionary); silences everything else
- `chatMemory.minChars` (default `8`) — drop shorter messages as chitchat
- `chatMemory.maxNarrativeChars` (default `500`) — truncate the captured narrative body at this many characters
- `chatMemory.cues.explicit` / `chatMemory.cues.preference` — override the cue dictionaries (defaults ship bilingual EN + zh-CN); preference cues ("I prefer", "from now on", "always use" + zh-CN equivalents) become `learning` observations. To inspect or override the actual default tokens, run `openclaw mem config get chatMemory.cues`
- `chatMemory.captureAssistant` (default `false`) — also capture assistant messages via `message_sent` (use sparingly — high noise)
Chat observations now carry `toolName: "message_received"` (user) or `toolName: "message_sent"` (assistant) — a 1:1 mapping with the originating hook so downstream filters can write `tool_name IN ('message_received','message_sent')` for "all chat" or pull just one direction. Still no schema migration; the existing `tool_name` column carries the value.
**2.1.0 → 2.2.0 migration (config keys):**
| Old (2.1.0) | New (2.2.0) |
|---|---|
| `chatMemory.minLen` | `chatMemory.minChars` |
| `chatMemory.captureAssistantPromises` | `chatMemory.captureAssistant` |
| (hard-coded 500) | `chatMemory.maxNarrativeChars` |
| chat observations had `tool_name: "chat"` | now `"message_received"` / `"message_sent"` |
### Context recall
How `before_prompt_build` picks observations to inject:
- `contextRecall.mode` (default `hybrid`) — `recent` keeps the chronological tail; `hybrid` runs an FTS5 search on the latest user message and merges the hits with the recent tail (deduped by id). Hybrid surfaces topical past memories instead of always showing the last N regardless of relevance.
- `contextRecall.searchLimitRatio` (default `0.5`) — fraction of `maxObservations` reserved for search hits in hybrid mode; the rest is filled with recent.
Hybrid mode falls back transparently to recent-only if the search throws or no user-message text is available.
Edit any knob with `openclaw mem config set <path> <value>`.
## Hardening (opt-outs)
Default behavior: claw-mem auto-injects a memory context block into every prompt (via `before_prompt_build`) and exposes `mem-search` / `mem-status` / `mem-forget` to the agent. That's the right default for "give the agent persistent memory and let it use it."
If your deployment wants memory to be **query-only** — captured in the background, but never auto-injected and never callable from inside the agent loop — the gateway provides two host-side switches in `~/.openclaw/openclaw.json`:
```jsonc
{
"plugins": {
"entries": {
"claw-mem": {
"hooks": {
// Block claw-mem's before_prompt_build hook from stuffing context
// into the next prompt. Observation capture (after_tool_call) and
// session summarization (session_end) still fire — only the
// prompt-side injection is suppressed.
"allowPromptInjection": false
}
}
}
},
// Host-level filter: hide these tools from the agent's tool list entirely.
// Useful for tightening the agent's surface (e.g. blocking exec/process)
// and/or making memory access human-only via the CLI.
"skipTools": ["exec", "process", "mem-search"]
}
```
Pick what fits your threat model:
- **Capture but don't inject** — set `allowPromptInjection: false`. Observations still accumulate; you read them via `openclaw mem search` when needed.
- **CLI-only memory** — also add `mem-search` (and optionally `mem-status` / `mem-forget`) to the host's `skipTools`. The agent can no longer query memory; only humans can via `openclaw mem …`.
- **Default ("memory just works")** — leave both unset.
Note: the plugin-config knob `skipTools` (under `plugins.entries.claw-mem.config.skipTools`, listed above) is a **different** filter. It controls which **observed** tool calls the *observer* records, not which tools the *agent* can see. The host-level `skipTools` at the top of `openclaw.json` is what gates agent visibility.
## Verification (post-install smoke test)
After `openclaw plugins install @chainofclaw/claw-mem` (or an in-place upgrade), restart the gateway and run:
```bash
openclaw mem status # proves the plugin is loaded; shows 0/0/0 on a fresh DB
openclaw mem peek # context that would inject on the next prompt
openclaw mem db migrate-status # confirms schema is current; 0 pending migrations
```
All three should succeed without errors. Common failure modes:
- **`unknown command 'mem'`** — the plugin failed to load. Check `plugins.allow` in `~/.openclaw/openclaw.json` and the gateway startup logs for `[claw-mem] Loaded` (or a `[claw-mem] Bootstrap failed:` line).
- **`Bootstrap failed: ... EACCES ... /home/.../.claw-mem`** — the default `~/.claw-mem` isn't writable on this host. Apply the explicit `dataDir` override from the [Install](#after-install-writable-data-dir) section.
- **`mem search` / `mem-search` agent tool returns "tool not found"** — check whether the host has `mem-search` in `skipTools` (see [Hardening](#hardening-opt-outs)). If yes, that's intentional; query via the CLI or remove the entry.
## When NOT to use this skill
- You want ephemeral agent sessions with no persistent history — don't install claw-mem; OpenClaw's in-session context is enough.
- You want memory-like search *only for code* — tools like grep / ripgrep / `codebase_search` serve that need without the persistence overhead.
- You want on-chain / cross-device backup — add [coc-soul](https://clawhub.ai/ngplateform/coc-soul) on top; claw-mem only persists locally.
## Reference
- `references/cli.md` — every `openclaw mem|config|db` subcommand, plus the standalone-bin appendix
- `references/config.md` — complete config schema
- `references/observer.md` — how observations are extracted, when hooks fire
- `references/programmatic-api.md` — using `Database` / `ObservationStore` / `SearchEngine` as a library
Source and issue tracker: <https://github.com/NGPlateform/claw-mem>.
FILE:references/cli.md
# `claw-mem` CLI reference
The OpenClaw plugin (`@chainofclaw/claw-mem` after `openclaw plugins install`) mounts its CLI under a single root: `openclaw mem`. Memory queries and maintenance live as direct subcommands; SQLite maintenance, plugin config, and version info live as nested subgroups (`mem db …`, `mem config …`, `mem version`). Sibling plugins (`coc-node`, `coc-soul`) mount their own roots — there is no shared umbrella anymore. Anything not listed below (node lifecycle, soul backup, bootstrap, doctor, init) lives either in those sibling plugins or in the **standalone `claw-mem` binary** — see the appendix at the end if you installed that separately.
> **Inside an agent loop, prefer the registered tools** (`mem-search`, `mem-status`, `mem-forget`) over shelling out — the tool calls don't depend on PATH or shell context. The CLI commands below are for human/ops use from a terminal.
## Memory commands (`openclaw mem …`)
| Command | Purpose |
|---|---|
| `mem search <query>` | FTS5 full-text search over observations. Flags: `--limit <n>`, `--type <kind>`, `--agent <id>`, `--json`. |
| `mem status` | Counts of observations / summaries / sessions / agents, plus DB path and `tokenBudget`. Quick sanity check. |
| `mem forget <sessionId>` | Delete all observations for a specific session. |
| `mem peek` | Dump the memory context that would be injected on the next prompt (respects `tokenBudget`). Flags: `--agent <id>`, `--json`. |
| `mem prune` | Delete old observations. `--older-than <days>` keeps the last N days; `--before <iso>` for explicit cutoff. |
| `mem export <file>` | Dump observations + summaries + sessions to a JSON file. Flag: `--agent <id>` to scope. |
| `mem import <file>` | Load a previously exported file. Uses snake_case field names (matches SQLite schema). |
## DB commands (`openclaw mem db …`)
| Command | Purpose |
|---|---|
| `db size` | On-disk size of the SQLite DB + FTS index (main + WAL/SHM). Flag: `--json`. |
| `db vacuum` | Reclaim space after large deletes. Flag: `--json`. |
| `db migrate-status` | Check schema version against code's expected version. Flag: `--json`. |
## Config commands (`openclaw mem config …`)
| Command | Purpose |
|---|---|
| `config list` | Print the full effective config. Flags: `--from-disk`, `--section <name>`. |
| `config get <path>` | Read a dotted key (e.g. `tokenBudget`, `summarizer.mode`). Flags: `--json`, `--from-disk`. |
| `config set <path> <value>` | Write a key to `~/.claw-mem/config.json`. String-coercion is careful not to stringify hex keys or URLs. Flag: `--json` (treat value as JSON). |
| `config path` | Print the config file location. |
## `openclaw mem version`
Prints the loaded plugin version (matches `~/.openclaw/extensions/claw-mem/package.json`).
---
## Appendix: standalone `claw-mem` bin
The standalone binary is a separate artifact: install it with `npm i -g @chainofclaw/claw-mem`. It is **not** placed on PATH by `openclaw plugins install`. When you run it outside OpenClaw, it mounts the full command tree — including the umbrella commands that the OpenClaw plugin path does not expose:
- `claw-mem mem …` — same memory subtree as `openclaw mem`, just rooted at the bare bin
- `claw-mem db …` / `config …` — same as `openclaw mem db` / `openclaw mem config`
- `claw-mem node …` — re-mount of `@chainofclaw/node`
- `claw-mem backup … / did … / guardian … / recovery … / carrier …` — re-mount of `@chainofclaw/soul`
- `claw-mem bootstrap dev|prod` — one-shot bootstrap pipeline (starts hardhat, deploys contracts, installs a node, runs first backup)
- `claw-mem status` / `doctor` / `init` / `tools` / `uninstall` — cross-layer health + management
If you don't have the bin and don't want to install it: stick with `openclaw mem …` for memory work, install `coc-node` for node lifecycle, and `coc-soul` for backup/recovery. That's the supported in-OpenClaw composition.
---
## Appendix: in-place tarball install (when `openclaw plugins install` is unusable)
If `openclaw plugins install @chainofclaw/claw-mem --dangerously-force-unsafe-install --force` errors out (npm cache `EACCES` on the openclaw user, registry blocked, sandboxed runner without npm install permissions, etc.), you can install the byte-identical artifact manually — this completely bypasses OpenClaw's install pipeline and its `child_process` static scan:
```bash
# 1. Build/pack from source (or download the tarball from npm directly)
cd /path/to/claw-mem/packages/claw-mem
npm pack --pack-destination /tmp # → /tmp/chainofclaw-claw-mem-<v>.tgz
# 2. Wipe the existing extension dir and extract the tarball in place
rm -rf ~/.openclaw/extensions/claw-mem
mkdir -p ~/.openclaw/extensions/claw-mem
tar -xzf /tmp/chainofclaw-claw-mem-<v>.tgz \
-C ~/.openclaw/extensions/claw-mem --strip-components=1
# 3. Install runtime deps only (skips dev/build tooling)
cd ~/.openclaw/extensions/claw-mem
npm install --omit=dev
# 4. Restart the gateway and verify
openclaw mem status # → JSON with observation/summary/session counts
```
To download the tarball from npm without a source clone: `npm pack @chainofclaw/claw-mem@<version> --pack-destination /tmp` works as long as you have npm registry access; the rest of the steps are identical.
Always back the DB up before any version change: `cp ~/.claw-mem/claw-mem.db ~/.claw-mem/claw-mem.db.pre-<v>.bak` (or use whatever `dataDir` is configured if you've overridden it).
FILE:references/config.md
# Configuration
`claw-mem` reads `~/.claw-mem/config.json`. The `CLAW_MEM_DATA_DIR` env var overrides the dataDir.
## Memory-layer fields (owned by claw-mem)
```json
{
"enabled": true,
"dataDir": "~/.claw-mem",
"tokenBudget": 8000,
"maxObservations": 50,
"maxSummaries": 10,
"dedupWindowMs": 30000,
"skipTools": ["TodoWrite", "AskUserQuestion", "Skill"]
}
```
### `tokenBudget`
Hard cap on how many tokens of memory context are injected. 8000 is a reasonable default for a 200K context model; turn down if prompts are already tight.
### `maxObservations` / `maxSummaries`
Ceiling on how many items `buildContext` considers. Higher = more recall, worse latency.
### `dedupWindowMs`
If the same tool call (same content hash) happens within this window, only one observation is recorded. Reduces noise from repeated operations like `ls` or `pwd`.
### `skipTools`
Observer ignores these tool names entirely — they produce no observation. Useful for:
- Meta-tools that don't produce domain knowledge (`TodoWrite`, `AskUserQuestion`, `Skill`)
- Read-only exploration that would flood memory (`Read`, `Glob` — consider adding these for large codebases)
## Umbrella composition (for the full claw-mem binary)
When installed as the full `@chainofclaw/claw-mem` package, config also inherits:
- `storage.*` — owned by the underlying `@chainofclaw/node` library
- `node.*` — same
- `backup.*` / `backup.carrier.*` — owned by `@chainofclaw/soul`
- `bootstrap.*` — meta
See [`coc-node` references/config.md](https://clawhub.ai/skill/coc-node) and [`coc-soul` references/config.md](https://clawhub.ai/skill/coc-soul) for details on those subtrees.
## Modify config safely
```bash
claw-mem config set tokenBudget 12000
claw-mem config set skipTools '["TodoWrite","AskUserQuestion","Skill","Read"]'
```
For JSON arrays / objects pass them inside single quotes. `config set` carefully does **not** coerce hex strings (like `0xac09…`) to numbers.
FILE:references/observer.md
# Observer and session hooks
claw-mem plugs into OpenClaw's session lifecycle with two hooks:
## Observation capture (per tool call)
Every time the agent calls a tool, the observer:
1. Checks `skipTools` — if the tool is in the list, abort
2. Extracts structured fields: `type`, `title`, `facts[]`, `narrative`, `concepts[]`, `filesRead[]`, `filesModified[]`, `toolName`, `promptNumber`
3. Computes a content hash; de-duplicates against recent observations (within `dedupWindowMs`)
4. Estimates tokens for future budgeting
5. Writes to the `observations` table + FTS index
The `type` classification is a closed enum (claw-mem does not mutate it):
- **discovery** — newly found fact about the codebase / environment / world
- **decision** — chose X over Y for a reason
- **pattern** — recurring structure worth noting
- **learning** — new capability or understanding
- **issue** — bug, blocker, unknown
- **change** — mutation to files / state / config
- **explanation** — answer to a user question
## Memory injection (per new prompt)
Before the next user prompt is handed to the model, claw-mem builds a **memory context**:
1. Find recent observations relevant to the current session / agent / question
2. Apply `tokenBudget`, `maxObservations`, `maxSummaries` caps
3. Render as markdown (one section per observation type)
4. Inject as a system-message-level prefix
`claw-mem mem peek` renders exactly this artifact without triggering the injection. Use it to debug "why didn't the agent remember X?"
## Session summarization (on session end)
When a session closes, claw-mem summarizes it into the `session_summaries` table:
- `request` — what the user asked for
- `investigated` — what the agent explored
- `learned` — key takeaways
- `completed` — what shipped
- `nextSteps` — what's still open
- `notes` — loose ends
Summaries have a higher injection priority than raw observations in future memory contexts.
## Opting out
- `config.enabled: false` — disable the whole skill
- `skipTools: [...]` — add more tool names to skip
- `mem forget <sessionId>` — delete observations from a specific session
- `mem prune --before <iso>` — drop everything older than a cutoff
FILE:references/programmatic-api.md
# Programmatic API (library usage)
Every memory primitive is exported from `@chainofclaw/claw-mem`. Use these when embedding claw-mem in your own agent framework, not as an OpenClaw plugin.
## Open the database
```ts
import { Database } from "@chainofclaw/claw-mem";
const db = new Database("/home/you/.claw-mem/claw-mem.db");
db.open();
// ... use db.connection (the underlying node:sqlite handle)
db.close();
```
`db.open()` runs pending migrations on first call.
## Write an observation
```ts
import { ObservationStore } from "@chainofclaw/claw-mem";
const obs = new ObservationStore(db);
obs.insert({
sessionId: "session-1",
agentId: "my-agent",
type: "discovery",
title: "FTS index rebuilt on launch",
facts: ["FTS5 table rebuilt on every schema migration"],
narrative: null,
concepts: ["sqlite", "fts"],
filesRead: [],
filesModified: [],
toolName: "Read",
promptNumber: 1,
});
```
## Search
```ts
import { SearchEngine } from "@chainofclaw/claw-mem";
const search = new SearchEngine(db);
const { results, totalCount, source } = search.search({ query: "fts", limit: 5 });
// source is "fts" or "like" (fallback when FTS5 query parsing fails)
```
## Build injection context
```ts
import { buildContext } from "@chainofclaw/claw-mem";
const ctx = buildContext(db, {
sessionId: "session-1",
agentId: "my-agent",
tokenBudget: 8000,
maxObservations: 50,
maxSummaries: 10,
});
console.log(ctx.markdown);
console.log("tokens used:", ctx.tokensUsed);
```
The returned `markdown` is literally what would be prepended to the next prompt.
## Extract an observation from a tool call
```ts
import { extractObservation } from "@chainofclaw/claw-mem";
const observation = extractObservation({
toolName: "Read",
toolInput: { file_path: "/src/foo.ts" },
toolResult: "... file content ...",
sessionId: "session-1",
agentId: "my-agent",
promptNumber: 3,
});
if (observation) obs.insert(observation);
```
## Summarize a session
```ts
import { summarizeSession } from "@chainofclaw/claw-mem";
const summary = summarizeSession(db, {
sessionId: "session-1",
agentId: "my-agent",
});
// persisted to session_summaries
```
## Re-exported from sub-packages
For convenience the umbrella also re-exports the `node` and `soul` public APIs — you don't need to import `@chainofclaw/node` separately:
```ts
import { NodeManager, BackupManager } from "@chainofclaw/claw-mem";
```
Operate COC (ChainOfClaw) blockchain nodes — install, start, stop, monitor, and remove validator, fullnode, archive, gateway, and dev nodes. Use when the use...
---
name: coc-node
description: Operate COC (ChainOfClaw) blockchain nodes — install, start, stop, monitor, and remove validator, fullnode, archive, gateway, and dev nodes. Use when the user wants to run a COC node, inspect the status of a running node (block height, peer count, BFT state), view node logs, edit node-config.json, or probe RPC endpoints. Also covers preparing a machine to provide ≥ 256 MiB of P2P storage to the COC network. **Smooth out-of-box experience (1.2.0+):** read-only commands (list / status / coc-rpc-query against an installed node) work immediately after `openclaw plugins install` with zero config; the activation banner reports `data dir`, `storage quota`, `tracked nodes`, and `coc repo` status so the operator sees at a glance what works now and what (if anything) needs config to unlock install/start. Data dir auto-resolves to a writable path along the same chain @chainofclaw/claw-mem and @chainofclaw/soul use (`config.dataDir → $COC_NODE_DATA_DIR → $CLAW_MEM_DATA_DIR/coc-node → $OPENCLAW_STATE_DIR/coc-node → ~/.claw-mem/coc-node`); legacy `~/.chainofclaw/nodes.json` from pre-1.2.0 installs is detected as a fallback. Fail-fast actionable EACCES at activation rather than silent breakage mid-command. Starting / installing a node additionally requires a local clone of the COC source repo (set $COC_REPO_PATH or `bootstrap.cocRepoPath`); the activation banner tells you whether one was auto-detected.
version: 1.2.0
metadata:
openclaw:
homepage: https://www.npmjs.com/package/@chainofclaw/node
primaryEnv: CLAW_MEM_DATA_DIR
requires:
bins:
- node
anyBins:
- coc-node
- openclaw
install:
- kind: node
package: "@chainofclaw/node"
version: "1.2.0"
bins:
- coc-node
---
# coc-node — COC blockchain node lifecycle
Operate a COC node on this machine. The skill is backed by the npm package [`@chainofclaw/node`](https://www.npmjs.com/package/@chainofclaw/node) which ships both a standalone `coc-node` CLI and an OpenClaw plugin (skill id `coc-node`).
## What this skill can do
- **Install** a new COC node of any type: `validator`, `fullnode`, `archive`, `gateway`, or `dev`
- **Start / stop / restart** nodes; follow logs across the node / agent / relayer streams
- **Report status** — block height, peer count, BFT activity, process PID, per-service health
- **Edit** a node's `node-config.json` in `$EDITOR`
- **Probe** any COC RPC endpoint safely (whitelisted methods only: `eth_blockNumber`, `eth_getBlockByNumber`, `net_peerCount`, `coc_chainStats`, `coc_getBftStatus`, `eth_syncing`, `eth_chainId`, …)
## Zero-config on install (1.2.0+)
**Everything you can do without a COC source repo works immediately after `openclaw plugins install` — no further setup.** The activation banner makes it explicit:
```
[coc-node] data dir: /home/<you>/.claw-mem/coc-node
[coc-node] storage quota: advertised=256 MiB, reserved=256 MiB, enforce=true
[coc-node] tracked nodes: 0
[coc-node] coc repo: detected at /home/<you>/COC — install/start commands enabled
[coc-node] Loaded — no nodes yet, run `openclaw coc-node node install <name>` to add one
[coc-node] CLI is mounted at `openclaw coc-node ...`. ...
```
Or, if no COC repo is on this machine:
```
[coc-node] coc repo: not detected — read-only mode (list / status / coc-rpc-query work; install / start need bootstrap.cocRepoPath or $COC_REPO_PATH pointing at a COC source clone)
```
That second line is the **only** thing you need to read to know whether `node install` / `node start` will work. Everything else (list, status, log inspection, RPC probes against an already-running node) is unconditionally available.
### Data directory
Auto-resolves to a writable path along a chain that's intentionally aligned with `@chainofclaw/claw-mem` and `@chainofclaw/soul` so the three plugins share one operator-managed root. Priority (highest first):
1. `config.dataDir` (per-instance plugin config)
2. `$COC_NODE_DATA_DIR` (coc-node-specific operator override)
3. `$CLAW_MEM_DATA_DIR/coc-node` (shared with claw-mem + soul — set this once and all three move together)
4. `$OPENCLAW_STATE_DIR/coc-node` (sandbox-managed state dir)
5. `~/.claw-mem/coc-node` (default — shared root with claw-mem + soul)
6. `~/.chainofclaw` (legacy pre-1.2.0 fallback; only picked when `nodes.json` already exists there)
Fails fast at activation with an actionable EACCES error naming each tried path, rather than silently breaking mid-command. `/tmp` is intentionally not a fallback.
### What needs setup to start a node yourself
Actually starting a node process requires the COC source repository (it spawns `node/src/index.ts` from there). Tell the skill where the repo is via **one of**:
- `COC_REPO_PATH` environment variable (simplest)
- `bootstrap.cocRepoPath` in plugin config
- Run inside (or anywhere under) the COC repo — auto-discovered via marker files
- Place a clone at `~/COC` — also auto-discovered
Plus ≥ 256 MiB free disk for the P2P storage reservation (mandatory COC network entry requirement).
The activation banner tells you whether the auto-detection succeeded. If `COC_REPO_PATH` is unset and no clone is at `~/COC`, `node install` and `node start` fail with a clear error pointing here — list / status / log / RPC commands keep working.
## Relationship with claw-mem and coc-soul
The three `@chainofclaw/*` skills are **fully decoupled** at the npm-dependency level. Each can be installed independently. They cooperate through shared on-disk conventions, not through code coupling:
| Skill | Owns | What it adds when paired |
|---|---|---|
| **coc-node** | Local node lifecycle (install / start / stop / status / RPC probe) | Independent of the other two. |
| [claw-mem2db](https://clawhub.ai/ngplateform/claw-mem2db) | Persistent agent memory (chat + tool capture, FTS5 search, hybrid recall) | Pure agent-side; doesn't touch the chain. coc-node doesn't read or write to its DB. |
| [coc-soul](https://clawhub.ai/ngplateform/coc-soul) | On-chain DID, IPFS backup, guardian recovery, carrier resurrection | Reads claw-mem's SQLite DB (when present) for semantic snapshots. Also independent of coc-node. |
**Shared dataDir convention.** All three default to writing under `~/.claw-mem` (or under `$CLAW_MEM_DATA_DIR` / `$OPENCLAW_STATE_DIR`), each in a scoped subdirectory:
- claw-mem → `~/.claw-mem/{claw-mem.db, config.json, ...}`
- coc-soul → `~/.claw-mem/keys/agent.key`
- coc-node → `~/.claw-mem/coc-node/{nodes.json, <node>/...}`
So one `CLAW_MEM_DATA_DIR=/somewhere/writable` env var moves all three. Operators in sandboxed Docker hosts (where `~/.claw-mem` is read-only) only have one knob to turn.
## How to invoke
**Inside OpenClaw (recommended — works automatically after `plugins install`):**
```bash
openclaw coc-node node install --type fullnode --network testnet
openclaw coc-node node list
openclaw coc-node node status
openclaw coc-node node logs <name> --follow --all
```
**Standalone bin (only if you ran `npm i -g @chainofclaw/node` separately):**
```bash
coc-node node install --type dev --network local --name dev-1 --rpc-port 28780
coc-node node list
```
> `openclaw plugins install` does NOT install the standalone `coc-node` binary into your PATH. Use `openclaw coc-node ...` (with the `openclaw` prefix), or install the bin globally via npm if you want the bare command.
## Typical flows
1. **Spin up a dev node against local hardhat** — `coc-node node install --type dev --network local` then `coc-node node start dev-1`.
2. **Join testnet as a fullnode** — `coc-node node install --type fullnode --network testnet --rpc-port 28780` then `coc-node node start`.
3. **Stand up a validator** — `coc-node node install --type validator --network testnet --advertised-bytes 1073741824` (1 GiB storage contribution).
4. **Diagnose a flaky node** — `coc-node node status` (snapshot) → `coc-node node logs --follow` (tail) → `coc-node node config show` (inspect config) → `coc-node node restart` if needed.
5. **Decommission a node** — `coc-node node stop NAME` then `coc-node node remove NAME --yes` (delete data) or `coc-node node remove NAME --yes --keep-data`.
## When NOT to use this skill
- Deploying COC smart contracts — that's a `contracts/` hardhat / script task, not node lifecycle.
- On-chain identity / backup / recovery — use the [coc-soul](https://clawhub.ai/ngplateform/coc-soul) skill.
- Agent memory / session capture — use the [claw-mem2db](https://clawhub.ai/ngplateform/claw-mem2db) skill.
## Reference
Detailed references live alongside this file:
- `references/cli.md` — every `coc-node` subcommand with flags and examples
- `references/config.md` — complete `~/.claw-mem/coc-node/config.json` schema
- `references/node-types.md` — validator vs fullnode vs archive vs gateway vs dev tradeoffs
- `references/troubleshooting.md` — common failure modes and fixes
Source and issue tracker: <https://github.com/NGPlateform/claw-mem/tree/main/packages/node>.
FILE:references/cli.md
# `coc-node` CLI reference
All commands live under `coc-node node`. Every subcommand prints `--help` with full flag detail.
## `node install` (alias: `node init`)
Create a new node — generates `node-config.json`, writes to the registry, does **not** start the process.
| Flag | Meaning | Default |
|---|---|---|
| `-t, --type <type>` | `validator` / `fullnode` / `archive` / `gateway` / `dev` | `dev` |
| `-n, --network <net>` | `testnet` / `mainnet` / `local` / `custom` | `local` |
| `--name <name>` | Unique node name | auto-generated |
| `--data-dir <dir>` | Override data directory | `~/.chainofclaw/nodes/<name>` |
| `--rpc-port <n>` | JSON-RPC port | 18780 (local) / 28780 (testnet) |
| `--advertised-bytes <n>` | Storage to advertise to the P2P network | 268435456 (256 MiB min) |
## `node list`
Table of every node tracked by the registry. `--json` for machine-readable output.
## `node start [name]` / `node stop [name]` / `node restart [name]`
Omit `name` to apply to all registered nodes.
## `node status [name]`
Live status. Without `name`: prints each registered node. With `--json`: structured output including:
- `running` (bool), `pid`, `dataDir`
- `blockHeight`, `peerCount`, `bftActive` (only if RPC is reachable)
- `services.{node,agent,relayer}.{running, pid}`
## `node remove <name>`
Deregister a node.
- `--yes` — skip confirmation prompt
- `--keep-data` — preserve the data directory on disk (default: delete)
## `node config show [name]`
Pretty-print the node's `node-config.json`.
## `node config edit <name>`
Open `node-config.json` in `$EDITOR`.
## `node logs <name>`
Tail / follow logs.
- `--follow` (`-f`) — continuous tail (like `tail -F`)
- `--service <svc>` — `node` (default), `agent`, or `relayer`
- `--all` — interleave all three service logs
- `--lines <n>` — how many lines when not following (default 100)
## RPC probe
A read-only RPC helper is also exported by the underlying library. If you only need to inspect a remote node:
```ts
import { rpcCall, ALLOWED_RPC_METHODS } from "@chainofclaw/node";
const height = await rpcCall("http://remote:28780", "eth_blockNumber", []);
```
`ALLOWED_RPC_METHODS` is the allowlist — anything else fails at the client side (defense-in-depth against accidental exposure of mutating methods).
FILE:references/troubleshooting.md
# Troubleshooting
## `node start` fails with "COC repo not located"
The skill can't find the COC source repo. Either:
- Set env var: `export COC_REPO_PATH=/absolute/path/to/COC`
- Or edit config: `coc-node config set bootstrap.cocRepoPath /absolute/path/to/COC`
- Or cd into the COC repo before running `coc-node` (auto-discovery walks upward)
Read-only commands (`list`, `status`, `config show`) do not need this.
## Port already in use
Default local port is 18780; default testnet port is 28780. On collision, pass `--rpc-port` explicitly. Other derived ports (`ws`, `p2p`, `wire`, `ipfs`) are offset from `rpcPort`; see `node config show <name>` to see exactly which ports a node uses.
## `storage-reservation` warn
`enforceQuota: false` in config disables the reservation. For production, keep `enforceQuota: true` and mount `dataDir` on a dedicated disk so the reservation file doesn't collide with OS or home-dir files.
## Node shows "STOPPED" after `node start`
- Check `coc-node node logs NAME --follow` — most startup failures print a clear reason
- Verify Node.js version ≥ 22 (`node -v`)
- Verify `COC_REPO_PATH` points at a complete clone (run `ls $COC_REPO_PATH/node/src/index.ts`)
## RPC status empty
If `coc-node node status NAME` shows `RUNNING` but no `blockHeight`/`peerCount`:
- The node is up but RPC hasn't finished starting — retry in a few seconds
- Or RPC bind is non-localhost and can't be reached from the calling process — inspect `rpcBind` / `rpcPort` in `config show`
## Removing stale registry entries
If a node process died without being removed from the registry:
```bash
coc-node node remove NAME --yes --keep-data # deregister without touching data
```
Then `coc-node node install --name NAME` will create a fresh registry entry.
FILE:references/config.md
# `~/.chainofclaw/config.json` schema
`coc-node` reads `$COC_NODE_CONFIG` if set, otherwise `~/.chainofclaw/config.json`. Missing keys fall back to defaults baked into the package.
```json
{
"dataDir": "~/.chainofclaw",
"node": {
"enabled": true,
"defaultType": "dev",
"defaultNetwork": "local",
"port": 18780,
"bind": "127.0.0.1",
"autoAdvertiseStorage": true
},
"storage": {
"quotaBytes": 268435456,
"advertisedBytes": 268435456,
"reservedBytes": 268435456,
"enforceQuota": true,
"reserveFile": ".quota.reserved"
},
"bootstrap": {
"cocRepoPath": "/path/to/COC"
}
}
```
## Field reference
### `dataDir` (string)
Where node registry and per-node data directories live. `~` is expanded.
### `node.*`
| Key | Type | Default | Notes |
|---|---|---|---|
| `enabled` | bool | `true` | Gate for the whole skill; set `false` to disable plugin activation |
| `runtimeDir` | string | (COC repo's `runtime/`) | Where to find `coc-agent.ts` / `coc-relayer.ts` |
| `defaultType` | enum | `dev` | Used when `--type` is omitted |
| `defaultNetwork` | enum | `local` | Used when `--network` is omitted |
| `port` | number | `18780` | RPC port default |
| `bind` | string | `127.0.0.1` | Bind address |
| `autoAdvertiseStorage` | bool | `true` | Auto-pick `advertisedBytes` when omitted |
### `storage.*`
| Key | Type | Default | Notes |
|---|---|---|---|
| `quotaBytes` | number | `268435456` | Hard cap for this process's disk usage |
| `advertisedBytes` | number | `268435456` | P2P network claim (min 256 MiB) |
| `reservedBytes` | number | `268435456` | Pre-allocate via `fallocate` to prevent over-commit |
| `enforceQuota` | bool | `true` | When `false`, skip the reservation check |
| `reserveFile` | string | `.quota.reserved` | Name of the placeholder file |
### `bootstrap.cocRepoPath` (string)
Absolute path to your COC source-repo clone. Required for `node start` (spawn needs `node/src/index.ts` etc.). Read-only commands tolerate this being unset.
FILE:references/node-types.md
# Node types — when to choose which
`--type` at install time.
| Type | Role | Services | Rewards | Storage footprint |
|---|---|---|---|---|
| `dev` | Local development | node only; hardhat fake L1 | none | minimal, everything in memory |
| `fullnode` | Verifier + relay | node + agent | marginal (relay fees) | 256 MiB – several GiB |
| `validator` | BFT block production + verification | node + agent | high (block rewards + fees) | 256 MiB – TB (archive-grade optional) |
| `archive` | Historical state serving | node + agent + archive mode | moderate (PoSe service fees) | TB-scale |
| `gateway` | JSON-RPC / IPFS fan-out for clients | node + relayer | moderate (gateway fees) | 256 MiB – GiB |
## Choosing
- **First time, just want to poke around** → `dev` + `--network local` (hardhat). No chain identity, no gas.
- **Run a node that contributes storage to testnet** → `fullnode` + `--network testnet`. Minimum viable participant.
- **Become a validator** → requires stake + peer approval. `--type validator` installs the role; staking happens on-chain afterward.
- **Mirror full history** → `archive`. Expensive disk-wise; appropriate for block explorers and analytics.
- **Edge ingress for end-users** → `gateway`. Accepts RPC / IPFS from clients; no block production.
Validator selection is stake-weighted and rotates deterministically. See the [COC whitepaper §XII](https://github.com/NGPlateform/COC/blob/main/docs/COC_whitepaper.en.md) for the consensus model.
Give an AI agent a persistent on-chain soul on COC — register and manage the agent's decentralized identity (DID), anchor encrypted backups to IPFS + SoulReg...
---
name: coc-soul
description: Give an AI agent a persistent on-chain soul — register and manage a decentralized identity (DID), encrypt and anchor agent state to IPFS + SoulRegistry, configure guardians for social recovery, and enable cross-carrier resurrection so the agent can resume on a different device if the host dies. **Pairs with `claw-mem2db` to deliver "digital / silicon-based persistence" for AI agents**: when claw-mem is co-installed, every backup automatically captures claw-mem's chat history + tool-call observations + session summaries as a token-budgeted semantic snapshot, so an agent recovered on a fresh host can replay its memory context — not just its files. Soul also runs fully standalone (without claw-mem), in which case backups still cover identity / config / workspace / chat files but skip the semantic snapshot. Use when the user wants their AI agent to survive device loss, transfer ownership, delegate capabilities, run a guardian / carrier node, inspect on-chain identity state, or get persistent cross-device memory paired with claw-mem. Zero-config on COC testnet — installation auto-generates an EOA keystore (~/.claw-mem/keys, shared with claw-mem; or $OPENCLAW_STATE_DIR/coc-soul/keys in sandboxed hosts), auto-drips testnet COC from the public faucet for gas, and pre-fills RPC + IPFS + contract addresses for the live testnet. The first `openclaw coc-soul backup init` works with no manual setup.
version: 1.2.10
metadata:
openclaw:
homepage: https://www.npmjs.com/package/@chainofclaw/soul
primaryEnv: CLAW_MEM_DATA_DIR
requires:
bins:
- node
anyBins:
- coc-soul
- openclaw
install:
- kind: node
package: "@chainofclaw/soul"
version: "1.2.6"
bins:
- coc-soul
---
# coc-soul — agent identity, backup, and resurrection
The **soul layer** for AI agents: on-chain DID, encrypted backups to IPFS, social recovery via guardians, and cross-device resurrection via carriers. Backed by the npm package [`@chainofclaw/soul`](https://www.npmjs.com/package/@chainofclaw/soul) which ships both a standalone `coc-soul` CLI and an OpenClaw skill (id `coc-soul`).
Soul works **standalone** (backs up the agent's home tree to chain + IPFS), and gets one extra capability when **`claw-mem2db` is installed alongside it**: each backup also captures claw-mem's chat history, tool-call observations, and session summaries as a token-budgeted semantic snapshot. Recover on a fresh host and the agent gets back not just its files but its remembered context — chat preferences, decisions, conversation history. **This is the "digital / silicon-based persistence" story.**
---
## 30-second decision tree (operators read here first)
If the user is asking "how do I recover on another machine?", pick **one** path before saying anything else:
1. **Have backup material (manifest CID or `~/.openclaw/.coc-backup/latest-recovery.json`)** → use the **restore** path: `openclaw coc-soul backup restore ...`
2. **Lost the owner key OR need to migrate ownership** → use **resurrection** (owner-key) or **guardian social recovery**
Don't merge the two explanations until the path is selected. `recovery` and `resurrection` are different flows (see `references/guardian-recovery.md`).
## Critical CID terminology (avoid confusion)
| Term | Meaning | Use it for |
|---|---|---|
| `manifest CID` / `latestManifestCid` | Backup restore point (IPFS manifest) | `backup restore --manifest-cid <cid>` |
| full-backup CID | Earlier baseline snapshot | Roll back to a baseline state |
| latest incremental CID | Newest chain tip | Restore the latest state |
| identity CID / hash | Identity-content hash used in registration | **Not** the backup restore point |
Rule: when a user asks "what's your CID?", first confirm whether they mean the **latest backup manifest CID** vs. an older backup CID vs. the identity registration CID — they get conflated constantly.
## Key material — agent safety rules
| Secret / role | Purpose | Needed when | Chat-safe? |
|---|---|---|---|
| owner key / agent operator key | normal chain ops, backup anchor | daily ops | **Never paste in chat** |
| resurrection key | owner-key resurrection flow | `resurrection start` | **Never paste in chat** |
| guardian accounts | social recovery approvals | `recovery approve/complete` | addresses yes; **private keys never** |
**Hard rule for any agent reading this skill:** never request, transmit, or echo private keys in chat — including "split" or "encrypted" fragments. Always route key transfer to a local secure channel.
## Ultra-quick runbook (10 lines)
1. Pick the path first: `restore` or `resurrection` (see the decision tree above).
2. Run `openclaw coc-soul backup doctor --json` and read `chain.registered` / `restore.available` / `resurrection.configured`.
3. If there's a manifest CID or a `latest-recovery.json`, take the **restore** path.
4. **Restore to `/tmp/...` first** — never overwrite a production directory in one step.
5. Verify `merkleVerified: true` + exit code 0, then promote to the production path only after explicit user confirmation.
6. No owner key but resurrection was pre-configured → take the **resurrection** flow.
7. Need multi-party approval for ownership migration → take **guardian recovery** (quorum + timelock).
8. Script the `heartbeat` first, then schedule it via cron / systemd / OpenClaw scheduler.
9. Private keys never go through chat (including split / encrypted fragments or temporary paste).
10. Default command surface is `openclaw coc-soul ...`; the bare `coc-soul ...` only exists when the standalone bin was installed via `npm i -g @chainofclaw/soul`.
## Common failure → cause → fix
| Symptom | Likely cause | First action |
|---|---|---|
| `Unsupported state or unable to authenticate data` on restore | encryption mode / key mismatch | Re-read `encryptionMode` in `latest-recovery.json`: `password` mode requires `--password`; `privateKey` mode must NOT pass `--password` |
| `429 rate limit exceeded` from IPFS | manifest fetch is rate-limited | Exponential-backoff retry until `merkleVerified: true` |
| `[gateway] unauthorized (1008)` from cron / scheduled job | wrong gateway auth mode / token / proxy config | Fix gateway auth before scheduling anything |
| `[gateway] unauthorized (1008)` **right after a restore** | restore overlaid `gateway.auth.mode` from the source host; the old TUI command `--token "$(jq -r .gateway.auth.token ...)"` now resolves to the literal string `null` | Run `jq '.gateway.auth.mode'` to see the active mode and pick the matching flag (see the "Cross-host restore" section below). If the whole auth block was overwritten, copy `.gateway.auth.*` back from `~/.openclaw/.restore-overwrite-backup-*/openclaw.json` |
| `ENOENT ... backup/targeting.js` | extension install is missing files | Reinstall: `openclaw plugins install @chainofclaw/soul --dangerously-force-unsafe-install --force` |
| `data dir not writable` at startup | `~/.claw-mem` is owned by another uid (common Docker multi-user case) | 1.2.2+ auto-falls-back to `~/.openclaw/state/coc-soul`; on older versions `export CLAW_MEM_DATA_DIR=~/.openclaw/state` and restart the gateway |
| `plugins.allow is empty ... may auto-load` warning | gateway has no trusted-plugin allow list | Add `"plugins": {"allow": ["claw-mem","coc-soul","coc-node"]}` to `~/.openclaw/openclaw.json` |
Full per-command troubleshooting lives at the end of `references/backup.md` and `references/config.md`.
## Cross-host restore — read BEFORE you blanket-overwrite (1.2.4+)
The most dangerous restore scenario: backup made on host A (e.g. `$HOME=/home/node`), restoring on host B (`$HOME=/home/baominghao`). The backup's files contain absolute paths to host A; literal copies will (a) fake history, (b) corrupt SQLite if anyone tries byte-level `sed`, and (c) **wipe out host B's `gateway.auth` configuration**, locking the operator out with a 1008 right after restart.
**The agent must ask the user before any cross-host restore.** Don't auto-overwrite. Three-class policy:
| Class | What | Example fields | What restore does |
|---|---|---|---|
| **A. Runtime config (paths)** | Where on disk to read/write today | `agentDir`, `models.json` paths, `latest-recovery.json` `targetDir` | **Rewrite** old `$HOME` → new `$HOME`, structured (JSON parse, not `sed`) |
| **B. Historical content** | Records of past events | `sessions/*.jsonl`, `observations.{narrative,facts,files_*}`, `semantic-snapshot.json` | **Leave intact**. Rewriting fakes history. claw-mem doesn't blindly open these paths anyway. |
| **C. Host-local policy** | Belongs to **this** host's operator | `gateway.auth.*`, `gateway.bind`, `gateway.port`, `plugins.allow`, target-host provider keys | **Preserve target host's existing values** — never overlaid by backup |
**Auth-mode warning, in particular:** `gateway.auth.mode` and `.token` / `.password` belong to the host, not the agent. After restoring, always re-check:
```bash
jq '.gateway.auth.mode' ~/.openclaw/openclaw.json
```
Pick the matching TUI flag — `--token` only works when `mode = "token"` AND `.token` is non-null. If the active mode is `password` or `trusted-proxy`, `jq -r .gateway.auth.token` returns the literal string `"null"` and TUI sends that, which the gateway rejects with 1008. **Don't reflexively use `--token` after a restore — read the active mode first.**
Full procedure with command-line examples: `references/backup.md` → "Cross-host restore: directory-mismatch handling" + "Auth-mode preservation rule".
## Post-backup messaging contract (1.2.6+)
**After every successful `backup create`, the agent MUST relay the recovery info to the user.** The CLI 1.2.6+ prints it; agents that wrap the CLI must pass it through, not swallow it. The user needs four things to be able to restore later:
1. **The manifest CID** (`b.manifestCid`, e.g. `bafy...`) — what to ask for at restore time.
2. **The signing-key location** — where the private key needed to read the encrypted backup lives. One of:
- `~/.claw-mem/keys/agent.key` (default keystore, mode `0600`, auto-generated when `backup.privateKey` is unset; resolution chain: `$COC_SOUL_KEYSTORE_PATH` → `$OPENCLAW_STATE_DIR/coc-soul/keys/agent.key` → `~/.claw-mem/keys/agent.key`)
- `backup.privateKey` in `~/.openclaw/openclaw.json` (when operator set it explicitly)
3. **The encryption mode** — `none` / `privateKey` / `password` — determines whether `--password` is needed at restore time.
4. **The recovery package path** — `<sourceDir>/.coc-backup/latest-recovery.json` — small JSON file with all of the above pre-formatted; copy this off-host alongside the key for fast restore.
The CLI emits this block:
```
Backup complete (full):
manifest: bafyabc...
files: 127
bytes: 4194304
merkleRoot: 0xabc...
txHash: 0xdef...
Recovery info — keep this safe to restore on another host:
recovery package: /home/<user>/.openclaw/.coc-backup/latest-recovery.json
encryption mode: privateKey
signing key file: /home/<user>/.claw-mem/keys/agent.key (mode 0600 — copy off-host securely)
signer address: 0x...
To restore on another host (always restore to /tmp first, verify, then promote):
openclaw coc-soul backup restore --manifest-cid bafyabc... \
--target-dir /tmp/openclaw-restore-test
(if you also have /home/<user>/.openclaw/.coc-backup/latest-recovery.json on the target host:)
openclaw coc-soul backup restore --latest-local --target-dir /tmp/openclaw-restore-test
```
**Agent responsibilities when displaying this:**
- Echo the **manifest CID** verbatim (it's how the user later asks "restore my backup `bafy...`")
- Echo the **signing key file path** verbatim — this is the file the user must back up off-host (encrypted USB / passphrase-protected vault / hardware security module). **Do NOT print the key contents themselves.**
- Echo the **`To restore on another host`** block verbatim — operators on the recovery host will copy-paste it
- If the encryption mode is `password`, remind the user that `--password '<value>'` is required at restore time and they must remember it (or store it securely separately)
For agents running headless (no user attention right now): the same info is persisted to `~/.openclaw/.coc-backup/latest-recovery.json` automatically — operators can read it later via `cat` or `openclaw coc-soul backup status --json`.
---
## Relationship with claw-mem2db
claw-mem and coc-soul are **separate, decoupled skills**. Each works on its own; together they cover complementary halves of "agent persistence":
| Skill | Owns | What changes when paired |
|---|---|---|
| [claw-mem2db](https://clawhub.ai/ngplateform/claw-mem2db) | Local memory: chat + tool capture, FTS5 search, hybrid recall, in-process injection | Claw-mem itself doesn't change. Soul opportunistically reads the SQLite DB. |
| **coc-soul** | On-chain DID, IPFS backup, guardian recovery, carrier resurrection | When claw-mem's DB is detected at startup, every backup adds a `semantic-snapshot.json` slice (top-N observations + summaries within `tokenBudget`) to the manifest. On recovery, that snapshot is restored alongside the rest of the agent home. |
**Detection is automatic and silent.** At plugin activation, soul probes the same dataDir chain claw-mem uses (`$CLAW_MEM_DATA_DIR` → `$OPENCLAW_STATE_DIR/claw-mem` → `~/.claw-mem`) and logs one of two lines:
- `[coc-soul] claw-mem detected at <path> — semantic snapshot ... will be included in each backup`
- `[coc-soul] claw-mem not detected — backups will skip the semantic snapshot (install @chainofclaw/claw-mem alongside soul to enable memory replay on recovery)`
No coupling at the npm-dependency level: soul does not depend on the `@chainofclaw/claw-mem` package. It just opens the SQLite DB read-only when present and reads two tables (`observations`, `session_summaries`). If the DB schema is absent or unreadable, soul logs a warning and moves on — backup never fails because of a memory hiccup.
## Data dir alignment with claw-mem (1.2.0+)
Soul writes its own files (keystore, config.json) to the same root as claw-mem by default — `~/.claw-mem` — so the two plugins share one operator-managed directory. Resolution priority (matches claw-mem's chain):
1. `plugins.entries.coc-soul.config.backup.dataDir` (per-instance plugin config, when set)
2. `$CLAW_MEM_DATA_DIR` (shared with claw-mem)
3. `$OPENCLAW_STATE_DIR/coc-soul` (sandboxed-host fallback, soul-specific subdir)
4. `~/.claw-mem` (default)
5. `~/.openclaw/state/coc-soul` (1.2.2+ auto-fallback when the default is owned by the wrong uid — typical multi-user Docker host)
If none of these are writable, soul **fails fast at activation** with a copy-paste-ready EACCES message (each candidate path, the resolved `getuid()` + `HOME`, and a one-line fix). No silent `/tmp` fallback. No half-broken backup runs.
## Mental model
Every AI agent is identified by a `bytes32 agentId`, controlled by an EOA (owner). The skill covers five concerns:
| Area | What it does |
|---|---|
| **DID** | Register the agent on-chain, manage verification methods (keys), delegate capabilities, anchor verifiable credentials, record lineage (fork relationships) |
| **Backup** | Encrypt + upload agent state (identity / config / memory / chat / workspace / DB) to IPFS, anchor the manifest CID in SoulRegistry. With claw-mem present, also includes a token-budgeted semantic snapshot of recent observations + summaries. |
| **Guardian** | Designate trusted accounts that can jointly recover or resurrect the agent |
| **Recovery** | Social recovery flow — guardians collectively migrate the owner to a new address. The semantic snapshot rides along, so the recovered agent gets its memory context back too. |
| **Carrier** | Register a hosting node that can resurrect offline agents |
## Zero-config on COC testnet (1.1.6+)
**Out of the box, no setup is required to run against COC testnet.** A fresh `openclaw plugins install @chainofclaw/soul` lands an agent that can immediately query the chain, register a soul, and run backups. Specifically, on first activation the plugin:
1. **Auto-generates an agent EOA** if `backup.privateKey` is empty. The key file is written with mode `0o600` to one of (in priority order):
- `$COC_SOUL_KEYSTORE_PATH` (operator override)
- `$OPENCLAW_STATE_DIR/coc-soul/keys/agent.key` (set by OpenClaw inside its sandbox — the typical path)
- `~/.claw-mem/keys/agent.key` (standalone default)
The chosen path and resulting agent address are logged: `[coc-soul] auto-generated agent key at <path>` and `[coc-soul] agent address: 0x…`.
2. **Auto-drips testnet COC** to the new EOA from the public faucet (`backup.faucetUrl` defaults to `http://199.192.16.79:3003`, 10 COC per drip, 24h per-address cooldown). Logs: `[coc-soul] faucet dripped 10.0 COC to 0x… (tx 0x…)`. So the very first `openclaw coc-soul backup init` already has gas.
3. **Defaults `rpcUrl`, `ipfsUrl`, `contractAddress`, `didRegistryAddress`** to the live COC testnet (RPC `199.192.16.79:28780`, IPFS `199.192.16.79:28786`, deployed SoulRegistry / DIDRegistry).
**You do NOT need to set any of these manually for testnet usage.** The agent should `openclaw coc-soul backup init` directly. Override fields only when targeting mainnet, a private testnet, or an existing wallet.
To bypass the keystore (e.g. use a wallet you already have): set `backup.privateKey` in config. To disable the auto-faucet (mainnet): set `backup.faucetUrl: ""`.
## How to invoke
**Inside OpenClaw (recommended — works automatically after `plugins install`):**
```bash
openclaw coc-soul backup status
openclaw coc-soul did delegations --agent-id 0x...
```
**Standalone bin (only if you ran `npm i -g @chainofclaw/soul` separately):**
```bash
coc-soul backup status
```
> `openclaw plugins install` does NOT install the standalone `coc-soul` binary into your PATH. Use `openclaw coc-soul ...` (with the `openclaw` prefix), or install the bin globally via npm if you want the bare command.
## Typical flows
1. **First-time soul registration + backup (zero config)** — Just run `openclaw coc-soul backup init`. The plugin auto-generates the agent EOA, auto-drips testnet COC for gas, then registers on SoulRegistry and runs the first full backup. No manual privateKey, no manual faucet, no manual contract addresses. Watch the activation logs to see the chosen keystore path and the agent address.
2. **Periodic incremental backup** — `openclaw coc-soul backup create` (auto runs hourly if `backup.autoBackup: true`).
3. **Inspect agent state** — `openclaw coc-soul backup status` (summary), `openclaw coc-soul backup doctor` (actionable recommendations).
4. **Delegation** — `openclaw coc-soul did delegate --delegator <agentId> --delegatee <targetId> --scope <hash> --expires <epoch> --depth 0`.
5. **Guardian setup** — `openclaw coc-soul guardian add --agent-id <id> --guardian 0x...` (repeat for each guardian).
6. **Emergency recovery** (you lost your owner key) — a guardian runs `openclaw coc-soul recovery initiate`, the quorum approves via `recovery approve`, then after timelock `recovery complete`.
7. **Resurrection as carrier** — `openclaw coc-soul carrier register --endpoint https://...` on the hosting node; `openclaw coc-soul carrier start` runs the daemon.
## When NOT to use this skill
- Running a COC chain node yourself — use [coc-node](https://clawhub.ai/ngplateform/coc-node).
- Local semantic memory **only** (no chain backup needed) — use [claw-mem2db](https://clawhub.ai/ngplateform/claw-mem2db) on its own. Add coc-soul on top later if you decide you want the data on-chain.
- Smart contract deployment — that lives in the [COC source repo](https://github.com/NGPlateform/COC) `contracts/` tree.
## Reference
Detailed references live alongside this file:
- `references/did.md` — full `did` subcommand tree, delegation semantics, ephemeral identities, credentials, lineage
- `references/backup.md` — backup / restore / prune flows, encryption, semantic snapshot, categories
- `references/guardian-recovery.md` — guardian lifecycle + social recovery timelock + quorum rules
- `references/carrier.md` — carrier registration, daemon modes, resurrection request flow
- `references/config.md` — complete `backup.*` + `carrier.*` config schema
Source and issue tracker: <https://github.com/NGPlateform/claw-mem/tree/main/packages/soul>.
FILE:references/config.md
# `backup.*` + `carrier.*` config schema
Read from `~/.chainofclaw/config.json` (or `$COC_SOUL_CONFIG`). The skill looks at the `backup` key; carrier config lives nested under `backup.carrier`.
```json
{
"backup": {
"enabled": true,
"sourceDir": "~/.openclaw",
"rpcUrl": "http://localhost:18780",
"ipfsUrl": "http://localhost:5001",
"contractAddress": "0x...SoulRegistry...",
"didRegistryAddress": "0x...DIDRegistry...",
"rpcAuthToken": "optional-for-gated-rpcs",
"privateKey": "0x...",
"autoBackup": true,
"autoBackupIntervalMs": 3600000,
"maxIncrementalChain": 10,
"encryptMemory": false,
"encryptionPassword": "...",
"backupOnSessionEnd": true,
"semanticSnapshot": {
"enabled": true,
"tokenBudget": 8000,
"maxObservations": 50,
"maxSummaries": 10
},
"categories": {
"identity": true,
"config": true,
"memory": true,
"chat": true,
"workspace": true,
"database": true
},
"carrier": {
"enabled": false,
"carrierId": "0x...",
"agentEntryScript": "/path/to/agent-boot.sh",
"workDir": "/tmp/coc-resurrections",
"watchedAgents": [],
"pollIntervalMs": 60000,
"readinessTimeoutMs": 86400000,
"readinessPollMs": 30000
}
}
}
```
## Critical fields
| Field | Required? | Notes |
|---|---|---|
| `rpcUrl` | yes | Must reach a COC node (local or public testnet) |
| `contractAddress` | yes for write | SoulRegistry deployment address |
| `didRegistryAddress` | yes for DID ops | |
| `ipfsUrl` | yes for backup | Default `http://127.0.0.1:5001` (local Kubo) |
| `privateKey` | yes for write | Use `chmod 600` on the config file |
## Key handling
For testnet the anvil default key works fine. For mainnet:
- Do **not** commit this file to git
- Prefer hardware signer or cloud KMS (not currently supported by the CLI — in roadmap)
- Keep the file mode `600`
## Backup chain limit
`maxIncrementalChain: 10` means after 10 incremental backups, the next one is forced to full. This bounds restore time.
## Where config actually comes from (OpenClaw plugin mode)
When running through `openclaw coc-soul ...` (plugin mode), the **authoritative** source is `~/.openclaw/openclaw.json` under:
```jsonc
{
"plugins": {
"entries": {
"coc-soul": {
"enabled": true,
"config": {
"backup": {
// ...same shape as the standalone schema above...
}
}
}
}
}
}
```
The standalone `coc-soul` bin still reads `~/.chainofclaw/config.json` / `$COC_SOUL_CONFIG`, but in plugin mode plugin config wins.
## Minimal viable config (testnet)
`coc-soul` ships testnet defaults for `rpcUrl` / `ipfsUrl` / `contractAddress` / `didRegistryAddress` / `faucetUrl`. Minimal explicit config:
```jsonc
{
"plugins": {
"entries": {
"coc-soul": {
"enabled": true,
"config": {
"backup": { "enabled": true }
}
}
}
}
}
```
If `backup.privateKey` is absent, soul auto-generates an agent EOA + auto-drips testnet COC. First `openclaw coc-soul backup init` works immediately.
## dataDir + keystore resolution chains (1.2.2)
### Soul data dir (where keystore + scratch land)
Priority — first writable wins, fail-fast EACCES with a copy-paste fix at the bottom:
1. `plugins.entries.coc-soul.config.backup.dataDir`
2. `$CLAW_MEM_DATA_DIR` (shared with @chainofclaw/claw-mem)
3. `$OPENCLAW_STATE_DIR/coc-soul`
4. `~/.claw-mem` (default, shared with claw-mem)
5. `~/.openclaw/state/coc-soul` (1.2.2+ auto-fallback when default's parent is owned by the wrong uid — typical multi-user Docker host)
No `/tmp` fallback for durable identity state.
### Keystore (agent.key) priority
1. explicit `keyPath` (internal call sites)
2. `$COC_SOUL_KEYSTORE_PATH`
3. `$OPENCLAW_STATE_DIR/coc-soul/keys/agent.key`
4. `~/.claw-mem/keys/agent.key`
File mode is enforced to `0600` on write.
## Recommended overrides (production)
- `backup.privateKey` — supply explicitly only if you don't want auto-generated keystore
- `backup.encryptMemory: true` + `backup.encryptionPassword` — encrypt the memory payload before IPFS upload (otherwise it's plaintext at the CID)
- `backup.carrier.workDir` — override the default `/tmp/coc-resurrections` to a persistent path (e.g. `~/.openclaw/state/coc-soul/carrier`); `/tmp` is wiped on reboot mid-resurrection
- `plugins.allow: ["claw-mem", "coc-soul", "coc-node"]` — explicit trusted plugin list at the openclaw.json root, so the gateway stops warning `plugins.allow is empty`
## Docker / container deployment
- One persistent volume must back the soul data dir. If container FS is ephemeral, mount a host path for `~/.claw-mem` or set `CLAW_MEM_DATA_DIR` to mounted storage.
- If the in-container scheduler is unavailable, drive periodic `backup heartbeat` from the host (cron / systemd timer that calls `docker exec`).
## Failure-mode triage
| Symptom | Cause | Action |
|---|---|---|
| `data dir not writable` at activation | `~/.claw-mem` owned by another uid | 1.2.2+ auto-falls-back to `~/.openclaw/state/coc-soul`; older versions: `export CLAW_MEM_DATA_DIR=~/.openclaw/state` and restart |
| `backup` reports "not configured" | missing `contractAddress` or `privateKey` for the active network | `backup doctor --json` shows which field is empty |
| Carrier daemon no-ops | `backup.carrier.enabled: false` or required fields blank | Set both, restart |
| Unexpected key source loaded | env var override winning over config | Print `openclaw coc-soul did keys --agent-id <id>` to confirm; check `$COC_SOUL_KEYSTORE_PATH` and `$OPENCLAW_STATE_DIR` |
| `[gateway] plugins.allow is empty` warning | no allow-list set | `jq '.plugins.allow = ["claw-mem","coc-soul","coc-node"]' ~/.openclaw/openclaw.json > /tmp/oc && mv /tmp/oc ~/.openclaw/openclaw.json` |
FILE:references/backup.md
# `coc-soul backup` — soul backup and restore
## First-time
- `backup init` — register the agent on SoulRegistry (if not yet registered), run a first **full** backup, write `~/.coc-backup/latest-recovery.json` with the decryption material + manifest CID.
- `backup register` — register on-chain only, do not run a backup.
## Periodic
- `backup create` — incremental (default); `--full` forces a full backup regardless of chain length.
- `backup.autoBackup: true` + `backup.autoBackupIntervalMs` runs this on a timer inside the OpenClaw plugin.
- `backup.backupOnSessionEnd: true` + a `session_end` hook from OpenClaw also triggers `backup create` when the agent's session closes.
### Output: `backup create` recovery summary (1.2.6+)
Every successful `backup create` prints two blocks. The first is the receipt; the second is what the user needs to restore the backup later — relay it verbatim to the user (don't swallow it):
```
Backup complete (full):
manifest: <cid>
files: <n>
bytes: <n>
merkleRoot: 0x...
txHash: 0x... # only present if anchored on-chain
Recovery info — keep this safe to restore on another host:
recovery package: <sourceDir>/.coc-backup/latest-recovery.json
encryption mode: none | privateKey | password
signing key file: <path> # mode 0600 — copy off-host securely
signer address: 0x...
To restore on another host (always restore to /tmp first, verify, then promote):
openclaw coc-soul backup restore --manifest-cid <cid> \
--target-dir /tmp/openclaw-restore-test [ --password '<pw>' ]
(if you also have <path>/latest-recovery.json on the target host:)
openclaw coc-soul backup restore --latest-local --target-dir /tmp/openclaw-restore-test [ --password '<pw>' ]
```
The `--password` clause appears only when `encryption mode = password`. In `privateKey` mode, the operator must instead make sure the right key is loaded on the target host (either by copying the keystore file or by setting `backup.privateKey` in target's config).
The same fields are persisted in `<sourceDir>/.coc-backup/latest-recovery.json` (a small JSON written atomically after every backup) so the info survives even if the operator missed the terminal output:
```jsonc
{
"version": 1,
"agentId": "0x...",
"latestManifestCid": "bafy...",
"anchoredAt": 1777180566,
"txHash": "0x...",
"dataMerkleRoot": "0x...",
"backupType": "full" | "incremental",
"encryptionMode": "none" | "privateKey" | "password",
"requiresPassword": false | true,
"recommendedRestoreCommand": "openclaw coc-soul backup restore --latest-local --target-dir /tmp/openclaw-restore-test ..."
}
```
Treat both `latest-recovery.json` AND the signing-key file as a pair — back them up together (the manifest CID is also visible on-chain via the SoulRegistry contract, so even losing `latest-recovery.json` is recoverable from `backup find-recoverable --on-chain`, but losing the key means the encrypted payload is permanently unreadable).
## Inspect
- `backup status` — concise: chain registration state, last backup time, IPFS reachability
- `backup doctor` — structured diagnosis with actionable `recommended actions`. Use when something feels off.
- `backup list` / `backup history` — local archive table
## Restore (safety-first)
**Default: restore to `/tmp` first, verify, then promote.** Never overwrite a production directory with an unverified backup.
### Pre-restore inspection
If a local recovery package exists, read it first to know which mode + key the backup was written with:
```bash
cat ~/.openclaw/.coc-backup/latest-recovery.json
```
Capture:
- `latestManifestCid` — what to restore
- `encryptionMode` — `privateKey` | `password` | `none`
- `requiresPassword` — whether `--password` is required
### Restore commands (always to a temp dir first)
From local package:
```bash
openclaw coc-soul backup restore \
--package /path/to/latest-recovery.json \
--target-dir /tmp/openclaw-restore-test
```
From the latest local package (most common):
```bash
openclaw coc-soul backup restore \
--latest-local \
--target-dir /tmp/openclaw-restore-test
```
From a manifest CID:
```bash
openclaw coc-soul backup restore \
--manifest-cid <CID> \
--target-dir /tmp/openclaw-restore-test
```
### encryptionMode handling
- `encryptionMode: "password"` (or `requiresPassword: true`) → add `--password '<your-password>'`
- `encryptionMode: "privateKey"` → **do NOT pass `--password`**; ensure the right `backup.privateKey` / keystore key is loaded
- `encryptionMode: "none"` → no extra flag needed
Mismatched mode is the #1 reason `Unsupported state or unable to authenticate data` shows up. Triage that error as mode/key mismatch before suspecting tampering.
### Verify before promoting
Success criteria:
- exit code `0`
- output contains `merkleVerified: true`
If verification passes, **only then** confirm with the user before copying / moving the restored tree onto the production path.
### Cross-host restore: directory-mismatch handling
A backup made on a host where `$HOME=/home/node` will contain absolute paths like `/home/node/.openclaw/...`. Restoring on a host with `$HOME=/home/baominghao` puts those paths in the agent state where they don't exist. Rather than blindly string-replacing every occurrence (which silently corrupts SQLite binaries and rewrites historical chat content), the operator decides per-restore.
**Step 1 — detect the mismatch.** After restore-to-temp completes, scan the restored tree:
```bash
# Look for paths that don't match the current $HOME
grep -lrE '/home/[a-zA-Z_-]+/\.openclaw' /tmp/openclaw-restore-test 2>/dev/null \
| head -20
```
If any hit is from a path that's **not** the current `$HOME`, you have a cross-host restore.
**Step 2 — explain to the user, get a decision.** Present three options:
1. **Read-only inspect** — leave paths as-is, mount the restored tree only for inspection. Useful when you just want to recover a specific file or audit history without resuming the agent.
2. **Smart rebase** (recommended for resuming the agent) — rewrite **only** runtime-config paths, leave historical content intact. See the three-class table below.
3. **Full literal overwrite** — what happens if you don't intervene; almost always wrong (corrupts history, can break SQLite).
**Step 3 — apply smart rebase.** Three classes of content, three different policies:
| Class | What it is | Examples | Policy |
|---|---|---|---|
| **A. Runtime config (paths)** | Settings the runtime reads to find files on disk now | `openclaw.json` `agentDir` / `paths.*`, `models.json` paths, `device.json`, `latest-recovery.json` `targetDir` / `sourceDir`, `context-snapshot.json` cwd refs | **Rewrite** old `$HOME` → new `$HOME`. Done structurally (JSON parse → field edit → re-emit), never via byte-level `sed` |
| **B. Historical content** | Records of past events that **were** at those paths when written | `agents/*/sessions/*.jsonl` (tool calls + outputs), `memory/main.sqlite` `observations.{narrative,facts,files_read,files_modified}`, `semantic-snapshot.json` summaries | **Leave intact**. Rewriting fakes history. claw-mem runtime never blindly opens those paths — it just searches FTS text. |
| **C. Host-local policy (CRITICAL — see auth section below)** | Settings that belong to **this** host's operator, not the agent | `gateway.auth.*`, `gateway.bind`, `gateway.port`, `plugins.allow`, host-specific provider keys in `models.json` | **Preserve target host's existing values** — don't overlay the backup's. The new host's operator already configured these for the new environment. |
The rebase routine should:
1. Parse target file as JSON / structured (not byte-level `sed`)
2. Edit only A-class fields
3. For C-class fields in `openclaw.json`, **merge** rather than overwrite: keep the target host's existing `gateway.auth.*` / `gateway.bind` / `plugins.allow` exactly; only adopt the backup's agent-portable fields
4. Skip B-class files entirely
5. Write a `rebase-report.json` next to the restored tree so operators can audit what changed
**Step 4 — auth: re-confirm before launching the gateway.**
After rebase, dump the effective `gateway.auth` and use the matching TUI invocation:
```bash
jq '.gateway.auth' ~/.openclaw/openclaw.json
```
| `auth.mode` | TUI invocation |
|---|---|
| `"token"` | `openclaw tui --token "$(jq -r .gateway.auth.token ~/.openclaw/openclaw.json)"` |
| `"password"` | `openclaw tui --password '<password>'` |
| `"trusted-proxy"` | `openclaw tui --password '<password>'` (if header-based auth fronts the gateway, trust the proxy header in dev; otherwise pass `--password`) |
| `"none"` | `openclaw tui` |
**Common pitfall**: post-restore, `openclaw tui --token "$(jq -r .gateway.auth.token openclaw.json)"` returns the literal string `null` when the active auth mode no longer has a `.token` field (e.g. `mode` is now `password` or `trusted-proxy`). The TUI dutifully sends `null` and the gateway rejects with **1008**. Always re-read `auth.mode` after restore and pick the matching flag.
### Auth-mode preservation rule (must read before any production restore)
The most common production-breaking restore mistake: backup contains `gateway.auth.mode = "token"` with a valid token from the source host; target host has been carefully configured with `mode = "trusted-proxy"` or `"password"`. A literal-overwrite restore replaces target's auth, then the operator on target can't log in anymore — and **the backup's token is for a different gateway instance, useless on this host**.
Rule: **`gateway.auth` is a property of the host, not of the agent.** It does not get restored. The smart-rebase path explicitly preserves the target host's `gateway.auth.*` block. If you must do a literal overwrite (e.g. recovering on a fresh host with no existing config), regenerate auth before starting the gateway:
```bash
openclaw gateway init --auth password
# or whatever mode the new host should use
```
### Discover what this key can restore
```bash
openclaw coc-soul backup find-recoverable --json # local index
openclaw coc-soul backup find-recoverable --on-chain --json # walk the chain
```
## Prune
`backup prune` only touches **local archive index entries**, not IPFS pins.
```bash
openclaw coc-soul backup prune --older-than 30 --keep-latest 1 --dry-run
openclaw coc-soul backup prune --older-than 30 --keep-latest 1
```
## What gets backed up — file patterns (1.2.9)
The backup walks `~/.openclaw/` (or the configured `backup.sourceDir`) and captures files that match a built-in classifier. Patterns explicitly support **both** the legacy root-level layout and the current `workspace/`-prefixed layout that OpenClaw uses, so the backup picks up identity / memory files no matter which version of OpenClaw wrote them.
### Identity (markdowns; root **or** `workspace/`; not encrypted)
- `IDENTITY.md` — agent identity declaration (where the agent's name lives)
- `SOUL.md` — soul configuration
- `BOOTSTRAP.md` — bootstrap / setup instructions (1.2.9+)
### Memory (markdowns; root **or** `workspace/`; not encrypted)
- `MEMORY.md`
- `USER.md`
- `RECOVERY_CONTEXT.md` (regenerated on restore)
- everything under `memory/*.md` **or** `workspace/memory/*.md` (1.2.9+) — daily / per-topic notes (`workspace/memory/2026-04-27.md`, `workspace/memory/topic-foo.md`, etc.)
### Workspace (markdowns + state; root **or** `workspace/`; not encrypted)
- `AGENTS.md`
- `TOOLS.md` — tools manifest (1.2.9+)
- `HEARTBEAT.md` — soul's own heartbeat file (1.2.9+; soul writes it, soul backs it up)
- `workspace-state.json` (root location, legacy)
- `workspace/.openclaw/workspace-state.json` (current OpenClaw layout, 1.2.9+)
### Identity / config (fixed paths)
- `identity/device.json` (config, **encrypted**)
- `identity/device-auth.json` (config, **encrypted**, 1.2.9+ — paired with device.json for cross-device auth)
- `auth.json` (config, **encrypted**)
- `openclaw.json` (config, **encrypted**)
- `agents/<id>/agent/models.json` (config, **encrypted**, 1.2.9+ — holds literal LLM API keys after the 1.2.6 persistence change; **MUST** stay encrypted)
- `exec-approvals.json` (config, **encrypted**, 1.2.9+ — Bash / tool approval rules)
- `plugins/*/openclaw.plugin.json` (config, not encrypted)
- `credentials/*` (config, **encrypted**)
### Chat (not encrypted)
- `agents/*/sessions/*.jsonl`
- `agents/*/sessions/sessions.json`
### Database (encrypted)
- `memory/*.sqlite` (and SQLite WAL/SHM siblings)
- `memory/lancedb/*`
### Metadata
- `.coc-backup/context-snapshot.json` (workspace, auto-generated)
- `.coc-backup/semantic-snapshot.json` (memory, auto-generated)
### Walker descends into hidden dirs (`.`-prefixed) — allow-list
Walker skips `.`-prefixed directories by default to avoid pulling `.git/`, `.cache/`, etc. Three names are allow-listed: `.claude` (historical), `.coc-backup` (snapshot metadata), `.openclaw` (workspace state — added 1.2.9 to reach `workspace/.openclaw/workspace-state.json`). To extend the allow-list, edit `scanFiles()` in `src/backup/change-detector.ts`.
## What is intentionally excluded — denylist (1.2.10+)
Even when files are in a backed-up directory, the walker / classifier explicitly excludes the following to avoid wasted IO, host-cross-contamination, and circular references:
### Host-local secrets — must NEVER travel between hosts
| File | Why |
|---|---|
| `agents/<id>/agent/models.json` | LLM provider config; post-1.2.6 holds literal API tokens (`ANTHROPIC_AUTH_TOKEN` etc.). Each host has its own provider keys; copying source's keys to target is at best a leak, at worst breaks the target host's auth. Restore the agent, then re-configure provider on target via `openclaw infer model auth login`. |
| `agents/<id>/agent/auth-profiles.json` | OAuth profiles. Same reason — host-local credential state. |
### Operator audit copies — file-name patterns skipped at walker level
Skipped regardless of which directory they appear in:
- `*.bak`, `*.bak.<n>` — operator's manual backups
- `*.pre-<label>` — operator's pre-change snapshots (`openclaw.json.pre-allowlist`, `models.json.pre-llm-config`)
- `*.rejected.<iso-ts>` — config writes the gateway rejected (`openclaw.json.rejected.2026-04-23T09-35-42-752Z`)
- `*.last-good` — last known-good config marker
- `stale-*-backup-*.tar.gz` — operator's self-archives
### Install / restore audit dirs — pruned at walker level
Walker never enters these directories:
- `.git/` — git-managed history; backed up via git itself, not via soul
- `node_modules/` — re-installed per host via `openclaw plugins install`
- `.openclaw-install-backups/` — `openclaw plugins install` rotation copies
- `.restore-overwrite-backup-<ts>/` — pre-restore audit copy of openclaw home (left behind by previous restore-overwrite operations)
### Circular-reference state
| File | Why |
|---|---|
| `.coc-backup/state.json` | Holds `lastManifestCid` + `incrementalCount` — the **head of the backup chain itself**. Including it in a new backup creates a circular reference: the chain head would point at a state that doesn't exist yet. The chain head is restored by reading the manifest hierarchy on the target host, not by copying the source's pointer. |
### Already excluded by virtue of not matching FILE_RULES
These are skipped because nothing in the whitelist matches them — they're listed here so the design intent is explicit:
- `extensions/**` — plugin install dir; reinstalled per host
- `flows/registry.sqlite`, `tasks/runs.sqlite` (+ WAL/SHM siblings) — operational state, not portable
- `logs/**`, `canvas/**`, `update-check.json` — regenerable
- `*.sqlite-wal`, `*.sqlite-shm` — SQLite write-ahead-log artifacts; the main `.sqlite` carries everything needed
- `agents/*/sessions/*.jsonl.reset.<ts>` — operator-side session-reset markers
- `workspace/.git/**` (also denylisted explicitly above for defense in depth)
### Adding to the denylist
If you find another file that's leaking into manifests it shouldn't, edit:
- **dir-name skip**: `SKIP_DIRS_BY_NAME` or `SKIP_DIR_NAME_PATTERNS` in `src/backup/change-detector.ts`
- **file-name skip**: `SKIP_FILE_NAME_PATTERNS`
- **specific path skip**: `SKIP_FILE_RELATIVE_PATHS`
- pair with a regression test in `test/backup-suite/change-detector-extended.test.ts`
### Files outside this whitelist are NOT backed up
If you put important state in `~/.openclaw/<custom-dir>/` and the path doesn't match any pattern above, it will be silently skipped — extend the pattern set in `src/backup/change-detector.ts` and bump soul minor version if you need a new shape covered. **Always pair a pattern addition with a regression test** in `test/backup-suite/change-detector-extended.test.ts`.
### Upgrade notes
| From | What to do after upgrade |
|---|---|
| **pre-1.2.7** (no workspace/ prefix support) | Run `openclaw coc-soul backup create --full` once. Verify `workspace/IDENTITY.md` shows up in `backup list --json` file count + restore-to-`/tmp` smoke. |
| **1.2.7 / 1.2.8** | Run `backup create --full` once. New 1.2.9 patterns (`TOOLS.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `workspace/memory/*.md`, `workspace/.openclaw/workspace-state.json`, `identity/device-auth.json`, `agents/<id>/agent/models.json`, `exec-approvals.json`) will be added to the manifest. |
## Categories & semantic snapshot
`backup.categories.*` controls what gets bundled:
- `identity` — DID + keys
- `config` — OpenClaw / claw-mem config
- `memory` — SQLite memory DB
- `chat` — conversation history
- `workspace` — agent's working dir
- `database` — other DBs
`backup.semanticSnapshot` controls the compressed "agent context" snapshot included with each backup:
- `enabled` (default `true`) — pack a token-budgeted summary of memory
- `tokenBudget` (default 8000)
- `maxObservations` / `maxSummaries`
## Encryption
- `backup.encryptMemory: true` + `backup.encryptionPassword` — AES-GCM encrypt memory before IPFS upload
- Without encryption, backups are still integrity-checked via Merkle root but readable by anyone who fetches the CID
## Resurrection prep
- `backup configure-resurrection --resurrection-key-hash <bytes32> --max-offline-duration <seconds>` — set the "trigger" for an automatic resurrection request
- `backup heartbeat` — send a heartbeat so automatic resurrection doesn't fire
After both are set, verify with `backup doctor --json` — `resurrection.configured` must be `true`.
## CID + key disambiguation
| Term | What it is | Where it shows up |
|---|---|---|
| `latestManifestCid` | Latest restore point | `latest-recovery.json`, `backup status` |
| older full CID | Historical baseline restore point | `backup history` |
| identity CID / hash | Identity-content hash from registration | DID write commands — **not** a restore point |
Private keys (owner / resurrection / guardian) are **never** chat-safe. Don't transmit even split / encrypted fragments via chat.
## Failure-mode triage
| Symptom | Likely cause | Action |
|---|---|---|
| `Unsupported state or unable to authenticate data` | encryption mode / key mismatch | Re-read `latest-recovery.json` `encryptionMode` and use the matching `--password` (or none) |
| `429 rate limit exceeded` | IPFS gateway rate-limited | Retry with exponential backoff until `merkleVerified: true` |
| restore unavailable / blocked | chain not registered or no manifest | Run `backup doctor --json`; fix `chain.registered` first |
| `[gateway] unauthorized (1008)` | gateway auth / proxy mode wrong | Fix gateway auth (token / OAuth / proxy) before scheduled `heartbeat` |
| `[gateway] unauthorized (1008)` **right after restore** | restore overwrote `gateway.auth.mode` (was `token`, now `trusted-proxy` / `password`); old TUI command sends literal `null` token | `jq '.gateway.auth.mode' ~/.openclaw/openclaw.json` to see active mode; switch TUI invocation per the auth-mode table above. If smart-rebase wasn't used, restore target host's `gateway.auth.*` from the pre-restore backup at `~/.openclaw/.restore-overwrite-backup-*/openclaw.json` |
| Cross-host restored agent has stale paths in chat / observations | literal overwrite was used, OR smart-rebase ran on B-class history files (it shouldn't) | Roll those files back from `.restore-overwrite-backup-<ts>/` and re-run with smart-rebase scoped to A-class only |
| SQLite `PRAGMA integrity_check` reports errors after a manual rewrite | byte-level `sed` on `memory/main.sqlite` corrupted page offsets (string lengths changed) | Roll back `memory/main.sqlite` from backup, then use `UPDATE observations SET narrative = REPLACE(narrative, '<old>', '<new>')` etc. inside `sqlite3` (length-safe) and rebuild FTS: `INSERT INTO observations_fts(observations_fts) VALUES('rebuild')` |
| `ENOENT ... backup/targeting.js` | extension install corrupt / mismatched | `openclaw plugins install @chainofclaw/soul --dangerously-force-unsafe-install --force` |
| `data dir not writable` | `~/.claw-mem` owned by wrong uid | 1.2.2+ auto-falls-back to `~/.openclaw/state/coc-soul`; on older versions `export CLAW_MEM_DATA_DIR=~/.openclaw/state` and restart gateway |
FILE:references/did.md
# `coc-soul did` — DID identity management
Every subcommand acts on on-chain state. Read operations are free; write operations cost gas. **Flag names below are verified against the live CLI** — earlier revisions of this doc had the wrong names (e.g. `--key-hash` → really `--key-id`, `--cid` → really `--document-cid`).
## Preconditions for any write
1. `backup.didRegistryAddress` is configured
2. signer / private key is loaded and funded
3. target IDs / addresses are validated (bytes32 or 0x-address shape)
If `didRegistryAddress` is missing, every write subcommand fails early.
## Read commands (default first stop, no gas)
```bash
openclaw coc-soul did keys --agent-id <agentId> --json
openclaw coc-soul did delegations --agent-id <agentId> --json
```
Always `keys` / `delegations` **before** `revoke-*` / `update-*` to confirm the current state.
## Key management (write)
### Add a verification method
```bash
openclaw coc-soul did add-key \
--agent-id <agentId> \
--key-id <bytes32> \
--key-address 0x<address> \
--purpose <bitmask>
```
`--purpose` bitmask:
| Bit | Purpose |
|---|---|
| `1` | auth |
| `2` | assertion |
| `4` | capability invocation |
| `8` | capability delegation |
Example for an auth + assertion key: `--purpose 3`.
### Revoke a verification method
```bash
openclaw coc-soul did revoke-key \
--agent-id <agentId> \
--key-id <bytes32>
```
### Update the DID document CID
```bash
openclaw coc-soul did update-doc \
--agent-id <agentId> \
--document-cid <bytes32>
```
## Delegation
### Grant
```bash
openclaw coc-soul did delegate \
--delegator <agentId> \
--delegatee <agentId> \
--scope <bytes32> \
--expires <unix-ts> \
--parent <bytes32-or-zero> \
--depth 0
```
`--depth`:
- `0` (default) — leaf delegation; delegatee cannot re-delegate
- `1..3` — allow transitive re-delegation up to that many additional layers
### Revoke one delegation
```bash
openclaw coc-soul did revoke-delegation --delegation-id <bytes32>
```
### Emergency revoke all
```bash
openclaw coc-soul did revoke-all-delegations --agent-id <agentId>
```
## Credentials
### Anchor
```bash
openclaw coc-soul did anchor-credential \
--credential-hash <bytes32> \
--issuer <agentId> \
--subject <agentId> \
--credential-cid <bytes32> \
--expires <unix-ts>
```
### Revoke
```bash
openclaw coc-soul did revoke-credential --credential-id <bytes32>
```
## Ephemeral identities
### Create
```bash
openclaw coc-soul did create-ephemeral \
--parent <agentId> \
--ephemeral-id <bytes32> \
--ephemeral-address 0x<address> \
--scope <bytes32> \
--expires <unix-ts>
```
### Deactivate
```bash
openclaw coc-soul did deactivate-ephemeral --ephemeral-id <bytes32>
```
## Lineage + capabilities
### Record lineage (fork relationship)
```bash
openclaw coc-soul did record-lineage \
--agent-id <agentId> \
--parent <agentId> \
--fork-height <n> \
--generation <n>
```
### Update capability bitmask
```bash
openclaw coc-soul did update-capabilities \
--agent-id <agentId> \
--capabilities <uint16>
```
## EIP-712 signing
Delegation and credential anchoring use EIP-712 structured signatures. The CLI constructs the typed-data domain automatically using the configured RPC + `didRegistryAddress`. No manual signature flag is needed (`anchor-credential` does **not** take `--sig`).
## Easy-to-confuse flag map (real CLI vs old / sibling names)
| You might type | Actual flag |
|---|---|
| `--key-hash` | `--key-id` |
| `--verification-address` | `--key-address` |
| `--cid` (for update-doc) | `--document-cid` |
| `--parent-agent-id` (for record-lineage) | `--parent` |
| `--sig` (for anchor-credential) | (does not exist — signing is automatic) |
If you're unsure, run `openclaw coc-soul did <subcommand> --help` to confirm.
## DID is not backup
DID writes change identity-layer state (keys / delegations / credentials / lineage). They do **not** restore files or memory. For "recover this agent on another machine", see `references/backup.md` (restore path) or `references/guardian-recovery.md` (resurrection path).
FILE:references/guardian-recovery.md
# Guardians + social recovery
## Guardian set
Guardians are EOAs that can jointly initiate recovery or resurrection for an agent.
| Command | Effect |
|---|---|
| `guardian add --agent-id <id> --guardian 0x…` | Add a guardian (owner only) |
| `guardian remove --agent-id <id> --guardian 0x…` | Remove a guardian (owner only) |
| `guardian list --agent-id <id>` | List current guardians with ACTIVE / INACTIVE flags |
## Recovery flow (guardian-initiated owner migration)
Use when the owner has lost their private key but still has the guardians' trust.
1. Any guardian: `coc-soul recovery initiate --agent-id <id> --new-owner 0x…`
2. Other guardians approve: `coc-soul recovery approve --request-id <id>`
3. Once quorum (N-of-M) + timelock satisfied: `coc-soul recovery complete --request-id <id>`
4. Anytime before step 3, the **original** owner can veto: `coc-soul recovery cancel --request-id <id>`
5. `coc-soul recovery status --request-id <id>` shows current state at any point.
Quorum and timelock parameters are set on-chain at SoulRegistry deployment time. Typical config: 3-of-5 guardians with 48-hour timelock.
## Resurrection flow (agent-level)
Resurrection moves the agent's soul to a carrier so it can resume operation on different hardware. Distinct from recovery (which changes ownership). See [`carrier.md`](./carrier.md) for the carrier-side.
1. Guardian: `coc-soul guardian initiate --agent-id <id> --carrier-id <id>` — starts the request
2. Other guardians: `coc-soul guardian approve --request-id <id>`
3. Carrier: `coc-soul carrier submit-request --request-id <id>` — claim the request
4. `coc-soul guardian status --request-id <id>` — check readiness
Triggers (set via `backup configure-resurrection`):
- Explicit — a guardian manually initiates
- Offline — heartbeat missed for `maxOfflineDuration` seconds
- Key-hash — a pre-agreed key is submitted (disaster recovery)
## `recovery` vs `guardian` — don't conflate them
Both are guardian-touching, but they do different things:
| Subtree | Changes | Typical question |
|---|---|---|
| `coc-soul recovery ...` | **Owner address** of the agent (ownership migration after key loss) | "I lost the owner key — how do I transfer ownership to a new address?" |
| `coc-soul guardian initiate / approve / status` | **Resurrection request lifecycle** for moving the agent to a carrier | "Agent's host died — how do I get a carrier to pick it up?" |
| `coc-soul guardian add / remove / list` | **Guardian set membership** (owner-only admin) | "I want to change / add / list the guardian set" |
When you see "social recovery", clarify which one — the owner-migration `recovery` flow, or the guardian-mediated resurrection `guardian initiate` flow.
## Preconditions checklist (run before any recovery / resurrection action)
1. Agent is registered on-chain (`backup doctor --json` → `chain.registered: true`)
2. Guardian set is configured and reachable: `coc-soul guardian list --agent-id <id>`
3. Participants know the target `agentId` (bytes32)
4. For `recovery`: the new owner address is validated and signer-controlled
5. For resurrection (guardian-initiated): a registered carrier exists (`coc-soul carrier list`)
## Security rules
- Never transmit owner / resurrection / guardian **private keys** in chat — even split or encrypted fragments. Route key transfer through a local secure channel.
- It IS safe to share addresses, agent IDs, request IDs, transaction hashes.
- When users say "multisig" in this context, they mean the **guardian quorum threshold** (an N-of-M policy enforced at the SoulRegistry contract level), not a separate multisig wallet contract.
## Failure-mode triage
| Symptom | Cause | Action |
|---|---|---|
| `recovery approve` reverts with "not a guardian" | guardian set out of date | `guardian list --agent-id <id>` to confirm membership |
| `recovery complete` reverts before timelock | quorum reached but waiting period not elapsed | `recovery status --request-id <id>` shows `unlocksAt` — wait until past that timestamp |
| `recovery complete` reverts after timelock | owner cancelled mid-flight | `recovery status` will show `cancelled: true`; restart with a fresh `recovery initiate` |
| `guardian initiate` reverts with "carrier inactive" | target carrier deregistered or unavailable | `carrier list --include-inactive` to see all; pick an active one |
FILE:references/carrier.md
# Carrier operations
A **carrier** is a hosting node that can adopt and run an offline agent's soul. Carriers are discovered on-chain through `CarrierRegistered` events.
## Registration
| Command | Effect |
|---|---|
| `carrier register --carrier-id <id> --endpoint https://… --cpu-millicores 1000 --memory-mb 2048 --storage-mb 10240` | Publish availability on-chain |
| `carrier deregister --carrier-id <id>` | Remove |
| `carrier availability --carrier-id <id> --available <true\|false>` | Flip the available flag without deregistering |
## Discovery
- `carrier list` — scan `CarrierRegistered` / `CarrierDeregistered` events (auto-chunked by 10000 blocks since 1.0.8)
- `carrier info --carrier-id <id>` — fetch full record for a specific carrier
## Daemon
The carrier daemon watches its inbox of pending resurrection requests and orchestrates the agent spawn.
| Command | Effect |
|---|---|
| `carrier start` | Start the daemon (requires `backup.carrier.enabled: true` in config) |
| `carrier stop` | Graceful shutdown |
| `carrier status` | Is the daemon enabled + running? |
| `carrier submit-request --request-id <id>` | Hand a specific pending request to the local daemon |
## Resurrection inside the daemon
For each pending request the daemon:
1. Verifies the carrier has been explicitly confirmed by guardians
2. Downloads the agent's latest soul backup from IPFS
3. Decrypts with the resurrection key (provided by the initiating guardian)
4. Spawns the agent using `carrier.agentEntryScript` in `carrier.workDir`
5. Reports back on-chain that the agent is alive
## Configuration
See `backup.carrier.*` in the soul config schema. Critical knobs:
- `enabled` (default `false`) — safety gate
- `carrierId` — your on-chain carrier ID
- `agentEntryScript` — path to the script that boots an agent from unpacked state
- `workDir` (default `/tmp/coc-resurrections`, **strongly recommend overriding** to a persistent path like `~/.openclaw/state/coc-soul/carrier` — `/tmp` is wiped on reboot mid-resurrection)
- `pollIntervalMs` (default 60000)
- `readinessTimeoutMs` (default 86400000 = 24h)
## Preconditions checklist (before going live as a carrier)
1. `backup.carrier.enabled: true` in plugin config
2. `backup.carrier.carrierId` set to the bytes32 you registered on-chain
3. `backup.carrier.agentEntryScript` is an absolute path that exists and is executable
4. `backup.carrier.workDir` points to a **persistent** directory with enough disk for an extracted agent (NOT `/tmp` on a host with reboots)
5. The endpoint passed to `carrier register --endpoint` is actually reachable from the COC network
6. At least one resurrection drill completed end-to-end (initiate → approve → submit-request → agent boot)
## Failure-mode triage
| Symptom | Cause | Action |
|---|---|---|
| `carrier start` returns "carrier disabled" | `backup.carrier.enabled` is false | Set it true and restart the gateway |
| `carrier start` aborts with "missing carrierId / agentEntryScript" | required fields blank | Fill both in `~/.openclaw/openclaw.json` `plugins.entries.coc-soul.config.backup.carrier` |
| Carrier registered but `carrier list` doesn't show it | RPC pointed at wrong network OR event scan range too small | Check `backup.rpcUrl` matches the chain you registered on; for ancient carriers add `--from-block 0` |
| Daemon receives request but agent never boots | `agentEntryScript` exits non-zero, or `workDir` runs out of disk | Check daemon logs; verify `workDir` is on a writable, large-enough volume |
| Resurrection request stuck "awaiting carrier confirmation" | guardian quorum hasn't approved yet | `coc-soul guardian status --request-id <id>` to see approval count vs threshold |
## Security
- Carrier hosts run resurrected agent code with full state access. Treat the host as production.
- Use least-privilege: a dedicated user, restricted `agentEntryScript`, tight `plugins.allow` whitelist.
- Never accept resurrection-key fragments via chat. Out-of-band channels only.