Memory overview
Why Kumiho is the canonical store, the NoneMemory runtime binding, and the memory backend choices.
Memory in Revka is split across two layers. At the Rust runtime level there is a Memory trait whose only wired-in implementation is NoneMemory — a no-op that stores nothing. All durable, cross-session memory is delegated to Kumiho, a graph-native cognitive memory backend reached over the Model Context Protocol (MCP). This page explains that split: the trait and its fields, why the runtime binding is a no-op, how the [memory] config section behaves, and the classification and decay rules that decide what surfaces in context.
Read this page first if you’re trying to understand where your memories actually live, or why setting a local backend doesn’t persist anything. To install the sidecar and wire an agent into Kumiho, continue to Kumiho setup and Kumiho graph memory.
Why Kumiho is the canonical store
Section titled “Why Kumiho is the canonical store”A stateless LLM call forgets everything the moment it returns. Revka closes that gap not with a local database but with Kumiho: a Neo4j-backed knowledge graph hosted on the Kumiho control plane (cloud) or a self-hosted Community Edition. Kumiho MCP is injected as a sidecar into every non-internal agent, exposing kumiho_memory_* tools (kumiho_memory_engage, kumiho_memory_reflect, and more) that read and write the graph from inside the agent’s tool loop.
This is a deliberate architectural choice: cross-session continuity, graph-structured recall over typed provenance edges, and a shared substrate multiple agents can read and write all live in one canonical place. The Rust runtime stays stateless. For the agent-side usage pattern (engage before responding, reflect after), see Kumiho graph memory.
The Memory trait
Section titled “The Memory trait”Memory is an async Rust trait defining the interface any memory backend must implement. It is an internal extension point — not directly user-facing — and exists so a future custom backend can slot in without touching the rest of the system. Its methods are:
store, recall, get, list, forget, count, health_check, store_procedural, recall_namespaced, export, store_with_metadata, purge_namespace, and purge_session.
Default implementations are provided for the optional methods (recall_namespaced, export, store_with_metadata, purge_namespace, purge_session), so a backend only needs to override what it supports natively. The store family accepts:
| Field | Type | Meaning |
|---|---|---|
key | &str | Unique identifier for the entry. |
content | &str | The text content to store. |
category | MemoryCategory | Core, Daily, Conversation, or Custom(String). |
session_id | Option<&str> | Scopes the entry to a session. |
namespace | Option<&str> | Isolates entries between agents/contexts (defaults to "default"). |
importance | Option<f64> | 0.0–1.0 for prioritized retrieval. |
superseded_by | Option<String> | Links a deprecated entry to its replacement. |
since / until | Option<&str> | RFC 3339 time bounds for recall and export. |
NoneMemory — the runtime binding
Section titled “NoneMemory — the runtime binding”NoneMemory is the only concrete Memory implementation wired into the Revka runtime. Every operation succeeds silently: stores are discarded, recalls return empty. The trait still exists so future backends can be added, but today the runtime resolves to a no-op and leaves persistence entirely to Kumiho MCP at the agent level.
Two backend names resolve to NoneMemory:
[memory]backend = "kumiho" # recommended — persistence handled by Kumiho MCP[memory]backend = "none" # explicit no-opAny other backend name (for example sqlite, qdrant, lucid, or markdown) is rejected at startup with an error directing you to Kumiho.
The [memory] config section
Section titled “The [memory] config section”[memory] lives in ~/.revka/config.toml and controls the backend selection plus in-session behavior: auto-save, hygiene/retention, response caching, namespace isolation, and the memory policy. A representative block:
[memory]backend = "kumiho" # or "none"auto_save = true # auto-save user messages to memoryhygiene_enabled = true # archive/purge old entriesarchive_after_days = 7 # days before archivingpurge_after_days = 30 # days before purging archived entriesconversation_retention_days = 30 # days to keep conversation rowsmin_relevance_score = 0.4 # entries below threshold dropped from contextdefault_namespace = "default"
# Response cache (opt-in)response_cache_enabled = false # default: offresponse_cache_ttl_minutes = 60 # TTL per entryresponse_cache_max_entries = 5000response_cache_hot_entries = 256 # in-memory LRU tier size
# Snapshot export (for backup)snapshot_enabled = falsesnapshot_on_hygiene = falseauto_hydrate = true
# Audit trailaudit_enabled = falseaudit_retention_days = 30
[memory.policy]max_entries_per_namespace = 0 # 0 = unlimitedmax_entries_per_category = 0read_only_namespaces = []# retention_days_by_category = { core = 365, daily = 30 }| Key | Type | Default | Meaning |
|---|---|---|---|
backend | string | "none" | Runtime backend; only "kumiho" or "none" are accepted. |
auto_save | bool | true | Auto-save user inputs after each turn (assistant outputs excluded). |
hygiene_enabled | bool | false | Archive then purge old entries. |
archive_after_days | int | 7 | Days before an entry is archived. |
purge_after_days | int | 30 | Days before an archived entry is purged. |
conversation_retention_days | int | 30 | Max conversation history age. |
min_relevance_score | float | 0.4 | Entries scoring below this are dropped from context. |
default_namespace | string | "default" | Namespace for entries that don’t specify one. |
response_cache_enabled | bool | false | Enable the LLM response cache. |
audit_enabled | bool | false | Enable the memory audit trail. |
audit_retention_days | int | 30 | Audit retention window. |
Backend choices
Section titled “Backend choices”Two surfaces describe “backends,” and they differ — it’s worth being precise about which one you’re looking at:
- Runtime backend (this section). What the Rust runtime accepts in
[memory].backend: only"kumiho"and"none", both resolving toNoneMemory. Anything else is rejected at startup. - Onboarding wizard choices. The memory step of
revka onboardpresents a friendlier menu — Kumiho cloud, Kumiho local CE (self-hosted Community Edition), or none. The cloud/CE distinction is configured in the[kumiho]block viamode = "cloud"ormode = "local_ce", not in[memory].
In short: the persistence decision is Kumiho cloud vs. Kumiho CE vs. nothing — [memory].backend only toggles whether the Kumiho path is active, and [kumiho].mode selects which Kumiho you talk to.
MemoryCategory
Section titled “MemoryCategory”MemoryCategory tags and filters every entry. It serializes as a lowercase string and has four variants:
| Category | Serialized | Meaning | Time decay |
|---|---|---|---|
Core | "core" | Evergreen long-term facts. | Exempt — always full score. |
Daily | "daily" | Session logs. | Decays exponentially. |
Conversation | "conversation" | In-context exchanges. | Decays exponentially. |
Custom(String) | any other string | User-defined labels. | Decays exponentially. |
Set the category when capturing (via the type on kumiho_memory_reflect) or filter by it on the CLI:
revka memory list --category corerevka memory list --category dailyTime-decay scoring
Section titled “Time-decay scoring”Recall scores age. Revka applies exponential decay to a memory’s relevance score based on how old the entry is, so stale conversation chatter naturally falls out of context while evergreen facts stay put. The formula:
score × 2^(-age_days / half_life_days)- The default half-life is 7 days (
DEFAULT_HALF_LIFE_DAYS = 7.0), configurable viahalf_life_days. A value of0or less falls back to the default. Corememories never decay — they always retain full score.- Entries with no score (
score: None) and entries with unparseable timestamps are left unchanged.
The practical implication: a Conversation or Daily memory’s relevance is halved after 7 days and at roughly 25% after 14 days, while Core memories are immune. Combined with min_relevance_score, decay is what decides whether an aging memory still makes it into context.
Memory entry fields
Section titled “Memory entry fields”A MemoryEntry is the structured record returned by recall, get, list, and export, and the shape you see in JSON API responses and revka memory get <key> output.
| Field | Type | Meaning |
|---|---|---|
id | String | UUID. |
key | String | Human-readable identifier. |
content | String | The stored text. |
category | MemoryCategory | Classification (see above). |
timestamp | String | RFC 3339 creation time. |
session_id | Option<String> | Scoping session, if any. |
score | Option<f64> | Relevance 0.0–1.0, subject to time decay. |
namespace | String | Defaults to "default". |
importance | Option<f64> | 0.0–1.0; higher surfaces first in recall. |
superseded_by | Option<String> | Link to a replacement entry. |
Auto-save and synthetic filtering
Section titled “Auto-save and synthetic filtering”When auto_save = true, user-stated conversation inputs are stored automatically after each turn. Before anything is persisted, Revka filters out synthetic/noise patterns so machine-generated artifacts don’t pollute the memory graph. The following are never persisted:
- Cron task messages —
[cron:...] - Heartbeat tasks —
[Heartbeat Task...] - Distilled memory chunks —
[distilled_...], or any content containingdistilled_index_sig: - Empty strings
- Assistant autosave keys —
assistant_resp,assistant_resp_*
The distilled-chunk filters keep summary artifacts (which look like memory entries but are synthetic) from being stored as real memories. The assistant_resp* filter is a legacy protection: old auto-saves of assistant output are treated as untrusted and are never re-injected into the system prompt, so a model can’t be fed its own past output as if it were a verified fact. Note that auto-save covers user inputs only — assistant outputs are excluded by design.