# Machines

> Canonical path: `specs/product/machines.md`
> Status: living-spec
> Scope: Machine visibility, workspace-wide machine administration, and machine list scope behavior.

## Product Contract

AgentTeam has two different machine surfaces:

- The default Machines and Agents surface is `/machines`. It is current-user scoped for every human role, including workspace owners and admins.
- The workspace-wide machine inventory is `/admin/machines`. It is an explicit admin surface, not the default route.

The default `/machines` page must show only the machines and agents the current user can manage. A workspace admin must not see every workspace machine just because they can manage the workspace.

Workspace owners and admins may open the all-machines surface from the machine settings/admin entry. That surface may show every machine in the workspace and may expose destructive machine administration actions.

## Eviction

Machine eviction is workspace-wide administration. It must only be available when both of these conditions are true:

- the viewer is on the explicit all-machines admin surface
- the viewer can manage the workspace

The default `/machines` page must not show eviction actions.

## Member Removal

Removing a human workspace member is a soft offboarding operation for runtime
resources. Machines created by that user in the workspace become revoked, and
agents on those machines become archived. Archived agents are hidden from normal
machine/agent lists, cannot be started or authenticated, no longer receive
messages, and do not count toward workspace agent quota. Their technical names
are released so future agents can reuse those names. Existing daemon and web
sessions owned by the removed user must be disconnected. Historical messages and
audit data remain available.

## Presence And Runtime State

Machine online/offline is daemon WebSocket presence, not a TiDB fact.

- A machine is online only while the server has an active authenticated daemon connection for that machine.
- `ready` payload fields such as `runningAgents`, runtime session ids, agent status/activity, trajectory, and session-scoped context usage are temporary runtime state. They may be used from the current daemon connection for realtime UI and best-effort routing, but they must not be written to TiDB. The only compatibility exception is a sparse `agents.session_id` mirror for daemons that have not advertised `agent:local-session-resume`; new daemons must resume from daemon-local state instead.
- Detailed agent runtime telemetry is owner-private. Browser realtime delivery, reconnect bootstrap, agent lists, and bounded runtime snapshots may expose coarse active/inactive or online/offline presence to workspace members, but only the machine owner may receive activity detail, runtime session ids, current-instance data, or trajectory entries.
- Hostname, OS, daemon version, and runtime capability information may be persisted only as low-frequency last-known diagnostic metadata when the value actually changes. They are not the online/offline source of truth and must not be rewritten on every `ready` hot path.
- Current single-server deployments may keep presence and running-agent state in process memory. If a future multi-server deployment needs shared temporary state, it must use Redis/TTL or an equivalent ephemeral store, not TiDB.
- Page refresh does not require server-side DB restoration of machine presence or agent runtime state. The UI should render unknown/offline/idle until realtime connection state or a new daemon event is available.

## API Scope Contract

`GET /api/v1/workspaces/:workspaceId/machines` accepts a `scope` query parameter:

- empty or `scope=own`: return only the caller's own/current-user scoped machines
- `scope=all`: return all machines in the workspace and require workspace admin permission

Frontend callers must default to `scope=own`. A caller must not choose `scope=all` merely because `canManageWorkspace` is true; it must be in an explicit all-machines admin mode.

## Dashboard Aggregation

Machine Dashboard trends use one window-dependent bucket contract across local
Desktop and remote server access: 10 minutes for 6 hours, 20 minutes for 12
hours, 30 minutes for 24 hours, 1 hour for 3 days, and 2 hours for 7 days.

The token-source view is a single stacked trend. Desktop selects the global top
15 source identities by total input plus output tokens, preserves a stable
total-descending order, and combines every remaining source into one `Other`
series. The wire layer passes that series contract through, and clients render
the returned series without filtering, reordering, or regrouping it. The
Dashboard does not expose a separate per-source detail table.

## Regression Guardrail

`web/src/agentteam/workspace/MachinesAgentsRoute.test.ts` protects this contract by asserting that:

- `MachinesAgentsRoute` has an explicit all-machines mode
- all-machine loading is gated by that explicit mode and workspace admin permission
- the default page links admins to `/admin/machines` instead of loading all machines inline
- `/admin/machines` renders the all-machines route instead of redirecting to `/machines`

No separate shell guardrail exists today because the regression surface is frontend route wiring and query-scope selection. Add a root `guardrails/product/` check if another UI or API client starts selecting machine scopes independently.
