You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Mental model refresh OOM-kills the container at scale: a SINGLE refresh = 17.7GB RSS on a 40K-node / 815K-link bank
Summary
On a single large bank (~40K memory nodes, ~815K links, 15.6K observations), onerefresh_mental_model operation deterministically OOM-kills the hindsight-api process. The kernel reports Out of memory: Killed process (hindsight-api) anon-rss: ~17.5GB within ~60-90s of the refresh starting. This happens with tick-triggered refreshes and with a single manually-triggered refresh in isolation. Container memory is unbounded (no --memory cap), swap fills (3.8/3.8Gi), and the container crash-loops because the worker re-claims the pending refresh op on every restart.
This is the memory-side of the problem in #3290 and the mechanism described in #3122, but I could not find the OOM-at-scale report filed anywhere, so here is the reproduction data.
Environment
Hindsight v0.9.0 (image ghcr.io/vectorize-io/hindsight:latest, built 2026-08-07)
Docker, no --memory limit, --shm-size=2g
LLM: DeepSeek v4-flash via API (HINDSIGHT_API_LLM_PROVIDER=deepseek), HINDSIGHT_API_LLM_MAX_CONCURRENT=64
Controlled test matrix (all single-refresh, nothing else queued)
Each test: container recreated clean (tick disabled, HINDSIGHT_API_MENTAL_MODEL_REFRESH_TICK_SECONDS=0), all prior refresh ops marked failed in pg0, exactly ONE refresh submitted via POST /v1/default/banks/{bank}/mental-models/{id}/refresh, memory sampled every 30s.
Test
Config
t+30s RSS
Peak RSS
Result
1
baseline (chunks on, default recall)
12.2 GiB
17.7 GiB anon
kernel OOM kill
2
HINDSIGHT_API_RECALL_INCLUDE_CHUNKS=false
12.3 GiB
17.5 GiB anon
kernel OOM kill
3
chunks off + HINDSIGHT_API_RECALL_MAX_TOKENS=512 (default 2048)
1.3 GiB
17.4 GiB anon
kernel OOM kill
Kernel log (same signature all three runs):
Out of memory: Killed process (hindsight-api) total-vm:33.7GB, anon-rss:17.5GB
Test 3 is the informative one: capping the recall text budget delayed the initial climb (1.3GiB vs 12GiB at t+30s) but the process still blew past 17GiB before the refresh could complete. That matches #3122's mechanism — the serialized tool-result envelope (id, chunk_id, document_id, context, entities, metadata, scores, tags, type per observation) is not bounded by the recall token budget, so on a bank with 815K links the graph-expanded recall + envelope serialization in the reflect agent loop accumulates until the kernel kills the process.
Also found: the refresh concurrency knob appears to be dead code in v0.9.0
HINDSIGHT_API_MENTAL_MODEL_REFRESH_CONCURRENCY (default 8, documented as "Max concurrent mental model refreshes") is read into config (config.py L533/L1126/L3480) but never referenced by any engine file — worker.py, maintenance.py, memory_engine.py don't use it. Setting it to 1 changed nothing: all 13 due refreshes still ran concurrently. If the intent is a working cap, it needs wiring; if it's meant to be removed, the docs should say so.
Impact
Any bank that grows past the point where a single refresh's serialized recall envelope fits in container memory will crash-loop whenever refreshes are due (all models sharing refresh_cron become due simultaneously, so the whole sweep fires at once).
This is not just an agent-memory edge case: the same operation that powers knowledge pages (same engine, same refresh behaviour per the FAQ) makes the daily refresh cycle fatal at scale.
Mental model refresh OOM-kills the container at scale: a SINGLE refresh = 17.7GB RSS on a 40K-node / 815K-link bank
Summary
On a single large bank (~40K memory nodes, ~815K links, 15.6K observations), one
refresh_mental_modeloperation deterministically OOM-kills thehindsight-apiprocess. The kernel reportsOut of memory: Killed process (hindsight-api) anon-rss: ~17.5GBwithin ~60-90s of the refresh starting. This happens with tick-triggered refreshes and with a single manually-triggered refresh in isolation. Container memory is unbounded (no--memorycap), swap fills (3.8/3.8Gi), and the container crash-loops because the worker re-claims the pending refresh op on every restart.This is the memory-side of the problem in #3290 and the mechanism described in #3122, but I could not find the OOM-at-scale report filed anywhere, so here is the reproduction data.
Environment
ghcr.io/vectorize-io/hindsight:latest, built 2026-08-07)--memorylimit,--shm-size=2gHINDSIGHT_API_LLM_PROVIDER=deepseek),HINDSIGHT_API_LLM_MAX_CONCURRENT=64hermes-main, 40,619 nodes (23,471 world / 1,500 experience / 15,648 observation), 815,676 links, 1,009 documentsrefresh_cron: "0 6 * * *", delta mode,refresh_after_consolidation: falseControlled test matrix (all single-refresh, nothing else queued)
Each test: container recreated clean (tick disabled,
HINDSIGHT_API_MENTAL_MODEL_REFRESH_TICK_SECONDS=0), all prior refresh ops marked failed in pg0, exactly ONE refresh submitted viaPOST /v1/default/banks/{bank}/mental-models/{id}/refresh, memory sampled every 30s.HINDSIGHT_API_RECALL_INCLUDE_CHUNKS=falseHINDSIGHT_API_RECALL_MAX_TOKENS=512(default 2048)Kernel log (same signature all three runs):
Test 3 is the informative one: capping the recall text budget delayed the initial climb (1.3GiB vs 12GiB at t+30s) but the process still blew past 17GiB before the refresh could complete. That matches #3122's mechanism — the serialized tool-result envelope (id, chunk_id, document_id, context, entities, metadata, scores, tags, type per observation) is not bounded by the recall token budget, so on a bank with 815K links the graph-expanded recall + envelope serialization in the reflect agent loop accumulates until the kernel kills the process.
Also found: the refresh concurrency knob appears to be dead code in v0.9.0
HINDSIGHT_API_MENTAL_MODEL_REFRESH_CONCURRENCY(default 8, documented as "Max concurrent mental model refreshes") is read into config (config.pyL533/L1126/L3480) but never referenced by any engine file — worker.py, maintenance.py, memory_engine.py don't use it. Setting it to 1 changed nothing: all 13 due refreshes still ran concurrently. If the intent is a working cap, it needs wiring; if it's meant to be removed, the docs should say so.Impact
refresh_cronbecome due simultaneously, so the whole sweep fires at once).--memorycap so the OOM killer restarts the container — which loses the refresh and crash-loops if the op re-claims).Suggested fixes (in priority order)
--memorycap band-aid on the perf page.Happy to provide more data — bank dumps, pg0 queries, full docker logs, or run a targeted repro against a build that includes a fix.