| 1 |
# Workspaces: VS Code in a microVM, the runner side |
| 2 |
|
| 3 |
**Status: paused** (Andy, 2026-10-17): too large to take on, and to keep up, |
| 4 |
before the work already promised is delivered. Nothing below is built; the |
| 5 |
plan stands for when it is picked up again. |
| 6 |
|
| 7 |
oxygenhub#69, hub spec §13.3. Written 2026-10-17 with Andy's decisions: |
| 8 |
build it, **runner side first**; files **lazy from the start**; editor |
| 9 |
traffic over a **WebSocket with multiplexing**. This is the plan for the |
| 10 |
runner; the hub's part (the workspaces table, the button, the proxy, |
| 11 |
commits) follows it. |
| 12 |
|
| 13 |
## What is there today |
| 14 |
|
| 15 |
- Jobs run to their exit under a hard time limit, with a job token that |
| 16 |
lasts at most two hours (hub `JOB_TOKEN_MAX_AGE_MS`). |
| 17 |
- A microVM has no network device. Its only traffic is guest-initiated: |
| 18 |
the egress proxy over vsock (`flow-init/src/egress.rs`). |
| 19 |
- The runner speaks to the hub in short HTTPS calls; nothing long-lived. |
| 20 |
- flow-casfs mounts a state lazily with FUSE, on the host. Firecracker has |
| 21 |
no virtio-fs, so a FUSE mount on the host cannot be handed to a guest. |
| 22 |
- The pinned guest kernel (6.1.155) has overlayfs and vsock, but no FUSE. |
| 23 |
|
| 24 |
## The shape |
| 25 |
|
| 26 |
``` |
| 27 |
browser ──HTTPS/WS──► hub /{owner}/{name}/workspace/{id}/ ──┐ |
| 28 |
│ one WebSocket per workspace, |
| 29 |
runner ◄────────────────────────────────────────────────────┘ opened by the runner (yamux streams) |
| 30 |
│ vsock (host connects in) |
| 31 |
▼ |
| 32 |
microVM: flow-init ─► openvscode-server on 127.0.0.1:3000 |
| 33 |
/workspace = overlay(lower: casfs over vsock, upper: scratch disk) |
| 34 |
``` |
| 35 |
|
| 36 |
## Steps |
| 37 |
|
| 38 |
Each is an issue of its own, and each lands working and tested. |
| 39 |
|
| 40 |
1. **A guest kernel with FUSE.** The same 6.1 kernel and Firecracker's |
| 41 |
guest config, with `CONFIG_FUSE_FS=y`; pinned by BLAKE3 in `flow.toml` |
| 42 |
on earth-1, plex-1 and mars-1. Pipeline jobs are unaffected. |
| 43 |
2. **casfs over vsock.** flow-casfs gains a server half: on the host it |
| 44 |
answers lookups, listings and reads for one state over a vsock port of |
| 45 |
the workspace's VM, from the same graph-key cache it uses today. The |
| 46 |
guest half is a small FUSE client in the workspace rootfs. `/workspace` |
| 47 |
is an overlay: the state read-only below, the VM's scratch disk above, |
| 48 |
so edits are plain files. Opening a workspace costs a listing, not an |
| 49 |
export; the target is under a second on the Linux tree. |
| 50 |
3. **The workspace rootfs.** openvscode-server (pinned release, its |
| 51 |
checksum checked), `oxy`, the Oxygen VCS extension, a non-root user; |
| 52 |
built and imported like the rust image (`images/workspace`). |
| 53 |
4. **The `workspace` job kind.** `Claim` says `kind: "workspace"`. The |
| 54 |
runner keeps the VM until it is told to stop, or the hub's idle limit |
| 55 |
passes, instead of waiting for an exit; it reports liveness and open |
| 56 |
connections every 30 s, and the hub renews the job token while the |
| 57 |
workspace lives. A runner advertises whether it takes workspaces. |
| 58 |
5. **The tunnel.** flow-init accepts host connections on a vsock port and |
| 59 |
forwards each to openvscode-server. The runner opens one WebSocket to |
| 60 |
the hub per workspace (`/api/v1/flow/workspaces/{id}/tunnel`, its |
| 61 |
runner token) and carries each browser connection as a yamux stream to |
| 62 |
that port. Works for runners behind NAT and through Cloudflare. |
| 63 |
|
| 64 |
Then the hub (hub spec §13.3): the `workspaces` table and limits, "Open in |
| 65 |
workspace" behind a site setting (off), the owner-only reverse proxy for |
| 66 |
HTTP and WebSocket into the tunnel, and commits pushed to `ws/{account}/{id}` |
| 67 |
and signed by the hub as the account's delegate. |
| 68 |
|
| 69 |
## Downloads this needs |
| 70 |
|
| 71 |
Linux 6.1 sources (kernel.org) and Firecracker's guest config for step 1, |
| 72 |
and an openvscode-server release (GitHub, Gitpod) for step 3: each pinned |
| 73 |
and checked against its published checksum, as cosign was. |