NewRelease 0.12.3 is out: Engine v0.12.3, Dashboard 0.5.2, Helm 0.12.3, the four SDKs, CLI 0.8.2 and MCP 0.12.3Release 0.12.3 is outSee what's new →

Architecture

This page describes how a Dakera server is built: the request path, storage backends and caches, vector indexes, the knowledge graph, background jobs, durability, cluster mode and encryption. It is for operators sizing and running a deployment and for developers who want to know what happens behind the API.

Dakera is a single Rust binary. It serves a REST API (port 3000) and a gRPC API (port 50051) over the same engine, runs its embedding, reranking and extraction models locally with ONNX Runtime, keeps memories tidy with background jobs (decay and AutoPilot), and can run as a replicated cluster with gossip membership and leader election.

New in v0.12.0: the REST port binds before the models load (/health/live answers at once, /health/ready when the embedding model is loaded); everything on local disk derives from one data root, DAKERA_STORAGE_PATH; ANN indexes are saved and reloaded; every CPU model runs on one memory-mapped copy; opt-in lanes add attachments, speech to text, a visual lane for document pages and records; cluster replication is versioned and merges instead of overwriting; and dakera downgrade converts data back to v0.11.108. See What's new in v0.12.

Retrieval pipeline

Workflow of a recall in four lanes. Your agent sends POST /v1/memory/recall; the query is classified (keyword, semantic, hybrid, temporal or multi-hop) and embedded on-device with ONNX; BM25 search and vector search (HNSW or exact scan) run; results are fused (min-max or RRF) and scored by importance and recency; a cross-encoder reranks the top candidates; filters apply and the top_k memories return with scores and a rerank report.Workflow of a recall in four lanes. Your agent sends POST /v1/memory/recall; the query is classified (keyword, semantic, hybrid, temporal or multi-hop) and embedded on-device with ONNX; BM25 search and vector search (HNSW or exact scan) run; results are fused (min-max or RRF) and scored by importance and recency; a cross-encoder reranks the top candidates; filters apply and the top_k memories return with scores and a rerank report.
A recall from query to ranked memories; each step is described under Retrieval pipeline.Explore interactive ↗Full size ↗Full size ↗

System overview

Architecture diagram. Your agent calls one Dakera process over HTTP or gRPC. Inside it, the REST and gRPC APIs (ports 3000 and 50051) pass requests through middleware (auth, rate limits, audit) to the recall engine (classify, fuse, rerank). The engine uses ONNX Runtime on-device to embed and rerank, searches the HNSW and BM25 indexes, follows the knowledge graph for multi-hop questions, and reads and writes storage (write-ahead log, memory, disk or S3). Background jobs (decay and AutoPilot) also write to storage.Architecture diagram. Your agent calls one Dakera process over HTTP or gRPC. Inside it, the REST and gRPC APIs (ports 3000 and 50051) pass requests through middleware (auth, rate limits, audit) to the recall engine (classify, fuse, rerank). The engine uses ONNX Runtime on-device to embed and rerank, searches the HNSW and BM25 indexes, follows the knowledge graph for multi-hop questions, and reads and writes storage (write-ahead log, memory, disk or S3). Background jobs (decay and AutoPilot) also write to storage.
One process: no external vector store, embedding service, graph database or session store.Explore interactive ↗Full size ↗Full size ↗

What each layer contains

Dakera — System Architecture
API Layer
REST API
HTTP · :3000
gRPC API
Protobuf · :50051
SSE Events
EventBus
Prometheus
/metrics
Middleware — Auth · Rate Limiting · Security Headers · Audit · Validation · Tracing
Engine Layer
HNSW + exact scan
ANN above 1,000 vectors · opt-in RaBitQ
BM25
Full-text
Hybrid fusion + rerank
Relevance, importance, recency
Knowledge Graph
Memory graph · BFS
AutoPilot
Dedup + consolidation
Storage Layer
Caches
In-process · Redis · disk
Write-ahead log
Local backends
Store
Memory · segmented · filesystem · S3
Tiered (opt-in)
RocksDB hot → disk → S3
Inference Layer
Embeddings
bge-large · bge-m3 · colbert-small · MiniLM · E5
Reranker
bge-reranker-v2-m3
Entity Extraction
GLiNER + rule-based
Speech & vision (opt-in)
whisper-tiny.en · colmodernvbert
ONNX Runtime — the embedding model loads at startup, the reranker in the background, optional models on first use  ·  on-device, zero external API calls

Memory lifecycle

A memory is created with an importance, loses importance while it goes unused, and is deleted only by an explicit forget or when its TTL expires. Decay alone never deletes anything.

State diagram of a memory: Stored (importance 0.0 to 1.0), then Active while agents recall it, Decaying when it is not recalled (half-life 168 hours by default), and Dormant at the importance floor (0.01 by default). A recall returns a decaying or dormant memory to Active and restarts the decay clock. Consolidation (AutoPilot, deduplication or manual) merges an active memory into another, and an explicit forget or TTL expiry deletes a memory at any stage; decay alone never deletes.State diagram of a memory: Stored (importance 0.0 to 1.0), then Active while agents recall it, Decaying when it is not recalled (half-life 168 hours by default), and Dormant at the importance floor (0.01 by default). A recall returns a decaying or dormant memory to Active and restarts the decay clock. Consolidation (AutoPilot, deduplication or manual) merges an active memory into another, and an explicit forget or TTL expiry deletes a memory at any stage; decay alone never deletes.
Store, active, decaying, dormant. A recall restarts the decay clock; only a forget or a TTL deletes.Full size ↗Full size ↗
StageWhereWhat happens
StoreWritten to the store, then the cachesCreated with its importance (default 0.5), embedded, indexed and linked into the knowledge graph; entities are extracted when the namespace has extraction on.
Active—Recalled. A recall records the access (access_count, last_accessed_at), which restarts decay, but does not raise importance; an upvote does.
Decaying—Not accessed. The hourly decay pass lowers importance along the configured curve.
Dormant—Importance has reached the floor (DAKERA_DECAY_MIN_IMPORTANCE, default 0.01). The memory is kept and can still be recalled.
Forgotten—Deleted by POST /v1/memory/forget, a batch forget, or the expiry of its TTL.

Storage and caching

One storage backend holds the data, chosen with DAKERA_STORAGE:

BackendWhat it is
memory (default)In-process store. The write-ahead log makes it survive a crash or restart.
segmentedLog-structured store with background compaction.
filesystemFiles under the data root (DAKERA_STORAGE_PATH).
s3S3 or MinIO. Persists every write before acknowledging it, so it needs no write-ahead log.

Tiered storage (DAKERA_TIERED_STORAGE=true, with DAKERA_STORAGE=s3) puts a hot tier and a local-disk warm tier in front of S3 and moves records between them by how recently they were accessed. Since v0.12 the hot tier is a durable RocksDB store (DAKERA_HOT_TIER=memory keeps the old in-memory one); its writes are fsynced and survive S3 outages.

Caches sit in front of the store:

Reads check the in-process cache, then Redis, then the disk cache, then the store, filling the faster tiers on the way back. Writes go to the store first and then update the caches. A vector past its TTL counts as a miss in every cache.

Vector indexes

The engine chooses the index for each namespace; it is not selected per namespace or per request. A namespace at or below DAKERA_ANN_THRESHOLD (default 1000 vectors) is searched by an exact scan; above it the engine builds an HNSW graph.

IndexUsed whenNotes
Flat (exact scan)Vectors at or below DAKERA_ANN_THRESHOLD; DAKERA_ANN_THRESHOLD=0 disables ANNExact nearest neighbors, no approximation
HNSWAbove the thresholdNode vectors are held in RAM as f16 and returned candidates are re-scored against the stored f32 vectors, so answered scores are exact. Saved to disk and reloaded at start instead of rebuilt; writes, TTL sweeps, decay and re-embedding update the cached index in place. Width: DAKERA_HNSW_EF_SEARCH (default 100)
HNSW + RaBitQ (opt-in)DAKERA_SEARCH_MODE=rabitqQuantized codes for a faster graph walk, then an exact float re-rank. A latency mode: the codes sit next to the float vectors (no memory saving) and are rebuilt after a restart

IVF and SPFresh code is kept in the engine for snapshot decoding and benchmarks but is not routed: nothing selects it, and benchmarks found no size from 10,000 to 50,000 vectors where either beats HNSW. GET /v1/namespaces/{ns} and the admin index endpoints report the live index (flat or hnsw), the vectors indexed and the build time; POST /admin/indexes/rebuild rebuilds it. All indexes use SIMD-accelerated distance functions (cosine, Euclidean, dot product); an omitted distance_metric resolves to the namespace's metric. Opt-in late interaction (colbert-small) adds a fixed-dimensional-encoding first stage and a MaxSim rerank; see Search and ranking.

Knowledge graph

Dakera keeps a graph of memories next to the vector store. Edges are created when a memory is stored (by similarity and, when the namespace extracts entities, by shared entity), or explicitly through the API. There are five edge types. The graph is durable by default and lives under the data root (DAKERA_GRAPH_PATH sets another location; :memory: is the explicit in-memory choice), and edges are scoped by namespace, so one agent's links are never read or deleted by another:

Edge typeMeaning
related_toCosine similarity of at least 0.85
shares_entityTwo memories carry the same entity tag
precedesA related memory was created before the other
linked_byAn explicit link made with POST /v1/memories/{id}/links
supersedesA newer, very similar memory that shares an entity replaces an older one; recall demotes the older

The graph serves multi-hop and entity-centric retrieval and the cross-agent network. Query it with the dk knowledge commands or the knowledge graph routes.

Example memory graph with the five edge types. A user profile memory is related_to a similar project plan memory and shares_entity with a tool choice memory (both tagged maya). The project plan precedes a newer preference memory. The tool choice is linked_by an explicit link to a skill note. The newer preference supersedes an older, very similar preference, which recall demotes.Example memory graph with the five edge types. A user profile memory is related_to a similar project plan memory and shares_entity with a tool choice memory (both tagged maya). The project plan precedes a newer preference memory. The tool choice is linked_by an explicit link to a skill note. The newer preference supersedes an older, very similar preference, which recall demotes.
An example: six memories and the five edge types that connect them.Full size ↗Full size ↗

Event bus & SSE

Dakera includes a built-in event bus that streams real-time notifications over Server-Sent Events (SSE). Subscribe to memory lifecycle events — stored, recalled, decayed, forgotten, consolidated, importance_updated and session events. The stream carries every agent's events and needs a global admin key. Useful for building dashboards, audit UIs, and agent coordination pipelines.

# Subscribe to memory events (all agents)
curl -N -H "Authorization: Bearer $DAKERA_ROOT_API_KEY" \
  http://localhost:3000/v1/events/stream

AutoPilot

AutoPilot is a background task, on by default (DAKERA_AUTOPILOT_ENABLED=false turns it off), with two jobs:

Decay is a separate hourly pass (DAKERA_DECAY_INTERVAL_SECS, default 3600) that follows the configured curve from where the importance started; it no longer compounds. The density-based (DBSCAN) consolidation runs on request, through POST /v1/agents/{id}/consolidate. Trigger an AutoPilot cycle with POST /admin/autopilot/trigger. Passes share one lock (across replicas through Redis); in a cluster, AutoPilot and decay run on the elected leader only.

Retrieval pipeline

A recall goes through eight steps:

POST /v1/memory/recall
1
Query classification
Rule-based classifier (embedding-based with DAKERA_ML_CLASSIFIER=true): keyword, semantic, hybrid, temporal or multi-hop; the request's routing overrides it
2
Embedding
ONNX model embeds query on-device
3
Vector search
HNSW retrieval above DAKERA_ANN_THRESHOLD, exact scan below it (opt-in RaBitQ search mode; opt-in late interaction shortlists with a fixed-dimensional encoding and reranks with MaxSim)
4
BM25 search
Full-text keyword match with per-namespace index
5
Fusion
Merge vector and BM25 results: min-max normalization by default, Reciprocal Rank Fusion with "fusion": "rrf"
6
Temporal scoring
smart_score = relevance, importance and recency (recency counts from creation); access count is not a score term
7
Cross-encoder reranking
Cross-encoder (default bge-reranker-v2-m3) scores the top candidates; rerank_candidates / DAKERA_RERANK_MAX_CANDIDATES cap how many, and rerank_report says whether it ran
8
Filter & return
Apply the request's filters (tags, memory type, importance, session, time window), cut to top_k and return
← memories with scores, plus rerank_report; at most two recalls per core run at once (min. four), the rest queue and get 503 + Retry-After after 20 s

gRPC API

The gRPC API on port 50051 runs on the same engine as REST but covers raw vectors only:

FeatureREST APIgRPC API
Best forWeb clients, quick integration, debuggingHigh-volume raw-vector upserts and queries
ScopeFull API: memories, sessions, text, search, adminSeven RPCs over raw vectors (Health, GetNamespace, DeleteNamespace, Upsert, Query, DeleteVectors, WarmCache); no memory API
Type safetyJSON request and response bodiesStrongly typed via proto definitions
Browser supportNativeNeeds a gRPC-Web proxy
# gRPC is on by default; these are the defaults
DAKERA_GRPC_ENABLED=true
DAKERA_GRPC_PORT=50051

# The gRPC port has no TLS of its own; terminate TLS or mTLS at a proxy

v0.12: every RPC except Health needs an API key in the call metadata (x-api-key or authorization: Bearer); calls are rate limited, time-bounded and audited like REST, and requests are validated like REST (top_k must be 1–10,000).

Backup & WAL

The flat storage backends are protected by Dakera's own write-ahead log under {data root}/wal (snapshots in {data root}/wal-snapshots), synced according to DAKERA_WAL_SYNC (everywrite, periodic:N or manual). Write and fsync errors are returned to the caller, concurrent writes share one fdatasync (group commit), and on restart the log is replayed. A checkpoint rotates and truncates the active segment, so deleted content stays in the log for at most one checkpoint interval and in at most 10 snapshots. The RocksDB hot tier of tiered storage fsyncs every write itself (DAKERA_ROCKSDB_SYNC, default true). A replay that dropped entries is reported as wal_replay under degraded in GET /health.

Stored formats are versioned: vector records, index snapshots, RocksDB, the log, the disk and Redis caches and imports carry or check a format version, and a record written by a newer build is refused or skipped and counted, never misread. The server locks its data root (.dakera.lock), so a second server on the same root refuses to start.

For point-in-time backups, use the admin API (see Deployment → Backup & Restore). Backups carry memories, sidecars, attachments and graph edges, can be encrypted (AES-256-GCM per file) and compressed (zstd), and a stored backup schedule now actually runs. A restore with "overwrite": true gives the exact point in time of the backup.

Import & export

An agent's memories move in and out of Dakera through GET /v1/export and POST /v1/import, in jsonl (default), csv, mem0 or zep format. Export carries plaintext content and TTLs; import is a multipart upload whose content is decoded to at most the 100,000-byte content limit and refused per item past it, and import progress is available at GET /v1/import/{job_id}/status. Both need read / write on the agent's namespace.

# Export an agent's memories
curl "http://localhost:3000/v1/export?agent_id=my-agent&format=jsonl" \
  -H "Authorization: Bearer $DAKERA_ROOT_API_KEY" \
  -o memories.jsonl

# Import memories from a file (multipart upload)
curl -X POST "http://localhost:3000/v1/import?agent_id=my-agent&format=jsonl" \
  -H "Authorization: Bearer $DAKERA_ROOT_API_KEY" \
  -F file=@memories.jsonl

Compaction and index maintenance

The segmented backend and the RocksDB stores compact in the background. Two admin endpoints exist for maintenance windows: POST /admin/namespaces/{namespace}/optimize rebuilds the namespace's ANN index and runs storage compaction where the backend supports it (only the segmented backend does; compaction is null otherwise), and POST /ops/compact runs the storage backend's compaction or answers 501.

curl -X POST http://localhost:3000/admin/namespaces/my-ns/optimize \
  -H "Authorization: Bearer $DAKERA_ROOT_API_KEY"
# {"success":true,"job_id":...,"message":...,"index":{"namespace":"my-ns","index_type":"hnsw","rebuilt":true,...},"compaction":null}
When to run it: after bulk deletions or large-scale AutoPilot deduplication runs. Normal workloads do not need it.

Distributed architecture

A Dakera cluster is a set of nodes that each hold a full copy of the data. Any node accepts writes: a write is committed locally, then broadcast to every peer and acknowledged to the client whatever the peers answered; a peer that does not acknowledge within the sync timeout gets the change later from a durable outbox. It is an availability-first (AP) design: both sides of a partition keep accepting writes, and on heal the later write of each id wins, deletes included.

ComponentMechanismPurpose
MembershipSWIM-style gossip over UDP, HMAC-authenticated with DAKERA_CLUSTER_SECRETNodes discover each other and detect failure: alive, suspect, dead.
Leader electionLease-based, with terms and one vote per term; the quorum is a majority of the whole membershipThe leader runs background jobs that rewrite data (decay, auto-pilot, consolidation, re-embedding) and is the reconciliation reference. It gates no write. Every cluster timing derives from DAKERA_ELECTION_LEASE_MS.
VersioningA hybrid logical clock stamp on every replicated record; tombstones for deletes, kept DAKERA_CLUSTER_TOMBSTONE_RETENTION_SECS (default 7 days)Last writer wins by version. A change further ahead of the receiver's clock than DAKERA_CLUSTER_MAX_CLOCK_DRIFT_MS is refused and retried.
ReplicationSynchronous broadcast to every peer plus a durable outbox (DAKERA_CLUSTER_OUTBOX_DIR)An acknowledged write reaches a peer that was down once it returns.
ReconciliationEvery 5 minutes, merging both ways over every namespace, API keys, quotas and namespace configsConvergence after partitions and restarts; it reads nothing from an unchanged cluster. Knowledge-graph edges are not versioned and converge to the leader's copy.
Node-to-node traffic/internal/* routes require DAKERA_CLUSTER_SECRET (16 characters or more, identical on every node)Peers are authenticated; a node not in cluster mode answers 404 there.

There is no sharding or consistent-hash ring: capacity is that of one node, and a cluster adds availability, not capacity. Each S3-backed node needs its own bucket. See High Availability for cluster setup, failure modes, and operational procedures.

Encryption at rest

All memory content can be encrypted with AES-256-GCM authenticated encryption. Set DAKERA_ENCRYPTION_KEY to enable. Passphrases are stretched with PBKDF2-HMAC-SHA256 (100,000 iterations). Every sealed value is $enc$v2$<key_id>$…, with the namespace, record id and field as authenticated data, so a sealed value copied elsewhere does not decrypt. Data keys live in a replicated keyring wrapped under masters derived from DAKERA_ENCRYPTION_KEY; POST /admin/encryption/rotate-key (optionally with namespace) adds a key and re-seals in the background, and GET /admin/encryption/status reports progress. Old keys are kept, so reads never break mid-rotation and older backups restore. See Security.

Filter expressions

The vector routes take a metadata filter: query, batch query, count, bulk update, bulk delete, aggregate and unified query. Memory recall uses its own fields instead (tags, memory_type, min_importance, session_id, since / until).

{
  "filter": {
    "$and": [
      { "importance": { "$gt": 0.7 } },
      { "tags": { "$in": ["decision", "blocker"] } }
    ]
  }
}

Supported operators: $eq, $ne, $gt, $lt, $gte, $lte, $in, $nin, $and, $or, $exists, $regex, $glob, $contains, $icontains, $startsWith, $endsWith, $arrayContains, $arrayContainsAll, $arrayContainsAny.

Understand it — now run it

Run the single binary or the Docker image, then connect your agents through an SDK or the MCP server.

Get Started → Deployment Guide →

Related: Core Concepts · High Availability · Why Dakera is written in Rust · How agent memory works

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.