Skip to content
Back to case files
AI engineeringIn progress

A Cloud Agent Kernel — Clients Reduced to a Browser

Code, services and agents all moved onto a cloud host, with laptop, tablet and phone reduced to a browser — one message from your phone dispatches work, ships code and accepts results; any screen can take over

Role
Architecture and implementation (solo)
Scale
One cloud kernel · four client types · ports closed
Period
Oct 2026 — Present
Hermes AgentTailscalesystemdJSON-RPCWebSocketSQLiteFeishu / LarkWeChat iLinkmagpie (Go)Docker

What I did

  • Multi-device control: laptop (Desktop registry), tablet, phone (home-screen dashboard) and Feishu / WeChat (messages) share one cloud kernel and one session — switching devices migrates nothing
  • Messages as commands: one line from a phone dispatches work, asks follow-ups or accepts results; the agent edits in the cloud and returns the diff and screenshots — development continues on a commute
  • Single-writer control plane: one master owns state.db and the task board, the second node is active/passive failover — co-writing tears sessions and double-dispatches the same task
  • Zero inbound ports — the gateway binds loopback, reached through a Tailscale tailnet with device ACLs and a public_url auth gate; social clients ride outbound long connections only
  • A middle state on the way to AGI: the human keeps judgement, authorisation and acceptance while the agent executes and records — every step observable, interruptible and reversible

Shape: the kernel in the cloud, the client down to a browser

What I wanted was not another remote console but the whole chain moved up: kernel, code and execution environment on the cloud host, with the laptop and the phone reduced to a client that reads one web page.

  • Kernel: hermes-agent v0.22.4, hermes serve :9119 exposing JSON-RPC + WebSocket, bound to 127.0.0.1 only, never public.
  • Always on: hermes gateway install puts it under systemd so it restarts itself; one process serves every profile (multiplex_profiles), each profile a roster entry with its own memory and skills (coder / ops / research).
  • Clients: the laptop is Desktop’s remote gateway, in a registry that holds local, cloud and SSH connections side by side; the phone is the responsive dashboard, added to the home screen; social platforms are entrances only.

There is a deliberate trade here: hermes already ships the kernel, the messaging gateways, the dashboard and the multi-instance registry — most of the distance, roughly eighty percent. So I write no native console app and no control panel of my own. The saved time goes into the overall design and the failure paths, not into rebuilding what someone else already got right.

Multi-device control: change the screen, not the environment

Moving to the cloud was never about remote login. It is about the same working state continuing on any screen:

Situation Client What it is for
At the office Laptop · Desktop remote gateway Heavy work: design docs, long runs, logs, diff review
Commuting / queueing Phone · dashboard on the home screen Check progress, approve one dangerous command, tell the agent to continue
Travelling / meetings Tablet · the same URL A second console: watch the task board during a call
No console at all Feishu / WeChat · one message “Make the login button bigger” — the diff and a screenshot come back; “looks good” closes it

Three things have to hold at once: one session (not a repository copied to another machine and synced by hand), one permission model (a device outside the tailnet sees nothing), and one evidence trail (what changed, which checks ran, where the screenshots are — all in the session).

So “develop by messaging your phone” is not a slogan: the message is only the entrance. Execution happens inside the kernel on the cloud host, which has the code, can run the tests and leaves the record. The human keeps judgement and acceptance.

Why this matters: a middle state on the way to AGI

Model capability turns a page every few months, and today’s best workflow will likely be rewritten next year. So rather than chasing a fully automatic end state, I made the middle state solid:

  • The human keeps three things: judgement (what to do), authorisation (confirming dangerous actions), acceptance (whether the result is good enough).
  • The agent takes three things: execution, bookkeeping, and reporting back — every step observable, interruptible, takeable-over.
  • The system guarantees two: any step can be rolled back, and a human can still rescue it when every model is down (see the bypass command layer below).

When models get stronger, the executor changes and the system does not. When a model is wrong, the damage stays inside one step. Until AGI arrives, a semi-automatic system that can be taken over reliably is worth more than an uncontrollable fully automatic one.

Access: zero inbound ports

Port 443 on the host stays closed. Every entrance is “a device already inside the tailnet”:

Channel How Why
Tailscale tailnet Device-level ACLs, tailscale serve for the HTTPS cert No exposed 443; access granted per device
Auth gate public_url behind basic auth, even for the reverse-proxied loopback Anything reachable through a proxy gets checked again
Feishu / Lark WebSocket long connection, no public callback Approval-card buttons mean confirming dangerous commands on the phone
WeChat iLink bot identity, long-poll, results pushed only Group messages often go undelivered — treat it as a notification channel
Hermes Desktop Connects straight to the cloud gateway The desktop is still where heavy work happens

Social platforms cap what you can do, and their rules are not mine: patching their clients is a ban risk. So the split is fixed — the console handles the heavy work, the social side handles the light work.

Control plane: exactly one writer

With a second node, the first question is not how to scale but who is allowed to write. The answer here is single-writer:

  • state.db is SQLite: two nodes writing the same session means WAL contention and torn sessions — hermes refuses the second process outright.
  • The kanban.db dispatcher must have a single owner: two dispatchers spawn the same task twice.
  • Dangerous-command approval is in-process state: two masters each raise their own prompt, so one command gets approved twice, or approved on one side and rejected on the other.

So the second node is not another master; it is active/passive failover. Peers call each other over the API server on :8642 with API_SERVER_KEY granting one-way authority, and /v1/runs is idempotent, steerable and stoppable. Whoever dies, the other takes over — they never both work the same task.

The bypass command layer: no model in the loop

The fragile link is the model gateway: if it dies, the agent cannot think, and therefore cannot rescue itself. So the rescue was lifted out of the model and written as a script:

  • watchdog.sh runs under cron in no-agent mode every five minutes, reads /api/status, and only prints when something is wrong; a healthy run is silent.
  • It costs zero tokens and its stdout goes straight to Feishu — it still arrives when no model is reachable.
  • The self-heal whitelist has exactly two entries: restart the local service, export the cause of death. Cross-node, it notifies and never dispatches commands.

The backstop is backup: hermes backup snapshots to another site on a schedule, and a cold restore gets rehearsed before anyone talks about dual-active.

Providers and scheduling

Besides the kernel and the messaging gateways, the host also runs a fork of magpie (another project here: Magpie Plus), which manages the various agents and model providers in one place — account pools, balance expiry and routing behind a single entrance, so the kernel only knows one base_url.

The next step is to make scheduling part of the same system: hermes schedules and accepts, Claude Code writes the code, and sub-agents split the work to save tokens — the point being that every step of a long, multi-round task stays observable and interruptible.

Explicitly not doing

  • A Flutter console app: web first, and only if it proves insufficient.
  • Nodes trusting each other as mutual masters: write conflicts are not solved by trust.
  • Dual-active writes to state.db: it tears sessions.
  • Patching the WeChat / Feishu clients: a ban risk that buys nothing.
  • Opening terminal or file writes to social clients: one mistap on a phone is an incident.

Still going

  • Execution moving up too: code can still stay on my machine today (terminal: ssh); the goal is to pull the terminal into containers on the cloud host, isolation first, until the client really is only a browser.
  • State aggregation: compress every node’s state into one card — who is running, who died, how many days of balance are left.
  • Network hardening: tighten Tailscale ACLs down to ports, and swap the public_url gate for short-lived sessions.
  • Failover drills: turn backup-and-restore into a scripted run rather than a document read after the fact.