Skip to content

Shared Daemon

Stefan R. Steiner edited this page Sep 30, 2026 · 2 revisions

The Shared hyperd 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.


What the daemon is (and where its state lives)

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)
// Unix/macOS
{
  "pid":            12345,
  "hyperd_endpoint": "/Users/you/.hyperdb/sockets/hyper", // Unix domain socket path
  "health_port":     7485,                                // the daemon's control/lock port
  "started_at":     "2026-06-06T10:20:35Z",
  "version":        "0.7.4"
}
// 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.

Transport: local IPC, not TCP, for the engine connection

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.


How a client finds the daemon

A starting client resolves the daemon in this order:

  1. Read daemon.json and verify the daemon is really alive (see identity check below). If it checks out, connect.
  2. Scan the port range if the file is missing or stale: probe ports upward from the base, looking for a live daemon.
  3. Spawn a new daemon on the first free port if none is found.

Identity check (not just "is the port open?")

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.

Port scanning and pinning

  • Default: scan upward from 7485 (16 ports: 7485..=7500) and use the first free one. 7485 was chosen deliberately — the older default 7484 collides with hyperd'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=0 and --port 0 are both rejected explicitly. Port 0 means "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.

Security: state files are owner-only

~/.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.


The daemon stays resident

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 env

With 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).


Upgrades: newer clients take over

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.


Managing the daemon by hand

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.

Recovering from a wedged hyperd

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

Environment variables

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).