Repository navigation
Description
Parent
Part of #154 (remote agent sessions). Phase 4. Design doc docs/designs/remote-workrooms.md: Provider Decision, premise 8, the Success Criteria item "The same workroom behaves identically on two different providers", and Next Steps item 5 ("the container fixture alone does not establish parity across two real providers"). Provider survey: docs/designs/oq20-remote-git-credentials.md, "exe.dev" and "Other providers" (PR #248).
The provider: exe.dev (decided 2026-09-27, owner)
What is already known:
- Persistent Linux VMs reached over ssh (
ssh exe.dev), with the same commands over an HTTPS API (POST https://exe.dev/exec). The owner has an account. - No git config to override. The published image (
boldsoftware/exeuntu) and a live VM carry no credential helper and noinsteadOf: the only git config at any level isinit.defaultbranch=mainin~/.gitconfig, and there is no/etc/gitconfig. A VM's first-boot script is the user's own--setup-script. - Outbound HTTPS is open, so the broker at
codaset.devis reachable. cp <vm>copies a VM, and copies its tags by default. Whether it copies memory or disk only is not documented; find out, because it decides what a copy inherits from the base.- exe.dev's own GitHub integration and its integrations generally go unused under OQ20. Its Identity Federation (VM-minted OIDC tokens) is per integration, not per VM, so the enrolment code stays the way a VM proves who it is.
- It is ssh-shaped like boxd, so it tests the driver seam less hard than an SDK-transport provider would. Accepted.
What to build
A HostDriver for exe.dev through the portable derivation path (#252), so the agnosticism decision is tested against two real providers rather than one provider and a fixture.
- Drive exe.dev's ssh API, wrapping its
--jsonoutput in the driver's own result, as the boxd driver wrapsboxd --json. - The base machine and
deriveFromBaseon exe.dev: derive withcpor a fresh VM, whichever is correct first; re-init machine identity; enrol after the copy; fetch and check out; serve. - A supervisor that starts the agent at boot with
--screenson durable disk. - Driver traits declared honestly: transport kind, derivation speed and whether it carries live processes, disk durability, maximum lifetime, how exe.dev defines idle, egress to the broker, and machine identity.
- A lifecycle shim if exe.dev's idle behaviour needs one (see Far-side lifecycle shim: keep a busy box awake through the provider (boxd first) #257), or a recorded reason why it does not.
Acceptance criteria
- A remote workroom can be created, used and destroyed on exe.dev from the app.
- The same workroom behaves identically on boxd and on exe.dev, differing only in creation latency: terminal, Changes, History, the diff viewer, the Files tree, the badges, forwarded ports, and a push with no client attached.
- Two workrooms derived from one base on exe.dev share neither an enrolment key nor an ssh host key nor
/etc/machine-id. - After an exe.dev restart, the workroom's panes show their last screen.
- The driver's traits, including what
cpcopies, are recorded in the design doc.
Blocked by
#252 (portable derivation on the container driver). The parity criterion also needs #256 (boxd driver).
Filed by Claude Code at the owner's request, 2026-09-27. Provider decided at the owner's request, 2026-09-27.
Activity
[-]Second real provider driver (proposed: exe.dev)[/-][+]exe.dev driver: the second real provider[/+]docs(remote-workrooms): mark #254, #255 and #257 as merged
joelmoss commentedon Oct 8, 2026
🤖 Left by Claude Code (at the owner's request). Updated 2026-10-08 after
/plan-eng-review: changes are marked (review).Status (2026-10-08): built on branch
feat/259-exedev-driver, not merged. Unit suite green; the exe.dev live parity suite passes all 7 cases, rerun after the switch to normal ssh (6a3ede0). The boxd live suite passes 8 of 9:testAnIdleBoxStillSleepsWithTheAppAttachedfails: the app's own 10 s status poll keeps a connected box BUSY, so it never sleeps (#380). The same test passed on 2026-10-06, so it is a regression; this branch's boxd code behaves the same as master, and whether the cause is on master or on boxd's side is not yet bisected. The soak passed: an idle VM and a busy one ran 9 h with no sleep (sameboot_id, no tick gap over 61 s), sosleepsWhenIdlestaysfalse. Pool load is measured: two workrooms building at once share the plan's 2 vCPU, each build about 1.7 times slower than one alone (3715064). Left: the manual check of New Workroom › Remote › exe.dev from a Finder-launched app. Details: "As built (#259)" indocs/designs/remote-workrooms.md. Changes from this plan are marked (built) below.Spike results (2026-10-08, live account, region
lon)Six throwaway VMs (four in the spike, two for the review's tag probe), all removed afterwards. Every claim below was measured, not read from the docs.
Derivation:
cpis a cold copy of the flushed disk.cp <base> <name> --jsonreturns in 0.4-0.5 s ({"elapsed":0.42,…,"state":"starting"}). The copy answers ssh about 2 s later.sleep) and a file in/dev/shmwere both gone. The copy has its ownboot_id.sync(48 MB still dirty in the page cache) was missing from the copy. So the derive mustsyncthe base first, as on boxd. The base keeps running throughout.hostnameequals the VM name./etc/machine-idis new (written byexe-initat boot, same mtime as/etc/hostname). The gateway's per-VM sshd host key under/exe.dev/etc/sshis new.boxd.shworks unchanged on exe.dev (sudo sh -s -- exedev <socket> <screens>exits 0, both units active).workroom-identitysaw the new hostname and minted within about 2 s. It wiped the copiedbroker.jsonand screens, andworkroom-agentcame up.restart, the marker matched, so the copy kept itsmachine-id, itsbroker.jsonand its screens.The VM itself.
exedev, with passwordlesssudo./runis tmpfs and/is ext4, so the agent's state goes under/home/exedev/.local/state/workroom, as on boxd./etc/sshhost key. exe.dev runs its own sshd (/exe.dev/bin/sshd, config/exe.dev/etc/ssh).Transport.
exe.devand every*.exe.xyzVM present one RSA key,SHA256:JJOP/lwiBGOMilfONPWZCXUrfK154cnJFXcqlsi6lPo(the fingerprint exe.dev's docs publish).User exedev,IdentitiesOnly, and aknown_hostsholding only*.exe.xyz <gateway key>withStrictHostKeyChecking yes.printf hello | ssh … wc -cprinted 5),-ttgets a pty, and a remote exit status comes back (3 came back as 3).whoami --jsonlistsssh_keys[].fingerprint, which matched~/.ssh/id_ed25519.pubhere.-F /dev/null,IdentitiesOnly yes) exe.dev answeredPermission denied (publickey,keyboard-interactive)with neither the agent nor the Keychain. It authenticated withUseKeychain yesalone, withIdentityAgent $SSH_AUTH_SOCKalone, and with both.CLI shapes.
new --name workroom-<uuid> --jsonaccepts the 45-character name. It returnsvm_nameandssh_host.ls --jsonreturns{"vms":[…]}, each entry withvm_name,status(running),created_at,allocated_cpus,memory_capacity_bytesanddisk_capacity_bytes.restart --jsonreturns{"status":"restarted",…}. The VM came back in about 4 s, with a newboot_idand its disk kept.restart:{"error":"VM \"<name>\" not found"}, exit 1.cp:{"error":"vm \"<name>\" not found"}(lowercasevm), exit 1.rmexits 0 even for a VM that doesn't exist. It prints the error and then{"deleted":[],"failed":["<name>"]}, sodestroymust readdeleted/failed, never the exit status.whoami --jsonhasemail,regionandssh_keys, and no user ID, so the email is the account check.stop,startor idle timer.new --tag <t>tags a VM,ls --jsonliststagswhen a VM has any, andcp --copy-tags=falsegives a copy none.max_cpus 2,max_memory_gb 8,max_vms 50, shared by every VM.Idle. Three VMs sat idle for 40 minutes (12:55 to 13:35 BST), with the control plane polled every 60 s and no traffic to the VMs. All three read
runningthroughout. Afterwards the base still had the sameboot_id, itssleepprocess and its/dev/shmfile, so it was never suspended or restarted. The CLI has no idle timer to set, and the docs say nothing about idle suspension. Not measured: anything longer than 40 minutes.Decisions (owner, 2026-10-08)
--standalone.ssh exe.devin Terminal: no-Fand no key of its own, so the user's~/.ssh/config, agent and Keychain pick the key. A second exe.dev account is aHost exe.devblock in~/.ssh/config, as for any ssh, and the account check still refuses another account's VMs. (The first build chose a key itself, the first of~/.ssh/*.pubexe.dev accepted underIdentitiesOnly; that could pick the wrong account's key, and the owner asked for ssh to behave as close as possible to normal ssh.) Nothing is added to the exe.dev account. A Workroom-registered key was considered and dropped: it adds an account-wide key, and the planned generic SSH driver (Generic SSH driver: remote workrooms on any VM or server over plain ssh #378) can't register keys on arbitrary servers, so both share this path.HostPresenceDriverprotocol yet: exe.dev VMs don't sleep, so it would have one adopter. boxd's sleep, presence and let-go code stays boxd-specific until a second sleeping provider exists.Plan
1. Driver seam refactor (boxd only, no behaviour change, its own PR)
Rule: provider-specific behaviour lives on the driver, as traits or optional protocols.
RemoteHostsasks only whether a host is a local container or a remote provider. No registry and no factory.DriverKey.isBoxd, meaning "broker only, no relay, reports steps"isRemoteProvider(anything not.container); add.exeDev(account:)clonePath:isBoxd ? BoxdHostDriver.user : userexistingDriver(holding:),runtimeIsMissing,environment'sgitHubToken, AppStore'scodasetRequiredand step reporting (review)ghtokenagentSocket(of:):as? BoxdHostDriver(review)BoxdHostDriver.poll(review)extension HostDriver, shared by both drivers; the check strings stay per driverdriverName:runtime?.rawValue ?? boxdDriverPlaceand the literal[.remote(.boxd)]in the picker and the sidebarPlace.remoteProviderslistHostDescriptor.org/accountaccount(thewhoamiemail) and noorg; no new fieldsResources/host-setup/boxd.sh(review) First commit: three table-driven tests in
RemoteHostsTestsover the boxd and exedev keys, shown to fail when their branch breaks: a remote-provider create without Codaset throwscodasetRequired; a remote-provider environment has nogitHubToken; a remote-provider agent socket is the driver's own home-disk path. Then the refactor.After this, a new provider means one driver file, one
DriverKeycase and onePlaceentry. A driver that can't provision (#378) answerscreateandderiveFromBasewithnotImplemented. The CLI helpers inBoxdHostDriver(cli,CLIFailure,decode) stay where they are: exe.dev's error and exit conventions differ (above), so there is nothing shared to extract yet.2.
ExeDevHostDrivercreate:new --name workroom-<uuid> --tag workroom-base --no-email --json, then (built) wait for systemd to finish booting (the live suite foundnewreturns, and ssh answers, while the VM still boots: the setup'ssystemctlfailed with "Failed to connect to bus" on four of seven creates), run the setup script withsudo sh -s, then wait for the identity marker.deriveFromBase: (review) first refuse a source whosels --jsondoesn't listworkroom-base, since every driver must refuse to derive from a workroom instance. Thensyncon the base, thencp <base> <name> --copy-tags=false --json. Then wait for the identity marker (hostname = name) and for the agent. There is no reboot. A failure removes the copy by name.destroy:rm <name> --json. It decodes the last JSON object on stdout and readsdeleted/failed. It counts the not-found line (VM "<name>" not found, orvmlowercase fromcp), matched whole, as already removed.Account guard: check the
whoamiemail before every create, derive, destroy and rollback, as boxd'scheckOrgdoes.Transport: the control plane runs as
ssh exe.dev … --json, and each VM is reached asexedev@<name>.exe.xyz. (built, 6a3ede0) Both run/usr/bin/sshover the user's own configuration with only what a background link needs set as-o(ExeDevHostDriver.sshOptions):BatchMode yes, no agent, X11 or port forwarding, no multiplexing,RemoteCommand none,RequestTTY no(a pane's-toutranks it),EscapeChar none, keepalives (Persistent remote agent: supervised serve, stdio relay and the container fixture #228),ConnectTimeoutandLogLevel ERROR. They go through a newContainerHostDriver.Route(options, destination, environment); container and boxd keep their own-Fconfig. Moving boxd onto this path with Generic SSH driver: remote workrooms on any VM or server over plain ssh #378 is boxd driver: reach machines through the user's normal ssh, shared with exe.dev and the generic SSH driver #379.SSH_AUTH_SOCK(the app's, else the login shell's) is passed to ssh; the user's ownIdentityAgentwins over it. Measured: with a bare environment, ssh signed in through launchd's agent and through the Keychain alone.known_hosts: a VM's key is checked against theexe.deventry (HostKeyAlias exe.dev), since the gateway presents one key for both. Measured: without that entry, "Host key verification failed". Nothing is compared with the published fingerprint: a fingerprint built into the app would need an app update after a rotation.ssh exe.devonce in Terminal (it must sign in without asking), naming the published fingerprint.Traits:
sshStdioderiveSpeedderiveCarriesLiveProcessesfalsedurableDisktrue(writes stream off-machine continuously)maxLifetimenilkeepAwakeHoldsCredentialfalsesleepsWhenIdlefalse(40 minutes measured; overnight soak before merge)Lifecycle shim: none, and recorded as a reason in the design doc: a VM is never put to sleep while idle, so nothing needs keeping awake. The Far-side lifecycle shim: keep a busy box awake through the provider (boxd first) #257 heartbeat is harmless here.
(review) Before merge: an overnight soak (8 h or more) of one idle VM and one running a detached CPU job with no client, polling the control plane only. If either sleeps,
sleepsWhenIdleflips and the shim question reopens.Debug builds: fix(macapp): Debug builds talk only to a local Codaset; requests name the build #278's broker reverse forward runs over the agent's service connection (
ReverseForwardRegistry), not ssh-R, so it needs nothing per provider.3. App wiring
whoamiagainst the host's account.4. Tests
ExeDevHostDriverTests: a fake runner. It covers JSON decoding, thermwith exit 0 and afailedlist, both not-found spellings, (built) the ssh it runs (no-For key, the forced-oset,HostKeyAlias, the agent socket on a pane's attach), the error when ssh can't sign in, refusing an untagged source,syncbeforecp, and rollback.BoxdIntegrationTestsrun per provider (WR_EXEDEV_TESTS=1besideWR_BOXD_TESTS=1, the same cases) instead of copying it. It covers create, use and delete; distinct identities; restart keeping the last screens; and a push with no client attached. The boxd-only keep-awake cases stay boxd-only./etc/sshon boxd,/exe.dev/etc/sshon exe.dev), and asserts the gateway key separately as the one key shared on purpose.cp, the identity wait, the agent wait.5. Docs
cpcopies (an acceptance criterion), and the soak and load results.Acceptance mapping
/etc/machine-idmachine-idand the gateway host key;workroom-identitywipes enrolment (spike); the parity suite checks/exe.dev/etc/sshrestart)cpcopiesNot in scope (review)
HostPresenceDriverprotocol, until a second provider sleeps.test(macapp): exe.dev error text, a VM left behind, a refused key, th…
fix: pre-landing review fixes (#259)
fix(macapp): exe.dev's account commands get the login shell's ssh age…
docs: a second exe.dev account is a Host exe.dev *.exe.xyz block (#259)
fix(macapp): an exe.dev VM's spawned ssh gets the login shell's PATH (#…
docs: exe.dev's ssh agent and PATH rules, the provider tooltip and st…
test(macapp): pin the remote-provider credential and socket rules bef…
refactor(macapp): a driver seam for remote providers, boxd only, no b…
20 remaining items
fix(macapp): an exe.dev base waits for systemd, and a failed listing …
test(macapp): one parity suite for every real provider, run on boxd a…
docs(remote-workrooms): as built for the exe.dev driver (#259)
perf(macapp): a new exe.dev driver signs in with the key found before (…
fix(macapp): exe.dev runs the user's own ssh, not a key Workroom picks (
docs(remote-workrooms): the boxd live suite after the #259 refactor, …
chore(macapp): exe.dev comments match the user's own ssh; drop an unu…
docs(remote-workrooms): exe.dev soak and pool load results (#259)
test(macapp): exe.dev error text, a VM left behind, a refused key, th…
fix: pre-landing review fixes (#259)
fix(macapp): exe.dev's account commands get the login shell's ssh age…
docs: a second exe.dev account is a Host exe.dev *.exe.xyz block (#259)
fix(macapp): an exe.dev VM's spawned ssh gets the login shell's PATH (#…
docs: exe.dev's ssh agent and PATH rules, the provider tooltip and st…
docs: the exe.dev seam note after #380 removed the let-go (#259)