# TdaiCore and host adapters

> Host-neutral TdaiCore facade, OpenClawHostAdapter vs StandaloneHostAdapter, LLM runner boundaries, and how in-process OpenClaw hooks map to Gateway HTTP handlers.

- 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/core/tdai-core.ts`
- `src/core/types.ts`
- `src/adapters/index.ts`
- `src/adapters/openclaw/host-adapter.ts`
- `src/adapters/standalone/host-adapter.ts`
- `src/adapters/standalone/llm-runner.ts`
- `index.ts`

---

---
title: TdaiCore and host adapters
description: Host-neutral TdaiCore facade, OpenClawHostAdapter vs StandaloneHostAdapter, LLM runner boundaries, and how in-process OpenClaw hooks map to Gateway HTTP handlers.
summary: TdaiCore is the single host-neutral entry point for recall, capture, search, session flush, and pipeline lifecycle. Hosts inject a HostAdapter (OpenClaw in-process or Standalone Gateway) plus an LLMRunnerFactory so the same core runs under OpenClaw hooks or Hermes HTTP.
---

`TdaiCore` is the host-neutral facade for TencentDB Agent Memory. OpenClaw (in-process plugin) and Hermes (Gateway HTTP sidecar) both call the same methods; only the adapter layer differs.

This page is an **architecture pattern** page: contracts, wiring, lifecycle, and the OpenClaw-hook ↔ Gateway-route map.

## Architecture

```mermaid
flowchart TB
  subgraph hosts [Host shells]
    OC["index.ts OpenClaw plugin<br/>hooks + tools"]
    GW["TdaiGateway HTTP<br/>POST /recall /capture ..."]
    HER["Hermes memory_tencentdb<br/>prefetch / sync_turn"]
  end

  subgraph adapters [Adapter layer]
    OCA["OpenClawHostAdapter<br/>hostType: openclaw"]
    SHA["StandaloneHostAdapter<br/>hostType: standalone"]
    OCL["OpenClawLLMRunnerFactory<br/>CleanContextRunner / embedded agent"]
    STL["StandaloneLLMRunnerFactory<br/>OpenAI-compatible AI SDK"]
  end

  subgraph core [TdaiCore]
    TC["handleBeforeRecall<br/>handleTurnCommitted<br/>searchMemories / searchConversations<br/>handleSessionEnd / destroy"]
    PM["MemoryPipelineManager<br/>L1 → L2 → L3"]
    ST["IMemoryStore + EmbeddingService"]
  end

  OC --> OCA
  OCA --> OCL
  OCA --> TC
  GW --> SHA
  HER --> GW
  SHA --> STL
  SHA --> TC
  TC --> PM
  TC --> ST
```

**Design rules** (from `src/core/types.ts`):

1. Core depends only on `HostAdapter`, `LLMRunner` / `LLMRunnerFactory`, `Logger`, and `RuntimeContext` — never on OpenClaw or Express.
2. Each host supplies one `HostAdapter` and its own runner factory.
3. `RuntimeContext` is the identity/path bundle (`userId`, `sessionId`, `sessionKey`, `platform`, `dataDir`, `workspaceDir`).

## TdaiCore facade

Construct once, `initialize()`, then call the same APIs from hooks or HTTP.

```ts
// OpenClaw (in-process)
const adapter = new OpenClawHostAdapter({ api, pluginDataDir, openclawConfig: api.config });
const core = new TdaiCore({ hostAdapter: adapter, config: parsedCfg, sessionFilter });
await core.initialize();

// Gateway / Hermes sidecar
const adapter = new StandaloneHostAdapter({ dataDir, llmConfig, logger, platform: "gateway" });
const core = new TdaiCore({ hostAdapter: adapter, config: memoryCfg });
await core.initialize();
```

### Constructor options

| Field | Type | Role |
| --- | --- | --- |
| `hostAdapter` | `HostAdapter` | Runtime context, logger, LLM factory |
| `config` | `MemoryTdaiConfig` | Parsed plugin/gateway memory config |
| `sessionFilter` | `SessionFilter` (optional) | Skip internal/benchmark agents |
| `instanceId` | `string` (optional) | Metrics / reporting instance id |

### Public methods

| Method | Purpose | Typical caller |
| --- | --- | --- |
| `initialize()` | Data dirs, store init, optional pipeline manager + deferred runner wire | Plugin register / Gateway `start()` |
| `handleBeforeRecall(userText, sessionKey)` | Prefetch L1/L3 context for the next turn | OpenClaw `before_prompt_build`, `POST /recall` |
| `handleTurnCommitted(turn)` | L0 capture + pipeline notify | OpenClaw `agent_end`, `POST /capture` |
| `searchMemories(params)` | L1 structured search | `tdai_memory_search`, `POST /search/memories` |
| `searchConversations(params)` | L0 conversation search | `tdai_conversation_search`, `POST /search/conversations` |
| `handleSessionEnd(sessionKey)` | Flush **one** session’s buffered work (process stays up) | Hermes session end, `POST /session/end` |
| `destroy()` | Process teardown: drain bg tasks, destroy scheduler, close stores | OpenClaw `gateway_stop`, Gateway `stop()` |

### Result shapes

**`RecallResult`** (recall):

- `prependContext` — dynamic L1 text for the user prompt  
- `appendSystemContext` — stable system append (persona, scene nav, tool guide)  
- `recalledL1Memories`, `recalledL3Persona`, `recallStrategy` — metrics  

**`CaptureResult`** (capture):

- `l0RecordedCount`, `schedulerNotified`, `l0VectorsWritten`, `filteredMessages`

**Search returns** `{ text, total, strategy? }` as agent-ready formatted text plus counts.

### Lifecycle rules you must not conflate

| Event | Scope | Call |
| --- | --- | --- |
| Conversation ends (other sessions continue) | One `sessionKey` | `handleSessionEnd` → `scheduler.flushSession` |
| Host/process exit | Entire process | `destroy()` (scheduler, stores, embedding, bg drain) |

`handleSessionEnd` must not rebuild or destroy the global scheduler; concurrent Gateway sessions share one `TdaiCore` instance.

`handleTurnCommitted` races under concurrent HTTP captures: scheduler start uses a shared `schedulerStartPromise` so concurrent callers await the same start sequence. Capture may register fire-and-forget L0 embedding work in `bgTasks`; `destroy()` drains that set (5s hard timeout) before closing stores.

## HostAdapter contract

```ts
interface HostAdapter {
  readonly hostType: "openclaw" | "hermes" | "standalone";
  getRuntimeContext(): RuntimeContext;
  getLogger(): Logger;
  getLLMRunnerFactory(): LLMRunnerFactory;
}
```

Core uses the factory for extraction runners and the logger/dataDir from the adapter. Prefer host-specific helpers only at the shell (`buildRuntimeContextForSession` / `buildRuntimeContextForRequest`).

## OpenClawHostAdapter vs StandaloneHostAdapter

| Concern | `OpenClawHostAdapter` | `StandaloneHostAdapter` |
| --- | --- | --- |
| `hostType` | `"openclaw"` | `"standalone"` (default platform `"gateway"`) |
| Package deps | `openclaw/plugin-sdk` | None of OpenClaw |
| Logger | `api.logger` | Injected console/`Logger` |
| Data dir | `pluginDataDir` (e.g. under OpenClaw state `memory-tdai`) | Gateway `data.baseDir` |
| LLM path | `OpenClawLLMRunnerFactory` → `CleanContextRunner` / embedded agent | `StandaloneLLMRunnerFactory` → Vercel AI SDK + OpenAI-compatible HTTP |
| Session identity | Defaults empty; hooks pass session via `buildRuntimeContextForSession` | Defaults empty; handlers use `buildRuntimeContextForRequest` |
| Extra accessors | `getPluginApi()`, `getOpenClawConfig()`, `getPluginDataDir()` | — |

**OpenClaw construction** (`index.ts`):

- Resolves `pluginDataDir` from OpenClaw state dir + `memory-tdai`
- Builds adapter with `api`, `pluginDataDir`, `openclawConfig: api.config`
- Shell owns prompt caches, tools, hooks, cleaner, reporter; core owns memory algorithms

**Standalone construction** (`TdaiGateway`):

- Builds adapter with `dataDir`, `llmConfig` from gateway config, console logger
- One long-lived `TdaiCore` for all routes

## LLM runner boundaries

Core never calls OpenAI or OpenClaw agent APIs directly. Pipeline wiring asks the factory for runners:

| Stage | `enableTools` | Why |
| --- | --- | --- |
| L1 extraction / L1 dedup | `false` | Pure text JSON extraction |
| L2 scene / L3 persona | `true` | File tools: read/write/edit under a workspace |

### OpenClawLLMRunner

- Wraps `CleanContextRunner` (embedded Pi agent path).
- Tools map to host allow-lists (`read` / `write` / `edit` when enabled; tools disabled when not).

### StandaloneLLMRunner

- `ai` + `@ai-sdk/openai` against any OpenAI-compatible `baseUrl` / `apiKey` / `model` (BYOK).
- With tools: sandboxed `read_file`, `write_to_file`, `replace_in_file` relative to `workspaceDir` (path escape rejected).
- Optional `disableThinking` strategies for vLLM, DeepSeek, DashScope, OpenAI reasoning, Anthropic/Kimi, Gemini.

### Which factory TdaiCore wires

In `wirePipelineRunners()`:

```text
useStandaloneRunner = config.llm.enabled || hostAdapter.hostType !== "openclaw"
```

| Host | Default runners | Override |
| --- | --- | --- |
| OpenClaw | Host `OpenClawLLMRunner` (via `openclawConfig` into pipeline factory helpers) | If `config.llm.enabled`, swap in `StandaloneLLMRunnerFactory` from `config.llm` |
| Gateway / non-OpenClaw | Always standalone factory from host LLM config | — |

When standalone is active, core creates:

- L1 runner: `createRunner({ enableTools: false })`
- L2/L3 runner: `createRunner({ enableTools: true })`

When OpenClaw path is active without `llm.enabled`, L1/L2/L3 helpers receive `openclawConfig` and use the embedded path internally (no host-neutral runner instance passed).

## OpenClaw hooks ↔ Gateway HTTP

Same `TdaiCore` methods; different transport.

| Capability | OpenClaw (in-process) | Gateway HTTP | Hermes client |
| --- | --- | --- | --- |
| Recall | `before_prompt_build` → `handleBeforeRecall` | `POST /recall` | `prefetch()` |
| Capture | `agent_end` (success only) → `handleTurnCommitted` | `POST /capture` | `sync_turn()` |
| L1 search | Tool `tdai_memory_search` | `POST /search/memories` | tool → HTTP |
| L0 search | Tool `tdai_conversation_search` | `POST /search/conversations` | tool → HTTP |
| Session flush | *(no dedicated OpenClaw session-end hook; process uses stop)* | `POST /session/end` | session end |
| Process teardown | `gateway_stop` → `destroy()` | `TdaiGateway.stop()` → `destroy()` | supervisor stop |
| Health | n/a (plugin process) | `GET /health` (no auth) | health probes |
| Seed | CLI `memory-tdai seed` | `POST /seed` | ops/import paths |

### OpenClaw shell extras (not in TdaiCore)

The plugin shell around core still owns:

- Original prompt + message-count cache (`pendingOriginalPrompts`) so capture records pre-injection user text
- Recall result cache for `agent_turn` metrics
- `before_message_write` strip of `<relevant-memories>` from persisted user transcripts
- Session filter / empty `sessionKey` skip
- Lazy embedding warmup on first conversation
- Embedded-agent prewarm on first capture when scheduler not yet started
- Optional local retention cleaner and reporter init

### Gateway field mapping (compact)

| Core / domain | HTTP body / response |
| --- | --- |
| `handleBeforeRecall(query, session_key)` | Request: `query`, `session_key`; response: `context` ← `appendSystemContext`, `strategy`, `memory_count` |
| `handleTurnCommitted` | Request: `user_content`, `assistant_content`, `session_key`, optional `messages` / `session_id`; response: `l0_recorded`, `scheduler_notified` |
| `searchMemories` | Request: `query`, optional `limit` / `type` / `scene`; response: `results`, `total`, `strategy` |
| `searchConversations` | Request: `query`, optional `limit` / `session_key`; response: `results`, `total` |
| `handleSessionEnd` | Request: `session_key`; response: `{ flushed: true }` |

Auth: optional Bearer via gateway API key (all routes except `GET /health`). Details: [Secure the Gateway](/secure-gateway), [Gateway HTTP API](/gateway-http-api).

## Degraded modes

| Condition | Behavior |
| --- | --- |
| Store init fails | Log warn; recall/search degrade; pipeline runners still wire (JSONL fallback, no embedding) |
| `extraction.enabled` false | No pipeline manager; capture may still write L0 depending on capture config |
| Embedding missing / not ready | Keyword/BM25 paths may still work; warmup deferred until first conversation on OpenClaw |
| `destroy()` with stuck bg embed | 5s drain timeout, then close stores with residual-write warning |

## File layout

```
src/
├── core/
│   ├── tdai-core.ts      # TdaiCore facade
│   ├── types.ts          # HostAdapter, LLMRunner, RuntimeContext
│   ├── hooks/            # auto-recall, auto-capture
│   ├── tools/            # memory + conversation search
│   └── store/            # sqlite / tcvdb backends
├── adapters/
│   ├── openclaw/         # OpenClawHostAdapter + OpenClawLLMRunner*
│   └── standalone/       # StandaloneHostAdapter + StandaloneLLMRunner*
├── gateway/
│   └── server.ts         # TdaiGateway → StandaloneHostAdapter + TdaiCore
index.ts                  # OpenClaw plugin shell → OpenClawHostAdapter + TdaiCore
```

## Related pages

<CardGroup cols={2}>
  <Card title="Overview" href="/overview">
    Host integration surfaces and runtime assumptions.
  </Card>
  <Card title="Gateway HTTP API" href="/gateway-http-api">
    Full request/response schemas for TdaiGateway routes.
  </Card>
  <Card title="Hermes setup" href="/hermes-setup">
    Sidecar install, auto-discovery, and health checks.
  </Card>
  <Card title="Agent tools" href="/agent-tools">
    OpenClaw and Hermes tool schemas backed by TdaiCore search.
  </Card>
  <Card title="Memory layers" href="/memory-layers">
    L0–L3 model and pipeline scheduling behind capture.
  </Card>
  <Card title="Gateway lifecycle" href="/gateway-ops">
    memory-tencentdb-ctl start/stop/status for the standalone host.
  </Card>
</CardGroup>
