Jay Gupta
← Writing

Building Otto: my agents live on a cloud box now

11 Oct 2026 · 3 min read

For most of this year I ran OpenCode on my MacBook, usually three or four sessions at once. That works until you close the lid. Every session stops mid-task, and when you open it again the agent has lost the thread.

I wanted agents that keep running while my laptop sleeps, and a way to check on them from my phone. Most of the time an agent only needs a quick "yes, go ahead", and I'm rarely at my desk when it asks.

So I built Otto.

What it does

Paste an API key from Boat, boxd, E2B or Daytona. Otto starts a Linux machine, installs OpenCode, runs it as a service with a random password, and connects to it over HTTPS. The app runs on macOS, Windows, Linux, Android and iOS.

laptop or phone ──HTTPS──▶ cloud machine running OpenCode ──▶ your repo

The app talks to that machine directly. There's no Otto server, no account and nothing to pay me. If you already run OpenCode somewhere, you can connect to it instead.

Three decisions

I didn't build an agent. OpenCode v2 already has a solid server, with sessions, tools, permissions, MCP and an event stream. I spent my time on the app and the machine.

Tauri, not Electron. One React codebase covers desktop and mobile. The Rust side handles what a webview does badly: streaming HTTP without CORS problems, the OS keychain, native notifications. The Mac app is much smaller than an Electron build would be.

Plain infrastructure. The remote side is a shell script, a systemd unit and a password. When something breaks, I can SSH in and read the logs.

Three bugs

The OOM killer. An agent installed Chromium for Playwright, the machine ran out of memory, and Linux killed the OpenCode server instead of the install. systemd restarted it, so it looked like a random outage. The fix was one line in the service file, OOMScoreAdjust=-900. Now the kernel kills almost anything else first.

Phone keyboards. When the keyboard opens, most mobile browsers shrink the visible area and scroll the whole page up, so the header disappears and the layout jumps. Otto sizes itself to visualViewport.height, undoes that scroll, and drops the bottom safe area while the keyboard is open. The message box now sits on top of the keyboard, like in a native app.

Auto-scroll. "Follow new output unless the user scrolls up" sounds simple. But streaming text, loading images and layout shifts all fire scroll events too. What worked: only real input (wheel, touch, keys) stops the follow. Everything else keeps it, and sending a message always takes you back to the bottom.

What's not done yet

How it was built

Agents wrote most of the code. I made the decisions: Tauri, no backend, how providers work, how the phone layout should feel, and noticing that deleting a session had no confirmation. Otto is built around the same split. I decide what happens, from wherever I am, and the machine does the typing.


Otto is free and MIT licensed. On a Mac:

curl -fsSL https://jaygupta.dev/otto/install.sh | sh

Windows, Linux and Android builds are at jaygupta.dev/otto. If you find it useful, a star on GitHub helps a lot.

Read next
Stop grading your agents yes/no