-
Notifications
You must be signed in to change notification settings - Fork 2
Shared Daemon
By default, all hyperdb-mcp clients on a machine share one hyperd
process per user, managed by a small background daemon. Multiple AI clients
(Claude Code, Cursor, VS Code Copilot) then talk to the same engine and the
same persistent databases instead of each spawning their own hyperd. Pass
--no-daemon to opt out and get a private per-session hyperd (legacy
behavior).
This page explains how the daemon is discovered, how it stays out of your way, and the knobs for the rare cases where you need to intervene.
The daemon owns the hyperd process and advertises how to reach it in a small
JSON file:
~/.hyperdb/daemon.json # discovery file (override dir with HYPERDB_STATE_DIR)
~/.hyperdb/logs/ # hyperd logs
~/.hyperdb/sockets/ # Unix domain socket directory (Unix/macOS only)
// Windows
{
"pid": 12345,
"hyperd_endpoint": "\\\\.\\pipe\\hyper-<name>", // named pipe
"health_port": 7485,
"started_at": "2026-06-06T10:20:35Z",
"version": "0.7.4"
}Two channels, two owners — don't conflate them:
| Field | Owned by | Purpose |
|---|---|---|
hyperd_endpoint |
hyperd |
Where your queries connect: a Unix domain socket path (Unix/macOS) or a named pipe (Windows). |
health_port |
the Rust daemon wrapper | Single-instance lock and a control channel (PING / HEARTBEAT / STOP / STATUS), always TCP. |
The health port doubles as the lock: binding it succeeds for exactly one process per user, which is how "single instance" is enforced cross-platform.
The daemon reaches its own hyperd over a local IPC channel — a Unix
domain socket on Unix/macOS, a named pipe on Windows — instead of a loopback
TCP port. This is automatic; there's no flag to opt out, and it only affects
the daemon-managed engine connection. The health/control channel above, and
daemon discovery itself, are unchanged and still plain TCP.
On Unix/macOS the socket lives at <state dir>/sockets/hyper, inside a
directory created owner-only (0700) before hyperd binds inside it, so
there's no window where a widened permission could race the bind. daemon.json
records the connectable path hyperd actually bound — not the raw internal
descriptor, which (for a domain socket) doesn't reconstruct to a usable path.
When the daemon restarts a crashed hyperd (see Recovering from a wedged
hyperd below), the replacement gets a fresh socket/pipe under the same
directory, and the new path is published to daemon.json before the
in-memory STATUS record is updated — so a client reading either channel
during a restart never observes an endpoint that isn't actually live yet.
A starting client resolves the daemon in this order:
-
Read
daemon.jsonand verify the daemon is really alive (see identity check below). If it checks out, connect. - Scan the port range if the file is missing or stale: probe ports upward from the base, looking for a live daemon.
- Spawn a new daemon on the first free port if none is found.
A client doesn't trust a port merely because something accepts a TCP connection
there. It sends PING and requires the reply to be exactly:
PONG hyperdb-mcp <version>
An unrelated process occupying the port (it answers TCP but not this protocol)
is classified as camped and skipped. A stale daemon.json pointing at a
dead or foreign port is detected and removed, and the client moves on to scan or
spawn. This prevents a client from ever mistaking some other service for the
daemon.
daemon.json also carries an additive identity block (build version,
executable path) used by diagnostics. An older client that doesn't recognize
that block still parses the file's core fields and discovers the daemon
normally — only a record that fails both the strict and the lenient parse
is treated as unreadable — so a newer daemon's discovery file never becomes
invisible to an older client.
-
Default: scan upward from 7485 (16 ports:
7485..=7500) and use the first free one.7485was chosen deliberately — the older default7484collides withhyperd's conventional gRPC port, which is exactly the kind of process that would otherwise be mistaken for a daemon. -
Pin an exact port: set
HYPERDB_DAEMON_PORT=<port>. This disables scanning — the daemon uses exactly that port, and clients look only there. -
HYPERDB_DAEMON_PORT=0and--port 0are both rejected explicitly. Port0means "let the OS assign an ephemeral port," which no scanner would ever find — silently trying it used to make every client conclude no daemon existed and spawn its own.
~/.hyperdb/ (or HYPERDB_STATE_DIR) and everything under it — daemon.json,
logs/, sockets/ — are created and kept owner-only: directories at 0700,
files at 0600. daemon.json records the live endpoint to the engine, so it
and the logs that also mention it are never left world- or group-readable,
regardless of the process umask. The permission is set before any content is
written, so there's no window where the endpoint sits in a readable file.
By default the daemon — and the hyperd it owns — stay running once
started. Keeping hyperd warm means the next tool call connects instantly
instead of triggering a cold start and the "hyper is restarting, please retry"
round-trip.
If you want the daemon to shut itself down after a period of inactivity (for example on CI), opt in:
hyperdb-mcp daemon --idle-timeout 1800 # shut down after 30 min idle
# or
export HYPERDB_DAEMON_IDLE_TIMEOUT=1800 # same, via envWith neither set, the daemon only stops on an explicit stop, an OS signal, a
newer-version takeover (below), or repeated hyperd startup failures (3 within
60 seconds, which trips a safety shutdown so a broken binary doesn't spin
forever).
When you upgrade hyperdb-mcp, the next client built from a strictly newer
version automatically takes over: it stops the old daemon (which also stops the
old hyperd) and starts a fresh one on the same port. You don't have to
manually kill anything — the upgrade takes effect on the next client start.
Equal or older versions reuse the running daemon and never downgrade-kill it.
Note that the version compared is the crate's semantic version, so two local
builds of the same version (e.g. two dev builds of 0.4.0) are considered
equal and won't take over each other — use hyperdb-mcp daemon stop to force a
replacement in that case.
The daemon is normally invisible. For diagnostics:
hyperdb-mcp daemon status # PID, hyperd endpoint, health port, started_at, version
hyperdb-mcp daemon stop # gracefully stop the running daemon
hyperdb-mcp daemon # run as a daemon explicitly (rarely needed)status and stop locate the running daemon automatically (read daemon.json,
then scan), so they work even if the daemon landed on a non-default port. Pass
--port <PORT> to target a specific port explicitly.
The daemon detects a crashed hyperd (process exited) within ~5 seconds and
restarts it — spawning a fresh IPC socket/pipe and updating daemon.json with
the new endpoint; clients reconnect transparently. A client that notices the
failure first can fast-path the signal so the restart doesn't wait for the
polling tick. Restart attempts are rate-limited (at most 3 within 60 seconds);
a 4th attempt in that window is treated as a broken binary and shuts the
daemon down instead of spinning forever.
A hung-but-alive hyperd (still listening, but not answering queries) is the
one case the daemon can't auto-detect. Because the daemon now stays resident by
default, there's no idle timeout to eventually reap it. Recovery is:
hyperdb-mcp daemon stop # then let the next client spawn a fresh one| Variable | Effect |
|---|---|
HYPERDB_STATE_DIR |
Directory for daemon.json + logs (default ~/.hyperdb/). |
HYPERDB_DAEMON_PORT |
Pin the health/lock port exactly (default: scan from 7485). |
HYPERDB_DAEMON_IDLE_TIMEOUT |
Opt into idle shutdown after N seconds (default: stay resident). |