# REPORT — felhom-agent v0.90.0 (agent train: guest RAM resize R-24 + fast-tick R-28) **Date:** 2026-07-17 · **Baseline:** v0.89.0 (`9127f54`) → **v0.90.0** (`ac112c9`, pushed) · **MinAgent coupling:** felhom-controller v0.143.0 gates its guest-memory-resize UI on this agent. ## 1. What shipped Two ruled features on one train (Viktor's one-train/one-floor ruling), plus a diagnosis passenger. - **R-24 guest RAM resize (controller-direct)** — new self-scoped local-API surface `GET`/`POST /guest/memory` (`internal/localapi/guestmemory.go`). The agent is the security boundary: bounds recomputed FRESH per request from a live read; applies via PVE `SetConfig` (live cgroup apply, no reboot); verify-after-apply. - **R-28 fast-tick-until-first-convergence** — `internal/fasttick/`, wired over four cached sources; pulses the shared out-of-band trigger every 30 s while unapplied, self-disarms on convergence. Seams added: `reconcile.Engine.LastResult()`, `wgtunnel.Manager.TunnelConvergence()`. - **Guests-0/0** — diagnosed live, hypothesis REFUTED (no code change). ## 2. Phase-0 probe (the gate — PASSED) Prerequisite: prove `PUT /nodes/{node}/lxc/{vmid}/config memory=X` hot-applies under the AGENT token. Run on the nested demo box `demo-vm-felhom-4846bc` (hostname `felhom-appliance`, guest LXC 9201) — reached via the hub-vaulted G1 break-glass root credential (the box's root PW was unknown; retrieved operator-side from the hub SQLite `host_recovery` table, then shredded). Transcript: ``` node=felhom-appliance vmid=9201 token_user=felhom-agent@pve BEFORE config.memory=2890 MB | status.maxmem=3030384640 B (2890 MB) | status=running | host-uptime=8226s BEFORE guest MemTotal: 2959360 kB | guest /proc/uptime=8227.76 --- PUT memory=3146 (agent token) --- PUT data field: None (empty == synchronous apply, no UPID) AFTER config.memory=3146 MB | status.maxmem=3298820096 B (3146 MB) | status=running | host-uptime=8229s AFTER guest MemTotal: 3221504 kB | guest /proc/uptime=8231.54 VERDICT maxmem_grew=True guest_running=True guest_not_rebooted=True REVERT memory=2890 → maxmem 3030384640 B (2890 MB), running; guest MemTotal 2959360 kB ``` Proven: live cgroup apply (maxmem moves, guest running), **no reboot** (guest /proc/uptime keeps climbing), synchronous (no UPID), agent token has the privilege (no 403), and **/proc/meminfo ripples via lxcfs** — the mechanism that makes the controller's deploy-page memory math follow for free. The gate PASSED → the resize leg was implemented. ## 3. Files created / modified - `internal/localapi/guestmemory.go` (new) + `guestmemory_test.go` (new); `internal/localapi/server.go` (Options.Memory + fields + routes); `cmd/felhom-agent/main.go` (wire Memory=px; construct + launch fasttick). - `internal/fasttick/fasttick.go` + `fasttick_test.go` (new). - `internal/reconcile/engine.go` (`LastResult`/`recordResult`) + `lastresult_test.go` (new). - `internal/wgtunnel/manager.go` (`TunnelConvergence` + cached snapshot refresh in `Apply`). - `CHANGELOG.md`, `REPORT.md` (this), `CONTEXT.md`, `REUSE.md`. ## 4. Tests + red-proofs (all restored green) - localapi memory: GET fields, grow, shrink-above-floor, the three refusals (each asserting **SetConfig count == 0**), cross-guest 403, fresh-bounds-per-request, verify-not-reflected 502, nil-config 503. - fasttick: pulse-while-unconverged, silent-when-converged, the ruled disarm-on-convergence, full-channel non-blocking drop, first-reason. reconcile: `LastResult` effect + pre-first-run ok=false. - **Red-proofs (run-fail-restore):** (i) floor guard removed → below_usage_floor 412 test fails; (ii) max guard removed → above_max test fails; (iii) fast-tick ignores convergence (always pulse) → the silent + disarm tests fail. All confirmed red, then restored. Full suite: `go build ./... && go vet ./... && go test ./...` — all 28 packages pass. ## 5. Guests-0/0 diagnosis — hypothesis REFUTED, no fix Live read-only checks on the nested box: pool `felhom` `members: [9201]`; the agent token's ListLXC sees `[9201]` (VM.Audit comes from the `/pool/felhom` grant — so pool membership IS the visibility mechanism); hub reports 1/1. The agent log shows the observed 0/0 was the **pre-provision window**: `16:11 stale-lock … pool=felhom listed=0` + `desired … guests=0`, then `16:16 lanresolver … vmid=9201` (guest created). No guest existed yet → empty list, no error (the silent shape) — correct, not a bug. The existing `PoolAddVMID` re-assert (`bringup.go:498`) already covers the known restore-over-existing edge (campaign-2 R2). **Item 2's fast-tick is precisely the mitigation** — it shortens the pre-provision reporting window from ≤15 min to ~30 s. ## 6. Deploy verification (BOTH hosts) Built on 180 (`go build -ldflags -X main.version=0.90.0`), deployed to both Proxmox hosts (backup + install + restart): - **felhom-pve** (the N100): `felhom-agent 0.90.0` active; `capabilities self-check ok=63 total=63 degraded=0`; **`fast-tick armed: 30s out-of-band cadence while desired-state is unapplied`**; poke listener + local-api up. - **demo-vm-felhom-4846bc** (nested, 192.168.0.135): `felhom-agent 0.90.0` active; `capabilities self-check ok=63 degraded=0`; `fast-tick armed: 30s …`; local-api on 192.168.0.135:8443. (Converged box → fast-tick quiet after the startup line, as designed.) ## 7. NOT yet live-validated / operator GO - **Fast-tick real-onboarding proof:** the fast-tick's value shows on a REAL fresh box's pre-tunnel window (the rehearsal S5 bind sequence should now be visibly instant). Not manufacturable non-destructively here; both agents show the armed startup line + the unit tests prove the pulse/disarm. - **Floor LAST (operator GO):** both demo agents verifiably run 0.90.0 (the safety gate) and the controller coupling is PROVEN LIVE via the X-Felhom-Agent-Version header (see the controller REPORT's live refusals). Publishing 0.90.0 to Gitea + the hub Day-0 manifest **vouch + MinAgent-floor raise to 0.90.0** are set via the operator-password-gated Day-0 manifest UI (CC cannot log in) — the operator (Viktor) GO step, matching the v0.89 train's "publish/vouch = operator follow-up". ## 8. Observations - The nested demo host is tiny (4 GB): its resize bounds are degenerate (`max_mb 1866 < min_mb 2048 < current 2890`) — no resize *succeeds* through the bounded endpoint there (all refused correctly). The successful apply is Phase-0-proven at the agent layer; the controller live-validation exercised the refusals (above_max, below_min) end-to-end. - REUSE.md gained the fasttick Source pattern + the localapi MemoryOps seam.