← Workstreams

Workstream: HostedJvm

Status: Planned · Component: Maximize developer productivity

This document is an aspirational vision and design-scope document, not a milestone plan. It describes the end-state we are building toward and the architecture decisions that define it; decomposition into concrete tasks happens later. The MVP vs. mature-platform split is the closest thing here to sequencing.

North star

Ask for a JVM — "2 GB, in Frankfurt, Java 21, gone by 18:00" — and get one, already wired for console, health, thread dumps, and heap dumps, that shuts itself down when you forget about it.

Make getting a JVM somewhere a single request. Today, running a JVM that is not on your own machine means choosing a cloud, provisioning a host, installing a runtime, copying a JAR, remembering to tear it all down, and then driving the process with nothing better than SSH. HostedJvm collapses that to request-and-drive: a caller states what the JVM needs (memory, region, Java version, a deadline), the service places it on whichever backend can satisfy the request — a DigitalOcean droplet, an AWS instance, a box the operator already owns — and hands back a handle that is the RemoteJvm control surface: lifecycle, health and memory, console I/O, static-method invocation, jstack- and heap-dump-equivalents, all over url://. Every JVM carries an auto-shutdown deadline, so an abandoned JVM is a bounded cost, never an orphan.

The four verbs in the framing — ask, place, drive, expire — are the four pillars of the design:

Guiding principles

  • A JVM is a lease, not a server. Every hosted JVM has a deadline from the moment it is requested. There is no "forever" option; there is only renewal. This is the same stance the DigitalOcean Droplets service takes toward droplets — an orphaned resource bills indefinitely, so termination is the invariant everything else serves.
  • The handle is RemoteJvm. HostedJvm does not invent a second way to drive a JVM. The existing RemoteJvmApi contract — JvmProcess with health, memory, console, invoke, thread dumps, heap dumps — is what a caller receives, so tooling written against a local child JVM works unchanged against a JVM on the other side of the world. HostedJvm adds where, for whom, and until when; it does not re-describe how.
  • Providers are plugins; the service is the policy. Which cloud, which account, which region is a provider's business. Who may ask, how much they may hold, how long a lease may run, and which environment is the default is the service's business, held in admin-managed records rather than compiled in. New providers are installed, not deployed.
  • Reject what cannot be satisfied; park what cannot be afforded yet. A hard requirement the chosen environment cannot meet (a region it does not offer, more memory than its largest size) is refused with an error that names the requirement and what was available — never silently substituted. A request that is satisfiable but over quota is parked as pending until the caller's quota frees, per Rate Limits — exhaustion parks the request.
  • Third parties through their proxies. A provider that reaches a cloud does so through that cloud's url:// 3P proxy, never with a raw vendor key of its own. HostedJvm holds proxy credentials; the proxies hold the vendor keys.
  • Compose with the platform, don't duplicate it. Identity, quota, and billing are shaped for W3Wallet; persistence is the Event Log; workload bytes come from Blobstore, Maven, or a kompile build; droplets come from the droplet proxy. HostedJvm should be remarkable for how little infrastructure it owns.
  • Mitigations must be loud. A lease the sweeper terminates, a request parked on quota, a provider that failed to place — each is a first-class recorded event the caller and the operator can see, never a silent outcome.

The request model

A caller asks for a JVM by stating:

Field Kind Meaning
environment optional The admin-defined environment to place in. Absent → the deployment's default environment.
region hard or preferred Where the JVM should run, in the provider's own region vocabulary (fra1, eu-central-1, local). A hard region is a requirement; a preferred region is a hint the placer honors when it can.
memoryBytes hard Maximum heap the JVM must be able to run with. Maps to -Xmx plus the host headroom the provider knows it needs.
cpus soft Desired processor count; the placer picks the smallest size that meets memory and comes closest on CPUs.
diskBytes soft Scratch disk the workload expects.
jvmMajorVersion hard 17, 21, … — the provider must have, or be able to install, a matching runtime.
deadline required, defaulted The instant the JVM is shut down unless renewed. Defaults to the environment's default lease length; capped by the environment's maximum. See §4.
workload required What to run — see §2.
labels optional Free-form key/value tags for the caller's own bookkeeping, echoed on every listing.

What comes back is a lease: a stable id, the resolved placement (provider, environment, region, host, size), the deadline, and a HostedJvm handle that extends JvmProcess. Listing, renewing, and terminating are addressed by lease id — url://hosted-jvm/leases/{id} resolves to the handle, per the resource URL addressing convention.

Current state — built vs. aspirational

The pieces HostedJvm composes already exist; the service that composes them does not.

Already built What it provides to this workstream
RemoteJvmApi The per-host contract: RemoteJvmManager builds a JvmProcess (classpath, arguments, environment, JvmSpec selection, heap/metaspace caps, crash heap dumps); the process exposes lifecycle, JvmHealthInfo, JvmMemoryInfo, ordered console output, stdin, invoke, JvmThreadDump, and dumpHeap. It has no notion of tenants, regions, quotas, or deadlines — those are this workstream's additions.
RemoteJvmLocal + RemoteJvmAbutment The child-process backend and the JSON-over-TCP bridge that lets a parent drive it. On a remote host, the agent-side AbutmentServer is the thing a provider stands up next to the user's JVM.
RemoteJvmDiscovery getJvmSpecs() — how a provider learns which runtimes a host has, and therefore whether it can satisfy jvmMajorVersion.
JvmLaunchApi launchJvm(jar) — a local convenience over RemoteJvmLocal. It stays as it is: HostedJvm's local provider uses RemoteJvmLocal directly, and nothing remote goes through launchJvm.
DigitalOcean Droplets (url://digitalocean-droplets/) Droplet creation, expiry via WebCron, renewDroplet, a periodic sweep, and fleet history reconstructed from the vendor's own action log. The DigitalOcean provider is a consumer of this proxy.
GithubProxy keys (live page) The working reference for shared-key and bring-your-own-key registration: an account is registered with a token and a label, the registrant is issued the credential that addresses it, the key is validated before storage and never returned by any read. HostedJvm's provider accounts follow this exactly.
W3Wallet The target shape for identity, allowances, and billing; partially built. HostedJvm is designed to map onto it (§7) without waiting on it.

Aspirational (the substance of this workstream): everything in the design — the HostedJvm* repository family, the provider SPI and its three initial providers, environments and quota, leases with enforced deadlines, the url://hosted-jvm/ service on ContainerNursery, the CLI, and the WUI.

The design

1. The API family and tenancy

HostedJvm is a new API family rather than an extension of RemoteJvmApi, because the two answer different questions: RemoteJvmApi says how to drive one JVM on one host; HostedJvm says who may have a JVM, where, and until when. Following the standard layered architecture and the FooManager vends Foo tenancy rule:

Repository Layer Contents
HostedJvmApi Api HostedJvmManager (multi-tenant: request, list, renew, terminate, get), HostedJvm (single-tenant handle: extends RemoteJvmApi's JvmProcess with lease metadata — leaseId, environment, placement, deadline), HostedJvmAdmin (providers, environments, accounts — the <Service>Admin interface), and the request/lease/quota data types.
HostedJvmProviderApi Api (SPI) HostedJvmProvider — the contract a backend implements: capabilities (regions, sizes, runtimes), place(request) → ProviderPlacement, release(placement), reconcile(); plus the account-credential type the provider needs. Kept separate from HostedJvmApi so a provider artifact depends only on the SPI and RemoteJvmApi, never on the caller-facing surface.
HostedJvmEmbedded Embedded The manager: placement, leases and the sweeper, environments, quota accounting, provider loading, Event Log persistence, reconciliation. Fully testable in-process with the local provider and a ManualClock.
HostedJvmServiceServer ServiceServer Hosts the Embedded at url://hosted-jvm/; verifies each caller's credential at the boundary (authorization at the url:// boundary).
HostedJvmProviderLocal, HostedJvmProviderDigitalOcean, HostedJvmProviderAws Providers The three initial backends — see §5.
HostedJvmCli CLI request, list, get, renew, terminate, console, health, threads, heap, plus an admin namespace.
HostedJvmWui WUI The operator and user faces — see §11.

RemoteJvmApi gains only what the hosted case genuinely needs and a local caller also benefits from — chiefly a way to request a runtime by major version rather than by a JvmSpec the caller has already enumerated, and a heap-dump destination that is not a path on the remote host (see open questions). JvmLaunchApi is untouched.

A sketch of the caller-facing contract:

interface HostedJvmManager {
    fun request(request: JvmRequest): HostedJvm          // may return a lease in PENDING state when parked on quota
    fun list(): List<HostedJvm>                           // the caller's own leases only
    fun get(leaseId: String): HostedJvm
    fun renew(leaseId: String, newDeadline: Instant): HostedJvm
    fun terminate(leaseId: String)
}

interface HostedJvm : JvmProcess {                        // everything RemoteJvm already offers, plus:
    val leaseId: String
    val state: LeaseState                                 // PENDING, PLACING, RUNNING, TERMINATING, TERMINATED
    val environment: String
    val placement: Placement                              // provider, region, host, size
    val deadline: Instant
    val terminationReason: TerminationReason?             // CALLER, DEADLINE, QUOTA_REVOKED, PROVIDER_LOST, ADMIN
    fun utilization(window: Duration): List<UtilizationSample>   // heap, non-heap, threads, CPU, GC — sampled by the service
    fun captureHeapDump(live: Boolean): HeapDumpCapture   // stored in Blobstore, listed on the lease
    fun captureThreadDump(): ThreadDumpCapture            // stored and listed likewise; also returns JvmThreadDump
    fun captures(): List<Capture>                         // every dump taken on this lease, with who/when/why
}

2. Workloads — what runs in the JVM

A request names a workload from the same sources LambdaServer accepts, so code is met where it already lives:

  • A Maven coordinate (resolved from kotlin.directory or Maven Central, with its transitive closure) and a main class or an executable-JAR manifest.
  • A JAR in Blobstore, addressed by content hash — the path for anything built locally.
  • A kompile build rule from a git repository — repo + rule, built once by Kotlin Build and the resulting classpath pulled from the shared build cache.
  • No workload at all — a bare JVM running only the RemoteJvm agent, driven entirely through invoke, writeStdin, and classpath entries the caller adds later. This is the "give me a JVM to poke at" case and it is first-class.

Every hosted JVM, whatever its workload, is launched with the RemoteJvm agent attached (RemoteJvmAgent on the classpath, AbutmentServer listening), because that is what makes console, health, dumps, and invoke work. A workload that wants to be called by others simply runs a UrlResolver node and announces its own url:// address — the hosted JVM joins the fabric like any other peer, and HostedJvm has no opinion about it beyond having placed it. Exposing raw TCP ports through a provider's firewall is deferred (see open questions).

3. Environments and placement

An environment is the unit an admin configures and a caller names: one provider, one provider account, a set of allowed regions and sizes, a default and maximum lease length, and a per-caller quota (§6). A deployment designates one environment as the default, used when a request names none.

Placement is deterministic and explainable:

  1. Resolve the environment (named, else default). A named environment the caller is not permitted to use is refused.
  2. Check hard requirements against the environment's capabilities — region, jvmMajorVersion, and whether any allowed size meets memoryBytes. Any miss is refused with an error naming the requirement and what the environment offers.
  3. Check quota. A request that would exceed the caller's quota in this environment is parked as a PENDING lease with a recorded reason and is placed automatically when quota frees (a sibling lease ends). The caller may cancel a pending lease; it never silently expires.
  4. Ask the provider to place: the smallest allowed size satisfying memory, closest on CPUs, in the hard region or the preferred region if offered, else the environment's first allowed region. The provider returns a ProviderPlacement and a RemoteJvmManager bound to that host, and the Embedded builds the JVM through it.

In the MVP each lease gets its own host from a cloud provider (one droplet per lease). Packing several leases onto one host is a mature-platform milestone and changes nothing in the API — placement.host simply stops being unique per lease.

4. Leases and the auto-shutdown deadline

The deadline is mandatory and the service's one non-negotiable invariant: no lease exists without one. A request that omits it gets the environment's default (1 hour unless the admin set otherwise); a request that exceeds the environment's maximum is refused, not clamped, so the caller knows the cap exists.

  • Renewal — renew(leaseId, newDeadline) moves the deadline forward (never backward — shortening is terminate), subject to the same maximum measured from now. Renewal is the only way to keep a JVM alive; there is no keepAlive flag.
  • Enforcement is provider-independent. The Embedded runs a sweeper on a Clock (driven by ManualClock in tests) that terminates every lease past its deadline: shutdown() the JVM, wait a bounded grace, shutdownNow(), then release the placement back to the provider. Termination is recorded with reason DEADLINE.
  • The provider's own expiry is a second safety net, never the primary. The DigitalOcean provider creates droplets through the droplet proxy, which already attaches a WebCron expiry and a sweep of its own; the provider sets that expiry to the lease deadline plus a short margin, and renews it when the lease renews. If HostedJvm itself is down when a deadline passes, the droplet still dies. A provider that offers no native expiry (local, and AWS until its proxy does) relies on the Embedded sweeper and on reconciliation after a restart (§9).
  • Idle shutdown — terminating a lease that has seen no console, RPC, or invoke activity for a configurable window — is designed in (it is just another TerminationReason) but is not part of the MVP; the deadline alone bounds cost.

5. Providers

A provider is a Maven artifact implementing HostedJvmProvider, installed at runtime by an admin call with its coordinate — the way LambdaServer loads a function from a coordinate — not compiled into the service. Installing resolves the artifact, loads it in an isolated classloader, and registers its capabilities; environments then reference it by name.

The three initial providers, in build order:

  1. Local (HostedJvmProviderLocal) — places JVMs on the host the service itself runs on, via RemoteJvmLocal. It needs no account. It exists for self-hosted deployments, for development, and as the provider every Embedded test runs against — it is deliberately not the hosted instance's default environment, because the ContainerNursery host is already shared by many services and is not spare compute. Its capacity is a configured memory budget, not "whatever is free".
  2. DigitalOcean (HostedJvmProviderDigitalOcean) — consumes url://digitalocean-droplets/. Its account credential is a droplet-proxy account credential, not a DigitalOcean token: an operator or user registers their DigitalOcean token with the droplet proxy and hands the resulting credential to HostedJvm. Placement creates a droplet (region, size, an image with the requested JDK), waits for SSH, stages the RemoteJvm agent and the workload, and connects an AbutmentClient through the fabric. Fleet history and cost come from the proxy's reconstructed record, not from HostedJvm sampling anything.
  3. AWS (HostedJvmProviderAws) — the same shape over an url://aws-ec2/ 3P proxy that does not exist yet. That proxy is a prerequisite of this provider and is sequenced after DigitalOcean and local; the provider is not allowed to hold raw AWS keys to get there sooner.

A provider also implements reconcile(): enumerate what it is actually running under its account and return it, so the Embedded can adopt or terminate hosts it has no lease for.

6. Environments, defaults, and quota

Environments, providers, and accounts are admin-managed records persisted by the Embedded, edited through HostedJvmAdmin (and its CLI and WUI faces), never deploy-time configuration. An environment carries:

  • provider name and the provider account it draws on;
  • allowed regions and allowed sizes (memory, CPUs, disk) and the JDK versions available;
  • default lease length and maximum lease length;
  • a per-caller quota: maximum concurrent leases, maximum total memoryBytes held, and a rolling allowance of GB-hours per day (memory × time, the unit cost actually scales with);
  • which callers may use it — everyone, or a listed set — and whether it is the default.

Quota is enforced in the Embedded and accounted per caller per environment from the lease record (every lease knows its memory and its lifetime). Exhaustion parks the request (§3); it does not reject it. An admin may lower a quota below a caller's current holdings, which parks nothing retroactively but blocks new placement and is surfaced to the caller as the reason.

7. Identity, keys, and bring-your-own-key

HostedJvm adopts the registration model GithubProxy runs today, unchanged in mechanism:

  • A provider account is registered with registerAccount(provider, credential, label) and the registrant is issued the credential that addresses it. The provider validates the credential against its backend (for DigitalOcean, that the droplet-proxy credential can list droplets) before it is stored; it is held only inside the service and never returned by any read.
  • Shared-key mode — the operator registers accounts and binds them to environments open to every caller; callers draw on the operator's budget within quota. The hosted instance's default environment is of this kind.
  • Bring-your-own-key mode — a user registers their own provider account and creates (or asks the admin to create) an environment bound to it, visible only to callers the owner lists. Placement in that environment costs the owner, not the operator, and the owner's quota settings are theirs to set.
  • Every caller presents its own credential on every call; the service keeps no session. A caller sees only its own leases; an admin sees all.

The whole model is shaped to become W3Wallet-native without an API change: a registered account's capacity is an asset pool (hosted-jvm:gb-hours per environment), a caller's quota is an allowance attenuated from it, and the sweeper's accounting is the metering that settlement will read — see the W3Wallet spend-and-settlement design. Until W3Wallet lands, the Embedded's own quota records play the allowance's part.

8. The control plane over url://

A HostedJvm handle is a JvmProcess, served over url:// by the ServiceServer and backed, provider-side, by the RemoteJvm abutment to the actual process. Everything RemoteJvm offers is available remotely: getHealthInfo, getMemoryInfo, incremental getConsoleOutput(fromIndex), writeStdin, invoke(className, method, args), getThreadDump, and dumpHeap. Beyond passing those through, the service itself observes every lease — a caller should be able to answer "how is my JVM doing, and what was it doing when it went wrong?" without having been watching at the time:

  • Utilization is tracked, not just readable. The Embedded samples JvmHealthInfo and JvmMemoryInfo for every running lease on a fixed cadence (heap used and committed against max, non-heap, per-pool usage, thread count and peak, GC count and time, CPU, loaded classes) and keeps the samples as a per-lease time series — a projection the CLI, the WUI, and other services read via utilization(window). Listings carry the latest sample so list already shows memory pressure; environments aggregate their leases so an operator sees utilization per environment and per provider account. Samples are retained for the lease's lifetime plus a bounded tail after termination, so the last minutes before a deadline or crash are still there afterwards.
  • Heap dumps and thread dumps are first-class captures. captureHeapDump(live) and captureThreadDump() take the dump through RemoteJvm, store it in Blobstore, and record a capture on the lease — who requested it, when, why (caller, an admin, or the service itself), size, and duration. captures() lists them, and each is fetchable by its Blobstore reference long after the JVM is gone. Crash heap dumps (enableCrashHeapDump is always set on a hosted JVM) are collected the same way when a JVM dies from out-of-memory, so a lease that OOMed at 03:00 has its dump waiting in the morning. The raw JvmProcess.getThreadDump() remains available for callers that want the result in-band rather than stored; the raw dumpHeap() returns a path on a host the caller cannot reach, so on a hosted JVM it, too, yields a Blobstore reference — how RemoteJvmApi expresses that without breaking local callers is an open question.
  • Thresholds are observations, never mitigations. An admin may set per-environment alerts (heap above a fraction of max for a window, thread count above a ceiling, deadlocked threads detected) that record an event and, optionally, take an automatic thread or heap capture — never restart or terminate the JVM. Surfacing the evidence is the service's job; acting on it is the owner's.
  • Console output is a projection. Per the CQRS and state-management direction, the console is an append-only stream the WUI and CLI subscribe to rather than poll; getConsoleOutput(fromIndex) remains for one-shot reads. The utilization series is served the same way.

9. Persistence and reconciliation

The Embedded is Event Log-backed: every lease transition (requested, parked, placed, renewed, terminating, terminated-with-reason), every provider install, every environment and account change is an appended event, and the lease list, quota ledger, and environment catalog are projections over it. This gives the audit trail an operator needs ("who held what at 03:00, and why did it die?") for free and keeps the service restartable.

On startup and on a schedule, the Embedded reconciles against each provider's reconcile(): a host the provider is running that no lease accounts for is terminated and recorded (PROVIDER_ORPHAN); a lease whose host the provider no longer has is closed with PROVIDER_LOST; and every live lease has its deadline re-armed in the sweeper. Reconciliation is what makes the provider-independent sweeper trustworthy after a crash, and it is the same discipline the droplet service applies to its fleet.

10. Deployment spectrum

HostedJvm sits on the standard deployment spectrum:

  • Embedded — HostedJvmEmbedded with the local provider is a library any program can use to lease and drive JVMs on its own host with deadlines and quota — the mode every test uses.
  • Self-hosted — the ServiceServer run standalone on a user's own box or VPS, with the local provider as the default environment and the user's own cloud accounts registered. Keys stay on the user's hardware, per the Decentralized by Default philosophy.
  • Hosted — the operated instance on ContainerNursery at url://hosted-jvm/, default environment DigitalOcean in shared-key mode, with bring-your-own-key open. ContainerNursery hosts the service; it never hosts the leased JVMs.

11. Frontends

  • HostedJvmCli — in the MVP. User verbs mirror the manager (request, list, get, renew, terminate) and the handle (console with follow, health, utilization over a window, threads and heap which capture and store, captures to list and fetch them, invoke, stdin); an admin namespace covers install-provider, register-account, and environment CRUD. Local mode runs the Embedded in-process with the local provider; remote mode connects to url://hosted-jvm/, per the CLI architecture.
  • HostedJvmWui — a later milestone, following GOOD_DESIGN. The user face lists leases with a live deadline countdown, placement, a memory and thread utilization chart over a selectable window, the capture list with one-click heap and thread dumps, and a subscribed console; the admin face has an environments page and a keys page modelled directly on GithubProxy's — shared budget, registered accounts with validation state and usage, and an add-a-key form that forgets the credential as soon as the response is written. Stateless, url://-only, per the WUI rules.

Prioritization — MVP vs. mature platform

MVP — the smallest thing that is honestly "a hosted JVM with a deadline":

  1. HostedJvmApi, HostedJvmProviderApi, HostedJvmEmbedded with the local provider, leases, the sweeper on a Clock, environments, per-caller quota with parking, Event Log persistence, reconciliation — all proven by end-to-end tests that lease real child JVMs, drive them through JvmProcess, let a ManualClock pass the deadline, and assert the process is gone.
  2. The DigitalOcean provider over url://digitalocean-droplets/, one droplet per lease, deadline mirrored into the droplet expiry, tested end-to-end against a real droplet in CI the way the droplet service tests itself.
  3. HostedJvmServiceServer deployed to ContainerNursery at url://hosted-jvm/ with credential enforcement at the boundary; HostedJvmCli in local and remote modes; workloads from Maven coordinates and Blobstore JARs; utilization sampling with utilization(window) and stored heap and thread captures (§8); a ProductionHealth check that leases a tiny JVM, sees it healthy, and sees it die at its deadline.

Mature platform:

  • Runtime provider installation from a Maven coordinate (the MVP ships providers compiled in, behind the same SPI, so installation changes no contract).
  • The url://aws-ec2/ proxy and the AWS provider.
  • Kompile-built workloads from git; several leases packed per host; idle shutdown; port exposure.
  • HostedJvmWui with the keys page; W3Wallet allowances and settlement replacing the Embedded's own quota ledger; the console as a subscribed projection in every frontend.

Decision log

Decisions taken 2026-10-05, when this workstream was created:

  • A new API family, not an extension of RemoteJvmApi. RemoteJvmApi remains the per-JVM control contract every provider implements; HostedJvmManager vends a HostedJvm that extends JvmProcess with lease metadata. JvmLaunchApi is untouched. See §1.
  • Workloads come from LambdaServer's sources plus "nothing". Maven coordinate, Blobstore JAR, kompile build rule from git, or a bare agent-only JVM — always with the RemoteJvm agent attached. See §2.
  • Hard requirements are refused, never substituted; quota exhaustion parks, never rejects. See §3.
  • The deadline is mandatory, defaulted to the environment's 1-hour default, capped by the environment, renewable only forward, enforced by a provider-independent sweeper, with the provider's native expiry as a second net. Idle shutdown is designed in but post-MVP. See §4.
  • DigitalOcean goes through the droplet proxy; AWS waits for an url://aws-ec2/ proxy. HostedJvm never holds a raw vendor key. Bring-your-own-DigitalOcean-key means registering the token with the droplet proxy and handing HostedJvm the proxy credential. See §5.
  • Providers are runtime-installed Maven artifacts behind HostedJvmProviderApi; environments are admin records. See §5 and §6.
  • Identity and keys follow GithubProxy's registration model today and map onto W3Wallet pools and allowances later, with no API change. Quota units are concurrent leases, total memory, maximum lease length, and GB-hours per day. See §7.
  • The local provider is not the hosted instance's default. The ContainerNursery host is shared, saturated compute; the hosted default is DigitalOcean, one droplet per lease in the MVP, packing later. See §5 and §10.
  • Persistence is the Event Log with startup reconciliation against every provider. See §9.
  • HostedJvm is positioned as the general compute substrate; BuildTest, LambdaServer's executor pool, NetLabManager, and AiCliHostSupervisor are candidate consumers, not committed migrations. See relationship to other workstreams.
  • CLI in the MVP; WUI later, with its keys page modelled on GithubProxy's. See §11.
  • The service tracks utilization and owns dump captures. Health and memory are sampled by the service into a per-lease time series, and heap and thread dumps are stored captures in Blobstore with provenance, available after the JVM is gone; thresholds only observe and capture, never restart. See §8.

Open questions

  • Heap dumps off-host. JvmProcess.dumpHeap(outputPath, live) returns a path on the JVM's host. The hosted handle needs to return something the caller can fetch — a Blobstore reference. Does RemoteJvmApi grow an overload that takes a destination sink, or does HostedJvm override dumpHeap to interpret outputPath as a Blobstore name? The former keeps the contract honest for local callers too.
  • Requesting a runtime by version. RemoteJvmBuilder.setJvmSpec presumes the caller has enumerated getJvmSpecs() on the target host, which a hosted caller cannot do before placement. A requireJvmMajorVersion(n) on the builder — satisfied by discovery at launch, failing loudly if absent — seems right, and benefits RemoteJvmLocal users equally.
  • Machine images. Does the DigitalOcean provider install a JDK on a stock image at placement time (slow, simple) or maintain per-JDK snapshots (fast, a cache to invalidate)? The MVP installs; the answer determines how "placing" latency is budgeted.
  • Port exposure. A workload that wants to serve plain TCP/HTTP, not url://, needs a firewall rule and an address the caller learns. Which providers can offer it, and is it a request field or an environment capability?
  • Several callers, one JVM. Can a lease owner grant another caller console-only or invoke-only access to their JVM? Natural once W3Wallet attenuation exists; until then it is probably "no".
  • Billing for self-hosted and embedded. As for LambdaServer: the hosted instance meters GB-hours; is a self-hosted instance expected to settle through the shared W3Wallet hub, or does it opt out?
  • Sampling cadence and retention. How often utilization is sampled (per-lease RemoteJvm health calls are cheap but not free on a packed host), how long the series is kept after termination, and whether the per-environment aggregate is a stored projection or computed on read.
  • Pending-lease lifetime. A parked request waits for quota; should it also carry its own "give up by" instant so a caller who walked away does not get a surprise JVM an hour later?

Relationship to other workstreams

  • RemoteJvm (project) — the control contract HostedJvm vends and every provider implements; the two RemoteJvmApi additions above are this workstream's only asks of it.
  • DigitalOcean Droplets (project) — the DigitalOcean provider's only route to DigitalOcean, and the model for lease expiry plus reconstructed fleet history.
  • GithubProxy — the registration and keys-page model HostedJvm adopts for provider accounts, shared-key and bring-your-own-key alike.
  • W3Wallet — the destination for identity, allowances, and settlement; HostedJvm's quota ledger is the stand-in until then.
  • LambdaServer — shares workload sources (Maven, JAR, built-from-git) and the "a library first, a hosted service second" stance; its executor pool is a candidate HostedJvm consumer for isolation beyond the in-process child JVM.
  • Pluggable Execution Environments and Kompile Remote Build — the url://buildtest/ droplet pool provisions its own droplets today; a HostedJvm environment is a candidate runner backend, and the build optimizer's memory-aware sizing is exactly a JvmRequest. Candidate consumers, not committed migrations.
  • NetLab and AiCliHostSupervisor — both provision hosts for their workers today; both are candidate consumers for the same reason.
  • Blobstore, Event Log, Kotlin Build — workload bytes, persistence, and built-from-git workloads respectively.
  • ContainerNursery — hosts the service, never the leased JVMs; the layering is the same one the LambdaServer decision log records.
  • Manager–Daemon Pairing — a future "bring your own host" provider (a user enrolls a box they own as a placement target) is this pattern exactly; it is not among the initial three providers.

Graduation

There is no Documentation Repository project page for HostedJvm yet. This workstream graduates when the MVP tier is live: url://hosted-jvm/ on ContainerNursery leasing real DigitalOcean-backed JVMs with enforced deadlines, per-caller quota, the CLI, and a production health check watching a lease die on time. At that point add a Documentation Repository project doc and an ALL_PROJECTS entry, and it is no longer tracked as a workstream here.