Skip to content

v11.8.4

Choose a tag to compare

@doobidoo doobidoo released this 05 Sep 07:07
· 88 commits to main since this release

[11.8.4] - 2026-08-25

PATCH release. The headline fix stops a hybrid deployment from burning through Cloudflare's D1 free-tier read allowance; three smaller correctness fixes ride along.

Fixed

  • fix(hybrid): stop the sync loop from scanning D1 on every cycle (#289). Cloudflare mailed that this account regularly exceeds the D1 free-tier daily limits it starts enforcing on 1 September 2026 (5 million rows read, 100,000 rows written), and it did: the background sync called get_stats() twice per cycle, once as the health check and again immediately after inside the capacity check, and that statement scanned the memories table four times over. At the default 300s interval that came to roughly 19 million rows read per day against the 5 million allowance — the budget was gone after about six hours of uptime. Writes were never the problem, 64 new memories in seven days. Measured against the live database via the D1 REST API's meta.rows_read (17,658 rows, 9,985 of them tombstones): SELECT 1 reads zero, the old get_stats() read 33,669, the new one reads 25,994, and both return identical values for all seven fields. Four changes carry the fix: a health_probe() that is SELECT 1 and reads nothing, with a base-class default and a getattr fallback so other backends are untouched; capacity monitoring moved off the sync cadence onto its own interval, MCP_HYBRID_CAPACITY_CHECK_INTERVAL (default 3600s); get_stats() rewritten to conditional aggregates so it counts in one pass instead of one subquery per figure; and CloudflareStorage.purge_deleted(), because the existing tombstone purge only ever touched the primary, so D1 kept every tombstone forever and scanned them on every stats call — they had reached 57% of the table. Together the sync loop drops from roughly 19M to well under 1M rows read per day. Two things worth knowing before upgrading a hybrid deployment: the first run after this ships hard-deletes D1 tombstones already past the 30-day retention window (Vectorize entries and R2 objects were already gone at soft-delete time, so this only reclaims D1 rows, and the one-off write stays well inside the daily budget), and the new interval is documented in .env.example.
  • fix(health): report the embedding model actually in use (#290). With an external embedding endpoint configured, memory_health and /api/health/detailed kept reporting all-MiniLM-L6-v2 while a differently-sized model was doing the work — the external initialisation path set the embedding function and its dimension but never the name, so the attribute kept the constructor default. Closes #254.
  • fix(sqlite_vec): stop reporting memories plus embedding rows as a memory count (#290). The hash-fallback refusal message summed the memories and memory_embeddings tables and presented that sum as a memory count, roughly double the true figure — exactly the kind of number that ends up quoted in an incident writeup. The two counts are now reported separately. Closes #228.
  • fix(stdio): keep startup diagnostics off the JSON-RPC channel (#287). With LM Studio detected, the server wrote human-readable diagnostics to stdout, which on a stdio MCP server is the protocol channel itself — a strict client sees a corrupt stream before the handshake completes. The existing guard had the diagnosis right and the remedy backwards: LM Studio speaks stdio too, so restricting the output to that client protected the one client that was fine while still breaking it. Moved to stderr instead of deleted, so the warnings still reach the user. Closes #275.
  • fix(cli): give get_storage a hybrid branch (#287). memory status --storage-backend hybrid raised ValueError: Unsupported storage backend: hybrid outright — hybrid was offered on the command line and documented as the accepted name, but the function never branched on it. Closes #233.