rig
Zero-dependency, zero-daemon developer environment supervisor and process runner for multi-service repositories.
PyPI Package • Quick Start • Comparison • Socket Inheritance • Machine Supervision • AI Agent Protocol • CLI Reference
The Acute Friction
Local multi-service development across multiple git branches and checkouts is routinely broken by five recurring headaches:
| Problem | Traditional Workaround | The rig Solution |
|---|---|---|
| Port Collisions | lsof -i :3000 & kill -9 |
Dynamic & Sticky Port Leasing: Auto-assigns friendly ports (3000, 8000), increments on collision, and remembers ports across restarts. |
| Port Binding Race Conditions | Probe port, close socket, hope child binds before another process grabs it | Zero-Race Socket Inheritance (type: "fd"): Binds kernel port 0, holds the socket open, and passes descriptor directly to child processes (--fd {fd}). |
| Orphaned Zombie Processes | killall node / pkill python |
Atomic File-Backed Registry: Tracks PID and PGID per instance in ~/.local/state/rig/. Cleans up orphaned services even if the directory was deleted (rm -rf). |
| Heavy Supervisor Daemons | Docker Compose for everything, systemd, supervisord, Procfile wrappers | Zero Runtime Dependencies & Zero Daemons: Standard library Python 3.10+ only. Starts instantaneously, uses kernel flock locks, and exits cleanly. |
| Invisible Machine State | No single view of what processes or checkouts are running | Machine-Wide Visibility: Run rig ps from anywhere to inspect all active checkouts, their allocated ports, health, and status across your entire machine. |
Feature Comparison
How rig compares against standard development orchestrators:
| Capability | rig |
Docker Compose | Foreman / Overmind | systemd / supervisord |
|---|---|---|---|---|
| Zero Third-Party Dependencies | ✅ (Standard Library) | ❌ (Docker Engine) | ❌ (Ruby/Go toolchains) | ❌ (System-level packages) |
| Zero Background Daemons | ✅ (File-mutex lock) | ❌ (Requires dockerd) |
❌ (Requires background tmux/daemon) | ❌ (Requires system daemon) |
| Dynamic & Sticky Port Allocation | ✅ Built-in | ❌ Manual config | ❌ Hardcoded ports | ❌ Hardcoded ports |
Zero-Race Socket Inheritance (type: "fd") |
✅ Kernel socket pass | ❌ Bridge network NAT | ❌ No | ⚠️ Systemd socket units only |
| Machine-Wide Multi-Project View | ✅ rig ps |
⚠️ Per-project compose | ❌ Checkout-isolated only | ⚠️ Global service list |
| Cross-Checkout Targeted Teardown | ✅ rig down <slug> |
❌ Must cd to folder |
❌ Must cd to folder |
⚠️ System unit names |
| Typed JSON Envelopes for AI Agents | ✅ --json on every cmd |
⚠️ Untyped CLI json | ❌ Plain text output | ❌ Plain text output |
| Deterministic Error Codes | ✅ Typed error codes | ❌ Generic 0 or 1 | ❌ Generic 0 or 1 | ❌ Generic exit status |
The 3-Step Fast Track
1. Install Globally (User Space)
Install rig-cli from PyPI using standard, isolated tool runners:
# Recommended (pipx)
pipx install rig-cli
# Ultra-fast alternative (uv)
uv tool install rig-cli
2. Initialize Any Repository
Run rig init inside your project root. rig inspects your directory and generates a tailored rig.json. It detects Docker Compose postgres and redis services, a Django backend (manage.py), a generic Python ASGI backend such as FastAPI (pyproject.toml or requirements.txt), and a Node dev server (package.json, run with npm, pnpm, yarn, or bun as its lockfile indicates):
rig init
Preview detected services without writing to disk:
rig init --dry-run
3. Launch and Supervise
# Start all services in the active mode
rig up
# Check status of the local checkout
rig status
# Inspect service logs
rig logs backend -n 50
# Stop services in this checkout
rig down
Machine-Wide Supervision (rig ps)
rig maintains a machine-wide state registry under $XDG_STATE_HOME/rig/instances/ (~/.local/state/rig/instances/). Every project instance records its directory, PID, PGID, active mode, and allocated ports.
rig ps
PROJECT ID MODE STATUS SERVICES ROOT
pinpoint-leads e9297fec native running postgres:running, web:running ~/code/pinpoint-leads
winnow-post 74376561 default stopped api:stopped ~/code/winnow-post
tertia-club d7bfeefc native running worker:running, ui:running ~/code/tertia-club
Add -w, --wide to show the full instance identifier (<project>-<hash>) in an INSTANCE column.
Add --health to probe HTTP endpoints for real-time health checks:
rig ps --health
Targeted Teardown & Garbage Collection
Without a target, rig down stops the services of the current checkout. With a target, rig down stops a recorded instance from anywhere on your machine, even outside its checkout directory. The target must match either the project name or the full instance directory name (<project>-<hash>, as shown by rig ps --wide). Matching is case-insensitive. When a project name matches more than one instance, rig refuses with E_AMBIGUOUS and asks for the full instance identifier.
# Stop by project name
rig down pinpoint-leads
# Stop by full instance identifier
rig down pinpoint-leads-e9297fec
# Stop ALL running instances across the entire machine
rig down --all
# Clean up stale instance state files
rig prune
# Force-kill lingering orphaned processes and prune
rig prune --force
Zero-Race Socket Inheritance (type: "fd")
When a service specifies type: "fd", rig eliminates the classic time-of-check to time-of-use (TOCTOU) port race:
┌─────────┐ 1. socket(AF_INET, SOCK_STREAM)
│ rig │ ──── 2. bind("127.0.0.1", 0) ───► OS Kernel assigns port
│ │ ──── 3. listen(128)
└────┬────┘
│ 4. subprocess.Popen(..., pass_fds=[fd])
▼
┌─────────┐
│ Uvicorn │ ──── 5. uvicorn.run(..., fd=sock.fileno())
└─────────┘ (Socket is NEVER closed between allocation and server startup)
Python / Uvicorn Example
import argparse
import socket
import uvicorn
parser = argparse.ArgumentParser()
parser.add_argument("--fd", type=int, default=None)
args, _ = parser.parse_known_args()
if args.fd is not None:
sock = socket.fromfd(args.fd, socket.AF_INET, socket.SOCK_STREAM)
uvicorn.run("main:app", fd=sock.fileno())
else:
uvicorn.run("main:app", host="127.0.0.1", port=8000)
Multi-Stack Modes (native vs container)
Define base infrastructure and mode overlays in a single rig.json:
{
"$schema": "https://raw.githubusercontent.com/evgesha9400/rig/main/rig.schema.json",
"project": "my-app",
"default_mode": "native",
"services": {
"db": {
"type": "compose",
"compose_file": "docker-compose.yml",
"compose_service": "postgres",
"compose_port": 5432
}
},
"modes": {
"native": {
"services": {
"backend": {
"type": "fd",
"cwd": "backend",
"command": ".venv/bin/python -m app.main --fd {fd}",
"healthcheck_path": "/healthz",
"depends_on": ["db"]
},
"frontend": {
"type": "port",
"cwd": "frontend",
"command": "npm run dev -- --port {port}",
"healthcheck_path": "/",
"depends_on": ["backend"],
"env": {
"VITE_API_PORT": "{backend_port}"
}
}
}
},
"container": {
"services": {
"backend": {
"type": "compose",
"compose_file": "docker-compose.yml",
"compose_service": "backend",
"compose_port": 8000,
"healthcheck_path": "/healthz",
"depends_on": ["db"]
}
}
}
}
}
Collision-Safe Mode Switching
rig prevents conflicting processes from running simultaneously:
# Blocked with exit code 3 (E_MODE_CONFLICT) to prevent colliding ports/services:
rig up --mode container
# Cleanly tears down native services first and launches container mode:
rig up --mode container --switch
AI Agent & Automation Protocol
rig is built for autonomous execution by AI agents (Claude Code, AntiGravity, Codex, Cursor) and CI/CD pipelines:
Why AI Coding Assistants Use rig
- Deterministic Process Control: AI agents frequently lose control of background tasks or suffer port collisions when re-running test servers.
rigmanages the full process lifecycle with PID/PGID process groups. - Predictable JSON Responses: Agents never have to scrape ANSI text or parse unpredictable terminal formatting.
- Actionable Resolution Hints: When an error occurs,
rigreturns an exact diagnostic hint for automated recovery.
Universal --json Output Envelope
Every CLI command supports --json and emits a typed, deterministic envelope:
{
"schema": "rig.status/1",
"ok": true,
"data": {
"project": "pinpoint-leads",
"instance": "pinpoint-leads-e9297fec",
"mode": "native",
"generation": 1,
"services": {
"backend": {
"running": true,
"type": "fd",
"port": 8003,
"url": "http://127.0.0.1:8003",
"pid": 59608
}
}
}
}
Errors provide actionable resolution hints:
{
"schema": "rig.error/1",
"ok": false,
"error": {
"code": "E_MODE_CONFLICT",
"message": "Instance is running in mode 'native'; cannot start mode 'container'",
"hint": "Pass --switch to stop the active mode first, or run 'rig down' before starting a new mode."
}
}
Deterministic Exit Codes
| Code | Constant | Meaning |
|---|---|---|
0 |
EXIT_OK |
Success. |
1 |
EXIT_OP_FAILED |
Service failure, healthcheck timeout, or teardown error. |
2 |
EXIT_USAGE |
Invalid CLI arguments or schema validation error. |
3 |
EXIT_MUTEX_CONFLICT |
Instance lock busy (checkout.lock) or unswitched mode conflict. |
4 |
EXIT_NOT_FOUND |
Target project, service, or instance not found. |
5 |
EXIT_REFUSED |
Destructive action refused without confirmation. |
6 |
EXIT_EXTERNAL_TOOL |
Missing system binary (docker, compose). |
130 |
EXIT_INTERRUPTED |
Interrupted by signal (SIGINT, SIGTERM). |
CLI Command Reference
| Command | Arguments | Description |
|---|---|---|
rig init |
[--dry-run] [--force] [--up] |
Scans repository and generates a validated rig.json. |
rig up |
[--mode MODE] [--scope SCOPE] [--switch] |
Starts services in dependency order with healthchecks. |
rig down |
[target] [--all] [--scope SCOPE] |
Gracefully stops services (SIGTERM ➜ SIGKILL). |
rig status |
[--json] |
Shows tabular or JSON status of services in the current checkout. |
rig ps |
[--health] [-w, --wide] [--json] |
Lists all active and stopped rig projects machine-wide. |
rig logs |
[service] [-n TAIL] [--mode MODE] |
Tails service logs from .local-run/logs/. |
rig check |
[--mode MODE] |
Validates manifests, working directories, and binary execution. |
rig prune |
[--force] [--json] |
Reclaims stale or orphaned instance metadata across the machine. |
rig schema |
[--json] |
Prints the formal JSON Schema for rig.json. |
rig -v, --version |
Displays current installed version (rig 1.1.0). |
Configuration Reference (rig.json)
To inspect or validate the JSON Schema directly:
rig schema
| Property | Type | Description |
|---|---|---|
project |
string |
Required. Slug identifier for instance isolation and Compose project naming. |
default_mode |
string |
Mode to boot when --mode is omitted. When unset, rig uses the first key declared in modes. |
services |
object |
Base services active across all modes. |
modes |
object |
Named mode configurations (native, container, etc.). |
type |
"fd" | "port" | "compose" |
Required. Port allocation and execution strategy. |
command |
string | string[] |
Command line to execute. Supports {fd}, {port}, {<service>_port}. |
cwd |
string |
Working directory relative to repository root (defaults to .). |
healthcheck_path |
string |
Root-relative HTTP path to poll for readiness (e.g. /healthz). health is an accepted alias. |
healthcheck_timeout |
number |
Seconds to wait for the healthcheck to pass (defaults to 45). |
preferred_port |
integer |
Preferred local port (1 to 65535). rig tries this port first. |
depends_on |
string[] |
Upstream services that must pass healthchecks before boot. |
compose_file |
string |
Relative path to Docker Compose file (for type: "compose"). |
compose_service |
string |
Service name within the Docker Compose file. |
compose_port |
integer |
Container port whose published host port rig records (for type: "compose"). |
Zero Runtime Dependencies
rig is committed to zero third-party runtime dependencies. It relies exclusively on the Python standard library (socket, subprocess, os, signal, json, fcntl, shlex, dataclasses, pathlib).
- No background daemons (
systemd,dockerd,supervisord) required to orchestrate native processes. - No Node.js or Ruby runtimes required.
- Instantaneous startup with file-backed atomic state.
Symmetrical Makefile Integration
Include examples/rig.mk in your root Makefile (include rig.mk), or inline its targets:
# Standard local dev targets for rig.
# Include this file in your project's Makefile: `include rig.mk` (or inline it).
.PHONY: up down local-up local-down backend-up backend-down ui-up ui-down status logs
RIG ?= rig
# Service for `make logs`, e.g. `make logs SERVICE=backend`.
SERVICE ?=
up:
@$(RIG) up --scope full
down:
@$(RIG) down --scope full
local-up:
@$(RIG) up --scope local
local-down:
@$(RIG) down --scope local
backend-up:
@$(RIG) up --scope backend
backend-down:
@$(RIG) down --scope backend
ui-up:
@$(RIG) up --scope ui
ui-down:
@$(RIG) down --scope ui
status:
@$(RIG) status
logs:
@$(RIG) logs $(SERVICE)
Links & Ecosystem
- PyPI Package: https://pypi.org/project/rig-cli/
- GitHub Repository: https://github.com/evgesha9400/rig
- JSON Schema: https://raw.githubusercontent.com/evgesha9400/rig/main/rig.schema.json
- Issue Tracker: https://github.com/evgesha9400/rig/issues
- Releases & Changelog: https://github.com/evgesha9400/rig/releases
License
MIT © 2026 Evgeny Aleshin
Release files for rig-cli 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rig_cli-1.1.0.tar.gz | 157.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rig_cli-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 237.5 kB
Release files / rig_cli-1.1.0.tar.gz
| Download URL | rig_cli-1.1.0.tar.gz |
|---|---|
| Size | 157.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
85caf267656658626d067019763f0439083cd6fc9406d983eabd93d530622645
|
|
BLAKE2b-256 checksum How to use checksums |
3b5909d9b433df3a4c24f3f039e5562ce809ab7881e0f16c5cb0a87afa92dc73
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / rig_cli-1.1.0-py3-none-any.whl
| Download URL | rig_cli-1.1.0-py3-none-any.whl |
|---|---|
| Size | 80.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a2e471275b9b466218ea3415be04528357891be8c29c649bb2b0edd473a7ee6
|
|
BLAKE2b-256 checksum How to use checksums |
df63a485a0afe86da25a7f4ef567f23b4fe8373adfa200b200a1f48d972ad0c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|