Repository navigation
fix: memory keeps every byte and stops freezing the daemon - #379
Merged
Merged
Conversation
Memory claimed to be verbatim but cut tool output at 64 KiB, embedded only the first ~512 tokens of each episode, and ran the embedding model on the daemon's main thread. Measured, those were the real costs: - Embedding a batch of 8 episodes froze the event loop for ~450 ms. The model now runs on a Bun Worker; the longest stall while embedding 16 long episodes dropped to 14-74 ms. - Full-text indexing a body costs ~9 ms per MiB on the event path (220 ms for 10 MiB), which is why output was capped. Storage and indexing are now separate: the full body is kept zstd-compressed (~5 ms, ~5% of the size for 10 MiB) in episode_payloads, and only a bounded view (head, tail and the diagnostic lines of the middle, <=256 KiB) is indexed. get_episode pages through the full body. - bge-small reads 512 tokens and drops the rest, so semantic recall never saw most tool results. Episodes are now embedded in up to 8 windows, placed on the head, tail and diagnostic lines, and scored by their best window. A failure buried 10K chars into a 21K-char log went from rank 8 to rank 1 on a natural-language query. - With output compression on, the hint promised the raw output was in memory, but only the compressed text was stored. The wrapper now spills the raw output to a private file named in the hint, and the chunker stores it in the episode. memory.embeddings.enabled (default true, CODEOID_MEMORY_EMBEDDINGS) turns semantic recall off entirely: the model is never loaded and recall runs on full-text search. memory.embeddings.maxWindows (default 8) sets windows per episode. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
KunalJavelin
approved these changes
Oct 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Memory is meant to be verbatim, but it was quietly lossy, and its "background" embedding was freezing the daemon.
get_episodepages through the full body.New settings
memory.embeddings.enabled(defaulttrue, envCODEOID_MEMORY_EMBEDDINGS): turn semantic recall off. The model is never downloaded or loaded, and recall uses full-text search only.memory.embeddings.maxWindows(default8): embedding windows per episode. Each costs about 45 ms of worker-thread CPU.Both appear in the Memory settings tab.
Not in this PR
docs/conductor-session-resolution.mdcovers it.storageStats()gives a size readout for a future UI.Testing
memory-lossless.test.ts.For agents: where things are
src/daemon/memory/lossless.ts:indexView(bounded index view),embedWindows(window placement).src/daemon/memory/store.ts: new tablesepisode_payloads(zstd body) andepisode_vectors(windows 1..n; window 0 stays inepisodes.embedding), plusgetFullContent,setWindowEmbeddingsandstorageStats. The vector matrix now has one row per window;idsrepeats per window.src/daemon/memory/engine.ts:ingestsplits body and view; the embed pump batches windows (32 texts per call); recall scores best window per episode (vectorScorespassed to the ranker);embeddingsEnabled/maxEmbedWindowsoptions.src/daemon/memory/embedder-worker.ts+WorkerEmbedderinembedder.ts: the off-thread model.src/daemon/compress/spill.ts: write/take/prune spill files. Theraw=<uuid>reference is anchored to the trailing hint line. Spills are pruned after 24 h at boot.src/daemon/memory/chunker.ts: the 64 KiB cap is gone; spilled raw output is swapped in.src/daemon/memory/tools.ts:get_episodetakesoffset/max_chars(default 64K, max 256K).🤖 Generated with Claude Code