How st works
What st runs on a machine, how the apps reach it, what it serves, and how it knows what the agents are doing.
st is the part that runs on each machine. Everything you see in the
desktop app, the phone apps or a browser comes from the st on the machine
it shows.
One binary, one agent process
st is a single file. It carries what it needs: ttyd, tmux, zellij (and
psmux, a tmux for Windows, on Windows) and the web page it serves. They are
unpacked into a cache the first time it runs. A machine's own tmux is used
ahead of the carried one.
Running st starts an agent process in the background and gives the prompt
back. The agent does the work from then on:
- It puts the terminal on a local port, behind a token.
- It opens the way in from outside (below).
- It watches every pane on the tmux server for coding agents.
- Signed in, it reports to your account: that it is alive, its sessions, the agents' states.
The tmux sessions belong to tmux, not to the agent. st exit stops the
agent and the tunnel, and the sessions keep running. An upgrade hands the
running share to the new version without dropping it. tmux is the default;
zellij and no multiplexer at all are the other choices, see
multiplexers.
ttyd is used as-is, for pty ↔ WebSocket, and listens on loopback only. Everything a client sees — the web page, the API, the login — comes from the proxy in front of it.
Ways in
Where the public address comes from depends on the plan. On Free it is a
Cloudflare quick tunnel, and the address changes each time the tunnel starts.
On Pro it is the sophonz relay, and the address stays. Either way the machine
dials out, so no port is open on it. The machine's tailnet is a third choice
(st 0.3.93 or later). st route shows which one a machine uses and changes it.
- On the same computer. The agent listens on
127.0.0.1. The desktop app talks to this computer'sstthere.st --no-tunnelstops at this: no public address at all. - Through a quick tunnel (Free).
ststartscloudflared, which dials out to Cloudflare and gets atrycloudflare.comaddress.stprints the token and an app link,https://link.sophonz.io/j#<tunnel address>, with a QR code holding that link. The tunnel's address is inside the link, after the#: copy that part to open it directly. WithSOPHONZ_QR_DIRECT=1,stshows the tunnel's address as it used to. Nothing listens from outside; the machine made the connection. The address changes when the tunnel restarts, andstreconnects by itself when it drops. Cloudflare offers this path for testing and development, with no uptime guarantee (Ways to run). - Through the sophonz relay (Pro). Pro includes a relay server that
sophonz runs, in place of the quick tunnel.
stopens one WebSocket out to it, and the relay sends the requests for the machine's hostname down that connection, so here too nothing listens from outside. The address is three words, such ashttps://quiet-otter-lamp.relay.sophonz.io, and it stays the same across restarts. One wildcard certificate covers every machine, and the relay pulls its list of machines from the control plane every 30 seconds, so adding a machine restarts nothing and drops no one. From st 0.4.1 a machine signed in to a Pro account fetches its relay address and token itself; a route you picked and a relay of your own (st relay set) come first, andst relay resetpicks the quick tunnel and stays there, leaving a chosen tailnet as it is (Share a terminal). Through the relay, the account link and the apps' machine list work; notifications have not been confirmed there yet. Like Cloudflare with a tunnel, the relay passes the bytes along: terminal traffic is not end-to-end encrypted yet. - Through your tailnet. On a machine that is on a tailnet, the first
stasks once, before the QR, how phones should reach it. On Tailscale,stserveshttps://<machine>.<tailnet>.ts.netthroughtailscale servewhen the tailnet has HTTPS certificates; without them it serves plainhttp://100.x.y.z:<port>, which browsers open but the apps do not. Only devices on the same tailnet get in, so the phone needs the Tailscale app. On Tailscale, the account link, opening from the apps' machine list and notifications do not work yet. - Through your account. After
st loginthe machine also has a link that does not change,link.sophonz.io/m/<slug>. It opens only for you, signed in. When you follow it, the control plane asks the machine's agent for a key good for one visit and about a minute, and passes you on to the machine's current address.
The token that opens the terminal never leaves the machine. The control plane stores the machine's current address and nothing that would open it. See Accounts and links.
The desktop app and the phone apps list every machine on the account and open each one this way. Ways to run compares the options.
The web terminal and the API
On that one address, behind the token:
- The web terminal. A terminal page with a key bar, the Nerd Font glyphs prompts use (cached after the first load), a sidebar of sessions and windows, chat and the agent graph. The browser pages describe it.
- An API the apps use. Sessions, windows and panes (list, open, new,
rename, close), the agents and their questions, answering a question,
a pane's conversation as chat and typing into it, documents,
machine metrics, agent usage limits and past sessions, a pane's
files and git changes (read-only), GitHub Actions runs, upgrading
st, and the orchestration graph.
All of it comes from the machine to the client. The control plane is not in the path of the terminal, the chat or the metrics.
Agent states
st hooks installaddsst hookto the settings of Claude Code, Codex, Gemini CLI, Kimi Code and Qwen Code, and writes a plugin for opencode and an extension for Pi. On each event the agent runs it.st hooksends one short event to thestagent on the same machine and stops there. Not the transcript and not the prompt.- The agent turns events, and what it reads on the screen, into a state for each pane: working, waiting, done, error or idle. A question it can read becomes a prompt with options.
- The apps read those states live from the machine. Signed in, the agent also sends state changes to the control plane, which sends the notifications through Apple and Google.
- A notification's content is encrypted on the machine to each device's own key. The control plane and Apple or Google see only names, states, counts and times; that is also all the Live Activity carries.
Without hooks st still reads states from the screen, less exactly.
Agents and their state has what each state means and
where it shows.
Orchestration on the same pieces
Handing work to other agents adds no new server. It is the same st, using
what it already has:
- A worker is a tmux window (psmux on Windows, where workers are adopted
rather than started).
st worker startopens a window in the coordinator's session and starts Claude Code, Codex, opencode or Pi in it. - The task is typed the way the apps' chat types: into the agent's own input box, never over a prompt.
- The hooks confirm it.
st worker startreturns once the worker's hooks show it has started its turn. - Who works for whom survives a restart of
st, and goes when the pane closes. The run's objective and the workers' reports are kept in~/.sophonz/orch/<run>/run.json. - Another machine is reached through the account, the way the apps open
it: the coordinator's
sttalks to that machine'sst, which starts the worker there. That machine has to allow it (st orch accept-remote on).st0.3.85 or later on both.
Orchestration has the commands.