On this page

Process heartbeat

GET /api/heartbeat/pm2 merges what the pm2 daemon currently holds with a registry of services expected to exist — each project's declared processes plus whatever pm2 was told to resurrect. Each entry carries the pm2 snapshot (status, pid, cpu, memory, uptime, restarts, versions, log paths) plus registry enrichment: registered, project, category (commander for project-owned services, platform for the rest), source, tier, critical, port, healthEndpoint, unconfirmed (a hand-declared name with no ecosystem file behind it, so the UI asks for verification instead of asserting), manager (pm2 or systemd), serviceType, and a history ring. The response ends with self, historyIntervalMs, whether a Guardian manifest was found, and counts: {online, expected, missing}.

Two distinctions matter when reading it. missing is not stopped: a registered pm2 service the daemon has no entry for at all is a post-reboot casualty, while pm2's own stopped is a service the daemon knows and is not running. And **a systemd-managed service is not "down" for being absent from pm2** — those are resolved by probing the declared health endpoint first, then systemctl is-active read from stdout (its exit code is non-zero for every non-active state), with the result cached.

GET /api/heartbeat/pm2/:name/logs tails that process's out and err logs, bounded by a configured default and hard cap; log paths come from pm2, never the request. Restart, start and stop exist but are operator-hand: the name must be in the registry, the verb comes from a three-value enum, the systemd path runs fixed argv rather than a configured command string, and when control is disabled, the declaration deviates, or privilege is denied, the route answers 409 with needsManualStart and the command to run by hand.

Dependency locks

GET /api/heartbeat/deps reports detected and locked versions of Claude Code, Node, PostgreSQL and Guardian. Locks resolve through the config chain: settings key, then environment, then the signed version-lock manifest, then none. ok is null when there is no lock ("unlocked", never an invented true), and a lock matches by exact prefix, so 20 pins a major and 2.1.199 pins a patch. Guardian also reports liveness from its status API: any HTTP answer proves the daemon is up, only a refused or timed-out socket is down. GET /api/guardian/status reports {linked, reachable, authorized, status}; linking verifies the key against Guardian before storing it, and refuses to persist it without an encryption key.

Host metrics and history

GET /api/heartbeat/system returns memory from /proc/meminfo MemAvailable (the kernel's reclaimable-aware figure, not the MemFree that overstates pressure), cpu: {load1, load5, load15, cores, util, steal}, rolling 24-hour highs (high24) and the window length; GET /api/heartbeat/disk reports used, free and percent. Both degrade to nulls with an error rather than failing the view.

util and steal come from /proc/stat deltas, not load average. Busy is everything except idle and iowait, matching mpstat and top; steal — the hypervisor withholding vCPU from this guest — is inside busy and reported separately, because it is invisible in load average and is the signal that matters on a throttled VPS. A first sample, counter wrap or malformed input yields null, never a fabricated 0.

Thresholds are served, not hardcoded in the client: GET /api/client-config carries heartbeatPctWarn/heartbeatPctErr (named defaults 70/90 — disk, memory, load-per-core) and heartbeatStealWarn/heartbeatStealErr (8/20), steal on its own scale because sustained single-digit steal already means throttling. Both histories are sampled on server-side intervals independent of any open browser, so the graphs cover periods nobody was watching: a bounded per-process ring and a 24-hour host ring, each persisted (the host ring written to a temp file and renamed, so a reader never sees a torn file).

Usage, cost and window meters

GET /api/usage/stats aggregates Claude Code's own transcripts — message.usage per assistant turn — into per-day, per-model and per-account totals. Query: days (1–3650), account, project (a UUID; an unknown one yields an empty match set, not everything), hours=1 for the hourly breakout, refresh=1 to bypass the cache. It is gated on token-analytics, cached per filter combination, and single-flighted so concurrent callers share one scan. GET /api/usage/stats/progress reports that scan's live byte and file counters, clamped; GET /api/sessions/:id/usage folds one session's own transcript.

Four properties explain the numbers.

  1. Per-file incremental folds. Each transcript keeps {offset, carry, agg}

and only appended bytes are folded; a shrunken or rotated file resets. Folds are single-flighted per file (concurrent polls once double-counted the same byte range) and the cache is bounded. Transcript IO is chunked and yields to the event loop, because this process also carries live PTY sockets.

  1. Message-id dedup. Claude Code writes one transcript line per content block,

each repeating the same message.usage under one message.id. Counting every line multiplied tokens and cost several-fold; folding dedupes by id, and id-less legacy lines count once each.

  1. Sub-agent folds. Lines marked isSidechain or carrying an agentId, plus a

session's own subagents/*.jsonl, are folded into totals — they cost real tokens under the parent — and tallied separately so the share is visible.

  1. Pricing is a declared catalog with staleness on the record. Per-model

USD/MTok values are named defaults overridable by a settings key or environment variable; cache economics default to multipliers of base input (read 0.1x, five-minute write 1.25x, one-hour write 2x), with an absolute per-model override where a model deviates, matched by longest model prefix. Responses carry the as-of date, age, whether it is overridden, and a stale flag past the configured age; unpriced models are reported, never priced at zero.

GET /api/usage/window reports per-account rolling meters for a session window and a weekly window, summed from hourly buckets. Subscription budgets are not published, so an absent budget makes the meter estimate from that account's own heaviest observed window and flag it estimated; each meter reports tokens, budget, pct, resetAtMs and the window length.

Corpus and drift alerts

GET /api/transcripts/stats publishes the kind rollup and unknownPct with its alert threshold: a rising unknown share is the cheapest early warning that the CLI changed its transcript layout. GET /api/transcripts/flood-status publishes the last sweep's runaway populations (dir, recent, total, windowHours, threshold, owning project when it resolves) — advisory, because hiding data is an operator decision. GET /api/heartbeat/activity answers `{active, open, windowMs}`: active means a PTY produced output inside the window, so a generating agent counts and an idle shell does not.

POST /api/integrations/:id/health probes one configured external integration descriptor-driven: request shape, auth header and outcome interpretation come from the provider type, and the result is persisted on the row. Promotion has its own health rung (see commander-deploy). There is no aggregate "everything is fine" number here: each signal carries its own confidence, and a degraded reading is published as degraded rather than smoothed into a green dot.

commander-sessions (what flood and activity describe), commander-deploy (version locks, health rung), commander-security (gating, operator-hand).

You've seen the proof

Ready for a number? Scope your deployment and we'll price it against your own economics.

Get your quote →