# Agent tools

> Registered tools tdai_memory_search and tdai_conversation_search: parameters, hybrid/embedding/keyword strategies, RRF merge, and combined 3-calls-per-turn limit.

- 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

- `index.ts`
- `src/core/tools/memory-search.ts`
- `src/core/tools/conversation-search.ts`
- `openclaw.plugin.json`
- `src/core/store/search-utils.ts`

---

---
title: "Agent tools"
description: "Registered tools tdai_memory_search and tdai_conversation_search: parameters, hybrid/embedding/keyword strategies, RRF merge, and combined 3-calls-per-turn limit."
---

OpenClaw registers two agent-callable tools — `tdai_memory_search` (L1 structured memories) and `tdai_conversation_search` (L0 raw messages) — via `api.registerTool()` in the plugin entrypoint. Both share the same host-neutral search path: `TdaiCore.searchMemories` / `searchConversations` → `executeMemorySearch` / `executeConversationSearch`, with automatic hybrid FTS + embedding retrieval and Reciprocal Rank Fusion (RRF). Tool contracts are declared in `openclaw.plugin.json` as `contracts.tools`.

## When tools register

| Condition | Behavior |
| --- | --- |
| `recall.enabled \|\| capture.enabled` | Both tools register |
| Both `recall.enabled` and `capture.enabled` are false | Tools are **not** registered; debug log: `Memory tools … not registered — memory features disabled` |

Registration is host-side OpenClaw only. Hermes exposes parallel tools under different names (see [Host surfaces](#host-surfaces)).

## Tool inventory

| Tool | Layer | Purpose |
| --- | --- | --- |
| `tdai_memory_search` | L1 | Long-term structured memories (preferences, events, instructions) |
| `tdai_conversation_search` | L0 | Raw dialogue messages (exact wording, timeline, session context) |

Recommended order for the model: try `tdai_memory_search` first; fall back to `tdai_conversation_search` when structured memories are insufficient or exact quotes are needed. Auto-recall also injects a `<memory-tools-guide>` block that documents this order and the per-turn call budget.

```mermaid
flowchart LR
  subgraph agent["Agent turn"]
    T1["tdai_memory_search"]
    T2["tdai_conversation_search"]
  end
  subgraph core["TdaiCore"]
    SM["searchMemories"]
    SC["searchConversations"]
  end
  subgraph exec["Tool executors"]
    EM["executeMemorySearch"]
    EC["executeConversationSearch"]
  end
  subgraph store["IMemoryStore + EmbeddingService"]
    FTS["searchL0Fts / searchL1Fts"]
    VEC["searchL0Vector / searchL1Vector"]
  end
  T1 --> SM --> EM
  T2 --> SC --> EC
  EM --> FTS
  EM --> VEC
  EC --> FTS
  EC --> VEC
```

## `tdai_memory_search`

Searches L1 memory records. OpenClaw label: **Memory Search**.

### Parameters

| Name | Type | Required | Default / bounds | Description |
| --- | --- | --- | --- | --- |
| `query` | `string` | yes | — | What to recall about the user |
| `limit` | `number` | no | `5`, clamped to `[1, 20]` | Max results returned |
| `type` | `string` enum | no | — | `persona` \| `episodic` \| `instruction` (exact match after merge) |
| `scene` | `string` | no | — | Case-insensitive substring match on `scene_name` |

### Response shape (to the agent)

Text content is produced by `formatSearchResponse`:

- Configuration failure: plain message asking for embedding / FTS setup
- Empty: `No matching memories found.`
- Hits: `Found N matching memories:` plus lines shaped as  
  `- **[type]** (priority: …) [scene: …] (score: 0.xxx)` and content

OpenClaw tool `details`: `{ count, strategy }` on success; `{ error }` on failure.

### Result item fields

| Field | Meaning |
| --- | --- |
| `id` | L1 `record_id` |
| `content` | Memory text |
| `type` | `persona` / `episodic` / `instruction` |
| `priority` | Priority; negative values format as `(global instruction)` |
| `scene_name` | Scene association |
| `score` | Rank score (RRF score when hybrid) |
| `created_at` / `updated_at` | From store timestamps (`timestamp_start` / `timestamp_end`) |

## `tdai_conversation_search`

Searches L0 conversation messages. OpenClaw label: **Conversation Search**.

### Parameters

| Name | Type | Required | Default / bounds | Description |
| --- | --- | --- | --- | --- |
| `query` | `string` | yes | — | Conversation content to find |
| `limit` | `number` | no | `5`, clamped to `[1, 20]` | Max messages returned |
| `session_key` | `string` | no | — | Exact filter on `session_key` (applied after merge) |

### Response shape (to the agent)

From `formatConversationSearchResponse`:

- Configuration failure: plain embedding/FTS setup message
- Empty: `No matching conversation messages found.`
- Hits: `Found N matching message(s):` with blocks  
  `**[role]** Session: … [recorded_at] (score: …)` and full message text

OpenClaw tool `details`: `{ count }` on success (strategy is not currently returned in `details`, though the executor logs it).

### Result item fields

| Field | Meaning |
| --- | --- |
| `id` | L0 `record_id` |
| `session_key` | Session identity |
| `role` | `user` or `assistant` |
| `content` | Single-message text (`message_text`) |
| `score` | Rank score (RRF when hybrid) |
| `recorded_at` | Capture timestamp |

## Search strategies

Agent tools do **not** take a `strategy` parameter. Capability and result presence choose the effective strategy:

| Effective `strategy` | When |
| --- | --- |
| `hybrid` | Both FTS and vector paths return at least one candidate |
| `embedding` | Only vector path returns candidates (or both empty while embedding is available) |
| `fts` | Only FTS path returns candidates (or both empty while only FTS is available) |
| `none` | Empty query, missing store, or neither embedding nor FTS available |

### Execution pipeline

1. Validate `query` (non-empty after trim) and `vectorStore`.
2. Probe capabilities: `embeddingService` present → vector path; `vectorStore.isFtsAvailable()` → FTS path.
3. Over-retrieve candidates:
   - L1: `candidateK = limit * 3`
   - L0: `candidateK = limit * 3`, or `limit * 4` when `session_key` filter is set
4. Run FTS and vector searches in parallel (`Promise.all`).
5. Merge with RRF when both lists are non-empty; otherwise keep the single non-empty list.
6. Apply secondary filters (`type` / `scene` for L1; `session_key` for L0).
7. Slice to `limit`.

### FTS (keyword) path

- Query normalization: `buildFtsQuery(query)` in the SQLite store layer.
- Tokenization: jieba `cutForSearch` when available (Chinese-friendly, stop-word filtered); otherwise Unicode word split.
- Output form: quoted tokens joined with `OR` (e.g. `"北京" OR "烤鸭"`). Empty tokenization → skip FTS for that call.
- Store APIs: `searchL1Fts` / `searchL0Fts`.

### Embedding path

- Embed the raw `query` via `EmbeddingService.embed`.
- Store APIs: `searchL1Vector` / `searchL0Vector` with the embedding and original query string.
- Failures are non-fatal: that path returns `[]` and the other path may still succeed.

### Failure / degrade messages

When neither embedding nor FTS is available, tools return `strategy: "none"` and a message that memory/conversation search requires an embedding provider or FTS5 support (configure `embedding.provider`, e.g. `openai_compatible`).

Empty query or missing store returns empty results with `strategy: "none"` without that configuration message.

## RRF merge

Both tools use Reciprocal Rank Fusion with constant **`RRF_K = 60`**:

```text
score(item) = Σ over lists  1 / (K + rank + 1)   // rank is 0-based
```

- Items are keyed by `id` (`record_id`).
- Scores sum across lists when an item appears in both FTS and vector rankings.
- Output is sorted by descending RRF score; each item’s `score` field is replaced with the RRF score for consistent ranking semantics.
- Shared helper `rrfMerge` / `RRF_K` also lives in `src/core/store/search-utils.ts` for other hybrid call sites (tool modules currently inline equivalent L0/L1 helpers).

## Combined 3-calls-per-turn limit

| Aspect | Behavior |
| --- | --- |
| Scope | `tdai_memory_search` **and** `tdai_conversation_search` **combined** |
| Budget | **3** calls per agent turn |
| Enforcement today | **Soft** — model-facing only |
| Hard gate | **Not implemented** — entrypoint TODO notes a future `before_tool_call` + early-return hard limit |

How the soft limit is communicated:

1. **Tool descriptions** on both OpenClaw tools:  
   `Limit: tdai_memory_search and tdai_conversation_search share a combined limit of 3 calls per turn. Stop searching after 3 total attempts.`
2. **Auto-recall guide** (`MEMORY_TOOLS_GUIDE` in `auto-recall`): injected as system context when recall runs; tells the model to retry with different keywords/tools within the budget, then stop and answer from existing context.

There is no runtime counter rejection in the tool `execute` handlers yet. Hosts that need hard throttling must implement it outside these executors until the planned hook lands.

## Host surfaces

### OpenClaw plugin

| Item | Value |
| --- | --- |
| Tool names | `tdai_memory_search`, `tdai_conversation_search` |
| Contract | `openclaw.plugin.json` → `contracts.tools` |
| Metrics | `report("tool_call", { tool, query, limit, …, success, durationMs, … })` |
| Errors | Text: `Memory search failed: …` / `Conversation search failed: …` |

### Gateway HTTP (Hermes sidecar / standalone)

Same core methods, different wire format:

| Endpoint | Maps to |
| --- | --- |
| `POST /search/memories` | `core.searchMemories` — body: `query`, optional `limit`, `type`, `scene` |
| `POST /search/conversations` | `core.searchConversations` — body: `query`, optional `limit`, `session_key` |

Responses: `{ results: string, total, strategy? }` where `results` is the **formatted text** (not a structured array). Missing `query` → HTTP 400.

### Hermes MemoryProvider

| Hermes tool name | OpenClaw equivalent | Notes |
| --- | --- | --- |
| `memory_tencentdb_memory_search` | `tdai_memory_search` | Args: `query`, `limit`, `type` (no `scene` in Hermes schema) |
| `memory_tencentdb_conversation_search` | `tdai_conversation_search` | Args: `query`, `limit` (no `session_key` in Hermes schema) |

`limit` is coerced to `[1, 20]` (default 5) with defensive parsing for string/float LLM args. Old `tdai_*` names are **not** registered on Hermes; unknown names return an error JSON. Tools require a connected Gateway (circuit breaker and reconnect probes apply).

## Verification signals

| Signal | Meaning |
| --- | --- |
| Log `[memory-tdai] [tool] tdai_memory_search called:` | OpenClaw tool invoked |
| Log `strategy=hybrid\|embedding\|fts` | Effective retrieval path |
| Log `[hybrid] RRF merged: fts=…, vec=…` | Both paths contributed |
| Tool text `Found N matching memories:` / `message(s):` | Successful hit formatting |
| Message about embedding / FTS not configured | Degraded setup |
| Debug: tools not registered — memory features disabled | Both `recall` and `capture` off |

Smoke from an agent turn (OpenClaw): call `tdai_memory_search` with a known seeded fact, then `tdai_conversation_search` for the source wording. Stay within three total attempts per turn.

## Related pages

<CardGroup>
  <Card title="Memory layers" href="/memory-layers">
    L0 conversation vs L1 atom records that these tools search.
  </Card>
  <Card title="Configure storage backends" href="/configure-storage">
    sqlite-vec + FTS5 vs tcvdb, and embedding provider setup required for hybrid search.
  </Card>
  <Card title="Gateway HTTP API" href="/gateway-http-api">
    `POST /search/memories` and `POST /search/conversations` request/response fields.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    `recall`, `capture`, `embedding`, and store settings that gate tool registration and capability.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    First smoke checks with tdai tools after enabling the plugin.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    No recall, embedding degrade, and recovery probes when tools return empty or setup messages.
  </Card>
</CardGroup>
