Skip to content

fix(_memory): make interrupted reindex resumable, verified by model identity - #7

Closed
omar-nahhas wants to merge 2 commits into
readyfrom
fix/memory-rebuild-resume-model-check
Closed

fix(_memory): make interrupted reindex resumable, verified by model identity#7
omar-nahhas wants to merge 2 commits into
readyfrom
fix/memory-rebuild-resume-model-check

Conversation

@omar-nahhas

Copy link
Copy Markdown

Summary

Fixes a real production issue hit today: a memory reindex (triggered when the configured embedding model changes) embedded every document in a single pass and only persisted the result at the very end. Interrupting that pass — a crash, a pod restart, a slow/shared embeddings backend timing out mid-batch — threw away every already-embedded document, forcing the next attempt to start over from zero. Against a slow or shared embeddings backend, this cost hours of redone work on every interruption.

  • Reindexing now embeds in batches (Memory._REBUILD_BATCH_SIZE, 20) and checkpoints to disk after each batch (index.rebuilding.faiss/.pkl), so an interrupted rebuild resumes where it left off.
  • The checkpoint is tagged with the embedding model it was built under (index.rebuilding.json) and is only trusted if that matches the model we're about to (re)index with. If the target model changed again while a rebuild was interrupted, the stale checkpoint is discarded and a fresh rebuild starts — otherwise resuming would add new-model vectors into an old-model FAISS index, failing a dimension assertion on the next batch.
  • On successful completion the checkpoint files are cleaned up.

Test plan

  • New tests/test_memory_rebuild_resume.py — 9 tests covering the checkpoint save/load/clear helpers directly, plus Memory.initialize() end-to-end for both the resume-under-same-model and discard-under-changed-model cases. All pass.
  • Ran the broader suite (excluding two pre-existing, unrelated failures already present on main: email_parser_test.py has a stale import, rate_limiter_test.py needs live OpenRouter credentials) — no regressions introduced by this change.

🤖 Generated with Claude Code

…dentity

Memory.initialize() used to embed every document in one pass when a
reindex was needed (embedding model changed) and only persist the result
at the very end. Interrupting that pass -- a crash, a restart, a slow or
shared embeddings backend timing out mid-batch -- threw away every
already-embedded document; the next attempt started over from zero. On a
large memory store against a slow backend this can cost hours of redone
work every time the process is interrupted.

Reindexing now embeds in batches (Memory._REBUILD_BATCH_SIZE) and
checkpoints to disk after each one (index.rebuilding.faiss/.pkl), so an
interrupted rebuild resumes from where it left off instead of restarting.

The checkpoint is not trusted blindly: it's tagged with the embedding
model it was built under (index.rebuilding.json), and a resume is only
used if that matches the model we're about to (re)index with. If the
target model changed again while a rebuild was interrupted, the stale
checkpoint is discarded and a fresh rebuild starts -- otherwise resuming
would add new-model vectors into an old-model FAISS index, which fails a
dimension assertion on the very next batch.

Adds tests/test_memory_rebuild_resume.py covering the checkpoint
save/load/clear helpers directly and Memory.initialize() end-to-end for
both the resume-under-same-model and discard-under-changed-model cases.
…e ordering

Two gaps flagged by review on the resumable-reindex fix:

1. The rebuild checkpoint's model-identity check only compared
   provider/name. A kwarg that changes the embedder's actual output (e.g.
   an OpenAI-style `dimensions` override, or an api_base pointed at a
   different backend) could change while provider/name stayed the same,
   letting a checkpoint built under the old kwargs get resumed into under
   the new ones. The signature now includes build_kwargs() (minus api_key,
   which is deliberately excluded since it's persisted to disk in the
   memory dir and a credential rotation alone isn't "a different model").

2. The rebuild checkpoint was cleared right after the batch loop, before
   the final index/embedding.json were durably written. A crash in that
   gap left neither a valid final index nor a resumable checkpoint,
   reintroducing the full re-embed-from-scratch failure mode the checkpoint
   exists to prevent. Clearing now happens strictly after the final save
   succeeds.

Adds 3 tests: api_key exclusion, kwarg-change rejection, and
checkpoint-survives-a-crash-during-final-save.
@omar-nahhas

Copy link
Copy Markdown
Author

Closing — superseded by the equivalent PR against the true upstream (agent0ai#1792) and the merged fix in our private fork (NuevaNext/agent-zero#88, agent0ai#89).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants