---
module: devices.agent-profile
title: Agent Profile
tab: Claude
order: 40
audience: dev
---

Terse act-without-guessing reference. Feature = hardware profiles controlling agent module provisioning.

## Ownership
- **Service:** `hiveops-devices` backend (Spring Boot, port 8096), DB `hiveiq_devices`. Frontend: `hiveops-devices` SPA port 5177 (`devices.bcos.cloud`).
- **Component:** `frontend/src/components/Settings/AgentProfile.svelte`
- **Backend:** `AgentProfileController` / `AgentProfileService` / `AgentProfileRepository` / entity `AgentProfile`.

## Endpoints (all under `/api/agent-profiles`)
| Method + Path | Auth | Notes |
|---|---|---|
| GET `/api/agent-profiles` | any JWT | list, sorted by sort_order asc, then key |
| POST `/api/agent-profiles` | `MSP_ADMIN`/`BCOS_ADMIN` | 400 if blank key/label; 409 if key exists; key lowercased+trimmed |
| PUT `/api/agent-profiles/{key}` | `MSP_ADMIN`/`BCOS_ADMIN` | 400 if blank label; key immutable |
| DELETE `/api/agent-profiles/{key}` | `MSP_ADMIN`/`BCOS_ADMIN` | 409 if any ATM uses it; 404 if missing |
| GET `/api/agent-profiles/modules-manifest` | any JWT | proxies CDN modules.json, 60s cache; 502 `{"modules":[]}` on failure |

## Tables (DB `hiveiq_devices`)
| Table | Use |
|---|---|
| `agent_profiles` | PK `profile_key`(varchar40); `label`,`description`(TEXT),`enabled`(bool),`sort_order`(int),`created_at`,`updated_at`. Flyway V14. |
| `atms` | `hardware_profile`(varchar40) = device→profile link; read by delete-guard `COUNT(*) WHERE hardware_profile=:key`. |

## Kafka topics
- **None** for profile CRUD. Do NOT expect `hiveops.device.events` on profile writes (that topic is device rows only).

## External deps
- CDN manifest: `https://cdn.bcos.cloud/downloads/agent/modules/modules.json` (shape: `{"modules":[{name,version,filename,profiles?[]}]}`).
- Token: `agent.cdn.token` / env `CDN_TOKEN` → sent as header `X-CDN-Token`, server-side only.

## Module→profile logic (client-side join)
- module `profiles` absent/empty ⇒ installs on ALL ATMs (universal).
- module `profiles` contains profileKey ⇒ exclusive (★) to that profile.

## Gotchas
- Read endpoints (`GET list`, `GET modules-manifest`) have **no `@PreAuthorize`** — open to any authenticated user; only mutations are admin-gated.
- `profileKey` is the PK and **immutable**; UI disables the key field on edit. "Rename" = create new + reassign devices + delete old.
- Manifest cache is **per-process** (`AtomicReference`); CDN changes take up to 60s per replica.
- Manifest failure ⇒ HTTP **502** body `{"modules":[]}`; UI shows "Could not load modules manifest from CDN" and blanks the Modules column.
- `enabled=false` pauses provisioning for that profile; ATM match is on `atm.hardware.profile` agent property (lowercase, exact).
- Module membership is NOT in the DB — it lives in CDN `modules.json`. Never look for a module list on the profile row.

## Do NOT
- Do NOT invent module-list columns on `agent_profiles` — modules come from CDN `modules.json`.
- Do NOT try to change a profile's key via PUT — it's ignored/immutable.
- Do NOT expect Kafka events from profile writes.
- Do NOT route these calls through incident/journal — `hiveops-devices` owns them.
- Do NOT expose `X-CDN-Token` to the SPA; only the backend attaches it.
- Do NOT delete a profile still referenced by any `atms.hardware_profile` — reassign first.
