Skip to main content

Hub Mode

The hub is a session-less supervisor. It binds the well-known port, runs the shared services (WebSocket server, mDNS, relay, Telegram, device tokens), serves the machine's session list, and spawns child session daemons when a client asks it to create one. It never spawns Claude Code itself and never appears in that session list as a session of its own.

This is the mode behind remi start / remi serve, the macOS menu-bar app, and the iOS/web apps' "create session" flow. It is separate from wrapper mode (plain remi in a terminal, which spawns Claude directly in that terminal) and from a single-purpose remi --daemon you start by hand.

Why a hub

Before the hub existed, remi start created one Claude session per invocation, and that daemon's only job was to host it. That works for one machine watched from one phone, but it does not compose: every remote client needs to know a session already exists somewhere before it can talk to anything, and there is no single place that answers "what is running on this machine right now, and can I start something new."

The hub answers both. It is always reachable at a predictable port, it can list every session daemon running on the machine (including ones it did not spawn), and it can spawn a new one on request. Session daemons keep doing exactly what they did before: one process per Claude Code session, PTY, hooks, transcript watching.

Starting the hub

# Foreground (the LaunchAgent / systemd entrypoint)
remi serve

# Detached (background)
remi start

# Stop the hub only; session daemons keep serving
remi stop

# Stop the hub AND every session daemon it or you started
remi stop --all

# Status: hub PID, port, connections, adapters, plus any running session daemons
remi status

remi serve and remi start both prefer the well-known port (18765) and probe a 20-port range if it is taken. Neither one creates a session; sessions come from the app or from remi new. The hub self-writes ~/.remi/daemon.pid at boot, which is also how a LaunchAgent-launched hub (see below) becomes visible to remi stop/remi status.

remi status and remi stop used to only know about the hub itself, so a running session daemon was invisible to both; "Daemon is not running" could be true right next to a daemon remi ls was actively listing. Both commands now enumerate session daemons too (pid, port, name), and remi stop without --all names whatever it left running instead of going quiet about it.

Autostart (--install)

remi --install

Installs and starts a login item that runs remi serve:

  • macOS: a LaunchAgent at ~/Library/LaunchAgents/com.yooz.remi.plist, resolved through your PATH so it survives a Homebrew upgrade.
  • Linux: a systemd user service at ~/.config/systemd/user/remi.service.

KeepAlive/restart-on-failure is configured so a crash brings the hub back, but a clean remi stop is not resurrected. Remove the login item with remi --uninstall.

Session daemons

A session daemon is what you get from remi new, from creating a session in the app, or from running remi (wrapper mode) or remi --daemon directly. Each one:

  • Hosts exactly one Claude Code session and its PTY.
  • Writes its own ~/.remi/status-<port>.json (never the shared daemon-status.json, which belongs to the hub alone).
  • Is discoverable by any daemon on the machine through ~/.remi/live-sessions/, which is how remi ls shows sessions spawned by a completely different process.
  • Shares the hub's ~/.remi/daemon.log when it was spawned by the hub (it is launched with REMI_SPAWNED_CHILD=1 and the same log file); a manually run remi --daemon writes to that same file too.

Multi-writer sessions

Any client attached to a session, phone, browser, another terminal via remi attach, can send input. There is no exclusive per-session lock: earlier versions of remi allowed only one non-read-only client to write and queued the rest, which meant a phone racing a laptop for control routinely lost and had nothing useful to do until the other side detached. That lock is gone. Safety moved to where it actually matters: a per-session serialized write queue ensures concurrent input from multiple clients is applied one keystroke-batch at a time rather than interleaved. Terminal resize is last-writer-wins; a read-only "query" connection (like the macOS menu-bar app's own status view, or remi ls) never counts as a writer and never competes for the queue.

The macOS menu-bar app

packages/macos/ in the remi repo ships a sandboxed, attach-only SwiftUI app that discovers a local hub by port-scanning 127.0.0.1:18765-18784, connects in query mode, and hosts the full web UI in a window. It cannot start or stop the hub (App Sandbox has no process-signal capability); it walks you through remi --install / remi start in an onboarding panel instead. See docs/MACOS_APP.md in the remi repository for the full design.