From d43f9d468c321ffac3aee920d0783ec4417c2e9b Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Fri, 22 May 2026 10:13:05 -0600 Subject: [PATCH 1/3] fix: guard content indexing without model Return a SQLite error when content indexing reaches embedding computation before memory_set_model has initialized a custom, local, or remote engine. Add a regression test covering memory_add_text before model configuration so the call fails cleanly instead of dereferencing a null engine. --- sessions/session-2026-02-10.md | 70 ---------------------------------- src/sqlite-memory.c | 12 ++++++ src/sqlite-memory.h | 2 +- test/unittest.c | 16 ++++++++ 4 files changed, 29 insertions(+), 71 deletions(-) delete mode 100644 sessions/session-2026-02-10.md diff --git a/sessions/session-2026-02-10.md b/sessions/session-2026-02-10.md deleted file mode 100644 index b0f54bd..0000000 --- a/sessions/session-2026-02-10.md +++ /dev/null @@ -1,70 +0,0 @@ -# Session Summary - 2026-02-10 - -## Project: sqlite-memory - -SQLite extension for AI agent memory with semantic search, hybrid retrieval, and offline-first sync between agents. - ---- - -## Work Completed - -### 1. Documentation Updates - -Updated `README.md` and `API.md` to reflect changes to the `memory_search` virtual table: - -- **Renamed column**: `score` → `ranking` in all query examples and documentation -- **Documented columns**: `path`, `snippet`, `ranking` properly described -- **Preserved settings**: `min_score` setting name unchanged (configuration option, not column) - -### 2. GitHub Project Description - -Created project descriptions for GitHub: - -**Short (About field):** -> SQLite extension for AI agent memory with semantic search, hybrid retrieval, and offline-first sync between agents - -**Full description:** -> A SQLite extension that gives AI agents persistent, searchable memory. Features hybrid semantic search (vector similarity + FTS5), markdown-aware chunking, and local embedding via llama.cpp. Memory databases can be synced between agents using offline-first technology—each agent works independently and syncs when connected, making it ideal for distributed AI systems, edge deployments, and collaborative agent architectures. - ---- - -## Key Files Modified - -| File | Changes | -|------|---------| -| `README.md` | Updated `memory_search` examples to use `ranking` column | -| `API.md` | Updated column documentation and examples for `memory_search` | - ---- - -## memory_search Virtual Table Schema - -```sql -SELECT * FROM memory_search WHERE query = 'search text'; -``` - -| Column | Type | Description | -|--------|------|-------------| -| `query` | TEXT (HIDDEN) | Search query (required in WHERE clause) | -| `hash` | INTEGER | Content hash identifier | -| `path` | TEXT | Source file path or generated UUID | -| `context` | TEXT | Context label (NULL if not set) | -| `snippet` | TEXT | Text snippet from matching chunk | -| `ranking` | REAL | Combined similarity score (0.0 - 1.0) | - ---- - -## Previous Session Context - -This session continued from earlier work that included: -- Implementing memory deletion, timestamps, and statistics features -- Building Makefile with conditional llama.cpp/remote engine support -- Adding support for both local (llama.cpp) and remote (vector.space) embedding -- Renaming `max_items` to `max_results` throughout codebase -- Creating comprehensive README.md and API.md documentation - ---- - -## Version - -sqlite-memory v0.5.1 diff --git a/src/sqlite-memory.c b/src/sqlite-memory.c index 4356404..60421e0 100644 --- a/src/sqlite-memory.c +++ b/src/sqlite-memory.c @@ -1575,12 +1575,20 @@ static int dbmem_process_callback (const char *text, size_t len, size_t offset, if (!cache_hit) { // compute embedding if (ctx->is_custom) { + if (!ctx->custom_engine || !ctx->custom_provider.compute) { + dbmem_context_set_error(ctx, "memory_set_model must be called before adding content"); + return SQLITE_ERROR; + } rc = dbmem_context_custom_compute(ctx, text, (int)len, &result); if (rc != 0) return rc; } else if (ctx->is_local) { #ifndef DBMEM_OMIT_LOCAL_ENGINE + if (!ctx->l_engine) { + dbmem_context_set_error(ctx, "memory_set_model must be called before adding content"); + return SQLITE_ERROR; + } rc = dbmem_local_compute_embedding(ctx->l_engine, text, (int)len, &result); if (rc != 0) return rc; #else @@ -1591,6 +1599,10 @@ static int dbmem_process_callback (const char *text, size_t len, size_t offset, else { #ifndef DBMEM_OMIT_REMOTE_ENGINE + if (!ctx->r_engine) { + dbmem_context_set_error(ctx, "memory_set_model must be called before adding content"); + return SQLITE_ERROR; + } rc = dbmem_remote_compute_embedding(ctx->r_engine, text, (int)len, &result); if (rc != 0) return rc; #else diff --git a/src/sqlite-memory.h b/src/sqlite-memory.h index 0ee29bc..7a057a3 100644 --- a/src/sqlite-memory.h +++ b/src/sqlite-memory.h @@ -26,7 +26,7 @@ extern "C" { #endif -#define SQLITE_DBMEMORY_VERSION "1.2.1" +#define SQLITE_DBMEMORY_VERSION "1.2.2" // public API SQLITE_DBMEMORY_API int sqlite3_memory_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); diff --git a/test/unittest.c b/test/unittest.c index 6f17745..28889de 100644 --- a/test/unittest.c +++ b/test/unittest.c @@ -2730,6 +2730,21 @@ TEST(sqlite_custom_provider_set_model) { sqlite3_close(db); } +TEST(sqlite_memory_add_text_requires_model) { + sqlite3 *db = open_test_db(); + ASSERT(db != NULL); + + sqlite3_stmt *stmt = NULL; + int rc = sqlite3_prepare_v2(db, "SELECT memory_add_text('Hello world, this is a test.');", -1, &stmt, NULL); + ASSERT_EQ(rc, SQLITE_OK); + rc = sqlite3_step(stmt); + ASSERT_EQ(rc, SQLITE_ERROR); + ASSERT(strstr(sqlite3_errmsg(db), "memory_set_model must be called before adding content") != NULL); + sqlite3_finalize(stmt); + + sqlite3_close(db); +} + TEST(sqlite_custom_provider_add_text) { sqlite3 *db = open_test_db(); ASSERT(db != NULL); @@ -3231,6 +3246,7 @@ int main(int argc, char *argv[]) { printf("\nCustom provider tests:\n"); RUN_TEST(sqlite_custom_provider_register); RUN_TEST(sqlite_custom_provider_set_model); + RUN_TEST(sqlite_memory_add_text_requires_model); RUN_TEST(sqlite_custom_provider_add_text); RUN_TEST(sqlite_custom_provider_skips_whitespace_only_text); RUN_TEST(sqlite_custom_provider_persists_truncated_metadata); From c5f7d5ec9d1e3d8f9f7b9daa484c3de1ce966e36 Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Fri, 22 May 2026 10:31:51 -0600 Subject: [PATCH 2/3] fix: clarify empty memory search error Report that memory_search has no indexed content instead of exposing the missing embedding dimension detail. This keeps the vector initialization failure actionable for users who search before adding content. --- src/sqlite-memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlite-memory.c b/src/sqlite-memory.c index 60421e0..2594edf 100644 --- a/src/sqlite-memory.c +++ b/src/sqlite-memory.c @@ -772,7 +772,7 @@ bool dbmem_context_load_vector (dbmem_context *ctx) { } if (ctx->dimension == 0) { - dbmem_context_set_error(ctx, "SQLite-vector extension cannot be loaded because embedding dimension is not specified"); + dbmem_context_set_error(ctx, "memory_search cannot run because no content has been indexed yet. Add content with memory_add_text(), memory_add_file(), or memory_add_directory() before searching."); return false; } From 873cf0466debea54524f3755c1a62db75c11c44d Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Fri, 22 May 2026 11:31:44 -0600 Subject: [PATCH 3/3] fix: normalize missing model error without remote engine Check for missing provider or model before dispatching to any embedding backend. This keeps memory_add_text reporting that memory_set_model is required even when the remote engine is omitted at compile time. --- src/sqlite-memory.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sqlite-memory.c b/src/sqlite-memory.c index 2594edf..823eba0 100644 --- a/src/sqlite-memory.c +++ b/src/sqlite-memory.c @@ -1573,6 +1573,11 @@ static int dbmem_process_callback (const char *text, size_t len, size_t offset, } if (!cache_hit) { + if (!ctx->provider || !ctx->model) { + dbmem_context_set_error(ctx, "memory_set_model must be called before adding content"); + return SQLITE_ERROR; + } + // compute embedding if (ctx->is_custom) { if (!ctx->custom_engine || !ctx->custom_provider.compute) {