Pre-submission checklist | 提交前检查
Bug Description | 问题描述
Plugin Version
@memtensor/memos-local-plugin v2.0.7
- Platform: Hermes Agent (Linux, 2-core 4GB)
Description
When the Hermes memory provider's initialize() is called (e.g. on each session start or reconnect), a new MemosBridgeClient (and thus a new --no-viewer Node.js bridge process) is spawned without closing the previous one. The old bridge process becomes an orphan, consuming ~93 MB of RAM each. Over time, these accumulate indefinitely.
The headless bridge reaper (_reap_stale_headless_bridges_locked) does NOT clean these up because their parent process is still alive — it only reaps bridges whose parent has died or are legacy entries older than HEADLESS_STALE_SECONDS.
Root Cause
File: plugins/memory/memtensor/__init__.py, initialize() (line ~296)
def initialize(self, session_id, **kwargs):
# ...
ensure_bridge_running()
new_bridge = MemosBridgeClient() # ← spawns new Node process
self._bridge = new_bridge # ← overwrites old reference, old bridge leaked
Compare with _reconnect_bridge() (line ~1727) which does it correctly:
def _reconnect_bridge(self, ...):
old_bridge = self._bridge
if old_bridge:
old_bridge.close() # ← correctly closes old bridge first
new_bridge = MemosBridgeClient()
Additionally: In bridge_client.py _reap_stale_headless_bridges_locked() (line ~206), the reaper only kills bridges when parent_dead or legacy_stale. Since the parent (Hermes dashboard or gateway) remains alive, none of the leaked bridges are ever reaped.
And: bridge.cjs (line ~57) intentionally skips the PID-file singleton guard for --no-viewer bridges, so there's no Node-side protection either.
How to Reproduce | 如何重现
- Run Hermes with
memory.provider: memtensor configured
- Start a
hermes dashboard or hermes gateway session
- Each session start / reconnect calls
initialize() and spawns a new bridge
- Check
ps aux | grep bridge.cjs — orphaned --no-viewer processes accumulate
Observed timeline from a live instance (dashboard PID 333413):
23:40:53 PID 335421 --no-viewer (initialize call #1)
23:40:57 PID 335577 --no-viewer (initialize call #2, 4s later)
23:42:15 PID 337303 --no-viewer (initialize call #3)
23:42:17 PID 337443 --no-viewer (initialize call #4, 2s later)
23:47:42 PID 342591 --no-viewer (initialize call #5)
23:47:45 PID 342675 --no-viewer (initialize call #6, 3s later)
6 orphaned bridges = ~558 MB wasted RAM on a 3.6 GB machine.
Suggested Fix
In initialize(), close the existing bridge before creating a new one:
def initialize(self, session_id, **kwargs):
# Close previous bridge if any
if self._bridge:
old_pid = getattr(self._bridge, "pid", "?")
logger.info("MemOS: closing previous bridge (pid=%s) before re-init", old_pid)
with contextlib.suppress(Exception):
self._bridge.close()
self._bridge = None
# ... rest of existing initialize() code ...
Optionally, also strengthen the reaper in bridge_client.py to deduplicate bridges under the same living parent (keep only the most recent one per parent).
Environment | 环境信息
- OS: Ubuntu 24.04.4 LTS (2-core, 4GB RAM)
- Node.js: v22.x
- Hermes Agent: latest (Telegram gateway + CLI dashboard)
- memos-local-plugin: v2.0.7 (installed via npm)
Additional Context | 其他信息
{
"bridges": [
{"pid": 335421, "parentPid": 333413, "startedAt": 1781538054.3, "agent": "hermes"},
{"pid": 335577, "parentPid": 333413, "startedAt": 1781538058.4, "agent": "hermes"},
{"pid": 337303, "parentPid": 333413, "startedAt": 1781538136.1, "agent": "hermes"},
{"pid": 337443, "parentPid": 333413, "startedAt": 1781538138.2, "agent": "hermes"},
{"pid": 342591, "parentPid": 333413, "startedAt": 1781538463.7, "agent": "hermes"},
{"pid": 342675, "parentPid": 333413, "startedAt": 1781538466.0, "agent": "hermes"},
{"pid": 359586, "parentPid": 355477, "startedAt": 1781539434.6, "agent": "hermes"}
]
}
Willingness to Implement | 实现意愿
Pre-submission checklist | 提交前检查
Bug Description | 问题描述
Plugin Version
@memtensor/memos-local-pluginv2.0.7Description
When the Hermes memory provider's
initialize()is called (e.g. on each session start or reconnect), a newMemosBridgeClient(and thus a new--no-viewerNode.js bridge process) is spawned without closing the previous one. The old bridge process becomes an orphan, consuming ~93 MB of RAM each. Over time, these accumulate indefinitely.The headless bridge reaper (
_reap_stale_headless_bridges_locked) does NOT clean these up because their parent process is still alive — it only reaps bridges whose parent has died or are legacy entries older thanHEADLESS_STALE_SECONDS.Root Cause
File:
plugins/memory/memtensor/__init__.py,initialize()(line ~296)Compare with
_reconnect_bridge()(line ~1727) which does it correctly:Additionally: In
bridge_client.py_reap_stale_headless_bridges_locked()(line ~206), the reaper only kills bridges whenparent_deadorlegacy_stale. Since the parent (Hermes dashboard or gateway) remains alive, none of the leaked bridges are ever reaped.And:
bridge.cjs(line ~57) intentionally skips the PID-file singleton guard for--no-viewerbridges, so there's no Node-side protection either.How to Reproduce | 如何重现
memory.provider: memtensorconfiguredhermes dashboardorhermes gatewaysessioninitialize()and spawns a new bridgeps aux | grep bridge.cjs— orphaned--no-viewerprocesses accumulateObserved timeline from a live instance (dashboard PID 333413):
6 orphaned bridges = ~558 MB wasted RAM on a 3.6 GB machine.
Suggested Fix
In
initialize(), close the existing bridge before creating a new one:Optionally, also strengthen the reaper in
bridge_client.pyto deduplicate bridges under the same living parent (keep only the most recent one per parent).Environment | 环境信息
Additional Context | 其他信息
{
"bridges": [
{"pid": 335421, "parentPid": 333413, "startedAt": 1781538054.3, "agent": "hermes"},
{"pid": 335577, "parentPid": 333413, "startedAt": 1781538058.4, "agent": "hermes"},
{"pid": 337303, "parentPid": 333413, "startedAt": 1781538136.1, "agent": "hermes"},
{"pid": 337443, "parentPid": 333413, "startedAt": 1781538138.2, "agent": "hermes"},
{"pid": 342591, "parentPid": 333413, "startedAt": 1781538463.7, "agent": "hermes"},
{"pid": 342675, "parentPid": 333413, "startedAt": 1781538466.0, "agent": "hermes"},
{"pid": 359586, "parentPid": 355477, "startedAt": 1781539434.6, "agent": "hermes"}
]
}
Willingness to Implement | 实现意愿