st의 동작 방식

st가 장치에서 무엇을 실행하는지, 앱이 어떻게 접속하는지, 무엇을 제공하는지, 에이전트가 하는 일을 어떻게 아는지 설명합니다.

st는 각 장치에서 실행되는 부분입니다. 데스크탑 앱, 폰 앱, 브라우저에 보이는 것은 모두 화면에 나온 그 장치의 st에서 옵니다.

바이너리 하나, 에이전트 프로세스 하나

st는 파일 하나이고, 필요한 것은 모두 그 안에 들어 있습니다. ttyd, tmux, zellij(Windows에서는 Windows용 tmux인 psmux도)와 st가 제공하는 웹 페이지입니다. 처음 실행할 때 이것들을 캐시에 풀어 둡니다. 장치에 tmux가 이미 있으면 내장된 것보다 그쪽을 먼저 씁니다.

st를 실행하면 백그라운드에 에이전트 프로세스가 뜨고 프롬프트는 바로 돌아옵니다. 그 뒤의 일은 에이전트가 맡습니다.

  • 터미널을 로컬 포트에 올리고 토큰으로 막아 둡니다.
  • 바깥에서 들어올 경로를 엽니다(아래 참고).
  • tmux 서버의 모든 pane을 살펴 코딩 에이전트를 찾습니다.
  • 로그인돼 있으면 장치가 살아 있다는 것, 세션 목록, 에이전트 상태를 계정에 알립니다.

tmux 세션은 에이전트가 아니라 tmux에 속합니다. st exit는 에이전트와 터널만 멈추고 세션은 계속 실행됩니다. 업그레이드할 때도 실행 중인 공유를 끊지 않고 새 버전에 넘깁니다. 기본은 tmux이고, zellij를 쓰거나 멀티플렉서 없이 쓸 수도 있습니다. 멀티플렉서를 보세요.

브라우저 또는 앱데스크탑 앱, 폰 앱, 브라우저
Cloudflare quick tunnelFreetrycloudflare.com, 실행마다 새 주소
sophonz 릴레이Proquiet-otter-lamp.relay.sophonz.io, 바뀌지 않는 주소
내 장치 — 바깥에 열어 둔 포트는 없습니다
proxytoken웹 페이지 · API · 로그인
ttyd모든 세션이 프로세스 하나를 씁니다
tmux · zellij · shell세션은 어떤 연결보다 오래 남습니다
브라우저 또는 앱 ──https──► Cloudflare quick tunnel(Free) 또는 sophonz 릴레이(Pro) ──► proxy (token) ──► ttyd ──► tmux | zellij | 셸

ttyd는 고치지 않고 pty ↔ 웹소켓 연결에 쓰며, 루프백에서만 연결을 받습니다. 클라이언트가 보는 것(웹 페이지, API, 로그인)은 모두 그 앞의 프록시가 제공합니다.

접속 경로

공개 주소를 어디서 받는지는 요금제에 따라 다릅니다. Free에서는 Cloudflare quick tunnel이 주소를 주고, 터널이 시작할 때마다 주소가 바뀝니다. Pro에서는 sophonz 릴레이가 주소를 주고, 그 주소는 바뀌지 않습니다. 어느 쪽이든 장치가 바깥으로 연결을 맺으므로 장치에 열린 포트는 없습니다. 장치의 tailnet을 쓸 수도 있습니다(st 0.3.93 이상). 장치가 어느 경로를 쓰는지는 st route로 확인하고 바꿉니다.

  • 같은 컴퓨터에서. 에이전트는 127.0.0.1에서 연결을 받습니다. 데스크탑 앱은 여기서 이 컴퓨터의 st와 통신합니다. st --no-tunnel은 여기까지만 열고 공개 주소는 만들지 않습니다.
  • quick tunnel로(Free). st가 cloudflared를 띄우면 cloudflared가 Cloudflare에 바깥쪽으로 연결을 맺고 trycloudflare.com 주소를 받아 옵니다. st는 토큰과 앱 링크 https://link.sophonz.io/j#<터널 주소>, 그리고 그 링크를 담은 QR 코드를 출력합니다. 터널 주소는 링크의 # 뒤에 들어 있어서, 그 부분을 복사해 바로 열 수도 있습니다. SOPHONZ_QR_DIRECT=1이면 예전처럼 터널 주소를 그대로 보여 줍니다. 바깥에서 들어오는 연결을 기다리는 포트는 없습니다. 연결은 장치 쪽에서 맺은 것입니다. 터널이 다시 시작되면 주소가 바뀌고, 연결이 끊기면 st가 알아서 다시 잇습니다. Cloudflare는 이 경로를 테스트와 개발용으로 내주며 가동 보장이 없습니다(실행 방식).
  • sophonz 릴레이로(Pro). Pro에는 quick tunnel 대신 쓰는, sophonz가 운영하는 릴레이 서버가 포함됩니다. st가 릴레이로 웹소켓 하나를 열고, 릴레이는 그 장치의 호스트명으로 온 요청을 이 연결로 보냅니다. 여기서도 바깥에서 들어오는 연결을 기다리는 포트는 없습니다. 주소는 https://quiet-otter-lamp.relay.sophonz.io 같은 세 단어이고 다시 시작해도 바뀌지 않습니다. 와일드카드 인증서 한 장이 모든 장치를 덮고, 릴레이가 컨트롤 플레인에서 장치 목록을 30초마다 받아 오므로 장치를 더해도 릴레이가 다시 시작하지 않고 다른 장치의 연결도 끊기지 않습니다. st 0.4.1부터 Pro 계정에 로그인한 장치는 릴레이 주소와 토큰을 스스로 받아 오고, 직접 고른 경로와 직접 운영하는 릴레이(st relay set)가 그보다 먼저입니다. st relay reset은 quick tunnel을 고르고 거기에 머뭅니다. 고른 tailnet은 그대로 둡니다(공유하기). 릴레이에서는 계정 링크와 앱의 장치 목록이 동작하고, 알림은 아직 확인되지 않았습니다. 터널에서 Cloudflare가 그렇듯 릴레이도 바이트를 넘겨 주는 자리이고, 터미널 트래픽은 아직 종단 간 암호화되지 않습니다.
  • tailnet으로. tailnet에 들어 있는 장치에서는 처음 실행한 st가 QR을 보여 주기 전에 폰이 어떻게 접속할지 한 번 묻습니다. Tailscale에서는 tailnet에 HTTPS 인증서가 있으면 st가 tailscale serve로 https://<machine>.<tailnet>.ts.net을 엽니다. 인증서가 없으면 그냥 http://100.x.y.z:<port>를 엽니다. 브라우저는 이 주소를 열지만 앱은 열지 않습니다. 같은 tailnet의 기기만 들어올 수 있으므로 폰에 Tailscale 앱이 있어야 합니다. Tailscale에서는 계정 링크, 앱의 장치 목록에서 열기, 알림이 아직 동작하지 않습니다.
  • 계정으로. st login을 하고 나면 장치에 바뀌지 않는 링크 link.sophonz.io/m/<slug>가 하나 더 생깁니다. 이 링크는 로그인한 본인에게만 열립니다. 링크로 들어가면 컨트롤 플레인이 장치의 에이전트에게서 한 번 방문할 때 1분 정도 쓸 수 있는 키를 받아, 장치의 현재 주소로 보내 줍니다.

터미널을 여는 토큰은 장치 밖으로 나가지 않습니다. 컨트롤 플레인이 저장하는 것은 장치의 현재 주소뿐이고, 그것만으로는 장치를 열 수 없습니다. 로그인과 링크를 보세요.

데스크탑 앱과 폰 앱은 계정에 있는 장치를 모두 보여 주고, 각 장치를 이 방식으로 엽니다. 어느 방식이 맞는지는 실행 방식에서 비교합니다.

웹 터미널과 API

주소 하나에서 토큰 뒤로 다음을 제공합니다.

  • 웹 터미널. 키바, 프롬프트가 쓰는 Nerd Font 글리프(처음 받은 뒤 캐시합니다), 세션과 창을 보여 주는 사이드바, 채팅, 에이전트 그래프를 갖춘 터미널 페이지입니다. 브라우저 문서에서 설명합니다.
  • 앱이 쓰는 API. 세션·창·pane(목록, 열기, 새로 만들기, 이름 바꾸기, 닫기), 에이전트와 그 질문, 질문에 답하기, pane의 대화를 채팅으로 보고 입력하기, 문서, 장치 지표, 플랜 한도와 지난 세션, pane의 파일과 git 변경 사항(읽기 전용), GitHub Actions 실행 내역, st 업그레이드, 오케스트레이션 그래프를 다룹니다.

이 모두가 장치에서 클라이언트로 바로 갑니다. 터미널, 채팅, 지표는 컨트롤 플레인을 거치지 않습니다.

에이전트 상태

내 장치
에이전트 훅Claude Code, Codex, Gemini CLI, opencode, Pi, Kimi Code, Qwen Code가 이벤트마다 실행합니다
st hook짧은 이벤트 하나를 보내고 끝납니다. 대화 기록도 프롬프트도 보내지 않습니다
st 에이전트화면에서 읽은 내용을 더합니다
상태와 질문pane마다 일하는 중 · 기다리는 중 · 끝남 · 오류 · 대기 중
앱장치에서 바로, 실시간으로 읽습니다
컨트롤 플레인이름, 상태, 개수, 시각만
내 기기들로 푸시암호화내용은 기기마다의 키로 암호화됩니다
에이전트 훅 ──► st hook ──► 장치의 st 에이전트 ──► 상태와 질문. 거기서 장치의 주소를 통해 앱으로, 그리고 이벤트로 컨트롤 플레인에 ──► 내 기기들로 푸시
  1. st hooks install이 Claude Code, Codex, Gemini CLI, Kimi Code, Qwen Code의 설정에 st hook을 넣고, opencode에는 플러그인을, Pi에는 확장을 써 넣습니다. 에이전트는 이벤트가 생길 때마다 이것을 실행합니다.
  2. st hook은 같은 장치의 st 에이전트에게 짧은 이벤트 하나를 보내고 끝납니다. 대화 기록도 프롬프트도 보내지 않습니다.
  3. 에이전트는 이벤트와 화면에서 읽은 내용을 바탕으로 pane마다 상태를 정합니다. 상태는 일하는 중, 기다리는 중, 끝남, 오류, 대기 중 하나입니다. 읽어 낼 수 있는 질문은 선택지가 달린 프롬프트로 바뀝니다.
  4. 앱은 이 상태를 장치에서 실시간으로 읽습니다. 로그인돼 있으면 에이전트가 상태 변화를 컨트롤 플레인에도 보내고, 컨트롤 플레인은 Apple과 Google을 거쳐 알림을 보냅니다.
  5. 알림 내용은 장치에서 암호화하는데, 알림을 받는 기기마다 그 기기의 키를 씁니다. 컨트롤 플레인과 Apple, Google이 볼 수 있는 것은 이름, 상태, 개수, 시간뿐이고, 라이브 액티비티에 실리는 내용도 이것뿐입니다.

훅이 없어도 st는 화면에서 상태를 읽어 냅니다. 다만 덜 정확합니다. 상태별 의미와 어디에 표시되는지는 에이전트와 상태에 있습니다.

오케스트레이션도 같은 부품 위에서

에이전트가 다른 에이전트에게 일을 맡기는 데 새 서버는 필요하지 않습니다. 같은 st가 이미 가진 것을 그대로 씁니다.

  • 워커는 tmux 창입니다(Windows에서는 psmux이고, 거기서는 워커를 띄우지 않고 데려옵니다). st worker start가 코디네이터의 세션에 창을 열고 그 안에서 Claude Code, Codex, opencode, Pi 중 하나를 시작합니다.
  • 작업은 앱의 채팅과 같은 방식으로 입력됩니다. 에이전트 자신의 입력창에 넣으며, 질문 화면 위에 입력하는 일은 없습니다.
  • 훅으로 시작을 확인합니다. st worker start는 워커의 훅이 턴을 시작했다고 알려 온 뒤에야 끝납니다.
  • 누가 누구의 워커인지는 st를 다시 시작해도 남습니다. pane이 닫히면 함께 사라집니다. 실행(run)의 목표와 워커의 보고는 ~/.sophonz/orch/<run>/run.json에 남습니다.
  • 다른 장치에는 계정을 통해 닿습니다. 앱이 다른 장치를 여는 방식과 같습니다. 코디네이터의 st가 그 장치의 st에 요청하면 워커는 그 장치에서 시작됩니다. 그 장치가 이를 허용해야 하고(st orch accept-remote on), 두 장치 모두 st 0.3.85 이상이어야 합니다.

명령은 오케스트레이션에 있습니다.