# AI features guide

> Prompt cache, semantic cache, LLM cost tracking, vector search, agent memory, and RAG.

URL: https://docs.hotkv.com/ai-features/

HotKV's AI features are native commands in the same engine as your key-value
data, not a bolt-on service. This guide covers the prompt cache, semantic
cache, the smaller keyed caches, LLM cost tracking and budgets, vector
search, agent memory, the RAG pipeline, and embedding de-duplication, with
realistic examples for each.

## Licensing and plans

Vector search (`VSIM.*`) is a core HotKV command family included on every
plan, with no license required. Every other feature on this page is an
enterprise AI feature that needs its license feature. On HotKV Cloud, they
are included from the Performance plan up; on On-Premise, your license
lists which of them are enabled. Once licensed, a feature's commands are
available straight away; to keep clients away from a feature you do not use,
deny its commands with an [ACL rule](https://docs.hotkv.com/operate/acl/index.md).

`LICENSE.INFO` lists the features your server has, by the names in the
table below. `LICENSE.CHECK <feature>` checks one and replies `1` or `0`;
it takes the name without hyphens, for example `LICENSE.CHECK promptcache`.

<div className="table-scroll">
<table>
<thead><tr><th>Feature</th><th>Command family</th><th>License feature</th><th>Config section</th></tr></thead>
<tbody>
<tr><td>Prompt Cache</td><td><code>PCACHE.\*</code></td><td><code>prompt-cache</code></td><td><code>[pcache]</code></td></tr>
<tr><td>Semantic Cache</td><td><code>SCACHE.\*</code></td><td><code>semantic-cache</code></td><td><code>[scache]</code></td></tr>
<tr><td>Tool Cache</td><td><code>TOOL.CACHE.\*</code></td><td><code>tool-cache</code></td><td><code>[tool]</code></td></tr>
<tr><td>Negative Cache</td><td><code>NCACHE.\*</code></td><td><code>negative-cache</code></td><td><code>[ncache]</code></td></tr>
<tr><td>Guard Cache</td><td><code>GUARD.\*</code></td><td><code>guard-cache</code></td><td><code>[guard]</code></td></tr>
<tr><td>Rerank Cache</td><td><code>RERANK.\*</code></td><td><code>rerank-cache</code></td><td><code>[rerank]</code></td></tr>
<tr><td>LLM Stats</td><td><code>LLMSTATS.\*</code></td><td><code>llm-stats</code></td><td><code>[llmstats]</code></td></tr>
<tr><td>LLM Budget</td><td><code>LLMBUDGET.\*</code></td><td><code>llm-budget</code></td><td><code>[llmbudget]</code></td></tr>
<tr><td>Agent Memory</td><td><code>AMEM.\*</code></td><td><code>agent-memory</code></td><td><code>[agent_memory]</code></td></tr>
<tr><td>Agent Summary</td><td><code>AMEM.SUMMARY.\*</code></td><td><code>amem-summary</code></td><td><code>[amem_summary]</code></td></tr>
<tr><td>RAG Pipeline</td><td><code>RAG.\*</code></td><td><code>rag-pipeline</code></td><td><code>[rag]</code></td></tr>
<tr><td>Prompt Registry</td><td><code>PROMPT.\*</code></td><td><code>prompt-registry</code></td><td><code>[prompt]</code></td></tr>
<tr><td>Embedding Dedup</td><td><code>EMBED.DEDUP.\*</code></td><td><code>embed-dedup</code></td><td><code>[embed]</code></td></tr>
<tr><td>VSIM Persistence</td><td><code>VSIM.SAVE</code>/<code>LOAD</code>/<code>LIST</code>/<code>DROP</code></td><td><code>vsim-persist</code></td><td><code>[vsim_persist]</code></td></tr>
</tbody>
</table>
</div>

When a feature is not licensed, its commands reply `-NOLICENSE <feature>
requires a HotKV license. Contact sales@hotkv.com`, where `<feature>` is
the name from the table. The official SDKs raise a distinct, catchable
error for it (for example `hotkv.NotLicensedError` in Python), so your
application can degrade gracefully instead of crashing.

## Vector search (VSIM)

`VSIM.*` is HotKV's built-in HNSW-indexed approximate nearest neighbor
search, with cosine, L2, and inner product distance. The semantic cache,
agent memory's semantic store, and the RAG pipeline below build on it, and
you can also use it directly for your own embeddings.

```
VSIM.CREATEINDEX products 384 METRIC COSINE
VSIM.SET products sku-1 0.12 0.98 0.44 ...
VSIM.SEARCH products 5 0.10 0.95 0.40 ...
VSIM.INFO products
```

`COSINE` indexes store the L2-normalized vector, so `VSIM.GET` on one
returns the normalized vector, not necessarily byte-identical to what you
set; ranking is unaffected, since cosine similarity is scale invariant.
VSIM indexes are saved with your snapshots and AOF rewrites like the rest
of your data (see [Persistence and backups](https://docs.hotkv.com/persistence/index.md)).

With the `vsim-persist` license feature, `VSIM.SAVE <name> <blob>` stores a
blob of vector data you supply as a named file (under the `dir` of
`[vsim_persist]` when that section sets `enabled = true`, otherwise the
default `vsim_snapshots`), `VSIM.LOAD <name>` returns it (nil if there is none),
`VSIM.LIST` names the saved ones, and `VSIM.DROP <name>` deletes one.

## Prompt cache (PCACHE)

An exact-match cache for LLM prompt/response pairs, so it has no false
positives. Use it for deterministic prompts, system prompts, and tool
specs, where the same input should always return the same cached output.

<div className="table-scroll">
<table>
<thead><tr><th>Command</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><code>PCACHE.CREATE &lt;ns&gt; [MAX_ENTRIES n] [TTL s] [COST_PER_HIT usd]</code></td><td>Create a namespace</td></tr>
<tr><td><code>PCACHE.SET &lt;ns&gt; &lt;prompt&gt; &lt;response&gt; [TTL s] [META json] [usage]</code></td><td>Upsert an entry, optionally with token usage</td></tr>
<tr><td><code>PCACHE.GET &lt;ns&gt; &lt;prompt&gt;</code></td><td>Response only (nil on miss)</td></tr>
<tr><td><code>PCACHE.GETEX &lt;ns&gt; &lt;prompt&gt;</code></td><td>Response, metadata and hit count (nil on miss)</td></tr>
<tr><td><code>PCACHE.DEL &lt;ns&gt; &lt;prompt&gt;</code></td><td>Delete one entry (0/1)</td></tr>
<tr><td><code>PCACHE.STATS &lt;ns&gt;</code></td><td>Entries, hits, misses, evictions, memory, hit rate, p50/p95/p99 lookup latency, cost saved</td></tr>
<tr><td><code>PCACHE.FLUSH &lt;ns&gt;</code></td><td>Empty but keep namespace</td></tr>
<tr><td><code>PCACHE.DROP &lt;ns&gt;</code></td><td>Delete namespace</td></tr>
<tr><td><code>PCACHE.LIST</code></td><td>All namespaces</td></tr>
<tr><td><code>PCACHE.CONFIG &lt;ns&gt; SET &lt;MAX_ENTRIES|TTL|COST_PER_HIT&gt; &lt;v&gt;</code></td><td>Reconfigure at runtime</td></tr>
</tbody>
</table>
</div>

The optional usage group on `PCACHE.SET` is `MODEL <model> INPUT <n>
OUTPUT <n> [CACHEREAD <n>] [CACHEWRITE5M <n>] [CACHEWRITE1H <n>]`. Token
counts without `MODEL` are rejected with an error. Pass the group and
HotKV attributes the original call's tokens, and their exact cost when the
model is priced (see LLM cost tracking below), to every later hit. Without
it, savings are tracked only through the namespace's static
`COST_PER_HIT`.

```
PCACHE.CREATE llm_prompts MAX_ENTRIES 100000 TTL 3600 COST_PER_HIT 0.012
PCACHE.SET llm_prompts "Translate: Hello" "Bonjour" MODEL gpt-4o INPUT 20 OUTPUT 3
PCACHE.GET llm_prompts "Translate: Hello"
"Bonjour"
PCACHE.STATS llm_prompts
 1) "entries"                   -> (integer) 1
 3) "hits"                      -> (integer) 1
19) "cost_saved_usd"            -> "0.012"          # static COST_PER_HIT metric
21) "usage_saved_requests"      -> (integer) 1
31) "usage_saved_cost_usd"      -> "0.000080"        # exact: (20 in + 3 out) @ gpt-4o rates
```

`TOOL.CACHE.SET`, `NCACHE.SET`, `GUARD.SET` and `RERANK.SET` accept the
same usage group.

## Semantic cache (SCACHE)

A similarity-based cache: your application supplies the query embedding,
and HotKV returns the closest cached response if its similarity clears the
namespace's threshold. Use it for paraphrased requests where an exact
prompt cache would miss.

<div className="table-scroll">
<table>
<thead><tr><th>Command</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><code>SCACHE.CREATE &lt;ns&gt; &lt;dims&gt; [METRIC cosine|l2|ip] [THRESHOLD 0-1]</code></td><td>Create a namespace (threshold default <code>0.85</code>)</td></tr>
<tr><td><code>SCACHE.SET &lt;ns&gt; &lt;query_text&gt; &lt;response&gt; &lt;val1&gt; ... &lt;valN&gt; [TTL s] [usage]</code></td><td>Store a cached response with its embedding (vector values follow <code>response</code>)</td></tr>
<tr><td><code>SCACHE.GET &lt;ns&gt; &lt;val1&gt; ... &lt;valN&gt; [THRESHOLD 0-1]</code></td><td>On a hit, the response and its similarity; nil on a miss. <code>THRESHOLD</code> overrides the namespace threshold for this lookup</td></tr>
<tr><td><code>SCACHE.STATS &lt;ns&gt;</code></td><td>Entries, hits, misses, hit rate, p50 search latency, savings</td></tr>
<tr><td><code>SCACHE.FLUSH &lt;ns&gt;</code></td><td>Remove all entries, keep the namespace</td></tr>
<tr><td><code>SCACHE.DROP &lt;ns&gt;</code></td><td>Delete the namespace</td></tr>
<tr><td><code>SCACHE.LIST</code></td><td>All namespaces</td></tr>
<tr><td><code>SCACHE.THRESHOLD &lt;ns&gt; &lt;threshold&gt;</code></td><td>Update the similarity threshold</td></tr>
</tbody>
</table>
</div>

Like `PCACHE.SET`, `SCACHE.SET` accepts an optional trailing usage group
(`MODEL <model> INPUT <n> OUTPUT <n> [CACHEREAD <n>] [CACHEWRITE5M <n>]
[CACHEWRITE1H <n>]`, in any order after the vector) so a later hit
attributes tokens and cost saved. Every cache family's `STATS` reports
the savings as `usage_saved_requests`, `usage_saved_input_tokens`,
`usage_saved_output_tokens`, `usage_saved_cache_read_tokens`,
`usage_saved_cache_write_tokens` and `usage_saved_cost_usd`:

```
SCACHE.CREATE llm_cache 1536 METRIC cosine THRESHOLD 0.92
SCACHE.SET llm_cache "What is the capital of France?" "The capital of France is Paris." 0.1 0.2 0.3 ... TTL 3600 MODEL gpt-4o INPUT 15 OUTPUT 8
SCACHE.GET llm_cache 0.1 0.2 0.29 ...
1) "The capital of France is Paris."
2) "0.9993"
```

### Tiered caching

Query the prompt cache first for a zero-risk exact hit, then fall back to
the semantic cache for a paraphrase-tolerant match, and only call the
model on a double miss:

```python
def complete(prompt: str, embedding: list[float]) -> str:
    if hit := client.execute("PCACHE.GET", "llm_prompts", prompt):
        return hit
    if hit := client.execute("SCACHE.GET", "llm_cache", *embedding):
        return hit[0]  # [response, similarity]
    response = call_llm(prompt)
    client.execute("PCACHE.SET", "llm_prompts", prompt, response)
    client.execute("SCACHE.SET", "llm_cache", prompt, response, *embedding)
    return response
```

## Keyed caches

Four more caches work like the prompt cache, each dedicated to a specific
shape of LLM-adjacent data. All of them support the same optional usage
group on `SET` for savings attribution.

<div className="table-scroll">
<table>
<thead><tr><th>Cache</th><th>Command prefix</th><th>Use it for</th></tr></thead>
<tbody>
<tr><td>Tool Cache</td><td><code>TOOL.CACHE.\*</code></td><td>Memoizing deterministic tool/function-call results, keyed by a deterministic input string</td></tr>
<tr><td>Negative Cache</td><td><code>NCACHE.\*</code></td><td>Short-TTL caching of negative results ("user not found", "refused by moderation") to absorb repeated lookups</td></tr>
<tr><td>Guard Cache</td><td><code>GUARD.\*</code></td><td>Caching only responses that already passed schema validation or a guardrail, so a hit is known-safe to replay</td></tr>
<tr><td>Rerank Cache</td><td><code>RERANK.\*</code></td><td>Caching cross-encoder rerank scores for a <code>(query, document)</code> pair in a RAG pipeline</td></tr>
</tbody>
</table>
</div>

Each has `CREATE <ns> [MAX_ENTRIES n] [TTL s]`, `SET`, `GET`, `DEL`,
`STATS`, `FLUSH`, `DROP` and `LIST` under its own prefix, with the same
arguments as the `PCACHE` command of that name:

```
TOOL.CACHE.CREATE sqltool TTL 300
TOOL.CACHE.SET sqltool "SELECT 1" "[[1]]"
TOOL.CACHE.GET sqltool "SELECT 1"
"[[1]]"
```

## LLM cost tracking and budgets

`LLMSTATS.*` records per-tenant or per-model usage with a one-minute
rolling window, and owns a runtime-overridable price table that both
`LLMSTATS.RECORD` and `LLMBUDGET.CONSUME` share, so a model's price is
defined once and used consistently everywhere, including the cache
savings numbers above. `LLMBUDGET.*` enforces a hard cap on tokens or cost
over a fixed window that starts again every `window_seconds`, rejecting a request server-side before you pay the
upstream provider.

### LLMSTATS

<div className="table-scroll">
<table>
<thead><tr><th>Command</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><code>LLMSTATS.BUCKET &lt;name&gt;</code></td><td>Create a bucket (<code>RECORD</code> also creates it)</td></tr>
<tr><td><code>LLMSTATS.RECORD &lt;name&gt; &lt;in&gt; &lt;out&gt; &lt;cached&gt; &lt;cost_usd&gt; &lt;lat_ms&gt; [ERROR 0|1] [MODEL &lt;model&gt;] [CACHEWRITE5M &lt;n&gt;] [CACHEWRITE1H &lt;n&gt;]</code></td><td>Record one request</td></tr>
<tr><td><code>LLMSTATS.GET &lt;name&gt;</code></td><td>Totals + 1-minute rolling window</td></tr>
<tr><td><code>LLMSTATS.RESET &lt;name&gt;</code></td><td>Zero counters</td></tr>
<tr><td><code>LLMSTATS.DROP &lt;name&gt;</code></td><td>Delete bucket</td></tr>
<tr><td><code>LLMSTATS.LIST</code></td><td>All buckets</td></tr>
<tr><td><code>LLMSTATS.PRICE SET &lt;model&gt; INPUT &lt;usd/Mtok&gt; OUTPUT &lt;usd/Mtok&gt; [CACHEREAD &lt;usd&gt;] [CACHEWRITE5M &lt;usd&gt;] [CACHEWRITE1H &lt;usd&gt;]</code></td><td>Set or override a model's price</td></tr>
<tr><td><code>LLMSTATS.PRICE GET &lt;model&gt;</code></td><td>Show a model's price and source (default/custom/unpriced)</td></tr>
<tr><td><code>LLMSTATS.PRICE LIST</code></td><td>All known models</td></tr>
<tr><td><code>LLMSTATS.PRICE DEL &lt;model&gt;</code></td><td>Remove a custom override</td></tr>
</tbody>
</table>
</div>

`<cached>` is the count of cache-read tokens. `ERROR 1` marks the request
as failed. Passing `MODEL` computes cost from the price table using the
token counts given, and `<cost_usd>` is ignored (pass `0`); an unpriced
model still records its tokens but contributes `$0` to cost, and
increments `unpriced_requests` so that is never mistaken for a real
zero-cost request. Without `MODEL`, `<cost_usd>` is used exactly as given.
You can also seed custom prices at startup with `price_overrides` under
`[llmstats]`, one `"model:input:output"` entry per model (USD per million
tokens, optionally followed by `:cacheread:cachewrite5m:cachewrite1h`).

HotKV ships current list prices for Anthropic and OpenAI models in
general availability. Anthropic reports uncached input tokens,
cache-write tokens split by 5-minute/1-hour TTL, and cache-read tokens as
separate fields; OpenAI reports a single prompt-token count that
*includes* cached tokens, so subtract the cached count yourself before
passing it as `<in>` when recording an OpenAI call with `MODEL`.

```
LLMSTATS.BUCKET model_sonnet5
# 100k uncached input, 2k output, 50k served from the provider's own
# cache, 10k written to a 5-minute-TTL cache. cost_usd (0) is ignored
# because MODEL is given.
LLMSTATS.RECORD model_sonnet5 100000 2000 50000 0 420 MODEL claude-sonnet-5 CACHEWRITE5M 10000
LLMSTATS.GET model_sonnet5
11) "cost_usd"  "0.255"

# A self-hosted model with no built-in price:
LLMSTATS.PRICE SET my-finetuned-model INPUT 0.80 OUTPUT 2.40
LLMSTATS.RECORD model_custom 1000 200 0 0 15 MODEL my-finetuned-model
```

### LLMBUDGET

<div className="table-scroll">
<table>
<thead><tr><th>Command</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><code>LLMBUDGET.SET &lt;name&gt; &lt;token_limit&gt; &lt;cost_limit_usd&gt; &lt;window_seconds&gt;</code></td><td>Define a budget (<code>0</code> = no cap)</td></tr>
<tr><td><code>LLMBUDGET.CONSUME &lt;name&gt; &lt;tokens&gt; &lt;cost_usd&gt; [MODEL &lt;model&gt;] [INPUT &lt;n&gt;] [OUTPUT &lt;n&gt;] [CACHEREAD &lt;n&gt;] [CACHEWRITE5M &lt;n&gt;] [CACHEWRITE1H &lt;n&gt;]</code></td><td>1 = allowed, 0 = rejected</td></tr>
<tr><td><code>LLMBUDGET.STATUS &lt;name&gt;</code></td><td>Tokens and cost used against their limits, the window and the seconds left in it, allowed/rejected counts, <code>unpriced_requests</code></td></tr>
<tr><td><code>LLMBUDGET.RESET &lt;name&gt;</code></td><td>Zero consumption</td></tr>
<tr><td><code>LLMBUDGET.DROP &lt;name&gt;</code></td><td>Delete budget</td></tr>
<tr><td><code>LLMBUDGET.LIST</code></td><td>All budgets</td></tr>
</tbody>
</table>
</div>

`LLMBUDGET.SET` is purely positional; there is no `TOKENS`/`COST`/`WINDOW`
keyword form. As with `LLMSTATS.RECORD`, passing `MODEL` to `CONSUME`
prices `INPUT`+`OUTPUT`+`CACHEREAD`+`CACHEWRITE5M`+`CACHEWRITE1H` from the
shared price table and ignores the given `<tokens>`/`<cost_usd>`.

```
LLMBUDGET.SET tenant_acme 0 5.00 3600
LLMBUDGET.CONSUME tenant_acme 0 0 MODEL claude-sonnet-5 INPUT 100000 OUTPUT 2000
(integer) 1
LLMBUDGET.STATUS tenant_acme
 5) "cost_used_usd"  "0.220000"
```

```python
if client.execute("LLMBUDGET.CONSUME", tenant, 0, 0, "MODEL", model, "INPUT", in_tok, "OUTPUT", out_tok) == 0:
    raise RateLimitError("budget exhausted")
response = call_llm(...)
```

## Agent memory (AMEM)

Four memory types for AI agents, each with its own command family and
lifecycle:

<div className="table-scroll">
<table>
<thead><tr><th>Type</th><th>What it is</th><th>Key commands</th></tr></thead>
<tbody>
<tr><td>Conversation</td><td>Session-scoped chat history, a bounded sliding window</td><td><code>AMEM.CONV.CREATE/APPEND/GET/WINDOW/CLEAR/DROP</code></td></tr>
<tr><td>Semantic</td><td>Long-term facts, retrieved by embedding similarity</td><td><code>AMEM.SEM.CREATE/STORE/RECALL/FORGET/LIST</code></td></tr>
<tr><td>Working</td><td>Scratchpad key/value state for an in-progress task</td><td><code>AMEM.WORK.SET/GET/DEL/CLEAR/LIST</code></td></tr>
<tr><td>Episodic</td><td>Timestamped, append-only event log with time-range queries</td><td><code>AMEM.EPIS.LOG/QUERY/CLEAR/STATS</code></td></tr>
</tbody>
</table>
</div>

`AMEM.SEARCH <agent_id> <k> <val1> ... <valN>` returns the `k` closest
entries of that agent's semantic memory, each with its source, key,
content and similarity (`k` precedes the vector). With the `amem-summary`
license feature, `AMEM.SUMMARY.SET`/`APPEND`/`GET`/`DROP`/`LIST` keep a
versioned rolling summary per agent, so a long-running agent's prompt does
not have to grow unbounded.

```
AMEM.CONV.CREATE agent-42 WINDOW 500
AMEM.CONV.APPEND agent-42 user "What is the weather today?"
AMEM.CONV.APPEND agent-42 assistant "I'll check the weather for you."
AMEM.CONV.WINDOW agent-42 5

AMEM.SEM.CREATE knowledge-base 1536
AMEM.SEM.STORE knowledge-base fact-1 "Paris is the capital of France." 0.1 0.2 0.3 ...
AMEM.SEM.RECALL knowledge-base 3 0.1 0.2 0.3 ...

AMEM.WORK.SET task-123 current_step "analyzing data"
AMEM.EPIS.LOG agent-42 action "Called weather API for New York"
```

## RAG pipeline (RAG)

`RAG.*` runs chunking plus retrieval inside the same engine as the caches
above, so a RAG pipeline does not need a separate vector database. The
command family is `RAG.CREATE`, `RAG.INGEST`, `RAG.SEARCH`, `RAG.STATS`,
`RAG.DROP`, and `RAG.LIST`: create a pipeline with its vector dimension
(chunking defaults to `STRATEGY fixed`, `CHUNK_SIZE 512`, `CHUNK_OVERLAP
64`), ingest documents with your own precomputed chunk embeddings (the
reply is the number of chunks), and search by query text and embedding
together, with `ALPHA` weighting the two. The official SDKs expose a typed
RAG helper; for example, in the PHP SDK:

```php
$client->rag()->create('kb', 384);
$client->rag()->ingest('kb', 'doc:1', $text, vectors: $chunkVectors);
$client->rag()->search('kb', 'how do I reset my password?', vector: $queryVector);
```

The raw commands behind those calls:

```
RAG.CREATE kb 384 STRATEGY sentence CHUNK_SIZE 512 CHUNK_OVERLAP 64
RAG.INGEST kb doc:1 "How do I reset my password? Click Forgot password on the login screen..." VECTORS 0.02,0.11,... 0.05,0.09,...
RAG.SEARCH kb "how do I reset my password?" K 5 ALPHA 0.5 VECTOR 0.03,0.10,...
```

Pair `RAG.SEARCH` results with the Rerank Cache above to cache
cross-encoder rerank scores for repeated `(query, document)` pairs.

## Embedding de-duplication (EMBED.DEDUP)

Content-hash de-duplication for embedding vectors, with reference
counting. If the same content is embedded more than once, for example the
same PDF uploaded by many users, `EMBED.DEDUP.PUT` stores the vector once
and increments a refcount on every duplicate, instead of paying for a
fresh embedding call and storing the same vector again.

```
EMBED.DEDUP.PUT org abc123 ada-002 "vector-bytes"    -> 1   # stored
EMBED.DEDUP.PUT org abc123 ada-002 "vector-bytes"    -> 0   # duplicate, refcount 2
EMBED.DEDUP.GET org abc123                            -> "vector-bytes"
EMBED.DEDUP.RELEASE org abc123                        -> 1   # refcount 1
```

An entry is removed when `EMBED.DEDUP.RELEASE` takes its refcount to zero.
`EMBED.DEDUP.STATS <ns>` reports hits, misses, and entries;
`EMBED.DEDUP.PURGE <ns>` removes every entry but keeps the namespace,
`EMBED.DEDUP.DROP <ns>` deletes the namespace, and `EMBED.DEDUP.LIST`
names them all.

## Also included: prompt registry

With the `prompt-registry` license feature, `PROMPT.*` is a versioned
registry for prompt templates with an active-version pointer, so you can
roll back a bad prompt with one `PROMPT.SETACTIVE` call instead of a code
deploy: `PROMPT.PUT`, `PROMPT.GET`, `PROMPT.LATEST`, `PROMPT.SETACTIVE`,
`PROMPT.VERSIONS`, `PROMPT.DROP`, `PROMPT.LIST`.

## Next steps

- [Connecting and SDKs](https://docs.hotkv.com/sdks/index.md) for each language's typed helpers and
  the `PromptCache`/`SemanticCache` get-or-call wrappers.
- [MCP server](https://docs.hotkv.com/ai/mcp-server/index.md): let Claude, Cursor and other AI assistants use
  HotKV directly.
- [LangGraph checkpointer and store](https://docs.hotkv.com/ai/langgraph/index.md): persist agent state and
  long-term memory in HotKV.
- [Caching gateway](https://docs.hotkv.com/ai/gateway/index.md): cache OpenAI-compatible API calls by changing
  one base URL.
- [AI engines in a cluster](https://docs.hotkv.com/clustering/ai-engines/index.md): how the AI features are
  sharded, replicated and moved across nodes.
- [Command reference](https://docs.hotkv.com/commands/index.md) for the full command list.
- [Performance and benchmarks](https://docs.hotkv.com/performance/index.md) for the measured token
  savings from the prompt and semantic cache.