Architecture
Helmora is a client, a bridge on your machine, one adapter per agent runtime, an optional relay, and an account service. Each piece has a narrow job, and most of the design follows from keeping those jobs separate.
The pieces
Section titled “The pieces”Client
Section titled “Client”One codebase for web, iOS, and Android. It speaks the Helmora Protocol and renders what comes back: transcripts, tool calls, diffs, plans, approvals, questions, usage.
What it is not: it holds no session state and knows no agent’s native schema. The bridge is authoritative; the client is a view. That’s why the same session looks the same on a laptop and a phone, and why switching devices mid-turn is unremarkable.
Bridge
Section titled “Bridge”A daemon on your machine. It speaks the Helmora Protocol upstream and each agent’s own protocol downstream, hosts the adapters, keeps session history when the runtime doesn’t, terminates encryption when a relay is in the path, and exposes a small admin surface on loopback.
The bridge is where all platform variation lives — macOS versus Linux versus Windows, where the CLIs are, which folders exist. The client never learns any of it.
Adapters
Section titled “Adapters”One plugin per agent runtime. An adapter declares its capabilities, translates the runtime’s events into Helmora Protocol updates, and accepts prompts, cancels, and shutdowns.
First-party adapters today: Claude Code, Codex, GitHub Copilot, and a generic ACP adapter with OpenCode and Goose presets. Adapters absorb churn — when a provider changes its SDK, one adapter changes and nothing else does.
An optional managed service that forwards WebSocket frames between a client and a bridge that can’t reach each other directly. It verifies account-issued tokens, binds the signed device identity to the routing label, and forwards opaque encrypted envelopes within one authenticated user’s device map.
It must never decrypt a frame, log message contents, or carry an unencrypted payload. It also emits its own presence and reachability signals, which is how the app knows a bridge went quiet.
Account service
Section titled “Account service”Identity, device pairing, and the agent catalog. It issues the tokens the relay verifies and serves the public keys used to establish encryption. It is not in the payload path — but it is a trusted directory, and it does hold some plaintext operational metadata. Security names exactly what.
Two paths
Section titled “Two paths”Same machine. When the app and the bridge are on the same host, the app talks to the bridge over loopback and sends plain Helmora Protocol JSON. No relay, no round-trip off the machine. The bridge does not expose that plaintext surface on the LAN.
Remote. Otherwise, both the client and the bridge make outbound WebSocket connections to the relay, which routes between them. This is what makes NAT, cellular, and hotel wifi stop mattering: neither end needs an inbound port. Payloads are encrypted at the endpoints before they leave the device.
Details and the operator-run alternatives are in Remote access.
The path of a prompt
Section titled “The path of a prompt”You type a prompt. The app frames it as a protocol message and sends it — over loopback, or encrypted through the relay. The bridge resolves the right adapter. The adapter invokes the local CLI or SDK. The runtime emits its native text, reasoning, tool, and lifecycle events; the adapter translates each one into a protocol update; the bridge sends it back; the app renders it live. The turn ends with an explicit stop reason.
Every step of that runs on your machine except the forwarding.
Capability negotiation
Section titled “Capability negotiation”Runtimes are not the same, and Helmora does not pretend otherwise.
Each adapter declares what it actually supports — can it interrupt, can
it steer, and does steering inject into the live turn or abort and
restart it, does it expose plan usage, can it compact, which runtime
controls can change mid-session and when they take effect. The client
renders only those. There is no if agent === "…" branching in the UI.
The practical result: a control you can see is a control that works. A missing button on one runtime is a declared absence, not an untested corner. Per-runtime detail is on each runtime page.