# Configuration Reference
Procman reads a single `.pman` file (passed as a positional argument). The file contains
top-level blocks in any order.
## Top-Level Structure
```
config {
logs = "./my-logs"
}
env {
RUST_LOG = args.log_level
}
arg port {
type = string
default = "3000"
short = "p"
description = "Port to listen on"
}
job migrate {
run "db-migrate up"
}
service web {
env PORT = args.port
run "serve --port $PORT"
}
service worker if args.enable_worker {
run "worker-service start"
}
task test_suite {
run "pytest tests/"
}
event recovery {
run "./scripts/recover.sh"
}
```
A `.pman` file may contain:
- **`config { }`** — global settings (logs, log_time)
- **`arg name { }`** — CLI argument declaration
- **`env { }`** / **`env KEY = expr`** — global environment variable bindings
- **`job name { }`** — a one-shot process that runs to completion
- **`job name if expr { }`** — a conditionally evaluated one-shot job
- **`service name { }`** — a long-running daemon process
- **`service name if expr { }`** — a conditionally evaluated service
- **`task name { }`** — an on-demand one-shot process (does not auto-start; triggered via `-t`/`--task`)
- **`task name if expr { }`** — a conditionally evaluated task
- **`event name { }`** — a dormant process, only started via `on_fail spawn`
## Process I/O defaults
- **stdin**: redirected to `/dev/null`. Procman puts every child in its own
process group, so an inherited TTY would be a background-pgid read and
raise SIGTTIN. If you need to feed input to a process, plumb it through
the `run` string itself (e.g. `run "source | program"`).
- **stdout / stderr**: captured (combined into one stream) and written to
the per-process and combined log files; ANSI escapes are stripped from
log files only.
## Identifiers
Job names, event names, arg names, and variable names are identifiers. Valid
identifiers match `[a-zA-Z_][a-zA-Z0-9_-]*` — they start with a letter or
underscore, followed by letters, digits, underscores, or hyphens.
## String Literals
String literals are double-quoted. Supported escape sequences: `\"` (literal
quote), `\\` (literal backslash), `\n` (newline), `\t` (tab). No other
backslash escapes are recognized.
## Duration Literals
Duration literals are a number followed by a unit suffix: `s` (seconds), `ms`
(milliseconds), `m` (minutes). Fractional values are allowed (e.g., `1.5s`).
## The `none` Literal
`none` represents the absence of a value. It is valid only in specific
positions: `timeout = none` (infinite wait), `default = none` (no default).
Using `none` in env value positions or boolean contexts is a parse-time error.
## `config { }` Block
Global settings applied to all jobs.
### `config.logs`
Optional log directory path. Defaults to `logs/procman`. Recreated each run.
```
config {
logs = "./my-logs"
}
```
## `env { }` Block
Global environment variable bindings applied to all jobs. Overridable per-job.
Declared at the top level.
Block form:
```
env {
RUST_LOG = args.log_level
}
```
Single-binding form:
```
env RUST_LOG = args.log_level
```
Both forms can appear multiple times and coexist in the same file.
## `arg name { }` Block
CLI arguments parsed after `--`. Declared at the top level. Underscores become
dashes on the CLI (`log_level` → `--log-level`).
```
arg port {
type = string
default = "3000"
short = "p"
description = "Port to listen on"
}
arg enable_feature {
type = bool
default = false
}
```
| `type` | no | `string` | `string` or `bool` |
| `short` | no | — | Single character shorthand |
| `description` | no | — | Help text for `-- --help` |
| `default` | no | — | Fallback value. Args without a default are required. |
Arg values are referenced in expressions as `args.name`. There is no `env`
field on args — use a top-level `env { }` block to explicitly bind args to
environment variables.
Running `procman myapp.pman -- --help` prints generated usage based on the
arg definitions.
### Env Precedence
Lowest to highest:
| System env (inherited) | lowest |
| CLI `-e KEY=VALUE` flags | |
| Top-level `env { }` | |
| Per-job `env` | |
| Per-iteration `for` bindings | highest |
Note: `var` bindings from `contains` conditions are procman expressions, not
direct env injections. They enter the environment only when explicitly assigned
via `env KEY = var_name`.
## `job` and `service` Blocks
Each `job` block defines a one-shot process (runs to completion). Each `service`
block defines a long-running daemon process. Both share the same fields.
### `run` (required)
The command to execute. All commands are passed to `bash -euo pipefail -c`, so
shell features like pipes, redirects, `&&`, variable expansion, and multi-line
scripts all work naturally. `bash` must be on `PATH`.
Inline form:
```
run "echo hello"
```
Multi-line fenced form:
```
run """
./run-migrations
echo "DATABASE_URL=postgres://localhost:5432/mydb" > $PROCMAN_OUTPUT
"""
```
Procman never interpolates inside shell strings. Values flow in exclusively
via environment variables.
An empty or whitespace-only `run` value is rejected at parse time.
### `env` (optional)
Environment variables merged into the job's environment. Single-binding and
block forms can coexist:
```
service api {
env DB_URL = @migrate.DATABASE_URL
env {
API_KEY = "secret"
LOG_DIR = args.log_dir
}
run "start-api --db $DB_URL"
}
```
### `job` vs `service`
A `job` is a one-shot process that runs to completion. Exit code 0 is treated
as success and does **not** trigger supervisor shutdown. A non-zero exit code
still triggers shutdown. Jobs can write key-value output to `$PROCMAN_OUTPUT`,
which other processes reference via `@job.KEY` expressions (see
[Process Output](templates.md)).
A `service` is a long-running daemon process. If a service exits, it triggers
supervisor shutdown.
### `task`
A `task` is a one-shot process that does not auto-start. Tasks must be explicitly
triggered via the `-t`/`--task` CLI flag. Like jobs, tasks run to completion —
exit code 0 is success, non-zero triggers shutdown.
Tasks are useful for operations that should only run on demand: test suites,
database migrations, cleanup scripts, or any one-shot utility work.
```
task test_suite {
wait {
after @migrate
}
run "pytest tests/"
}
```
Trigger with: `procman myapp.pman -t test_suite`
Tasks share all the same fields as jobs and services (`run`, `env`, `wait`,
`for`, `if`, `watch`).
### `wait` (optional)
A block of conditions that must all be satisfied before `run` executes. See
the [Dependencies](dependencies.md) chapter for the full reference.
```
wait {
after @migrate
http "http://localhost:3000/health" {
status = 200
timeout = 30s
poll = 500ms
}
}
```
### `if` (optional)
An expression on the `job` or `service` line. If falsy, the job/service is not
evaluated at all — no dependency waiting, no env resolution. Skipped jobs still
register as exited so `after` dependents can proceed.
```
service worker if args.enable_worker {
run "worker-service start"
}
```
### `watch` (optional)
Named runtime health check blocks that monitor a service after it starts. See the
[Dependencies](dependencies.md) chapter for condition syntax.
```
service web {
run "web-server --port 8080"
watch health {
http "http://localhost:8080/health" {
status = 200
}
initial_delay = 5s
poll = 10s
threshold = 3
on_fail shutdown
}
}
```
### `for` (optional)
Iteration block that wraps `env` and `run`, spawning one instance per element.
See the [Fan-out](fan-out.md) chapter for full details.
```
job nodes {
for config_path in glob("configs/node-*.yaml") {
env NODE_CONFIG = config_path
run "start-node --config $NODE_CONFIG"
}
}
```
## `event name { }` Block
Event handlers are declared at the top level. They are never auto-started —
they are spawned via `on_fail spawn @name` in a watch block.
```
event recovery {
run "./scripts/recover.sh"
}
```
`on_fail spawn @name` must reference an `event`, not a `job`.
## Shell Blocks
Procman never interpolates inside shell strings. Values flow into shell
exclusively via environment variables set with `env` bindings.
Inline:
```
run "echo hello"
```
Multi-line fenced:
```
run """
./run-migrations
echo "DATABASE_URL=postgres://localhost:5432/mydb" > $PROCMAN_OUTPUT
"""
```
## Expression Language
Expressions appear in `if` conditions, `env` value positions, and `var`
bindings. They are never evaluated inside shell strings.
### Value References
| `args.name` | CLI arg value |
| `@job.KEY` | Output from a job's `PROCMAN_OUTPUT` |
| `local_var` | Job-scoped variable (from `for` or `var` binding) |
### Literals
| String | `"hello"`, `"3000"` |
| Number | `42`, `3.14` |
| Bool | `true`, `false` |
| Duration | `5s`, `500ms`, `2m` |
| None | `none` |
### Operators
| Comparison | `==`, `!=`, `>`, `<`, `>=`, `<=` |
| Logical | `&&`, `\|\|`, `!` |
| Grouping | `( )` |
No arithmetic in v1.
### Type Errors
Type errors in expressions cause immediate procman runtime panic and shutdown.
There is no silent coercion. A type error is a bug in the config.
## Parse-Time Validation
Procman validates the configuration at parse time and exits with an error
(with `file:line:col` location) if any of these checks fail:
- **Syntax errors** — malformed blocks, missing fields, invalid tokens
- **Unknown identifiers** — referencing an arg or job that doesn't exist
- **`after @job` targets** — must reference a `job` (not a `service`)
- **`@job.KEY` references** — must point to a `job` (not a `service`) and
require `after @job` in the process's `wait` block (direct or transitive)
- **Circular dependencies** — cycles in `after` references
- **`on_fail spawn @name`** — must reference an `event`
- **Variable shadowing** — reusing a name already bound by `for`, `var`, or
args
- **Empty `run` commands** — rejected at parse time