fix: persistent daemon to prevent zombie processes and enable multi-session access (closes #1229) - #1270
Conversation
…ession access (closes MemPalace#1229) Introduces a daemon + bridge architecture so all AI agent sessions share a single long-lived MemPalace process rather than each spawning their own MCP server. This eliminates two failure modes described in MemPalace#1229: 1. Zombie processes: SIGKILL bypasses Python atexit/trap cleanup, leaving stale PID files that block every subsequent session from connecting. The daemon outlives any individual session; only the bridge (a 60-line relay) dies with the session. 2. Concurrent ChromaDB writer corruption: multiple PersistentClient holders racing on the HNSW mmap files cause the sqlite metadata and in-memory index to diverge (see also MemPalace#1222). The daemon serialises all tools/call requests through a threading.Lock, giving a single-writer guarantee. Files added under examples/: - mempalace-daemon.py — persistent Unix socket MCP server (LaunchAgent target) - mempalace-bridge.py — lightweight stdio<->socket relay (MCP command per session) - com.mempalace.daemon.plist — macOS LaunchAgent template; auto-restarts on crash Docs added: - docs/multi-session-daemon.md — problem description, architecture, install steps, and MCP config examples for Claude Code, Codex, Gemini CLI, and generic clients. No changes to the core mempalace package or mcp_server.py — the daemon imports handle_request() directly, making this purely additive. Tested on macOS 14/15, Python 3.11/3.12, MemPalace 3.3.x with four concurrent sessions (Claude Code + Codex + Gemini CLI + GG). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Moved to v3.4 milestone — this is a feature-scale change (new long-running daemon process model + multi-session access pattern), not a maintenance fix. v3.3.5 is scoped to stability/bug fixes only. Companion issue #1229 moved with it. Continuing review here is welcome; we'll target landing this in the v3.4 cycle once v3.3.5 ships. |
|
Real-world testimony — running these 4 files unmodified on macOS (M-series, bash 3.2) since 2026-05-05 (9 days) alongside
The multi-session bridge is the load-bearing piece — without it, the two concurrent MCP server processes race exactly as #1229 describes. Not asking to bump the milestone, just adding a field signal that the design works end-to-end on a non-trivial palace. Happy to share logs / plist / anything else if it helps review. |
|
Heads-up for anyone following this PR: I've been running this exact daemon design in production (it's a great fix for the concurrent-writer HNSW corruption). But the peer-writer guard merged in 3.5.0 (#1818 / #1823) holds |
|
Hi @Vasanth19 — I’ve reworked #1976 so it complements this PR rather than introducing a competing standalone daemon. The revised implementation keeps #1270’s daemon + bridge architecture, but uses the existing package-level
I removed the separate There is still rollout work before the full Tier 3 goal is complete: hook and CLI workflows need to consistently use daemon-backed execution rather than direct writer paths. I’d appreciate your feedback on whether this division makes sense and whether any parts should be moved into, or coordinated more closely with, #1270. |
|
Follow-up to my earlier comment: the package-integration and writer-routing work discussed there has now been implemented as a staged PR stack. Current pieces:
The routing PRs intentionally preserve the current The stack is currently: All of these are intended to complement the daemon + bridge direction proposed in #1270, not to erase its design history or compete with it. I’m leaving the branches unchanged while maintainers decide which architectural direction and merge sequence they prefer. |
…#1270 backport)
- hooks/mempal_save_hook.sh, hooks/mempal_precompact_hook.sh: shebang
/opt/homebrew/bin/bash (macOS bash 3.2 lacks mapfile) + SAVE_INTERVAL=25
- docs/multi-session-daemon.md, examples/com.mempalace.daemon.plist,
examples/mempalace-{bridge,daemon}.py: anticipated backport of upstream
PR MemPalace#1270 (persistent daemon + bridge), still open
Preserved before merging upstream/develop (1077 commits ahead), per
Proyectos/Taiko/MemPalace/18-Estrategia-Upgrade-v3.7.0.md (Pista B).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Problem
Closes #1229.
The current
mempalace-mcp/mempalace.mcp_servermodel runs one Python process per agent session. In practice, developers run Claude Code, Codex, Gemini CLI, and GG simultaneously. This exposes two compounding failure modes.Failure 1 — Zombie processes after SIGKILL
MCP hosts sometimes force-quit sessions with
SIGKILL. Python'satexitandsignal.signal(SIGTERM)cleanup never fires onSIGKILL, so the process exits without releasing the PID file. The next session sees a stale PID file, decides another instance is running, and refuses to start. Every MCP tool call returns "Connection closed". Manual PID-file removal is the only recovery — until it happens again.Failure 2 — Concurrent ChromaDB writers corrupt HNSW
When multiple sessions each hold an open
PersistentClientagainst the samechroma.sqlite3and simultaneously callupsert(), the writes interleave at the mmap level. The in-memory HNSW tree and on-disk sqlite metadata diverge — exactly the divergence that #1222 detects but cannot prevent. The only safe fix is a single process owning the ChromaDB connection.Solution — Daemon + Bridge Architecture
tools/callrequests are serialized through athreading.Lockinside the daemon. Protocol messages (initialize,tools/list,ping) remain lock-free.ThrottleIntervalseconds (default: 5).Changes
Four files added — no changes to the core
mempalacepackage:examples/mempalace-daemon.pyexamples/mempalace-bridge.pyexamples/com.mempalace.daemon.plistlaunchctl load)docs/multi-session-daemon.mdTest Plan
mempalace-daemon.pystarts and creates~/.mempalace/mcp.sockmempalace-bridge.pyconnects and relaystools/list/mempalace_statuscorrectlymempalace_add_drawercalls succeed without HNSW divergencemcp_server.pybehavior for users not using the daemonTested on
🤖 Generated with Claude Code