process
Long-running user binary. The orchestrator supervises the PID,
captures stdout / stderr into a ring buffer, runs the configured
healthcheck, and tears the process down on stop / project close.
Use this for app code (Python web servers, Node dev servers, Go APIs) and for any binary we don't ship a blessed wrapper for.
Manifest knobs
[services.api]
type = "process"
command = "python app.py" # or "node server.js", "go run ./..."
cwd = "." # relative to the project root
port = "auto" # injected as $PORT in the env
healthcheck = { kind = "tcp" } # or "http", "command"; see below
depends_on = [
{ service = "postgres", condition = "healthy" },
]
The orchestrator does not parse the command string itself — it
hands it to bash -c so shell quoting, pipes, and redirects work the
way they look. $PORT is the orchestrator-allocated port, $HOME is
preserved from the daemon's environment, and every dependency's
$<NAME>_PORT / $<NAME>_HOST are exported.
Runtime detection
When the project root contains a recognized lockfile
(package.json / pyproject.toml / Gemfile / go.mod / etc.),
the orchestrator runs mise install for the matching tool before
the first start and prepends the resolved bin/ to the service's
PATH. So command = "python app.py" picks up the Python the
project's lockfile pins, not the system Python.
The detection is purely additive — without a lockfile (or without mise installed), the service falls back to whatever's on the daemon's inherited PATH.
Healthcheck variants
tcp(default ifportis set): TCP connect to127.0.0.1:<port>.http:GET <url>returning a 2xx (or one ofexpect_statusif set).command: run a shell command; healthy iff it exits 0.
Retry policy is 3 attempts at 5s / 15s / 45s by default. Override
via healthcheck_retries = N for slow first-runs.
Inspection drawer
process services don't get a type-specific inspector — they show
the Summary / Logs / Env / Ports tabs that every service has. The
Logs tab is usually where the diagnostic ends up.
Common gotchas
cwdis relative to the project root. Not relative to wherever you invokeunibench startfrom.$PORTis the orchestrator's port, not your service's. Frameworks that readprocess.env.PORT(Express, Next.js, etc.) pick it up automatically. Frameworks that hard-code a port (Flask's default 5000, Vite's default 5173) need an explicit-p $PORTor--port $PORTflag.- Lifecycle hooks run synchronously.
pre_startblocks the service start until it exits cleanly — useful for one-shot DB migrations, expensive for anything slow.