NewRelease 0.12.2 is out: Engine v0.12.2, Dashboard 0.5.1, Helm 0.12.2, the four SDKs, CLI 0.8.1 and MCP 0.12.2Release 0.12.2 is outSee what's new →

Upgrading to v0.12 (from v0.11.108, v0.12.0 or v0.12.1)

This guide is for operators moving a v0.11.108, v0.12.0 or v0.12.1 deployment to v0.12.2, the current v0.12 release. 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. A v0.12.1 deployment needs only the first section; a v0.12.0 deployment needs the first two.

An unchanged v0.11.108 deployment upgrades in place: stop v0.11.108, start v0.12.2 on the same data and the same environment. It starts, keeps its data and answers as before, except for the defects v0.12 fixes on purpose, which are listed below. One exception: a cluster node needs DAKERA_CLUSTER_SECRET from v0.12.1 (Cluster secret). Going back is supported for every deployment on a v0.11 embedding model, encrypted or not: stop v0.12, 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.

Upgrading from v0.12.1 to v0.12.2

Nothing to migrate by hand. Back up first (POST /admin/backups), then replace the image or binary (ghcr.io/dakera-ai/dakera:0.12.2). Every change is in the Changelog under v0.12.2; these are the ones to check before or right after the upgrade:

  1. Sessions now end automatically after 4 hours without activity (DAKERA_SESSION_IDLE_TIMEOUT_SECS, default 14400; 0 turns the server default off; at most 30 days). Sessions already open longer than that are closed, with their summaries, on the first passes after the upgrade. An agent that keeps a session open while idle sends POST /v1/sessions/{id}/touch, or starts it with idle_timeout_secs: 0. The timeout can also be changed at runtime with PUT /admin/config {"session_idle_timeout_secs": N}.
  2. Keys: an entry ending in * on a key created before v0.12.2 stays a literal name (it granted nothing before and still grants nothing) until the key's namespaces are saved again with PATCH /admin/keys/{key_id}; the dashboard's API Keys page offers "Re-save to activate patterns". Keys no longer need _dakera_sessions for their agents' sessions. Invalid entries (a**b, blank, _dakera_*) are now refused when a key is created or edited. GET /v1/auth/whoami shows how the server reads a key, including its inert_namespaces.
  3. Writes that are now refused with 400: a client-supplied dakera-curated tag or _dakera_* metadata key other than _dakera_content_date / _dakera_lang; caller ids of the form mem_s + 24 hex; memory-shaped records written through the raw vector routes of an agent namespace (use /v1/memory/*); agent ids longer than 241 bytes; content over DAKERA_MAX_MEMORY_CONTENT_BYTES (100000 bytes) on update as well as store (a memory already above it may still be updated to content no larger than it is now).
  4. Listings: GET /v1/agents/{id}/memories and /wake-up leave sentence sub-memories out unless include_derived=true.
  5. Storage: the RocksDB hot tier writes binary records (DAKERA_ROCKSDB_RECORD_FORMAT, default binary; json restores the previous writer). Rolling back to v0.12.0 or v0.12.1 needs nothing; before starting v0.11.108 on this data, run dakera downgrade, which rewrites them as JSON first.
  6. On the first start the derivation reconciler heals existing sentence sub-memories in the background (stale and duplicate children deleted, missing ones derived, graph edges rebuilt where they were built from placeholder vectors); GET /admin/derivations/status shows its progress.

Clients: new request and response fields are additive and optional. What a client can notice is listed above: 400 refusals that name the field and the rule, 403 when a Read key ends a session, sessions ended by the server, and listings without sub-memories. The Dashboard 0.5.1 released with v0.12.2 uses its new routes and also works with v0.12.0 and v0.12.1.

Upgrading from v0.12.0 to v0.12.1

Stop v0.12.0 and start v0.12.1 on the same data and environment. Nothing is migrated by hand; the stored record, index and snapshot formats are those of v0.12.0. Upgrading from v0.12.0 straight to v0.12.2 is supported too: follow this section, then the one above. From v0.11.108, follow the rest of this page.

Before you start:

  1. Take a backup: POST /admin/backups.
  2. Cluster: every node needs the same DAKERA_CLUSTER_SECRET. If your v0.12.0 cluster runs without one, a v0.12.1 node refuses to start: set it and switch all nodes in one window (Cluster secret). A cluster that already has it upgrades node by node as usual. Single-node deployments are not affected.
  3. Dashboards and reports: memory totals stop counting sentence sub-memories and drop; session totals count ended sessions and rise (Statistics totals). Tell the owners of dashboards, alerts and usage reports built on these fields.

What happens on the first start (nothing to do):

Check: GET /health/ready answers 200, GET /v1/agents lists every agent, and a keyword search returns hits.

Going back to v0.12.0 is not part of the release checks: restore the backup from step 1 if you need to. Going back to v0.11.108 works from v0.12.1 with the same dakera downgrade.

Upgrade checklist (from v0.11.108)

  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 need DAKERA_CLUSTER_SECRET (v0.12.1): generate one secret, set it on every node, and upgrade the nodes in one window. A cluster node without it refuses to start, and nodes with and without it do not see each other (Cluster secret). 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. Memory and session counts change meaning (v0.12.1): memory totals stop counting sentence sub-memories and drop; session totals count ended sessions and rise. Warn the owners of dashboards, alerts and usage reports (Statistics totals).
  12. Check the v0.12.2 changes: sessions end after 4 hours idle by default, stricter write validation, key patterns and listings without sub-memories (Upgrading from v0.12.1 to v0.12.2).
  13. Stop v0.11.108 and start v0.12.2 on the same data and environment.
  14. Full-text indexes need no step on v0.12.1: indexes built by v0.11 are re-analysed in the background at startup. Only if you upgrade to v0.12.0 instead, rebuild them once with POST /admin/fulltext/reindex and {"rebuild": true} (details and commands).
  15. Watch /health: config_warnings, degraded, embed_migration, and embedding_model_change if you changed the model (and, with encryption, GET /admin/encryption/status).

Full-text indexes after the upgrade

No step on v0.12.1. v0.12.1 re-analyses the full-text indexes built by v0.11 by itself, in the background, at startup. The manual rebuild below is only for a deployment that runs v0.12.0.

Full-text indexes built by v0.11 may have no stemmer (a v0.11.108 deployment keeps the analyzer each index was created with). On them a keyword query such as release misses released, and keyword-routed recall can return nothing.

v0.12.1: automatic

At startup, every full-text index built with another analyzer than the configured one is re-analysed in the background, one namespace at a time, while it keeps serving: keyword search finds the stem variants meanwhile, writes made during it are kept, and a restart resumes. Each cluster node does its own (full-text indexes are node-local). An index holding documents that exist only in the index (POST /v1/namespaces/{ns}/fulltext/index), or agent memories that cannot be opened at that moment, keeps its analyzer instead of losing them; the server logs it. Watch the log for Full-text index re-analysed under the configured analyzer. There is no new setting.

v0.12.0: rebuild once by hand

On v0.12.0, rebuild the full-text indexes once after upgrading a deployment that holds data from v0.11.108. Until you do, keyword search and keyword-style recall can return nothing. Fresh installs do not need it. The rebuild re-analyses the indexes under v0.12's text analysis. 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. A vector-API namespace needs its own call with {"namespace": "<name>", "rebuild": true}; documents it holds only through POST /v1/namespaces/{ns}/fulltext/index are dropped by a rebuild, so re-index them. The response lists each namespace it processed (see Multilingual search for the fields).

Docker Compose (v0.12.0)

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 (v0.12.0)

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

On v0.12.1 after the background re-analysis, or on v0.12.0 after the rebuild:

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
Full-text indexes (v0.12.1) Indexes built with another analyzer than the configured one, such as v0.11's indexes without stemming, are re-analysed in the background, one namespace at a time, while they keep serving (Full-text indexes). log line Full-text index re-analysed under the configured analyzer
Statistics (v0.12.1) Statistics, session and agent listings answer from counts the store maintains. The first call per namespace reads it once to build them; later calls answer from the maintained counts. The totals use one definition (Statistics totals). nothing to do
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.

Embedding model changes

v0.12.1 (no step): set DAKERA_MODEL to another model of the registry and restart. The server starts and re-embeds the store in the background, one agent namespace at a time (on the leader in a cluster). Until its turn, each namespace is answered, queries and writes alike, with the model its memories are in, so recall stays correct. At the same dimension a namespace is re-embedded in place while it serves; at a new dimension it is rewritten through a staging copy, and its writes answer 503 with Retry-After for the few seconds that takes. Progress: /health embedding_model_change (from, to, namespaces_switched, namespaces_left) and GET /v1/capabilities "reembed_pending": true. A restart resumes where the change stopped.

Startup is still refused where the change cannot run in the background: under DAKERA_TIERED=1, with late-interaction scoring, with the visual lane, or when the recorded model is not in this build's registry. There, and for every model change on v0.12.0, start once with DAKERA_ALLOW_MODEL_CHANGE=1 (it only acknowledges the change), then re-embed with POST /admin/namespaces/migrate-dimensions and {"target_dimension": <dim>, "reembed_same_dimension": true} (see Switching an existing store).

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.2 --check-config

From v0.12.1 it also reports a cluster node without DAKERA_CLUSTER_SECRET, or with one shorter than 16 characters. 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

Statistics totals (v0.12.1)

The count endpoints disagreed with each other: one agent that stored one 3-sentence memory was reported as 5, 4 or 5 "memories" depending on the endpoint, because each counted different records: the memory, the sentence sub-memories Dakera derives from it for keyword search (one per sentence, created in the background for a memory of two sentences or more), and the namespace's bookkeeping records. v0.12.1 uses one definition everywhere:

Field names and types do not change; the values of these fields do:

Field Up to v0.12.0 From v0.12.1
GET /v1/agents memory_count every record of the agent's namespace the agent's memories (the old figure is the new vector_count)
GET /v1/agents/{id}/stats total_memories memories + sub-memories memories (sub-memories in the new sub_memories)
GET /admin/memory-type-stats total every record of every agent namespace memories (new sub_memories, agent_namespaces)
GET /v1/agents/{id}/stats total_sessions, GET /v1/agents session_count open sessions only every session, ended or not (active_sessions stays open-only)
GET /v1/sessions?agent_id=… the agent's open sessions every session of the agent, newest first
GET /v1/kpis session_count_weekly open sessions sessions started in the last 7 days

New fields only: GET /v1/ops/stats total_memories, internal_vectors, internal_namespaces, agent_namespaces; GET /v1/analytics/overview total_memories, internal_vectors. Summed over agents, GET /v1/agents memory_count equals total_memories. GET /v1/agents also lists agents that have memories but no session (they were missing).

What to expect after the upgrade. Memory counts drop, by roughly the average number of sentences per memory for agents that store long, multi-sentence memories, and session counts rise, because ended sessions are counted. No data changes: storage, recall and namespace quotas (which count stored records, max_vectors) are unaffected.

What to do. Dashboards, alerts, usage or billing reports and any text that shows "N memories" from these fields will step on upgrade day: annotate the step, and re-base thresholds on the new figures. A consumer that needs the previous GET /v1/agents figure reads vector_count. Clients parse the responses unchanged (only fields were added).

Other error and response changes

Logs, metrics and alerts

Cluster upgrade

Cluster secret (required from v0.12.1)

What changes. Every node in cluster mode (DAKERA_CLUSTER_MODE=true) needs DAKERA_CLUSTER_SECRET: at least 16 characters, the same value on every node. It authenticates every node-to-node request (/internal/*, sent in a header) and every gossip packet (HMAC-SHA256 with a send time and a nonce, so replays are dropped). It authenticates; it does not encrypt, so keep node-to-node traffic on a private network or behind TLS. Single-node deployments (cluster mode off) are not affected.

Why it is required now. v0.11 had no secret: anyone who reached a node's API port could call the node-to-node routes (POST /internal/sync/key/add minted a SuperAdmin API key), and anyone who reached the gossip port could join the cluster. v0.12.0 required the secret on fresh installs only and let an upgraded node start without one, still open. From v0.12.1 a cluster node without the secret refuses to start, fresh install or upgraded.

What a node without it does. It exits with code 78 before it serves anything, logging:

cluster mode needs DAKERA_CLUSTER_SECRET (at least 16 characters, the same on every node): it authenticates every node-to-node request (/internal/*, gossip), …

dakera --check-config reports the same (DAKERA_CLUSTER_SECRET missing, or shorter than 16 characters), so run it first.

Generate and set it.

openssl rand -hex 32        # 64 characters; any value of 16 or more works

Store it like any other credential (a Kubernetes Secret, your secret manager, an .env file outside version control) and pass the same value to every node as DAKERA_CLUSTER_SECRET. The repository's docker-compose.yml has a development default (dev-cluster-secret-change-me): set your own.

Upgrade procedure. Nodes with and without the secret cannot talk: a node with it answers a node without it with 401 and drops its gossip, and a node without it cannot read authenticated gossip. So the cluster must not run half-and-half for long.

Starting point What to do
v0.12.0 with DAKERA_CLUSTER_SECRET already set Nothing new: upgrade node by node as usual, keeping the same secret.
v0.12.0 without it, or v0.11.108 (which had none)

Recommended: one window. Stop every node, set the same secret on each, start them all on v0.12.1 or later. The cluster is unavailable for the restart only; no data is lost (each node keeps its own copy).

Rolling, no downtime. Upgrade one node at a time, each started on v0.12.1 or later with the secret. Until the last node is done the cluster is two groups, upgraded nodes and the others, that do not see each other: each group elects its own leader and replicates only within itself, and every node keeps serving reads and writes from its own data, so a client may not see a write made on a node of the other group. A change a v0.12 node could not deliver because of the secret stays in its durable outbox and is retried. Once every node runs v0.12.1 or later with the secret they form one membership again, elect one leader, and the reconciliation with the leader (at startup, then every 5 minutes) merges every node's data by version. Keep this window to minutes; while the groups are split, leader-only jobs (decay, consolidation, a background model change) run in each group.

Check it worked. On every node, GET /admin/cluster/status lists every other node as a healthy member and the same leader, and the log has no refused a node-to-node request without the cluster secret warnings (a node logs that when a peer sends the wrong secret or none). For replication, write on one node and read on another.

Rotating the secret needs the same window: a node accepts one secret, so nodes on the old and new values are split until all have the new one.

Rolling back to v0.11.108: v0.11 ignores the variable; roll the whole cluster back together (Rolling back: clusters).

Node id and mixed v0.11 / v0.12 periods

Storage, backups and timings

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 and later have 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. From v0.12.1 needed only where the background model change cannot run (DAKERA_TIERED=1, late-interaction scoring, the visual lane, a recorded model this build does not know); see Embedding model changes
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_CLUSTER_SECRET is required on every cluster node from v0.12.1 (Cluster secret)
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, 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.