Skip to content

feat(memory_store): add pure-python local memory store implementation#111

Merged
jinliyl merged 3 commits intomainfrom
fix_fs
Feb 19, 2026
Merged

feat(memory_store): add pure-python local memory store implementation#111
jinliyl merged 3 commits intomainfrom
fix_fs

Conversation

@jinliyl
Copy link
Member

@jinliyl jinliyl commented Feb 19, 2026

No description provided.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jinliyl, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new pure-Python local memory store, providing a lightweight, in-memory solution with disk persistence for managing memory chunks and file metadata. Alongside this, a robust disk-based caching mechanism has been integrated into embedding models, which will drastically reduce redundant API calls and enhance overall performance. These changes offer greater flexibility in memory backend choices and optimize embedding generation processes.

Highlights

  • New Local Memory Store: Introduced a pure-Python, in-memory LocalMemoryStore with JSONL file persistence, offering a lightweight alternative to existing SQLite and ChromaDB backends for memory management.
  • Persistent Embedding Cache: Implemented disk-based caching for embedding models, allowing embeddings to be loaded from and saved to JSONL files, significantly reducing API calls and improving performance on repeated queries.
  • Default Memory Store Update: Updated the default memory store backend in cli.yaml and fs.yaml configurations to use the newly added local memory store.
  • Enhanced Resource Management: Added explicit logging for the closing of various service components (vector stores, memory stores, file watchers, LLMs, and embedding models) within the ServiceContext for better debugging and lifecycle management.
  • Improved Base Class Integration: Ensured that derived close and close_sync methods in embedding models and LLMs correctly call their super() counterparts, facilitating proper resource cleanup and cache persistence.
Changelog
  • reme/init.py
    • Updated the project version to 0.3.0.0b2.
  • reme/config/cli.yaml
    • Changed the default memory store backend from chroma to local.
  • reme/config/fs.yaml
    • Changed the default memory store backend from chroma to local.
  • reme/core/context/service_context.py
    • Added a cache_dir parameter to embedding model initialization, pointing to a dedicated directory for embedding caches.
    • Introduced logging for each service component (vector stores, memory stores, file watchers, LLMs, and embedding models) as they are closed.
  • reme/core/embedding/base_embedding_model.py
    • Imported json and Path modules for file operations.
    • Added cache_dir parameter to the constructor with a default value of '.reme' and reduced max_cache_size default.
    • Initialized cache_path and created the directory if it didn't exist.
    • Implemented _load_cache and _save_cache methods to persist and load embedding cache to/from a JSONL file.
    • Modified _get_cache_key to include model_name and dimensions for unique cache keys across different models.
    • Added logging for cache hits, including a preview of the text.
    • Called _save_cache in both close_sync and close methods to ensure cache persistence on shutdown.
  • reme/core/embedding/openai_embedding_model.py
    • Called super().close() to ensure base class cleanup and cache saving.
  • reme/core/embedding/openai_embedding_model_sync.py
    • Called super().close_sync() to ensure base class cleanup and cache saving.
  • reme/core/llm/openai_llm.py
    • Called super().close() to ensure base class cleanup.
  • reme/core/llm/openai_llm_sync.py
    • Called super().close_sync() to ensure base class cleanup.
  • reme/core/memory_store/init.py
    • Updated module docstring to include the new pure-Python local implementation.
    • Imported LocalMemoryStore.
    • Added LocalMemoryStore to the __all__ export list.
    • Registered LocalMemoryStore with the R.memory_stores registry under the 'local' key.
  • reme/core/memory_store/chroma_memory_store.py
    • Called super().close() to ensure base class cleanup.
  • reme/core/memory_store/local_memory_store.py
    • Added a new file implementing LocalMemoryStore, a pure-Python in-memory store with JSONL persistence.
    • Defined _ChunkRecord dataclass for internal chunk representation.
    • Implemented _load_chunks, _save_chunks, _load_metadata, and _save_metadata for disk persistence.
    • Provided start and close lifecycle methods to manage loading and saving data.
    • Implemented upsert_file, delete_file, delete_file_chunks, and upsert_chunks for write operations.
    • Implemented list_files, get_file_metadata, and get_file_chunks for read operations.
    • Implemented vector_search, keyword_search, and hybrid_search using pure Python logic (cosine similarity and substring matching).
    • Added _merge_hybrid_results static method for combining search results.
    • Implemented clear_all to remove all indexed data.
  • reme/core/memory_store/sqlite_memory_store.py
    • Called super().close() to ensure base class cleanup.
  • tests/test_embedding_cache.py
    • Imported shutil and tempfile for temporary directory management.
    • Wrapped embedding cache tests in try...finally blocks to create and clean up temporary directories for cache_dir.
  • tests/test_memory_store.py
    • Updated module docstring to include LocalMemoryStore in the test suite.
    • Imported ThreadPoolExecutor for managing synchronous operations in the new store.
    • Imported LocalMemoryStore.
    • Added LOCAL_DB_PATH and LOCAL_FTS_ENABLED to TestConfig.
    • Updated get_store_type to recognize LocalMemoryStore.
    • Modified create_memory_store to instantiate LocalMemoryStore with its specific configuration and a ThreadPoolExecutor.
    • Added assertions in test_start_store to verify LocalMemoryStore initialization.
    • Added cleanup logic for LocalMemoryStore's JSON persistence files and directories in cleanup_store.
    • Updated argument parsing in main to include a --local option and ensure LocalMemoryStore is included when testing all stores.
Activity
  • No specific activity (comments, reviews, or progress updates) were provided in the context for this pull request.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new pure-Python LocalMemoryStore, which is a great addition for setups without external dependencies. The implementation also adds a persistent on-disk cache for embeddings, which will significantly improve performance by reducing redundant API calls. The code is well-structured and the new functionality is integrated cleanly. I've left a few comments with suggestions to improve the performance and memory efficiency of the new LocalMemoryStore's persistence mechanism, fix some overly verbose logging, and correct a minor bug in the test cleanup logic. Overall, this is a solid contribution.

Comment on lines 219 to 220
await self._save_chunks()
await self._save_metadata()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Calling _save_chunks() and _save_metadata() on every write operation (like upsert_file) is inefficient as it rewrites the entire persistence files each time. This can cause significant performance degradation with a larger number of chunks. It's better to rely on the close() method to persist the final state. Please consider removing these immediate save calls from upsert_file, delete_file, delete_file_chunks, and upsert_chunks.

Comment on lines 61 to 106
async def _load_chunks(self) -> None:
"""Load chunks from JSONL file into memory."""
if not self._chunks_file.exists():
return
try:
data = await self._run_sync_in_executor(
self._chunks_file.read_text,
encoding="utf-8",
)
self._chunks = {}
for line in data.strip().split("\n"):
if not line:
continue
rec = json.loads(line)
chunk_id = rec["id"]
self._chunks[chunk_id] = _ChunkRecord(**rec)
logger.debug(f"Loaded {len(self._chunks)} chunks from {self._chunks_file}")
except Exception as e:
logger.warning(f"Failed to load chunks from {self._chunks_file}: {e}")

async def _save_chunks(self) -> None:
"""Persist chunks to JSONL file."""
try:
lines = []
for rec in self._chunks.values():
chunk_dict = {
"id": rec.id,
"path": rec.path,
"source": rec.source,
"start_line": rec.start_line,
"end_line": rec.end_line,
"text": rec.text,
"hash": rec.hash,
"embedding": rec.embedding,
"updated_at": rec.updated_at,
}
lines.append(json.dumps(chunk_dict, ensure_ascii=False))
data = "\n".join(lines)
await self._run_sync_in_executor(
self._chunks_file.write_text,
data,
encoding="utf-8",
)
logger.debug(f"Saved {len(self._chunks)} chunks to {self._chunks_file}")
except Exception as e:
logger.error(f"Failed to save chunks to {self._chunks_file}: {e}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _load_chunks and _save_chunks methods are not memory-efficient. _load_chunks reads the entire file into memory with read_text(), and _save_chunks builds the entire file content in memory before writing. For large stores, this can lead to high memory usage. Consider refactoring these to process the file line-by-line to improve scalability.

Comment on lines +435 to +443
logger.info("\n=== Vector Search Results ===")
for i, r in enumerate(vector_results[:10], 1):
snippet_preview = (r.snippet[:100] + "...") if len(r.snippet) > 100 else r.snippet
logger.info(f"{i}. Score: {r.score:.4f} | Snippet: {snippet_preview}")

logger.info("\n=== Keyword Search Results ===")
for i, r in enumerate(keyword_results[:10], 1):
snippet_preview = (r.snippet[:100] + "...") if len(r.snippet) > 100 else r.snippet
logger.info(f"{i}. Score: {r.score:.4f} | Snippet: {snippet_preview}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logging of search results and snippets at the INFO level can be very verbose and might expose sensitive content from user files in production logs. It's better to use DEBUG level for this kind of detailed diagnostic information. This also applies to the merged hybrid results logging further down.

Suggested change
logger.info("\n=== Vector Search Results ===")
for i, r in enumerate(vector_results[:10], 1):
snippet_preview = (r.snippet[:100] + "...") if len(r.snippet) > 100 else r.snippet
logger.info(f"{i}. Score: {r.score:.4f} | Snippet: {snippet_preview}")
logger.info("\n=== Keyword Search Results ===")
for i, r in enumerate(keyword_results[:10], 1):
snippet_preview = (r.snippet[:100] + "...") if len(r.snippet) > 100 else r.snippet
logger.info(f"{i}. Score: {r.score:.4f} | Snippet: {snippet_preview}")
logger.debug("\n=== Vector Search Results ===")
for i, r in enumerate(vector_results[:10], 1):
snippet_preview = (r.snippet[:100] + "...") if len(r.snippet) > 100 else r.snippet
logger.debug(f"{i}. Score: {r.score:.4f} | Snippet: {snippet_preview}")
logger.debug("\n=== Keyword Search Results ===")
for i, r in enumerate(keyword_results[:10], 1):
snippet_preview = (r.snippet[:100] + "...") if len(r.snippet) > 100 else r.snippet
logger.debug(f"{i}. Score: {r.score:.4f} | Snippet: {snippet_preview}")

if db_dir.exists():
shutil.rmtree(db_dir)
logger.info(f"✓ Cleaned up directory: {db_dir}")
for suffix in ("_chunks.json", "_file_metadata.json"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a small bug in the cleanup logic for LocalMemoryStore. The chunks are persisted to a file named _chunks.jsonl (JSON Lines format), but the cleanup code is looking for _chunks.json. This will cause the test to leave the chunks file behind.

Suggested change
for suffix in ("_chunks.json", "_file_metadata.json"):
for suffix in ("_chunks.jsonl", "_file_metadata.json"):

@jinliyl jinliyl merged commit b130cf3 into main Feb 19, 2026
2 checks passed
@jinliyl jinliyl deleted the fix_fs branch February 19, 2026 16:35
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.

1 participant

Comments