Operations
Index CRD
An Index represents one namespace exposed through the gateway. It
declares which upstream namespace to use, snapshot policy, cache
posture, and consistency mode. The backend connection itself lives in a
VectorStore.
apiVersion: hevlayer.com/v1
kind: Index
metadata:
name: products
namespace: layer
spec:
backend:
storeRef: turbopuffer-default
namespace: products
distanceMetric: cosine_distance
embedding:
model: voyage-3-large@v1
outputDim: 1024
normalization: l2
metadata:
labels:
app: shop
tags:
- catalog
snapshot:
interval: 5m
retention: never
facetFields:
- category
- brand
search:
fullText: true
scan:
threads: 8
cache:
ttl: 24h
capGiB: 64
mode: standard
consistency: strong
Backend
| Field | Purpose |
|---|---|
backend.storeRef | Optional VectorStore name in the same namespace. The gateway routes requests for this upstream namespace to that store. Defaults to the namespace’s default store. |
backend.namespace | Optional upstream namespace override. Defaults to the Index name. |
backend.distanceMetric | Vector metric, default cosine_distance. |
For kind: search stores, the operator accepts only the metrics the backend
serves directly today: L2-style metrics for single-vector namespaces and
cosine_distance for multivector namespaces. Unsupported values put the Index
in Ready=False with reason MetricMismatch.
Embedding
spec.embedding declares the embedding identity of this namespace’s vectors.
It is optional for a single-namespace query, where distances are only ever
compared within the namespace. It is required to include a namespace in a
federated vector query:
the gateway merges those by distance, which is only meaningful when every
namespace in the set shares one embedding space.
| Field | Purpose |
|---|---|
embedding.model | Model identity and version, e.g. voyage-3-large@v1. Treated as an opaque token compared for equality across a namespace set. |
embedding.outputDim | Vector dimensionality. Part of the identity because a model truncated to a smaller dimension (Matryoshka) is not comparable to its full-width output. Cross-checked against the namespace schema. |
embedding.normalization | Vector normalization, e.g. l2 or none. |
Together with backend.distanceMetric, these form the embedding profile the
gateway compares across a fan-out. Two namespaces are distance-comparable only
when all four match; otherwise a fused vector query over them falls back to
rank-interleave (or is rejected under strict).
spec.embedding declares an already-computed embedding’s identity for
comparison purposes; it does not compute anything. To have the gateway
compute vectors for you, declare embed: on a schema attribute instead — see
below.
Schema-attribute embedding
A schema attribute can declare embed: so its source value is embedded on
write. This is Turbopuffer’s native-embeddings wire (embed on a schema
attribute, private beta at
turbopuffer.com/docs/embedding).
With native serving on a Turbopuffer store, Layer validates and transparently
forwards the wire to Turbopuffer’s managed embedding service. Autoscaler
serving and the hev search fallback use that same service through the gateway,
then write only concrete derived vectors to the active store.
// simple form — tpuf-compatible
"schema": {
"text": { "type": "string", "embed": "voyage/voyage-4-lite" }
}
// extended form
"schema": {
"text": {
"type": "string",
"embed": {
"model": "Snowflake/snowflake-arctic-embed-m-v1.5",
"revision": "refs/pr/5",
"dims": 768,
"attribute": "text_vector",
"instructions": {
"document": "Represent this passage for retrieval: ",
"query": "Represent this query for retrieving passages: "
},
"chunk": {
"strategy": "recursive",
"unit": "characters",
"size": 1200,
"overlap": 120
},
"serving": { "prefer": "autoscaler" }
}
}
}
| Field | tpuf-compatible | Purpose |
|---|---|---|
embed (string) | ✅ | Simple form. A provider-namespaced model id such as voyage/voyage-4-lite. |
embed.model | ✅ | Extended form’s provider-namespaced model id. |
embed.dims | ✅ | Output dimensionality via Matryoshka truncation. |
embed.attribute | ✅ | Override the derived vector attribute name. Omitting it derives embed_<attr>. |
embed.serving.prefer | Layer extension | native, autoscaler, or local (lattice is an alias for local Lattice serving). Defaults to native. Layer consumes this field before forwarding the native wire. |
embed.revision | Layer extension | Pin a Hugging Face model revision. It participates in profile identity and cache keys. |
embed.instructions | Layer extension | Optional document and query prefixes for asymmetric embedding models. |
embed.modality | Layer extension | text (default) or image. Image values are URLs or base64 strings for a CLIP-family checkpoint; query-time Embed still uses its text tower. |
embed.chunk | Layer extension | Split or fan out the source before embedding. See Chunking. |
Layer extensions require prefer: autoscaler, with one exception: a
CLIP-family modality: image profile may use prefer: local without the
revision, instruction, or chunking extensions (see
Local CLIP). Native and local serving otherwise
reject extensions with 422. A missing production embedding provider returns
503 service_unavailable.
The first row write that establishes an embedded schema must include
distance_metric. Omitting it returns 422. The default derived vector
column is embed_<attr> — for example, embed_title for source attribute
title. The source attribute is stored alongside the vector.
Model ids must be provider-namespaced. Native mode uses Turbopuffer’s stock model menu. Autoscaler mode accepts any Hugging Face repo id the configured provider can serve, including finetuned and mixed-case checkpoints. Provider load or model-support failures are returned as upstream embedding errors.
Chunking
For scalar text, embed.chunk reuses the Pipeline chunk shape: strategy is
none, fixed, recursive, sentence, or markdown; unit is
characters or tokens; and size, overlap, and tokenizer control the
window. A split document remains as its original row, while each embedded
chunk is written as {id}#{i} with _hevlayer_parent_id and
_hevlayer_chunk_index.
Structured fields use a two-level section composition:
"schema": {
"text": { "type": "string", "embed": {
"model": "BAAI/bge-m3",
"chunk": {
"strategy": "section",
"sectionSource": "jsonFields",
"fields": ["boxed_warning", "drug_interactions"],
"sectionAttribute": "section",
"split": {
"strategy": "recursive",
"unit": "characters",
"size": 1200,
"overlap": 120
}
},
"serving": { "prefer": "autoscaler" }
}}
}
Each configured non-empty string field on the document row fans out into the
embedded attribute first; optional split then windows that section. Rows use
ids {id}#{section}#{i}, retain the document attributes, and add the
configured section attribute plus the standard parent/index attributes.
Chunked writes require string document ids and upsert_rows; columnar writes
return 422.
Serving mode
Per model, embed.serving.prefer chooses how vectors are computed.
prefer | Behavior |
|---|---|
native (default) | On Turbopuffer, forward the compatible wire to its managed service. On hev search, resolve through the configured provider because the store has no native embedding service. |
autoscaler | Resolve through the configured inference provider and send only concrete vectors to the active store. Use this for BYO checkpoints and Layer extensions. |
local | Resolve in the gateway process with its configured Lattice or CLIP artifact and send only concrete vectors to the active store. |
lattice | Alias for local with the erikkaum/lattice-retrieval model. |
Layer does not switch modes after a provider failure.
For Lattice, generate a deployment artifact with the upstream slicer, mount
model.safetensors beside its tokenizer.json, and set
LAYER_LATTICE_MODEL_PATH to the model file. The requested dimensions must
match the artifact. The recommended int4-row, 512-dimensional artifact
quantizes model weights; Layer stores its normalized output vectors as f32.
For a CLIP-family model, set LAYER_LOCAL_CLIP_MODEL_PATH to the checkpoint
directory — provisioning and the image-input contract are covered in
Local CLIP.
Layer durably records gateway-served attribute profiles in S3, so later writes
do not need to repeat schema. Query vectors use a short in-memory TTL cache.
When changing an embedded source attribute, upsert the full row; patching that
attribute returns 422 because Layer cannot safely recompute a vector from a
partial row.
For a hev search deployment, configure at least one credentialed
kind: turbopuffer VectorStore alongside the search store; Layer uses the
first one by name as the managed embedding provider. Without one,
gateway-served embedding returns 503 service_unavailable.
Snapshot policy
| Field | Default | Purpose |
|---|---|---|
snapshot.facetFields | [] | Fields the gateway materializes into durable facet snapshots. Empty disables the automatic writer. |
snapshot.interval | 5m | Minimum spacing between automatic snapshot writes after upstream-stable advances. |
snapshot.retention | never | never keeps all snapshot bodies; a duration such as 30d prunes older bodies while keeping the latest. |
Search backend policy
spec.search applies when the Index targets a kind: search VectorStore.
The operator uses it to drive the backend’s explicit index lifecycle.
| Field | Default | Purpose |
|---|---|---|
search.fullText | false | Build the backend’s BM25 index for the namespace’s text column. Enable this for lexical, FTS, or hybrid-text namespaces. |
Scan policy
scan.threads sets the per-namespace default for origin scan fan-out:
the maximum concurrent upstream requests one scan may issue during
scatter/gather. It defaults to 8 and is clamped by the gateway’s server
cap and the active shard count. Request-level threads overrides this
default for one scan.
Cache policy
Aerospike remains an ephemeral cache; durable snapshot history stays in S3. Cache warming uses the same scan fan-out policy as other origin scans.
Status
The operator reports observed generation, metadata sync state, and
conditions. status.snapshot.lastRun and lastSuccess are reserved for
the gateway history bridge.