Skip to content

Explore: promote the shared hyperd daemon into a reusable crate (back burner) #293

Description

@StefanSteiner

Status

Design exploration, on the back burner. Nothing here is approved or
scheduled. This issue exists so the thinking is not lost — an earlier draft was
withdrawn pending a separate prerequisite, and the useful conclusions are
recorded here instead.

Two of the conclusions are negative, and they are the interesting ones: the
headline framing of the original proposal turns out to be wrong in a specific
and useful way, and the most ambitious version of the feature should not be
built at all. A narrower subset is worth doing, and exactly one small piece of
it has a cost that rises at the 1.0.0 freeze whether or not the rest ever
happens.

The idea

hyperdb-mcp runs a resident hyperd that multiple MCP sessions share.
Discovery goes through a record in a state directory, liveness through a
localhost health/control port, and the daemon notices when hyperd dies and
restarts it under a bounded restart policy.

Every other consumer of hyperdb-api — tests, examples, CLI tools, the
benchmark suite, downstream applications — spawns a private hyperd through
HyperProcess::new.

Real engineering went into that daemon and it is under-leveraged. The proposal
was a mode meaning "use the shared daemon rather than spawning your own
hyperd", so that short-lived processes stop paying hyperd startup cost and
stop multiplying hyperd instances.

Who benefits

  • Test suites. Hundreds of sequential server starts, one application, one
    build. A recorded make test run was 121 s for 1515 tests, and the suite has
    a large number of helper-backed spawn sites, so this is where startup cost is
    paid most often.
  • Repeatedly-invoked CLIs. A tool called in a loop or a script pays a full
    spawn per invocation against a short workload.
  • Warm serverless pools running the same function. Many short processes,
    one trust domain — but only if the daemon survives between invocations, which
    is platform-dependent.

Note what these have in common: the processes sharing the engine are the same
application, the same build, and the same trust domain
. That observation drives
the rest of the design.

The key architectural finding

This is the most valuable conclusion in the exploration, and it reshapes
everything after it.

"Expose a new mode meaning use the shared daemon rather than spawning your own
hyperd" implies hyperdb-api cannot currently talk to a hyperd it does not
own. It can, and always could. Verified against the tree:

  • Connection::connect(endpoint, database_path, create_mode) takes a bare
    endpoint string (hyperdb-api/src/connection.rs:247), as do
    Connection::without_database.
  • ConnectionBuilder::new(endpoint) is endpoint-based
    (hyperdb-api/src/connection_builder.rs:78).
  • AsyncConnection::connect(endpoint, database, mode) likewise
    (hyperdb-api/src/async_connection.rs:80). There is no AsyncConnection
    constructor taking a HyperProcess at all, so async callers already pass an
    endpoint string.
  • PoolConfig { endpoint, .. } and SyncPoolConfig { endpoint, .. } are
    endpoint-based (hyperdb-api/src/pool.rs:232, :690); pool.rs does not
    mention HyperProcess anywhere.
  • grpc::GrpcConnection::connect(endpoint, database_path) and its async twin
    likewise (hyperdb-api/src/grpc_connection.rs:138, :350).
  • None of them hold a HyperProcess, and none stop hyperd on drop.
    HyperProcess is the only type in hyperdb-api whose Drop stops the
    process (hyperdb-api/src/process.rs:1125); no other impl Drop in the crate
    touches engine lifetime.
  • The MCP's own daemon mode is built out of exactly that public path.
    Engine::try_daemon_mode calls Connection::connect(endpoint, …) and stores
    hyper: None, so its Drop structurally cannot stop the shared engine
    (hyperdb-mcp/src/engine.rs:606, :651).

So what is genuinely absent from hyperdb-api is much narrower than the
proposal assumes:

  1. Discovery — turning "the shared daemon, wherever it is" into an endpoint.
  2. Supervision — starting the daemon if absent, restarting hyperd when it
    dies, shutting down when idle.

Item 2 requires a daemon process, which requires a binary, argument parsing,
and a logging subscriber. hyperdb-api must not grow those — and, having no
feature flags by design
(confirmed: hyperdb-api/Cargo.toml has no
[features] section at all), it could not gate them if it did.

The daemon is therefore a supervisor that consumes HyperProcess. It is a
higher layer than hyperdb-api, not a lower one. The assumed direction is
inverted:

Assumed: the daemon must move DOWN
    hyperdb-mcp  ->  hyperdb-api  ->  daemon/* relocated into api or core   [wrong]

Actual: the daemon belongs ABOVE
    hyperdb-mcp             ->  hyperdb-daemon  ->  hyperdb-api
    other consumers         ->  hyperdb-daemon
    (CLIs, tests, scripts)

Consequence: in the minimum viable slice, hyperdb-api gains no public API,
no dependency, and nothing entering the 1.0 freeze.
That is both the cheapest
answer and the correct one.

Where the code should live

A new hyperdb-daemon crate that depends on hyperdb-api, shipping a
client library plus a hyperdb-daemon binary behind a default = ["cli"]
feature — exactly mirroring hyperdb-bootstrap, which already sets that
precedent (default = ["cli"], cli = ["dep:clap", "dep:anyhow", "dep:tracing-subscriber"], consumable as a pure library with
default-features = false). hyperdb-mcp then depends on hyperdb-daemon and
drops its own daemon/*. Dependency direction is hyperdb-daemon → hyperdb-api,
the same direction hyperdb-mcp → hyperdb-api already goes, so no layering rule
bends.

Alternatives considered and rejected:

  • Into hyperdb-api — needs a binary, so clap and a subscriber, which
    with no feature flags every user pays for; and it puts a resident-service
    surface inside the 1.0 semver freeze.
  • Into hyperdb-api-core — worse. That crate is positioned as
    forever-internal and sits below the API; supervision is strictly above the
    wire protocol.
  • A crate that hyperdb-api depends on — makes discovery a mandatory
    transitive dependency for every hyperdb-api user, most of whom will never
    use it, with no flag available to opt out.

What stays in hyperdb-mcp is all of the policy: attachment replay, the
persistent/ephemeral database model, _table_catalog, the KV store, watched
directories, the doctor, and the product's own version-takeover UX. The
genuinely generic residue is the discovery record, the port scan, the
control-protocol shape, HyperProcess ownership with a restart limiter, and the
detached-spawn skeleton.

Isolation is the central open problem

Treat this as a prerequisite, not a detail to sort out during
implementation.
It is the difference between a feature that is safe to offer
and one that is not.

The resource argument

memory_limit is a Hyper instance-global parameter, applied to the process
at startup through Parameters and documented as "Hyper's global memory limit",
default 80 % of host RAM (hyperdb-api/tests/stress_test/README.md:173,
hyperdb-api/tests/stress_test/simulation.rs:271). Searches for
soft_memory_limit, hard_memory_limit, admission control, or any per-session
limit find nothing.

There is no per-session resource isolation to configure. One tenant's
oversized query applies memory pressure to every other tenant on the same
instance, and there is no knob to prevent it, because the engine does not expose
one. This is not a gap in the daemon — it is a property of the engine, and it is
not fixable in this repository.

Blast radius

When a shared hyperd dies, every tenant loses in-flight transactions and
every session's attach state. The daemon restarts hyperd, but attachment
replay lives in hyperdb-mcp's AttachRegistry — it is product policy, not a
library guarantee
. A library client would simply find its attachments silently
gone against a freshly restarted engine. Rebuilding session state after a
peer-induced restart would be a burden pushed onto every caller.

Related: the current version-takeover rule (a client whose version is strictly
greater terminates the incumbent daemon and respawns) is actively dangerous when
generalised. Application A on v1.2 would terminate the daemon that application B
on v1.1 is mid-transaction against. A library must never take over. If the
resident daemon speaks a compatible control protocol, use it as-is even if
older; if it does not, start a separate daemon rather than displacing the
incumbent. Deliberate takeover stays available as an explicit operator action in
the CLI, where a human is choosing it.

The recommended answer: cohort scoping

Do not multi-tenant. Key the daemon to a cohort — a caller-supplied
identifier defaulting to something derived from the calling application, rather
than to a global constant. Different cohorts get different daemons, different
hyperd processes, and therefore real isolation, while still getting the
warm-start sharing that motivated the whole idea.

<state-dir>/
  daemons/
    <cohort-hash>/
      daemon.json
      logs/

What this buys: multi-tenancy stops being a blocker and becomes a documented
boundary; the instance-global memory_limit problem stops being
cross-application; blast radius is scoped to one application's own processes;
and version skew mostly disappears, because a cohort is usually one build.

What it costs: a machine running five cohorts runs five hyperd processes,
which is worse than one and much better than one-per-process. That is the right
place on the curve, and it is honest about not being free.

Open experiment, currently unresolved

No test proves whether one session on a shared hyperd sees another
session's attached databases.
The API models attach per connection
(attach_database/detach_database are Connection methods emitting
ATTACH/DETACH DATABASE), and the MCP's registry is per-process, so the
expectation is that it does not — but the expectation is untested.

The nearest existing evidence proves a different claim: every case in
hyperdb-mcp/tests/attach_tests.rs constructs engines via
Engine::new_no_daemon, i.e. two private engines, so what those tests show
is that attach state does not survive a new process.

The experiment is cheap and should be run before anything ships: two connections
to one hyperd; A attaches a file under an alias; B queries that alias and
also enumerates pg_catalog.pg_database. Land it as a permanent
characterization test whichever way it resolves, so the documented model has
evidence behind it. If B can see A's attachment, cohort scoping moves from
strongly recommended to mandatory.

Trust boundary — deferred

Trust boundary — deferred. Establishing an authentication and isolation
model for a shared engine is a prerequisite for this work and is being handled
separately outside this issue.

Proposed public API sketch

Additive, and living entirely in hyperdb-daemon. Note that the connect calls
below are today's unmodified hyperdb-api calls.

Callers have three genuinely different intentions and must be able to say which:

/// How to obtain a `hyperd` to talk to.
pub enum Acquisition {
    /// Spawn a private `hyperd`. Today's behaviour; remains the default.
    Private,
    /// Use the shared cohort daemon. Fail if it cannot be reached or started.
    Shared,
    /// Prefer the shared cohort daemon; fall back to a private `hyperd`.
    /// Never slower than `Private` by more than `fallback_deadline`.
    SharedOrPrivate,
}
/// A `hyperd` obtained by either route. Knows whether it owns the process.
pub enum Engine {
    Private(hyperdb_api::HyperProcess),
    Shared(SharedEngine),
}

impl Engine {
    /// libpq endpoint, usable with every `hyperdb-api` connect API.
    pub fn endpoint(&self) -> &str;
    pub fn connection_endpoint(&self) -> &ConnectionEndpoint;
    pub fn acquired(&self) -> Acquired;              // Private | Shared
    /// Why `Shared` was declined, when `SharedOrPrivate` fell back.
    pub fn fallback_reason(&self) -> Option<&FallbackReason>;
}

/// Drop releases the lease and stops heartbeating.
/// It does NOT stop `hyperd` — that asymmetry with `HyperProcess::drop` is
/// the whole point and is the invariant most worth testing.
pub struct SharedEngine { /* … */ }

#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Options {
    pub cohort: Cohort,
    pub state_dir: Option<PathBuf>,
    pub idle_timeout: Option<Duration>,   // Some(_) by default, unlike today
    pub fallback_deadline: Duration,
    pub hyperd_path: Option<PathBuf>,
    pub parameters: Option<Parameters>,   // applied only when we start it
}

pub fn acquire(how: Acquisition, options: &Options) -> Result<Engine>;
pub async fn acquire_async(how: Acquisition, options: &Options) -> Result<Engine>;

The central invariant

A shared engine's Drop must release its lease without stopping hyperd.
That is the deliberate asymmetry with HyperProcess::drop, and it is the single
most important property in the crate. The test that pins it should assert both
halves so it cannot pass by accident: acquire a shared engine, drop it, prove
the daemon and its hyperd are still alive and serving; then acquire a private
engine, drop it, prove hyperd exited.

This asymmetry is also why a non-stopping variant of HyperProcess is a
non-goal. HyperProcess::drop is load-bearing for the test suite —
TestConnection and TestServer both hold a HyperProcess field purely so
that scope exit reaps the server — so a non-stopping variant would silently
orphan a server per test. Shared handles must be a distinct type.

Other API notes

  • Options is #[non_exhaustive] deliberately. ChartOptions in hyperdb-mcp
    was found to be source-breaking to extend precisely because it was not, and
    this crate will grow knobs.
  • No silent fallback. Shared returns a typed error naming why (no
    daemon reachable, spawn timed out, protocol unsupported). SharedOrPrivate
    succeeds but reports which mode it got and why the preferred one was
    declined, and logs at warn. The MCP currently logs its fallback at debug
    and returns Ok(None), which is exactly why a ten-second stall in the
    discovery path went unnoticed in normal operation (now fixed, fix(mcp): discover() parses the whole DaemonRecord strictly, then discards what the strictness bought #270).
  • SharedOrPrivate carries a deadline, sized from measurement, so the
    preferring mode is provably not a pessimisation. This is a correctness
    property, not a tuning knob.
  • Default idle_timeout should be Some(_). Today's daemon defaults to
    None and runs forever unless configured. That is right for a product that
    wants to stay warm for its user and wrong for a library: a cargo test run
    must not leave a resident service behind.
  • Prefer a lease over pure heartbeat idling. Heartbeat-only has a real
    failure mode — a client that is alive but idle past the timeout has the engine
    shut down underneath it. Clients would register on acquire and release on
    drop, with the idle timer running only while the lease count is zero;
    heartbeats remain the mechanism that expires leases held by processes that
    died without releasing. This needs new control commands, hence a protocol
    version bump.
  • Version the control protocol separately from the crate. Crate semver is
    the wrong compatibility axis for a wire protocol, and report_hyperd_error_to_daemon changed its public signature in a patch release with no BREAKING changelog entry #276 is the evidence.
    Add an explicit protocol integer to the discovery record and the ping
    response; a client supports protocol N and N-1, and a daemon advertising an
    unsupported protocol is treated as absent rather than as an error, so the
    client starts its own daemon instead of failing.

Before and after

The Connection::new line changes to Connection::connect; nothing else moves.

// today
let hyper = HyperProcess::new(None, None)?;
let conn = Connection::new(&hyper, "db.hyper", CreateMode::CreateIfNotExists)?;

// shared, or private if the daemon is unavailable
let engine = hyperdb_daemon::acquire(
    Acquisition::SharedOrPrivate,
    &Options::for_cohort("my-cli"),
)?;
let conn = Connection::connect(engine.endpoint(), "db.hyper", CreateMode::CreateIfNotExists)?;
if engine.acquired() == Acquired::Private {
    tracing::warn!(reason = ?engine.fallback_reason(), "shared engine unavailable");
}
// and the pool needs no change at all
let pool = create_pool(PoolConfig::new(engine.endpoint(), "db.hyper"))?;

That pool line is the clearest illustration of the architectural finding:
connection pooling over a shared daemon works today, with no new API,
because PoolConfig.endpoint is already a string and the pool has never owned a
process.

The benefit is unmeasured, and measuring it should gate the work

The entire premise is that sharing saves startup cost. This repository does
not contain a measurement of hyperd spawn-to-usable wall clock.
Both
benchmark documents were checked directly:

  • docs/BENCHMARK_GUIDE.md measures throughput. Its only spawn hits are
    spawn_blocking task spawns and "task-spawn overhead".
  • docs/hyperd-release-benchmarks.md mentions "cold-start variance", but in
    context that refers to insert-throughput variance, not process spawn.

The honest state is one figure from an adjacent code path and one CI upper
bound:

Figure What it actually measured Source
~156 ms First embedded Hyper start in a proc-macro host hyperdb-api-derive/README.md:216
"10+ seconds under load" CI upper bound, hyperd startup alone, "especially macOS" hyperdb-mcp/tests/daemon_tests.rs:1774

No hyperd memory figure exists anywhere in the repository — searches find
only benchmark-host RAM totals and a qualitative "reduced memory overhead"
claim in the MCP README.

This is not a reason to abandon the idea. It is a reason not to design against a
number nobody has, because the answer changes the design:

  • If cold spawn is ~150 ms, the warm win per process is ~150 ms, and this is
    worth doing only for workloads that pay it hundreds of times.
  • If cold spawn is seconds on the platforms people actually use, the win is
    large and the feature is clearly justified.
  • If cold spawn is cheaper than the daemon's own cold-acquisition path, the
    feature is net-negative for the first process and only ever wins on the
    second — still fine, but it changes what the default should be.

How to measure it

  • Time HyperProcess::new(None, None) plus one trivial query to first row.
    20 iterations, report median and p95, release build.
  • Both transports (TransportMode::Tcp and TransportMode::Ipc).
  • macOS and Linux, recording the exact host, the hyperd version from
    hyperdb-bootstrap/hyperd-version.toml, and whether the page cache was warm.
  • Then time warm acquisition — discovery plus connect against an
    already-running daemon — over the same iteration count. The delta between
    those two medians is the per-process win
    , and warm acquisition is the number
    the design actually needs, because discovery is not free either.
  • Separately time the degraded discovery paths, because they size
    fallback_deadline: warm hit, dead-port timeout, and a full port scan.
  • Sample hyperd RSS at idle and after a representative query, and total RSS
    for N = 1, 4, 16 concurrent processes. Note the interaction: memory_limit
    defaults to 80 % of host RAM and is instance-global, so N private processes
    each believe they may use 80 % of the machine — which is an argument for
    sharing on memory grounds that nobody has quantified.
  • Do not assert on a duration in a test. Print timings under --nocapture
    and assert nothing, following the existing spike pattern. A wall-clock
    assertion is a flaky test on shared CI.
  • Record the methodology and figures in docs/BENCHMARK_GUIDE.md. Do not
    add a row to docs/hyperd-release-benchmarks.md — that file takes a row on a
    hyperd pin bump or a material API change, and conflating a startup
    measurement with it would make a future engine delta unattributable.

Where it pays off, and where it does not

Workload shape Verdict Why
Test suite, hundreds of sequential server starts Clear win Pays spawn cost most often; single application and build
CLI invoked repeatedly in a loop or script Clear win Per-invocation spawn dominates a short workload
Serverless warm pool, same function Likely win Many short processes, one trust domain — if the daemon survives between invocations
Long-running server, one process Neutral to negative Spawn paid once; adds a discovery dependency, a second failure domain, and a peer that can take the engine down
Concurrent bulk ingest from several processes Actively worse See below
Unrelated applications sharing one engine Do not Instance-global memory_limit, shared blast radius

The "actively worse" row is measured, not guessed. From
docs/BENCHMARK_GUIDE.md (Apple M3 Max, 96 GB, hyperd 0.0.26479,
2026-09-05, medians of 5):

Workload 1 connection 4 connections Direction
AsyncArrowInserter, 100M rows 68.90 M/s 48.47 M/s ~30 % worse
query.full_scan, async 24.91 M/s 73.45 M/s ~2× better
query.filtered, async 26.90 M/s 48.31 M/s better

The guide states plainly that "Parallelism no longer helps Arrow inserts"
single-connection AsyncArrowInserter outruns the 4-connection variant, "so
spending connections on an Arrow insert buys nothing on this host"
(docs/BENCHMARK_GUIDE.md:201). Read for this design: one hyperd serves
concurrent readers well and concurrent bulk writers poorly.
A shared daemon
whose tenants are all ingesting will contend on exactly the workload where extra
connections already measure negative.

Two caveats on that reading. The guide warns the ×4 rows are
"order-of-magnitude only", with a ±20–61 % spread, because four workers contend
on a 14-core laptop. And the Windows figures invert the insert result
(single-connection AsyncArrowInserter 5.39 M/s versus 20.28 M/s at ×4 over
TCP), so the conclusion is host- and engine-version-specific, not universal.

The win is narrower than the original proposal implies. It is concentrated
in many-short-processes-same-application, which is real and worth serving, and
it is absent or negative for the single long-lived process that most library
users actually are.

Prerequisites and related work

  • fix(mcp): recover when a live hyperd becomes unresponsive #242 — a live-but-unresponsive hyperd is never recovered. This is a
    prerequisite, not a follow-on.
    Reproduced by suspending the managed child
    process: a SELECT 1 blocked for over 30 s, the daemon kept seeing the
    process as alive, and never restarted it. Acceptable for a tool a developer can restart; not
    acceptable for a library that silently enrolled the caller in a resident
    service.
  • report_hyperd_error_to_daemon changed its public signature in a patch release with no BREAKING changelog entry #276 — should hyperdb-mcp's daemon::* be public? Directly related, and
    extracting the daemon into its own crate would resolve it.
    The
    recommendation recorded on that issue was to narrow the surface, on the
    evidence that crates.io reverse dependencies for hyperdb-mcp are zero,
    no workspace crate depends on it, and the crate's own lib.rs already
    declares it "not a documented API surface". Extraction gives that surface a
    home in a crate whose stated job is exactly that, where it can carry its own
    compatibility promise.
  • MCP: single engine mutex + no op timeout lets one stalled hyperd call hang all tool calls #118 — one engine mutex serializes every MCP tool call. Not part of this
    work, but relevant if a shared daemon ever grows a single-lock front end.
  • The daemon has little elapsed field exposure, and macOS CI still skips its
    crash-and-restart tests. Promoting it to a library capability while its
    crash-recovery coverage runs on a subset of platforms is not defensible. This
    is an argument for elapsed time, not more code.

Recommendation on timing

The user has put the broader feature on the back burner. The one piece with
a real deadline is the extraction.

  1. Extract daemon/* into hyperdb-daemon before 1.0.0. It removes a
    public module from hyperdb-mcp, which is free before 1.0.0 and a major
    bump after. It is worth doing on report_hyperd_error_to_daemon changed its public signature in a patch release with no BREAKING changelog entry #276's merits alone, independent of
    whether any of the rest ever ships, and it is the cheapest piece — a move
    plus a re-export decision. Scope discipline matters: it is a move, not a
    redesign. The evidence that it worked is the existing daemon test suite
    passing unchanged under the new crate, reported as a characterization rather
    than dressed up as red-before-green.
  2. Measure startup cost and hyperd RSS. Cheap, and it gates whether any of
    the rest is justified.
  3. Resolve the deferred prerequisitesfix(mcp): recover when a live hyperd becomes unresponsive #242 and the trust-boundary work
    being handled separately.
  4. Then, if the measurement holds, ship cohort-scoped acquire() as a
    0.1.0 of the new crate, after 1.0.0, with Private as the default and no
    silent fallback.
  5. Do not build cross-application sharing. The instance-global
    memory_limit and the shared blast radius make it indefensible, and neither
    is fixable in this repository.

The highest-value follow-on, once numbers exist, is teaching the test helpers to
accept a shared engine — but the risk there is the whole phase. Tests
currently get a fresh engine each time, so sharing one means leaked state
between tests. Enumerate what leaks (temp tables, attach aliases, session
settings, memory pressure), prove per-test cleanup, convert one file, measure
the delta, and stop for review before converting the suite.

One hyperdb-api decision has a 1.0.0 deadline

Connection::new takes a concrete &HyperProcess:

pub fn new(instance: &HyperProcess, database_path: impl AsRef<Path>, create_mode: CreateMode) -> Result<Self>

If a shared-engine handle should ever be usable where a HyperProcess is
usable, that parameter must become a trait bound. Generalising a concrete
parameter to impl Trait is source-compatible for ordinary call sites but not
for turbofish or function-pointer uses, so it is cheapest to do — or decide
against — before the freeze.

The recommendation is not to do it. Connection::connect(endpoint, …)
already covers the shared case, and adding a trait purely for symmetry is
speculative generality; adding it later is a minor addition, since a new trait
plus a new inherent method breaks nothing. But it is the only identified
hyperdb-api change whose cost rises after 1.0.0, so it wants an explicit
human confirmation rather than a default.

Non-goals

Open questions needing a human decision

  • Does the measured benefit justify building this at all? Blocked on the
    measurement above. This is the real go/no-go.
  • What is the default cohort? Derived from the executable path, from the
    crate name, or a required explicit argument with no default? A path-derived
    default silently splits cohorts when a binary is rebuilt to a different
    location; a required argument is more honest but less ergonomic.
  • Leases, or heartbeat-only idle? Leases avoid shutting the engine out from
    under an idle-but-live client, at the cost of new control commands and a
    protocol bump. Is that complexity warranted in a first release, or is a
    generous idle timeout plus heartbeats enough?
  • Should Connection::new be generalised to a trait before 1.0.0?
    Recommendation: no. Deadline: 1.0.0.
  • Does the extraction re-export daemon from hyperdb-mcp for
    compatibility?
    Recommendation: no — a re-export preserves the surface the
    change exists to remove, and report_hyperd_error_to_daemon changed its public signature in a patch release with no BREAKING changelog entry #276 established no known downstream library
    consumer.
  • Does hyperdb-mcp keep its version-takeover behaviour after extraction, or
    does it become a CLI-only operator action?
    The MCP's binary-upgrade UX
    currently depends on it.
  • Do the C++, Python, or Java Hyper APIs — or upstream Hyper itself — already
    have a shared-instance concept?
    Nothing in this repository describes one,
    but that is absence of evidence, and it rests on a grep of this tree. Needs
    external verification before "novel" is claimed anywhere public, and
    terminology should be aligned rather than invented if a concept already
    exists.

Recorded from an offline design exploration and its phased plan, both verified
against the tree on 2026-09-06. The documents themselves are deliberately not
committed. Every code reference above was re-checked against the repository
before being repeated here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions