docs: design exploration for promoting the shared hyperd daemon to a first-class API capability - #291
Closed
StefanSteiner wants to merge 1 commit into
Closed
Conversation
… capability Adds a design spec and a phased implementation plan for the proposal to let any hyperdb-api consumer use the resident hyperd daemon that hyperdb-mcp currently keeps to itself. Documents only; no code, no decision made. The exploration reaches two negative conclusions worth stating up front. First, the framing is wrong in a useful way. hyperdb-api already connects to a hyperd it does not own -- Connection::connect, AsyncConnection::connect and PoolConfig all take a bare endpoint and hold no HyperProcess, and the MCP's own daemon mode is built out of exactly those calls. What is missing is discovery and supervision, and supervision needs a binary, so the daemon belongs in a crate ABOVE hyperdb-api rather than moved down into it. That makes the minimum viable slice add nothing to hyperdb-api at all: no public API, no dependency, nothing entering the 1.0 freeze. Second, cross-application sharing should not be built. memory_limit is a Hyper instance-global parameter with no per-session equivalent anywhere in the tree, so one tenant can apply memory pressure to every other tenant and no knob prevents it. Combined with a shared crash blast radius and a --no-password engine endpoint published through a world-readable daemon.json, the honest documentation for that feature would have to warn that an unrelated application can exhaust your memory budget and kill your engine. The design instead recommends cohort-scoped daemons: share within one application and trust domain, which is where all the claimed benefit actually is. The subset recommended is the extraction itself, which bears directly on open issue tableau#276 and has a hard deadline at 1.0.0 because removing hyperdb-mcp's public daemon module afterwards costs a major version. That phase is worth executing even if every later phase is rejected. Benefit figures are labelled measured versus estimated. Notably neither BENCHMARK_GUIDE.md nor hyperd-release-benchmarks.md contains a hyperd spawn-to-usable wall clock, and no hyperd memory figure exists anywhere, so measuring both is a gate rather than a follow-up. Refs tableau#276, tableau#270, tableau#242, tableau#118
Contributor
Author
|
Closing immediately and unpublishing this branch. This design exploration included a concrete threat model for the shared-daemon trust boundary, which turned out to describe an unpatched weakness in code that ships today rather than a hypothetical property of a future feature. This repository is public, and The design work itself is retained offline and will be reopened with the trust-boundary section reduced to a non-actionable pointer once the underlying finding has been reported through the documented channel. |
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.
What this is
A design exploration — documents only, no code. Two Markdown files under
docs/superpowers/, following the repo's spec-then-plan convention:docs/superpowers/specs/2026-09-06-shared-hyperd-daemon-api-design.md— the specdocs/superpowers/plans/2026-09-06-shared-hyperd-daemon.md— a phased planNothing is decided. These are written to be argued with, and two of their
conclusions are negative. Please push back.
The question explored:
hyperdb-mcpruns a residenthyperdthat MCPsessions share, while every other
hyperdb-apiconsumer spawns its own viaHyperProcess. Should that daemon become a capability any consumer can use,so short-lived processes stop paying
hyperdstartup cost?Recommendation
Do a narrow subset. Do the extraction now; gate everything else. Don't build
the ambitious version at all.
hyperdb-mcp/src/daemon/*into a newhyperdb-daemoncrate,before
1.0.0. Worth doing on issue report_hyperd_error_to_daemon changed its public signature in a patch release with no BREAKING changelog entry #276's merits alone; the windowcloses at the freeze.
hyperdstartup cost and memory. Neither exists in the repotoday. Gates everything after it.
0.1.0of the new crate, after
1.0.0.Two findings that reshape the proposal
The framing is wrong in a useful way.
hyperdb-apialready connects to ahyperdit does not own —Connection::connect,AsyncConnection::connect,ConnectionBuilder,PoolConfig, andGrpcConnection::connectall take abare endpoint and hold no
HyperProcess, and none stopshyperdon drop. TheMCP's own daemon mode is built out of exactly those calls
(
engine.rs:616withhyper: None). What's actually missing is discoveryand supervision — and supervision needs a binary, so the daemon belongs in a
crate above
hyperdb-api, not moved down into it. The arrow points theopposite way from what the proposal assumes.
Consequence: the minimum viable slice adds nothing to
hyperdb-api— nopublic API, no dependency, nothing entering the 1.0 freeze. Connection pooling
over a shared daemon works today with no new API, because
PoolConfig.endpointis already a string.
Cross-application sharing should not be built.
memory_limitis a Hyperinstance-global parameter defaulting to 80% of host RAM, and greps find no
per-session equivalent anywhere. One tenant can apply memory pressure to every
other tenant and there is no knob to prevent it — that's a property of the
engine, not a gap in the daemon. Combine it with a shared crash blast radius
(attach state does not survive a restart, and replay is MCP policy) and the
honest documentation would read "an unrelated application can exhaust your
memory budget and kill your engine, and you must rebuild your session state
when it does." Instead the design proposes cohort-scoped daemons: share
within one application and trust domain, which is where all the claimed
benefit actually is.
Bearing on #276
Issue #276 asks whether
hyperdb_mcp::daemon::healthshould be public at allbefore 1.0. This design answers it: no. The module is public by accident of
module organisation — nothing about running an MCP server requires callers to
reach into its daemon control protocol. The surface belongs to a crate whose
stated job is exactly that, where it can carry its own compatibility promise.
That gives Phase 1 a real deadline: removing
pub mod daemonafter1.0.0isa major version; before it, free. Phase 1 is worth executing even if every
other phase here is rejected. The plan also proposes versioning the control
protocol separately from crate semver, which addresses #276's root cause — a
wire protocol whose compatibility axis was crate semver.
Security and trust boundary
Three confirmed weaknesses, and they chain:
hyperdruns--no-passwordwith--init-user=tableau_internal_user(
process.rs:488,:541), andConnection::connecthardcodes that userwith no password (
connection.rs:221).daemon.jsonis written with default umask permissions — noset_permissionsanywhere (discovery.rs:236), so underumask 022it isworld-readable, publishing the endpoint.
health.rs:232); any localprocess can
STOPthe daemon, or tripRESTART_LIMITwith threeREPORT_HYPERD_ERRORmessages.Chained, 1 + 2 is a confused-deputy privilege escalation: another local UID
reads the file, connects with no password, and borrows the daemon owner's
filesystem authority over
.hyperfiles. Thin risk for a single-user dev tool;materially different as a standing machine-wide service, with CI runners the
worst case. Critically, fixing the control port alone would be security
theater — the
--no-passwordengine endpoint is the real surface.Honest assessment of the asset
The daemon has been resident-by-default since v0.5.0 in early June, but its
entire hardening history is a single working day (2026-09-06: #267, #279, #280,
#286-carrying-#278), with nine distinct concurrency and lifecycle defects found
in that window. The load-bearing finding: the eight crash-and-restart tests
were
#[ignore]d unconditionally, so it shipped for three months with noautomated crash-recovery coverage on any platform — and when #279 turned them
on, CI went red and stayed red for two commits. The recent verification
discipline is above average, but this code has zero field exposure, macOS
CI still skips all eight restart tests, and #242 (a wedged-but-live
hyperdisnever recovered, >30s observed) and #118 remain open.
Open questions needing a decision
Ordered by deadline. Full tradeoffs are in the spec.
daemonfor compatibility? Rec: no1.0.0refactor(daemon)!:breaking marker acceptable inside the-rcline?1.0.0Connection::newfrom&HyperProcessto a trait? Rec: no1.0.0hyperd, or document reachability as full authority?hyperdb-mcpkeep version takeover, or does it become CLI-only?hyperd? Currently unproven either wayThe first experiment matters most: the API models attach per-connection and the
MCP registry is per-process, but no test proves cross-session invisibility on
one shared instance — the closest evidence uses two private engines, which
proves something else.
Quantified benefit — and a gap
Figures are labelled measured vs. estimated. Neither
docs/BENCHMARK_GUIDE.mdnor
docs/hyperd-release-benchmarks.mdcontains ahyperdspawn-to-usablewall clock, and no
hyperdmemory figure exists anywhere in the repo. Thebest available are ~156 ms (first embedded Hyper in a proc-macro host, hardware
unstated) and "10+ seconds under load" as a CI upper bound. So measuring is a
gate, not a follow-up, and the spec says how.
What the repo does have is multi-connection data, and it cuts against the
premise for one workload:
AsyncArrowInsertermeasures 68.90 M/s at 1connection vs 48.47 M/s at 4, with the guide stating plainly that
"parallelism no longer helps Arrow inserts." A shared daemon whose tenants all
ingest concurrently contends on exactly the workload where extra connections
already measure negative.
The win is narrower than the proposal implies: clear for test suites and
repeatedly-invoked CLIs, neutral-to-negative for the single long-lived process
most library users actually are.
Verification
npx markdownlint-cli2(no arguments): 0 issues, 70 files — baseline was0 in 68, plus the 2 files this PR adds.
git show --statis two.mdfiles only.Release-As:footer. Release-please PR chore: release main #282 untouched.where entries will be needed per phase.
Please do not merge
This is an exploration for review. Refs #276, #270, #242, #118.