Gateway agent cache and resource lifecycle leaks
NousResearch/hermes-agent · cluster 10865 · agent
Data as of
3 open items. Fix merged in #11028. Check it covers your case.
The short answer
- Known?
- Yes. 21 related items grouped here, 6 issues already closed.
- Fix event
- A fix was merged to main in PR #11028. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #11028, #41975, #122925. That is separate from the fix event. - Still open
- 2 issues and 1 PR.
Next step: read #11028, then check whether your report is one of the 2 still open.
Read #11028Pick up a Lab taskA cluster of bug reports and fixes centered on memory and resource leaks in the gateway's agent cache, covering missing cleanup on eviction paths, unclosed aiohttp client sessions, and unreleased memory providers from temporary and cached AIAgent instances.
Open issues2
Open PRs1
- PR#64284fix: clean up evicted memory providers (salvage of #63703 + review fixes)
Fixes on record
-
#11028 fix(gateway): close temporary agents after one-off tasks
files:
gateway/run.py,tests/gateway/test_background_command.py,tests/gateway/test_compress_command.py,tests/gateway/test_flush_memory_stale_guard.py,tests/gateway/test_session_hygiene.py -
#41974 fix(gateway): release evicted agent clients to stop RSS leak (#29298)
files:
gateway/run.pyShow the diff
-
gateway/run.py@@ -12493,25 +12493,67 @@ async def _interrupt_and_clear_session( self._release_running_agent_state(session_key) def _evict_cached_agent(self, session_key: str) -> None: - """Remove a cached agent for a session (called on /new, /model, etc).""" + """Remove a cached agent for a session (called on /new, /model, etc). + + Pops the entry AND soft-releases the evicted agent's LLM client + pool so the httpx connection (sockets + held buffers) is freed + promptly rather than waiting on CPython GC — AIAgent holds + reference cycles (callbacks, tool state) that delay refcount + collection, so a manual release is required to keep gateway RSS + flat across many /new, /model, undo and reset operations (#29298, + same leak class as #25315). + + The release is soft (``release_clients()``): it frees the client + pool and per-turn child subagents but PRESERVES the session's + terminal sandbox, browser daemon, and tracked bg processes (keyed + on task_id), because the session may resume with a freshly-built + agent. Call sites that want a hard teardown (true conversation + boundaries like /new) already call ``_cleanup_agent_resources`` + before evicting; ``release_clients`` is idempotent and safe to + run again after that (the client is already None). + + Cleanup runs on a daemon thread so we never block holding + ``_agent_cache_lock`` on slow socket teardown — mirrors the + cap-enforcer and idle-sweeper paths. + """ _lock = getattr(self, "_agent_cache_lock", None) + evicted = None if _lock: with _lock: - entry = self._agent_cache.pop(session_key, None) - # Release clients on a daemon thread, same as _enforce_agent_cache_cap. - # Without this, every /new, /model, /reasoning, codex-runtime change, - # and /undo leaked a full agent: OpenAI client, httpx transport, SSL - # context, and conversation history. Only the /new path cleaned up - # first; the rest popped the entry and dropped it on the floor. - if entry is not None: - agent = entry[0] if isinstance(entry, tuple) and entry else None - if agent is not None: - threading.Thread( - target=self._release_evicted_agent_soft, - args=(agent,), - daemon=True, - name=f"agent-cache-cmd-evict-{session_key[:24]}", - ).start() + evicted = self._agent_cache.pop(session_key, None) + else: + _cache = getattr(self, "_agent_cache", None) + if _cache is not None: + evicted = _cache.pop(session_key, None) + + agent = evicted[0] if isinstance(evicted, tuple) and evicted else evicted + if agent is None or agent is _AGENT_PENDING_SENTINEL: + return + + # Don't tear down an agent that's actively mid-turn — its client, + # sandbox and child subagents are in use by the running request. + running_ids = { + id(a) + for a in getattr(self, "_running_agents", {}).values() + if a is not None and a is not _AGENT_PENDING_SENTINEL + } + if id(agent) in running_ids: + return + + try: + threading.Thread( + target=self._release_evicted_agent_soft, + args=(agent,), + daemon=True, + name=f"agent-evict-{str(session_key)[:24]}", + ).start() + except Exception: + # If we can't spawn a thread (interpreter shutdown), release + # inline as a best-effort fallback. + try: + self._release_evicted_agent_soft(agent) + except Exception: + pass @staticmethod def _init_cached_agent_for_turn(agent: Any, interrupt_depth: int) -> None:
-
-
#41975 fix(gateway): close residual memory-leak sites under heavy scheduled workload
files:
gateway/run.py,model_tools.py,tests/test_get_tool_definitions_cache_isolation.py -
#122925 fix(agent): close one-shot AIAgents on every exit path
files:
batch_runner.py,hermes_cli/cli_commands_mixin.py,plugins/platforms/feishu/feishu_comment.py,tests/test_agent_close_lifecycle.py,tui_gateway/methods_prompt.py
8 PRs closed without merging
From the close comments: superseded by another PR 4 · closed without comment 1 · done elsewhere 1 · merged via another change 1 · other 1
-
#10899 fix(gateway): close temporary agents after one-off tasks · superseded by another PR →
#11028,#10945 -
#10945 fix(gateway): clean up temporary agent resources · superseded by another PR →
#11028,#10899 -
#25318 fix(gateway): prevent memory leak from agent cache eviction and unbounded session data · merged via another change →
3d029a5,#41975 - #25332 fix: stop gateway memory leak on cached agent and session expiry · closed without comment
-
#25377 fix(gateway): release cached agents on eviction · done elsewhere →
3d029a5
Show 3 more
- #50383 fix(tui-gateway): close AIAgent instances to release resources (#50197) · other
-
#50403 fix(tui-gateway): close AIAgent instances to release resources (#50197) · superseded by another PR →
2fa2f42,94f3dbe,#122925,#50197 -
#64644 fix(gateway): shut down memory provider on soft cache eviction (#64278) · superseded by another PR →
#64284,#64278,#64644
Closed issues · 6
- Closed#10865Multiple AIAgent instances leak aiohttp client sessions
- Closed#19251Investigate residual gateway memory leak under heavy scheduled workload
- Closed#25315fix: gateway memory leak — _evict_cached_agent drops agents without cleanup, _session_messages never cleared
- Closed#48287[Bug]: Agent cache (_agent_cache) causes 50GB+ virtual memory leak in long-running gateway sessions
- Closed#50197Multiple AIAgent instances lack lifecycle cleanup (shutdown_memory_provider + close)
- Closed#58817[Bug]: Gateway memory grows ~5x over 20h on Linux — planned-restart masking leak, not fixing it
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.08: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab