Skip to Content

Architecture

One Go core, many surfaces. This page is the block-diagram view: which process runs where, what talks to what, and where state lives.

The shape, in one paragraph

Every machine runs the same cmdop binary. When started with cmdop agent start, that binary becomes a long-running daemon that holds an outbound TLS connection to the relay and exposes a local gRPC server (Unix socket on macOS / Linux, named pipe on Windows). The desktop client and the CLI are clients of that local server — they never talk to the relay directly. Cross-machine work flows through the daemon over the relay; the relay bridges to the target machine’s daemon, which runs its own agent loop under its own permission gate.

The pieces

The Go core

A single Go module produces:

  • cmdop — the CLI.
  • cmdop-desktop — the Wails-wrapped desktop binary; embeds the same Go core and a React frontend.
  • The agent loop, the tool catalog, the Connect subsystem, the daemon services, the MCP server.

CLI and desktop are thin shells over the same packages. There is no parallel “desktop logic” duplicated from the CLI.

The daemon

Started by cmdop agent start. Boot sequence (see the daemon concept for the full list):

  1. File logger.
  2. PID file at ~/.cmdop/cmdop.pid (per-user, not per-host).
  3. Status manager writes ~/.cmdop/daemon.status on every connection event.
  4. Permissions system loads permissions.yaml, opens the local ask socket.
  5. Local SDK / desktop server starts on a Unix socket or Windows named pipe.
  6. Optional MCP server (--with-mcp).
  7. Connection loop dials the relay with backoff.

Status verdict (cmdop agent status):

  • STARTING — running but not yet handshaken.
  • ONLINE — connected, status file fresh (< 30 s).
  • DEGRADED — process alive, status file stale or relay disconnected.

The desktop client

A Wails app: Go services on the back, React on the front. Seven tabs (Chat, Machines, Board, Activity, Connection, Projects, Settings) plus a Spotlight-like launcher (Option+Space on macOS, Alt+Space on Windows / Linux) and a system tray.

The desktop client attaches to the local daemon over the same gRPC surface the CLI uses. It also adds:

  • Per-machine inspector chat — direct pipe to a remote agent, no local LLM paraphrasing.
  • Projects sidebar — local files plus remote files from any online machine over cmdop://machine/<id>/<path>.
  • Document inspector — embedded media playback, EXIF, audio metadata.

The CLI

Verbs grouped into families: chat, run / skills, agent, connect, permissions, issue / session / trigger, mcp, developer-tools. Same Go core, same agent loop, same audit log. Use it in scripts and SSH-into-this-VPS-to-debug-it sessions.

Connect

The agent-to-agent funnel. Every cross-machine call — cmdop connect exec, ssh_session tool, ask_agent, ask_agents — goes through remoteagent.Ask / AskStream. One resolver, one dial path, one error taxonomy.

Connect owns:

  • Workspaces — ~/.cmdop/ssh_workspaces.json (mode 0600), name → API key, name → server.
  • Machines — fuzzy hostname/UUID resolution.
  • Share links — TTL-bounded guest tokens for one machine.
  • Server-to-server — ask_agent, ask_agent_stream, ask_agents.
  • Persistent multi-command sessions — sessionmgr, ring buffer, idle TTL.

MCP

Two roles:

  • Servercmdop mcp stdio (or cmdop agent start --with-mcp) advertises CMDOP tools over MCP for Claude Desktop and Cursor.
  • Client — registered external MCP servers (filesystem, GitHub, custom) merge their tools into the agent’s catalog with a <server>:<tool> namespace.

See the MCP overview.

The web cabinet

Workspaces, billing, observability, account. Not where you do work. Operational surfaces (web terminal, web AI chat) are demoted to alt-surface views — the desktop and CLI are the primary tools.

Where state lives

WhatWhere
Per-machine identity~/.cmdop/cmdop.db (SQLite)
PID file~/.cmdop/cmdop.pid (per-euid)
Status file~/.cmdop/daemon.status
Workspaces~/.cmdop/ssh_workspaces.json (mode 0600)
Permission rules~/.cmdop/permissions.yaml (mode 0600)
Audit log~/<log-dir>/audit.log (lumberjack 10 MB × 5)
Daemon log~/<log-dir>/cmdop.log (size-rotated)
Skills (global)~/.cmdop/skills/
Skills (workspace)<repo>/.cmdop/skills/

<log-dir> is ~/Library/Logs/cmdop on macOS, ~/.cache/cmdop/logs on Linux, %LOCALAPPDATA%\cmdop\logs on Windows.

How a desktop click reaches a remote machine

Desktop UI (React) └─ Wails IPC ──▶ Desktop Go services └─ Local gRPC ──▶ Daemon (this machine) └─ remoteagent.Ask ──▶ Relay (TLS, outbound) └─ Bridge ──▶ Daemon (vps-audi) └─ Permission gate └─ Agent loop └─ Tool execution

The two daemons never directly listen on each other’s network. The relay is the only bridge. Both daemons keep the connection outbound; that means no inbound port to forward, no firewall hole to punch.

Where the agent loop sits

The agent loop is the LLM turn cycle: build a prompt, call the model, parse tool calls, execute them, append results, repeat. It runs inside the daemon. CLI, desktop, and SDK call into it through chat.ForCLI, chat.ForDesktop, and chat.ForDaemon façades — the same loop, parameterized for the surface.

Local chat (your own cmdop chat, your own desktop chat tab) bypasses the permission gate. The gate fires only for remote callers. See the permissions concept.

A single prompt’s journey, in plain language.

OAuth, the gate, the floor, and per-mode token storage.

Boot order, status verdict, heartbeat, and shutdown.

Last updated on