Skip to content

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.

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.

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:

FieldTypeMeaning
key&strUnique identifier for the entry.
content&strThe text content to store.
categoryMemoryCategoryCore, Daily, Conversation, or Custom(String).
session_idOption<&str>Scopes the entry to a session.
namespaceOption<&str>Isolates entries between agents/contexts (defaults to "default").
importanceOption<f64>0.01.0 for prioritized retrieval.
superseded_byOption<String>Links a deprecated entry to its replacement.
since / untilOption<&str>RFC 3339 time bounds for recall and export.

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-op

Any other backend name (for example sqlite, qdrant, lucid, or markdown) is rejected at startup with an error directing you to Kumiho.

[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 memory
hygiene_enabled = true # archive/purge old entries
archive_after_days = 7 # days before archiving
purge_after_days = 30 # days before purging archived entries
conversation_retention_days = 30 # days to keep conversation rows
min_relevance_score = 0.4 # entries below threshold dropped from context
default_namespace = "default"
# Response cache (opt-in)
response_cache_enabled = false # default: off
response_cache_ttl_minutes = 60 # TTL per entry
response_cache_max_entries = 5000
response_cache_hot_entries = 256 # in-memory LRU tier size
# Snapshot export (for backup)
snapshot_enabled = false
snapshot_on_hygiene = false
auto_hydrate = true
# Audit trail
audit_enabled = false
audit_retention_days = 30
[memory.policy]
max_entries_per_namespace = 0 # 0 = unlimited
max_entries_per_category = 0
read_only_namespaces = []
# retention_days_by_category = { core = 365, daily = 30 }
KeyTypeDefaultMeaning
backendstring"none"Runtime backend; only "kumiho" or "none" are accepted.
auto_savebooltrueAuto-save user inputs after each turn (assistant outputs excluded).
hygiene_enabledboolfalseArchive then purge old entries.
archive_after_daysint7Days before an entry is archived.
purge_after_daysint30Days before an archived entry is purged.
conversation_retention_daysint30Max conversation history age.
min_relevance_scorefloat0.4Entries scoring below this are dropped from context.
default_namespacestring"default"Namespace for entries that don’t specify one.
response_cache_enabledboolfalseEnable the LLM response cache.
audit_enabledboolfalseEnable the memory audit trail.
audit_retention_daysint30Audit retention window.

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 to NoneMemory. Anything else is rejected at startup.
  • Onboarding wizard choices. The memory step of revka onboard presents a friendlier menu — Kumiho cloud, Kumiho local CE (self-hosted Community Edition), or none. The cloud/CE distinction is configured in the [kumiho] block via mode = "cloud" or mode = "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 tags and filters every entry. It serializes as a lowercase string and has four variants:

CategorySerializedMeaningTime 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 stringUser-defined labels.Decays exponentially.

Set the category when capturing (via the type on kumiho_memory_reflect) or filter by it on the CLI:

Terminal window
revka memory list --category core
revka memory list --category daily

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 via half_life_days. A value of 0 or less falls back to the default.
  • Core memories 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.

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.

FieldTypeMeaning
idStringUUID.
keyStringHuman-readable identifier.
contentStringThe stored text.
categoryMemoryCategoryClassification (see above).
timestampStringRFC 3339 creation time.
session_idOption<String>Scoping session, if any.
scoreOption<f64>Relevance 0.01.0, subject to time decay.
namespaceStringDefaults to "default".
importanceOption<f64>0.01.0; higher surfaces first in recall.
superseded_byOption<String>Link to a replacement entry.

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 containing distilled_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.