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

2open issues
1open PR
21items grouped
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.

A 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

  • Issue#43657aiohttp ClientSession leak after auxiliary tasks (title_generation, etc.)
  • Issue#64278[Bug]: Gateway soft agent-cache eviction leaves memory providers running and leaks threads/HTTP clients

Open PRs1

  • PR#64284fix: clean up evicted memory providers (salvage of #63703 + review fixes)

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #11028 fix(gateway): close temporary agents after one-off tasks

    merged to main

    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)

    merged to main

    files: gateway/run.py

    Show 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

    merged to main

    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

    merged to main

    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.00 means 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

At a glance

Subsystem
agent
Merged PRs
#11028, #41974, #41975, #122925
Closed issues
6
Attempts
8 PRs closed without merging
Fix link checked
25 Sep 2026

For your agent

The same facts, typed.