# 3pm concepts

The model behind every command, SDK call and API route, in one page. Each
section ends with the document that goes deeper.

## The process manager

- **One binary.** `3pm` is both the CLI and the daemon. The first command
  spawns the daemon (detached, its own session); a lock guarantees exactly
  one daemon per home.
- **Home.** `~/.3pm`, or `$THREEPM_HOME`. It holds the sockets, `3pm.log`
  (the daemon's own log), `logs/<app>-out.log` / `-error.log`, `pids/`,
  `dump.json` (`3pm save`), the optional `config.toml`, the installed
  `modules/`, the files actions leave in `actions/`, and the fleet link
  state in `agent/`.
- **Sockets.** The CLI and the daemon talk NDJSON over Unix sockets:
  `rpc.sock` (request/response), `pub.sock` (event bus: lifecycle, log
  lines), `ctl.sock` (apps talking back: readiness, graceful stop,
  application metrics, actions), `host.sock` (the host-metrics agent). Wire
  catalogue: [protocol.md](protocol.md).
- **App and instance.** An app is a name owning N instances (`-i N`). Each
  instance is a process row with its own `id`, pid, status and restart
  counter, and gets `THREEPM_INSTANCE` = 0…N-1. Commands target a name, an
  id, a glob (`'api-*'`), `all`, or every app of an ecosystem file.
- **Status.** `launching` → `online`; `stopping` → `stopped`;
  `waiting restart` (backoff); `errored` (crash loop: 16 exits under
  `min_uptime`, re-armed by `3pm restart`).
- **Supervision.** Restart policy `always` (default) / `on_failure` /
  `never`; instant restart or exponential backoff; stop = `kill_signal`
  (SIGINT) to the process group, then SIGKILL after `kill_timeout`;
  memory/CPU guards on the whole process tree; cron restarts; watch mode.
- **Ecosystem file.** The declarative form of `3pm start`: TOML, JSON,
  YAML, or pm2's `ecosystem.config.js`. `validate` checks it, `apply`
  reconciles the daemon with it (create / restart changed / leave the
  rest), profiles with `--env`. [ECOSYSTEM.md](ECOSYSTEM.md).
- **Readiness and graceful stop.** An app connects to `$THREEPM_CTL_SOCK`,
  says `hello`, then `ready` (`--wait-ready`) or receives `shutdown`
  (`--shutdown-with-message`). Any language, a few lines.
- **Logs.** Captured per instance, rotated by the daemon (20 MB × 5, 14
  days by default), streamed live by `3pm logs` from the event bus.
  [logs.md](logs.md).
- **Persistence.** `3pm save` writes `dump.json`, `3pm resurrect` restores
  it, `3pm startup` installs a boot service (systemd, launchd…) that does.
- **Zero-downtime update.** `3pm update` hands the daemon off to the new
  binary in place: same pids, same pipes, apps keep running. `install.sh`
  run again does it.
- **Containers.** `3pm runtime` is a PID 1 entrypoint: foreground, output
  streamed, exits when nothing is left to supervise. [RUNTIME.md](RUNTIME.md).
- **Hosted users.** A root daemon can host other accounts: one socket set
  per account, each seeing only its apps. [logs.md](logs.md) (`[users]`).
- **Resources.** Soft guards (`max_memory_restart`, `max_cpu_percent`),
  hard cgroup v2 limits (`cgroup_enforce`, Linux), `deny_gpu`.
  [resources.md](resources.md).
- **Modules.** Optional features shipped next to the binary and run as
  apps: `serve` (static file server, [SERVE.md](SERVE.md)) and `agent`
  (the fleet link). [modules.md](modules.md).

## Metrics

| Layer | Where it comes from | Read with |
|---|---|---|
| Process | CPU and memory of each instance's whole process tree, every second | `3pm ls`, `3pm ls --tree`, `3pm show` |
| Host | A separate host-agent process, cached, in three tiers (1 s / 10 s / 5 min): CPU, PSI, memory, disks, RAID, network, TCP, sensors, GPUs, NTP | `3pm hostmetrics` |
| Daemon | 3pm's own memory, fds, supervision counters | `3pm internals` |
| Application | What the app reports through an SDK: gauges, counters, meters, histograms, events, errors | `3pm show <app>`, `3pm events -f app` |

References: [metrics.md](metrics.md), [hostmetrics.md](hostmetrics.md),
[app-metrics.md](app-metrics.md).

## SDKs

Three SDKs with the same surface, talking to `$THREEPM_CTL_SOCK`; outside
3pm every call is a no-op, and none of them blocks or throws.

| SDK | Language | Install | Reference |
|---|---|---|---|
| `@3pm/api` | Node.js ≥ 18, Electron | `npm i https://3pm.edgecr4ft.net/sdk/node/3pm-api-<version>.tgz` | [sdk/node.md](sdk/node.md) |
| `threepm-io` | Python ≥ 3.9 | `pip install --index-url https://3pm.edgecr4ft.net/simple/ threepm-io` | [sdk/python.md](sdk/python.md) |
| `threepm_io.hpp` | C++17, header-only | CMake `FetchContent` from `https://3pm.edgecr4ft.net/sdk/cpp/` | [sdk/cpp.md](sdk/cpp.md) |

Versions and sha256 of every archive: `https://3pm.edgecr4ft.net/sdk/sdk.json`.

- **Metrics**: `gauge`, `counter` (cumulative), `meter`, `histogram`
  (base-2 exponential), `metric(name, fn)` (a gauge read at every flush);
  labels, at most 50 label sets per metric.
- **Events**: `event(name, {level, attrs})`, `notifyError(err)`.
- **Actions**: `io.action(name, handler)` exposes a function to
  `3pm trigger <app> <name>` and to the fleet backend; `3pm:profile` (CPU
  or heap profile, Node) is built in and driven by `3pm profile <app>`.
- **Node only**: the programmatic client `ThreePM` (pm2's API:
  `start`, `list`, `stop`, `restart`, `delete`, `launchBus`), and fleet
  calls `io.call` / `io.open` / `io.stream` to other nodes.

## The fleet: `3pm link` and `3pm-backend`

`3pm-backend` is the server side: one binary (axum + SQLite), optional
VictoriaMetrics (metrics) and VictoriaLogs (events) behind it, a dashboard
and a JSON API. A host running 3pm joins it with `3pm link`.

| Thing | Identifier | What it is |
|---|---|---|
| Account | token `3pmu_…` | Owns buckets. The API takes it as `Authorization: Bearer 3pmu_…`; the dashboard trades it for a session cookie. The first account registered adopts existing buckets. |
| Bucket | cluster id `3pmc_…` | A group of hosts and its certificate authority. The pasted cluster key is `3pmc_<id>` or `3pmc_<id>.<tls pin>`. |
| Enrollment token | `3pmk_<id>.<secret>` | Minted per bucket, pasted once: `3pm link 3pmc_… 3pmk_… wss://backend`. |
| Node | `3pmn_…` | One enrolled host, with a key and a certificate. `pending` until approved (unless the token pre-approves), revocable. Presence: `online`, `stale` (3 s without a frame), `offline` (30 s). |
| Session | `3pms_…` | One live WebSocket link (`/api/v1/link`, sub-protocol `3pm.link.v1`). |
| Node bearer | `3pma_…` | Minted for the node in every `welcome`: lets the node call the backend's HTTP routes itself, within the scopes its owner grants (`fleet:list`, `fleet:read`, `fleet:invoke`). |
| Capabilities | `metrics:read`, `proc:restart`, `proc:stop`, `proc:action` | What an agent offers the backend (`3pm link --cap`). |

- **Telemetry**: the agent sends the redacted process table and host
  gauges about once a second, a full host frame every 60 s, application
  metrics and process trees when they change. The backend keeps a live
  window in memory, persists to VictoriaMetrics/VictoriaLogs when
  configured.
- **Live feed**: `GET /api/buckets/{clusterId}/events` (SSE): `init`, then
  `node`, `telemetry`, `emit`, `call`, `artifact`.
- **Calls**: `nodeCall` (`restart`, `stop`, `ping`) and `nodeAction` (an
  `io.action`) place a command on a node; its state goes `sent` → `acked`
  → `replied` (or `unacked`, `refused`, `lost`); read with `getCall`.
  Files it leaves (profiles, dumps) are artifacts, kept 30 days.
- **Node to node**: `3pm fleet nodes|apps|inspect|top|call` and `io.call`
  use the node's own bearer.

References: the HTTP API [backend/api.md](backend/api.md) (every route,
its body, answer and auth), the backend's features
[backend/features.md](backend/features.md), the agent
[fleet/agent.md](fleet/agent.md), the wire protocol
[fleet/link-protocol.md](fleet/link-protocol.md).

## Distribution

- `curl -fsSL https://3pm.edgecr4ft.net/install.sh | sh`: macOS and Linux
  (glibc ≥ 2.28), x86_64 and arm64, into `~/.3pm/bin`; rerun to update in
  place.
- `https://3pm.edgecr4ft.net/latest/version.json`: the current release,
  its assets and sha256.
