fix: add limit=10000 safety cap to all unbounded ChromaDB .get() calls#137
fix: add limit=10000 safety cap to all unbounded ChromaDB .get() calls#137bensig merged 3 commits intoMemPalace:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses OOM/memory-pressure risks caused by unbounded ChromaDB collection.get() calls by introducing a conservative safety cap (limit=10000) in read-heavy endpoints, and updates surrounding project/testing infrastructure included in the diff.
Changes:
- Add
limit=10000to several metadata/document retrieval paths in MCP tools and Layer1 generation to prevent unbounded in-memory loads. - Add/expand pytest coverage for MCP server handlers, the KnowledgeGraph, Dialect, and the
search_memoriesAPI (via shared fixtures). - Update packaging/tooling configuration (pyproject build backend + dev dependency declarations) and remove
requirements.txt.
Reviewed changes
Copilot reviewed 9 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
mempalace/mcp_server.py |
Caps metadata fetches used for status/list/taxonomy handlers to avoid unbounded loads. |
mempalace/layers.py |
Caps Layer1 drawer retrieval to prevent loading the entire collection into memory. |
tests/conftest.py |
Adds shared fixtures and HOME isolation to keep tests hermetic. |
tests/test_mcp_server.py |
Adds direct tool-handler + dispatch-layer coverage for MCP server behaviors. |
tests/test_searcher.py |
Adds coverage for the programmatic search_memories API. |
tests/test_knowledge_graph.py |
Adds CRUD + temporal query/timeline/stats coverage for KnowledgeGraph. |
tests/test_dialect.py |
Adds coverage for Dialect compression, detection, encoding/decoding, and stats. |
pyproject.toml |
Updates build-system/tooling configuration and dev dependency declarations. |
requirements.txt |
Removed in favor of pyproject/lock-based dependency management. |
mempalace/__init__.py |
Updates __version__ to 3.0.0. |
Comments suppressed due to low confidence (1)
mempalace/mcp_server.py:75
- With the new
limit=10000,total_drawerscan exceed the summed counts inwings/rooms(since those are computed from only the first 10k metadatas). To avoid silently returning internally inconsistent stats, consider adding an explicit truncation indicator (e.g.,sampled_drawers,sample_limit,truncated: count > sampled_drawers) so clients/agents know the breakdown is approximate when the cap is hit.
count = col.count()
wings = {}
rooms = {}
try:
all_meta = col.get(include=["metadatas"], limit=10000)["metadatas"]
for m in all_meta:
w = m.get("wing", "unknown")
r = m.get("room", "unknown")
wings[w] = wings.get(w, 0) + 1
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def tool_get_taxonomy(): | ||
| col = _get_collection() | ||
| if not col: | ||
| return _no_palace() | ||
| taxonomy = {} | ||
| try: | ||
| all_meta = col.get(include=["metadatas"])["metadatas"] | ||
| all_meta = col.get(include=["metadatas"], limit=10000)["metadatas"] | ||
| for m in all_meta: |
There was a problem hiding this comment.
This PR aims to cap unbounded col.get() calls, but tool_diary_read in this same module still calls col.get(..., include=["documents","metadatas"]) without any limit, then sorts/slices in Python. That path can still load an unbounded number of documents/metadatas into memory and trigger the same OOM risk. Consider adding a safety cap there as well (e.g., limit=min(max(last_n, 1000), 10000) plus a truncation flag/message when capped).
| return "## L1 — No palace found. Run: mempalace mine <dir>" | ||
|
|
||
| # Fetch all drawers (with optional wing filter) | ||
| kwargs = {"include": ["documents", "metadatas"]} | ||
| kwargs = {"include": ["documents", "metadatas"], "limit": 10000} | ||
| if self.wing: | ||
| kwargs["where"] = {"wing": self.wing} | ||
|
|
There was a problem hiding this comment.
The comment says "Fetch all drawers" but the query is now capped (limit=10000). Updating the comment to reflect the safety cap (and that L1 summaries may be based on a sample when the palace is larger) would prevent documentation drift and confusion during future maintenance.
| [project.optional-dependencies] | ||
| dev = ["pytest>=7.0", "build>=1.0", "twine>=4.0"] | ||
| dev = ["pytest>=7.0", "ruff>=0.4.0"] | ||
|
|
||
| [dependency-groups] | ||
| dev = ["pytest>=7.0", "ruff>=0.4.0"] | ||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["mempalace"] |
There was a problem hiding this comment.
The PR title/description focuses on adding a limit=10000 safety cap, but this diff also changes packaging/tooling (switches build backend to Hatchling, adds dependency-groups, and removes setuptools config). If these packaging changes are intended, the PR description should call them out explicitly (or consider splitting into a separate PR) to reduce review/merge risk for what is otherwise a targeted runtime fix.
|
Hey Igor — CI is failing, looks like it needs a rebase on main (we've merged a bunch of changes recently). Can you rebase and push? |
Prevents OOM when the palace grows large. The following unbounded metadata fetches now have a safety cap: - tool_status: col.get(include=['metadatas'], limit=10000) - tool_list_wings: same - tool_list_rooms: same (including wing-filtered variant) - tool_get_taxonomy: same - Layer1.generate: col.get(include=['documents','metadatas'], limit=10000) Layer2 already had a limit parameter — no change needed. Finding: MemPalace#3 (CRITICAL — unbounded data fetching causes OOM) Includes test infrastructure from PR MemPalace#131. 92 tests pass.
9123379 to
161a0d1
Compare
161a0d1 to
45c2c92
Compare
…nused fixture param
Problem
Several tool handlers call
col.get(include=["metadatas"])without a limit. As the palace grows, these calls load all metadata into memory at once. With sufficiently large palaces, this causes:Finding:
CRITICAL — unbounded data fetching causes OOM
Fix
Added
limit=10000to all unboundedcol.get()calls:tool_statuscol.get(include=["metadatas"], limit=10000)tool_list_wingstool_list_roomstool_get_taxonomytool_diary_readLayer1.generate{"include": ["documents", "metadatas"], "limit": 10000}Layer2 already had a
limitparam — no change needed.10,000 is a conservative cap that covers typical palace sizes while preventing catastrophic memory usage. For palaces exceeding this, the counts will be approximate (reporting on the first 10K drawers).
Packaging changes (carried over from base)
This branch also includes the
setuptools→hatchlingbuild backend migration and[dependency-groups]adoption from the test infrastructure work. These are inherited from the shared base and not new to this PR.92 tests pass (includes test infrastructure from PR #131).