# REPORT — slice 9 (agent half): host metrics to the controller (v0.14.0) (2026-06-10) > Overwrite-latest report. Cumulative history: [CHANGELOG.md](CHANGELOG.md). ## What was implemented The agent half of **slice 9** — re-serving the host's health to the customer's controller, plus the one new collector (CPU/chassis temperature). The de-privileged controller (slice 8C) sees only its own cgroup, so it cannot read host metrics itself; the agent already collects host CPU/mem/loadavg/ uptime + per-storage targets for the hub, and slice 9 exposes that to the customer over the local API. ### `internal/hub/cputemp.go` — CPU/chassis-temp collector (new) - `TempReader` seam + `SysfsTempReader`: reads the CPU package temperature from **sysfs** — hwmon (`coretemp`/`k10temp`/`zenpower`/`cpu_thermal`, preferring the `Package id 0` input) then thermal zones (preferring `x86_pkg_temp`/`coretemp`/`cpu-thermal`, falling back to `acpitz`). - **No external binary, no privilege** (sysfs is world-readable) → the root-CLI fence is untouched. - **Graceful-null**: a missing sensor, an unsupported board, an implausible reading (outside 5–150 °C), or any read error all degrade to `nil` ("n/a") — never fails the report. Same nullable contract as the per-disk `SmartSummary.TemperatureC`. ### `internal/hub` — shared wire field + collector reuse - `HostMetrics` gains **`CPUTempC *int` (`cpu_temp_c`)** — nullable, on the **shared** struct, so the **hub report carries it too** (operator freebie). Cross-repo host-report golden updated **byte-identical** with the hub's copy. - `Collector` gains a nil-safe `temp TempReader` (defaults to the real `SysfsTempReader`; `SetTempReader` injects a fake in tests). `Collect()` now sets `cpu_temp_c` on the report. - **`Collector.HostMetricsNow(ctx)`** — a fresh `NodeStatus` + CPU-temp read returning just the host block; the source for the local API (current cpu%/temp, not the 15-min hub snapshot). ### `internal/localapi` — `GET /host/metrics` (new endpoint) - `host_metrics.go`: host-wide health (cpu%/mem/load/uptime/`cpu_temp_c`) + per-storage capacity (total/used/fraction, thin-pool, SMART temp+wear). Token-authed via `withGuest` (host-wide data; a cross-guest `?vmid=` still 403). Best-effort on storage (a view error still returns the host block). Served only when the `HostMetrics` provider (the shared collector) is wired in `buildLocalAPIServer` — else 503 "not configured". ## Tests (all green) - `cputemp_test.go`, `hostmetrics_test.go`, `host_metrics_test.go`: hwmon/thermal-zone selection + **graceful-null**, `HostMetricsNow` populate/null/hard-error, endpoint populated + `cpu_temp_c:null` serialization + **401 without a token** + 403 cross-guest + 503 not-configured. - `go test ./...` green; `go vet ./internal/hub ./internal/localapi` clean. ## Versioning / docs - Version `0.13.0 → 0.14.0`; `CHANGELOG.md` updated. Doc 03 §6 (local-API surface) + §9 (roadmap + changelog) updated. ## Assumption (noted, not built) - **One customer per host** (home-server model): `/host/metrics` is host-wide. A multi-customer host would leak cross-customer CPU/mem → revisit then. ## Pending - **Live validation** on the demo (build + deploy agent v0.14.0; controller monitoring page → real N100 CPU%/temp + per-storage, cross-checked vs `pvesh`/`free`/`df`).