feat(gateway): periodic memory trim in idle reaper for long-lived processes - #64591
feat(gateway): periodic memory trim in idle reaper for long-lived processes#64591aider4ryder wants to merge 2 commits into
Conversation
…cesses The idle session reaper runs every 5 minutes but only called malloc_trim when a session was actually reaped. Long-lived gateway processes (days/weeks) accumulated unreleased RSS because: 1. Python gen2 GC rarely triggers under steady-state allocation 2. glibc retains freed heap pages as RSS until explicitly trimmed 3. Active sessions that never hit the idle TTL never triggered trim Add trim_memory() at the end of _reap_idle_sessions() so every 5-min scan releases releasable pages regardless of session reap activity. Also includes the carried mem_trim infrastructure (PR NousResearch#63708): - hermes_cli/mem_trim.py: config-driven malloc_trim(0) helper - tui_gateway/server.py: post-turn trim in _run_prompt_submit finally - run_agent.py: force trim on agent close - context.memory_trim config schema (enabled, cooldown_seconds) Tests: 8 mem_trim unit tests + new periodic trim integration test + 324 gateway tests pass with no regressions.
teknium1
left a comment
There was a problem hiding this comment.
Thanks for extending the allocator-trim work with the idle-reaper boundary. The current implementation confirms the reaper runs every 300 seconds but has no trim on current main (tui_gateway/server.py:847,875-881,953-966).
Problems
- The new periodic call reads
context.memory_trimon the daemon reaper thread. In remote multi-profile mode, profile selection is a request/turnContextVar(tui_gateway/server.py:995-1038,8985-8987), while the reaper starts at module initialization (tui_gateway/server.py:953-966). Since the helper callsload_config(), periodic enabled/cooldown values resolve to the launch profile, not a remotely served session profile. The added tests cover turn cleanup but not this periodic configuration path. - The PR is conflicting and contains the open allocator-trim implementation plus an unrelated text-verbosity commit. The final memory commit itself is limited to six files (
97c5008445fb).
Suggested changes
- Define the intended process-wide profile/config behavior and add a regression test for the periodic reaper under remote-profile mode.
- Salvage the focused memory-trim commit only.
Automated hermes-sweeper review.
| # returns releasable pages, preventing unbounded RSS growth over days/weeks. | ||
| try: | ||
| from hermes_cli.mem_trim import trim_memory | ||
|
|
There was a problem hiding this comment.
trim_memory() reads load_config() here, but this daemon reaper has no per-session profile ContextVar. In desktop remote-profile mode, turns bind a profile only in their own thread, so this periodic call always uses the launch profile's context.memory_trim settings. Please define the intended process-wide profile policy and cover this path with a remote-profile test.
…gateway/slash_worker housekeeper + forced-trim logging (PR NousResearch#63708 + NousResearch#64591 enhanced, CSA tier-4 reviewed)
…gateway/slash_worker housekeeper + forced-trim logging (PR NousResearch#63708 + NousResearch#64591 enhanced, CSA tier-4 reviewed)
…gateway/slash_worker housekeeper + forced-trim logging (PR NousResearch#63708 + NousResearch#64591 enhanced, CSA tier-4 reviewed)
|
Thanks @aider4ryder for kicking off the memory-trim work here! Closing this in favor of your own #66355, which supersedes it with the config-driven version — configurable cooldown, RSS telemetry, and broader lifecycle coverage (gateway housekeeping, slash_worker, agent close) plus unit tests. This branch also picked up an unrelated text-verbosity commit, so consolidating on the clean #66355 keeps review simple. Let's continue the trim discussion over there. |
…coverage Add config-driven glibc malloc_trim for long-lived Hermes processes: - hermes_cli/mem_trim.py: trim_memory() with configurable cooldown, RSS snapshot telemetry, and forced-trim INFO logging - gateway/run.py: periodic trim in gateway housekeeping loop - tui_gateway/server.py: trim in idle reaper (~every 5 min) - tui_gateway/slash_worker.py: trim on turn boundary - run_agent.py: force trim on agent close - hermes_cli/config.py: context.memory_trim config section (enabled, cooldown_seconds, log_every_n, info_log_min_delta_mb) CSA tier-4 reviewed (4 rounds, 0 HIGH/MEDIUM/CRITICAL remaining). Supersedes PR #63708 + #64591 with enhanced telemetry and gateway/slash_worker coverage.
…coverage Add config-driven glibc malloc_trim for long-lived Hermes processes: - hermes_cli/mem_trim.py: trim_memory() with configurable cooldown, RSS snapshot telemetry, and forced-trim INFO logging - gateway/run.py: periodic trim in gateway housekeeping loop - tui_gateway/server.py: trim in idle reaper (~every 5 min) - tui_gateway/slash_worker.py: trim on turn boundary - run_agent.py: force trim on agent close - hermes_cli/config.py: context.memory_trim config section (enabled, cooldown_seconds, log_every_n, info_log_min_delta_mb) CSA tier-4 reviewed (4 rounds, 0 HIGH/MEDIUM/CRITICAL remaining). Supersedes PR NousResearch#63708 + NousResearch#64591 with enhanced telemetry and gateway/slash_worker coverage.
Problem
Long-lived gateway processes (days/weeks without restart) accumulate high RSS even though the memory trim infrastructure from #63708 is deployed. Root cause:
trim_memory()is only called at turn completion and agent close, but the idle reaper thread (running every 5 min) never triggers it unless a session is actually reaped.For active sessions that never hit the 6h idle TTL,
gc.collect()+malloc_trim(0)are never invoked, so glibc retains freed heap pages as RSS indefinitely.Solution
Add
trim_memory(reason="idle reaper periodic trim")at the end of_reap_idle_sessions()so every reaper scan (default 300s) releases releasable pages regardless of whether any session was reaped.This builds on the carried #63708 infrastructure:
hermes_cli/mem_trim.py: config-drivenmalloc_trim(0)with cooldown and kill-switch_run_prompt_submitfinally blockAIAgent.close()context.memory_trimconfig schema (enabled,cooldown_seconds)Observability
Before (gateway running 28h, PID 115324):
Expected after: periodic trim every 5 min should keep RSS bounded. The 60s cooldown in
trim_memoryprevents redundant trims when turn-completion and reaper fire close together.Testing
test_reap_idle_sessions_calls_periodic_trim— verifies trim fires even with zero reaped sessionsPYTHONPATH=. pytest tests/hermes_cli/test_mem_trim.py tests/test_tui_gateway_server.py -x -q # 10 passed, 324 passed