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 blended. 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 or prefer: blended; native
serving rejects them with 422. The gateway consumes every extension field
and sends only concrete vectors to the active store. 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. The native menu includes the Voyage 4
family, cohere/embed-v4.0, google/gemini-embedding-*,
qwen/qwen3-embedding-*, and baai/bge-m3. Bare ids such as
voyage-3-lite are rejected.
The autoscaler leg does not apply a gateway model allowlist. It accepts any
provider-namespaced Hugging Face repo id that the configured inference provider
can serve, including mixed-case ids such as
Snowflake/snowflake-arctic-embed-m-v1.5 and CLIP-family 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.
Model-serving policy
Per model, embed.serving.prefer chooses how vectors are computed.
prefer | Behavior |
|---|---|
native (default) | On Turbopuffer, transparently forward the compatible wire to tpuf’s managed service. On hev search, Layer resolves through its credentialed Turbopuffer provider and sends only concrete vectors to search. |
autoscaler | Resolve with Turbopuffer native embeddings through the gateway. The active store never receives embed: or Embed; writes contain derived vectors and queries contain a concrete ANN vector. |
blended | Uses the same provider handoff as autoscaler. The traffic-shift policy is independent of the embed wire and does not change request or response shapes. |
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.