Keep every run in sight.
RunBuoy sends safe, read-only status for commands, builds, experiments, and AI agent runs from a Mac or Linux machine to a native iPhone app, Lock Screen Live Activity, and Dynamic Island.
runbuoy run -- python3 experiment.pyRunBuoy is not a remote terminal, SSH client, mobile tmux client, remote operations console, or phone-based agent approval tool.
flowchart LR
S["Shell / Codex"] --> C["runbuoy CLI + local Worker"]
C -->|"outbound HTTPS only"| A["RunBuoy Server"]
A --> P["APNs + Read API"]
P --> I["Native iOS app / Live Activity"]
Execution data flows only Machine → Server → iPhone. iPhone can pair,
register its own notification tokens, read Runs/Machines/Messages, change
local receiving preferences, and remove a receiving subscription. It cannot
start, cancel, retry, approve, signal, attach, type into, or otherwise control
a Machine. There is no remote command queue, Machine inbox, WebSocket control
channel, terminal stream, public terminal URL, or tunnel.
cli: Python 3.12 Typer CLI, tmux-owned Worker, PTY/process groups, local SQLite outbox, Unix event socket, progress adapters, and local-only controlsserver: FastAPI/SQLAlchemy/Alembic projection service, scoped credentials, pairing, webhooks, transactional push outbox, and mock/production APNsapps/ios: native SwiftUI iOS 18 app plus WidgetKit/ActivityKit extensionpackages/protocol: OpenAPI, JSON Schema, and cross-platform fixturesskills/runbuoy: explicit$runbuoyCodex skillinfra: API, worker, and PostgreSQL Docker Compose environment
cp infra/.env.example infra/.env
docker compose --env-file infra/.env -f infra/docker-compose.yml up --buildMock APNs records exact payloads and requires no Apple credentials.
uv tool install runbuoy
runbuoy doctor
runbuoy capabilities --jsonYou can alternatively use pipx install runbuoy. RunBuoy supports macOS and
Linux and requires Python 3.12 or newer. tmux is required for durable Runs;
install it with brew install tmux on macOS or your Linux system package
manager. Installation, upgrade, packaging, and release details are in
docs/cli-distribution.md.
Build the native app in apps/ios, set the Server HTTPS URL, bootstrap the
installation, and choose Pair New Machine. On the Machine:
runbuoy pairScan the short-lived QR code. The QR has a five-minute, single-use challenge and no long-lived token. One iPhone installation can pair multiple Machines.
runbuoy run -- python3 experiment.py
runbuoy notify \
--title "Build completed" \
--body "Release build succeeded" \
--level successThe default remote payload contains a safe title such as
python · experiment.py, structured status, progress, safe messages,
timestamps, and exit code. Full argv, cwd, environment, source, stdout,
stderr, terminal frames, input, and credentials stay local.
RunBuoy never fabricates percentage or ETA.
Structured progress:
from runbuoy import progress
progress(
current=37,
total=100,
phase="processing",
message="Processing item 37",
)runbuoy emit progress \
--current 37 \
--total 100 \
--phase processing \
--message "Processing item 37"Line progress:
runbuoy run \
--progress lines \
--total 100 \
--match '^Hello World$' \
-- python3 script.pyRegex progress:
runbuoy run \
--title "Gurobi Experiment" \
--progress regex \
--pattern '^PROGRESS: ([0-9]+)/([0-9]+)$' \
-- python3 experiment.pyWithout an explicit source, the Live Activity shows indeterminate state, elapsed time, phase, and last update—never a synthetic percent.
These commands read or control only local files, tmux, the recorded process group, or a mode-restricted Unix socket:
runbuoy list
runbuoy status <run-id>
runbuoy logs <run-id>
runbuoy attach <run-id>
runbuoy cancel <run-id>No Server or iOS endpoint can invoke them.
Other commands:
runbuoy pair
runbuoy notify
runbuoy emit progress
runbuoy emit phase
runbuoy emit message
runbuoy emit attention
runbuoy doctor
runbuoy config
runbuoy capabilities --jsonFull logs remain local. A bounded, redacted tail is opt-in:
runbuoy run --share-log-tail 20 -- commandThe limit is 1–100 lines. ANSI is stripped, line and payload sizes are bounded, credential patterns are redacted, iOS labels the uploaded excerpt, and Server retention is at most 24 hours. This is not a terminal stream.
Revocable webhook credentials use the Authorization header:
curl -X POST "$RUNBUOY_URL/v1/hooks/$HOOK_ID/notifications" \
-H "Authorization: Bearer $RUNBUOY_HOOK_SECRET" \
-H "Content-Type: application/json" \
-d '{"title":"Build completed","body":"Release build succeeded","level":"success"}'External Run upsert and event paths are:
PUT /v1/hooks/{hook_id}/runs/{external_run_id}
POST /v1/hooks/{hook_id}/runs/{external_run_id}/events
Long-lived secrets never appear in URLs. iOS renders a safe Markdown subset without HTML, JavaScript, executable schemes, or WebView.
- A Run still active after five seconds may start a Live Activity.
- Short success is history-only; short failure receives a normal alert.
- Each Device gets at most two active activities, prioritized by attention, failure/warning, then recency.
- Ordinary progress is coalesced to at most one update every three seconds and less than 1% changes are suppressed.
- Phase, attention, terminal success, and failure update immediately.
- Heartbeats update health but do not directly cause a push.
- APNs 410 invalidates the token; retry is bounded.
Production APNs uses HTTP/2, TLS, ES256 provider tokens, current rotating
ActivityKit tokens, and Apple-specified headers/payloads. See
docs/apns-setup.md.
The iOS 18 app is Swift/SwiftUI with ActivityKit, WidgetKit, UserNotifications, URLSession, Keychain, local cache, and QR scanning. It has no React Native, Expo, JavaScript runtime, WebView, terminal/SSH/PTY library, persistent WebSocket, background polling, mutation App Intent, or notification action.
Build without signing:
xcodebuild \
-project apps/ios/RunBuoy.xcodeproj \
-scheme RunBuoy \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO \
buildSigning and physical-device steps are in
docs/ios-signing.md.
uv sync --group dev
uv run pytest packages/protocol/tests
uv run python scripts/check_read_only_boundary.py
(cd cli && uv sync --all-groups && uv run pytest)
(cd server && uv sync --all-extras && uv run pytest)
./scripts/e2e_smoke.shCI runs protocol/security, Ruff/mypy, Server tests with PostgreSQL, CLI tests
on Linux and macOS with tmux, unsigned iOS build/tests, and mock-APNs E2E.
See docs/development.md.
- Product requirements
- Architecture
- Protocol
- Security and threat model
- Self-hosting
- APNs setup
- CLI distribution and PyPI releases
- Code provenance
- macOS and Linux only; Windows is not an MVP target.
- Anonymous workspaces only; there are no email accounts or teams.
- APNs production delivery requires external Apple credentials and a physical signed device and is not exercised by CI.
- Push delivery is best effort; offline Runs remain locally durable and later converge.
- Safe log-tail sharing is intentionally bounded rather than real-time.
RunBuoy retains the upstream MIT license and history. See
THIRD_PARTY_NOTICES.md.