AO

Remote Access

Access the AO dashboard from another device or over Tailscale. Covers ports, binding, reverse proxies, power management, and security.

Remote Access

By default the AO dashboard binds to localhost:3000 and is only accessible from the machine it runs on. This page explains how to access it from another device — your phone, a second laptop, or a remote machine — using Tailscale or direct network binding.

Tailscale creates a private WireGuard mesh network between your devices. Every device gets a stable IP like 100.x.x.x and a DNS name like my-laptop.tail1234.ts.net. No port forwarding, no firewall rules required.

Setup

  1. Install Tailscale on both the machine running AO and the device you want to access it from.

    # macOS
    brew install --cask tailscale
    
    # Ubuntu / Debian
    curl -fsSL https://tailscale.com/install.sh | sh
  2. Start Tailscale and authenticate:

    sudo tailscale up
  3. Find your machine's Tailscale IP:

    tailscale ip -4
    # e.g. 100.64.0.1
  4. Bind AO to all interfaces so Tailscale traffic can reach it:

    # agent-orchestrator.yaml
    port: 3000

    Then start AO with HOST=0.0.0.0 so it listens on all interfaces (not just localhost):

    HOST=0.0.0.0 ao start

    Access the dashboard from another Tailscale device at:

    http://100.64.0.1:3000

    Or using the MagicDNS hostname (if you have MagicDNS enabled in your Tailnet):

    http://my-laptop.tail1234.ts.net:3000

Tailscale serve (optional — HTTPS)

For HTTPS with a valid certificate, use tailscale serve:

tailscale serve https:443 / http://localhost:3000

This makes the dashboard available at https://my-laptop.tail1234.ts.net with a Let's Encrypt certificate managed by Tailscale. The WebSocket connections for the terminal also work through Tailscale serve.

Binding to a specific interface

AO uses Next.js for the dashboard. To bind to all interfaces (required for any remote access without Tailscale serve), set the HOST environment variable:

# Bind to all interfaces
HOST=0.0.0.0 ao start

Or set it in your shell profile:

export HOST=0.0.0.0
ao start

To bind to a specific IP only (e.g. your Tailscale IP):

HOST=100.64.0.1 ao start

The terminal WebSocket ports (terminalPort and directTerminalPort) also need to be reachable. If you're using Tailscale, the mesh handles this transparently as long as the ports are not firewalled locally.

macOS: preventing sleep

If you run AO on a Mac, the machine going to sleep will kill the agents and the dashboard. Use caffeinate to prevent sleep while AO is running:

caffeinate -i ao start

caffeinate -i prevents idle sleep (triggered by inactivity) but still allows display sleep. For a machine you want to run headlessly overnight:

# Prevent all sleep (including display)
caffeinate -dims ao start

To run AO persistently in the background as a launchd service, create a plist at ~/Library/LaunchAgents/com.ao.orchestrator.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.ao.orchestrator</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/ao</string>
    <string>start</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>EnvironmentVariables</key>
  <dict>
    <key>HOST</key>
    <string>0.0.0.0</string>
  </dict>
</dict>
</plist>

Load it with:

launchctl load ~/Library/LaunchAgents/com.ao.orchestrator.plist

Reverse proxy

If you want to expose AO over a domain name with TLS, or place it behind an authentication layer, you can front it with nginx or Caddy.

Important: AO uses two WebSocket servers (one for tmux-attached terminals, one for direct PTY terminals). Your proxy must forward HTTP upgrade headers for both.

Environment variables for proxied setups

VariablePurpose
HOST=0.0.0.0Bind the Next.js dashboard to all interfaces
TERMINAL_PORTOverride the tmux WS server port (server-side)
DIRECT_TERMINAL_PORTOverride the direct PTY WS server port (server-side)
NEXT_PUBLIC_TERMINAL_WS_PATHOverride the WebSocket base path the browser client dials — required when the proxy rewrites the path

nginx

server {
  listen 443 ssl;
  server_name ao.example.com;

  # SSL cert config here

  # Dashboard
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  # Terminal WebSockets — tmux mux
  location /ws/terminal/ {
    proxy_pass http://127.0.0.1:14800/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
  }

  # Terminal WebSockets — direct PTY
  location /ws/direct/ {
    proxy_pass http://127.0.0.1:14801/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
  }
}

Then start AO with the matching path env var so the browser client dials through the proxy:

HOST=0.0.0.0 NEXT_PUBLIC_TERMINAL_WS_PATH=/ws/terminal ao start

Caddy

Caddy handles WebSocket upgrades automatically — no explicit Upgrade headers needed:

ao.example.com {
  reverse_proxy /ws/terminal/* 127.0.0.1:14800
  reverse_proxy /ws/direct/* 127.0.0.1:14801
  reverse_proxy 127.0.0.1:3000
}

Start AO the same way:

HOST=0.0.0.0 NEXT_PUBLIC_TERMINAL_WS_PATH=/ws/terminal ao start

Pinning WebSocket ports

By default AO auto-detects available ports for the WebSocket servers starting at 14800/14801. To pin them (required when configuring a reverse proxy), set them in agent-orchestrator.yaml:

port: 3000
terminalPort: 14800
directTerminalPort: 14801

Or pass them as environment variables when starting:

HOST=0.0.0.0 TERMINAL_PORT=14800 DIRECT_TERMINAL_PORT=14801 ao start

Accessing the dashboard from mobile

The AO dashboard is a responsive web app — it works on mobile browsers. Connect over Tailscale and open the URL in Safari or Chrome. The Kanban board and session detail views are usable on a phone screen.

Limitations on mobile:

  • The built-in terminal (xterm.js) works but is difficult to type in on a touch screen. Use it to read agent output; for sending messages use the session detail input field.
  • There is no native mobile app. Notifications go through your configured notifiers (Slack, desktop, etc.) — there is no push notification to the browser.

Security considerations

AO has no authentication. Anyone who can reach the HTTP port can view all sessions, read terminal output, send messages to agents, and trigger merges. Never expose the dashboard port to the public internet.

Mitigations:

  • Use Tailscale — the mesh is authenticated and encrypted end-to-end. Only your devices can reach the IP.

  • If you must use a public host, put a reverse proxy with HTTP Basic Auth (nginx, Caddy) in front of AO. See the Reverse proxy section above.

  • Firewall the port at the OS level and only allow Tailscale traffic:

    # UFW example — allow only Tailscale interface
    sudo ufw allow in on tailscale0 to any port 3000
    sudo ufw deny 3000

Environment variables and secrets in agent processes are visible to anyone with dashboard access. Do not run AO on a shared machine without Tailscale or auth.

Webhook endpoint: If you expose the dashboard publicly and have GitHub (or another tracker) configured, the /api/webhooks endpoint receives push events from GitHub. This endpoint is protected by a webhook secret configured in your tracker settings — verify the secret is set before exposing the port publicly.

For more on where AO stores session data and secrets, see Storage.

Port reference

PortDefaultConfig keyEnv var overridePurpose
3000dashboard HTTPportPORTNext.js app + API routes
14800tmux terminal WSterminalPortTERMINAL_PORTWebSocket for tmux-attached terminal
14801direct terminal WSdirectTerminalPortDIRECT_TERMINAL_PORTWebSocket for direct PTY terminal

If you run multiple AO instances on the same machine, change all three ports to avoid EADDRINUSE errors:

# Second AO instance
port: 3001
terminalPort: 14810
directTerminalPort: 14811

Note: When terminalPort and directTerminalPort are not set in the config or as env vars, AO auto-detects a free port pair starting from 14800. Set them explicitly whenever you configure a reverse proxy or firewall rules.

See also