# Seed historical conversations

> Import conversation JSON via openclaw memory-tdai seed or POST /seed: input formats A/B, flags, config overrides, output directory layout, and L0→L1→L2→L3 execution path.

- 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

- `src/cli/README.md`
- `src/cli/commands/seed.ts`
- `src/cli/index.ts`
- `src/core/seed/input.ts`
- `src/core/seed/seed-runtime.ts`
- `src/core/seed/types.ts`
- `src/gateway/types.ts`

---

---
title: "Seed historical conversations"
description: "Import conversation JSON via openclaw memory-tdai seed or POST /seed: input formats A/B, flags, config overrides, output directory layout, and L0→L1→L2→L3 execution path."
---

`openclaw memory-tdai seed` and Gateway `POST /seed` import historical conversation JSON into a dedicated data directory, then run the same capture and pipeline path used at runtime: per-round L0 write, L1 atom extraction on `everyNConversations` boundaries, with L2/L3 runners wired for scene and persona work.

## Surfaces

| Surface | Entry | Host |
|---------|--------|------|
| OpenClaw CLI | `openclaw memory-tdai seed --input <file>` | Plugin CLI under the `memory-tdai` namespace |
| Gateway HTTP | `POST /seed` | `TdaiGateway` (auth required when `TDAI_GATEWAY_API_KEY` / `server.apiKey` is set; only `GET /health` skips auth) |

Both paths share validation (`loadAndValidateInput` / `validateAndNormalizeRaw`) and execution (`executeSeed`). The CLI loads a file; the Gateway accepts an in-body JSON payload.

## Prerequisites

- OpenClaw plugin installed and CLI registered, **or** a running Gateway with memory + LLM config.
- Input JSON in Format A or Format B (below).
- For L1/L2/L3 LLM steps: plugin or Gateway `llm` settings with a usable `apiKey` (seed uses `StandaloneLLMRunnerFactory` when `llm.enabled` and `llm.apiKey` are set).
- Empty or new output directory (resume from checkpoint is **not** implemented).

## CLI: seed command

```bash
openclaw memory-tdai seed --input <file> [options]
```

### Flags

| Flag | Required | Default | Description |
|------|----------|---------|-------------|
| `--input <file>` | yes | — | Path to conversation JSON |
| `--output-dir <dir>` | no | `<stateDir>/memory-tdai-seed-<YYYYMMDD-HHmmss>` | Pipeline data directory |
| `--session-key <key>` | no | from input, else `seed-user` | Fallback when a session omits `sessionKey` |
| `--config <file>` | no | plugin config only | JSON override, two-level deep-merged onto plugin config |
| `--strict-round-role` | no | `false` | Each round must include at least one `user` and one `assistant` |
| `--yes` | no | `false` | Skip interactive timestamp auto-fill confirmation |

### Examples

```bash
# Basic
openclaw memory-tdai seed --input conversations.json

# Explicit output dir
openclaw memory-tdai seed --input data.json --output-dir ./seed-output

# Config override + non-interactive
openclaw memory-tdai seed --input data.json --config seed-config.json --yes

# Strict roles
openclaw memory-tdai seed --input data.json --strict-round-role --yes
```

### Output directory rules

1. Explicit `--output-dir` is resolved with `path.resolve`.
2. Default is under OpenClaw `stateDir`: `memory-tdai-seed-<timestamp>`.
3. If the directory exists and contains `.metadata/checkpoint.json` → exit with error (resume not implemented).
4. If the directory exists and is non-empty without a checkpoint → exit with error.

## Gateway: POST /seed

:::endpoint POST /seed Batch-seed historical conversations (blocking)

Same Format A/B payload as the CLI, wrapped in a request envelope. Execution is **blocking** and can run for minutes on large inputs.

### Request fields

<ParamField body="data" type="object | array" required>
Seed payload: Format A `{ sessions: [...] }` or Format B `[...]`.
</ParamField>

<ParamField body="session_key" type="string">
Fallback session key when sessions omit `sessionKey`.
</ParamField>

<ParamField body="strict_round_role" type="boolean">
Require user + assistant in every round (default: false).
</ParamField>

<ParamField body="auto_fill_timestamps" type="boolean">
When all messages lack timestamps, auto-fill with monotonic epoch ms (default: **true**; no interactive prompt).
</ParamField>

<ParamField body="config_override" type="object">
Two-level deep-merge onto gateway memory config. Gateway always injects its `llm` block (`enabled: true` plus gateway LLM fields) before merge.
</ParamField>

### Response fields

<ResponseField name="sessions_processed" type="number">
Sessions in the normalized input.
</ResponseField>

<ResponseField name="rounds_processed" type="number">
Rounds fed through capture.
</ResponseField>

<ResponseField name="messages_processed" type="number">
Total messages in input.
</ResponseField>

<ResponseField name="l0_recorded" type="number">
L0 rows actually recorded.
</ResponseField>

<ResponseField name="duration_ms" type="number">
Wall-clock duration of `executeSeed`.
</ResponseField>

<ResponseField name="output_dir" type="string">
`<data.baseDir>/seed-<YYYYMMDD-HHmmss>`.
</ResponseField>

### Errors

| Status | Condition |
|--------|-----------|
| `400` | Missing `data` |
| `400` | Validation failure — body includes `error` string and `validation_errors` array |
| `401` | Auth enabled and Bearer token missing/invalid |
| `404` | Wrong method/path |

:::

<RequestExample>
```bash
curl -sS -X POST "http://127.0.0.1:18790/seed" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TDAI_GATEWAY_API_KEY" \
  -d '{
    "data": {
      "sessions": [{
        "sessionKey": "user-alice",
        "conversations": [[
          { "role": "user", "content": "Hello", "timestamp": 1711929600000 },
          { "role": "assistant", "content": "Hi!", "timestamp": 1711929601000 }
        ]]
      }]
    },
    "auto_fill_timestamps": true
  }'
```
</RequestExample>

<ResponseExample>
```json
{
  "sessions_processed": 1,
  "rounds_processed": 1,
  "messages_processed": 2,
  "l0_recorded": 1,
  "duration_ms": 12345,
  "output_dir": "/home/user/.memory-tencentdb/memory-tdai/seed-20260401-220000"
}
```
</ResponseExample>

## Input formats

### Format A — object wrapper

```json
{
  "sessions": [
    {
      "sessionKey": "user-alice",
      "sessionId": "conv-001",
      "conversations": [
        [
          { "role": "user", "content": "Hello", "timestamp": 1711929600000 },
          { "role": "assistant", "content": "Hi!", "timestamp": 1711929601000 }
        ],
        [
          { "role": "user", "content": "What is the weather?" },
          { "role": "assistant", "content": "Sunny." }
        ]
      ]
    }
  ]
}
```

### Format B — top-level array

```json
[
  {
    "sessionKey": "user-alice",
    "conversations": [
      [
        { "role": "user", "content": "Hello" },
        { "role": "assistant", "content": "Hi!" }
      ]
    ]
  }
]
```

### Field reference

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `sessionKey` | string | yes (non-empty) | User / channel identity |
| `sessionId` | string | no | Auto `crypto.randomUUID()` if omitted |
| `conversations` | message[][] | yes | Outer array = rounds; inner = messages in that round |
| `role` | string | yes | Non-empty string (`user` / `assistant` expected; strict mode enforces both per round) |
| `content` | string | yes | Non-empty after trim |
| `timestamp` | number \| string | no | Epoch **integer** ms, or ISO 8601 string |

## Validation pipeline

Six layers; failures throw `SeedValidationError` (CLI exits `1`; Gateway returns `400`).

| Layer | Stage id | Checks |
|-------|----------|--------|
| 1 | `file` | CLI only: exists, non-empty, valid JSON |
| 2 | `top_level` | Format A (`sessions` array) or Format B (array) |
| 3 | `session` | Non-empty `sessionKey`; `conversations` is 2D array; at least one session |
| 4 | `round` | Each round is a non-empty message array; optional strict user+assistant |
| 5 | `message` | `role` and `content` required; `timestamp` integer ms or parseable ISO |
| 6 | `timestamp_consistency` | **All** messages have timestamps, **or none** — mixed is rejected |

### Timestamp fill

- **All present** → keep as-is (ISO strings → epoch ms).
- **All missing** → CLI prompts (or `--yes` auto-fills); Gateway auto-fills when `auto_fill_timestamps` is true (default).
- **Fill strategy** → single global monotonic counter starting at `Date.now()`, +100 ms per message across all sessions (required when multiple sessions share one `sessionKey` so L0 capture cursors do not drop later sessions).

Normalization defaults: missing `sessionKey` → `--session-key` / `session_key` / `"seed-user"`; missing `sessionId` → UUID.

## Config overrides

CLI `--config` and Gateway `config_override` use the same **two-level deep merge**: if both base and override values for a key are plain objects, shallow-merge fields; otherwise override replaces the value.

Typical seed acceleration override:

```json
{
  "pipeline": {
    "everyNConversations": 3,
    "enableWarmup": false,
    "l1IdleTimeoutSeconds": 2,
    "l2DelayAfterL1Seconds": 1,
    "l2MinIntervalSeconds": 1,
    "l2MaxIntervalSeconds": 10
  }
}
```

Isolated TCVDB database for a seed run:

```json
{
  "storeBackend": "tcvdb",
  "tcvdb": {
    "database": "my_seed_test_db"
  },
  "pipeline": {
    "everyNConversations": 3,
    "enableWarmup": false,
    "l1IdleTimeoutSeconds": 2
  }
}
```

Gateway path also forces `llm` from gateway settings before applying `config_override`.

## Execution path (L0 → L1 → L2 → L3)

```mermaid
flowchart TB
  subgraph Input
    A[Format A/B JSON]
    V[validate + normalize]
    A --> V
  end

  subgraph SeedRuntime["executeSeed"]
    P[createPipeline + L2/L3 runners]
    S[scheduler.start]
    R[For each session × round]
    L0[performAutoCapture L0]
    W1["waitForL1Idle every N rounds"]
    WT[session tail + final L1 idle]
    D[pipeline.destroy]
    M[manifest.seed update]
    V --> P --> S --> R
    R --> L0
    L0 --> W1
    W1 --> R
    R --> WT --> D --> M
  end

  subgraph Artifacts["outputDir"]
    C[conversations/ L0]
    Rec[records/ L1]
    Sc[scene_blocks/ L2]
    Pe[persona/ L3]
    Vdb[vectors.db or tcvdb]
  end

  L0 --> C
  W1 --> Rec
  P -.->|scheduled, not fully awaited| Sc
  P -.->|scheduled, not fully awaited| Pe
  P --> Vdb
```

### Behavior details

1. **`captureStartTimestamp = 0`** — disables live cold-start filtering so historical messages are not dropped.
2. **Per round** — messages mapped to `{ role, content, timestamp }` and passed to `performAutoCapture` with the seed `outputDir` as `pluginDataDir`.
3. **L1 batching** — after every `pipeline.everyNConversations` rounds **within a session**, seed polls until L1 is idle (`l1Idle`, no buffered messages, conversation count 0). Without this pause, all rounds would pile into one L1 batch.
4. **Session tail + global wait** — residual L1 work drained per session, then once for all session keys.
5. **L2/L3** — runners are attached via `createL2Runner` / `createL3Runner` (same factory path as live runtime), but seed **only waits for L1 idle** before `pipeline.destroy()`. In-flight L2 scene / L3 persona work may be cut short; latest L2/L3 artifacts are not guaranteed on every run.
6. **SIGINT** — first Ctrl+C finishes the current round and shuts down; second forces exit.

### Summary output (CLI)

```
Sessions / Rounds / Messages / L0 recorded / Duration / Output path
```

Progress stages reported during the run: `l0_captured`, `l1_waiting`.

## Output directory layout

:::files
```
<output-dir>/
├── conversations/          # L0 JSONL
├── records/                # L1 JSONL
├── scene_blocks/           # L2 scene blocks (if L2 completed before destroy)
├── persona/                # L3 persona artifacts (if L3 completed before destroy)
├── vectors.db              # SQLite + sqlite-vec (storeBackend=sqlite only)
├── .metadata/
│   ├── manifest.json       # store binding + seed run record
│   └── checkpoint.json     # pipeline progress (presence blocks re-seed into same dir)
└── .backup/                # rolling backups when enabled by runtime
```
:::

### manifest.seed

On success, seed appends to `.metadata/manifest.json`:

```json
{
  "version": 1,
  "createdAt": "2026-04-01T22:00:00.000Z",
  "store": {
    "type": "sqlite",
    "sqlite": { "path": "vectors.db" }
  },
  "seed": {
    "inputFile": "conversations.json",
    "sessions": 3,
    "rounds": 42,
    "messages": 128,
    "startedAt": "2026-04-01T22:00:00.000Z",
    "completedAt": "2026-04-01T22:05:30.000Z"
  }
}
```

`inputFile` is the basename only (CLI). Live runtime directories keep `seed: null`.

## CLI vs Gateway differences

| Concern | CLI | Gateway |
|---------|-----|---------|
| Input source | File (`--input`) | Body field `data` |
| Timestamp missing | Interactive confirm unless `--yes` | Auto-fill default true |
| Output dir | `--output-dir` or `stateDir/memory-tdai-seed-*` | Always `data.baseDir/seed-*` |
| Config base | OpenClaw plugin config | Gateway memory + injected `llm` |
| openclawConfig | Passed from plugin CLI context | `{}` |
| Progress | stdout progress line | debug logs only |

## Failure modes

| Symptom | Cause | Action |
|---------|--------|--------|
| `Input file not found` / empty / JSON parse | Layer 1 file errors | Fix path and JSON |
| `Unrecognized input format` | Not Format A or B | Wrap with `sessions` or use an array of sessions |
| Timestamp consistency failed | Mixed presence of `timestamp` | Add timestamps to all messages or strip all |
| `--strict-round-role` errors | Round missing user or assistant | Fix rounds or drop the flag |
| Output directory not empty / checkpoint exists | Resume not implemented | New directory or clean target |
| Config override file missing / not object | CLI merge helper | Valid JSON object path |
| Long run, sparse L2/L3 | L1-only idle wait then destroy | Re-run with longer pipeline windows, or inspect partial artifacts; full L1+L2+L3 idle wait not yet exposed |
| Gateway `401` | API key set, missing Bearer | Align `TDAI_GATEWAY_API_KEY` client header |

## Verify after seed

<Steps>
  <Step title="Confirm summary counts">
    CLI box or Gateway JSON: `sessions_processed`, `rounds_processed`, `l0_recorded` &gt; 0 for non-empty input.
  </Step>
  <Step title="Inspect on-disk layers">
    Check `conversations/`, `records/`, and `.metadata/manifest.json` under the reported `output_dir`. Use [Inspect local memory](/inspect-local-memory) tools (`read-local-memory`) against that directory.
  </Step>
  <Step title="Optional: search via tools or Gateway">
    Point live config or tools at the seed data dir / TCVDB database if you seeded into a shared backend; otherwise treat the seed directory as an isolated corpus.
  </Step>
</Steps>

## Next

<CardGroup>
  <Card title="Memory layers" href="/memory-layers">
    L0 conversation, L1 atom, L2 scene, L3 persona model and drill-down paths.
  </Card>
  <Card title="Gateway HTTP API" href="/gateway-http-api">
    Full route list including POST /seed request and response shapes.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    memory-tdai seed flags and other package bin commands.
  </Card>
  <Card title="Inspect local memory" href="/inspect-local-memory">
    Query L0–L3 artifacts and on-disk layout after a seed run.
  </Card>
  <Card title="Plugin configuration reference" href="/plugin-config-reference">
    Pipeline, LLM, embedding, and store fields usable in seed overrides.
  </Card>
  <Card title="Use Tencent VectorDB" href="/use-tcvdb">
    storeBackend tcvdb and database isolation for seed test DBs.
  </Card>
</CardGroup>
