NewDakera v0.12.0 is out: multilingual, multimodal and multi-vector memory, faster reranked recall, one-command rollbackSee what's new →

Upgrading from v0.11.108

This guide is for operators moving a v0.11.108 deployment to v0.12.0. It lists the steps to take before the upgrade, what happens on the first start, and every behavior change an existing deployment can notice, with what to do about each.

An unchanged v0.11.108 deployment upgrades in place: stop v0.11.108, start v0.12.0 on the same data and the same environment. It starts, keeps its data and answers as before, except for the defects v0.12.0 fixes on purpose, which are listed below. Going back is supported for every deployment on a v0.11 embedding model, encrypted or not: stop v0.12.0, run dakera downgrade, start v0.11.108 (see Rolling back to v0.11). For a summary of the release, see What's new in v0.12.0.

Upgrade checklist

  1. Take a backup: POST /admin/backups.
  2. Check the configuration with the new image: run dakera --check-config against the deployment's environment and data volume. It lists every warning the first start will give (see Checking a configuration first).
  3. Give gRPC clients an API key when authentication is on (gRPC authentication).
  4. Check keys pinned to namespaces and backup tooling against the new permissions model.
  5. Check namespace quotas. They are now enforced: a quota set on v0.11 (which ignored it) refuses writes over it (Quotas).
  6. Decide about the backup schedule. A stored, enabled schedule starts running at its next slot; disable it first if you do not want that.
  7. Point probes at /health/ready (readiness) and /health/live (liveness). The port now binds before the models load; the Helm chart and the compose file already do this.
  8. Re-check any threshold on smart_score (or DAKERA_ABSTAIN_MIN_SMART_SCORE) (Recall and ranking).
  9. Clusters: plan a short mixed period and read Cluster upgrade. Leave DAKERA_CLUSTER_SECRET unset until every node runs v0.12.0; an encrypted cluster holds sealed changes for v0.11 nodes until they are upgraded.
  10. Model volumes: do not run dakera models prune until you are staying on v0.12 (it removes the files v0.11.108 reads).
  11. Stop v0.11.108 and start v0.12.0 on the same data and environment.
  12. Rebuild the full-text indexes once with POST /admin/fulltext/reindex and {"rebuild": true}, as soon as the server is ready. This is required after upgrading data from v0.11.108 (details and commands).
  13. Watch /health: config_warnings, degraded, embed_migration (and, with encryption, GET /admin/encryption/status).

After the upgrade: rebuild the full-text indexes

Required post-upgrade step. After you upgrade a deployment that holds data from v0.11.108, rebuild its full-text indexes once. Until you do, keyword search and keyword-style recall can return nothing. Fresh v0.12.0 installs do not need it. From v0.12.1 (not released yet) Dakera applies it automatically; until then, run it yourself.

Full-text indexes built by v0.11 are re-analysed under v0.12's text analysis. The rebuild does that once, so keyword search and keyword-style recall return results again. It only rebuilds derived search indexes; your memories are not touched. On a production deployment with about 18,000 memories it took about 18 seconds. Run it once the server is ready (GET /health/ready answers 200), with a key of global admin scope:

curl -X POST https://<your-dakera>/admin/fulltext/reindex \
  -H "x-api-key: <global admin key>" -H "content-type: application/json" \
  -d '{"rebuild": true}'

Omit namespace to cover every agent memory namespace; add "namespace": "<ns>" to rebuild one. The response lists each namespace it processed (see Multilingual search for the fields).

Docker Compose

docker compose exec dakera curl -s -X POST http://localhost:3000/admin/fulltext/reindex \
  -H "x-api-key: $DAKERA_ROOT_API_KEY" -H 'content-type: application/json' -d '{"rebuild":true}'

This needs curl inside the container; the v0.12 image's healthcheck uses it. If your image does not have it, run the host-side curl above against the published port (for example http://localhost:3000).

Kubernetes

Forward the service port and run the same curl from your workstation:

kubectl port-forward svc/dakera 3000:3000 &
curl -X POST http://localhost:3000/admin/fulltext/reindex \
  -H "x-api-key: $ADMIN_KEY" -H "content-type: application/json" \
  -d '{"rebuild": true}'

Or run it as a one-off Job from an image that has curl, pointing at the in-cluster service URL. Adjust the service name and namespace to your release.

Check that it worked

If either still returns nothing, see Troubleshooting.

What happens on the first start

Nothing below needs an operator step. Recall and ingest are served throughout.

What How it behaves How to watch it
Configuration check Names and values v0.12 cannot use are warnings, not refusals, because the data root already holds data. They are logged as CONFIGURATION WARNING and listed in GET /health under config_warnings. The same configuration on an empty data directory (a fresh install) is refused with exit code 78. startup log; /health config_warnings
Data locations v0.12 derives every local path from one data root (DAKERA_STORAGE_PATH). Data v0.11 kept elsewhere (the write-ahead log at ./data/wal, the image's old /app/data, the tiered defaults /data/hot and /data/cache/warm) is used where it is while the new place is empty. /health config_warnings (data_location)
Knowledge graph Lives under the data root and survives restarts. v0.11 kept it in memory unless DAKERA_DATA_DIR was set, so a deployment without it starts with an empty graph, exactly what a v0.11 restart gave. With DAKERA_DATA_DIR set, the graph stays where v0.11 kept it, and the first start migrates it once (edges are keyed by namespace), about 0.7 s per 500,000 edges. Data root resolved log line
Text endpoints without model v0.11.108 embedded upsert-text, query-text and batch-query-text with bge-large whatever DAKERA_MODEL said; v0.12 embeds with the configured model. On an upgraded deployment a text namespace that v0.11 filled keeps its model, so nothing breaks. nothing to do
Embed-side migration Memories stored with the query instruction (single store, update, consolidation and session summaries in v0.11) are re-embedded on the document side in the background. Recall ranks move a little while it runs; that is the fix taking effect. About 35 minutes per 10,000 memories on bge-large. /health embed_migration (state: pending, surveying, running, complete, not_needed); GET /admin/reembed/migration
Encryption re-seal With DAKERA_ENCRYPTION_KEY, every $enc$v1$ value is re-sealed to $enc$v2$ (bound to its namespace, record and field) by a background pass; v1 values keep decrypting until then. GET /admin/encryption/status
Embedding-model record The store records its model and embedding recipe. The default model's vectors are unchanged. A v0.11 store has no record: the running model is adopted. gte-modernbert-base (now CLS-pooled) and modernbert-embed-base (now with its required prefixes) changed recipe: their stored memories and text vectors are re-embedded by the embed-side migration, with no refusal. startup log; /health embed_migration
Models The image's models (bge-large, the reranker) are read from the image itself, already converted: nothing is downloaded or converted at the first start, whatever is mounted at /app/models. A model volume kept from v0.11 still holds that image's copies, now unused (about 0.9 GB): dakera models prune frees them, but they are what v0.11.108 reads, so prune only once you are staying on v0.12. Models you downloaded there (another DAKERA_MODEL, whisper, ...) are used as before and converted once for v0.12. dakera models list
Tiered hot tier The default is now a persistent RocksDB hot tier (v0.11: in memory). It starts empty; reads fall through to warm and cold. DAKERA_HOT_TIER=memory keeps the old tier. /admin/storage/tiers
Telemetry Only when telemetry is on; DAKERA_TELEMETRY and DO_NOT_TRACK are read as v0.11 read them, and an opted-out deployment still sends nothing. v0.12.0 adds lifecycle events (engine_started, engine_stopped, engine_crashed, engine_version_changed) and sends the heartbeat every 6 hours instead of 24. engine_started and the heartbeat carry the machine's hostname, and PostHog keeps the connecting IP address and derives a coarse location from it (GeoIP). No content, ids, secrets or paths are sent. See Telemetry. startup log line; DAKERA_TELEMETRY_DEBUG=1

Stored records are written byte-for-byte as in v0.11 (the binary record format is read-only in v0.12). Index snapshots and the BM25 base format are unchanged; HNSW keeps its in-RAM vectors in f16 with no on-disk change.

Startup and configuration changes

Health endpoints

The REST port now answers while the server starts. v0.11 bound the port only after the embedding model had loaded, so a model that was not in the image (bge-m3, colbert-small) downloaded behind a closed port, and a Docker HEALTHCHECK or Kubernetes liveness probe restarted the process.

Endpoint Answer
GET /health/live 200 as soon as the port is bound. Use it for liveness.
GET /health/ready 200 once the embedding model is loaded and storage answers; 503 while starting, with "starting": true, a reason and downloads (each model file with received_bytes and total_bytes). Use it for readiness.
GET /health The same startup answer while starting; afterwards status, plus degraded (components running degraded, each with component and reason) and config_warnings. Unauthenticated, so reasons show paths as <path> and credential-bearing URLs as <url>.

Every other request answers 503 with Retry-After: 5 until the models are loaded. A client that waited for the connection to be accepted should wait for /health/ready (or retry the 503). A download that was cut short continues where it stopped when the server serves byte ranges. The images' HEALTHCHECK start period is now 10 minutes, and the Helm chart's liveness probe reads /health/live behind a 10-minute startup probe.

Checking a configuration first

dakera --check-config runs the startup configuration check alone (same environment, same data root, same decision), prints what it found and exits 0 (the server would start, warnings included) or 78 (it would refuse). It starts nothing and writes nothing.

docker run --rm -e DAKERA_ROOT_API_KEY=... -v dakera-data:/data \
  ghcr.io/dakera-ai/dakera:0.12.0 --check-config

Once running, the gauge dakera_config_warnings counts the config_warnings entries; alert on > 0.

Security and access

gRPC authentication and validation

v0.11.108's gRPC port (DAKERA_GRPC_PORT, default 50051) had no authentication. With DAKERA_AUTH_ENABLED on (the default), every RPC except Health now needs a key the REST API accepts, sent in the call metadata:

x-api-key: <key>
authorization: Bearer <key>

What to do: give every gRPC client a key in its metadata before upgrading, or keep DAKERA_AUTH_ENABLED=false if the deployment ran without authentication.

Permissions

A key pinned to namespaces (for example an admin key minted by POST /v1/namespaces/{ns}/keys) used to pass every check that asked for a scope only. These routes now answer 403 where v0.11.108 did not:

Route v0.11.108 v0.12
POST /admin/backups/restore, POST /admin/backups/upload, GET /admin/backups/{id}/download admin super_admin with no namespace restriction (a bundle carries every API key record)
Every other /admin/* and /v1/admin/* route admin admin with no namespace restriction, except DELETE /admin/namespaces/{ns}, POST /admin/namespaces/{ns}/optimize, POST /admin/cache/warm and POST /admin/indexes/rebuild, which a pinned admin key may still call for its own namespaces
/admin/keys/* super_admin super_admin with no namespace restriction
/ops/diagnostics, /ops/jobs, /ops/compact, /ops/shutdown, /ops/events, /v1/ops/metrics, /debug/config admin admin with no namespace restriction
/v1/analytics/*, /v1/audit*, /v1/kpis, /v1/events/stream, POST /v1/knowledge/network/cross-agent admin admin with no namespace restriction
GET /v1/agents/{agent_id}/sessions read on _dakera_sessions also the agent's namespace
GET /v1/export, POST /v1/import any key read (export) / write (import) on the agent's namespace _dakera_agent_{agent_id}
POST /v1/namespaces, PUT /v1/namespaces/{ns}, GET /v1/namespaces/{ns}/events the scope only the scope on that namespace
POST /v1/namespaces/{ns}/keys with extra_namespaces any namespace only namespaces the calling key can reach

Deployments with authentication off (every request is super_admin) see none of these. An unrestricted admin key can no longer download, upload or restore a backup bundle: use a global super_admin key. POST /ops/shutdown needs a key of global admin scope.

Other security changes

Storage, data and backups

Quotas are enforced

v0.11 stored, returned and replicated namespace quotas (/admin/quotas..., and max_vectors_per_namespace of PUT /admin/config) but no write consulted them. Every write path (REST, gRPC, memory, import, re-embed, background and admin writes) now goes through one quota check. With the default hard enforcement, a write that would take a namespace over max_vectors, max_storage_bytes, max_dimensions or max_metadata_bytes is refused with 413 QUOTA_EXCEEDED before anything is written. soft logs a warning and writes; none only tracks. A deployment that set quotas on v0.11 and relies on them not biting should raise them, or set enforcement to soft or none, before upgrading.

Backups

Other storage changes

Recall and ranking

Recall no longer ranks what earlier recalls returned higher. v0.11 raised the importance of every memory a recall returned and scored the access count; both are query-independent, so the results of one query crowded out more relevant memories in every later one. In v0.12 a recall records access_count and last_accessed_at but changes neither importance nor the score. The score's recency term counts from the memory's created_at. POST /v1/memory/feedback (upvote) still raises importance.

smart_score values move with this. The score is w_vec * relevance + w_imp * importance + w_rec * recency; the weights sum to:

Route Weight sum
Default and importance-weighted 0.88
Multi-hop queries 0.73
Temporal-inference queries 0.98

v0.11 gave the rest to the frequency term. A memory that no recall, feedback or session promotion had touched scores exactly as in v0.11; one that v0.11 recalls had returned scores up to 0.27 lower, and one last used long after it was stored up to 0.13 lower. An absolute cut on smart_score calibrated on v0.11 (DAKERA_ABSTAIN_MIN_SMART_SCORE, or a client filtering on the score) can drop such memories: re-check it against v0.12's scores. Relative order within a query is what changed on purpose.

Other behavior changes:

More recall and write behavior

Errors and API responses

Logs, metrics and alerts

Cluster upgrade

Environment variables

Names that now only warn

Set by v0.11 deployments but never read, or no longer read. On an upgraded deployment each is a warning; on a fresh install it refuses the boot. Remove them, or set what the right column says.

Name Set instead / why
DAKERA_L2_CACHE_PATH DAKERA_DISK_CACHE_DIR (the L2 read cache)
DAKERA_CACHE_REDIS_URL DAKERA_REDIS_URL
DAKERA_S3_ACCESS_KEY, DAKERA_S3_SECRET_KEY AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
DAKERA_LOG_LEVEL RUST_LOG
DAKERA_API_KEY the client-side key of the SDKs and the MCP server (Dashboard 0.4.0 has no key: operators sign in with their own); the server's is DAKERA_ROOT_API_KEY
DAKERA_STORAGE_BACKEND DAKERA_STORAGE
DAKERA_RELEVANCE_NORM DAKERA_NORMALIZE_RELEVANCE
DAKERA_INSTANCE_ID DAKERA_CLUSTER_NODE_ID
DAKERA_DEV_MODE, DAKERA_INFERENCE_ENABLED, DAKERA_DECAY_ENABLED nothing: set by v0.11's compose and README, never read

Removed variables

Read by a v0.11 release, not by v0.12 (a warning on an upgraded deployment):

Variable What happened
DAKERA_AUTO_INDEX_SELECTION Removed: the selector only ever built HNSW, which every namespace uses
DAKERA_RANKER, DAKERA_RANKER_INPUT, DAKERA_RANKER_MODEL The opt-in GBDT ranker is removed; the linear ranker used by default is the only one
DAKERA_TOKEN_BUDGET The CPU embedder runs one text per worker; DAKERA_MAX_SEQ_LENGTH bounds a text
DAKERA_VOCAB_BUNDLE_REPO Build-time only
DAKERA_QUERY_DECOMP, DAKERA_CE141_EVENT_TIME_FILTER, DAKERA_QUANT_LEVEL, DAKERA_CLASSIFIER_PATH Already removed during v0.11

Variables added in v0.12

All opt-in or deploy inputs; a deployment that sets none behaves as before apart from the changes above. DAKERA_HOT_TIER is not new, but its default changed to rocksdb (memory keeps the v0.11 tier).

Variable Purpose
DAKERA_ATTACHMENTS, DAKERA_ATTACHMENT_MAX_BYTES, DAKERA_WHISPER_MODEL, DAKERA_WHISPER_MODEL_PATH Attachments and speech to text (Multimodal memory)
DAKERA_VISION, DAKERA_VISION_MODEL, DAKERA_VISION_MODEL_PATH Image indexing and the visual recall lane
DAKERA_RECORDS, DAKERA_RECORD_MAX_VECTORS, DAKERA_RECORD_MAX_BYTES Records: one vector plus extra representations
DAKERA_SCORING_STRATEGY late-interaction reranks with MaxSim
DAKERA_MAX_SEQ_LENGTH Truncation length of the text models (bge-m3 default 2048)
DAKERA_FULLTEXT_LANGUAGE, DAKERA_FULLTEXT_CJK_BIGRAMS, DAKERA_QUERY_LANG Multilingual full-text analysis and query routing
DAKERA_RABITQ_BITS Bits per dimension (1-8) of the RaBitQ search mode, selected with DAKERA_SEARCH_MODE=rabitq (an existing variable that gained the rabitq value)
DAKERA_ALLOW_MODEL_CHANGE Acknowledge that the store was embedded by another model
DAKERA_MODEL_PATH_SKIP_VERIFY Skip pin verification for a deliberately different local model export
DAKERA_RERANK_MAX_CANDIDATES Cap how many candidates the cross-encoder scores
DAKERA_EXTRACTOR_PROVIDER, _MODEL, _BASE_URL, _TIMEOUT_SECS, _ALLOW_PRIVATE_URLS Server-default entity extraction provider
DAKERA_RATE_LIMIT_ENABLED, DAKERA_RATE_LIMIT_RPS, DAKERA_RATE_LIMIT_BURST Canonical names of the server-wide rate limit (RATE_LIMIT_RPS and RATE_LIMIT_BURST stay as deprecated aliases)
DAKERA_CLUSTER_SECRET, DAKERA_CLUSTER_OUTBOX_DIR, DAKERA_CLUSTER_TOMBSTONE_RETENTION_SECS (default 604800), DAKERA_CLUSTER_MAX_CLOCK_DRIFT_MS (default 60000) Cluster authentication and replication bounds
DAKERA_ELECTION_LEASE_MS (default 15000), DAKERA_ELECTION_AUTO_ELECT Cluster failure-detection scale and election
DAKERA_GRAPH_PATH Knowledge graph file (:memory: for in-memory)
DAKERA_ROCKSDB_SYNC (default true) Fsync RocksDB hot-tier writes
DAKERA_S3_TIMEOUT_SECS, DAKERA_S3_IO_TIMEOUT_SECS S3 deadlines
DAKERA_CONFIG_LENIENT Start although the environment has invalid values or unknown names

The cluster timings all follow DAKERA_ELECTION_LEASE_MS, accepted between 1000 and 600000 (a value outside is invalid like any other); scheduled reconciliation runs every 5 minutes. RATE_LIMIT_RPS and RATE_LIMIT_BURST still work as deprecated aliases, with a warning. Several tuning variables that appeared during v0.12 development (cluster timings other than the lease, DAKERA_RECALL_TRACK_ACCESS, DAKERA_EMBED_* threading knobs and others) were removed before release. A deployment that ran a v0.12 pre-release and still sets one starts with a warning if it holds v0.11 data; a deployment that a pre-release installed refuses to start naming the variable (remove it, or start once with DAKERA_CONFIG_LENIENT=1).

Names that do nothing where they are set

A variable set where it has no effect is logged and listed in config_warnings as env_ignored, never refused: DAKERA_MODEL other than bge-large together with DAKERA_TIERED=1 (the tiered embedding engine always embeds with bge-large; tiered storage is DAKERA_TIERED_STORAGE), tiered-storage settings without DAKERA_TIERED_STORAGE=true, DAKERA_NODE_ID different from DAKERA_CLUSTER_NODE_ID, and DAKERA_GRPC_PORT with gRPC off.

Booleans

A fresh v0.12 install reads 1/true/yes/on and 0/false/no/off (trimmed, any case) as written on every boolean variable. v0.11.108 read some spellings the other way (DAKERA_GRPC_ENABLED=yes disabled gRPC, DAKERA_WAL=off left the log on, DAKERA_AUTH_ENABLED=no left authentication on). An upgraded deployment keeps v0.11.108's reading for this release; each such value is listed in config_warnings (env_reading) with the true or false spelling that both versions read alike.

One value refuses on every deployment: DAKERA_WAL_SYNC=periodic:0, a write-ahead log that never fsyncs. Set everywrite, periodic:N with N greater than 0, or manual.

Docker model store and air-gapped installs

The default image ships bge-large and the reranker, already converted, and needs no network (--network none works), writes nothing at start, and works with a read-only root filesystem. Anything else is downloaded on first use into the model cache (HF_HOME, /app/models in the images).

The full reference is in Model store and air-gapped installs.

Models, media and inference

Notes for upgrading

See also What's new in v0.12.0, Configuration, Deployment and Security.

Stay sharp on agent memory
Benchmark results, SDK releases, and production patterns. Under 500 words per issue.
✓ You're in. First issue lands soon — watch for Dakera in your inbox.