docs/adr/0279-fleet-counts-come-from-the-host-roster.md

ADR 0279: Fleet counts come from the host roster

Status: Accepted, 2026-10-17. Tracks VUH-2448.

The owner needs to see how many agents are still working across machines and which finished agents can be retired. App captions, animated poses and cached message lists cannot establish that count or permission to close a pane.

The host projects its existing native fleet roster through owner-only GET /v1/fleet/roster. The response includes one timestamp, complete/incomplete source evidence, every observed agent, and counts by status, machine and lead. Native working/waiting/idle/done maps to working/waiting/idle/finished; unsupported statuses stay unknown. A pending host owner question takes precedence as waiting. A native harness idle response proves quiescence and preserves Herdr done; a resumed working or blocked harness still takes precedence. The client renders these facts without reconstructing them.

An idle or finished exact occupant becomes stale after more than one continuous hour, with no queued messages, unacknowledged reports, running native children or owner question pending. The host reuses its existing efficiency observation when available and persists the observed inactive interval; a replacement occupant starts a new interval. Missing historical status timing starts at the first observation, never a guessed age. Incomplete source reads retain history and name the gap; they cannot authorize retirement.

POST /v1/fleet/roster/retire takes only the seat and native occupant IDs. It refreshes the host roster and resolves the existing hire's lead internally. The owner's intent is attributed to that lead for the existing PaneTidy.close implementation used by close_worker_pane. Kept reports, native drafts, owner-started panes, process identity and dirty/unlanded work retain their guards. A change to waiting or working, new pending work, changed owner or occupant, or lost owner authority refuses before the native effect. The app route has no force, report-path or unlanded-reason override.

A closed result supplies the existing history ID and five-minute undo deadline. POST /v1/fleet/roster/undo resolves the original attribution privately and calls PaneTidy.undo. A transport loss is uncertain: no client retries a mutation automatically. Refusals are settled results with their reason; the pane remains. Retirement does not decide or change tracker completion.

Only the operator or an owner-paired Take Control device may use these routes. Hosted visitor, support, ordinary worker and social/captain credentials are excluded. The relay, encrypted gateway route catalog, API client and clankie fleet roster [retire SEAT OCCUPANT | undo HISTORY_ID] use this same contract. The app adds one shared-shell view after VUH-2443.