August 23, 2026

Reaching AI Agents from a Phone with Paseo

Paseo turns long-running AI coding agent sessions on a headless machine into something you can watch and drive from your phone, over an end-to-end encrypted relay or a direct connection.

Coding agents like OpenCode, Claude Code, or Codex live inside the terminal of a working machine. The problem shows up when a session runs long — an agent mid-refactor on a headless server — and I am not in front of it. SSH from a phone works, but reading agent output on a small terminal is awkward. Paseo targets exactly that: a daemon managing agents, with mobile, desktop, web, and CLI clients to drive sessions anywhere. It manages agents, does not bundle one: install and authenticate at least one agent CLI (say OpenCode) on the same machine.

Why Paseo

Agents on headless machines have no interface; results only show up if you stay glued to a terminal. Previous options fell short: SSH from a phone offers no comfortable chat view; VNC is heavy for reading logs; manual port forwarding opens an attack surface. What I wanted: low latency, no unnecessary open ports, safety on untrusted networks. Paseo offers two paths — relay and direct.

Architecture and How It Works

Paseo uses a client-server architecture similar to Docker. The daemon runs where agents execute — default 127.0.0.1:6767, HTTP/WebSocket endpoints — owning the agent lifecycle: start, stop, stream output. Clients (phone, CLI, web) connect over WebSocket to render chat and terminal output in real time.

Two ways clients reach the daemon:

  1. Relay (recommended): the daemon opens an outbound connection to the relay server and clients meet it there — no port forwarding, no firewall setup. Traffic is end-to-end encrypted: persistent ECDH keypair on the daemon, public key to the phone over the QR code, then a NaCl box channel (Curve25519 + XSalsa20-Poly1305). The relay is untrusted by design: it only sees IPs, message sizes, session IDs.
  2. Direct: the daemon listens on a network address (LAN, Tailscale, VPN) and clients connect to host:6767. On untrusted networks, bring your own TLS/VPN.

Configuration and state live under PASEO_HOME (default ~/.paseo): config.json and the daemon keypair.

Deployment and Setup

Install the CLI via npm:

npm install --global @getpaseo/cli
paseo

The first paseo run starts the daemon, asks about the relay, prints a pairing QR code. To pair again:

paseo daemon pair

In Docker Compose, the official image runs the daemon and bundled web UI:

services:
  paseo:
    image: ghcr.io/getpaseo/paseo:latest
    container_name: paseo
    restart: unless-stopped
    ports:
      - "6767:6767"
    environment:
      PASEO_PASSWORD: "change-me"
      # PASEO_HOSTNAMES: "paseo.example.com,.lan"
    volumes:
      - ./paseo-home:/home/paseo
      - ./workspace:/workspace

The UI is reachable at http://localhost:6767. The image does not bundle agent CLIs; build a child image from ghcr.io/getpaseo/paseo:latest and install the agents you need, or run the provider login flow inside the container. Agent credentials live in /home/paseo; keep mounts scoped.

For access from outside without the relay, a Cloudflare Tunnel is the shortest path:

cloudflared tunnel --url http://localhost:6767

Start the daemon with the allowed hostname:

paseo daemon start --web-ui --hostnames "paseo.example.com"

Common mistakes:

  • Direct connection timeout: in the client app, use port 443 (the HTTPS tunnel/proxy port), not 6767, and enable SSL.
  • 403 Host not allowed: the domain is not on the allowlist; add it via --hostnames/PASEO_HOSTNAMES.
  • UI loads but never connects: the proxy must forward the WebSocket upgrade and X-Forwarded-Proto, or the app cannot pick wss://.

Security and Operations

  • Set a password. paseo daemon set-password or PASEO_PASSWORD; bcrypt hash stored in config.json. Passwords protect access, not traffic.
  • Host allowlist. The daemon validates the Host header (DNS-rebinding protection); by default only localhost and IPs. Custom domains go through daemon.hostnames.
  • Never bind 0.0.0.0 without a password. Anyone on the same network can drive your agents.
  • Treat the QR/pairing link like a password. It carries the daemon’s public key, the trust anchor; do not share it.
  • State needs guarding. Back up PASEO_HOME (config.json, daemon keypair) with the workspace volume. Logs live at $PASEO_HOME/daemon.log. Config changes: paseo reload covers most settings; startup-level ones need paseo daemon restart.

Honest Limitations

  • No bundled agents. Every agent CLI must be installed and its login maintained separately on the machine.
  • Relay is off by default. Remote access needs a pairing step, and relay mode depends on an external service — safe from reading thanks to end-to-end encryption, but still a dependency.
  • Password is not TLS. On a public network without relay or HTTPS, traffic can still be read; URL security relies on the proxy/tunnel edge.
  • A real attack surface. Agents can touch mounted workspaces and provider credentials; exposing the daemon to the internet raises the stakes.
  • Orchestration needs company. Some features rely on the GitHub CLI (gh), authenticated separately.
  • No high availability. Sessions live on a single machine; if it dies, they die with it.

Conclusion

Paseo fits a simple pattern best: one user, one working machine, following agent sessions from a phone while away. Setup demands discipline — password, hostname allowlist, scoped mounts — but then it bridges headless agents and a small screen well. For multi-user or multi-machine deployments it is not the first choice; a reasonable contextual limit, not a hidden flaw.

References