Your agents don’t have to live on your laptop
Remote Hosts runs Daintree's whole backend on another Mac or Linux machine over your own ssh. Close the laptop and the host keeps your agents running.
Running a few coding agents on a laptop is fine. Running a dozen is where it starts to hurt. They're tied to the one machine you also carry around, and when the laptop sleeps, every agent on it stops with it.
My own setup is four Mac Studios driven from one laptop. The compute lives in the Studios; the review happens wherever I am. Power cuts were a real design constraint too: whatever I built had to come back by itself.
The usual answer is tmux on a box you reach over ssh. It works. You keep the terminals, and the worktrees, agent states, diffs and review become separate tools you assemble yourself. Remote Hosts is how Daintree, a review-first workspace for running agents in parallel, handles it now.
The whole backend, on another machine
A Daintree window can run its whole backend on another Mac or Linux machine, a host. Projects, Git worktrees, terminals, agents, plugins and the MCP server all live there, and the window on your laptop shows them and takes your keyboard. Close the laptop and the host keeps the agents running. Open it again and the window reconnects, the terminals catch up on what they printed meanwhile, and anything waiting for review is still waiting.
There's no separate server build. Every copy of Daintree on macOS and Linux can be both the window you use and a host for your other machines. The split is explicit: at the time of writing, 565 of the app's IPC channels run on the host, 388 run on the machine in front of you, and 62 are hybrids that must spell out how they divide. There's no default, so every new channel has to pick a side.
The shape isn't new. VS Code's Remote-SSH installs a server on the remote machine and runs most extensions there, with themes and other UI extensions kept local. The difference is what lives on the other end: the whole habitat, with a dozen agent terminals that have to keep running whether or not anyone is watching.
Your own ssh, no relay
There's no relay, no cloud service and no account behind this. Daintree runs the system ssh binary, so your ~/.ssh/config, keys, ssh agent and jump hosts apply unchanged. Tailscale SSH, which authenticates with Tailscale's node keys instead of yours, applies too, provided it isn't stopping to ask you to sign in.
Daintree adds a few ssh_config options of its own. BatchMode=yes means ssh never prompts: a password or host-key question fails the connection instead of hanging on a prompt nobody can see. ForwardAgent=no and ForwardX11=no keep your agent and display away from the host. ControlMaster=auto with ControlPersist=10m gives each host one shared connection that carries everything, forwarded ports included. You can run the same kind of check yourself:
ssh -o BatchMode=yes you@studio-01.local 'echo ok'If that prints ok, the ssh part is done, and the Add host dialog checks the rest. If it prints an error, that's the error setup will quote. The trade-off is no password prompts, ever. I'm fine with that: a desktop app that stores ssh passwords is a worse idea than one that asks you to run ssh-copy-id.
A private socket, not a port
Daintree opens no network port of its own for the link. On the host, Host mode listens on a Unix socket created 0600 inside a 0700 folder, and won't use that folder if it's a symlink or belongs to another user. The folder matters as much as the file: the unix(7) man page notes that some systems ignore a socket file's permissions and that portable programs shouldn't rely on them for security. Beside the socket, an owner-only host.json holds a token of 32 random bytes, new every launch. Your machine reads it over ssh, forwards a local socket to the host's, and presents the token before anything else is accepted.
Some ssh servers forbid Unix-socket forwarding. OpenSSH's sshd_config documentation points out that this doesn't improve security unless users are also denied a shell, because they can install their own forwarders. So the fallback is an ssh session running daintree --attach-stdio. Same login, different pipe. Other machines reach a host only through ssh as that user, and Remote Hosts adds no privilege beyond that login.
One machine has control. Git moves the work.
Two of my machines watching the same agent is fine. Two typing into it is not. So one machine at a time has control of a host project: its windows type into terminals, change worktrees and receive the agents' MCP actions. Every other window showing the project is viewing. Output updates live, typing is refused, and Take control is one click. It's a coordination lock, not a permission.
Committed work moves between machines through Git. Switching hosts never pushes, clones or creates anything. Carrying a branch across is the separate, explicit Continue this branch on…, which finds the host's copy of the repository or clones one, checks where your branch stands against its remote, and opens a worktree for it there. With unpushed commits it offers to push them, never as the preselected choice, and previews up to 20 of them before it publishes. Pushes run on the machine you're leaving, with its credentials. Clones run on the machine you're going to, with its own. Uncommitted changes stay where they were made.
Credentials are never copied or synced. Each host signs in to its own agent CLIs, forges and plugins, and what it stores stays there. Daintree reports which agent CLIs a host has, but it never installs one there because it exists on your laptop. A host is configured as itself.
What it took to make headless Linux real
The Mac side came together first, because that's where I work, and Linux hosts had only been checked in unit tests and CI. Then I tried a real one, a two-vCPU Ubuntu 24.04 VM with no display, 150 ms away. Host mode didn't work at all.
The service died within a second, every time, with Missing X server or $DISPLAY. Daintree had already decided to run headless, and logged that it had: it appended --ozone-platform=headless from its main process. Chromium does choose its display backend from that flag, at startup, and on Linux the default is X11. But it chooses before any of Daintree's code runs, so a flag added from the main process arrives too late. The same binary started fine with the switch on its real command line, so the systemd user unit now passes it, always. The lines that matter:
[Service]
ExecStart="/opt/Daintree/daintree" "--host-mode" "--ozone-platform=headless"
KillMode=mixedA service that outlives your login shouldn't depend on an X server that goes away with it anyway.
That fix uncovered the next few. Setup had waited 60 seconds on a crash-looping service, then told you to go read journalctl. Now it watches the restart count, stops after two, and quotes the journal's first error. Every stop, including the one at every reboot, dumped core. systemd's default KillMode=control-group terminates every process in the unit, so Chromium's helpers died mid-shutdown and the main process tripped a fatal check on its way down. KillMode=mixed sends SIGTERM to the main process only and saves SIGKILL for whatever is left, so Daintree closes its own helpers.
Linger is what keeps a Linux host serving with nobody logged in. loginctl enable-linger starts a user manager at boot and keeps it after you log out, and setup runs it for you, without sudo where the distribution allows. On the test VM, Host mode was back by itself 17 seconds after sudo systemctl reboot. A second user with no sudo at all got a working host from the AppImage, which needs no root.
What 150 ms feels like
These numbers come from that VM. Keystroke echo had a p50 of 150.1 ms over 100 samples. File search and diffs landed within a few milliseconds of the round trip. The host adds next to nothing; what you feel is the network.
The link multiplexes five priority lanes to limit how much one kind of traffic holds up another. To check, I had four terminals each print about 18,600 lines a second for 20 seconds while a fifth sat quiet. Echo in the quiet one held a p50 of 153 ms and a p95 of 373 ms, every flooding terminal's last line arrived, and echo was back to 148 ms when the flood stopped.
Failure is the part I cared about most. I killed the ssh connection mid-output: the window showed Reconnecting…, and 2.4 seconds later all 300 lines printed during the outage were there. Each terminal's output is sequence-numbered in a bounded buffer on the host, so a reconnecting window resumes where it left off, or redraws from the host's screen if it was away long enough for the backlog to expire. An operation whose answer was lost with the link, such as a push, is never guessed at: the window asks the host once it's back.
On a Mac, the power-cut story runs through login. Host mode starts at login, so with automatic login on, a restart brings it back while your laptop's windows retry and reconnect. And if ssh reaches a set-up host whose Daintree has stopped, the client starts it again: three to four seconds on a Studio, from stopped to a window I could type in.
Day to day
Once you've added a host, a chip appears beside the project name. On your own machine it reads This Mac, or This machine on Linux. On a host it becomes a bordered pill with a server icon and SSH · studio-01, so a remote window is never mistaken for a local one. Click it, pick a machine, and the window opens the project it last had open there, or the host's project list. The terminals you left keep running where they are. Cmd-click, or Ctrl-click on Linux, opens a host in a new window instead.
The Hosts overview puts every machine in one dialog: CPU and memory-pressure sparklines, observed agent counts, projects and link latency. When you create a worktree, the New Worktree dialog suggests the least loaded machine and shows why. You pick.
In a window on a host, localhost means the host's localhost. Open http://localhost:5173 in a browser panel and Daintree forwards port 5173 over the existing ssh connection, on the same port here when it's free, so the page keeps its address. Sign-in links with a localhost callback get that port forwarded first, so a CLI's browser login on the host can complete from your laptop. Forwards listen on loopback only.
What it doesn't do
- macOS and Linux only. Windows builds don't include Remote Hosts, and there's no WSL support.
- x64 Linux only. There's no arm64 Linux build yet.
- A Mac host needs a logged-in session. Unattended, that means automatic login, which Apple doesn't allow with FileVault on.
- It's not a sandbox. Agents on a host run as your user there, with everything that account can reach. For a separate environment, look at Remote Compute, which attaches a container or VM you configure to one worktree.
- Headless Linux usually has no keyring, so plugins can't store secrets there.
- Restarting or updating a host ends its terminals. An update from Daintree warns first and can wait until the host reports no agents working.
- Keep both machines on the same release. A different build connects, best effort, with a warning. A different link protocol is refused.
Getting started
You need Daintree on the machine you sit at, and ssh to the host that works without a prompt. Linux hosts also need x64 and systemd user services. On a Mac host, turn on Remote Login first. Run the ssh check above, then open Settings → Hosts and click Add host. The dialog lists machines on your tailnet and local network, checks the one you pick, installs the matching build there if needed and turns on Host mode.
The docs cover the rest: Remote Hosts, adding a host, setting up a Linux host, continuing a branch, the trust model in Remote Host Security and troubleshooting.
The Daintree app is free, Apache 2.0 licensed and needs no account, and Remote Hosts puts no Daintree service between your machines. Close the laptop. The host keeps the agents running, and the review waits for you.