Skip to content

Memory System

Wayland Core can remember across sessions. When memory is on, the engine writes structured records during a run (event summaries, distilled facts, skill outcomes, user preferences) and reads them back the next time it boots in the same project. This stops a long-lived agent from re-asking the same questions and re-discovering the same project quirks.

Memory is on by default, so a fresh install gets a working memory store with no setup. Tune it, or turn it off, per project in .wayland-core.toml or globally in the global config:

[memory]
enabled = true # on by default; set false to disable
dream_cycle_throttle_secs = 1800 # minimum gap between consolidation cycles
decay_interval_secs = 3600 # how often the decay sweep runs

The --no-memory CLI flag is listed in the source but not yet wired. Use memory.enabled = false in wcore.toml to disable memory from the command line.


The v2 memory model (wcore-memory/src/v2_types.rs) organizes every record by partition (what kind of thing it is) and tier (how long it persists). The two axes are independent: a partition’s default tier is a hint, not a lock. Valid combinations are enforced at write time.

PartitionEnum variantContentsDefault tier
P1 WorkingPartition::WorkingLive conversation and tool context for the current sessionSession
P2 EpisodicPartition::EpisodicTimestamped events with summaries and extracted atomic factsProject
P3 SemanticPartition::SemanticDistilled facts as subject/predicate/object triplesProject
P4 ProceduralPartition::ProceduralReusable skill artifacts with Thompson sampling statsProject
P5 CorePartition::CoreUser model: preferences, style, persistent identity signals (system-only write)Global
TierStore locationLifetime
Session<config_dir>/memory/sessions/<id>.dbOne run. Each session gets its own file after rebind_session fires at init.
Project.wayland-core/memory/memory.dbLives in the repo. Shared across all sessions in that project.
Global<config_dir>/memory/memory.dbShared across all projects on the machine.

To redirect all tiers, export WCORE_MEMORY_DIR before launch.

Not every cell of the 5x3 matrix is open. The rules (v2_types.rs::valid_combinations):

  • P1 Working: Session only.
  • P2 Episodic: Session, Project, or Global.
  • P3 Semantic: Project or Global (never Session).
  • P4 Procedural: Project or Global (never Session).
  • P5 Core: Global only (system-only write).

Writes to an invalid combination return MemoryError::AccessDenied through the gate check.


The MemoryApi trait (wcore-memory/src/api.rs) is the only surface callers use directly. The production implementation is PartitionDispatcher.

MethodWhat it does
record_episode(ep, tok)Write a P2 Episodic entry. Returns EpisodeId (UUIDv7).
assert_fact(f, tok)Write a P3 Semantic subject/predicate/object triple. Returns FactId.
upsert_procedure(p, tok)Insert or update a P4 Procedural skill artifact. Returns ProcedureId.
list_procedures(tier, tok)Return the full P4 set at the given tier (no pagination; P4 is small by design).
update_user_model(key, val, tok)Write a key/value pair to the P5 Core user model. Caller must hold AccessToken::System.
search(q, tok)Vector similarity search across partitions. Returns Vec<Hit> with partition, tier, score, and preview.
get_episode(id, tok)Fetch one P2 entry by EpisodeId.
user_model(tok)Read all P5 Core entries.
dream_now()Trigger consolidation immediately (normally runs on the throttle schedule). Returns DreamReport.
compact(target_tokens)Compact memory to a token budget. Returns CompactReport.
record_skill_use(skill_name, succeeded, latency_ms)Update Thompson sampling stats for a P4 procedure row.
top_procedures(tier, k, min_uses, tok)Top-K P4 skills by Beta-mean score, filtered to rows with at least min_uses invocations.
kg_ingest_facts(transcript)Extract facts from a transcript and write them to the knowledge graph. Returns fact count.
clear_partition(partition, tier, tok)Bulk-delete all rows in one partition at one tier. Returns the row count deleted.
rebind_session(session_id)Swap the Session-tier store from the bootstrap boot.db onto the real per-session file.

Three of these are also available as LLM-invocable tools registered at bootstrap:

  • session_search (wcore-tools/src/session_search.rs): full-text search over past episodes.
  • assert_fact (wcore-tools/src/assert_fact.rs): write a semantic S/P/O triple during a run.
  • record_episode (wcore-tools/src/record_episode.rs): write an episodic entry during a run.

At the end of each session the engine scores the fact candidates that surfaced during the run and persists those above a confidence threshold to long-term memory. This is on by default as of the 2026-06-04 smart-default decision (auto_memorize.rs:69).

The AutoMemorize runner receives a SessionDigest (session id, turn count, and a list of FactCandidate triples each with a confidence in [0.0, 1.0]). It filters by min_confidence (default 0.5), requires at least min_facts_to_persist (default 1) to survive, then calls the persist closure with the filtered slice. If neither threshold is met, no facts leave the session.

Auto-Memorize uses an opt-out model: absent any configuration, it is on. Two mechanisms disable it.

Environment kill switch (session-scoped):

Terminal window
WAYLAND_AUTO_MEMORIZE=off wayland-core "..."

Persistent opt-out (write to the consent file):

Terminal window
echo "off" > "$WAYLAND_CONFIG_DIR/auto-memorize.consent"
# or equivalently, using the default config dir:
echo "off" > ~/.config/wayland-core/auto-memorize.consent

The consent file path resolves through wcore_config::config::wayland_config_dir(), so setting WAYLAND_HOME redirects it to $WAYLAND_HOME/auto-memorize.consent for hermetic sandboxing.

Decision order (auto_memorize.rs::consent_granted):

  1. If WAYLAND_AUTO_MEMORIZE=off (case-insensitive), return false. This overrides the file.
  2. If the consent file exists and its contents are one of off, opt-out, optout, false, no, or disable, return false.
  3. Otherwise (absent file, unreadable file, any other content including a legacy opt-in file), return true.

A background tokio task ticks ConsolidationEngine::decay() on the interval set by decay_interval_secs (default 3600 seconds, i.e. one hour). The first tick is skipped at boot so freshly-opened memory has time to settle before the first sweep fires (memory.rs::spawn_decay_scheduler). Transient errors from individual decay ticks are logged as warnings; the scheduler keeps running.


The Embedder trait (wcore-memory/src/embed/mod.rs) requires every backend to return an L2-normalized vector of constant length dim() for each input. The backend is selected per-project via memory.embedder.backend in wcore.toml.

BackendTOML valueDescription
Hashed (default)"hashed"Deterministic 384-dim hashed-token bag. No API key, no model load. Good for offline development and CI.
OpenAI"open_ai"OpenAI embeddings API. Reads the API key from the env var named in api_key_env.
Voyage"voyage"Voyage AI embeddings API. Reads the API key from the env var named in api_key_env.
Local BGE"local_bge"Local bge-small-en-v1.5 model via candle/ggml. Requires the local-embedder Cargo feature.

Configuration in wcore.toml:

[memory]
enabled = true
decay_interval_secs = 3600
dream_cycle_throttle_secs = 1800
[memory.embedder]
backend = "open_ai" # hashed | open_ai | voyage | local_bge
api_key_env = "OPENAI_API_KEY" # env var name; unused for hashed and local_bge
model = "text-embedding-3-small" # optional; falls back to per-backend default

The api_key_env field takes the name of an environment variable, not the key itself. The engine reads the named variable at startup. For hashed and local_bge the field is ignored.


The --memory-show flag prints the procedures and user model for a session in plain text. It works whether memory is currently enabled or not:

Terminal window
wayland-core --memory-show <SESSION_ID> --project-dir ~/project

When memory is off or the session has no data, all counts print as zero. Exits 0 in all cases so scripts can probe state safely.

Inside an active session the TUI /memory slash command opens the diagnostics surface (the same screen accessible via the /doctor//cost//memory triptych), which lists stored entries and offers a delete affordance.

You can also open any tier’s SQLite file directly with sqlite3 for ad-hoc queries:

Terminal window
sqlite3 .wayland-core/memory/memory.db ".tables"

Terminal window
rm .wayland-core/memory/memory.db # project tier
rm -rf "$(dirname "$(wayland-core --config-path)")/memory" # global tier and user model

Both are non-recoverable. To clear a single partition without deleting the file, use clear_partition via the API or the /memory TUI surface.


This is the engine-level store the CLI reads and writes. The desktop app surfaces its own Memory and Wiki views on top of the same model, so what the CLI records in a project is the same memory the app can show.