Hermes Android · 2026

A phone app for a self-hosted agent

In progressIndependent engineer

A self-hosted AI agent is only as useful as the client used to reach it. A browser tab on a phone is a weak compromise: limited session security, no proper terminal, and two data streams competing on one page.

The constraint

The agent stays on infrastructure you operate. The phone is a client; keys stay in the platform keystore. Interface captures will wait until they exist.

Built with

  • Kotlin
  • Jetpack Compose
  • WebSocket

How it works

The phone holds a Compose client. Two WebSockets reach the self-hosted agent: one for chat, one for the terminal. Unlock is Keystore.

The calls that shaped it

Each decision with the pressure that forced it and the price it keeps costing.

  1. Kotlin and Compose

    The client streams a conversation and carries a real terminal at the same time. A phone layout squeezed out of a desktop page cannot hold either of those properly.

    Native Material 3, sized and shaped for a phone screen.

    The cost: Android only. There is no shared web client to fall back on.

  2. Two WebSockets, one process

    Chat and a terminal are two live streams. Put them on one socket and a long command blocks the conversation; split them across processes and the app has two lifecycles to reconcile.

    JSON-RPC chat on one socket, a PTY on the other, without racing the UI. MVVM, Hilt, six Gradle modules.

    The cost: Two connection states to model, and every screen showing either one has to handle the other being down.

  3. Keystore for the lock

    A self-hosted agent answers anything holding the token, so a session that stays signed in turns a lost phone into access to the host it runs on.

    PIN or biometric unlock through Android Keystore. The session is not "remember me" in a cookie.

    The cost: No convenient sign-in. The lock has to be cleared every time it lapses.

This is a native Android client for an agent you host yourself: a locked session, a real terminal, and two streams that stay in sync. The phone carries the keys; the host runs the agent.

Where it stands

In progress. Pairs with the autonomous agent operations case when you need both the runtime and a phone client.

  • Native Android client: Material 3, MVVM, Hilt, six Gradle modules.
  • Two WebSockets in one process: JSON-RPC chat on one, a PTY on the other.
  • Unlock is a PIN or biometric through Android Keystore, so no session is stored.
  • In progress. Interface captures will be added once they exist.

What was handed over

  1. Module map and how the two sockets are owned
  2. How the client is pointed at a host
  3. What the Keystore lock does and does not protect
Next project PwnLink A phone app for a Pi in the field