ADR-0009: Dolt Database Persistence Backend
Date: 2026-03-06 Status: Accepted — Phases 1–3 implemented (2026-03-07) Deciders: Kannaka Team Technical Story: Enhance kannaka-memory with versioned, queryable, and syncable persistence
Context and Problem Statement
Currently, kannaka-memory uses bincode snapshots for persistence of hypervector memories with wave dynamics (amplitude, frequency, phase, decay). While this provides fast serialization/deserialization, it has several limitations:
- No version history - Cannot track how memories evolve over time
- Limited queryability - Cannot perform SQL queries on memory metadata
- No branching - Cannot experiment with speculative memory states
- No collaboration - Cannot sync memories across instances
- No backup/restore - Binary snapshots are opaque and fragile
Decision Drivers
- Versioned memory evolution: Track how memories change over time
- Queryable metadata: SQL queries on amplitude, frequency, content, relationships
- Branching for speculation: Create memory branches for "what-if" thinking
- Synchronization: Share memory state across kannaka instances
- Backup and restore: Robust persistence with cloud backup capabilities
- Performance: Maintain reasonable read/write performance for memory operations
Considered Options
- PostgreSQL with custom versioning
- SQLite with manual snapshots
- Dolt (MySQL-compatible with Git-like versioning)
- Keep bincode snapshots (status quo)
Decision Outcome
Chosen option: Dolt database - provides MySQL compatibility with Git-like versioning, enabling all desired features while maintaining familiar SQL interface.
Positive Consequences
- ✅ Git-like versioning: Full history of memory evolution with branches and merges
- ✅ SQL queryability: Rich queries on memory metadata and relationships
- ✅ Branching capability: Speculative thinking with memory branches
- ✅ Synchronization: Push/pull memory state via DoltHub or Git remotes
- ✅ Cloud backup: Push memory database to DoltHub for redundancy
- ✅ Familiar interface: MySQL-compatible SQL interface
- ✅ Atomic commits: Consistent memory state snapshots
Negative Consequences
- ⚠️ Additional complexity: More complex than simple bincode files
- ⚠️ Dependency: Requires Dolt installation and SQL server
- ⚠️ Learning curve: Team needs to learn Dolt-specific operations
- ⚠️ Performance overhead: SQL operations may be slower than direct bincode access
Database Schema Design
Core Tables
-- Primary memory storage with wave dynamics
CREATE TABLE memories (
id VARCHAR(36) PRIMARY KEY,
content LONGTEXT NOT NULL,
amplitude DOUBLE NOT NULL,
frequency DOUBLE NOT NULL,
phase DOUBLE NOT NULL,
decay_rate DOUBLE NOT NULL,
created_at DATETIME NOT NULL,
layer_depth TINYINT UNSIGNED NOT NULL,
hallucinated BOOLEAN DEFAULT FALSE,
parents LONGTEXT, -- JSON array of parent memory IDs
vector_data LONGTEXT NOT NULL, -- Base64/JSON encoded hypervector
xi_signature LONGTEXT, -- Encoded signature vector
geometry LONGTEXT -- JSON serialized MemoryCoordinates
);
-- Skip-list connections between memories
CREATE TABLE skip_links (
source_id VARCHAR(36) NOT NULL,
target_id VARCHAR(36) NOT NULL,
weight DOUBLE NOT NULL,
link_type VARCHAR(32) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (source_id, target_id),
INDEX idx_target (target_id)
);
-- System metadata and configuration
CREATE TABLE metadata (
key_name VARCHAR(64) PRIMARY KEY,
value_text LONGTEXT
);
Design Decisions
- Binary data encoding: Use JSON/Base64 encoding for vectors (Dolt doesn't support BLOB)
- Wave dynamics as columns: Direct SQL access to amplitude, frequency, phase, decay_rate
- JSON for complex structures: Parents array and geometry stored as JSON text
- Composite primary key: Skip-links use (source_id, target_id) as natural key
Migration Plan
Phase 1: Database Setup (Current)
- Initialize Dolt database at
~/.kannaka/dolt-memory - Create schema with memories, skip_links, metadata tables
- Add initial metadata entries
- Commit schema as baseline
Phase 2: Data Migration ✅
tools/migrate-to-dolt.jsrewritten — no hardcoded paths; config via env vars / CLI flags- Server readiness polling (30 s timeout, 1 s tick) replaces fixed 2 s sleep
- Idempotent upserts (
ON DUPLICATE KEY UPDATE) — safe to re-run on existing data - Progress file (
migration-progress.json) for crash-resumable large migrations - Post-migration row-count verification before Dolt commit
datetimestored as"YYYY-MM-DD HH:MM:SS"— compatible with Phase 1 fix
Phase 3: Rust Integration ✅
doltfeature flag inCargo.tomlgatesmysqldependencyDoltMemoryStore— hybrid in-memory cache + Dolt write-through, implementsMemoryStoreDoltConfigstruct — all settings from env vars (DOLT_HOST,DOLT_PORT,DOLT_DB,DOLT_USER,DOLT_PASSWORD,DOLT_AUTO_COMMIT,DOLT_COMMIT_THRESHOLD) withfrom_env(),try_from_env(),default()constructors- Dirty-set tracking —
get_mut()marks IDs dirty;flush_dirty()/update(&id)sync to Dolt - Delete atomicity — Dolt delete attempted before cache eviction
- Backward compatible — bincode
persistence.rspath unaffected
Phase 4: Advanced Features ✅
- Memory branching —
create_branch,checkout,checkout_new_branch,delete_branch,list_branches,current_branch - Automatic versioned commits —
commit(message)with--authorheader; returnsOk(true/false)to distinguish committed vs nothing-to-commit; threshold auto-commit still fires fromsync_memory_to_dolt - DoltHub backup —
push(remote, branch)andpull(remote, branch)withNonedefaulting toDoltConfig.remote/default_branch; configured viaDOLT_REMOTE/DOLT_BRANCHenv vars - Memory diff —
diff(from_ref, to_ref)queriesdolt_diff_memoriessystem table; returnsVec<MemoryDiff>withDiffKind::{Added, Removed, Modified} - Merge —
merge_branch(branch)callsDOLT_MERGE, reloads cache, returns merge commit hash - Commit log —
log(limit)queriesdolt_log; returnsVec<CommitInfo>with hash, author, date, message - Speculation helpers —
speculate(branch)/collapse_speculation(branch, msg)/discard_speculation(branch)for high-level what-if workflows
Future Vision
Speculative Memory Branches
# Branch memory for a thought experiment
dolt branch speculation-climate-change
# Work with speculative memories
kannaka --branch speculation-climate-change think "What if CO2 doubled?"
# Merge back successful thoughts
dolt merge speculation-climate-change
# Discard failed speculation
dolt branch -D speculation-failed-experiment
Memory Collaboration
# Push memory state to shared repository
dolt push origin main
# Pull memories from another kannaka instance
dolt pull origin collaborative-research
# Share specific memory branches
dolt push origin memory-research-2026
Memory Analytics
-- Find memories losing amplitude (fading)
SELECT id, content, amplitude, decay_rate
FROM memories
WHERE amplitude < 0.5 AND decay_rate > 0.01;
-- Analyze memory frequency distributions
SELECT
FLOOR(frequency * 10) / 10 AS freq_band,
COUNT(*) as memory_count
FROM memories
GROUP BY freq_band
ORDER BY freq_band;
-- Find highly connected memories
SELECT m.id, m.content, COUNT(sl.source_id) as connection_count
FROM memories m
JOIN skip_links sl ON m.id = sl.target_id
GROUP BY m.id
ORDER BY connection_count DESC
LIMIT 10;
Implementation Notes
- Connection management:
mysql::Poolfor connection pooling - Datetime serialization:
parse_dolt_datetime/format_dolt_datetimehelpers useNaiveDateTime + and_utc()— avoids the%zrequirement ofDateTime::parse_from_str resonance_key: Stored asVec::new()on Dolt round-trips (full 10K-dim keys are not persisted inskip_linksrows)- Error handling:
DoltConfig::try_from_env()returnsNonewhen no Dolt vars are set, enabling graceful fallback to bincode - Configuration: All settings from env vars; see
DoltConfigdocs insrc/dolt.rs
Test Coverage
- 11 unit tests in
src/dolt.rs— always run, no live DB required:DoltConfigdefaults, env-var overrides, invalid port,auto_commitvariantstry_from_envNone / Some paths- Datetime round-trip, fractional seconds, invalid input, Phase 1 regression proof
- 9 integration tests in
tests/dolt_integration.rs— skip gracefully when no Dolt server is reachable:- Insert / get / search / delete round-trip
- Dirty-set tracking →
flush_dirtypersists mutations update(&id)single-memory flush- Delete atomicity verification
resonance_keyround-trip (must beVec::new())- UTC datetime preservation across Dolt
- Env-var tests use a
static Mutexto prevent parallel-test races
Links
- Dolt Documentation
- DoltHub - Cloud hosting for Dolt databases
- Kannaka Memory Architecture
- Migration Script