# Plugin configuration reference

> Full memory-tencentdb config schema: field types, defaults, enums, validation rules (retention, embedding, llm, offload, tcvdb, bm25, report), and parseConfig behavior.

- Repository: TencentCloud/TencentDB-Agent-Memory
- GitHub: https://github.com/TencentCloud/TencentDB-Agent-Memory
- Human docs: https://grok-wiki.com/public/docs/tencentcloud-tencentdb-agent-memory-5a33bbf5540a
- Complete Markdown: https://grok-wiki.com/public/docs/tencentcloud-tencentdb-agent-memory-5a33bbf5540a/llms-full.txt

## Source Files

- `openclaw.plugin.json`
- `src/config.ts`
- `README.md`
- `src/utils/no-think-fetch.ts`
- `src/utils/memory-cleaner.ts`
- `src/core/report/reporter.ts`

---

---
title: "Plugin configuration reference"
description: "Full memory-tencentdb config schema: field types, defaults, enums, validation rules (retention, embedding, llm, offload, tcvdb, bm25, report), and parseConfig behavior."
---

`memory-tencentdb` accepts a flat object of functional groups. Host JSON Schema lives in `openclaw.plugin.json` (`configSchema`, `additionalProperties: true`). Runtime resolution and validation are owned by `parseConfig()` in `src/config.ts`, which always returns a fully filled `MemoryTdaiConfig`. Empty input `{}` is valid zero-config: every field has a default. Invalid or incomplete remote embedding settings do not throw; they set `embedding.configError` and degrade to non-vector search.

## Config surfaces

| Host | Where the object lives | How it is parsed |
| :--- | :--- | :--- |
| OpenClaw | `~/.openclaw/openclaw.json` → `memory-tencentdb` / plugin `config` | `api.pluginConfig` → `parseConfig(raw)` in plugin `register()` |
| Gateway (Hermes / standalone) | `tdai-gateway.yaml` / `.json` key `memory` | `loadGatewayConfig()` → `parseConfig(memory)` |
| Seed CLI / HTTP | `pluginConfig` override on seed requests | Same `parseConfig` |

Enable the plugin separately from tunables:

```jsonc
// ~/.openclaw/openclaw.json
{
  "memory-tencentdb": {
    "enabled": true,
    "config": {
      "timezone": "Asia/Shanghai",
      "storeBackend": "sqlite",
      "recall": { "strategy": "hybrid", "maxResults": 5 }
    }
  }
}
```

Some install docs place groups at the plugin root (`memory-tencentdb.capture`, …). Runtime always consumes whatever object OpenClaw exposes as `pluginConfig`; groups are the keys listed below, not the outer `enabled` flag.

Gateway reuses the same groups under `memory`:

```jsonc
// ~/.memory-tencentdb/memory-tdai/tdai-gateway.json (excerpt)
{
  "llm": { "baseUrl": "https://api.openai.com/v1", "apiKey": "...", "model": "gpt-4o" },
  "memory": {
    "storeBackend": "sqlite",
    "recall": { "enabled": true, "strategy": "hybrid" }
  }
}
```

Gateway-only keys (`server.*`, top-level `llm`, `data.baseDir`) are **not** part of `MemoryTdaiConfig`. See [Environment variables](/environment-variables) and [Gateway lifecycle](/gateway-ops).

## `parseConfig` behavior

```ts
parseConfig(raw: Record<string, unknown> | undefined): MemoryTdaiConfig
```

| Rule | Behavior |
| :--- | :--- |
| Missing root | `raw ?? {}` |
| Missing groups | Treated as `{}` (`obj()` helper) |
| String fields | Non-empty trimmed strings only (`str`); optional allow empty via `optStr` |
| Numbers | Finite `number` only; non-numbers ignored → default |
| Booleans | Strict `typeof === "boolean"` |
| String arrays | Filters to non-empty strings |
| Unknown keys | Ignored (schema allows extras; parser only reads known paths) |
| Fail-soft | Embedding misconfig → `configError` + `enabled: false`, plugin keeps running |
| Fail-hard | Parser itself rarely throws; host registration logs parse failures if unexpected |

Resolved shape is `MemoryTdaiConfig`: top-level `timezone`, `storeBackend`, plus groups `capture`, `extraction`, `persona`, `pipeline`, `recall`, `embedding`, `tcvdb`, `bm25`, `memoryCleanup` (derived), `report`, `llm`, `offload`.

## Top-level fields

<ParamField body="timezone" type="string" default="system">
User/LLM-facing timestamps and local-day boundaries. Values: `"system"`, IANA name (`Asia/Shanghai`), or UTC offset (`+08:00`, `-05:30`). Storage instants remain UTC.
</ParamField>

<ParamField body="storeBackend" type="string" default="sqlite">
Enum at schema: `sqlite` \| `tcvdb`. Parser maps anything other than `"tcvdb"` to `"sqlite"`.
</ParamField>

## `capture` — L0 recording and local retention

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Auto-capture conversations |
| `excludeAgents` | `string[]` | `[]` | Glob patterns; matched agents skip capture, recall, and pipeline |
| `l0l1RetentionDays` | `number` | `0` | TTL for local L0/L1 files; `0` = no cleanup |
| `allowAggressiveCleanup` | `boolean` | `false` | Required for retention of `1` or `2` days |
| `cleanTime` | `string` | `"03:00"` | Daily cleaner schedule `HH:mm` / `H:mm` |

### Retention validation

| `l0l1RetentionDays` | `allowAggressiveCleanup` | Resulting `capture.l0l1RetentionDays` | `memoryCleanup` |
| :--- | :--- | :--- | :--- |
| `≤ 0` | any | `0` | `enabled: false`, `retentionDays` undefined |
| `≥ 3` | any | value as-is | `enabled: true`, `retentionDays` set |
| `1` or `2` | `true` | value as-is | enabled |
| `1` or `2` | `false` (default) | forced to `0` | disabled |

`cleanTime` is normalized by `normalizeCleanTime`: must match `^(\d{1,2}):(\d{2})$`, hour `0–23`, minute `0–59`. Invalid values fall back to `"03:00"`. Examples: `"3:05"` → `"03:05"`; `"24:00"`, `"3:5"`, `"abc"` → invalid.

### Derived `memoryCleanup`

Not a user-facing schema group. Built as:

```ts
{
  retentionDays,           // undefined when cleanup off
  enabled: retentionDays != null,
  cleanTime,               // normalized HH:mm
}
```

`LocalMemoryCleaner` deletes aged shards under `conversations/` (L0) and `records/` (L1), with floor guards (`MIN_RETAIN_L0 = 50`, `MIN_RETAIN_L1 = 20` records). Cutoff uses local calendar days, not rolling 24h windows.

## `extraction` — L1 atom extraction

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Background L1 extraction |
| `enableDedup` | `boolean` | `true` | Vector/keyword conflict detection |
| `maxMemoriesPerSession` | `number` | `20` | Cap per L1 pass |
| `model` | `string` | omit | `provider/model`; else host default model |

## `persona` — L2 scenes / L3 profile

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `triggerEveryN` | `number` | `50` | Persona rebuild every N new memories |
| `maxScenes` | `number` | `15` | Max scene blocks |
| `backupCount` | `number` | `3` | Persona backup generations |
| `sceneBackupCount` | `number` | `10` | Scene block backups |
| `model` | `string` | omit | `provider/model` for persona LLM |

## `pipeline` — L1→L2→L3 scheduling

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `everyNConversations` | `number` | `5` | L1 every N conversation rounds |
| `enableWarmup` | `boolean` | `true` | Threshold 1→2→4→…→`everyN` for new sessions |
| `l1IdleTimeoutSeconds` | `number` | `600` | L1 after idle |
| `l2DelayAfterL1Seconds` | `number` | `10` | Delay L2 after L1 completes |
| `l2MinIntervalSeconds` | `number` | `900` | Min gap between L2 runs per session |
| `l2MaxIntervalSeconds` | `number` | `3600` | Max L2 poll interval while session active |
| `sessionActiveWindowHours` | `number` | `24` | Stop L2 polling after inactivity |

## `recall` — auto-recall injection

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Auto-recall before turn |
| `maxResults` | `number` | `5` | Max memories returned |
| `maxCharsPerMemory` | `number` | `0` | Per-item char cap; `0` = unlimited |
| `maxTotalRecallChars` | `number` | `0` | Total inject budget; `0` = unlimited |
| `scoreThreshold` | `number` | `0.3` | Minimum score |
| `strategy` | `string` | `"hybrid"` | `embedding` \| `keyword` \| `hybrid` |
| `timeoutMs` | `number` | `5000` | Overall recall timeout; on exceed, skip inject + warn |

`validateStrategy` whitelists only the three enums; unknown strings fall back to `"hybrid"`.

## `embedding` — vector provider

Schema marks remote fields as required in description, but the parser **never throws** on missing fields.

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` (schema) | Forced `false` when provider is effectively disabled |
| `provider` | `string` | `"none"` | See provider rules below |
| `baseUrl` | `string` | `""` | Required for remote / `qclaw` |
| `apiKey` | `string` | `""` | Required for remote / `qclaw` |
| `model` | `string` | `""` / model when remote | Required for remote / `qclaw` |
| `dimensions` | `number` | `0` when provider `none` | Must be `> 0` for remote |
| `sendDimensions` | `boolean` | `true` | Include `dimensions` in request body (OpenAI Matryoshka). Set `false` for BGE-M3-style backends that return HTTP 400 matryoshka errors |
| `proxyUrl` | `string` | omit | **Required** when `provider === "qclaw"` |
| `conflictRecallTopK` | `number` | `5` | Dedup conflict recall size |
| `maxInputChars` | `number` | `5000` | Truncate embed input |
| `timeoutMs` | `number` | `10000` | Per-call timeout (retries up to 3 in embed client) |
| `recallTimeoutMs` | `number` | omit | Overrides `timeoutMs` on user-facing recall path |
| `captureTimeoutMs` | `number` | omit | Overrides on background capture/dedup path |
| `modelCacheDir` | `string` | omit | Internal; not in plugin schema |
| `configError` | `string` | omit | Filled by parser on invalid remote config |

### Provider resolution

| `provider` | Outcome |
| :--- | :--- |
| `"none"` (default) | `enabled = false`, `dimensions = 0` (skip vec0 table creation until a real provider is set) |
| `"local"` | Treated as disabled; `configError` explains local is not user-exposed |
| `"qclaw"` | Requires `proxyUrl`, `baseUrl`, `apiKey`, `model`, `dimensions > 0`; missing → disable + `configError` |
| any other string | OpenAI-compatible remote; requires `apiKey`, `baseUrl`, `model`, `dimensions > 0`; missing → disable + `configError` (plugin continues, keyword-only) |

Incomplete remote config example (plugin still starts):

```text
[memory-tdai] [EMBEDDING CONFIG ERROR] Remote embedding provider 'openai' requires 'apiKey', 'baseUrl', 'model', and 'dimensions' to be set. Missing: apiKey, dimensions. Embedding has been disabled.
```

Valid OpenAI-style example:

```json
{
  "embedding": {
    "enabled": true,
    "provider": "openai",
    "baseUrl": "https://api.openai.com/v1",
    "apiKey": "<KEY>",
    "model": "text-embedding-3-small",
    "dimensions": 1536,
    "sendDimensions": true,
    "timeoutMs": 10000,
    "recallTimeoutMs": 3000,
    "captureTimeoutMs": 15000
  }
}
```

BGE-M3 / fixed-dimension backend:

```json
{
  "embedding": {
    "provider": "openai",
    "baseUrl": "http://localhost:8080/v1",
    "apiKey": "not-needed",
    "model": "bge-m3",
    "dimensions": 1024,
    "sendDimensions": false
  }
}
```

## `tcvdb` — only when `storeBackend: "tcvdb"`

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `url` | `string` | `""` | Instance URL (required for real use) |
| `username` | `string` | `"root"` | Account |
| `apiKey` | `string` | `""` | Required for real use |
| `database` | `string` | `""` | Auto-generated from instance id when empty |
| `alias` | `string` | `""` | Optional friendly name for `database.json` |
| `embeddingModel` | `string` | `"bge-large-zh"` | Server-side embedding model |
| `timeout` | `number` | `10000` | Request timeout ms |
| `caPemPath` | `string` | omit | CA PEM path for HTTPS |

`parseConfig` does **not** reject empty `url`/`apiKey` at parse time; store factory fails later if backend cannot connect. Prefer setting all required fields before switching backend.

## `bm25` — sparse encoding (esp. tcvdb hybrid)

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Local BM25 sparse vectors via `@tencentdb-agent-memory/tcvdb-text` |
| `language` | `string` | `"zh"` | Enum `zh` \| `en`; parser maps non-`en` to `zh` |

## `llm` — standalone OpenAI-compatible override

When `enabled: true`, L1/L2/L3 extraction bypasses the host LLM runner (e.g. OpenClaw embedded agent) and calls the configured API directly. Default is host LLM.

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `false` | Standalone mode switch |
| `baseUrl` | `string` | `"https://api.openai.com/v1"` | OpenAI-compatible base |
| `apiKey` | `string` | `""` | Auth key |
| `model` | `string` | `"gpt-4o"` | Model id |
| `maxTokens` | `number` | `4096` | Max output tokens |
| `timeoutMs` | `number` | `120000` | Request timeout |
| `disableThinking` | `boolean \| string` | `false` | See thinking strategies |

Gateway top-level `llm` (in `tdai-gateway.json`) is a separate object used by `StandaloneHostAdapter` and does not require `llm.enabled`. Plugin `config.llm` is the OpenClaw in-process override.

## `offload` — context compression (independent switch)

Default `enabled: false` so long-term memory works without context-engine slot registration.

| Field | Type | Default (runtime) | In `openclaw.plugin.json`? | Notes |
| :--- | :--- | :--- | :---: | :--- |
| `enabled` | `boolean` | `false` | yes | Master switch |
| `mode` | `"local" \| "backend" \| "collect"` | auto | **no** | Explicit, else `backendUrl` present → `backend`, else `local` |
| `model` | `string` | omit | yes | `provider/model`; else host default |
| `temperature` | `number` | `0.2` | yes | LLM temperature |
| `disableThinking` | `boolean \| string` | `false` | yes | Local-mode only strategies |
| `forceTriggerThreshold` | `number` | `4` | yes | Tool pairs before force L1 |
| `dataDir` | `string` | omit → `~/.openclaw/context-offload` | yes | Absolute custom root |
| `defaultContextWindow` | `number` | `200000` | yes | Window size for ratio triggers |
| `maxPairsPerBatch` | `number` | `20` | yes | L1 batch size |
| `l2NullThreshold` | `number` | `4` | yes | `node_id=null` count → L2 |
| `l2TimeoutSeconds` | `number` | `300` | yes | Idle L2 trigger |
| `mildOffloadRatio` | `number` | `0.5` | yes | Mild compression fraction of window |
| `aggressiveCompressRatio` | `number` | `0.85` | yes | Aggressive trigger |
| `mmdMaxTokenRatio` | `number` | `0.2` | yes | Mermaid inject budget |
| `backendUrl` | `string` | omit | yes | Remote L1/L1.5/L2/L4 service |
| `backendApiKey` | `string` | omit | yes | Backend auth |
| `backendTimeoutMs` | `number` | **`120000`** | yes (schema default **10000**) | Prefer runtime default |
| `offloadRetentionDays` | `number` | `0` | **no** | Session/ref/MMD TTL; see below |
| `logMaxSizeMb` | `number` | `50` | **no** | Cap for `*.log` under data root; `0` disables |
| `userId` | `string` | omit | **no** | `X-User-Id` for backend; else primary non-loopback IPv4 |

### Offload mode

| Mode | Behavior |
| :--- | :--- |
| `local` | Call LLM via AI SDK with `offload.model` or host model |
| `backend` | Route L1/L1.5/L2/L4 through `backendUrl` |
| `collect` | Async L1/L1.5/L2 for data collection; disables L3 compression; does **not** take `contextEngine` slot |

### `offloadRetentionDays` normalization

| Input | Stored |
| :--- | :--- |
| `≤ 0` | `0` (disabled) |
| `(0, 3)` | `0` (invalid → forced off) |
| `≥ 3` | as-is |

Minimum effective retention is **3** days (no aggressive 1–2 path unlike L0/L1 capture retention).

Enable path (also needs `plugins.slots.contextEngine: "memory-tencentdb"` and after-tool-call patch) is covered in [Enable context offload](/enable-context-offload).

## `report` — metrics

| Field | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `false` | Reporting off by default |
| `type` | `string` | `"local"` | Only `"local"` implemented |

`initReporter` with `type: "local"` logs structured JSON via the host logger:

```json
{
  "tag": "METRIC",
  "category": "plugin",
  "plugin": "memory-tdai",
  "instanceId": "<uuid>",
  "pluginVersion": "<semver>",
  "ts": "<ISO-8601>",
  "event": "<name>"
}
```

Unknown `type` → reporting stays disabled (debug log). `report()` never throws into business paths. Instance id persists under `<pluginDataDir>/.metadata/instance_id`.

## `disableThinking` strategies

Shared by `llm.disableThinking` and `offload.disableThinking` via `normalizeDisableThinking()`:

| Value | Effect |
| :--- | :--- |
| `false` / omit | No wrapper (`globalThis.fetch`) |
| `true` | Alias for `"vllm"` |
| `"vllm"` | `chat_template_kwargs.enable_thinking = false` |
| `"deepseek"` | top-level `enable_thinking: false` |
| `"dashscope"` | top-level `enable_thinking: false` (Qwen / DashScope) |
| `"openai"` | `reasoning_effort: "low"` (cannot fully disable o-series thinking) |
| `"anthropic"` / `"kimi"` | `thinking: { type: "disabled" }` |
| `"gemini"` | `thinking_config: { thinking_budget: 0 }` |
| unknown string | Warn + treat as `false` |

Only chat bodies with a `messages` array are mutated; embedding and other requests pass through.

## Schema vs runtime differences

Use **`parseConfig` defaults and validation** as operational truth. Notable gaps:

| Topic | Schema (`openclaw.plugin.json`) | Runtime (`parseConfig`) |
| :--- | :--- | :--- |
| Extra keys | Allowed (`additionalProperties: true`) | Ignored unless known |
| Offload `mode`, `offloadRetentionDays`, `logMaxSizeMb`, `userId` | Not listed | Fully supported |
| `offload.backendTimeoutMs` default | `10000` | `120000` |
| `memoryCleanup` | Not listed | Derived from `capture.*` |
| `embedding.modelCacheDir` / `configError` | Not listed | Internal / diagnostic |
| Invalid retention 1–2 | Documented | Silently disabled unless `allowAggressiveCleanup` |
| Incomplete embedding | Documented as required fields | Soft-fail with `configError` |

## Worked examples

### Zero-config

```json
{ "memory-tencentdb": { "enabled": true } }
```

Resolved highlights: `storeBackend: "sqlite"`, `embedding.provider: "none"` (keyword-only), capture/recall/extraction on, offload off, report off.

### Production-ish OpenClaw block

```jsonc
{
  "memory-tencentdb": {
    "enabled": true,
    "config": {
      "timezone": "Asia/Shanghai",
      "storeBackend": "sqlite",
      "capture": {
        "enabled": true,
        "excludeAgents": ["bench-judge-*"],
        "l0l1RetentionDays": 90,
        "cleanTime": "03:00"
      },
      "extraction": {
        "enabled": true,
        "enableDedup": true,
        "maxMemoriesPerSession": 20
      },
      "pipeline": {
        "everyNConversations": 5,
        "enableWarmup": true,
        "l1IdleTimeoutSeconds": 600
      },
      "recall": {
        "enabled": true,
        "maxResults": 5,
        "scoreThreshold": 0.3,
        "strategy": "hybrid",
        "timeoutMs": 5000
      },
      "persona": {
        "triggerEveryN": 50,
        "maxScenes": 15
      },
      "embedding": {
        "provider": "openai",
        "baseUrl": "https://api.openai.com/v1",
        "apiKey": "<EMBEDDING_KEY>",
        "model": "text-embedding-3-small",
        "dimensions": 1536
      },
      "bm25": { "enabled": true, "language": "zh" },
      "llm": {
        "enabled": false
      },
      "offload": {
        "enabled": false
      },
      "report": {
        "enabled": false,
        "type": "local"
      }
    }
  }
}
```

### TCVDB backend

```json
{
  "storeBackend": "tcvdb",
  "tcvdb": {
    "url": "http://10.0.1.1:8100",
    "username": "root",
    "apiKey": "<TCVDB_KEY>",
    "database": "tdai_memory",
    "embeddingModel": "bge-large-zh",
    "timeout": 10000,
    "caPemPath": "/path/to/ca.pem"
  },
  "bm25": { "enabled": true, "language": "zh" }
}
```

### Standalone LLM for extraction

```json
{
  "llm": {
    "enabled": true,
    "baseUrl": "https://api.deepseek.com/v1",
    "apiKey": "<KEY>",
    "model": "deepseek-v3",
    "maxTokens": 4096,
    "timeoutMs": 120000,
    "disableThinking": "deepseek"
  }
}
```

### Offload with backend + retention

```json
{
  "offload": {
    "enabled": true,
    "mode": "backend",
    "backendUrl": "https://offload-api.example.com",
    "backendApiKey": "<TOKEN>",
    "backendTimeoutMs": 120000,
    "mildOffloadRatio": 0.5,
    "aggressiveCompressRatio": 0.85,
    "mmdMaxTokenRatio": 0.2,
    "offloadRetentionDays": 14,
    "logMaxSizeMb": 50,
    "userId": "ops-user-1"
  }
}
```

## Config validation checklist

| Check | Expected |
| :--- | :--- |
| Plugin enabled | `memory-tencentdb.enabled: true` + gateway restart |
| Logs | `[memory-tdai] Config parsed: capture=…, recall=…, offload=…` |
| Embedding error | No `[EMBEDDING CONFIG ERROR]` if vector search is intended |
| Retention 1–2 | Only works with `allowAggressiveCleanup: true` |
| Offload | `offload.enabled` **and** `plugins.slots.contextEngine: "memory-tencentdb"` |
| TCVDB | Non-empty `tcvdb.url` + `apiKey` before `storeBackend: "tcvdb"` |
| Strategy typos | Unknown `recall.strategy` → silent `"hybrid"` |
| Thinking strategy typos | Unknown `disableThinking` → warn + `false` |

## Related pages

<CardGroup>
  <Card title="Configure OpenClaw" href="/configure-openclaw">
    Enable the plugin, daily vs advanced tuning groups, timezone, and post-restart checks.
  </Card>
  <Card title="Configure embedding" href="/configure-embedding">
    OpenAI-compatible providers, required fields, `sendDimensions`, and keyword-only degradation.
  </Card>
  <Card title="Enable context offload" href="/enable-context-offload">
    `offload.enabled`, contextEngine slot, after-tool-call patch, and compression ratios.
  </Card>
  <Card title="Use Tencent VectorDB" href="/use-tcvdb">
    `storeBackend: tcvdb`, connection fields, BM25 language, and HTTPS CA path.
  </Card>
  <Card title="Environment variables" href="/environment-variables">
    Gateway `TDAI_*` / `MEMORY_TENCENTDB_*` keys and `tdai-gateway.json` resolution order.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    No recall, embedding 400/matryoshka, retention cleanup, and config-related failure modes.
  </Card>
</CardGroup>
