RFC/draft: opt-in libSQL/Turso storage backend for concurrent multi-writer memory#262
Draft
Donach wants to merge 8 commits into
Draft
RFC/draft: opt-in libSQL/Turso storage backend for concurrent multi-writer memory#262Donach wants to merge 8 commits into
Donach wants to merge 8 commits into
Conversation
Donach
force-pushed
the
turso-backend-minimal
branch
2 times, most recently
from
July 9, 2026 20:26
6616d9b to
a779688
Compare
Donach
force-pushed
the
turso-backend-minimal
branch
from
July 10, 2026 07:51
a779688 to
edef56a
Compare
Adds a flake.nix so Nix/NixOS users can build, run, and install icm directly from the repo: nix run github:rtk-ai/icm nix profile install github:rtk-ai/icm Design notes: - cargoLock.lockFile vendors deps straight from the committed Cargo.lock, so there is no cargoHash to bump on dependency updates — the flake is zero-maintenance for day-to-day development. - OPENSSL_NO_VENDOR + ORT_STRATEGY=system link the nixpkgs openssl and onnxruntime (fastembed/ort cannot download prebuilts in the Nix sandbox). - Default features (rusqlite backend, embeddings, tui) — the binary is identical in behaviour to a stock cargo build. - devShells.default provides the Rust toolchain + native deps for hacking. - Linux systems only for now (x86_64/aarch64); darwin likely works but is unverified, trivially addable. No impact on cargo users; .gitignore gains the nix 'result' symlink.
Upstream (rtk-ai/icm) has been on an active backend-pluggability sprint since our fork point (v0.10.50 / commit 804dac2). 76 commits landed, the biggest being: - feat(store/postgres): opt-in PostgreSQL backend (rtk-ai#302) - feat(store/opensearch): opt-in OpenSearch backend (rtk-ai#304) - refactor(store): additive backends + runtime selection in one binary (rtk-ai#301) (ICM_DB_BACKEND=sqlite|postgres|opensearch; all backends in one binary) - feat(init): icm forget + icm list in icm_block (rtk-ai#252) - fix(init): Codex PostToolUse opt-in (rtk-ai#293) - feat(serve): persistent local HTTP API (rtk-ai#291) - feat(store/facts): structured facts table (rtk-ai#273) - feat(store/code_areas): code_areas table (rtk-ai#196) The fork's changes (3 commits, 4d2853f..9a67d54) were: 1. Minimal-diff libSQL overlay (dbcompat.rs — rusqlite-shaped sync facade) 2. Make turso an opt-in Cargo feature (backend-rusqlite vs turso, mutually exclusive) 3. flake.nix + onnxruntime LD_LIBRARY_PATH wrapProgram fix Integration strategy (forward-porting, not rebasing commits): - Upstream restructured to additive backends + runtime dispatch via Store enum in backend.rs (BackendKind::Sqlite/Postgres/OpenSearch + dispatch! macro). - We add Turso as a 4th additive backend: `--features turso` compiles in TursoStore; ICM_DB_BACKEND=turso (or TURSO_DATABASE_URL set) selects it. - turso_store.rs = upstream store.rs adapted to use `dbcompat as rusqlite` + turso-specific open_connection/apply_pragmas (URL env var logic). - turso_schema.rs = upstream schema.rs adapted similarly. - dbcompat.rs: updated libsql dep to include `remote` feature (needed for Builder::new_remote in libsql 0.9; fork had `core,replication` only). - MappedRows<'_, F> → MappedRows<T> (dbcompat's type has no lifetime param). - flake.nix: bumped version string to 0.10.57-turso; builds with --no-default-features --features turso (additive, not conflicting). Both builds verified: - cargo check -p icm-cli (default, backend-sqlite): OK - cargo check --no-default-features --features "turso,embeddings,tui" -p icm-cli: OK Conflicts resolved: - crates/icm-store/src/lib.rs: keep upstream's full module structure, add turso/dbcompat/turso_schema/turso_store under #[cfg(feature = "turso")] - crates/icm-store/Cargo.toml: keep upstream's feature/dep structure, add turso feature + libsql/once_cell optional deps - Cargo.lock: take upstream's, regenerate (cargo update) for new libsql+remote - Cargo.toml: add libsql/libsql-ffi/once_cell workspace deps with remote feature Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the 7393-line verbatim copy (turso_store.rs) and 847-line schema copy (turso_schema.rs) with a shared-source approach: - Add sqlite_backend.rs: wraps store.rs/schema.rs with real rusqlite - Add turso_backend.rs: wraps the same files with the dbcompat shim - Both use `#[path]` to load store.rs and schema.rs from src/ directly - lib.rs restructured to declare file-based backend modules - backend.rs updated to import from the new module paths - store.rs: collect_rows() now uses impl Iterator instead of MappedRows<'_, F> so it compiles under both rusqlite (MappedRows<'stmt, F>) and dbcompat (IntoIter<Result<T>>); add `use super::sql;` for qualified sql:: paths - schema.rs: import params from super::sql directly; use params! not sql::params! - Delete turso_store.rs (7398 lines) and turso_schema.rs (847 lines) All three cargo check variants pass clean: cargo check -p icm-cli (default/sqlite) cargo check --no-default-features --features turso,tui (turso-only) cargo check --features backend-sqlite,turso,tui (both compiled in) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VhDidsi8NWn6udUMLr6Sv
Three correctness fixes surfaced by the shared-source compile: 1. schema.rs test: `super::store` resolves to the schema module's own parent (sqlite_backend::schema), not to the backend wrapper. Need `super::super::store` to reach the sibling store module. 2. store.rs perf test: `perf_fts_search_100` hard-coded a 1 s ceiling that only holds for native rusqlite. The dbcompat/libsql path wraps an async runtime in a sync facade, which is 2–3× slower in-process. Raise the ceiling to 5 s so the test remains a sanity-check rather than a rusqlite-only benchmark that always fails on the turso path. 3. Cargo.toml: libsql `tls` feature is required even for plain http:// URLs — the libsql crate gates rustls on it, and Builder::new_remote panics without it. Add `"tls"` to the feature list. Also regenerate Cargo.lock for the libsql tls dependency. Smoke-tested all turso paths against a local sqld: memoir create/list, memory store/recall, facts set/get, feedback record/list, transcript start-session/record/show/search All 194 turso-backend unit tests pass (0 failures after the ceiling fix). All 190 sqlite-backend tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove flake.nix and flake.lock (NixOS packaging, fork-only) - Update docs/turso-backend.md: - Remove flake.nix mention - Correct "mutually exclusive" → additive feature (with link caveat) - Replace "byte-identical via alias" with the #[path] shared-source description - Update test counts (190/194, all passing) - Update Known limitations (perf ceiling adjusted, not a failure) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the base flake (see the flake-packaging PR this stacks on) with a second package output built with --no-default-features --features turso,embeddings,tui. A separate output rather than one combined binary because libsql-ffi and libsqlite3-sys both bundle the sqlite3 amalgamation and their symbols collide at link time (documented in docs/turso-backend.md). Also wraps the binary with the libstdc++/onnxruntime LD_LIBRARY_PATH fix: system-ORT onnxruntime needs a newer GLIBCXX than the Rust toolchain's libstdc++ that otherwise wins the RPATH ordering.
…estructure Adversarial review of the shared-source restructure found the turso backend never actually went remote: dbcompat had open_remote()/open_replica(), but the shared store.rs called Connection::open(path) — always a local libsql file. TURSO_DATABASE_URL was silently ignored; a dead server "worked" by writing to a local file (split-brain waiting to happen). - provider modules (sqlite_backend.rs / turso_backend.rs) now own open_backend() and apply_pragmas(); the turso variant restores the original env dispatch (URL → remote, +ICM_TURSO_REPLICA → embedded replica, none → local file) and remote-safe PRAGMAs. Shared store.rs diff: two lines (open + pragma call). - BackendKind::from_env(): with nothing set, default to the first *compiled* local-capable backend (sqlite, else turso) instead of a hard not-compiled error in turso-only builds. Test updated to match. - flake.nix: parameterised mkIcm builder — the previous overrideAttrs approach silently produced a default-features (rusqlite!) binary named icm-turso, because buildRustPackage consumes buildFeatures at call time. Verified: dead URL now fails loudly (connection refused); store/recall + memoir against a throwaway local sqld with the row confirmed SERVER-side via raw /v2/pipeline HTTP; icm-store tests 190/190 (sqlite) + 194/194 (turso); ICM_DB_BACKEND=sqlite on a turso-only build errors as intended.
…10s) The libsql HTTP client has no default request timeout, so a stalled remote connection (packet loss, wedged server) hangs a synchronous icm CLI call — and any editor/agent hook that shells out to it — forever. Route every DB round-trip in the dbcompat facade through a deadline: ICM_DB_TIMEOUT seconds, default 10, 0 disables. Local-file operations are far under the limit and unaffected. Timeouts surface as a distinct error naming the variable instead of an indefinite hang.
Donach
force-pushed
the
turso-backend-minimal
branch
from
July 10, 2026 12:22
7c076e3 to
6f74f1f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary — opt-in libSQL/Turso storage backend (4th additive backend)
ICM keeps memory in a single local SQLite file, so it can't be shared or written
concurrently from more than one machine/process. This adds an opt-in Turso/libSQL
backend that runs the existing
icm-storeSQL over the asynclibsqlclient, sothe store can live in a libSQL/Turso database — local file, a remote
sqld/Tursoserver, or an embedded replica. A remote server is the multi-writer path (serialises
writes from every ICM process).
The default build is completely unchanged —
rusqlite, exactly as today.Update: now using shared-source (
#[path]) — upstream-mergeableThe original draft duplicated ~7,000 lines (
turso_store.rs+turso_schema.rs).This revision eliminates the copies entirely via Rust's
#[path]attribute:Net change vs the original draft: −8,022 lines (302 inserted, 8,324 deleted).
store.rsandschema.rsare only touched by two mechanical changes:use rusqlite::→use super::sql::(provider alias)collect_rows<T>signature:MappedRows<'_, F>→impl Iterator<Item = sql::Result<T>>This makes the backend a genuine minimal diff against upstream and easy to rebase going forward.
What's new since the original draft
dispatch!macroICM_DB_BACKENDis unset butTURSO_DATABASE_URLorLIBSQL_URLis non-empty, turso is selected automatically (gated behind#[cfg(feature = "turso")])dbcompat.rs: 540-line synchronous rusqlite-shaped facade over the async libsql clientFeature flag / env vars
Test results
All upstream tests pass on both backends. Smoke-tested against sqld 0.24.33:
memoir, memory, facts, feedback, transcript — all ✅.
Head branch
This PR's head has been updated to
Donach:rebase/turso-on-upstream-v0.10.57(the
turso-backend-minimalbranch on the Donach/icm fork is our productionbranch and cannot be force-pushed — it is tracked by NixOS infrastructure).
RFC — happy to reshape per your feedback before marking ready.