# Host adapters

> Host-neutral TdaiCore boundary: RuntimeContext, HostAdapter, LLMRunnerFactory, and OpenClaw vs standalone/Hermes Gateway adapter paths.

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

---

---
title: "Host adapters"
description: "Host-neutral TdaiCore boundary: RuntimeContext, HostAdapter, LLMRunnerFactory, and OpenClaw vs standalone/Hermes Gateway adapter paths."
---

`TdaiCore` is the single host-neutral facade for recall, capture, search, pipeline scheduling, and session flush. Hosts never call extraction or store internals directly; they implement `HostAdapter` + `LLMRunnerFactory` and invoke `TdaiCore` methods. Two concrete adapters ship in-tree: `OpenClawHostAdapter` (in-process OpenClaw plugin) and `StandaloneHostAdapter` (Hermes Gateway HTTP sidecar).

## Architecture boundary

```mermaid
flowchart TB
  subgraph hosts [Host shells]
    OC["index.ts OpenClaw plugin"]
    GW["src/gateway/server.ts TdaiGateway"]
  end

  subgraph adapters [Adapter layer]
    OHA["OpenClawHostAdapter hostType=openclaw"]
    SHA["StandaloneHostAdapter hostType=standalone"]
    OCR["OpenClawLLMRunnerFactory CleanContextRunner"]
    SLR["StandaloneLLMRunnerFactory AI SDK OpenAI-compatible"]
  end

  subgraph core [Host-neutral core]
    TC["TdaiCore"]
    HA["HostAdapter interface"]
    RF["LLMRunnerFactory / LLMRunner"]
    RC["RuntimeContext"]
  end

  OC --> OHA
  GW --> SHA
  OHA --> HA
  SHA --> HA
  OHA --> OCR
  SHA --> SLR
  OCR --> RF
  SLR --> RF
  HA --> TC
  RF --> TC
  RC --> HA
```

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

1. `TdaiCore` depends only on abstract interfaces — never on OpenClaw or Hermes APIs.
2. Each host supplies one `HostAdapter` and one `LLMRunnerFactory`.
3. `RuntimeContext` is the identity/path source for session and data directory scoping.

:::files
src/
├── core/
│   ├── types.ts          # HostAdapter, RuntimeContext, LLMRunner*
│   └── tdai-core.ts      # TdaiCore facade
├── adapters/
│   ├── index.ts          # barrel re-exports
│   ├── openclaw/
│   │   ├── host-adapter.ts
│   │   └── llm-runner.ts # CleanContextRunner bridge
│   └── standalone/
│       ├── host-adapter.ts
│       └── llm-runner.ts # Vercel AI SDK + sandbox tools
├── gateway/
│   └── server.ts         # StandaloneHostAdapter + HTTP routes
index.ts                  # OpenClawHostAdapter + hooks/tools
:::

## Core contracts

### `RuntimeContext`

Unified identity, platform, and path bag:

| Field | Type | Role |
| --- | --- | --- |
| `userId` | `string` | User identifier (`default_user` in CLI/OpenClaw defaults) |
| `sessionId` | `string` | Per-conversation session id |
| `sessionKey` | `string` | Stable session key for L0/L1 grouping |
| `platform` | `"openclaw" \| "hermes" \| "cli" \| "gateway" \| string` | Host platform tag |
| `agentIdentity` | `string?` | Optional agent profile name |
| `agentContext` | `"primary" \| "subagent" \| "cron" \| "flush"?` | Execution role |
| `workspaceDir` | `string` | Tool sandbox / workspace root |
| `dataDir` | `string` | Plugin data root (L0, records, scene_blocks, vectors) |

Population sources:

| Host | How context is filled |
| --- | --- |
| OpenClaw | Base defaults from adapter; per-hook via `buildRuntimeContextForSession(sessionKey, sessionId?)` |
| Hermes / Gateway | Base defaults from adapter; per-request via `buildRuntimeContextForRequest({ userId, sessionId, sessionKey, platform })` |

### `HostAdapter`

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

| Method | Core use |
| --- | --- |
| `getRuntimeContext()` | `dataDir` for stores/pipeline; session defaults |
| `getLogger()` | All `[memory-tdai]` / `[core]` logging |
| `getLLMRunnerFactory()` | L1/L2/L3 extraction runners when standalone LLM path is selected |
| `hostType` | Rare branching (pipeline runner selection) |

### `LLMRunner` / `LLMRunnerFactory`

| Surface | Behavior |
| --- | --- |
| `LLMRunParams` | `prompt`, optional `systemPrompt`, `taskId`, `timeoutMs` (default 120_000), `maxTokens`, `workspaceDir`, `instanceId` |
| `LLMRunner.run()` | Returns text; empty string if model produces no output; throws on timeout/network/unrecoverable failure |
| `LLMRunnerCreateOptions.modelRef` | Optional full `"provider/model"` string; overrides host default |
| `LLMRunnerCreateOptions.enableTools` | Default `false` (text-only); `true` enables file tools for L2/L3 |

Tool policy:

| `enableTools` | Used by | Runner behavior |
| --- | --- | --- |
| `false` | L1 extraction, L1 dedup | Pure text |
| `true` | L2 scene, L3 persona | File tools allowed |

### `TdaiCore` public API

Construct with `{ hostAdapter, config, sessionFilter?, instanceId? }`, then `await core.initialize()`.

| Method | Host mapping | Effect |
| --- | --- | --- |
| `handleBeforeRecall(userText, sessionKey)` | OpenClaw `before_prompt_build`; Hermes `prefetch`; `POST /recall` | L1/L3 recall → `prependContext` / `appendSystemContext` |
| `handleTurnCommitted(turn)` | OpenClaw `agent_end`; Hermes `sync_turn`; `POST /capture` | L0 capture + pipeline notify |
| `searchMemories(params)` | Tool `tdai_memory_search`; `POST /search/memories` | L1 hybrid search |
| `searchConversations(params)` | Tool `tdai_conversation_search`; `POST /search/conversations` | L0 search |
| `handleSessionEnd(sessionKey)` | Hermes `on_session_end`; `POST /session/end` | Flush **one** session only |
| `destroy()` | OpenClaw `gateway_stop`; Gateway `stop()` | Full teardown |

<Warning>
`handleSessionEnd` must not call `destroy()`. Session end flushes one conversation while other concurrent sessions keep running. Process exit uses `destroy()` (scheduler + stores + embedding close).
</Warning>

## Adapter comparison

| | OpenClaw | Standalone (Gateway / Hermes) |
| --- | --- | --- |
| Class | `OpenClawHostAdapter` | `StandaloneHostAdapter` |
| `hostType` | `"openclaw"` | `"standalone"` |
| Entry | `index.ts` `register(api)` | `TdaiGateway` constructor |
| Process model | In-process plugin | Separate Node sidecar (HTTP) |
| Logger | `api.logger` | Console logger (`[tdai-gateway]`) |
| Data dir | `{openclawStateDir}/memory-tdai` | `GatewayConfig.data.baseDir` |
| Default `userId` | `"default_user"` | `defaultUserId ?? "default_user"` |
| Default `platform` | `"openclaw"` | `"gateway"` (overridable) |
| `workspaceDir` | `process.cwd()` | Same as `dataDir` |
| LLM path | `OpenClawLLMRunnerFactory` → `CleanContextRunner` / embedded agent | `StandaloneLLMRunnerFactory` → OpenAI-compatible HTTP via Vercel AI SDK |
| Session scoping helper | `buildRuntimeContextForSession` | `buildRuntimeContextForRequest` |
| Extra accessors | `getPluginApi()`, `getOpenClawConfig()`, `getPluginDataDir()` | — |

### OpenClawHostAdapter options

```ts
interface OpenClawHostAdapterOptions {
  api: OpenClawPluginApi;
  pluginDataDir: string;   // e.g. ~/.openclaw/.../memory-tdai
  openclawConfig: unknown; // model resolution for CleanContextRunner
}
```

Factory wiring: `OpenClawLLMRunnerFactory({ config, agentRuntime: api.runtime.agent, logger: api.logger })`.

### StandaloneHostAdapter options

```ts
interface StandaloneHostAdapterOptions {
  dataDir: string;
  llmConfig: StandaloneLLMConfig;
  logger: Logger;
  defaultUserId?: string;
  platform?: string; // default "gateway"
}
```

```ts
interface StandaloneLLMConfig {
  baseUrl: string;      // e.g. https://api.openai.com/v1
  apiKey: string;
  model: string;        // e.g. gpt-4o
  maxTokens?: number;   // default 4096
  timeoutMs?: number;   // default 120_000
  disableThinking?: false | "vllm" | "deepseek" | "dashscope" | "openai" | "anthropic" | "kimi" | "gemini";
}
```

Standalone tools when `enableTools: true`: sandboxed `read_file`, `write_to_file`, `replace_in_file` under `workspaceDir` (max 20 tool iterations). Paths that escape the workspace return an error payload.

`modelRef` handling: if `opts.modelRef` is `"provider/model"`, the factory uses the segment after `/` as the OpenAI-compatible model name.

## Host entry paths

### OpenClaw (in-process)

```text
register(api)
  → parseConfig(api.pluginConfig)
  → pluginDataDir = join(resolveOpenClawStateDir(...), "memory-tdai")
  → hostAdapter = new OpenClawHostAdapter({ api, pluginDataDir, openclawConfig: api.config })
  → core = new TdaiCore({ hostAdapter, config, sessionFilter })
  → core.initialize()
  → register tools + hooks that call core.*
```

| OpenClaw surface | TdaiCore call |
| --- | --- |
| `before_prompt_build` | `core.handleBeforeRecall(userText, sessionKey)` → inject `prependContext` / `appendSystemContext` |
| `agent_end` (success) | `core.handleTurnCommitted({ userText, messages, sessionKey, ... })` |
| `tdai_memory_search` | `core.searchMemories(...)` |
| `tdai_conversation_search` | `core.searchConversations(...)` |
| `gateway_stop` | `core.destroy()` (+ cleaner shutdown) |

Shell responsibilities that stay **outside** core: prompt/message-count caches across hooks, session filter short-circuits, embedding warmup trigger, metric reporting, CLI metadata registration mode.

### Hermes Gateway (standalone sidecar)

```text
new TdaiGateway(configOverrides?)
  → loadGatewayConfig(...)
  → adapter = new StandaloneHostAdapter({ dataDir, llmConfig, logger, platform: "gateway" })
  → core = new TdaiCore({ hostAdapter: adapter, config: memory, sessionFilter })
start()
  → initDataDirectories + core.initialize()
  → http server routes → core.*
stop()
  → core.destroy()
```

| HTTP route | TdaiCore call |
| --- | --- |
| `POST /recall` | `handleBeforeRecall(query, session_key)` |
| `POST /capture` | `handleTurnCommitted({ userText, assistantText, messages, sessionKey, sessionId })` |
| `POST /search/memories` | `searchMemories` |
| `POST /search/conversations` | `searchConversations` |
| `POST /session/end` | `handleSessionEnd(session_key)` |
| `POST /seed` | seed runtime (uses core stores/config; not a HostAdapter method) |
| `GET /health` | store readiness via `getVectorStore()` / `getEmbeddingService()` |

Gateway LLM credentials resolve from `tdai-gateway.yaml` / env (`TDAI_LLM_BASE_URL`, `TDAI_LLM_API_KEY`, `TDAI_LLM_MODEL`, …). Memory behavior still uses the same `MemoryTdaiConfig` schema as the OpenClaw plugin (`parseConfig`).

## Pipeline LLM selection

`TdaiCore.wirePipelineRunners()` chooses how L1/L2/L3 call models:

```text
useStandaloneRunner =
  cfg.llm.enabled  OR  hostAdapter.hostType !== "openclaw"
```

| Condition | L1 runner | L2/L3 runners |
| --- | --- | --- |
| OpenClaw, `llm.enabled: false` (default) | Host path via `openclawConfig` / embedded agent (`llmRunner` undefined) | Same |
| OpenClaw, `llm.enabled: true` | New `StandaloneLLMRunnerFactory` from `cfg.llm` | `enableTools: true` standalone runner |
| Standalone / Gateway | Adapter factory, `enableTools: false` | Adapter factory, `enableTools: true` |

OpenClaw plugin config override (`llm` group, defaults):

| Key | Default |
| --- | --- |
| `enabled` | `false` |
| `baseUrl` | `https://api.openai.com/v1` |
| `apiKey` | `""` |
| `model` | `gpt-4o` |
| `maxTokens` | `4096` |
| `timeoutMs` | `120000` |
| `disableThinking` | `false` |

Providers stay BYOK/BYOC: any OpenAI-compatible base URL and key work for standalone mode; OpenClaw mode uses the host’s embedded agent and models unless `llm.enabled` forces the standalone override.

## Lifecycle map

```mermaid
sequenceDiagram
  participant Host as Host shell
  participant Adapter as HostAdapter
  participant Core as TdaiCore
  participant Store as Stores / pipeline

  Host->>Adapter: construct + getLogger/dataDir/factory
  Host->>Core: new TdaiCore + initialize
  Core->>Store: initDataDirectories + initStores + wire runners

  Host->>Core: handleBeforeRecall
  Core-->>Host: RecallResult inject contexts

  Host->>Core: handleTurnCommitted
  Core->>Store: L0 capture + scheduler notify

  Host->>Core: handleSessionEnd sessionKey
  Core->>Store: flushSession only

  Host->>Core: destroy
  Core->>Store: drain bg tasks close stores reset
```

## Verification signals

| Path | What success looks like |
| --- | --- |
| OpenClaw | Logs `[memory-tdai]` with data dir; `before_prompt_build` / `agent_end` complete; tools return search text |
| OpenClaw + `llm.enabled` | Core debug: `Using standalone LLM override: model=..., baseUrl=...` |
| Gateway start | `Gateway listening on http://{host}:{port}`; security posture log for auth/CORS |
| Gateway health | `GET /health` → `status: "ok"` when vector store ready, else `"degraded"` |
| Session end | One session flushed; other sessions’ pipeline state unchanged |

## Failure modes

| Symptom | Likely cause | Probe |
| --- | --- | --- |
| No extraction / empty L1–L3 | Standalone LLM missing `apiKey` / wrong `baseUrl` | Gateway LLM env; plugin `llm` block |
| OpenClaw pipeline still uses host agent | `llm.enabled` left `false` | Confirm config parse logs |
| Concurrent sessions wiped on end | Caller invoked `destroy` instead of `handleSessionEnd` | Use `POST /session/end` only for per-session flush |
| Recall silent | Store init failed (core continues degraded) | Health stores flags; `[memory-tdai] [core] Store init failed` |
| Gateway unauthenticated exposure | `TDAI_GATEWAY_API_KEY` unset on non-loopback bind | Startup security WARN |

## Related pages

<CardGroup>
  <Card title="Overview" href="/overview">
    Host entry points: OpenClaw plugin, Hermes provider, Gateway.
  </Card>
  <Card title="Install on Hermes" href="/install-hermes">
    MemoryProvider install, Gateway sidecar supervision, config keys.
  </Card>
  <Card title="Gateway HTTP API" href="/gateway-http-api">
    Route contracts that map to TdaiCore methods.
  </Card>
  <Card title="Gateway control" href="/gateway-control">
    `memory-tencentdb-ctl` start/stop/status and LLM credential injection.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    Full schema including the `llm` override group.
  </Card>
  <Card title="Agent tools" href="/agent-tools">
    `tdai_memory_search` and `tdai_conversation_search` tool surface.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Silent plugin, missing recall, Gateway circuit breaker recovery.
  </Card>
</CardGroup>
