Railway hosting
Self-hosting is the default model, but not everyone wants to run their own box. Railway hosting is Subspace’s managed offering: a real, dedicated instance per tenant, run by a fleet operator instead of by you, without giving up the trust properties that make Subspace worth self-hosting in the first place. It’s the same server, the same embedded Postgres, the same data model, just running on infrastructure someone else watches.
Tenant-per-instance, not multi-tenant
Section titled “Tenant-per-instance, not multi-tenant”Each tenant gets its own tenant cell: one Railway service, one volume-backed instance, running the same production image you’d run yourself. There’s no shared database, no shared process, no row-level tenant isolation to get subtly wrong. A tenant’s data lives in a Postgres cluster only that tenant’s cell process can reach, which is the same isolation model a self-hosted box gives you, just provisioned on demand instead of by hand.
The control plane
Section titled “The control plane”A fleet is only a managed offering if operating it is cheap, so the control plane ships alongside the fleet rather than as an afterthought:
- Tenant registry: one row per tenant, tracking the Railway service, the volume, the domain, the plan, and the deployed version.
- Idempotent provisioning: a new tenant is created through Railway’s API from a
template, a fresh service, a fresh volume,
SUBSPACE_PROFILE=cloud, a claim token, and a KMS-wrapped key reference. A Railway-generated domain is available immediately; a customslug.subspace.appsubdomain attaches asynchronously (certificate issuance is not instant). Provisioning is zero-touch: request a tenant, get a claim link. - Fleet operations: staged upgrades roll out to a canary tenant first, then
cohorts, gated on workflow replay-compatibility checks so an in-flight agent run
never gets stranded mid-upgrade by a bundle change. A Postgres major-version bump
rides the same
pg_upgradeorchestration a self-hosted instance uses, forced through a pre-upgrade backup first. - Monitoring: every cell exposes a status endpoint (version, migration state, backup freshness, disk usage, queue depth), polled by the control plane so a silently-stopped backup job is caught by monitoring, not by needing a restore.
The device execution lane
Section titled “The device execution lane”Cloud hosting changes the right trust story for anything that executes on a machine:
terminals, coding agents, os.exec, Jupyter kernels, and macOS automation don’t run
on the tenant cell in the cloud. They run on your paired device, the same laptop
or desktop you’d run Subspace on yourself, with the cell acting as the durable
coordinator rather than the executor.
This is the authorization inversion that makes hosted execution safe: a cloud process holding your shell is a different, much larger blast radius than a cloud process holding your notes. So the execution capability moves to the device you already trust, and the cell only ever dispatches a job to it.
- A durable job (queued, offered, awaiting device approval, running, and a terminal
state including
succeeded,failed,cancelled,expired, orunknown) is the unit of work between the cell and your device.unknownmatters as much as the success states: if a device’s acknowledgment of a non-idempotent command is lost, the job goes tounknownrather than being silently retried, so a command that might have already run never runs twice by accident. - Every dispatch passes a seven-point authorization check: the run’s tenant matches the target tenant, the run’s owner is an active member, the target device belongs to that same owner (an admin’s elevated status does not satisfy this), the device has advertised the capability the job needs, the agent definition grants that capability, cloud policy allows it, and your device-local consent policy (ask every time, allow a signed action, or deny while locked) allows it too.
- Terminals and coding agents keep running on your device even if the socket to the cell drops, including Railway’s periodic WebSocket rotation described below; the local process survives the disconnect and a reconnect resumes streaming from where it left off, rather than the job dying with the socket.
- Server-host execution (running these same capabilities on the tenant cell itself) stays available, but only in local, self-hosted mode. On a hosted tenant, it simply isn’t an option, by design, not by policy toggle.
This is the same reason coding agent dispatch already distinguishes where code actually executes: hosted Subspace pushes that distinction all the way through the product, not just for agents.
Storage durability
Section titled “Storage durability”A tenant cell’s data lives on its Railway volume at $SUBSPACE_HOME, exactly the
home directory layout a self-hosted
instance uses. On top of that:
- Per-tenant encryption key escrow: each tenant has its own data-encryption key. The control plane escrows only a KMS-wrapped copy of it, never the plaintext key, so a control-plane compromise alone can’t decrypt tenant data.
- WAL-G and blob backups: the same continuous WAL archiving and blob replication described in backup and restore run per tenant, writing to a per-tenant storage prefix.
Public-exposure hardening
Section titled “Public-exposure hardening”A tenant cell is, unlike a self-hosted instance, reachable from the public internet by
design, so it runs under a dedicated cloud capability profile
(SUBSPACE_PROFILE=cloud) that closes the gaps a loopback-only deployment doesn’t
need to think about: claim-token provisioning
is mandatory rather than optional, internal broker and workflow endpoints bind on a
separate loopback-only listener that never rides the public port, and the profile
disables every capability that only makes sense with a trusted local operator at the
keyboard, server-host os.exec, container execution, server-host PTY spawn, Jupyter
kernel spawn, and local MLOps launchers, in favor of the device execution lane above.
Client pairing and reconnect
Section titled “Client pairing and reconnect”All four clients, web, desktop, mobile, and the browser extension, pair against a hosted, claimed tenant the same way they pair against a self-hosted instance. Because Railway recycles public WebSocket connections roughly every 15 minutes and doesn’t offer sticky sessions, every satellite socket, the desktop PTY relay, the extension’s job queue, mobile, proactively reconnects a few minutes ahead of that rotation, with jitter so a fleet’s sockets don’t all reconnect in the same instant, and resumes from a cursor rather than losing anything mid-stream. A forced reconnect on a hosted tenant should never be visible as lost terminal output or a dropped job.
Google OAuth restricted-scope verification
Section titled “Google OAuth restricted-scope verification”Gmail and Google Calendar connectors need Google’s restricted-scope verification, a security assessment separate from ordinary OAuth app review, before mainstream users can connect their accounts on a hosted tenant. Because a hosted deployment serves a public origin (unlike a self-hosted instance, where each operator registers their own OAuth credentials), the verification and the redirect-URI design are centralized: Google doesn’t allow wildcard redirect URIs, so the control plane runs one registered OAuth callback and relays the result to the correct tenant rather than each tenant subdomain registering its own.
Billing, quotas, and compliance
Section titled “Billing, quotas, and compliance”Hosted tenants run under plan-based quota enforcement wired to the control plane (suspension and hard caps on overage), with LLM usage metered at the same broker seam that already mediates every model call, so usage-based limits don’t require a second accounting path. Documented recovery-point and recovery-time objectives, published data-retention and deletion policies, and supply-chain practices (image signing, SBOM, vulnerability scanning) round out what makes this a sellable managed service rather than just “self-hosting, but we run the box.”