Santiago Mansilla.

An agent box: where your agent loops actually live

Loop engineering needs somewhere to live: an agent box on your server —an outbound daemon, your own worktree engine and access without opening ports.

Santiago Mansilla 7 min read

Loop engineering — designing agent loops that work on their own instead of hand-prompting every step — has a requirement people rarely mention: a loop that works on its own needs somewhere to live. Not your laptop, which shuts when you close it; an always-on machine, isolated from anything that matters, that you can start and steer from wherever you are.

I built that box — an agent box — on a personal Hetzner: a Claude Code process running 24/7 that shows up as an “environment” in the app on my phone without opening a single port on the machine. The trick is the direction of the traffic: the process registers outbound, against Anthropic, and every session you create runs on your server, inside a worktree — an isolated working copy of your repository — that your own scripts set up. This first part covers the architecture that makes it portable and safe; what breaks when a loop really works on its own goes in the second.

How does your server show up as an environment without opening ports?

The daemon — claude remote-control --spawn worktree, a process that stays alive in the background — opens outbound HTTPS/WebSocket connections to api.anthropic.com and listens on no inbound port. It registers with a POST to .../v1/environments/bridge and gets back an environment_id; that registration is exactly what makes the server appear in the UI, in the environment picker.

From there it enters a poll loop: it asks Anthropic every so often, “is there work for me?” When you create a session from the chat, Anthropic acts as a relay between your phone and the process living on your machine, and everything (messages, diffs, permissions, push notifications) travels over that outbound connection. Nothing enters the server: there are no open ports and no SSH for the agent; your SSH is only for administering the box.

In practice: model access as outbound. A daemon that registers outward and asks “is there work?” instead of a port you open and expose. The agent needs no inbound; only outbound.

The worktree is the unit of isolation, and you decide who creates it

A single bridge — that outbound process — holds up to 32 concurrent sessions, each in its own git worktree: a branch with its own separate working directory, so two sessions don’t clobber each other’s files. By default, the daemon creates them under ./project/.claude/worktrees/, coupled to the vendor’s folder and with a layout you don’t control.

That’s the extension point that changes everything. If you define the WorktreeCreate and WorktreeRemove hooks — your own commands that the daemon runs at specific moments — they completely replace the default mechanism. The contract is tiny: the hook receives JSON on stdin and must return a single line on stdout, the worktree path. With that, you point creation at your own script: a worktree that’s a sibling of master, with its branch born from master, its free ports and its generated .env.

In practice: replace the default worktree mechanism with your hooks. Have them return the path of a worktree that your script creates, with your layout and your ports. Don’t let the vendor decide where your code lives or how it’s isolated.

Neutral engine and thin adapter: 80% doesn’t know your vendor

In an agent box built this way — an agent daemon on your own machine — ~80% of the work — infra, dedicated user, toolchain, worktree engine, ports, private network — is neutral: none of it knows Claude exists. All the vendor coupling fits in 2 hooks of ~50 lines plus about 15 of configuration. That proportion isn’t luck: it’s the design decision that keeps the prototype from dying when the wind changes.

The split is by layers. scripts/ is the neutral engine: it names branches, assigns ports, starts and stops environments, cleans up when done, generates the .env. .claude/hooks/ is the adapter: it parses the JSON the vendor sends and calls those scripts. A future adapter for Codex or Cursor would point at the same scripts, and every worktree would live in the same place with the same layout. This isn’t a Claude-only pattern: Cursor’s self-hosted cloud agents — available since March 2026 — are workers in your infrastructure that open outbound connections to their cloud, with no inbound ports, the same shape as Claude’s bridge.

In practice: put all the workflow logic in neutral scripts and keep the vendor adapter thin enough to rewrite in an afternoon. The health test is a question — how many lines do I need to plug in another vendor? — and if the answer is more than “one adapter,” logic leaked where it shouldn’t have.

A dedicated box, isolated from anything that matters

The box where you run agents shouldn’t be the same one where anything you care about lives. The reason is a failure that doesn’t shout: a dev worktree that assumes localhost:5432 connects to whatever database it finds on that port — no error, no warning — and if that database is real, you’ve already written where you shouldn’t. On a shared box you carry that risk permanently; on a dedicated one, it disappears by construction.

The principle is isolation by construction, not by configuration: a separate machine — or a separate provider outright — with no production credentials or data, master protected by human review. On top of that, two things an agent box demands: swap — disk-backed memory that stands in for RAM — because a Vite build plus a headless Chrome — no graphical interface — triggers the OOM-killer — the kernel process that kills processes when RAM runs out — and would take down the other sessions; and running each agent as a Linux user without sudo, which bounds its playground with ordinary file permissions.

That last point is also what makes the box multi-user without redesigning it: the model that scales to a team is one Linux user + one vendor login + one systemd unit per person, each with their own worktrees on the same machine.

In practice: dedicate a box to this — with no production credentials or data — add swap, and run each agent as a user without sudo. When there are several of you, one user and one unit per person. Isolate it by construction, not by trusting a config.

Seeing the app from your phone: a private network, not an open port

Chatting with the agent from your phone already works — the relay handles that. What’s missing is seeing the app it builds: the dev servers listen on the server’s localhost, invisible from outside. The answer isn’t opening a port, it’s a private network: Tailscale sets up a WireGuard VPN — an encrypted tunnel between your devices.

You install Tailscale on the server and on the phone with the same account, tailscale up, and one ufw allow in on tailscale0 — the firewall would otherwise block the tailnet, the private network. From there the phone sees the server’s private IP (100.x.y.z) from any network, 4G included. But the non-obvious part is three tweaks per worktree, because the dev servers listen only on localhost and the frontend carries absolute localhost URLs that, from the phone, point at the phone itself:

  • Vite with --host 0.0.0.0, so it listens on all interfaces (safe here: UFW blocks anything public and only tailscale0 is allowed).
  • VITE_API_URL=http://100.x.y.z:<port>: the phone’s browser runs the JS, so “localhost” would be the phone; it has to point at the tailnet IP.
  • The backend’s CORS accepting that same origin.

The result: http://100.x.y.z:<port> opens, on the phone, the frontend of the worktree the agent is working on, with hot-reload, from any network. And still nothing enters the server: UFW only opens 22/80/443 to the outside; the dev ports are invisible from the internet and reachable only inside the tailnet, end-to-end encrypted by WireGuard.

In practice: to see the app from outside, don’t open a port — join the device to a private network and expose the dev server only inside it. And don’t forget the three tweaks (host binding, API URL, CORS), or the app loads but every call fails.

That’s the stable architecture: an outbound daemon, your own worktree engine, private-network access, and a dedicated box. It’s the natural sequel to the four boundaries for an agent with access to your books: here the first boundary is that nothing enters the box. With this, your loops stop depending on your laptop being open — they live on the box, and you steer them from wherever you are.

What’s left is the other half: what breaks when a loop works on its own for hours and nobody’s watching. That’s the second part of this series.

Where do you run your long-running agents: on a dedicated box you can steer from your phone, or on a laptop you have to leave open?

Subscribe to the newsletter

AI engineering in production — one email a week, no noise.

Subscribe →