Skip to content

feat(capture): memory embedding tolerates a busy local Ollama - #45

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-8615-memory-embedding-tolerates-a-busy-local-ollama
Sep 22, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-8615-memory-embedding-tolerates-a-busy-local-ollama

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Finishes https://linear.app/abklabs/issue/CL-8615/memory-embedding-tolerates-a-busy-local-ollama.

Behavior:

  • captureDocument commits the row-store transaction, then schedules the embed pass detached (queueMicrotask fire-and-forget) and returns {status:"captured"} with no network await — a slow or busy local Ollama (embeddings queued behind chat inference) never stalls the awaiting add call.
  • The background pass (runBackgroundEmbedPass) retries the fresh-chunk embed up to 3 attempts (1s, 5s delays), then runs one bounded pending-chunk sweep (default 100, oldest-first) via reembedPendingChunks/findPendingChunks. Failures are logged and left pending, never thrown.
  • embedInsertedChunksWithConfig/ensureEmbedModel/activateEmbedModel/embedChunks accept an optional fetchImpl; captureDocument takes an optional third-position CaptureBackgroundOpts {fetchImpl, schedule, sleep, maxAttempts, pendingLimit} (only the memory.ts add path uses the default).
  • EMBED_TIMEOUT_MS plumbing (EngineConfig.embed.timeoutMs -> EmbedClientConfig.timeoutMs -> AbortSignal.timeout) already existed; a docs note for slow machines was added to workbench docs/local-dev.md (separate checkout, not part of this PR).
  • Registry flow and search/promotion semantics unchanged; no INTERCHANGE touches.

Verification:

  • bun run typecheck: clean.
  • bun run test: 487 pass, 0 fail across 45 files (8 in src/services/capture.test.ts, incl. 3 new CL-8615 tests).
  • New tests follow the repo fake-SQL/fetch-stub conventions and dodge memory.test.ts mock.module leakage via cache-busted dynamic import (same trick as services/search.test.ts).
  • Live Ollama soak was not run: no live embedder was exercised here, so a busy-Ollama soak remains unverified end-to-end.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review

Verdict: approve with should-fix. MERGEABLE on current main. CI test green (2026-09-19). No prior reviews.

Behavior matches CL-8615: captureDocument commits, then queueMicrotask fire-and-forget runBackgroundEmbedPass (3 attempts, 1s/5s, then a bounded pending sweep) and returns {status:"captured"} without awaiting the network. sql is a process-level postgres.js pool, so background queries are not request-scoped. deriveFromRawCapture still awaits embed (replay unchanged). Lexical-only still returns degraded synchronously.

Should-fix

  • findPendingChunks has no version-status filter. Add A (embed pending) → forget A (tombstone redacts text, does not delete embeddings) → add B → sweep can embed "[redacted]" for A. Search filters tombstones so this is not a hit leak; it is wasted embed work and a placeholder vector. Join memory.version and skip tombstoned (and likely non-live generations).
  • No serialization of background passes. Every add independently schedules up to 3 embed attempts plus a 100-chunk sweep. A burst of N adds while Ollama is busy becomes N concurrent storms, then a synchronized 1s retry. Add latency is fixed; recovery against a busy daemon can get worse.
  • add no longer reports degraded when embed is configured. The old path returned degraded: ["embed_unavailable"] on embed failure; the new success path omits it. Intentional per the PR body, but hosts that treat one “is this response degraded” check as embed health will now see a clean capture while chunks are still pending. Document that, or keep a pending/degraded signal.

Fixes for the tombstone filter and a single-flight background pass are in flight on this branch.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Re-review

Verdict: approve. SHA 8d2ae2f.

Pending-chunk sweep inner-joins memory.version and skips tombstoned / non-live generations. Background embed is a process-wide serial drain with same-tenant coalesce. captureDocument still returns captured without awaiting embed. bun test ./src 490 pass, typecheck 0.

Corbits Builder and others added 2 commits September 22, 2026 08:12
Capture commits the rows and returns {status:"captured"} with no network
await; embedding runs detached with retry (1s, 5s) plus a bounded pending
sweep. Adds regression tests for the deferred pass, retry/sweep, and
embed-timeout signal threading.
… passes

Join memory.version in findPendingChunks so a forget-then-add cannot
sweep redacted placeholder text (tombstoned status and non-live
generations). Coalesce overlapping background embed passes so concurrent
adds share one in-flight drain instead of N retry storms against a busy
Ollama.
@TheGreatAxios
TheGreatAxios force-pushed the cl-8615-memory-embedding-tolerates-a-busy-local-ollama branch from 8d2ae2f to 26340df Compare September 22, 2026 15:13

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review · Verdict: Approve (posted as Comment — same account as author)

captureDocument commits the row-store transaction and returns {status:"captured"} immediately; embedding of the fresh chunks now runs in a detached background pass with bounded retry (1s, 5s) plus a bounded pending-chunk sweep, so a busy local Ollama never stalls the awaiting caller.

Findings

  • src/services/capture.ts:50 — the doc-comment line runs to 100 chars while every surrounding line wraps ~70-78 ("...HTTP caller). Unlike a fire-and-forget capture hook, this" trails onto the same line as the CL-8615 note). Cosmetic only, no behavior impact.

Notes

  • createBackgroundEmbedScheduler's chain is process-wide, so background embed passes for different tenants also serialize through the same chain, not just same-tenant coalescing — confirmed by the "passes for two tenants still never run concurrently" test. That's a reasonable choice for a single shared local Ollama (the stated scenario), but worth the author owning explicitly if this ever runs against per-tenant embed endpoints, since a slow/retrying pass for one tenant now adds up to ~6s of queuing delay to every other tenant's background embed.
  • findPendingChunks joining memory.version and excluding tombstoned/non-live rows closes a real gap: tombstoneDocument redacts chunk text to [redacted] without deleting the chunk row, so a pre-fix sweep on an unembedded chunk from a forgotten document would have embedded the placeholder text.
  • Diff is scoped to src/services/capture.ts and its test file only; no migrations, no unrelated files. Did not find dead code, unused exports, unreachable defensive branches, comments restating code, or tests pinning a dependency's own behavior — the SQL-substring assertions in the new tests match an existing pattern already used in timeline.test.ts/search.test.ts, not new hedging.

Checks

  • bun install: clean.
  • bun run typecheck: clean (tsc --noEmit, no output).
  • bun run test: 457 pass, 0 fail, 44 files (DATABASE_URL unset locally, so the one DB-gated file skips — matches the repo's documented behavior; the PR's own 487/45 figure was run with DATABASE_URL set).
  • Commit messages: two commits, both match their diffs (git show on each); no ticket references in either subject/body (git log --format='%B' scan clean); no filename tokens in subjects.

@TheGreatAxios
TheGreatAxios merged commit 013b108 into main Sep 22, 2026
2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-8615-memory-embedding-tolerates-a-busy-local-ollama branch September 22, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant