# Qdrant vector index

TnsAI 0.14.0 (`TnsAI@01fba8ef`, TAN-3101 / PR #131) ships an
optional Qdrant backend for `VectorMemoryStore`. The types are
`@since 0.14.0`. They ship in Maven Central `0.14.1`.

Role RAG uses Qdrant when a URL is configured and otherwise keeps the
in-memory index. No `@Retrieval` member turns this on.
`@VectorMemory(provider = "qdrant")` is a different path: fail-loud
SPI lookup, not this env-URL adapter (TAN-2911).

## Enable

```bash
export TNSAI_VECTOR_QDRANT_URL=http://localhost:6333
# or -Dtnsai.vector.qdrant.url=http://localhost:6333
```

Optional knobs, both system properties only:

| Property | Default | Role |
|---|---|---|
| `tnsai.vector.qdrant.collection` | `tnsai` | Collection prefix |
| `tnsai.vector.qdrant.apiKey` | empty | Sent as the `api-key` header |

The official Qdrant Java client is **not** a `tnsai-core` or required
`tnsai-intelligence` dependency. Production talks REST through JDK
`HttpClient` (`HttpQdrantTransport`: 5 s connect, 15 s request).

## Role bindings

`RoleRagBinding` asks `QdrantVectorIndex.configured(scope)` while
building the scoped vector store. A blank URL returns empty and the
binding constructs `new VectorMemoryStore(embedding)` as before.

The scope is the Role class plus the selected source-name set. The
collection becomes `prefix` + `_` + the first **sixteen** hex digits of
the SHA-256 of that scope (`fingerprint` walks eight bytes and writes
two hex chars each), so two Roles never share a collection. Each index
instance also tags every point with an `instanceId` owner derived from
the **same** fingerprint (`ownerFor(scope)`; a blank scope uses
`SHARED_OWNER`) — TAN-5821. A restart therefore still sees the previous
corpus. `clear()` deletes those owner points and never drops the
collection.

A `putIfAbsent` race loser calls `abandon()` / `discard()` — it does
**not** `clear()` the remote store. Upserts under the deterministic
owner are idempotent.

Transport faults on `size()` and search are errors, not an empty index.
A missing collection (`404`) is the one quiet case: size is `0` and
search is empty.

```java
@KnowledgeSource(name = "handbook", path = "knowledge/handbook")
@Retrieval(strategy = Retrieval.Strategy.SEMANTIC)
public final class HandbookRole extends Role {
}
```

With the URL set, that Role's semantic (and hybrid) index lands in
Qdrant and survives a process restart. Without the URL, the same
declaration stays in-process.

## Standalone store

Programmatic callers can open the same adapter without a Role:

```java
QdrantVectorMemoryStore.configured(embedding).ifPresentOrElse(
        store -> store.add(entry),
        () -> new VectorMemoryStore(embedding).add(entry));
```

Or name the collection explicitly:

```java
QdrantVectorMemoryStore store = QdrantVectorMemoryStore.builder()
        .url("http://localhost:6333")
        .collection("tnsai")
        .embeddingFunction(embedding)
        .build();
```

`VectorMemoryStore` now accepts a `VectorIndex`. `QdrantVectorIndex`
is the shipped adapter; `InMemoryVectorIndex` remains the no-URL
default.

## Not this page

- `@VectorMemory(provider = "qdrant")` — after TAN-2911
  (`TnsAI@31f3c503`) this string is fail-loud unless a
  `VectorStoreProvider` named `qdrant` is registered. Core only
  ships `inmemory`. This page's adapter is still the env-URL
  `QdrantVectorIndex` path, not that annotation.
- `tnsai-tools` `QdrantTools` — a separate agent-tool surface for
  collections and upserts, not the RAG `VectorIndex`.
- Maven Central `0.14.1` ships `VectorIndex` and this adapter.
- [Selective re-embed](selective-reembed.md) — local hash-keyed index,
  not this REST adapter.
