Configuration¶
Configuration is a Rust value, not a config server. Because uniko runs
entirely in-process, every model, threshold, and pipeline cadence is a field
on one struct — UnikoConfig, in uniko-store — that you set before the
KnowledgeBase opens. Start from
UnikoConfig::default(), override what you care about, and call validate()
to catch constraint violations before they reach the database.
use uniko_store::config::{UnikoConfig, EmbeddingConfig};
let mut config = UnikoConfig::default();
config.embedding = EmbeddingConfig::nomic_v15(); // swap the embedder
config.recall_limit = 20; // widen the recall bundle
config.validate()?; // fail fast on bad combos
Note
UnikoConfig derives Serialize / Deserialize, and the struct
carries #[serde(default)]. An empty JSON object ({}) deserialises
to exactly UnikoConfig::default(), and any field you omit falls
back to its spec default. This is what lets the benchmark harness
layer a JSON profile on top of the defaults without re-stating every
knob.
Swappable models¶
uniko's NLP, embedding, and reranking models all resolve through the
uni-xervo provider layer. A model is named by a provider id (e.g.
local/onnx, local/mistralrs, remote/openai) plus a model id; xervo
loads and warms it. Because the model selection is data, not code, you
swap a model by editing config fields — no recompile of the pipeline.
flowchart LR
UC[UnikoConfig] --> E[embedding: EmbeddingConfig]
UC --> N[nlp: NlpConfig]
UC --> R[reranker: RerankerConfig]
E --> X[uni-xervo provider layer]
N --> X
R --> X
X --> ONNX[local/onnx]
X --> MRS[local/mistralrs]
X --> REMOTE[remote/openai · remote/voyageai · ...]
The embedder¶
The embedder controls auto-embedding (Message, Chunk, Observation,
Summary) and the computed embeddings on Entity, Fact, Topic, Session,
and the rest of the graph. The default is BGE-small-en-v1.5 at 384
dimensions, served by the bundled local/onnx provider.
EmbeddingConfig ships several presets as constructor functions, plus a
preset(name) lookup for string-keyed selection:
The presets and their key properties, all defined in
uniko-store::config:
| Preset name | Constructor | model_id |
Dims | Notes |
|---|---|---|---|---|
bge-small |
bge_small_en_v15() |
BGESmallENV15 |
384 | Default. Query-side prefix only. |
bge-large |
bge_large_en_v15() |
BGELargeENV15 |
1024 | Top-tier MTEB quality; ~10× the params of BGE-small, expect 2–3× slower per-query embedding. |
nomic |
nomic_v15() |
NomicEmbedTextV15 |
768 | 8192 context; needs search_document:/search_query: prefixes. |
nomic-q / nomic-quantized |
nomic_v15_quantized() |
NomicEmbedTextV15Q |
768 | Quantized — faster, lower memory. |
minilm |
minilm_l6_v2() |
AllMiniLML6V2 |
384 | Legacy default for existing databases; no task prefixes. |
embeddinggemma / gemma |
embedding_gemma_300m() |
onnx-community/embeddinggemma-300m-ONNX |
768 | 300M-param Gemma-3 retrieval embedder via the local/onnx HF-Hub fallback. |
embeddinggemma-mistralrs / gemma-mistralrs |
embedding_gemma_300m_mistralrs() |
google/embeddinggemma-300m |
768 | Same weights via local/mistralrs (candle) on GPU. |
Warning
The embedding dimension is part of the on-disk vector index. The
dimensions field must match the model's true output, and you
cannot change the embedder under an existing database without
re-indexing — the index was built at the original dimension. Pick
the embedder up front. validate() rejects a zero dimensions or
batch_size, but it cannot detect a model/dimension mismatch.
Each preset also sets the right task prefixes. Nomic uses
document_prefix = "search_document: " and
query_prefix = "search_query: "; BGE uses a query-only prefix
("Represent this sentence for searching relevant passages: "); MiniLM
uses none. These are baked into the preset because the retriever was
trained on those literal strings.
By default the embedder resolves through local/onnx. To route
embeddings to an OpenAI-compatible endpoint instead, set the provider
to "remote/openai" and pass provider_options (this is where a
base_url lands):
let mut embed = EmbeddingConfig::bge_small_en_v15();
embed.provider = "remote/openai".into();
embed.provider_options = Some(serde_json::json!({
"base_url": "http://litellm-cloud:4000/v1"
}));
The NLP cascade¶
NER and observation extraction run through a single ONNX cascade model,
kniv-deberta. NlpConfig exposes the model id, the ONNX artifact to
load, and a batch size:
use uniko_store::config::NlpConfig;
let nlp = NlpConfig::default();
// model_id: "dragonscale-ai/kniv-deberta-nlp-base-en-xsmall"
// artifact: "onnx/cascade-int8.onnx"
// max_batch_size: 16
| Field | Default | Meaning |
|---|---|---|
model_id |
dragonscale-ai/kniv-deberta-nlp-base-en-xsmall |
HuggingFace id of the cascade model. |
artifact |
onnx/cascade-int8.onnx |
Which ONNX export under model_id/ to load (e.g. INT8 vs FP32). |
max_batch_size |
16 |
Maximum batch size for the ONNX session. |
execution_providers |
None |
EP override; None picks a feature-aware default (CUDA+CPU on gpu-cuda, CoreML+CPU on gpu-metal, CPU otherwise). |
Swapping the cascade variant (xsmall ↔ small, INT8 ↔ FP32) is a config
edit — change model_id or artifact and the loader picks it up.
The cascade's SRL (semantic-role-labelling) head is gated separately on the main config:
When true, the pipeline computes SRL frames (one extra ONNX forward
per VERB per sentence) and downstream extraction uses them. Set it to
false if profiling shows the per-verb re-forward cost is unacceptable
for a workload; NlpResult.srl_frames then stays empty and extraction
behaves as before.
The reranker¶
After hybrid recall fuses candidates with reciprocal rank fusion, an
optional cross-encoder re-scores the top top_n items. The reranker is
enabled by default with cross-encoder/ms-marco-MiniLM-L-6-v2:
use uniko_store::config::RerankerConfig;
let rr = RerankerConfig::default();
// enabled: true
// model_id: "cross-encoder/ms-marco-MiniLM-L-6-v2"
// top_n: 50
// apply_sigmoid: true
// style: "cross-encoder"
| Field | Default | Meaning |
|---|---|---|
enabled |
true |
Register and invoke the reranker. |
model_id |
cross-encoder/ms-marco-MiniLM-L-6-v2 |
Cross-encoder ONNX export. |
top_n |
50 |
Number of top RRF candidates re-scored. Must be >= recall_limit when enabled. |
apply_sigmoid |
true |
Map raw logits to [0, 1] via sigmoid. |
style |
cross-encoder |
"cross-encoder" for BERT-family encoders; "generative" for decoder-LM rerankers (e.g. Qwen3-Reranker). |
execution_providers |
None |
EP override, same semantics as the embedder. |
RerankerConfig ships two presets:
RerankerConfig::minilm_l6() (22M params, the default model) and
RerankerConfig::bge_base() (BAAI/bge-reranker-base, 278M params,
more accurate, CPU-feasible). Both construct with enabled: false; the
Default impl flips MiniLM on.
Tip
MiniLM is 12× faster than BGE-reranker-base, so default-on rerank is cheap relative to the rest of recall. To turn it off entirely:
Warning
When the reranker is enabled, validate() requires
reranker.top_n >= recall_limit. If you raise recall_limit above
top_n (default 50), bump top_n to match or validation fails —
otherwise truncation would discard items the reranker never saw.
Tunable thresholds¶
UnikoConfig exposes the numeric knobs that govern the recall cascade,
hybrid fusion, consolidation, and memory decay. All defaults below are
from UnikoConfig::default().
Recall and fusion¶
| Field | Default | Meaning |
|---|---|---|
recall_limit |
15 |
Maximum items returned from recall. |
recall_token_budget |
8192 |
Maximum total tokens in the context bundle. |
recall_min_score |
0.001 |
Minimum fused score for inclusion. |
recall_vector_weight |
0.5 |
Vector-similarity weight in hybrid fusion [0.0–1.0]. |
recall_bm25_weight |
0.5 |
BM25 fulltext weight in hybrid fusion [0.0–1.0]. |
rrf_k |
60.0 |
k constant for reciprocal rank fusion across query variants; higher flattens top-rank weighting. |
recall_per_variant_limit |
None |
LIMIT applied to each per-variant Cypher query; None falls back to recall_limit. |
query_variants |
[] |
Multi-query reformulation labels. Empty uses the default 4-variant set (keywords, original, declarative, type_anchored). |
Cascade phase gates¶
The recall cascade runs in phases and exits early once a phase covers the query. Coverage gates and the Phase 1 strategy live here:
| Field | Default | Meaning |
|---|---|---|
phase1_coverage_threshold |
0.75 |
Coverage threshold for Phase 1 (Compact) early exit. |
phase2_coverage_threshold |
0.65 |
Coverage threshold for Phase 2 (Expand) early exit. |
phase1_strategy |
"boost" |
Phase 1 contribution strategy: merge, boost, or off. |
phase1_boost_alpha |
0.6 |
Multiplicative weight for Fact scores in the session-chunk boost under phase1_strategy = "boost". |
phase2_mmr_lambda |
0.7 |
MMR relevance-vs-diversity for Phase 2 dedup (1.0 = pure relevance, 0.0 = pure diversity). |
phase2_mmr_duplicate_threshold |
0.85 |
Token-overlap threshold above which a Phase 2 candidate is dropped as a hard duplicate. |
phase2_temporal_enabled |
true |
Temporal-interval channel; fires only when the query has a parsed temporal phrase. |
phase2_graph_enabled |
true |
Graph spreading-activation channel; fires only when the query has a resolvable entity seed. |
phase2_graph_damping |
0.85 |
PPR damping factor for the graph channel. |
phase2_graph_max_iter |
30 |
PPR power-iteration cap for the graph channel. |
phase2_graph_edge_weights |
{} |
Per-edge-type weight multipliers for graph propagation; empty map uses the built-in default_phase2_graph_edge_weights. |
Note
phase1_coverage_threshold and phase2_coverage_threshold must be
in (0.0, 1.0]; validate() rejects values outside that range. The
phase1_strategy is stored as a string (not an enum) so configs
deserialised across feature flags stay compatible.
Entity admission¶
Strict admission keeps NER noise out of the :Entity graph at ingest time —
temporal, measurement, and quoted-string spans (already captured as
observations) and greeting/discourse fragments mis-tagged as people are
dropped, and the low-confidence catch-all is gated:
| Field | Default | Meaning |
|---|---|---|
entity_strict_admission |
true |
Drop NER noise and greeting fragments. Set false to restore legacy admit-everything behavior (for A/B comparison). |
entity_other_min_confidence |
0.9 |
Minimum confidence for an Other catch-all span (Event/Product/WorkOfArt/Group/Misc) to be admitted when strict admission is on. |
Consolidation¶
Consolidation (P4) collapses near-duplicate Facts and refreshes their embeddings:
| Field | Default | Meaning |
|---|---|---|
consolidation_threshold |
20 |
Number of observations that trigger consolidation. |
consolidation_interval_secs |
900 |
Seconds between periodic consolidation runs. |
consolidation_cluster_objects |
true |
Cosine-cluster object surface forms before mode-voting picks the canonical (vs legacy exact-string buckets). |
consolidation_date_augment_embedding |
true |
Prepend a month-year prefix (from the Fact's first_observed) to the embedded Fact text so temporally-near Facts co-locate. |
Memory decay¶
Importance decays exponentially and low-importance nodes are pruned:
config.half_life_days = 30.0; // importance * exp(-ln(2)/half_life * age_days)
config.prune_below = 0.05; // prune nodes below this importance
| Field | Default | Meaning |
|---|---|---|
half_life_days |
30.0 |
Half-life (days) for importance decay. Must be positive. |
prune_below |
0.05 |
Importance threshold below which nodes are pruned. Must be in [0.0, 1.0). |
Chunking and sessions¶
The chunking thresholds match the ingest pipeline's content router:
| Field | Default | Meaning |
|---|---|---|
message_chunk_threshold |
1024 |
Token count above which a Message is chunked (below this it embeds directly). |
action_output_artifact_threshold |
256 |
Token count above which an Action output overflows to an Artifact. |
max_chunk_tokens |
256 |
Maximum tokens per chunk. |
min_chunk_tokens |
32 |
Minimum tokens per chunk (fragments below this merge). |
chunk_overlap_tokens |
0 |
Overlap tokens between adjacent chunks; 0 = auto (10% of max, capped at 50). |
session_inactivity_secs |
3600 |
Inactivity window after which an open Session auto-closes and is summarised; 0 disables. |
Note
validate() requires min_chunk_tokens < max_chunk_tokens. The
design rationale and per-content-type chunking strategies (recursive
splitting, tree-sitter AST, DOM sections, etc.) are described in the
ingest pipeline; these fields are the runtime levers over that
machinery.
Vector index¶
The index algorithm and distance metric are set once at database creation:
use uniko_store::config::{VectorAlgorithm, VectorMetricChoice};
config.vector_algorithm = VectorAlgorithm::HnswSq { m: 16, ef_construction: 100 };
config.vector_metric = VectorMetricChoice::Cosine;
The default is HnswSq { m: 16, ef_construction: 100 } with Cosine
distance. Other variants cover HnswFlat, HnswPq, IvfSq, IvfPq,
and IvfRq for different scale / recall / memory trade-offs.
Pipelines, retries, and circuit breaker¶
| Field | Default | Meaning |
|---|---|---|
ingest_queue_capacity |
200 |
Bounded channel capacity for the ingest worker. |
consolidation_queue_capacity |
32 |
Bounded channel capacity for the consolidation worker. |
retry_max_attempts |
3 |
Maximum retry attempts for retryable operations. |
retry_initial_delay_ms |
500 |
Initial backoff delay (exponential base). |
retry_max_delay_ms |
30000 |
Backoff cap. |
circuit_failure_threshold |
5 |
Consecutive failures before the circuit breaker opens. |
circuit_recovery_ms |
60000 |
Milliseconds the breaker stays open before probing. |
validate() requires retry_initial_delay_ms <= retry_max_delay_ms.
Blob storage and external files¶
| Field | Default | Meaning |
|---|---|---|
blob_storage |
BlobStorage::Lance |
Backend for :ArtifactContent bytes; persisted on first open. Reopening with a different variant is a hard error (no implicit migration). |
catalog_path |
None |
Override the built-in xervo model catalog with a JSON file. |
schema_path |
None |
Load the schema from a JSON file instead of the builder. |
observation_rules_path |
None |
Load observation-extraction patterns from a YAML file instead of the bundled english.yml. |
Cargo feature flags¶
A few capabilities are gated behind Cargo features so a CPU-only or offline build stays lean. Enable them per crate.
| Crate | Feature | Default | Effect |
|---|---|---|---|
uniko-store |
gpu-cuda |
off | Enable NVIDIA CUDA acceleration in uni-db (requires the CUDA toolkit at build time). |
uniko-store |
gpu-metal |
off | Enable Apple Metal/CoreML acceleration in uni-db (macOS only). |
uniko-extract |
code-parse |
on | tree-sitter AST chunking for Python, Rust, JavaScript, TypeScript. |
uniko-extract |
onnx |
off | ONNX runtime (ort + tokenizers + ndarray) for the embedder / NLP cascade adapter. |
uniko-memory |
onnx |
off | Pass-through that enables uniko-extract/onnx. |
uniko-memory |
llm |
off | Adds an LLM-rewritten abstractive path for F59 Summaries. By default, summaries are deterministic, extractive, and fully offline. |
uniko-cortex |
llm |
off | Backs uniko-memory/llm. |
Tip
The execution_providers field on EmbeddingConfig, NlpConfig,
and RerankerConfig is feature-aware: with None, a build compiled
with gpu-cuda defaults to CUDA→CPU, gpu-metal to CoreML→CPU, and
a plain build to CPU. You only set execution_providers explicitly
to override that default.
See also¶
- Architecture — how the layers and the
KnowledgeBasefit together. - Recall — what the cascade phases and fusion weights actually do.