From e541ee2ef6ab20d136dd2a65050fbc8e6cbf5103 Mon Sep 17 00:00:00 2001 From: Brad McCormack Date: Sat, 1 Aug 2026 15:02:55 +1000 Subject: [PATCH 1/5] fix(mnemopi): align SQLite page size with OS page size Add the initial page-size detection and regression coverage for file-backed Mnemopi databases. --- packages/mnemopi/CHANGELOG.md | 3 ++ packages/mnemopi/src/db.ts | 25 +++++++++- packages/mnemopi/test/db-page-size.test.ts | 54 ++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 packages/mnemopi/test/db-page-size.test.ts diff --git a/packages/mnemopi/CHANGELOG.md b/packages/mnemopi/CHANGELOG.md index 3608b7e55..f2e6aa2a0 100644 --- a/packages/mnemopi/CHANGELOG.md +++ b/packages/mnemopi/CHANGELOG.md @@ -7,6 +7,9 @@ ### Fixed - Stripped `…` reasoning blocks from remote LLM output in `cleanOutput`, so reasoning-model responses no longer leak into consolidated memories or corrupt fact extraction (the reasoning wrapper previously survived parsing and every stored fact became reasoning prose). ([#7231](https://github.com/can1357/oh-my-pi/issues/7231)) +### Fixed + +- Fixed SQLite databases being created with a hardcoded 4 KB page size regardless of the OS page size. On systems with larger pages (e.g. Apple Silicon / Asahi Linux at 16 KB), every SQLite page read touches 4 OS pages, causing 4x I/O amplification. Mnemopi now detects the OS page size at startup via `getconf PAGE_SIZE` and sets `PRAGMA page_size` before any tables are created, eliminating the write amplification on non-4K-page systems. ## [17.2.2] - 2026-07-31 diff --git a/packages/mnemopi/src/db.ts b/packages/mnemopi/src/db.ts index 2ce63b94f..653f935fb 100644 --- a/packages/mnemopi/src/db.ts +++ b/packages/mnemopi/src/db.ts @@ -3,6 +3,24 @@ import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; import { dbPath } from "./config"; +let _osPageSize: number | undefined; + +function osPageSize(): number { + if (_osPageSize !== undefined) return _osPageSize; + try { + const proc = Bun.spawnSync(["getconf", "PAGE_SIZE"], { stdout: "pipe" }); + if (proc.exitCode === 0) { + const size = Number.parseInt(proc.stdout.toString().trim(), 10); + if (size >= 512 && size <= 65536 && (size & (size - 1)) === 0) { + _osPageSize = size; + return size; + } + } + } catch { /* fall through */ } + _osPageSize = 4096; + return 4096; +} + export type DatabasePath = string | ":memory:"; export interface OpenDatabaseOptions { @@ -33,11 +51,14 @@ export function openDatabase(path: DatabasePath = dbPath(), options: OpenDatabas if (options.loadExtension !== undefined) loadExtensions(db, options.loadExtension); return db; } - export function enablePragmas(db: Database, path?: DatabasePath): void { db.exec("PRAGMA foreign_keys=ON"); db.exec("PRAGMA busy_timeout=5000"); - if (path !== ":memory:") db.exec("PRAGMA journal_mode=WAL"); + if (path !== ":memory:") { + const pageSize = osPageSize(); + if (pageSize !== 4096) db.exec(`PRAGMA page_size=${pageSize}`); + db.exec("PRAGMA journal_mode=WAL"); + } } export function loadExtensions(db: Database, extensions: string | readonly string[]): void { diff --git a/packages/mnemopi/test/db-page-size.test.ts b/packages/mnemopi/test/db-page-size.test.ts new file mode 100644 index 000000000..b8d52b177 --- /dev/null +++ b/packages/mnemopi/test/db-page-size.test.ts @@ -0,0 +1,54 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { openDatabase, closeQuietly } from "../src/db"; + +function expectedOsPageSize(): number { + try { + const proc = Bun.spawnSync(["getconf", "PAGE_SIZE"], { stdout: "pipe" }); + if (proc.exitCode === 0) { + const size = Number.parseInt(proc.stdout.toString().trim(), 10); + if (size >= 512 && size <= 65536 && (size & (size - 1)) === 0) return size; + } + } catch { /* fall through */ } + return 4096; +} + +const roots: string[] = []; + +afterEach(() => { + for (;;) { + const root = roots.pop(); + if (root === undefined) break; + rmSync(root, { recursive: true, force: true }); + } +}); + +describe("db page size", () => { + it("creates a new database with page size matching the OS page size", () => { + const root = mkdtempSync(join(tmpdir(), "mnemopi-page-size-")); + roots.push(root); + const path = join(root, "test.db"); + + const db = openDatabase(path); + try { + const pageSize = db.query("PRAGMA page_size").get() as { page_size: number }; + expect(pageSize.page_size).toBe(expectedOsPageSize()); + } finally { + closeQuietly(db); + } + }); + + it("does not set page_size on in-memory databases", () => { + const db = openDatabase(":memory:"); + try { + const pageSize = db.query("PRAGMA page_size").get() as { page_size: number }; + // In-memory DBs keep the SQLite default (4096) since there is no + // persistent file whose I/O would benefit from OS page alignment. + expect(pageSize.page_size).toBe(4096); + } finally { + closeQuietly(db); + } + }); +}); From 68555bd677139a63a4db8e119f5cfc8b132c2b72 Mon Sep 17 00:00:00 2001 From: Brad McCormack Date: Sat, 1 Aug 2026 15:03:05 +1000 Subject: [PATCH 2/5] fix(mnemopi): make SQLite page-size alignment opt-in Keep SQLite default behavior unless a page size is explicitly configured, and route file-backed Mnemopi databases through the shared opener. --- packages/mnemopi/CHANGELOG.md | 3 +- packages/mnemopi/README.md | 1 + packages/mnemopi/src/core/cost-log.ts | 10 ++-- packages/mnemopi/src/core/query-cache.ts | 8 +-- packages/mnemopi/src/db.ts | 63 ++++++++++++++++----- packages/mnemopi/src/dr/recovery.ts | 4 +- packages/mnemopi/test/db-page-size.test.ts | 66 +++++++++++++--------- 7 files changed, 102 insertions(+), 53 deletions(-) diff --git a/packages/mnemopi/CHANGELOG.md b/packages/mnemopi/CHANGELOG.md index f2e6aa2a0..ef8eb6806 100644 --- a/packages/mnemopi/CHANGELOG.md +++ b/packages/mnemopi/CHANGELOG.md @@ -8,8 +8,9 @@ - Stripped `…` reasoning blocks from remote LLM output in `cleanOutput`, so reasoning-model responses no longer leak into consolidated memories or corrupt fact extraction (the reasoning wrapper previously survived parsing and every stored fact became reasoning prose). ([#7231](https://github.com/can1357/oh-my-pi/issues/7231)) ### Fixed +### Added -- Fixed SQLite databases being created with a hardcoded 4 KB page size regardless of the OS page size. On systems with larger pages (e.g. Apple Silicon / Asahi Linux at 16 KB), every SQLite page read touches 4 OS pages, causing 4x I/O amplification. Mnemopi now detects the OS page size at startup via `getconf PAGE_SIZE` and sets `PRAGMA page_size` before any tables are created, eliminating the write amplification on non-4K-page systems. +- Added opt-in SQLite page-size selection for file-backed databases. Set `MNEMOPI_DB_PAGE_SIZE` to a valid SQLite page size (a power of two from 512 to 65536) or `os`, or pass `pageSize` to `openDatabase`. The default remains SQLite's own page size because the virtual-memory page size does not establish storage-block behavior and larger WAL pages can increase sparse-write cost. Existing databases retain their page size. ## [17.2.2] - 2026-07-31 diff --git a/packages/mnemopi/README.md b/packages/mnemopi/README.md index 68a09c1cf..f99c2ec5c 100644 --- a/packages/mnemopi/README.md +++ b/packages/mnemopi/README.md @@ -83,6 +83,7 @@ In `per-project-tagged`, the wrapper is responsible for combining project-local Common environment fallbacks: - `MNEMOPI_DATA_DIR` / `MNEMOPI_DB_PATH`: default storage location. +- `MNEMOPI_DB_PAGE_SIZE`: optional SQLite page size for new file-backed databases; use a valid power of two from 512 to 65536 or `os` to request the detected system page size. Unset preserves SQLite's default. - `MNEMOPI_NO_EMBEDDINGS=1`: force FTS-only recall. - `MNEMOPI_EMBEDDING_MODEL`: defaults to `BAAI/bge-small-en-v1.5`. - `MNEMOPI_EMBEDDING_API_URL` and `MNEMOPI_EMBEDDING_API_KEY`: OpenAI-compatible embedding endpoint. diff --git a/packages/mnemopi/src/core/cost-log.ts b/packages/mnemopi/src/core/cost-log.ts index dea8ed533..f103550d1 100644 --- a/packages/mnemopi/src/core/cost-log.ts +++ b/packages/mnemopi/src/core/cost-log.ts @@ -1,8 +1,7 @@ -import { Database } from "bun:sqlite"; -import { mkdirSync } from "node:fs"; +import type { Database } from "bun:sqlite"; import { homedir } from "node:os"; -import { dirname, join } from "node:path"; - +import { join } from "node:path"; +import { openDatabase } from "../db"; export const DEFAULT_LOG_DIR = join(homedir(), ".mnemopi", "data"); export const DEFAULT_LOG_DB = join(DEFAULT_LOG_DIR, "cost_log.db"); @@ -22,8 +21,7 @@ type AggregateRow = { export function getConn(dbPath?: string): Database { const path = dbPath ?? DEFAULT_LOG_DB; - mkdirSync(dirname(path), { recursive: true }); - return new Database(path, { create: true, readwrite: true, strict: true }); + return openDatabase(path, { pragmas: false }); } export function initCostLog(dbPath?: string): void { diff --git a/packages/mnemopi/src/core/query-cache.ts b/packages/mnemopi/src/core/query-cache.ts index 915c52846..11aed1402 100644 --- a/packages/mnemopi/src/core/query-cache.ts +++ b/packages/mnemopi/src/core/query-cache.ts @@ -1,7 +1,6 @@ -import { Database } from "bun:sqlite"; -import { mkdirSync } from "node:fs"; -import { dirname } from "node:path"; +import type { Database } from "bun:sqlite"; import { type Env, enhancedRecallEnabled } from "../config"; +import { openDatabase } from "../db"; import { cosineSimilarity } from "./vector-math"; export type QueryCacheResult = Record; @@ -80,8 +79,7 @@ export class QueryCache { } #initDb(dbPath: string): void { - if (dbPath !== ":memory:") mkdirSync(dirname(dbPath), { recursive: true }); - const db = new Database(dbPath, { create: true, readwrite: true, strict: true }); + const db = openDatabase(dbPath, { pragmas: false }); this.#conn = db; if (dbPath !== ":memory:") db.exec("PRAGMA journal_mode=WAL"); db.exec(` diff --git a/packages/mnemopi/src/db.ts b/packages/mnemopi/src/db.ts index 653f935fb..b6796cf75 100644 --- a/packages/mnemopi/src/db.ts +++ b/packages/mnemopi/src/db.ts @@ -3,22 +3,57 @@ import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; import { dbPath } from "./config"; -let _osPageSize: number | undefined; +export type SqlitePageSize = number | "os"; -function osPageSize(): number { - if (_osPageSize !== undefined) return _osPageSize; +const MIN_SQLITE_PAGE_SIZE = 512; +const MAX_SQLITE_PAGE_SIZE = 65536; +let detectedSystemPageSize: number | null | undefined; + +function isValidPageSize(size: number): boolean { + return ( + Number.isInteger(size) && + size >= MIN_SQLITE_PAGE_SIZE && + size <= MAX_SQLITE_PAGE_SIZE && + (size & (size - 1)) === 0 + ); +} + +function detectSystemPageSize(): number | undefined { + if (detectedSystemPageSize !== undefined) return detectedSystemPageSize ?? undefined; try { const proc = Bun.spawnSync(["getconf", "PAGE_SIZE"], { stdout: "pipe" }); if (proc.exitCode === 0) { - const size = Number.parseInt(proc.stdout.toString().trim(), 10); - if (size >= 512 && size <= 65536 && (size & (size - 1)) === 0) { - _osPageSize = size; + const size = Number(proc.stdout.toString().trim()); + if (isValidPageSize(size)) { + detectedSystemPageSize = size; return size; } } - } catch { /* fall through */ } - _osPageSize = 4096; - return 4096; + } catch { + /* fall through */ + } + detectedSystemPageSize = null; + return undefined; +} + +function configuredPageSize(): SqlitePageSize | undefined { + const raw = process.env.MNEMOPI_DB_PAGE_SIZE?.trim(); + if (!raw) return undefined; + if (raw.toLowerCase() === "os") return "os"; + const size = Number(raw); + return isValidPageSize(size) ? size : undefined; +} + +function resolvePageSize(requested?: SqlitePageSize): number | undefined { + const pageSize = requested ?? configuredPageSize(); + if (pageSize === "os") return detectSystemPageSize(); + return pageSize; +} + +function applyPageSize(db: Database, path: DatabasePath, requested?: SqlitePageSize): void { + if (path === ":memory:") return; + const pageSize = resolvePageSize(requested); + if (pageSize !== undefined) db.exec(`PRAGMA page_size=${pageSize}`); } export type DatabasePath = string | ":memory:"; @@ -29,6 +64,7 @@ export interface OpenDatabaseOptions { readonly strict?: boolean; readonly loadExtension?: string | readonly string[]; readonly pragmas?: boolean; + readonly pageSize?: SqlitePageSize; } interface TxState { @@ -47,16 +83,17 @@ export function openDatabase(path: DatabasePath = dbPath(), options: OpenDatabas readwrite: options.readwrite ?? true, strict: options.strict ?? true, }); - if (options.pragmas !== false) enablePragmas(db, path); + if (options.pragmas !== false) enablePragmas(db, path, options.pageSize); + else if (options.readwrite !== false) applyPageSize(db, path, options.pageSize); if (options.loadExtension !== undefined) loadExtensions(db, options.loadExtension); return db; } -export function enablePragmas(db: Database, path?: DatabasePath): void { + +export function enablePragmas(db: Database, path?: DatabasePath, pageSize?: SqlitePageSize): void { db.exec("PRAGMA foreign_keys=ON"); db.exec("PRAGMA busy_timeout=5000"); if (path !== ":memory:") { - const pageSize = osPageSize(); - if (pageSize !== 4096) db.exec(`PRAGMA page_size=${pageSize}`); + applyPageSize(db, path ?? dbPath(), pageSize); db.exec("PRAGMA journal_mode=WAL"); } } diff --git a/packages/mnemopi/src/dr/recovery.ts b/packages/mnemopi/src/dr/recovery.ts index cd4aa8d43..50507c215 100644 --- a/packages/mnemopi/src/dr/recovery.ts +++ b/packages/mnemopi/src/dr/recovery.ts @@ -1,4 +1,4 @@ -import { Database } from "bun:sqlite"; +import type { Database } from "bun:sqlite"; import { createHash } from "node:crypto"; import { copyFileSync, @@ -188,7 +188,7 @@ function isSqliteFile(bytes: Uint8Array): boolean { function writeGzippedSqlDump(sql: string, tempPath: string): void { let db: Database | null = null; try { - db = new Database(tempPath, { create: true, readwrite: true, strict: true }); + db = openDatabase(tempPath, { pragmas: false }); db.exec(sql); } finally { closeQuietly(db); diff --git a/packages/mnemopi/test/db-page-size.test.ts b/packages/mnemopi/test/db-page-size.test.ts index b8d52b177..0791a775b 100644 --- a/packages/mnemopi/test/db-page-size.test.ts +++ b/packages/mnemopi/test/db-page-size.test.ts @@ -1,22 +1,27 @@ +import type { Database } from "bun:sqlite"; import { afterEach, describe, expect, it } from "bun:test"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { openDatabase, closeQuietly } from "../src/db"; - -function expectedOsPageSize(): number { - try { - const proc = Bun.spawnSync(["getconf", "PAGE_SIZE"], { stdout: "pipe" }); - if (proc.exitCode === 0) { - const size = Number.parseInt(proc.stdout.toString().trim(), 10); - if (size >= 512 && size <= 65536 && (size & (size - 1)) === 0) return size; - } - } catch { /* fall through */ } - return 4096; -} +import { closeQuietly, openDatabase } from "../src/db"; const roots: string[] = []; +function tempDb(): string { + const root = mkdtempSync(join(tmpdir(), "mnemopi-page-size-")); + roots.push(root); + return join(root, "test.db"); +} + +interface PageSizeRow { + page_size: number; +} + +function pageSize(db: Database): number { + const row = db.query("PRAGMA page_size").get() as PageSizeRow; + return row.page_size; +} + afterEach(() => { for (;;) { const root = roots.pop(); @@ -26,27 +31,36 @@ afterEach(() => { }); describe("db page size", () => { - it("creates a new database with page size matching the OS page size", () => { - const root = mkdtempSync(join(tmpdir(), "mnemopi-page-size-")); - roots.push(root); - const path = join(root, "test.db"); - - const db = openDatabase(path); + it("uses an explicitly requested page size for a new file-backed database", () => { + const db = openDatabase(tempDb(), { pageSize: 16384 }); try { - const pageSize = db.query("PRAGMA page_size").get() as { page_size: number }; - expect(pageSize.page_size).toBe(expectedOsPageSize()); + expect(pageSize(db)).toBe(16384); } finally { closeQuietly(db); } }); - it("does not set page_size on in-memory databases", () => { - const db = openDatabase(":memory:"); + it("keeps the existing page size when reopening a database with another request", () => { + const path = tempDb(); + const initial = openDatabase(path, { pageSize: 4096 }); try { - const pageSize = db.query("PRAGMA page_size").get() as { page_size: number }; - // In-memory DBs keep the SQLite default (4096) since there is no - // persistent file whose I/O would benefit from OS page alignment. - expect(pageSize.page_size).toBe(4096); + initial.run("CREATE TABLE existing_data (value TEXT NOT NULL)"); + } finally { + closeQuietly(initial); + } + + const reopened = openDatabase(path, { pageSize: 16384 }); + try { + expect(pageSize(reopened)).toBe(4096); + } finally { + closeQuietly(reopened); + } + }); + + it("does not set page size on in-memory databases", () => { + const db = openDatabase(":memory:", { pageSize: 16384 }); + try { + expect(pageSize(db)).toBe(4096); } finally { closeQuietly(db); } From 2163ee38a6d600b73e4c7c1387ffeaa812b6e26e Mon Sep 17 00:00:00 2001 From: Brad McCormack Date: Sat, 1 Aug 2026 15:03:17 +1000 Subject: [PATCH 3/5] fix(mnemopi): validate explicit SQLite page sizes Reject invalid page-size values before interpolating the PRAGMA and cover new, existing, and in-memory database behavior. --- packages/mnemopi/src/db.ts | 2 +- packages/mnemopi/test/db-page-size.test.ts | 27 ++++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/mnemopi/src/db.ts b/packages/mnemopi/src/db.ts index b6796cf75..564979db6 100644 --- a/packages/mnemopi/src/db.ts +++ b/packages/mnemopi/src/db.ts @@ -47,7 +47,7 @@ function configuredPageSize(): SqlitePageSize | undefined { function resolvePageSize(requested?: SqlitePageSize): number | undefined { const pageSize = requested ?? configuredPageSize(); if (pageSize === "os") return detectSystemPageSize(); - return pageSize; + return pageSize !== undefined && isValidPageSize(pageSize) ? pageSize : undefined; } function applyPageSize(db: Database, path: DatabasePath, requested?: SqlitePageSize): void { diff --git a/packages/mnemopi/test/db-page-size.test.ts b/packages/mnemopi/test/db-page-size.test.ts index 0791a775b..b451d1db1 100644 --- a/packages/mnemopi/test/db-page-size.test.ts +++ b/packages/mnemopi/test/db-page-size.test.ts @@ -1,16 +1,16 @@ import type { Database } from "bun:sqlite"; import { afterEach, describe, expect, it } from "bun:test"; -import { mkdtempSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; import { closeQuietly, openDatabase } from "../src/db"; const roots: string[] = []; function tempDb(): string { - const root = mkdtempSync(join(tmpdir(), "mnemopi-page-size-")); + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnemopi-page-size-")); roots.push(root); - return join(root, "test.db"); + return path.join(root, "test.db"); } interface PageSizeRow { @@ -26,7 +26,7 @@ afterEach(() => { for (;;) { const root = roots.pop(); if (root === undefined) break; - rmSync(root, { recursive: true, force: true }); + fs.rmSync(root, { recursive: true, force: true }); } }); @@ -40,16 +40,25 @@ describe("db page size", () => { } }); + it("ignores invalid explicit page sizes", () => { + const db = openDatabase(tempDb(), { pageSize: 12345 }); + try { + expect(pageSize(db)).toBe(4096); + } finally { + closeQuietly(db); + } + }); + it("keeps the existing page size when reopening a database with another request", () => { - const path = tempDb(); - const initial = openDatabase(path, { pageSize: 4096 }); + const dbPath = tempDb(); + const initial = openDatabase(dbPath, { pageSize: 4096 }); try { initial.run("CREATE TABLE existing_data (value TEXT NOT NULL)"); } finally { closeQuietly(initial); } - const reopened = openDatabase(path, { pageSize: 16384 }); + const reopened = openDatabase(dbPath, { pageSize: 16384 }); try { expect(pageSize(reopened)).toBe(4096); } finally { From 4313e408e77a08b2f47cdf882954f7e87d3be6c8 Mon Sep 17 00:00:00 2001 From: Brad McCormack Date: Sat, 1 Aug 2026 15:03:25 +1000 Subject: [PATCH 4/5] fix(mnemopi): follow namespace import convention in cost log Use namespace imports for the Node built-ins touched by cost logging. --- packages/mnemopi/src/core/cost-log.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mnemopi/src/core/cost-log.ts b/packages/mnemopi/src/core/cost-log.ts index f103550d1..77fd59c23 100644 --- a/packages/mnemopi/src/core/cost-log.ts +++ b/packages/mnemopi/src/core/cost-log.ts @@ -1,9 +1,9 @@ import type { Database } from "bun:sqlite"; -import { homedir } from "node:os"; -import { join } from "node:path"; +import * as os from "node:os"; +import * as path from "node:path"; import { openDatabase } from "../db"; -export const DEFAULT_LOG_DIR = join(homedir(), ".mnemopi", "data"); -export const DEFAULT_LOG_DB = join(DEFAULT_LOG_DIR, "cost_log.db"); +export const DEFAULT_LOG_DIR = path.join(os.homedir(), ".mnemopi", "data"); +export const DEFAULT_LOG_DB = path.join(DEFAULT_LOG_DIR, "cost_log.db"); export interface CostStats { total_calls: number; From b955946dff8f78c414e22a1238bee7f228794133 Mon Sep 17 00:00:00 2001 From: Brad McCormack Date: Sun, 2 Aug 2026 11:13:43 +1000 Subject: [PATCH 5/5] fix(mnemopi): restore [Unreleased] section after upstream merge The auto-merge during conflict resolution placed the page-size Added section under [17.2.3] (which already had the upstream's think-block fix) and left an empty ### Fixed header. Move the Added section back under [Unreleased] where it belongs; drop the empty header. No code changes. --- packages/mnemopi/CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mnemopi/CHANGELOG.md b/packages/mnemopi/CHANGELOG.md index ef8eb6806..4a4c46e36 100644 --- a/packages/mnemopi/CHANGELOG.md +++ b/packages/mnemopi/CHANGELOG.md @@ -2,15 +2,15 @@ ## [Unreleased] +### Added + +- Added opt-in SQLite page-size selection for file-backed databases. Set `MNEMOPI_DB_PAGE_SIZE` to a valid SQLite page size (a power of two from 512 to 65536) or `os`, or pass `pageSize` to `openDatabase`. The default remains SQLite's own page size because the virtual-memory page size does not establish storage-block behavior and larger WAL pages can increase sparse-write cost. Existing databases retain their page size. + ## [17.2.3] - 2026-08-01 ### Fixed - Stripped `…` reasoning blocks from remote LLM output in `cleanOutput`, so reasoning-model responses no longer leak into consolidated memories or corrupt fact extraction (the reasoning wrapper previously survived parsing and every stored fact became reasoning prose). ([#7231](https://github.com/can1357/oh-my-pi/issues/7231)) -### Fixed -### Added - -- Added opt-in SQLite page-size selection for file-backed databases. Set `MNEMOPI_DB_PAGE_SIZE` to a valid SQLite page size (a power of two from 512 to 65536) or `os`, or pass `pageSize` to `openDatabase`. The default remains SQLite's own page size because the virtual-memory page size does not establish storage-block behavior and larger WAL pages can increase sparse-write cost. Existing databases retain their page size. ## [17.2.2] - 2026-07-31