| title | Configuration |
|---|
Uteke supports uteke.toml configuration with layered resolution.
Uteke searches for config in this order. Last match wins (highest priority):
- (built-in defaults) β Hardcoded defaults
~/.codecora/uteke/uteke.tomlβ Global user-level config.uteke/uteke.tomlβ Project-level (in current working directory)
Config file path is auto-resolved (no --config flag). Layered merge: each file overlays the previous, with field-level granularity (only keys explicitly present override).
# uteke.toml
[store]
# Store location (default: ~/.codecora/uteke)
path = "~/.codecora/uteke"
# Default namespace (default: "default")
namespace = "default"
[logging]
# Log level: trace, debug, info, warn, error
level = "warn"
# Optional log file path. Empty = stderr only.
# file = ""
[server]
# Enable CLI auto-routing to server
enabled = false
# Server host
host = "127.0.0.1"
# Server port
port = 8767
[vector]
# Vector search engine when the binary ships BOTH engines (official builds):
# "usearch" (default β HNSW, C++ FFI, fastest) or "vecq" (training-free
# quantization, zero C++ dep β mobile/slim builds). Switching engines on an
# existing store auto-rebuilds the index from SQLite on next open (#1168).
# Env override: UTEKE_VECTOR_BACKEND=usearch|vecq
backend = "usearch"When [server] enabled = true, the CLI automatically routes commands through the running HTTP server:
# Start server
uteke-serve --port 8767
# CLI commands now route via HTTP (21ms vs 980ms cold start)
uteke recall "what was that context?"
uteke remember "New finding" --tags research
uteke statsIf the server is not running, CLI falls back to local store automatically.
| Setting | Default | Description |
|---|---|---|
enabled |
false | Enable CLIβserver routing |
host |
127.0.0.1 | Server bind address |
port |
8767 | Server port |
Configure the embedding backend. Three backends are supported:
onnx(default) β fully offline, EmbeddingGemma Q4, 768d. Zero API keys, zero network.openaiβ OpenAItext-embedding-3-small(1536d) ortext-embedding-3-large(3072d). Requires API key.ollamaβ local Ollama server with models likenomic-embed-text(768d) ormxbai-embed-large(1024d). No API key, runs onhttp://localhost:11434.
[embedding]
backend = "onnx" # onnx | openai | ollama
model = "embeddinggemma-q4" # backend-specific
max_seq_length = 2048
api_key = "" # OpenAI only (or use UTEKE_EMBEDDING_API_KEY)
base_url = "" # custom endpoint (Azure OpenAI, Ollama URL, proxy)
endpoint_path = "" # custom API path (default: /embeddings for OpenAI)
dims = 0 # 0 = use model default (override only if you know)| Setting | Default | Description |
|---|---|---|
backend |
onnx |
onnx, openai, or ollama |
model |
embeddinggemma-q4 |
Backend-specific model name |
max_seq_length |
2048 |
Max tokens per input |
api_key |
"" |
OpenAI API key (ONNX/Ollama ignore) |
base_url |
"" |
Custom endpoint. Empty = backend default |
endpoint_path |
"" |
Custom API path appended to base_url. Empty = /embeddings (OpenAI) |
dims |
0 |
Force dims. 0 = backend/model default |
When you set backend = "openai" or "ollama" and leave model/base_url/dims empty, uteke picks:
| Backend | Model | Base URL | Dims |
|---|---|---|---|
onnx |
embeddinggemma-q4 |
(local) | 768 |
openai |
text-embedding-3-small |
https://api.openai.com/v1 |
1536 |
ollama |
nomic-embed-text |
http://localhost:11434 |
768 |
Set backend = "openai", base_url = "https://<your-resource>.openai.azure.com/openai/deployments/<deployment>?api-version=2024-10-21" and api_key to your Azure key. The request path /embeddings is appended automatically. Azure requires the api-version query param β include it in base_url.
If you open an existing store with a different backend (different dims), the first embedding operation returns a clear error instead of silently corrupting the index:
Embedding dimension mismatch: index has 768d vectors but backend 'openai' produces 1536d.
Rebuild the index (`uteke repair`) or switch backend.
To migrate, run uteke repair after switching backends β it rebuilds the vector index from the SQLite source of truth using the new backend's embeddings. Because the dim-mismatch guard will block any embed-based operation on first contact, set UTEKE_ALLOW_DIM_MISMATCH=1 once to let uteke repair open the store with the new backend:
UTEKE_ALLOW_DIM_MISMATCH=1 uteke repairWhen the primary embedding backend fails (model not found, OOM, network error), uteke can transparently retry with a fallback endpoint. This is opt-in β if unconfigured, no cloud calls are made.
[embed_fallback]
api_key = "" # or use UTEKE_EMBED_FALLBACK_API_KEY
base_url = "" # e.g. "https://api.openai.com/v1"
endpoint_path = "" # path appended to base_url. Empty = "/embeddings"
model = "" # e.g. "text-embedding-3-small"| Setting | Default | Description |
|---|---|---|
api_key |
"" |
API key for the fallback endpoint |
base_url |
"" |
Fallback API endpoint (OpenAI-compatible) |
endpoint_path |
"" |
Path appended to base_url. Empty = /embeddings |
model |
"" |
Fallback embedding model |
Fallback is active when all three of api_key, base_url, and model are non-empty. No enabled flag needed β empty fields mean inactive.
Environment variables take precedence: UTEKE_EMBED_FALLBACK_API_KEY, UTEKE_EMBED_FALLBACK_BASE_URL, UTEKE_EMBED_FALLBACK_ENDPOINT_PATH, UTEKE_EMBED_FALLBACK_MODEL.
Dimension validation β if the fallback produces different dimensions than the primary, uteke rejects it at startup with a clear error. Both backends must produce vectors of the same dimensionality.
Configure LLM-backed fact extraction for uteke import --extract. This is
opt-in: the section is inert unless you pass --extract. When you do, uteke
sends source text to an OpenAI-compatible chat-completions endpoint and stores
the distilled atomic facts. This is the only feature that makes outbound LLM
calls; everything else stays offline.
[extraction]
model = "gpt-4o-mini" # chat model (or UTEKE_EXTRACTION_MODEL)
api_key = "" # or UTEKE_EXTRACTION_API_KEY; falls back to the
# embedding / OPENAI_API_KEY credential
base_url = "" # OpenAI-compatible base URL. Empty = OpenAI default
endpoint_path = "" # custom API path. Empty = /chat/completions
max_facts = 0 # cap facts per document. 0 = built-in default| Setting | Default | Description |
|---|---|---|
model |
"" |
Chat model used to distill facts |
api_key |
"" |
API key (falls back to embedding/OPENAI_API_KEY) |
base_url |
"" |
OpenAI-compatible base URL. Empty = OpenAI default |
endpoint_path |
"" |
API path appended to base_url. Empty = /chat/completions |
max_facts |
0 |
Cap facts kept per document. 0 = built-in default |
Resolution order per field: CLI flag (--extract-*) > UTEKE_EXTRACTION_* env
var > [extraction] config > built-in default.
Control minimum similarity score for recall results:
[recall]
# Minimum similarity score (0.0-1.0). Memories below this score are excluded.
# Default: 0.3 (balanced). Use 0.0 to disable filtering.
min_score = 0.0
# Strict-mode threshold (used with `--strict` flag)
min_score_strict = 0.5
# Default recall strategy for `uteke recall` when --strategy is not given.
# One of: fusion | vector | fts5 | hybrid | graph.
# fusion β weighted RRF of the vector and hybrid rankings (default since 0.16.0;
# LongMemEval 500Q R@5 0.946 vs 0.854 hybrid, #1123)
# vector β vector similarity only (original behavior)
# fts5 β full-text search only
# hybrid β vector + FTS5 fused via Reciprocal Rank Fusion (k=60)
# graph β hybrid + graph-signal reranking (#378): well-connected memories
# get a subtle log-scaled score boost
default_strategy = "fusion"
# Graph-augmented reranking weights (only affect the `graph` strategy).
# Boosts are additive + log-scaled, so 0.1 is subtle and saturates quickly.
graph_density_weight = 0.1 # edge-count boost
graph_authority_weight = 0.1 # incoming-edge (referenced-by) boost
graph_rerank_enabled = true # master switch; false β graph acts like hybrid| Setting | Default | Description |
|---|---|---|
min_score |
0.0 | Minimum similarity score (0.0-1.0). CLI only. |
min_score_strict |
0.5 | Strict-mode threshold (used with --strict). CLI only. |
default_strategy |
fusion |
Default recall strategy (vector|fts5|hybrid|graph|fusion). Since 0.16.0 fusion (weighted RRF: vectorΓ1.7 + hybridΓ1, #1123) is the default β LongMemEval fast50 R@5 0.98 vs 0.9267 hybrid |
graph_density_weight |
0.1 | Edge-density boost weight (graph strategy only) |
graph_authority_weight |
0.1 | Incoming-edge authority boost weight (graph strategy only) |
graph_rerank_enabled |
true | Master switch for graph reranking |
β οΈ Threshold defaults differ across interfaces (#995)
Interface Default min_scoreNotes CLI 0.0(from[recall] min_score)Reads from uteke.toml. Set--minor[recall] min_scoreto enable.HTTP API / Server 0.0(DEFAULT_MIN_SCORE)Server has its own constant, ignores [recall] min_score.Note (since 0.17.x, #1223): default recall scores under the fusion strategy are rank-based (RRF contribution + salience/recency boosts, typically ~0.0-0.2), not raw cosine similarity. A legacy cosine-era default threshold of 0.3 silently filtered out nearly all results, so the default is now 0.0 across every surface. Set a threshold explicitly (config
min_score,--min,--strict, or HTTPmin_score) only when you know the score scale of the active strategy. | MCP |0.0| MCP server hardcodes 0.0. |This means a score of 0.25 is returned by API/MCP but filtered out by CLI. For benchmark/retrieval evaluation, always pass
--min 0.0explicitly to evaluate raw ranking quality without UX threshold filtering.
Dual-axis recall ranking boost. Applied after the RRF merge and recall cache lookup.
- Salience β higher score for high-value memory types (decision > insight > fact > note). Per-type decay rates are hardcoded in
type_half_life_days(). - Recency β exponential decay
exp(-age/Ο)where Ο is a per-type time constant.
Opt-in per query via --salience / --recency CLI flags. The dream cycle's compact phase can use these for smarter pruning.
| Setting | Default | Description |
|---|---|---|
salience_weight |
0.15 | Salience boost weight (0 = disable) |
recency_weight |
0.15 | Recency boost weight (0 = disable) |
jaccard_weight |
0.0 | Jaccard token-overlap reranking weight (0 = disable) |
Enabled by default (0.15). Override per-query with --strict, --min <score>, or --strategy <name>.
Environment variables override config file values. Applied in Config::load() after config file merge. CLI flags override env vars.
Resolution order (highest priority first):
- CLI flag (
--min,--host,--port) - Environment variable (
UTEKE_*) - Config file (
uteke.toml) - Built-in default
| Env Var | Config Equivalent | Default | Description |
|---|---|---|---|
UTEKE_HOME |
β | ~/.codecora/uteke |
Data directory |
UTEKE_NAMESPACE |
[store] namespace |
default |
Default namespace (applied in CLI) |
UTEKE_AUTH_TOKEN |
β | β | Server auth token (applied in server) |
UTEKE_LOG_LEVEL |
[logging] level |
warn |
Log level (trace/debug/info/warn/error) |
UTEKE_VECTOR_BACKEND |
[vector] backend |
compiled-in default | Vector engine when both are compiled in: usearch, vecq. Ignored (with a warning) on single-engine builds or unknown values. Switching engines on an existing store auto-rebuilds the index from SQLite |
UTEKE_SERVER_HOST |
[server] host |
127.0.0.1 |
Server bind address |
UTEKE_SERVER_PORT |
[server] port |
8767 |
Server port |
UTEKE_RECALL_MIN_SCORE |
[recall] min_score |
0.0 |
Default similarity threshold |
UTEKE_RECALL_MIN_SCORE_STRICT |
[recall] min_score_strict |
0.5 |
Strict threshold |
UTEKE_RECALL_STRATEGY |
[recall] default_strategy |
fusion |
Default recall strategy (vector|fts5|hybrid|graph|fusion) |
UTEKE_GRAPH_DENSITY_WEIGHT |
[recall] graph_density_weight |
0.1 |
Edge-density boost weight |
UTEKE_GRAPH_AUTHORITY_WEIGHT |
[recall] graph_authority_weight |
0.1 |
Incoming-edge authority boost weight |
UTEKE_GRAPH_RERANK_ENABLED |
[recall] graph_rerank_enabled |
true |
Master switch for graph reranking |
UTEKE_EMBEDDING_BACKEND |
[embedding] backend |
onnx |
Embedding backend: onnx, openai, ollama |
UTEKE_EMBEDDING_MODEL |
[embedding] model |
backend-specific | Override model name |
UTEKE_EMBEDDING_API_KEY |
[embedding] api_key |
β | API key (OpenAI). Fallback: OPENAI_API_KEY |
UTEKE_EMBEDDING_BASE_URL |
[embedding] base_url |
backend-specific | Custom endpoint URL |
UTEKE_EMBEDDING_ENDPOINT_PATH |
[embedding] endpoint_path |
β | Custom API path (default: /embeddings) |
UTEKE_EMBEDDING_DIMS |
[embedding] dims |
0 (auto) |
Force embedding dimensionality |
UTEKE_MAX_SEQ_LENGTH |
[embedding] max_seq_length |
2048 |
Max tokens per embedding input |
UTEKE_EXTRACTION_MODEL |
[extraction] model |
β | Chat model for import --extract |
UTEKE_EXTRACTION_API_KEY |
[extraction] api_key |
β | API key. Fallback: embedding key / OPENAI_API_KEY |
UTEKE_EXTRACTION_BASE_URL |
[extraction] base_url |
OpenAI default | OpenAI-compatible endpoint base URL |
UTEKE_EXTRACTION_ENDPOINT_PATH |
[extraction] endpoint_path |
β | Custom API path (default: /chat/completions) |
UTEKE_EXTRACTION_MAX_FACTS |
[extraction] max_facts |
0 (default) |
Cap facts kept per document |
docker run -d --name uteke \
-p 127.0.0.1:8767:8767 \
-v uteke-data:/data \
-e UTEKE_LOG_LEVEL=info \
-e UTEKE_RECALL_MIN_SCORE=0.5 \
ghcr.io/codecoradev/uteke:latestIf you have an older flat-format config (pre-v0.0.4), uteke auto-migrates it on first run:
# Old format (auto-detected and migrated)
path = "~/.codecora/uteke"
default_namespace = "default"
log_level = "info"
β Auto-migrated to β
[store]
path = "~/.codecora/uteke"
namespace = "default"
[logging]
level = "info"No manual action needed β old config keys are automatically converted to the new sectioned format.
Namespace is resolved in this order (highest priority first):
--namespace flagβ CLI flag (highest priority)UTEKE_NAMESPACEβ Environment variableuteke.toml [store] namespaceβ Config file"default"β Built-in default
Switch default namespace permanently with uteke namespace switch <name> β this updates the config file.
Place a .uteke/uteke.toml in your project root to override defaults for that project:
# my-project/.uteke/uteke.toml
[store]
path = "./.uteke"
namespace = "my-project"
[logging]
level = "warn"Combined with shell hooks, this enables automatic project-scoped memory β each project gets its own isolated memory store.
A project directory can be an untrusted clone, so .uteke/uteke.toml is not allowed to decide where credentials or memory text are sent or how the server authenticates. These keys are ignored when they come from the project file (a warning is logged) and must be set in the global uteke.toml instead:
[embedding]:backend,api_key,base_url,endpoint_path[embed_fallback]and[extraction]:api_key,base_url,endpoint_path- the whole
[server]section (enabled,host,port,auth_token,read_only_token,cors_origins,allowed_hosts)
Everything else (store path, namespace, logging, recall tuning, lifecycle, ...) can be set per project. To trust a project file completely, set UTEKE_TRUST_PROJECT_CONFIG=1. The CLI and uteke-serve apply the same rules and the same per-key merge.
CLI flags always take precedence over config file values:
# Override store path
uteke --store /path/to/project/.uteke remember "project note"
# Override namespace
uteke --namespace agent-1 recall "context"
# Override namespace via env
UTEKE_NAMESPACE=agent-1 uteke recall "context"Logs are written to ~/.codecora/uteke/logs/uteke.log with daily rotation:
~/.codecora/uteke/logs/
βββ uteke.log # Current log
βββ uteke.log.2026-05-29 # Yesterday's log
βββ uteke.log.2026-05-28 # Two days ago
Non-blocking async writer β logging never blocks memory operations. Rotated files are kept until manually deleted.
All hardcoded limits can be overridden via env vars or the [limits] section:
[limits]
max_content_length = 100000 # Max memory content (chars). 0 = disable
max_tags_count = 20 # Max tags per memory
max_tag_length = 50 # Max single tag length (chars)
max_payload_size = 10485760 # Max server payload (bytes, default 10MB)
default_recall_limit = 5 # Default recall limitEnvironment variables override config values:
| Env Var | Default | Description |
|---|---|---|
UTEKE_MAX_CONTENT_LENGTH |
100000 | Max memory content length |
UTEKE_MAX_TAGS_COUNT |
20 | Max tags per memory |
UTEKE_MAX_TAG_LENGTH |
50 | Max tag length |
UTEKE_MAX_PAYLOAD_SIZE |
10485760 | Max server payload |
UTEKE_DEFAULT_RECALL_LIMIT |
5 | Default recall limit |
The server supports dual-role authentication:
[server]
enabled = true
host = "127.0.0.1"
port = 8767# Start with admin + read-only tokens
uteke-serve --auth-token admin-secret --read-only-token viewer-key
# Or via env vars
UTEKE_AUTH_TOKEN=admin-secret UTEKE_READ_ONLY_TOKEN=viewer-key uteke-serveRead-only tokens can only access GET endpoints (recall, search, list, stats, graph, health).
POST/DELETE operations return 403 Forbidden.
GET /health also answers without a token (for load balancers and uptime probes),
but tokenless β or invalidly-authenticated β requests receive only {"status":"ok"}.
Version, memory counts, and update info require a valid token (or auth disabled).
Controls automatic memory lifecycle management. Disabled by default β opt-in for long-running stores.
[aging]
enabled = false # Set true to enable automatic cleanup
max_age_days = 365 # Prune memories older than this
max_access_count = 10 # Only prune if accessed fewer than this many times
max_cold_count = 1000 # Max cold memories to keep before triggering cleanup| Setting | Default | Description |
|---|---|---|
enabled |
false | Enable automatic aging |
max_age_days |
365 | Maximum age in days before pruning |
max_access_count |
10 | Max access count to be considered "cold" |
max_cold_count |
1000 | Max cold memories before cleanup triggers |
Controls auto-aging and auto-dream background tasks when running in server mode.
[maintenance]
auto_aging_enabled = false # Periodically clean up cold, stale memories
auto_aging_interval_hours = 24 # How often to run aging
auto_dream_enabled = true # Periodically run dream cycle (lint β dedup β orphans)
auto_dream_interval_days = 7 # How often to dream| Setting | Default | Description |
|---|---|---|
auto_aging_enabled |
false | Enable auto-aging in server mode |
auto_aging_interval_hours |
24 | Aging check interval |
auto_dream_enabled |
true | Enable auto-dream in server mode |
auto_dream_interval_days |
7 | Dream cycle interval |
Configurable thresholds for the dream maintenance pipeline (contradiction scan, dedup, orphan detection). All values were hardcoded before v0.9.0.
[dream]
contradict_similarity_threshold = 0.6 # Above this cosine = NOT a contradiction
contradict_tag_jaccard_min = 0.4 # Min tag overlap to consider contradiction
contradict_max_memories = 200 # Max memories for O(nΒ²) scan
dedup_threshold = 0.92 # Cosine above this = merge candidate
orphan_importance_threshold = 0.15 # Below this + no edges = orphan| Setting | Default | Description |
|---|---|---|
contradict_similarity_threshold |
0.6 | Cosine threshold for contradiction detection |
contradict_tag_jaccard_min |
0.4 | Min Jaccard tag overlap for contradiction |
contradict_max_memories |
200 | Max memories loaded for contradiction scan |
dedup_threshold |
0.92 | Cosine threshold for dedup merging |
orphan_importance_threshold |
0.15 | Importance threshold for orphan flagging |
Uteke checks GitHub for newer releases at startup. The result is cached for 24 hours to avoid repeated network calls.
update_check = true # Set to false to disable startup update notification| Setting | Default | Description |
|---|---|---|
update_check |
true | Enable background update notification on startup |
When an update is available, a banner is printed to stderr (does not interfere with stdout pipes). The check is non-blocking: it runs in a background thread joined before process exit, so it never delays command execution.
Set the UTEKE_NO_UPDATE_CHECK=1 environment variable to skip the update check entirely. This is useful for benchmarks, scripts, or automated pipelines that invoke uteke many times via subprocess:
export UTEKE_NO_UPDATE_CHECK=1Uteke uses a soft-delete lifecycle model. Memories are never hard-deleted directly by automated processes. They transition through a deprecated state before eventual pruning.
remember() β ACTIVE β soft_deprecate() β DEPRECATED (hidden, restorable) β prune() β HARD DELETE
| State | Visible in recall? | Restorable? | TTL |
|---|---|---|---|
| ACTIVE | β Yes | N/A | β |
| DEPRECATED | β Hidden | β
promote() |
30 days (configurable) |
| PRUNED | β Gone | β No | β |
[lifecycle]
# Master switch: when true, ALL delete operations become soft-deletes.
# forget(), bulk_forget, aging_cleanup, consolidate, delete() all redirect to deprecate.
soft_delete_only = true
# Auto-lifecycle background thread (server mode only).
auto_aging_enabled = true
auto_aging_interval_hours = 168 # Run cycle every 7 days
# What qualifies as "aged" β candidate for deprecation.
min_age_days = 90 # Must be at least 90 days old
max_access_count = 3 # Accessed 3 times or fewer
# Rate limiting: max % of active memories deprecated per cycle.
max_deprecate_percent = 1.0 # 1% of active per cycle
min_deprecate_per_cycle = 1 # Floor (always at least 1)
max_deprecate_per_cycle = 50 # Ceiling (never more than 50)
# Deprecated TTL: how long before soft-deleted memories are pruned (hard delete).
deprecated_ttl_days = 30 # 30 days in deprecated state
auto_prune_enabled = true # Auto-prune expired deprecated memories
# Dream dedup: when consolidating duplicates, soft-delete instead of hard delete.
dream_dedup_soft_delete = true| Setting | Default | Description |
|---|---|---|
soft_delete_only |
true | Master switch: all deletes become soft-deletes |
auto_aging_enabled |
true | Enable auto-lifecycle background thread (server) |
auto_aging_interval_hours |
168 | Hours between auto-lifecycle cycles |
min_age_days |
90 | Minimum age (days) to be eligible for deprecation |
max_access_count |
3 | Max access count for deprecation eligibility |
max_deprecate_percent |
1.0 | Max % of active memories deprecated per cycle |
min_deprecate_per_cycle |
1 | Floor for per-cycle deprecation cap |
max_deprecate_per_cycle |
50 | Ceiling for per-cycle deprecation cap |
deprecated_ttl_days |
30 | Days before deprecated memories are pruned |
auto_prune_enabled |
true | Auto-prune expired deprecated memories in lifecycle cycle |
dream_dedup_soft_delete |
true | Consolidation soft-deletes instead of hard-deletes |
Each cycle runs two phases:
- Deprecate phase: Find aged memories (old + rarely accessed) β apply percentage cap β batch deprecate.
- Prune phase: Find deprecated memories past TTL β hard delete (irreversible).
The percentage cap ensures at most 1β2% of active memories are deprecated per cycle, preventing sudden data loss. The cap is calculated as:
cap = clamp(total_active Γ max_deprecate_percent / 100, min_per_cycle, max_per_cycle)
Hard delete only occurs in two explicitly controlled paths:
prune(): Deletes deprecated memories whose TTL has expired. Only runs whenauto_prune_enabled = true.forget(): Bypasses soft-delete only whensoft_delete_only = false(default istrue, so forget = soft-delete by default).
All other paths (delete(), bulk_delete(), aging_cleanup(), consolidate()) respect soft_delete_only and deprecate instead.