- Replaced time-based sleeps and polling loops with event-driven promise resolvers and fake timers across agent and tool tests.
- Migrated test suites to share in-memory auth storage and fixtures using lifecycle hooks.
- Updated catalog model definitions, metadata, and configurations.
- Remove redundant definedness, null, and type checks across test suites in multiple packages.
- Clean up unused assertions, metadata tests, and obsolete test cases.
- Add good versus bad test filter guidelines and requirements to project documentation.
Caught recall and retention failures at their fire-and-forget event boundaries, with bank and operation diagnostics.
Added agent-start and agent-end regression coverage for unavailable session data and bank access.
Fixes#8351
dispose({ timeoutMs }) legitimately detaches the consolidate pass when
the shutdown budget expires mid-flight (#3641), so asserting the shared
bank flush synchronously after dispose raced the detached pass on slow
CI runners (0 calls observed on run 31287979000). Signal the flush call
through a deferred and await it after releasing the lock; the bounded
<500ms return assertion is unchanged.
- Caught all-target recall failures inside the fire-and-forget auto-recall path.
- Kept first-turn recall eligible for retry after an engine failure.
- Added regression coverage for the background failure contract.
- Warned when a scoped recall target fails while preserving partial results from healthy targets.
- Re-threw the underlying failure when every target fails so recall cannot masquerade as an empty search.
- Added tool-level regression coverage for total and partial scoped failures.
Fixes#7364
Extended the shutdown busy-timeout clamp from the retain bank to all owned banks so a locked shared bank (per-project-tagged) cannot stall teardown for the default 5s SQLite timeout.
Fixes#7351
Capped synchronous SQLite busy waits before starting bounded final consolidation, then spent only the remaining shutdown deadline on asynchronous drains.
Added regression coverage for a locked Mnemopi writer in the headless teardown path.
Fixes#7351
- Serialized backend transitions across runtime state, tools, and prompts.
- Rehydrated Mnemopi listeners after clear and enqueue maintenance.
- Made memory.backend the sole post-migration local runtime gate.
Fixes#5638
Pre-fix resumed sessions wrote cumulative transcript rows under the
incremental ${sessionId}-<ts> source_id shape, so summing every prefixed
legacy row could overshoot the real retained prefix and permanently skip
unseen turns. Use per-row max instead: it can only under-count, which at
worst re-stores one suffix before an explicit cursor row takes over.
Adds a regression seeding a legacy bank with two incremental rows plus a
cumulative resumed row and asserting turns 7-8 still get retained.
The single 30s hang-guard from #4290 (sized for metadata fetches per #4229)
wrapped every Hindsight op including reflect, an agentic retrieve+synthesize
call whose healthy latency routinely exceeds 30s, so ordinary successful
reflects were aborted client-side.
- Give HindsightApi per-op deadlines with sensible defaults (reflect 120s,
retain 60s, recall/request 30s) plus a HindsightTimeouts option bag.
- Report the effective seconds in the timeout error instead of a hard-coded
"after 30s".
- Add hindsight.requestTimeoutMs / reflectTimeoutMs / recallTimeoutMs /
retainTimeoutMs settings and matching HINDSIGHT_*_TIMEOUT_MS env vars.
- Preserve caller-abort merging via withTimeoutSignal.
Fixes#6125
Persisted the retained user-turn cursor in transcript metadata and restored it when sessions resume, including legacy rows from before the cursor existed.
Made forced lifecycle retention slice from that cursor so enqueue and shutdown no longer add cumulative snapshots beside incremental windows.
Fixes#6058
Interactive shutdown (AgentSession.dispose) now runs a bounded
consolidation on the current session only and skips fresh LLM fact
extraction. The heavier cross-session consolidation with extraction is
still performed by the /memory enqueue command and the backend enqueue
path.
This avoids blocking /quit and /exit on a fresh LLM round-trip
and a full sleepAllSessions scan.
- Add full and extract options to consolidate() and
forceRetainCurrentSession()
- Route dispose() through consolidate({ full: false, extract: false })
- Route /memory enqueue and backend enqueue through
consolidate({ full: true })
Fixes#3641
Added an embedText projection for remember() so stored transcripts can remain readable while embeddings, FTS indexing, and embedding-model rebuilds use marker-free text. Updated coding-agent retention to pass the marker-free projection and strip retained protocol markers from recall display.
Fixes#4395
Auto-retain now slices the transcript after the last retained user turn before storing an episode, preventing cumulative duplicate session transcripts in mnemopi banks.
Fixes#4396
/quit and /exit hung for many seconds because AgentSession.dispose()
awaited MnemopiSessionState.dispose() unconditionally, and that path
runs consolidate() (state.ts:421) which fires a fresh LLM fact
extraction for the just-retained transcript and then awaits
flushExtractions() per owned bank. One LLM round-trip per shutdown,
no upper bound, no visible status.
- Add a timeoutMs option to MnemopiSessionState.dispose. When the cap
fires the in-flight consolidate is detached to the background and the
SQLite handles close once it settles, so writes never race a closed
handle.
- AgentSession.dispose passes SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1_500 on
the user-visible shutdown path. Per-turn maybeRetainOnAgentEnd has
already retained earlier turns, so the worst case is losing episodic
promotion for the last few turns. State-replacement disposes
(mnemopiBackend.start) stay unbounded.
- InteractiveMode.shutdown surfaces a 'Closing session…' status before
dispose runs so the brief pause is explained rather than mysterious.
Two regression tests in memory-tools.test.ts cover (1) dispose returns
within the budget when flushExtractions stalls and the deferred close
still runs once consolidate settles, and (2) unbounded dispose still
runs the full #2320 consolidate-then-close pipeline.
Fixes#3641
- Added an extractText override to pi-mnemopi remember paths so stored content and mined facts can use different text.
- Routed coding-agent mnemopi retention to store the full transcript while extracting only user-authored turns.
- Tightened deterministic Instruction extraction to require an explicit I/you subject.
Fixes#3372
Fix all Windows-specific test failures caused by path handling problems
and EBUSY errors from unclosed SQLite database handles.
Root causes fixed:
1. POSIX path assumptions: replaced hard-coded file:///tmp, /repo, etc.
with pathToFileURL/path.resolve/path.join computed expectations
2. shortenPath() now normalizes backslashes to forward slashes after ~
and respects home directory boundaries
3. HistoryStorage.resetInstance() leaked its Database — added #close()
that finalizes all prepared statements and closes the DB
4. AgentStorage gained the same resetInstance()/#close() pattern
5. SqliteAuthCredentialStore.close() leaked one-off prepared statements
from inline this.#db.prepare() calls — wrapped each in try/finally
6. model-cache.ts used a process-global DB even for custom dbPath —
now opens/closes per-call via withModelCacheDb
7. createAgentSession leaked AuthStorage on construction failure —
added ownsAuthStorage cleanup in catch block
8. MnemopiBackend.removeDbFiles() now truly best-effort (catches errors)
9. TempDir retry window expanded from 4x10ms to 40x25ms
10. TempDir prefix convention: non-@ prefixes created dirs relative to
cwd instead of os.tmpdir() — all test temp dirs now use @ prefix
11. Shell-escaped interpolated paths in bash tool tests
12. git core.autocrlf false in autoresearch test repo init
All 522 previously-failing Windows tests now pass.
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.