# Configuration reference

> Full plugin config schema: timezone, storeBackend, capture, extraction, persona, pipeline, recall, embedding, tcvdb, bm25, report, llm, offload — defaults, constraints, and degrade rules.

- 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-14fefdd76c97
- Complete Markdown: https://grok-wiki.com/public/docs/tencentcloud-tencentdb-agent-memory-14fefdd76c97/llms-full.txt

## Source Files

- `src/config.ts`
- `openclaw.plugin.json`
- `src/gateway/config.ts`
- `src/utils/no-think-fetch.ts`
- `src/core/store/embedding.ts`
- `SKILL.md`

---

---
title: "Configuration reference"
description: "Full plugin config schema: timezone, storeBackend, capture, extraction, persona, pipeline, recall, embedding, tcvdb, bm25, report, llm, offload — defaults, constraints, and degrade rules."
---

Plugin configuration is parsed by `parseConfig()` into a fully resolved `MemoryTdaiConfig`. Minimal valid config is `{}` — every field has a runtime default. OpenClaw hosts place the object under `memory-tencentdb` in `~/.openclaw/openclaw.json`. The Gateway reuses the same `memory` object shape nested under `tdai-gateway.yaml` / `tdai-gateway.json`. The JSON Schema in `openclaw.plugin.json` drives the OpenClaw UI; runtime behavior always follows `src/config.ts`.

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

## Config surfaces

| Surface | Where config lives | Parser |
| :--- | :--- | :--- |
| OpenClaw plugin | `~/.openclaw/openclaw.json` → `memory-tencentdb` | `parseConfig(api.pluginConfig)` in plugin `register()` |
| Standalone / Hermes Gateway | `tdai-gateway.yaml` / `.json` → `memory` (+ top-level `server`, `data`, `llm`) | `loadGatewayConfig()` then `parseConfig(memory)` |
| Seed CLI | `--config` / plugin config overrides | `parseConfig(pluginConfig)` |

Env overrides apply to **Gateway server/LLM/data only** (for example `TDAI_GATEWAY_PORT`, `TDAI_LLM_API_KEY`). Memory group fields are not individual env vars; they come from the file’s `memory` object.

```text
openclaw.json / tdai-gateway.yaml
        │
        ▼
   parseConfig()  ──► MemoryTdaiConfig
        │
        ├── capture → memoryCleanup (derived)
        ├── embedding ──► configError + enabled=false on incomplete remote
        ├── storeBackend + tcvdb ──► createStoreBundle()
        ├── llm ──► host LLM vs StandaloneLLMRunner
        └── offload ──► context engine slot / local|backend|collect
```

## Top-level fields

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `timezone` | `string` | `"system"` | `"system"`, IANA name (`Asia/Shanghai`), or UTC offset (`+08:00`). Storage timestamps stay UTC; this only affects display and local-day cleanup boundaries. |
| `storeBackend` | `"sqlite" \| "tcvdb"` | `"sqlite"` | Any value other than `"tcvdb"` resolves to `"sqlite"`. |
| `capture` | object | see below | L0 capture + retention switch |
| `extraction` | object | see below | L1 extraction |
| `persona` | object | see below | L2 scenes + L3 persona |
| `pipeline` | object | see below | L1→L2→L3 scheduling |
| `recall` | object | see below | Auto-recall injection |
| `embedding` | object | see below | Client-side embedding (sqlite path) |
| `tcvdb` | object | see below | Required when `storeBackend=tcvdb` |
| `bm25` | object | see below | Sparse encoder (esp. tcvdb hybrid) |
| `report` | object | see below | Metric JSON logs |
| `llm` | object | see below | Standalone OpenAI-compatible LLM for L1/L2/L3 |
| `offload` | object | see below | Context Offload (independent of long-term memory) |

`memoryCleanup` is **not** a user-facing top-level key. It is derived from `capture.l0l1RetentionDays`, `capture.allowAggressiveCleanup`, and `capture.cleanTime`.

---

## `timezone`

| Property | Type | Default | Constraints |
| :--- | :--- | :--- | :--- |
| `timezone` | `string` | `"system"` | Follows process TZ when `"system"`; otherwise IANA or ECMA-402 offset |

Passed to `initTimeModule({ timezone })` before any timestamp formatting or day-boundary cleanup.

---

## `storeBackend`

| Value | Behavior |
| :--- | :--- |
| `sqlite` (default) | Local `vectors.db` (sqlite-vec + FTS5). Client embedding via `embedding.*`. |
| `tcvdb` | Tencent Cloud VectorDB. Server-side embedding; client uses `NoopEmbeddingService`. Requires `tcvdb.url`, `tcvdb.apiKey`, and `tcvdb.database`. |

Missing tcvdb required fields throw at `createStoreBundle()`; store init failure wires the pipeline in **degraded mode** (JSONL capture may still work; vector recall/dedup is unavailable).

---

## `capture` (L0)

| Key | Type | Default | Constraints / notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Disable stops auto-capture |
| `excludeAgents` | `string[]` | `[]` | Glob patterns (e.g. `bench-judge-*`). Matched agents skip capture, recall, and pipeline scheduling |
| `l0l1RetentionDays` | `number` | `0` | `0` = no cleanup. Effective retention only when `>= 3`, or `1`/`2` with `allowAggressiveCleanup: true` |
| `allowAggressiveCleanup` | `boolean` | `false` | Required for retention of 1–2 days |
| `cleanTime` | `string` | `"03:00"` | `HH:mm` or `H:mm` (24h). Invalid values fall back to `"03:00"` |

### Retention degrade rules

| Input `l0l1RetentionDays` | `allowAggressiveCleanup` | Effective result |
| :--- | :--- | :--- |
| `<= 0` | any | Cleanup disabled (`retentionDays` undefined, `l0l1RetentionDays` stored as `0`) |
| `>= 3` | any | Cleanup enabled with that TTL |
| `1` or `2` | `true` | Cleanup enabled (aggressive) |
| `1` or `2` | `false` | **Silently forced to disabled** (same as `0`) |

`cleanTime` normalization: hour 0–23, minute 0–59, minutes must be two digits (`3:5` is invalid).

---

## `extraction` (L1)

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Background L1 extraction |
| `enableDedup` | `boolean` | `true` | Vector/keyword conflict detection on L1 write |
| `maxMemoriesPerSession` | `number` | `20` | Cap per L1 pass |
| `model` | `string` | omit | `provider/model`; omit uses host/default model (or `llm.*` when standalone LLM is enabled) |

---

## `persona` (L2 / L3)

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `triggerEveryN` | `number` | `50` | Persona rebuild every N new L1 memories |
| `maxScenes` | `number` | `15` | Max scene blocks |
| `backupCount` | `number` | `3` | Persona file backup count |
| `sceneBackupCount` | `number` | `10` | Scene block backup count |
| `model` | `string` | omit | `provider/model`; omit uses host/default |

---

## `pipeline` (L1→L2→L3 scheduling)

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `everyNConversations` | `number` | `5` | Steady-state L1 trigger period (conversation rounds) |
| `enableWarmup` | `boolean` | `true` | New session: threshold starts at 1 and doubles after each L1 (`1→2→4→…→everyN`) |
| `l1IdleTimeoutSeconds` | `number` | `600` | L1 after user idle |
| `l2DelayAfterL1Seconds` | `number` | `10` | Delay after L1 before L2 |
| `l2MinIntervalSeconds` | `number` | `900` | Min seconds between L2 runs per session |
| `l2MaxIntervalSeconds` | `number` | `3600` | Max L2 poll interval for an active session |
| `sessionActiveWindowHours` | `number` | `24` | Sessions idle longer stop L2 polling |

---

## `recall`

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Auto-recall injection |
| `maxResults` | `number` | `5` | Max injected L1 hits |
| `maxCharsPerMemory` | `number` | `0` | Per-memory char cap; `0` = unlimited |
| `maxTotalRecallChars` | `number` | `0` | Total auto-recall budget; `0` = unlimited |
| `scoreThreshold` | `number` | `0.3` | Minimum score |
| `strategy` | `string` | `"hybrid"` | `"keyword"` \| `"embedding"` \| `"hybrid"` |
| `timeoutMs` | `number` | `5000` | Overall recall budget; on timeout, skip injection and log a warning |

### Strategy and degrade

| Configured strategy | When embedding unavailable | Effective strategy |
| :--- | :--- | :--- |
| `keyword` | n/a | keyword (FTS) |
| `embedding` | no store/embed service | **falls back to keyword** + warn |
| `hybrid` | no store/embed service | **falls back to keyword** + warn |
| `hybrid` | tcvdb with `nativeHybridSearch` | single server-side hybrid call |
| `hybrid` | sqlite with embedding | keyword + embedding merged with client RRF (`k=60`) |
| invalid string | — | coerced to `"hybrid"` |

Unknown strategy strings are rejected by whitelist and replaced with `"hybrid"`.

---

## `embedding`

Used for **client-side** vectors on the sqlite backend (and L1 dedup). With `storeBackend=tcvdb`, store creation uses `NoopEmbeddingService` and TCVDB server embedding instead.

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` (schema) | Forced `false` when `provider` is `"none"` or `"local"`, or remote fields are incomplete |
| `provider` | `string` | `"none"` | See provider table |
| `baseUrl` | `string` | `""` | Required for remote / `qclaw` / `zeroentropy` |
| `apiKey` | `string` | `""` | Required for remote paths |
| `model` | `string` | `""` when none | Required for remote |
| `dimensions` | `number` | `0` when none | Positive int required for remote; `0` defers sqlite-vec table creation |
| `sendDimensions` | `boolean` | `true` | Omit `dimensions` in request body when `false` (BGE-M3 and similar) |
| `proxyUrl` | `string` | omit | **Required** when `provider=qclaw` |
| `conflictRecallTopK` | `number` | `5` | Dedup candidate pool |
| `maxInputChars` | `number` | `5000` | Truncate with warn |
| `timeoutMs` | `number` | `10000` | Per-call timeout (retries up to 3) |
| `recallTimeoutMs` | `number` | omit | Overrides `timeoutMs` on recall path |
| `captureTimeoutMs` | `number` | omit | Overrides `timeoutMs` on capture/dedup path |

### Provider rules

| `provider` | Required fields | Wire behavior |
| :--- | :--- | :--- |
| `"none"` (default) | none | Embedding **disabled**. `dimensions=0` → vec0 tables deferred |
| `"local"` | — | **Not exposed**. Treated as disabled; `configError` set; use a remote provider instead |
| `"qclaw"` | `proxyUrl`, `baseUrl`, `apiKey`, `model`, `dimensions>0` | Request to `proxyUrl` with `Remote-URL: {baseUrl}/embeddings` |
| `"zeroentropy"` | same remote quadruplet | `POST {baseUrl}/models/embed` with `input_type: "query"` |
| any other string (e.g. `openai`, `deepseek`) | `apiKey`, `baseUrl`, `model`, `dimensions>0` | OpenAI-compatible `POST {baseUrl}/embeddings` |

Incomplete remote / `qclaw` config **does not throw**: `embedding.enabled=false`, `embedding.configError` set, plugin continues. Startup logs:

```text
[memory-tdai] [EMBEDDING CONFIG ERROR] Remote embedding provider '…' requires …
```

`sendDimensions: false` example for fixed-dimension models:

```json
{
  "embedding": {
    "enabled": true,
    "provider": "openai",
    "baseUrl": "http://localhost:8080/v1",
    "apiKey": "<KEY>",
    "model": "bge-m3",
    "dimensions": 1024,
    "sendDimensions": false
  }
}
```

---

## `tcvdb`

Only used when `storeBackend` is `"tcvdb"`.

| Key | Type | Default | Required |
| :--- | :--- | :--- | :--- |
| `url` | `string` | `""` | **Yes** |
| `apiKey` | `string` | `""` | **Yes** |
| `database` | `string` | `""` | **Yes** (must be set explicitly) |
| `username` | `string` | `"root"` | no |
| `alias` | `string` | `""` | optional label for `database.json` |
| `embeddingModel` | `string` | `"bge-large-zh"` | server-side model |
| `timeout` | `number` | `10000` | ms |
| `caPemPath` | `string` | omit | PEM path for HTTPS |

Hard failures at store create:

- missing `url` or `apiKey`
- missing `database`

---

## `bm25`

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Local sparse encoder (`@tencentdb-agent-memory/tcvdb-text`) |
| `language` | `"zh" \| "en"` | `"zh"` | Any non-`en` string resolves to `"zh"` |

Used for hybrid sparse vectors, especially with the tcvdb backend.

---

## `report`

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `false` | Emit structured METRIC JSON via logger |
| `type` | `string` | `"local"` | Local structured log path |

---

## `llm` (standalone extraction LLM)

When `enabled`, L1/L2/L3 extraction bypasses the host built-in runner (e.g. OpenClaw embedded agent) and calls an OpenAI-compatible API directly.

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `false` | Off → use host LLM |
| `baseUrl` | `string` | `"https://api.openai.com/v1"` | OpenAI-compatible base |
| `apiKey` | `string` | `""` | Required when enabled |
| `model` | `string` | `"gpt-4o"` | Model id |
| `maxTokens` | `number` | `4096` | Max output tokens |
| `timeoutMs` | `number` | `120000` | Request timeout |
| `disableThinking` | `boolean \| string` | `false` | See [disableThinking strategies](#disablethinking-strategies) |

Gateway **top-level** `llm` (in `tdai-gateway.yaml`) is separate: it always configures the standalone runner for Gateway mode and is not gated by `llm.enabled`.

---

## `offload` (Context Offload)

Independent of long-term memory. Default `enabled: false` leaves the context-engine slot and short-term compression off.

| Key | Type | Default | Notes |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `false` | Master switch |
| `mode` | `"local" \| "backend" \| "collect"` | auto | If omitted: `"backend"` when `backendUrl` set, else `"local"`. **Parser-only** (not always listed in plugin JSON Schema) |
| `model` | `string` | omit | `provider/model`; falls back to host default |
| `temperature` | `number` | `0.2` | LLM temperature |
| `disableThinking` | `boolean \| string` | `false` | Local mode only |
| `forceTriggerThreshold` | `number` | `4` | Force L1 when pending tool pairs ≥ N |
| `dataDir` | `string` | omit | Absolute path; default `~/.openclaw/context-offload` |
| `defaultContextWindow` | `number` | `200000` | Context window size |
| `maxPairsPerBatch` | `number` | `20` | Max tool pairs per L1 batch |
| `l2NullThreshold` | `number` | `4` | Trigger L2 when `node_id=null` count ≥ N |
| `l2TimeoutSeconds` | `number` | `300` | Trigger L2 if last L2 older than N seconds |
| `mildOffloadRatio` | `number` | `0.5` | Mild compression threshold (fraction of window) |
| `aggressiveCompressRatio` | `number` | `0.85` | Aggressive compression threshold |
| `mmdMaxTokenRatio` | `number` | `0.2` | Mermaid injection token budget |
| `backendUrl` | `string` | omit | Routes L1/L1.5/L2/L4 through remote backend |
| `backendApiKey` | `string` | omit | Backend auth token |
| `backendTimeoutMs` | `number` | **`120000` (runtime)** | Plugin schema text may show `10000`; **runtime default is 120000** |
| `offloadRetentionDays` | `number` | `0` | Session/ref/mmd cleanup TTL. `0` = off; `(0,3)` forced to `0`; min effective `3` |
| `logMaxSizeMb` | `number` | `50` | Cap total debug `*.log` size; `0` disables |
| `userId` | `string` | omit | Sent as `X-User-Id` on backend; falls back to primary non-loopback IPv4 |

### Offload modes

| Mode | Behavior |
| :--- | :--- |
| `local` | LLM via AI SDK using `offload.model` or host model |
| `backend` | L1/L1.5/L2/L4 via `backendUrl` |
| `collect` | Async L1/L1.5/L2 data collection; **disables L3 compression** and does **not** occupy the contextEngine slot |

### Offload retention degrade

| Input | Effective |
| :--- | :--- |
| `<= 0` | `0` (disabled) |
| `1` or `2` | `0` (invalid → forced disabled) |
| `>= 3` | as configured |

---

## `disableThinking` strategies

Shared by `llm.disableThinking` and `offload.disableThinking`. Normalization:

| Raw value | Resolved strategy |
| :--- | :--- |
| `false` / omit | `false` (no wrapper) |
| `true` | `"vllm"` (shorthand) |
| known string | that strategy |
| unknown string | `false` + console warn |

| Strategy | Injection on chat-completion body |
| :--- | :--- |
| `vllm` | `chat_template_kwargs.enable_thinking = false` |
| `deepseek` | top-level `enable_thinking: false` |
| `dashscope` | top-level `enable_thinking: false` |
| `openai` | `reasoning_effort: "low"` (cannot fully disable) |
| `anthropic` | `thinking: { type: "disabled" }` |
| `kimi` | same as anthropic |
| `gemini` | `thinking_config: { thinking_budget: 0 }` |

Only requests with a `messages` array are modified; embedding and other non-chat calls pass through unchanged.

---

## Gateway-only config (standalone / Hermes)

`loadGatewayConfig()` layers file + env. Memory fields reuse `parseConfig` under the `memory` key.

| Section | Keys | Env overrides |
| :--- | :--- | :--- |
| `server` | `port` (8420), `host` (`127.0.0.1`), `apiKey?`, `corsOrigins[]` | `TDAI_GATEWAY_PORT`, `TDAI_GATEWAY_HOST`, `TDAI_GATEWAY_API_KEY`, `TDAI_CORS_ORIGINS` |
| `data` | `baseDir` | `TDAI_DATA_DIR`; root via `MEMORY_TENCENTDB_ROOT` (default `~/.memory-tencentdb/memory-tdai`) |
| `llm` | `baseUrl`, `apiKey`, `model`, `maxTokens`, `timeoutMs`, `disableThinking` | `TDAI_LLM_*` |
| `memory` | full plugin schema | none (file only) |

Config file resolution order:

1. `TDAI_GATEWAY_CONFIG`
2. `./tdai-gateway.yaml` or `.json` in CWD
3. `<dataDir>/tdai-gateway.yaml` or `.json`
4. env-only defaults

String leaves of the form `${VAR}` expand from the environment. Auth: when `server.apiKey` is set, all routes except `GET /health` and CORS `OPTIONS` require `Authorization: Bearer …`. Empty `corsOrigins` (default) sends no CORS headers.

---

## Schema vs runtime notes

| Topic | Behavior |
| :--- | :--- |
| Zero-config | `{}` or plugin enabled only is valid |
| Schema `additionalProperties` | `true` on root object |
| Incomplete embedding | Soft-disable + `configError` log; no crash |
| Incomplete tcvdb | Hard error at store create → degraded store |
| Aggressive L0/L1 cleanup | Requires explicit `allowAggressiveCleanup` |
| Offload vs memory | Independent switches; offload default off |
| `provider=local` embedding | Rejected at config entry; internal local code not reachable from user config |
| `backendTimeoutMs` | Trust **runtime** default `120000` over schema text |
| Parser-only offload keys | `mode`, `offloadRetentionDays`, `logMaxSizeMb`, `userId` exist in `parseConfig` even if the OpenClaw schema omits some |

---

## Example configurations

<Tabs>
  <Tab title="Zero-config">
```json
{
  "memory-tencentdb": {
    "enabled": true
  }
}
```
  </Tab>
  <Tab title="Production-ish">
```json
{
  "memory-tencentdb": {
    "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
    },
    "embedding": {
      "enabled": true,
      "provider": "openai",
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "${EMBEDDING_API_KEY}",
      "model": "text-embedding-3-small",
      "dimensions": 1536,
      "sendDimensions": true
    },
    "bm25": { "enabled": true, "language": "zh" },
    "llm": { "enabled": false },
    "offload": { "enabled": false }
  }
}
```
  </Tab>
  <Tab title="TCVDB">
```json
{
  "memory-tencentdb": {
    "storeBackend": "tcvdb",
    "tcvdb": {
      "url": "http://10.0.1.1:8100",
      "username": "root",
      "apiKey": "<TCVDB_API_KEY>",
      "database": "agent_memory_prod",
      "embeddingModel": "bge-large-zh",
      "timeout": 10000
    },
    "bm25": { "enabled": true, "language": "zh" },
    "recall": { "strategy": "hybrid" }
  }
}
```
  </Tab>
  <Tab title="Standalone LLM + offload">
```json
{
  "memory-tencentdb": {
    "llm": {
      "enabled": true,
      "baseUrl": "https://api.openai.com/v1",
      "apiKey": "<LLM_API_KEY>",
      "model": "gpt-4o-mini",
      "maxTokens": 4096,
      "timeoutMs": 120000,
      "disableThinking": false
    },
    "offload": {
      "enabled": true,
      "mode": "local",
      "temperature": 0.2,
      "forceTriggerThreshold": 4,
      "mildOffloadRatio": 0.5,
      "aggressiveCompressRatio": 0.85
    }
  }
}
```
  </Tab>
</Tabs>

Treat `apiKey` values as secrets. Prefer environment injection; do not paste real keys into chat logs or diagnostics packages without redaction.

---

## Verification signals

After config changes, restart the host Gateway and check:

| Signal | Healthy reading |
| :--- | :--- |
| Log prefix | `[memory-tdai]` config dump includes capture/recall/extraction/pipeline/offload |
| Embedding error | No `[EMBEDDING CONFIG ERROR]` unless intentional keyword-only mode |
| Store log | `Store initialized: backend=sqlite|tcvdb, provider=…` |
| Data dir (OpenClaw) | `~/.openclaw/.../memory-tdai/` with `conversations/`, `records/`, `scene_blocks/`, `vectors.db` (sqlite) |
| Offload | Mermaid injection + files under `~/.openclaw/context-offload` only when `offload.enabled` |
| Tools | `tdai_memory_search` / `tdai_conversation_search` return results |

---

## Related pages

<CardGroup>
  <Card title="Configure storage backends" href="/configure-storage">
    sqlite vs tcvdb, embedding quadruplets, BM25 language, store health checks.
  </Card>
  <Card title="Enable context offload" href="/enable-offload">
    Turn on offload.enabled, contextEngine slot, after-tool-call patch.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    Zero-config enable, gateway restart, smoke checks.
  </Card>
  <Card title="Gateway HTTP API" href="/gateway-http-api">
    Standalone Hermes sidecar endpoints and auth.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Silent plugin, no recall, embedding degrade, aggressive cleanup.
  </Card>
  <Card title="Data directories" href="/data-directories">
    On-disk layout for memory-tdai and context-offload trees.
  </Card>
</CardGroup>
