Architecture
frogg is a client-server system. A Node daemon runs agents and owns all state. Clients connect to one or more daemons to watch and control those agents. Closing a client does not stop agents.
System overview
Section titled “System overview” Electron desktop Expo app CLI (frogg) (app-only shell) web / iOS / Android | | | | local socket/pipe, | WebSocket, direct | WebSocket, direct | SSH, direct, relay | or via relay | or SSH +-----------+-----------+----------+-----------+ | | v v +------------------------------------------+ | Daemon (@frogg/server, Node) | | HTTP: /ws /api/* /mcp/agents web UI | | agents, projects, workspaces, worktrees | | terminals, schedules | +---------------------+--------------------+ | provider processes (Claude, Codex, ACP agents, ...) | $FROGG_HOME on diskComponents
Section titled “Components”Daemon (packages/server). Spawns and supervises agent processes, streams their timelines,
manages projects, workspaces, worktrees, terminals and schedules, and enforces authorization.
Agent state stays authoritative on the daemon across client disconnects. The daemon is installed
separately (install script, daemon bundle, Docker or frogg CLI) and listens on 0.0.0.0:9999 by
default. FROGG_LISTEN, daemon.listen in config.json, or --listen override it. The listen
target can also be a Unix socket or a Windows named pipe.
Expo app (apps/ui, package @frogg/app). One React Native codebase for web, iOS and Android.
Its web export is also the desktop renderer and the optional web UI the daemon can serve.
Desktop (apps/desktop-electron). An Electron shell around the Expo web export (shipped as
app-dist). The renderer runs sandboxed with context isolation; the preload exposes
window.froggDesktop. The main process owns native features: windows, notifications, updates, SSH
transports and remote daemon deployment. The desktop is app-only. It never bundles, starts or
stops a daemon, and the packaged smoke test asserts that.
CLI (apps/cli, package @frogg/cli, binary frogg). A command-line client over the same
WebSocket protocol, plus daemon lifecycle and service commands.
Connections
Section titled “Connections”All clients speak one WebSocket protocol. Schemas live in packages/protocol/src/messages.ts.
- Direct. A WebSocket upgrade on the daemon HTTP server at
/ws, over TCP, or a local socket or pipe from the desktop main process. - SSH. The desktop main process and the CLI (
--host ssh://user@host) run the local OpenSSH client asssh -T -W 127.0.0.1:<daemonPort> <host>and carry the WebSocket over its stdio. The remote daemon must already be running. The tunnel never installs or starts it. - Relay. An optional relay bridges clients and a daemon that cannot accept inbound
connections. Payloads are end-to-end encrypted with NaCl
box(tweetnacl), so the relay only routes ciphertext. The official brand configures no relay endpoint. You must run your own;packages/relayincludes a Cloudflare Workers adapter.
Protocol rules for adding or changing messages are in Coding standards.
Code map
Section titled “Code map”| Path | Package | Role |
|---|---|---|
apps/ui |
@frogg/app |
Expo client for web, iOS, Android and the desktop renderer |
apps/desktop-electron |
@frogg/desktop-electron |
Production Electron app-only shell |
apps/cli |
@frogg/cli |
frogg command-line client and daemon launcher |
apps/desktop |
@frogg/desktop |
Inactive Tauri shell. Reference source only |
apps/daemon-rs |
none (Cargo crate) | Inactive Rust daemon. Reference source only |
apps/desktop and apps/daemon-rs are excluded from releases. Do not build features on them.
apps/desktop is still an npm workspace, so root typecheck and build include it.
packages/
Section titled “packages/”| Path | Package | Role |
|---|---|---|
packages/protocol |
@frogg/protocol |
Zod wire schemas, message types, generated validators, SSH tunnel args |
packages/client |
@frogg/client |
Daemon WebSocket client (DaemonClient) and the FroggClient SDK facade |
packages/server |
@frogg/server |
The daemon |
packages/relay |
@frogg/relay |
Relay transport and E2E encryption |
packages/highlight |
@frogg/highlight |
Syntax highlighting |
packages/branding |
@frogg/branding |
Build-time product identity |
packages/expo-two-way-audio |
@frogg/expo-two-way-audio |
Native module for two-way audio streaming |
Other top-level directories: brands/ (brand manifests, brands/frogg is official),
deploy/ (Docker, Nix, install scripts), skills/ (agent skills), and
scripts/dev|ci|release|mobile.
Protocol and client
Section titled “Protocol and client”@frogg/protocol is the source of truth for everything on the wire. It depends on nothing in
the server. Server, app, CLI and @frogg/client all import it from its built dist/, which is
why protocol changes need npm run build:client before downstream typechecks see them.
@frogg/client exports DaemonClient through @frogg/client/internal/daemon-client and the
higher-level FroggClient from the package root.
Branding
Section titled “Branding”Product identity is data, not code. brands/<id>/brand.json defines the name, CLI name, daemon
port, env prefix, URL scheme, home directory, artifact prefixes and service URLs.
npm run brand:prepare resolves the brand selected by FROGG_BRAND_DIR (default brands/frogg)
into .generated/branding/. That output is never committed. Runtime code reads it through
brand from @frogg/branding. See Fork and rebrand for the full model.
Persistence
Section titled “Persistence”The daemon stores state as files under $FROGG_HOME. It resolves FROGG_HOME (the brand’s
<ENVPREFIX>_HOME), then falls back to ~/.frogg. Dev scripts use .dev/frogg-home in the checkout.
| Path | Contents |
|---|---|
config.json |
Persisted daemon configuration |
agents/ |
Agent records as JSON |
agent-timelines/ |
Agent timeline data |
projects/ |
projects.json and workspaces.json |
worktrees/ |
Worktrees the daemon creates |
uploads/ |
Uploaded files |
daemon.log |
Daemon log (rotated) |
runtime/ |
Runtime files such as managed-process state |
This table lists the main entries only. See Configuration for
config.json.
MCP endpoint
Section titled “MCP endpoint”The daemon serves an MCP endpoint at /mcp/agents on its HTTP server. Agents it launches get
this endpoint injected so they can use frogg tools: workspaces, agents, permissions, schedules and
heartbeats. The tools come from a transport-neutral catalog in
packages/server/src/server/agent/tools/; MCP is one adapter over it. The route authenticates
its own requests rather than using the daemon bearer check.