# tablestore-mem

`tablestore-mem` is an OpenClaw memory plugin backed by Alibaba Cloud TableStore memory APIs via the `@tablestore/agent-storage` SDK (`AgentStorageClient`). It supports both **AccessKey (AK/SK)** and **API Key** authentication.

It provides:

- Automatic retrieval before each turn through `before_prompt_build` with hidden system-context injection
- Automatic incremental writeback after each successful run through `agent_end` (only the new turn, chunked to TableStore limits)
- Automatic offline **Dream** memory consolidation (background + on-demand) to deduplicate, supersede, and clean up long-term memory
- Full access to the memory-service API surface: every one of the SDK's 22
  memory methods is reachable through the CLI (see [Full Memory API Reference](#full-memory-api-reference))
- Slash commands:
  - `/tablestore-mem-search`
  - `/tablestore-mem-add`
  - `/tablestore-mem-get`
  - `/tablestore-mem-dream-status`
- CLI commands:
  - `openclaw tablestore-mem search`
  - `openclaw tablestore-mem add`
  - `openclaw tablestore-mem doctor`
  - `openclaw tablestore-mem dream`
  - `openclaw tablestore-mem call` (generic passthrough to any memory API)
  - `openclaw tablestore-mem memory get|update|delete`
  - `openclaw tablestore-mem stores list|delete`
  - `openclaw tablestore-mem messages` / `requests`
  - `openclaw tablestore-mem tasks list|get`
  - `openclaw tablestore-mem dream-tasks list|cancel`
  - `openclaw tablestore-mem dream-actions list|apply`

## Features

- Native OpenClaw memory slot plugin
- Minimal required configuration
- Automatic Beijing instance provisioning and reuse when instance config is omitted
- Shared `instance.json` reuse across CLI and conversation hooks
- Automatic memory store creation
- Hermes-style scope mapping adapted for OpenClaw
- Cross-agent and cross-run retrieval under the same tenant
- CLI for direct add/search verification
- CLI doctor command for diagnosing connection, memory store, and scoped memory listing

## Requirements

- Node.js `>=20`
- OpenClaw `>=2026.3.23`
- Valid TableStore access key pair

## Installation

Install from a local directory:

```bash
cd /root/openclaw/tablestore-mem
npm install
openclaw plugins install /root/openclaw/tablestore-mem
openclaw plugins enable tablestore-mem
```

For OpenClaw `2026.4.26+`, if you want automatic writeback through `agent_end`, you must explicitly trust this non-bundled plugin to read conversation content:

```json
{
  "plugins": {
    "entries": {
      "tablestore-mem": {
        "hooks": {
          "allowConversationAccess": true
        }
      }
    }
  }
}
```

This is an OpenClaw security policy flag, not a TableStore plugin business setting.

After publish, install from npm:

```bash
npm install -g @tablestore/openclaw-tablestore-memory
```

Or use it as a package source for your OpenClaw plugin deployment workflow.

## Authentication

The plugin accepts either authentication mode:

1. **AccessKey (AK/SK)** — full functionality, including managed-instance auto-create when the endpoint/instance are omitted.
   ```json
   { "accessKeyId": "<your-ak>", "accessKeySecret": "<your-sk>" }
   ```
2. **API Key** — set `apiKey`; it is used instead of AK/SK. Because the API Key only reaches the memory service (not the control plane), managed-instance auto-create is unavailable, so an explicit **https** `endpoint` and `otsInstanceName` are required.
   ```json
   { "apiKey": "<your-api-key>", "endpoint": "https://<instance>.cn-beijing.ots.aliyuncs.com", "otsInstanceName": "<instance>" }
   ```

Resolution: if `apiKey` is set it takes precedence; otherwise `accessKeyId` + `accessKeySecret` are required.

## Required Configuration

With AccessKey, only these two fields are required:

```json
{
  "accessKeyId": "<your-ak>",
  "accessKeySecret": "<your-sk>"
}
```

Example OpenClaw config:

```json
{
  "plugins": {
    "slots": {
      "memory": "tablestore-mem"
    },
    "entries": {
      "tablestore-mem": {
        "enabled": true,
        "hooks": {
          "allowConversationAccess": true
        },
        "config": {
          "accessKeyId": "<your-ak>",
          "accessKeySecret": "<your-sk>"
        }
      }
    }
  }
}
```

Important:

- `accessKeyId` and `accessKeySecret` are still the only required plugin business configuration values.
- `plugins.entries.tablestore-mem.hooks.allowConversationAccess=true` is a separate OpenClaw trust flag required by OpenClaw `2026.4.26+` if you want the plugin's `agent_end` writeback hook to run.
- If you omit that flag, CLI commands and automatic retrieval through `before_prompt_build` still work, but automatic writeback after a conversation turn is blocked by OpenClaw.

## Optional Configuration

These fields are optional:

- `otsInstanceName`: optional instance name override; if unset, the plugin auto-creates one managed instance in `cn-beijing` and reuses it later
- `endpoint`: optional endpoint override; if unset, the plugin derives `https://<instance>.cn-beijing.ots.aliyuncs.com`
- `appId`: optional override, defaults to `openclaw`
- `tenantId`: optional tenant override; if configured, it takes precedence over runtime session user identity
- `memoryStoreName`: defaults to `openclaw_mem`
- `memoryStoreDescription`: optional memory-store description applied at `CreateMemoryStore` time; defaults to `OpenClaw long-term memory`
- `extractInstructions`: optional memory-store-level custom extraction prompt (≤4096 chars) that guides which long-term memories are extracted; applied on store creation and reconciled onto an existing store via `UpdateMemoryStore`
- `autoCreateMemoryStore`: defaults to `true`
- `writebackEnabled`: defaults to `true`
- `writebackCursorPersist`: defaults to `true`; persists the per-session writeback position to disk (`$OPENCLAW_HOME/plugins/tablestore-mem/writeback-state.json`) so a gateway restart does not re-ingest the in-progress session's history. Set `false` for memory-only behavior
- `includeScores`: defaults to `true`
- `searchTopK`: defaults to `5`
- `minSimilarity`: defaults to `0` (no filter); `0..1` — drops retrieved memories whose normalized cosine similarity is below the threshold
- `minQueryLength`: defaults to `6`
- `enableRerank`: defaults to `true`
- `dreamEnabled`: defaults to `true`
- `dreamIntervalHours`: defaults to `24`
- `dreamMinIntervalHours`: defaults to `dreamIntervalHours`
- `dreamApplyMode`: defaults to `safe_auto` (or `proposal`)
- `dreamConfidenceThreshold`: defaults to `0.9` — the single confidence (0..1) `safe_auto` requires to auto-apply `add`/`update`/`merge` actions when no per-action override is set
- `dreamConfidenceThresholds`: optional per-action `safe_auto` thresholds, e.g. `{"add":0.95,"update":0.85,"merge":0.8}`; each is `0..1`, and any unset action falls back to `dreamConfidenceThreshold` (`DELETE` is never auto-applied)
- `dreamInstructions`: optional custom consolidation instructions (≤4000 chars) passed to every Dream task (background, `session_end`, and CLI)
- `dreamMaxScopesPerRun`: defaults to `20` (max `20`)
- `dreamOnSessionEnd`: defaults to `true`

If `openclaw_mem` does not exist, the plugin creates it automatically on first use.

If both `otsInstanceName` and `endpoint` are omitted, the plugin:

- creates a TableStore VCU instance in `cn-beijing` on first use through the control-plane `CreateVCUInstance` API
- waits for the instance status to become ready
- updates the instance ACL through `UpdateInstance` to enable public data-plane access
- waits for the data endpoint DNS to become resolvable before first data-plane use
- persists the created `instanceName` and derived endpoint under the local OpenClaw home
- reuses that same instance on later runs

Connection resolution priority:

1. If `otsInstanceName` is configured, use it.
2. If `endpoint` is also configured, use that endpoint.
3. If `otsInstanceName` is configured but `endpoint` is omitted, derive `https://<otsInstanceName>.cn-beijing.ots.aliyuncs.com`.
4. If only `endpoint` is configured and it matches `https://<instance>.cn-beijing.ots.aliyuncs.com`, parse the instance name from the endpoint and use it.
5. If `otsInstanceName` is omitted and no usable configured endpoint is available, read the persisted auto-created instance from `$OPENCLAW_HOME/plugins/tablestore-mem/instance.json` and wait for its endpoint to become usable.
6. If no persisted instance exists, create one in `cn-beijing`, persist it, wait for readiness, and use it.

When `OPENCLAW_HOME` is not set, OpenClaw home defaults to `~/.openclaw`, so the auto-created instance state is stored at:

```text
~/.openclaw/plugins/tablestore-mem/instance.json
```

Environment variable overrides:

- `TABLESTORE_MEMORY_APP_ID`
- `TABLESTORE_MEMORY_TENANT_ID`
- `TABLESTORE_MEMORY_API_KEY`

## Scope Design

The plugin follows the Hermes-style scope design, adapted for OpenClaw.

### appId

- Source priority:
  1. plugin config `appId` or `app_id`
  2. `TABLESTORE_MEMORY_APP_ID`
- Default: `openclaw`

### tenantId

- Source priority:
  1. plugin config `tenantId` or `tenant_id`
  2. `TABLESTORE_MEMORY_TENANT_ID`
  3. current OpenClaw session `user_id`, `userId`, or OpenAI-compatible `user`
- Default: `__default__`

### agentId

- Source: OpenClaw runtime context
  - `agent_identity`
  - `agentIdentity`
  - `agentId`
  - derived from `sessionKey` if needed
- Default: `openclaw`

### runId

- Source priority:
  1. `gateway_session_key` or `gatewaySessionKey`
  2. `session_title` or `sessionTitle`
  3. current `sessionId`
- Default: `__default__`

### Read vs Write Scope

Writes preserve runtime agent/run identity. `tenantId` uses the configured tenant when set; otherwise it falls back to the current session user identity:

```json
{
  "appId": "<configured-app-or-openclaw>",
  "tenantId": "<configured-tenant-or-current-user>",
  "agentId": "<runtime-agent>",
  "runId": "<runtime-session-identity>"
}
```

Reads intentionally search across all agents and runs under the same effective tenant:

```json
{
  "appId": "<configured-app-or-openclaw>",
  "tenantId": "<configured-tenant-or-current-user>",
  "agentId": "*",
  "runId": "*"
}
```

This allows a memory written by one OpenClaw agent to be recalled by another agent under the same user scope.

## Memory Consolidation (Dream)

Over time the long-term store accumulates duplicate, evolving, and stale memories. The plugin uses the TableStore **Dream** task to consolidate them (rewrite/merge/supersede/drop), keeping the store precise.

How it works:

- Each successful writeback queues its **concrete** scope (`appId/tenantId/agentId/runId`) for consolidation.
- A background scheduler runs every `dreamIntervalHours` (default 24h), draining up to `dreamMaxScopesPerRun` queued scopes per cycle and consolidating each with `applyMode=safe_auto`, `incremental=true`.
- When `dreamOnSessionEnd` is enabled (default), a session's scope is also consolidated as soon as it ends (rotation/reset/idle/compaction) via the `session_end` hook — the same scope the turn was written to. `session_end` is not a conversation hook, so it works without `allowConversationAccess`. It shares the per-scope watermark and `dreamMinIntervalHours`, so it never double-consolidates a scope the background cycle just handled.
- `safe_auto` auto-applies `add`/`update`/`merge` actions whose confidence reaches the configured threshold; `DELETE` is never auto-applied. Thresholds resolve per action as **CLI `--threshold` (all actions) > per-action `dreamConfidenceThresholds` > single `dreamConfidenceThreshold` (default 0.9)**. A per-scope watermark in `dream-state.json` plus `dreamMinIntervalHours` prevents re-consolidating a scope too often.
- Every Dream task (background, `session_end`, and CLI) carries the configured `dreamInstructions` when set, letting you steer how memories are merged/rewritten.
- Consolidation always targets concrete scopes (not wildcards) and keeps results in place (`preserve_scope`) — this is the configuration the service auto-applies. It runs entirely off the hot path; failures are logged and never affect retrieval or writeback.
- The background scheduler only runs while the OpenClaw process is alive. For environments that are not long-running, drive consolidation with cron via the CLI below.

On-demand consolidation:

```bash
# Consolidate one user's memories now and wait for the result
openclaw tablestore-mem dream --uid alice --wait

# Generate proposals only (no auto-apply)
openclaw tablestore-mem dream --uid alice --apply-mode proposal --wait

# Override the safe_auto confidence threshold for all actions (0..1)
openclaw tablestore-mem dream --uid alice --threshold 0.8 --wait

# Steer this run with custom consolidation instructions
openclaw tablestore-mem dream --uid alice --instructions "Prefer merging duplicate preferences" --wait
```

The CLI enumerates the tenant's concrete scopes via `ListMemoryStoreScopes` and consolidates up to `dreamMaxScopesPerRun` of them. `--wait` prints the `dreamId`, final status, and action counts. `--threshold <0..1>` overrides the auto-apply confidence for all actions this run; `--instructions <text>` overrides `dreamInstructions` for this run; `--task-type <memory|skill|profile>` and `--no-incremental` are also available.

To disable automatic consolidation, set `dreamEnabled=false` (the `dream` CLI still works on demand).

## CLI Usage

Add one memory:

```bash
openclaw tablestore-mem add "Alice likes jasmine tea" --uid alice
```

Search memory:

```bash
openclaw tablestore-mem search "what does Alice like" --uid alice
# optional similarity-threshold filter (0..1)
openclaw tablestore-mem search "what does Alice like" --uid alice --min-similarity 0.3
```

Show help:

```bash
openclaw tablestore-mem --help
openclaw tablestore-mem add --help
openclaw tablestore-mem doctor --help
openclaw tablestore-mem search --help
```

Without `--uid`, the CLI uses the default/global tenant scope and is mainly useful for maintenance or debugging.

The CLI uses the same connection resolution and `instance.json` reuse flow as the automatic OpenClaw hooks.

Doctor:

```bash
openclaw tablestore-mem doctor --uid alice
```

Doctor checks:

- resolved endpoint and `otsInstanceName`
- memory store existence and metadata through `GetMemoryStore`
- scoped memory listing through `ListMemories`
- a small sample of returned memories for quick inspection

On the first AK/SK-only run, `doctor` may take longer because it can create a new instance and wait for the endpoint DNS to become ready.

Doctor returns structured JSON so users can share the output for troubleshooting.

## Slash Commands

Inside an OpenClaw session:

```text
/tablestore-mem-add Alice likes jasmine tea
/tablestore-mem-search jasmine tea
```

`/tablestore-mem-get <memoryId>` fetches a single memory in the current scope,
and `/tablestore-mem-dream-status` lists the recent Dream consolidation tasks for
the current scope. Only read-only, low-risk operations are exposed as slash
commands; destructive operations (delete/cancel) are CLI-only.

Slash command search intentionally returns visible text because it is an explicit user command. Automatic retrieval uses hidden system context and is not printed into the visible session transcript.

## Full Memory API Reference

Every memory-service method of the `@tablestore/agent-storage` SDK is reachable
through the CLI. High-value methods have first-class subcommands; the rest are
reachable through the generic `call` passthrough.

### Generic passthrough

```bash
# Call any memory method by name (camelCase) or /PascalCase path.
openclaw tablestore-mem call listMemoryStores --json '{}'
openclaw tablestore-mem call getMemory --uid alice --json '{"memoryId":"mem-1"}'
openclaw tablestore-mem call listMemoryTasks --uid alice --file ./body.json
```

- Body source precedence: `--json` > `--file` > (falls back to `{}`).
- `memoryStoreName` is injected from config when the body omits it.
- `--uid` injects the read scope only when the body has no `scope`.
- The method must be one of the 22 allowed memory methods, otherwise the call is
  rejected with the supported list.

### First-class commands

```bash
# Memory items
openclaw tablestore-mem memory get    --id mem-1 --uid alice
openclaw tablestore-mem memory update --id mem-1 --uid alice --text "new text" --metadata '{"topic":"tea"}'
openclaw tablestore-mem memory delete --id mem-1 --uid alice --yes     # destructive

# Stores
openclaw tablestore-mem stores list
openclaw tablestore-mem stores delete --name openclaw_mem --yes        # destructive

# Audit / observability
openclaw tablestore-mem messages --uid alice --limit 20
openclaw tablestore-mem requests --uid alice --operation AddMemories
openclaw tablestore-mem tasks list --uid alice --status running
openclaw tablestore-mem tasks get  --request-id req-1 --uid alice

# Dream tasks and actions
openclaw tablestore-mem dream-tasks list   --uid alice --status completed
openclaw tablestore-mem dream-tasks cancel --dream-id d-1 --yes        # destructive
openclaw tablestore-mem dream-actions list  --dream-id d-1 --status proposed
openclaw tablestore-mem dream-actions apply --dream-id d-1 --action-ids a1,a2,a3
```

Destructive commands (`memory delete`, `stores delete`, `dream-tasks cancel`)
require `--yes`; without it they abort and make no API call. All list commands
accept `--limit` / `--next-token` and echo back `nextToken` for pagination.

Unlike `search`/`add`/`doctor`, these commands are faithful API wrappers and do
**not** auto-create the memory store; a call against a missing store returns the
service error rather than silently provisioning one.

## OpenClaw Hook Trust

OpenClaw `2026.4.26+` treats some typed hooks as conversation-sensitive for non-bundled plugins.

For this plugin:

- `before_prompt_build` does not require `allowConversationAccess`
- `agent_end` does require `allowConversationAccess`

That means:

- retrieval can still work without the flag
- writeback will be blocked without the flag

Recommended config:

```json
{
  "plugins": {
    "entries": {
      "tablestore-mem": {
        "enabled": true,
        "hooks": {
          "allowConversationAccess": true
        },
        "config": {
          "accessKeyId": "<your-ak>",
          "accessKeySecret": "<your-sk>"
        }
      }
    }
  }
}
```

How to verify:

```bash
openclaw plugins inspect tablestore-mem --json
```

Expected result when writeback is enabled:

- `typedHooks` contains both `before_prompt_build` and `agent_end`
- `policy.allowConversationAccess` is `true`
- `diagnostics` does not contain a warning about `agent_end` being blocked

Expected result when the trust flag is missing:

- `before_prompt_build` is still present
- `agent_end` is missing
- `diagnostics` contains a warning like:
  `typed hook "agent_end" blocked because non-bundled plugins must set plugins.entries.tablestore-mem.hooks.allowConversationAccess=true`

## Runtime Behavior

Before prompt build:

- extracts the latest user prompt
- ensures the TableStore instance is resolved or auto-created
- waits for a newly auto-created instance to become ready before the first data-plane call
- ensures the memory store exists, creating `openclaw_mem` by default when missing
- searches TableStore memory using tenant-wide read scope
- calls `SearchMemories` with `enableRerank=true` by default
- injects matched memories into hidden system context
- does not print retrieved memories into the visible session transcript
- logs retrieval count and scope at debug level

After agent end:

- ensures the TableStore instance is resolved or auto-created
- reuses the same persisted `instance.json` state as CLI and retrieval hooks
- ensures the memory store exists, creating `openclaw_mem` by default when missing
- collects user/assistant messages from the completed turn
- writes them into TableStore using runtime write scope
- calls `AddMemories` with `sync=false` by default

OpenClaw `2026.4.26+` note:

- this `agent_end` path only runs when `plugins.entries.tablestore-mem.hooks.allowConversationAccess=true`
- if that trust flag is missing, OpenClaw blocks `agent_end` before the plugin can see conversation messages

The plugin does not call `ListMemories` as a fallback. Search results are exactly the results returned by `SearchMemories`; server-side scope filtering is trusted.

## Verification

Plugin inspection:

```bash
openclaw plugins inspect tablestore-mem
```

Expected shape:

- Typed hooks: `before_prompt_build`, `agent_end`
- Commands: `tablestore-mem-search`, `tablestore-mem-add`
- CLI doctor: `tablestore-mem doctor`
- CLI commands: `tablestore-mem`

Syntax check:

```bash
npm run check
```

Package dry run:

```bash
npm run pack:check
```

Packaged-plugin cold-start smoke test:

```bash
npm run smoke:pack
```

This check is the new release gate for “fresh install but unusable” failures. It:

- runs `npm pack`
- installs the packed tarball into a brand new OpenClaw profile
- verifies the plugin loads from the packed artifact
- writes the minimal AK/SK config directly into that fresh profile
- verifies hooks, slash commands, and `openclaw tablestore-mem` CLI registration
- verifies `doctor`, `add`, and `search` subcommand help can start successfully

Optional live OpenClaw smoke test against a real TableStore environment:

```bash
TABLESTORE_MEM_LIVE_ACCESS_KEY_ID="<ak>" \
TABLESTORE_MEM_LIVE_ACCESS_KEY_SECRET="<sk>" \
npm run smoke:live
```

Optional live overrides:

- `TABLESTORE_MEM_LIVE_ENDPOINT`
- `TABLESTORE_MEM_LIVE_OTS_INSTANCE_NAME`
- `TABLESTORE_MEM_LIVE_TENANT_ID`

When `TABLESTORE_MEM_LIVE_ENDPOINT` and `TABLESTORE_MEM_LIVE_OTS_INSTANCE_NAME` are both omitted, `npm run smoke:live` exercises the AK/SK-only managed-instance path and verifies that a newly created `instance.json` is persisted and reused on the second `doctor` run.

## Publishing

Pre-publish checklist:

1. Update `version` in `package.json`
2. Run `npm install`
3. Run `npm run release:check`
4. If you rely on the managed AK/SK-only path, run one live smoke:
   `TABLESTORE_MEM_LIVE_ACCESS_KEY_ID=... TABLESTORE_MEM_LIVE_ACCESS_KEY_SECRET=... npm run smoke:live`
5. If you want additional business validation, verify one real `doctor`, one real `add`, one real `search`

`npm run release:check` runs:

- `npm run check`
- `npm run test`
- `npm run pack:check`
- `npm run smoke:pack`

This means `npm publish` now fails before publish if the packed tarball cannot be installed into a clean OpenClaw profile or if the plugin fails to register its hooks and CLI after minimal configuration.

Publish:

```bash
npm publish
```

The package is configured for public publish under:

```text
@tablestore/openclaw-tablestore-memory
```

Note:

- npm package names cannot use `tablestore/openclaw-tablestore-memory` directly
- the valid npm scoped package name is `@tablestore/openclaw-tablestore-memory`

## Notes

- The plugin uses the `@tablestore/agent-storage` SDK (`AgentStorageClient`) directly, with AccessKey or API Key auth.
- It does not depend on `message:preprocessed`.
- It does not register model-callable memory tools.
- Retrieval uses `SearchMemories` results directly and does not perform `ListMemories` fallback.
- `agent_end` writeback persists only the new turn increment per session and chunks to TableStore limits (`<=20` messages / `<=32000` chars per call).
- When no tenant/user identity can be resolved and no `tenantId` is configured, the scope falls back to the shared `__default__` tenant.
- Dream consolidation auto-applies only within concrete scopes; it runs off the hot path and never blocks retrieval or writeback.
