Skill: ngplateform/coc-soul (version 1.2.10)

--- 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>.

path: references/config.md
content: # `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` |


path: references/backup.md
content: # `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 |


path: references/did.md
content: # `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).


path: references/guardian-recovery.md
content: # 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 |


path: references/carrier.md
content: # 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.


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)