Architecture
The big picture
Section titled “The big picture”Click a component to see its connections and configuration, or press Follow a request to walk through how a request flows.
- The frontend is the only REST client. The browser only ever calls the
frontend; the frontend server proxies
/api/*to the backend and attaches the OIDC bearer token, so no identity or scope is ever sent from the browser. - The backend owns PostgreSQL and Valkey. It publishes
AgentRequestmessages and records aTaskfor every mutating operation; it never blocks on the agent. - Each agent consumes its own
<hostid>.requestqueue, runs the work on the host (mostly PowerShell / Hyper-V cmdlets), and publishes progress, results and periodic inventory back onto per-host queues.
Two backend processes
Section titled “Two backend processes”| Process | Command | Role |
|---|---|---|
| API | uvicorn app.main:app |
Serves /api/*, publishes agent requests, creates tasks. On startup ensures each host’s RabbitMQ queues exist. |
| Worker | python -m app.worker |
Consumes every host’s response + inventory queues, updates PostgreSQL and the Valkey cache, times out stale tasks. |
How a request flows
Section titled “How a request flows”- An operator clicks Start on a VM in the frontend.
- The frontend optimistically flips the VM to a transitional state and calls
POST /api/vms/{id}/actions/start. - The backend API takes the per-VM lock in Valkey, writes a
Taskrow (queued), publishes anAgentRequestto<hostid>.requestwithcorrelation_id = task id, and returns202 { task }. - The agent on that host consumes the request, runs the Hyper-V operation, and
publishes progress messages and a final result to
<hostid>.response. - The backend worker consumes the response, updates the
Task(and the VM row live from the result payload), releases the lock, and refreshes the cache. - The frontend’s
TaskWatcherpollsGET /tasks/:iduntil it reaches a terminal status, then refetches inventory. The task appears in the Recent Tasks dock with initiator, progress, status and timestamps.
RabbitMQ queues (per host)
Section titled “RabbitMQ queues (per host)”<hostid> is a random 10-char [A-Za-z0-9] string assigned when the host is
registered.
| Queue | Direction | Notes |
|---|---|---|
<hostid>.request |
backend → agent | JSON AgentRequest, correlation_id = task id |
<hostid>.response |
agent → backend | Task progress + final result |
<hostid>.agent_status |
agent → backend | Last value only - version, intervals, hypervisor, liveness |
<hostid>.vm_inventory |
agent → backend | Last value only |
<hostid>.host_inventory |
agent → backend | Last value only - hardware + folders |
<hostid>.template_inventory |
agent → backend | Last value only |
<hostid>.iso_inventory |
agent → backend | Last value only |
The last-value queues are declared with x-max-length=1 / x-overflow=drop-head,
so a slow consumer always sees the latest snapshot and never a backlog.