at play · case study

The Hub

An AI coding agent on my own server, driven from my phone — Claude Code today, any provider by design. I type an idea from anywhere; the agent works on the VPS; a prototype is waiting before the walk is over. No third party in the path, and every request billed to the right meter.

hub infographic — a phone with three tabs reaches a self-managed VPS only through a private tailnet; on the VPS, chat and cron bill the API key, the terminal bills the subscription, and the dash watches systemd, docker and CIhover to inspect
role
design & code · ops
stack
FastAPI · Claude Agent SDK · xterm.js · Tailscale
runs on
a self-managed VPS — systemd, isolated users
status
private build, in daily use
0third parties in the traffic path
2billing paths, kept apart by design
3tabs — chat, terminal, dash
1install command per isolated instance

Born on a walk

The need: ask an LLM something from the phone, mid-walk, without a third-party service reading the traffic and without a fragile SSH-over-mobile ritual. So the agent lives where it should — on a server I run — and the phone carries only a thin, installable PWA. It's a personal idea tester in the OpenClaw family of self-hosted agents: a thought typed on the street becomes a running experiment on the VPS before the walk is over. The instrument panel is provider-agnostic — today it speaks Claude, both the Agent SDK and the first-party CLI; another provider is a lane in the diagram, not a rewrite. Three tabs:chat (streaming, voice input, per-session spend cap), a real terminal (a PTY with the first-party Claude Code CLI and thumb-sized keys), and a dash— CPU, RAM, disk, systemd and docker status, per-project CI and last commits.

The path

There is exactly one road to the server: the tailnet. hub binds to localhost and is published only through Tailscale's private HTTPS — it does not exist on the public internet, there is no relay, no vendor bridge, no one in the middle.

phonehub · PWAtailnet · WireGuardprivate HTTPS, device-to-devicethe public internet is not invitedVPS — hub servicesystemd · own linux userchat → Agent SDKAPI key · meteredterminal → claude CLIOAuth · subscriptioncron jobs → SDK, headlessAPI key · metereddash → systemd · docker · CIread-onlythe PTY strips the API key from its env — the CLI can only bill the subscription
One request's whole journey — phone to VPS inside the WireGuard tunnel, then onto one of four lanes, each wearing its own billing tag.

Two bills, kept honest

Anthropic's terms draw a hard line: the Agent SDK must run on a metered API key; subscription OAuth belongs to the first-party CLI only. hub is architected around that line instead of near it. The chat tab and every scheduled cron job run headless on the API key and pay per token. The terminal's claude runs as the real CLI on the Pro/Max subscription — and its PTY deliberately stripsANTHROPIC_API_KEY from the environment, so the two paths cannot cross even by accident.

Reading the compliance page before drawing the architecture is the whole trick — the billing split isn't a setting, it's the shape of the system.

A server of one's own

Everything under hub is handled solo, and repeatably: one install script provisions a full instance — its own linux user, its own systemd unit, venv, env file, and ACL-protected agent state — so a second hub (different project, different model, different system prompt) is one command on any box. The dash closes the loop: the same screen that talks to the agent watches the machine it lives on. Ops isn't outsourced here; it's part of the build.

Anthropic has since shipped the mainstream version of this idea — Remote Control, bridging a laptop session to the phone. hub still runs where that can't: on a server that never sleeps, over a network nobody else can see.