Proactive linking (ingesting new memories into the episodic graph as they
are stored) could only be toggled through the MNEMOPI_PROACTIVE_LINKING
environment variable, unlike its sibling recall features polyphonicRecall
and enhancedRecall, which hosts drive through configureRecallFeatures() and
the coding-agent mnemopi.polyphonicRecall / mnemopi.enhancedRecall
config.yml settings. The write-path gate in store.ts read process.env
directly and the existing env-only proactiveLinkingEnabled() helper was
dead code, so host configuration never reached it.
- Add proactiveLinking to RecallFeatureFlags / configureRecallFeatures()
and rewrite proactiveLinkingEnabled() to fall back to the configured
default, matching the polyphonic/enhanced resolvers. The
MNEMOPI_PROACTIVE_LINKING env var still takes precedence when set.
- Route the store.ts proactiveLinkIfEnabled gate through
proactiveLinkingEnabled() instead of reading process.env directly.
- Add the mnemopi.proactiveLinking coding-agent config.yml setting (off by
default, /settings -> Memory -> Mnemopi) and wire it through
loadMnemopiConfig and createScopedResources.
Closes#2440
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Added `mnemopi.polyphonicRecall` and `mnemopi.enhancedRecall` boolean settings (default off) to `settings-schema.ts` and `loadMnemopiConfig`, applied via the new `configureRecallFeatures()` in `createScopedResources`.
- Made `polyphonicRecallEnabled()`, `enhancedRecallEnabled()`, and `isEnhancedRecallEnabled()` fall back to the configured defaults while `MNEMOPI_POLYPHONIC_RECALL` / `MNEMOPI_ENHANCED_RECALL` env vars still win when set.
- Exported `configureRecallFeatures`/`RecallFeatureFlags` from the package root and `core` barrels; documented the settings in `docs/mnemosyne-memory-backend.md`.
- Added `recall-feature-flags.test.ts` covering defaults, config enablement, and env precedence; the mnemopi changelog hunk also carries the adjacent #2322 entry (same contiguous run).
Fixes#2323: Mnemopi: MNEMOPI_POLYPHONIC_RECALL and MNEMOPI_ENHANCED_RECALL not configurable via config.yml
The previous refactor dropped `if (this.aliasOf) return` into
consolidate(), which made `/memory enqueue` invoked from inside a
subagent a no-op. The old enqueue() body short-circuited only
forceRetainCurrentSession (via its own guard) but still flushed
extractions and slept on state.memory, which for aliased states points
at the parent's shared retain bank.
Moved the alias guard back out of consolidate(): forceRetainCurrentSession
keeps its own early-return (the subagent's transcript is the parent's
concern), so consolidate() runs the SQL-level flush+sleep on every
owned bank for both primary and aliased states. The lifecycle guard
stays in dispose() so disposing a subagent still does not flush, sleep,
or close the parent's memory.
Added a regression test: a /memory enqueue routed through the aliased
child state calls flushExtractions + sleepAllSessions(false) on the
parent's owned memory exactly once, and forceRetainCurrentSession runs
on the child (where its own guard returns early) but not on the parent.
Addresses #2327 review.
`/memory clear` (mnemopiBackend.clear) calls dispose() right before
removing the SQLite files. Running the consolidation pass first would
make a destructive clear spend tokens/time creating memories that get
deleted on the very next line, and a slow extraction could block the
clear path under llmMode: remote.
Added a `{ consolidate?: boolean }` option to
MnemopiSessionState.dispose; defaults to true so normal shutdown still
runs the SHMR/beam pipeline. mnemopiBackend.clear passes
`{ consolidate: false }` so the close path stays unsubscribe + close
only. Added a regression test that spies on forceRetainCurrentSession,
consolidate, flushExtractions, and sleepAllSessions to pin the new
behavior, while close still runs once per owned bank.
Addresses #2327 review.
MnemopiSessionState.dispose() now drains pending fact extractions and
runs sleepAllSessions on every owned bank before closing handles, and
AgentSession.dispose() awaits the result. This matches the manual
`/memory enqueue` slash command which was the only caller of the
consolidation pipeline.
Without the shutdown hook, episodic_memory, gists, consolidation_log,
graph_edges, and triples stayed empty for every deployment that never
typed `/memory enqueue|rebuild` — working memory accumulated forever
and long-term recall never formed.
Factored the consolidate step out of `mnemopiBackend.enqueue` into a
shared MnemopiSessionState#consolidate so the slash command and the
shutdown path share one implementation. Added two regression tests
verifying owned-bank consolidation order (flush -> sleep -> close, per
bank) and that aliased subagent dispose stays a no-op against the
parent.
Fixes#2320
- Added lazy async module loaders for @babel/parser, linkedom, puppeteer/browsers, @mozilla/readability, @xterm/headless, and mnemopi to avoid loading them during cold startup.
- Added an interactive startup splash before session construction and skipped it for resume/fork/continue, quiet mode, timing mode, or non-TTY runs.
- Updated JS import-rewrite and memory tests to match async parser loading and preloaded mnemopi modules for sync state helpers.
Mid-session edits to hindsight.bankId / bankIdPrefix / scoping kept the
active HindsightSessionState pinned to the bank selected at session
start, so retain/recall/reflect calls landed in the stale bank. Settings
hooks now fire onHindsightScopeChanged; the backend rebuilds the
primary state against the recomputed scope, disposing the previous one
after flushing its queue so queued tool-initiated retains still land in
the bank they were enqueued for.
Also:
- Renamed ensureBankMission to ensureBankExists. The old version
skipped creation entirely when bankMission was blank, so the first
mental-model POST (auto-seed) could land against a never-PUT bank.
Bank creation is now idempotent and unconditional, and runs before
mental-model bootstrap.
- Fixed AgentSession.dispose to flush the retain queue BEFORE clearing
the session state pointer. Reversed, HindsightRetainQueue.#doFlush's
identity guard would see the cleared pointer and drop the spliced
batch with a 'session vanished' warning.
- Snapshotted hindsightScopeCallbacks before iterating because each
rebuild subscribes a fresh callback inside the same fire; iterating
the live Set would spin.
Fixes#1902
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
- Resolved memory backend before instruction assembly and used it to build developer instructions.
- Added a `memoryRootEnabled` prompt option when the memory backend id is `local`.
- Typed Mnemosyne memory state types and updated `registerMnemosyneState()` to call `setMnemosyneSessionState()`.
- Documented that `@oh-my-pi/pi-mnemosyne/diagnose` export was added in the mnemosyne changelog.
- Added `memory_edit` tool for update, forget, and invalidate operations on Mnemosyne memories by id.
- Added `stats` and `diagnose` methods to `MemoryBackend` interface with Mnemosyne implementations.
- Exposed `/memory stats` and `/memory diagnose` slash commands in TUI and ACP modes.
- Refactored recall output to include memory ids via `formatScopedRecallWithIds`.
- Added tests for `MnemosyneSessionState` lifecycle, including turn-based auto-retain.
- Added tests covering scoped Mnemosyne database clearing and per-project bank derivation.
- Added a task renderer test to suppress per-task previews when `renderContext.hasResult` is true.
- Strips project-bank tokens from the query before a second recall pass on the global bank.
- Prevents broad user-preference memories from being missed when the query is packed with project-specific tokens.
- Added helper functions for bank name tokenization, phrase stripping, and query normalization.
- Renamed HindsightRecall/Reflect/RetainTool classes and files to Memory* for backend-neutral naming.
- Fixed Mnemosyne state to resolve separate db paths per bank instead of sharing one file.
- Updated test file and all imports to reflect the new names.