Codex review on #1833 flagged a real race: short-lived owners
(`cmdRemember` via `withMemory`, MCP `handleRemember` via `withBeam`)
schedule the embedding task on `beam.pendingExtractions` then close
the SQLite handle synchronously. By the time `embed()` awaits and
tries to insert, the DB is closed and `runEmbedding` swallows the
error — so `mnemopi store …` / `mnemopi sleep …` and the MCP write
paths silently dropped every `memory_embeddings` row.
- `withMemory` (CLI), `withBeam` (MCP), and `withSharedBeam` (MCP) are
now async and `await beam.flushExtractions()` before `close()`.
- CLI handlers (`cmdRemember`/`cmdUpdate`/`cmdStats`/`cmdExport`/`cmdImport`/
`cmdSleep`/`cmdScratchpad`) and MCP handlers (`handleRemember`/
`handleUpdate`/`handleSleep`/`handleScratchpad*`/`handleExport`/
`handleImport`/`handleValidate`/`handleSharedRemember`/etc.) return
`Promise<...>` to thread the new drain through.
- Pinned the contract with a new regression that drives the full
`cmdRemember` → `withMemory` → close → reopen cycle and asserts
`memory_embeddings` persisted across the close, plus updated the
affected sync `expect(cmdX(...)).toBe(0)` tests to await.
Follow-up to #1832.
The beam backend never invoked the embedding pipeline during normal
operation: `remember()`/`rememberBatch()`/`updateWorking()` skipped `embed()`
entirely and `recall()`/`recallEnhanced()` never called `embedQuery()` on
the query text. As a result `memory_embeddings` stayed empty in every
deployment and recall silently degraded to FTS-only regardless of the
configured provider (fastembed, OpenAI-compatible API, custom).
- Added `scheduleEmbedding` on `beam.pendingExtractions` (mirroring
`scheduleFactExtraction`) and wired it from `remember`, `rememberBatch`,
`updateWorking`, and `consolidateToEpisodic`. Writes
`INSERT OR REPLACE INTO memory_embeddings(memory_id, embedding_json, model)`
with the active runtime-options model, captured before the AsyncLocalStorage
scope exits and re-entered inside the task.
- Auto-derived `queryEmbedding` inside `recall()` via `embedQuery(query)` when
the caller did not pass one. `queryEmbedding: null` is preserved as the
explicit FTS-only opt-out; `undefined` triggers auto-derive.
- Propagated `queryEmbedding` through `Mnemopi`'s `toRecallOptions` so the
facade no longer strips the override on the way to the beam layer.
- Made `Mnemopi.recall`/`recallEnhanced`/`search`/`query`, the module-level
exports, `BeamMemory.recall`/`recallEnhanced`, the free `recall`/`recallEnhanced`,
and `orchestrateRecall` async. MCP `handleToolCall`/`callToolJson`/`handleJsonRpc`
follow suit so the recall handler can await.
- Fixed `withBeam`/`withSharedBeam` to defer `beam.close()` until the async
handler resolves; otherwise the new async recall hit
`RangeError: Cannot use a closed database`.
- Updated CLI, MCP entrypoints, coding-agent `MnemopiSessionState`, and every
affected test to await the new shapes.
Verified with a new regression suite (`test/issue-1832-embedding-population.test.ts`)
exercising both ends of the bug: empty `memory_embeddings` and zero
`dense_score`.
Fixes#1832