Repository navigation
fix: memory system bugs + end-to-end validation suite - #14
Merged
Merged
Conversation
- Reuse one Claude CLI client per chat instead of spawning one per turn - Resume conversations after restart (sessions.json), fresh session on failure - Replace the rolling history window with native sessions; /new resets - Static system prompt; time and memory context move into the user message - Load no ~/.claude settings/plugins/MCP servers, no built-in tools, dontAsk - Telegram /model and /effort, persisted to ~/.memclaw/.env - Background consolidation after the reply - Default to claude-sonnet-5-5 at medium effort; bump claude-agent-sdk
…n suite Search: build a safe OR-ed FTS5 query (punctuation no longer kills keyword search), fix inverted BM25 normalization, invalidate the embedding matrix cache on any re-index, apply file_filter before truncation. Index: skip AGENTS.md and hidden dirs, drop chunks of emptied files, embed before swapping chunks, re-sync on any mtime change, one chunk per entry without the previous entry's heading, count images from all platforms. Consolidation: never mark today's file done, only mark files that fit the budget, keep permanent memories saved while it runs, strip code fences, back up MEMORY.md. Agent/tools: serialize turns so concurrent chats don't share tool state, survive OpenAI outages in context search and startup sync, report saves as saved when only indexing failed, scope image_search to the platform, block file tools from Memclaw's internal files, index .md files from file_write. Tests: offline fake embedder for unit tests; tests/e2e runs against the real OpenAI and Claude APIs in a temp vault (MEMCLAW_E2E=1).
…gged daily
The old prompt replaced saved credentials with placeholders ("see backup")
to meet a 5,000-char target, had no notion of today's date, and mixed up
facts between people. Rewritten with explicit rules for exact values,
corrections, attribution and what to leave out; told today's date; budget
raised to 80k chars. memory_save(permanent=true) now also appends to the
daily file so MEMORY.md can always be rebuilt from the daily log.
Consolidation now writes a holistic profile and an index of references (→ memory/YYYY-MM-DD.md) instead of copying every detail; secrets and document numbers stay in the notes. References are checked before saving (missing notes dropped, unconfirmed ones logged), the consolidation input lists the vault's other notes, and the system prompt tells the agent to open a referenced note with file_read when it needs a detail.
Consolidation (the only one-shot call) gets its own model and effort, CLAUDE_CONSOLIDATION_MODEL / CLAUDE_CONSOLIDATION_EFFORT, independent of the chat model. Shown in memclaw status.
memory_save loses its permanent option (and memclaw save --permanent): the agent only writes today's notes and consolidation is MEMORY.md's single writer. consolidation_threshold defaults to 1, so it runs after the first reply once a day with notes is over. Each consolidation keeps a dated copy of the previous MEMORY.md in .history/ (last 60) instead of MEMORY.md.bak.
- file_write refuses MEMORY.md and the daily notes (read stays allowed): consolidation is MEMORY.md's only writer and the daily notes are the append-only record it is rebuilt from - consolidation effort defaults only on the default model, as for chat - a failed consolidation is retried an hour later, not after every reply - image_save registers the image before writing its note, so a failed embedding doesn't leave a note behind that a retry duplicates - file_write indexes under the path the index sync uses
image_search now returns the closest images with ids and descriptions and sends nothing; the agent calls the new image_send with the ids that match the request. A similarity threshold can't tell a passport from an ID card when both mention the user's name, and two searches (Serbian + English) sent the passport twice. image_send queues each image at most once per reply.
- /new forgets the session before awaiting the backend reset, so a message sent right after it can't resume the old session - /model refuses to switch when the model list can't be loaded, instead of saving an unchecked (possibly mistyped) id that breaks every turn - the fallback cost estimate includes cache-write tokens - long-id model buttons carry a hash of the id instead of a list position, so they can't pick a different model if the list changed
- drain the bridge's stderr in chunks: readline() raised on a line over 64 KiB, ending the drain, after which a full pipe would block the bridge - a timeout no longer counts as a dead bridge, so one slow run doesn't drop the shared bridge and abort other chats' runs - relaunch a bridge that exited between calls instead of failing the next message (or /model) first - shutdown iterates over a copy of the sessions, so a message arriving mid-shutdown can't skip closing the bridge and MCP server
# Conflicts: # tests/test_agent.py
The language-learning note sat at 90 days old, where temporal decay keeps about 12% of its score, so a vague query put it right at the top-3 edge and the test flipped with small embedding changes (e.g. weekday headers).
- one builder for the locked-down CLI options, shared by chat sessions and one-shots, so the lockdown can't drift between them - turn cost computed in _turn_cost, out of run_turn's connection handling - agent.handle's logging, pending-note acknowledgement and session saving pulled into named helpers - clear the repo's ruff findings (unused imports, ambiguous names)
- model/effort state, model-list lookups, the strict model check and saving to ~/.memclaw/.env move into ModelSelectionMixin, used by both the Claude and Cursor backends. Cursor's /model now also refuses an id it can't check when the model list doesn't load. - Cursor run_turn: _ensure_agent says whether the agent was already open, resumed or created (replacing two flags), and _send does the send
# Conflicts: # memclaw/agent.py # memclaw/backends/claude.py # memclaw/index.py # tests/conftest.py
Reading the consolidation marker, picking the daily files within the budget, building the prompt and replacing MEMORY.md (with the race check and history copy) become their own methods; meta.json is read in one place. 108 lines → 60, behaviour unchanged.
The Cursor backend is written and checked against the 0.1.x SDK; CI installed 1.0.38 (>=0.1.0 allowed it), which no longer exports UnknownAgentError. Pin to <0.2 so a major SDK upgrade is a deliberate change.
0.1.4 calls it UnknownAgentError, 0.1.9 AgentNotFoundError (both CursorAgentError subclasses, which is what the backend catches). The suite passes on both versions.
emanuilo
changed the base branch from
feat/claude-persistent-sessions
to
main
October 18, 2026 17:15
# Conflicts: # docs/memory-architecture.md
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.
Summary
I reviewed the whole memory path (store → index → search → context → consolidation → tools), fixed the bugs listed below, and added an end-to-end suite that runs against the real OpenAI and Claude APIs in a throwaway vault.
MEMORY.md is redesigned as a profile plus an index. The old prompt asked for under 5,000 characters, had no date, and said "remove outdated entries". On real data it swapped saved credentials for placeholders ("see prior backup") and gave the user's birthday to their partner. Copying everything verbatim instead turned MEMORY.md into a 14 KB dump of the daily notes.
Other fixes:
*.mdminus AGENTS.md and hidden dirs## HH:MMheading---ends a chunk with no carry-overmtime !=chat_id/ found images (reminder to wrong chat)memory_savereported an error after saving if indexing failed (agent retries → duplicates)image_searchreturned other platforms' images (Telegram can't send a WhatsApp ref)file_write/file_readcould hitmemclaw.db,.env,sessions.json,.cursor/hooks.mdfiles fromfile_writeweren't searchable until a syncplatform_imagesimage_searchonly lists candidates; the agent picks withimage_send; each image sent once per replyimage_savereported an error after writing the note when embedding failed (retry → duplicate)file_writecould replace MEMORY.md or a daily noteCLAUDE_CONSOLIDATION_MODEL, even one without effortCode structure:
_maybe_consolidate(108 → 60 lines) reads as its steps:_read_consolidated_through,_daily_text(files within the budget),_consolidation_message,_replace_memory(race check, history copy, write),_write_consolidated_through.Validation:
Evidence
Before (E2E on the parent commit, real APIs): 12 of 17 failed
After:
20 passed(claude-haiku-5-5) +376 passed, 20 skippedunit tests, ruff clean.test_consolidation_prompt_qualityfailed once in ~10 runs on Haiku (production consolidation uses Opus).Image retrieval (production log, "Daj mi sliku pasosa"):
Consolidation prompt (real model):
Production rebuild (77 daily files, Opus 5.5):
Merge Danger
Door: two-way
Revert restores the old behavior. Data written in the meantime stays compatible: same DB schema, same file formats.
.history/andcredentials.mdare just extra files.Blast Radius: memory
memory_saveno longer takespermanent, andmemclaw save --permanentis gone. An old session that still passes the flag gets a normal daily save.feat/claude-persistent-sessions). Merge that first. feat: native Cursor conversations, /model and /effort for Cursor #13 conflicts with this PR only indocs/memory-architecture.md.🤖 Generated with Claude Code