Many people keep their code on a remote machine: a build box, a dev server, a GPU host. BeyDock can run sessions there. The files, git, and provider sign-ins are all on that host, and the agent works where the code is. The hard part is that an SSH connection is fragile. Laptops sleep, Wi-Fi drops, and a process that dies with the connection takes your agent's work with it. This build log explains how we avoided that.
The sidecar moves to the host
The app talks to a Node sidecar over one JSON object per line. Locally, the sidecar is a child of the app. On an SSH host it runs there, so commands such as listing folders, reading files and running git happen on the host. File paths in a remote session are the host's paths. A feature that opens a local file, like "show in Finder", is limited to local sessions.
The sidecar is deployed automatically. The app hashes the sidecar's files into a 16-character version, then checks the host for ~/.beydock/sidecar/<version>. If it is missing, the files are streamed with tar | ssh tar, and npm install --omit=dev runs there. A symlink named current then points at the new version. Each version has its own folder, so a deploy never overwrites files a running process is using. SSH options are BatchMode=yes and keepalive every 15 seconds, which is why key-based login is required.
A daemon that outlives the connection
On the host, the real work is done by daemon.mjs, a detached process listening on a Unix socket at ~/.beydock/run/sidecar.sock (mode 0600, in a 0700 folder; no TCP). The app does not start it directly. It runs attach.mjs over SSH. attach.mjs connects to the socket, starts the daemon if it is not running, and pipes stdin and stdout through. When SSH dies, only attach.mjs dies.
Two attaches racing to start the daemon is handled with lock files created with the exclusive flag, since Node has no flock. One wins, and the other waits for the socket.
The daemon exits by itself after 15 minutes with no client, no live session, no active workflow trigger and no running workflow. Nothing stays on a host you stopped using.
Reconnecting without losing anything
The protocol is the same line-per-JSON protocol, with a handshake in front. The app sends hello with its lastSeq, the last event number it processed. The daemon answers ready with its own state, then replays every event after that number, in order, then sends replay_done.
Every event the daemon emits carries an increasing seq. They are held in a ring buffer of the last 20,000 events or 64 MB, whichever fills first, and each session also has a journal on disk, kept for seven days after the session ends. If the app was gone for so long that the buffer no longer reaches back far enough, the daemon sends replay_truncated and the app reloads those transcripts from the journal.
Messages that change state (start, send, permission replies) carry an opId, and the daemon never applies the same one twice. So after a reconnect the app can resend without sending your message twice.
The app also reconnects on its own: with backoff, and right away when the Mac wakes, when the network path changes, or when you send a message. While disconnected, the session shows a banner saying the host is reconnecting. Messages you type are queued and sent on connection. Permission requests that arrived while you were away come back as cards. When you return, you get one notification: "While you were away on build-box", summarizing finished sessions and anything waiting for approval.
Updates without killing work
The sidecar version is part of the handshake. If the daemon is older but speaks the same protocol, the app sends upgrade_when_idle, and the daemon restarts only when its last session and run finish. If the protocol differs, the Devices page asks for "Restart Now". An optional "Keep running after host restarts" switch installs a service unit, after showing you exactly what it will write.
The Hub, through a tunnel
When the Hub is on, remote sessions can use its tools, but the secrets stay on your Mac. The SSH connection carries a reverse tunnel, -R 127.0.0.1:0:127.0.0.1:<hub port>, so the remote side picks its own free loopback port. The app reads it from SSH's stderr.
That port changes whenever the tunnel is rebuilt, but sessions that live in the daemon have already been told a Hub address. So the sidecar runs a small relay on a stable local port, saved in ~/.beydock/hub-relay.json, and forwards connections to whatever the tunnel's port is now. With no tunnel, the relay refuses the connection immediately, instead of leaving the agent hanging.
Known limits
On Linux hosts sandbox-exec does not exist. Delegated Claude, Antigravity and OpenCode children on a Linux host rely on BeyDock's command checks, not an OS sandbox, and Antigravity children that edit files get no shell there. Codex children keep Codex's own sandbox. We would rather say so than pretend.
The host daemon is still in early access testing.