---
module: analytics.agents
title: Agents
tab: Internal
order: 20
audience: internal
---

> **Internal · ops/support/admin.** Grounded in hiveops-analytics + hiveops-devices source (2026-07-01). This is a **read-only** dashboard tab — there is nothing to push, edit, or approve here.

## What this tab actually is

The **Agents** tab (analytics SPA, `analytics.bcos.cloud`) is a single GET against the analytics backend that aggregates agent-software health across the fleet. It shows:

- **Version Distribution** — count of ATMs per `agentVersion`.
- **Connection Status** — Connected / Disconnected / Never Connected counts + percentages.
- **Outdated ATMs** — every ATM not on the computed "latest" version.

All three panels are derived from **one** upstream call. There is no analytics DB table behind this tab — it is computed live on each request.

## Access / roles

- **Viewing:** any authenticated JWT. The analytics `/api/analytics/agents` endpoint is **not** role-gated — analytics `SecurityConfig` only requires `.authenticated()` (permitAll is limited to `/actuator/health` and `/api/public/**`).
- **Upstream `GET /api/atms/paginated`** (hiveops-devices) is likewise authenticated-only, **no** `@PreAuthorize` — the `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` gates on that controller apply to *mutating* device endpoints, not the paginated list.
- There are **no admin-only actions on this tab.** (The admin-gated action in the analytics app is `POST /api/analytics/insights`, which belongs to the Adoons Insights panel, not Agents.)

## Data path (one line)

`Agents tab → GET /api/analytics/agents (hiveops-analytics :8089) → GET /api/atms/paginated?size=500&page=0 (hiveops-devices :8096) → atms/atm_properties tables`

The analytics service **forwards the caller's Bearer token** downstream (no service account). If the caller's token can't read devices, the tab degrades silently (see below).

## First things to check when it misbehaves

**Tab is empty / "No version data" / everything shows "unknown":**
1. `AgentInsightService.fetchAtmsPaginated()` **swallows all exceptions and returns an empty list** (`log.warn("AgentInsight: failed to fetch ATMs paginated: ...")`). So an empty tab = the upstream call failed or returned nothing. Check analytics logs (Loki, service `hiveiq-analytics`) for that warn line.
2. **Verify `INCIDENT_SERVICE_URL` points at a backend that serves `/api/atms/paginated`.** This endpoint is owned by **hiveops-devices** (extracted from incident ~2026-06-18). Analytics calls it through its `incidentWebClient` (default `http://hiveops-incident-backend:8080`). If that URL still points at plain incident (which no longer serves `/api/atms/paginated`), the call 404s → empty tab. This is the #1 thing to confirm on a fresh/mis-wired deploy.
3. Confirm the caller's JWT is valid and reaches devices (401/403 downstream also yields an empty list, not an error to the user).

**"Latest" version looks wrong / all ATMs flagged outdated:**
- "Latest" is **not** the CDN release version. `findLatestSemver()` picks the **highest semver already present in the fleet**. If no ATM is on the newest release yet, "latest" lags reality.
- The regex is strict: `\d+\.\d+\.\d+` only. Any version with a suffix/prefix (`v4.4.3`, `4.4.3-patch`, `4.4.3-standard`) **fails to parse** → `latestVersion` becomes `"unknown"` → **every** ATM is flagged outdated. If the whole fleet suddenly shows outdated, check whether agent version strings picked up a suffix.

**Counts look short on a large fleet:**
- The upstream call is hard-coded to `size=500, page=0`. **Only the first 500 ATMs are considered.** Fleets over 500 devices under-report version distribution, connection counts, and outdated lists. This is a code limitation, not a config knob.

**Connection status disagrees with the Devices app:**
- Status is computed in **hiveops-devices** (`calculateConnectionStatus`): `NEVER_CONNECTED` if `lastHeartbeat` is null; `CONNECTED` if last heartbeat ≤ **15 minutes** ago; else `DISCONNECTED`. (Note: older docs say "5 min" — the code uses **15**.) The Agents tab just reflects whatever devices returns, so reconcile at the Devices source, not here.

## Common failure modes → fix

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| Empty tab, `AgentInsight: failed to fetch` in logs | `INCIDENT_SERVICE_URL` not pointing at a backend serving `/api/atms/paginated` (devices), or downstream 401/403 | Repoint env to devices backend; verify JWT reaches devices |
| All ATMs "outdated", latest = `unknown` | Agent version strings not bare `x.y.z` semver | Confirm agent version format; the semver regex is strict |
| Version/connection counts too low | Fleet > 500 devices; only page 0 fetched | Known limitation (see uncertainties) — do not trust totals on large fleets |
| Status differs from Devices app | Different heartbeat window / stale mirror | Devices is source of truth; this tab is downstream-only |

## Related

- Customer-facing description: [analytics.agents] Overview tab.
- Service internals: [technical.analytics].
