feat: single-instance daemon for shared hyperd across MCP clients#26
Merged
StefanSteiner merged 3 commits intoMay 25, 2026
Merged
Conversation
A lightweight daemon manages one shared hyperd process per user so
multiple AI clients (Claude Code, Cursor, VS Code, etc.) can access the
same persistent databases simultaneously with reduced resource overhead.
Architecture:
- TCP port binding as cross-platform single-instance lock
- Discovery file at ~/.hyperdb/daemon.json (overridable via HYPERDB_STATE_DIR)
- Health protocol (PING/HEARTBEAT/STOP/STATUS) for liveness and idle tracking
- Auto-spawn: MCP clients transparently start the daemon if none is running
- Idle timeout (default 30 min) with heartbeat-based keep-alive
- Ephemeral databases DETACH + delete on session end (Windows-safe)
- --no-daemon flag to opt out and use legacy per-client hyperd
New files:
- hyperdb-mcp/src/daemon/{mod,discovery,health,run,spawn}.rs
- hyperdb-mcp/tests/daemon_tests.rs (26 tests: unit + integration)
The single-instance daemon shipped in the previous commit but the docs still framed persistent workspaces as "experimental, one session at a time" and listed the shared daemon as future work. - README: new "Operating Modes" section covering the two independent dimensions (engine: shared daemon vs --no-daemon; persistence: ephemeral vs --workspace); CLI Reference expanded with the daemon subcommand and HYPERDB_* env vars. - DEVELOPMENT.md: new "Daemon Mode Internals" section. - ROADMAP.md: drops the "Shared hyperd daemon" entry since it shipped. - CHANGELOG.md: unreleased entry summarizing the feature.
Adds liveness monitoring and automatic restart for the daemon's hyperd process. Previously, if hyperd crashed while the daemon kept running, clients would silently fail to connect to a stale endpoint. Detection: - New HyperProcess::has_exited() uses Child::try_wait() — correct on both Unix and Windows, reaps zombies as a side effect. Replaces the Windows-broken kill -0 / always-true heuristic that is_running used. - Daemon polls every 5 seconds. - Clients fast-path the signal via a new REPORT_HYPERD_ERROR health command when they detect a dead endpoint before the polling tick. Restart: - try_restart_hyperd drops old hyperd, spawns replacement with same parameters, atomically rewrites daemon.json with the new endpoint. - STATUS reads stay non-blocking — endpoint info is shared via Arc<Mutex<DaemonInfo>>, separate from the HyperProcess mutex. - Rate-limited to 3 restarts per 60 seconds; the 4th attempt triggers daemon shutdown so the user sees the failure clearly. Recovery: - Existing ConnectionLost → drop-engine → re-discover path picks up the new endpoint transparently. No MCP tool-handler changes. Tests: - 11 new tests covering has_exited semantics, restart-history math (off-by-N enforcement), the REPORT_HYPERD_ERROR command, and end-to-end engine recovery after SIGKILL. - Adversarial reviewers caught a Windows is_running bug in the plan phase and a spurious-double-restart race in the code phase; both fixed before this commit. Known limitation: hung-but-alive hyperd (TCP listening, query-stuck) is not detected — operator recovery is `hyperdb-mcp daemon stop`.
This was referenced May 25, 2026
Closed
Merged
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR turns
hyperdb-mcpinto a robust shared-engine architecture. Three commits land together:hyperdper user, shared across all MCP clients (Claude Code, Cursor, VS Code Copilot, etc.). Reduces memory overhead and lets multiple AI clients access the same persistent databases simultaneously.hyperdand restarts it automatically, with rate limiting to fail loudly when something is fundamentally broken.Architecture
Key design decisions:
~/.hyperdb/daemon.json(overridable viaHYPERDB_STATE_DIR)PING/HEARTBEAT/STOP/STATUS/REPORT_HYPERD_ERROR) for liveness checks, idle tracking, and crash signaling--no-daemonflag to opt out and use legacy per-clienthyperdOperating modes
Two independent dimensions:
.hyperdeleted on session end--workspace <path>.hyperfile--no-daemon--no-daemon--workspace <path>Crash recovery
The daemon polls
hyperdevery 5 seconds viaChild::try_wait()(zero SQL traffic — no log noise). If hyperd has exited:HyperProcess.daemon.jsonwith the new endpoint.ConnectionLostrecovery path.Clients can also signal
REPORT_HYPERD_ERRORto fast-path the restart when they detect a dead endpoint before the daemon's polling tick.Restart attempts are rate-limited to 3 per 60 seconds. The 4th attempt within the window triggers daemon shutdown so the user sees the failure clearly rather than spinning silently. The next MCP client to start will spawn a fresh daemon.
Known limitation: hung-but-alive
hyperd(TCP listening but query-stuck) is not detected. Operator recovery ishyperdb-mcp daemon stop.New CLI commands
New environment variables:
HYPERDB_STATE_DIR— override~/.hyperdb/HYPERDB_DAEMON_PORT— override default port 7484HYPERDB_DAEMON_IDLE_TIMEOUT— override 30-minute defaultTest plan
37 new tests, all passing:
DaemonStateidle tracking + shutdown flag; discovery file write/read/overwrite/remove/stale-cleanup; health protocol (PING,HEARTBEAT,STOP,STATUS, unknown command, multi-command); single-instance lock (second bind fails withAddrInUse); idle timeout triggers shutdown / heartbeats prevent it; engine connects to shared daemon; two engines share one hyperd; persistent database file survives engine drop; ephemeral database cleaned up on drop (DETACH + delete).HyperProcess::has_exitedcorrectness on Unix and after SIGKILL;DaemonStaterestart-flag round-trip; restart-history rate-limit math (records under limit, rejects 4th in window, prunes after window expires);REPORT_HYPERD_ERRORhealth command sets the flag; full integration —kill -9hyperd → daemon restarts it → endpoint is reachable; client report triggers restart; end-to-end engine recovery — a freshEngine::newafter a hyperd kill successfully reconnects and reads data persisted before the kill.cargo clippy --workspace --testsandcargo fmt --checkcleanps auxAdversarial review
Per request, both plan and code went through adversarial review. Findings caught and fixed before commit:
is_running()was a no-op (always returnedtrue); plan v1 would have shipped a Windows-broken feature. Fixed by switching toChild::try_wait()which is correct on both platforms. Also caught: alive-but-hung detection was overstated as covered (it isn't — documented as a known limitation).REPORT_HYPERD_ERRORarrived during a restart, the next monitor tick would consume the stale flag and restart the just-spawned healthy hyperd. Fixed by draining the flag after a successful restart.TestDaemon::Dropwaited only 200ms — could leave hyperd running between tests under load. Fixed by polling the daemon's health port until unreachable, with a 10s timeout.Commits
178e40e—feat: single-instance daemon for shared hyperd across MCP clientsfbccfa8—docs: describe shared-daemon operating modes and CLI surface760575c—feat(daemon): detect and restart crashed hyperd