Mob is mob programming with your AI agents: they work on your office computer, you steer from your phone or laptop. mobd is the office side; the apps are Mob Desktop, Mob for iPhone and Mob for Android. The name comes from mob programming, where a team works on one computer together: one at the keyboard, the others steering. In Mob the team is you and your agents.
Office control plane: HTTP task API + Codex, Claude Code and Pi runners. The same binary provides the desktop actuation MCP server (mobd mcp), launched by the agent for each task.
Desktop clients connect over HTTP/SSE and the live desktop stream (WebRTC, with a WebSocket fallback). The desktop app lives in xrdavies/mob; this repository builds independently.
On macOS the agent drives the office Mac's own desktop. On Linux every conversation gets its own virtual desktop (Xvfb and openbox) that sleeps when idle; see Linux.
Source: Go 1.27+ and Xcode Command Line Tools on macOS; on Linux a C compiler, pkg-config and
the GStreamer development headers (building on Linux). Run make build,
make check (Go vet/tests plus Node.js 24+ distribution tests), or make race.
See architecture, API reference
and runners.
Releasing: configure the GitHub release environment using the release guide,
then run make release-patch (or release-minor, release-major, or make release VERSION=vX.Y.Z) from a clean main matching origin/main. It bumps the version in cmd/mobd/main.go, asks for confirmation, commits it, tags that commit and pushes both; make version shows the current version
(Node.js 24+ and Git required). CI signs and notarizes the binary and creates a draft
Release; test its archive, then publish the stable Release on GitHub to distribute it through R2.
Install on macOS 13+ with Apple Silicon, as your normal user:
curl -fsSL https://dl.mymob.dev/mobd/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"Add the export PATH=… line to ~/.zshrc to make it available in new terminals.
The installer downloads the latest stable version from the public manifest,
checks SHA-256 and the Apple signing identity, and installs only mobd into
~/.local/bin. The archive also includes LICENSE and README.md.
No sudo, Go, Node.js, Python or source checkout is needed to install the binary.
See the release/installation guide for a pinned version or custom directory.
The standalone binary needs no source checkout, Go, Makefile or service scripts:
mobd --version
mobd init # creates ~/.mobd/config.json; never overwrites
# init defaults to "0.0.0.0:8080" for LAN clients.
# Set workdir and codexBin explicitly for a stable background environment.
mobd start # background service until stopped/logout
mobd status # address and /health check
mobd logs # follow logs (Ctrl-C to stop following)
mobd restart # reload config/environment/flags
mobd stop # stop; also removes login startup
mobd start --login # opt into startup at user login
mobd serve # foreground; Ctrl-C to stopInstall and authenticate the agent CLI you plan to use (Codex, Claude Code or Pi).
Pi is opt-in: add "pi" to runners; see Pi setup and tests.
On macOS, mobd start in a terminal walks through the permissions mobd and its agents need,
one prompt at a time: Accessibility, Screen & System Audio Recording, the Desktop, Documents
and Downloads folders (or Full Disk Access) and Local Network. Approve them then: the mob apps
cannot answer a prompt later. mobd permissions asks for missing ones again, and /health
reports them. The live desktop is
streamed by mobd itself (H.264 over WebRTC); Screen Sharing is not needed. For direct LAN/VPN access,
configure listen for that interface; loopback is enough behind Tailscale Serve,
a gateway or an SSH tunnel. mobd init sets listen to 0.0.0.0:8080 (all IPv4 interfaces).
The download domain serves files; clients connect to your office computer's mobd URL.
Follow the office smoke checklist to verify the setup.
To upgrade, finish active tasks, run mobd stop, rerun the installer, then run
mobd start (or mobd start --login to restore login startup). Configuration,
conversations and attachments are preserved. Stop a foreground mobd serve
process manually before installing. mobd does not use Tauri updater.
The background service is a per-user macOS LaunchAgent, not a root daemon. start
rejects an already loaded service or occupied port. restart preserves login
startup and reuses the saved config path/flags when no new options are supplied. Configuration resolved at start is saved privately to
~/.mobd/service.json; the LaunchAgent uses that snapshot, absolute Codex/Claude paths
when available, and the invoking shell's PATH/workdir. Config edits take effect
on restart. There is one managed service per user. stop, status and logs
operate on that service, independently of the caller's environment. Logs are in
~/.mobd/mobd.log. Use a stable binary path and stop before replacing the binary.
Ubuntu 22.04/24.04 and Debian 12/13 (amd64, arm64) are supported. The .deb from the
release brings Xvfb, openbox, dbus and GStreamer:
sudo apt install ./mobd_<version>_amd64.deb
mobd init && mobd start # a systemd user service; it enables linger
mobd desktops doctor # programs, cgroup v2, encoder, browsers, memory, diskOr the same installer as on macOS, which puts the tarball's mobd in ~/.local/bin and prints
the apt install line for the packages above. Other distributions:
building on Linux.
- A desktop per conversation: its first turn starts it; agents get its
DISPLAY, bus and aPATHwhosegoogle-chrome,chromiumandfirefoxuse the conversation's own browser profile andDownloads. Desktops sleep in the cgroup freezer afterdesktopSleepAfterwith no turn, viewer, CPU use or screen change, and wake for the next turn or live view. - Closing: archiving or deleting a conversation, the apps' "Close desktop", or
mobd desktops close.mobd stopasks whether to close them (--close-desktops,--keep-desktops); kept desktops re-attach on the next start.mobd uninstall(andapt remove) closes every desktop and removes the service; data is kept. - CLI:
mobd desktops list | close | wake | logs | doctor | seed.seed SESSIONmakes that conversation's browser profiles (log in to sites there first, then close the browser) the start of every new conversation's profile. - Memory and disk: below
memoryFloorPercentordiskFloorBytesno new desktop starts; turns of conversations with a desktop still run./health,GET /desktopsand SSE report it. - USB devices from Mob Desktop:
sudo mobd usb setuponce lets this computer use a device shared from a user's computer, as if it were plugged in here (the vhci-hcd kernel module and a small root helper that only attaches and detaches).mobd usb statusshows whether it is ready. USB redirection. - Live view: H.264 from VA-API, NVENC or OpenH264 in software through GStreamer. Allow UDP
streamPortin the firewall (sudo ufw allow 9527/udp) or use the WebSocket fallback. - Not isolated: desktops, agents and mobd run as one Unix user; agents can read each other's profiles and files.
Developers build/run the same CLI:
make build
make init
make start ARGS='--listen 0.0.0.0:8080'
make restart
make status
make logs
make stop
make serve ARGS='--config ./config.dev.json'A plain make build is ad-hoc signed, so macOS treats every rebuild as a new app and drops
its Screen Recording and Accessibility grants. On a Mac you rebuild often, sign dev builds
with a self-signed code-signing certificate kept in its own keychain
(make build SIGN_IDENTITY="mobd dev"): grant once, and later rebuilds keep the grants.
Setup and design: Signing dev builds.
Every client pairs once and then authenticates with its own persistent credential until it is revoked, deleted or authentication is reset. There is no anonymous mode. See pairing.
mobd link # QR + one-use link (10 minutes) for the remote
# address of mobd remote, else the LAN one
mobd link --lan # always the office network (LAN) address
mobd link --url https://office.example.com # exactly this address
mobd remote # remote access: show it and check it reaches mobd
mobd remote setup # set up Tailscale Serve or Cloudflare Tunnel (wizard)
mobd remote off # turn it off; paired devices stay paired
mobd clients list
mobd connections
mobd clients revoke CLIENT_ID
mobd clients delete CLIENT_ID
mobd link --close # close unused invitations only
mobd auth reset --yes # invalidate all credentials; retain historyCommands use a private local management socket. Use the address the client actually
reaches; link generation does not test connectivity or change the listener. An HTTP URL
such as http://192.168.1.20:8080 prints a risk warning to stderr and still generates
the link: HTTP does not encrypt invitations, credentials or data.
For separate home and office networks, use Tailscale + Tailscale Serve when both
devices can install Tailscale: a private HTTPS URL carries API requests, SSE and live
desktop signaling, and the video flows directly over the tailnet. Without Tailscale on
the client, use Tailscale Funnel, Cloudflare Tunnel, a self-hosted HTTPS gateway or an SSH tunnel; there
the live desktop falls back to the /stream/ws WebSocket. The
connection guide covers each option step by step.
A managed relay is planned, not implemented.
JSON mcpArgs, corsOrigins and runners are arrays of strings. questionTimeout is a Go duration string such as "30m".
Priority: command-line flags > exported MOBD_* variables > JSON > defaults.
The default config is ~/.mobd/config.json; a missing default file is allowed,
while an explicitly supplied --config path must exist. Unknown fields, malformed
JSON, invalid addresses and invalid concurrency values fail with an error.
init creates a private directory/file (0700/0600). No .env is read or executed.
Supported flags: --config, --listen, --codex-bin, --workdir,
--state-path, --max-running, --control-socket. Put other options in JSON or exported variables.
Relative filesystem paths resolve from the invoking directory. Use absolute paths
in long-lived configs; JSON does not expand ~, $HOME, or shell expressions.
All JSON keys and their environment equivalents are listed below. An exported
empty string overrides JSON too: it clears optional values, but is invalid for
required settings. MOBD_MOB_MCP_ARGS must be a JSON array (no comma fallback).
Conversation snapshots default to ~/Library/Application Support/mob/state.json
on macOS. Set statePath / MOBD_STATE_PATH to use a different location.
If the configured agent executable is unavailable, its runner reports an error and marks the task failed.
End users configure ~/.mobd/config.json and run the installed mobd directly;
no .env, Makefile or source checkout is required. Create a minimal config once:
mobd init
# Edit ~/.mobd/config.json before starting.
mobd start
mobd statusconfig.example.json is a more complete example
with runner settings. If you have downloaded the example or checked out the
repository, run the following from the directory containing it:
mkdir -p -m 700 "$HOME/.mobd"
cp -n config.example.json "$HOME/.mobd/config.json" # preserves an existing config
chmod 600 "$HOME/.mobd/config.json"Use either mobd init or the copy command for initial setup. If the config
already exists, edit it and use the example as a reference; neither command
overwrites or merges your existing settings.
Before starting, replace /Users/you/Projects with an existing absolute working
directory. Set codexBin / claudeBin to executable names on your shell's PATH,
or their absolute paths. Claude uses workdir unless you set claudeWorkdir.
To enable only Codex, set runners to ["codex"].
config.json says how mobd itself runs. The default agent and, per agent, the model
list, default model and effort and permissions are runtime settings: they live in
settings.json in the data directory, created on the first start, and are changed in
the apps (Settings → Agents) without a restart (settings).
mobd refuses to start while config.json or the environment still sets defaultRunner,
codexModels, claudeModels, piModels, codexPermissions, claudePermissions or
piPermissions, and names them: delete them, then mobd start (which also rewrites
service.json).
The example listens on 127.0.0.1:8080; keep it for Tailscale Serve or SSH tunnels.
For direct LAN/VPN clients, bind to the intended private interface (or
"0.0.0.0:8080" behind appropriate network controls).
Optional fields omitted from the example use their defaults,
including the conversation and artifact locations. JSON must not contain
comments or trailing commas, and paths do not expand ~ or $HOME.
After editing the config, run mobd restart to apply it. Use mobd serve to run
in the foreground, or select another file with mobd start --config /path/to/config.json.
Exported MOBD_* variables and command-line flags still take precedence over JSON.
For development, the repository root contains a mobd .env.example.
From the repository root, copy it once and edit .env for your machine:
cp -n .env.example .env # preserves an existing .env
# Edit .env: listen address, runner executables and working directories.This developer example listens on 127.0.0.1:8080; keep it for proxy/tunnel access,
or choose a private interface for direct LAN/VPN clients.
Its working directory is the directory where you source the file
($PWD); use a fixed absolute path if needed. This file configures mobd;
the desktop environment example configures the frontend.
Load the variables into the current shell and start the background service:
set -a
source ./.env
set +a
make start
make statusset -a exports the assignments so the child mobd process can read them.
Use make serve instead of make start for foreground operation. If you
installed the standalone binary, use mobd start / mobd serve / mobd status
after the same source steps. Neither mobd nor Makefile reads .env automatically.
mobd init is optional: an existing JSON config still loads, and exported
variables override its values; other fields use JSON or defaults.
After editing .env, reload it and restart the service:
set -a
source ./.env
set +a
make restart # or: mobd restartThe LaunchAgent keeps the resolved configuration from start / restart,
so it continues using those values after you close the shell. Removing a line
from .env does not unset a variable already exported in your shell; use
unset VARIABLE_NAME before reloading to fall back to JSON/defaults. An empty
assignment is an explicit override, not a request to use the default. .env
is ignored by Git; commit only the example.
mobd and the desktop client are released independently. /health advertises apiVersion: 3 with features, runners (each with models, efforts and readiness), the default runner's models/efforts, and readiness checks. Clients should use these capabilities rather than require matching product versions. API version 3 is separate from the binary release version printed by mobd --version.
- Every new turn starts
queued. A FIFO dispatcher runs at mostMOBD_MAX_RUNNINGturns at once; keep the default1for a shared desktop. Dispatch is asynchronous, so a newly submitted turn can still be queued before its runner starts. Queued tasks reportqueuePosition; cancelling an undispatched task finishes it without starting a runner. Queued turns are marked failed on restart, like running ones. - Codex
mcp_tool_call,command_execution,reasoning,todo_list,file_change,web_searchanderroritems become persisted activities (upserted by item ID, text capped at 16 KiB); tool screenshots are saved as artifacts.turn.completedusage is stored on the task. POST /taskswithoutsessionIdcreates a conversation and its first turn atomically. WithsessionId, it continues that conversation; an active turn returns 409, without adding a user message.- Codex defaults to
app-servermode: a new conversation starts a thread, and later turns resume its stored ID. It supports streamed replies, mid-turn steering and approval requests.codexMode: "exec"selects the JSONL CLI fallback. Claude Code is also implemented; each conversation stays pinned to the runner that started it (runners). - Conversation IDs, runner resume IDs, messages and task states are written atomically before acknowledgement/delivery. The file has mode 0600. Keep it and Codex's own session directory on the office host. Run one mobd writer per state path; this snapshot store is intended for a single office daemon.
- On restart, interrupted turns become failed and are never rerun automatically. Completed messages and resume IDs remain available for continuation.
- Cancel returns 202 and keeps the conversation busy until the process stops and output readers finish. SIGINT/SIGTERM also cancels active turns before shutdown.
- Use a service supervisor that cleans up child processes after a hard daemon crash/SIGKILL; otherwise stop any orphaned Codex process before resuming its conversation. Snapshot recovery marks the interrupted turn failed, but cannot stop a process left by a dead daemon.
- SSE events have monotonic per-task
idfields.Last-Event-ID/?after=resume in-process replay without duplicating messages. Raw logs are memory-only and capped at 2000 lines per task; after a daemon restart,GET /sessions/:idis the authoritative transcript and task snapshot.
Runner protocol details, approval behavior and the shared contract tests are documented in runners.
CORS covers API responses and SSE, plus OPTIONS preflight for GET/POST/PATCH/DELETE with Content-Type or Authorization. Use the client's origin (scheme, host, and port, without a trailing slash), not the office API address. CORS does not replace authentication or LAN/VPN access controls.
- Mob Desktop local mobd proposal: enable the local machine by default, manage local mobd from Desktop, and add remote machines as needed.
- Agent concepts, collaboration design and cross-machine collaboration: shared terminology, the local request/reply flow and the first federation proposal.
- Releases and installation: signing, GitHub Actions, R2, install and upgrade.
- Architecture, API reference, runners: server behavior and supported capabilities.
- Configuration reference: every setting, with its default and environment variable.
- API examples: HTTP API usage with curl.
- Connection setup, pairing: networks, client pairing and authentication.
- Office smoke checklist, security: deployment and verification.
- Live desktop streaming: capture, encoding, WebRTC and WebSocket transports, rate control and input.
- Live view on cellular (historical): investigation of iPhone live view failing on cellular.
- USB redirection: shared open-source USB/IP plan, platform limits and client responsibilities. Apple entitlement application: required macOS importer authorization, entry points and materials.
- Signing dev builds: keep macOS grants across rebuilds.
- Linux: one virtual desktop per conversation (Xvfb), sleep, memory, browsers, packaging.
- Linux design: packages, interfaces, desktop state machine, API, CLI, and what was built differently.
- Building on Linux: other distributions, from source.
- Windows: portable x64 ZIP for testing with
mobd.exe serve; service management and desktop support remain planned.