|
| 1 | +# Pi.dev AI Assistant for Tactical RMM |
| 2 | + |
| 3 | +An in-portal AI assistant that operates on a **single device at a time** through |
| 4 | +Tactical RMM's existing agent channel, plus **scheduled AI tasks** that |
| 5 | +periodically check a device and raise TRMM alerts when they find a problem. |
| 6 | + |
| 7 | +- Right-click a device → **Pi.dev** → chat window scoped to that device. |
| 8 | +- The assistant acts on the device with **the same REST endpoints** TRMM already |
| 9 | + uses (`/cmd/`, `/runscript/`, processes, event logs, software, checks…), so |
| 10 | + everything it does is audited and goes over the normal agent path. |
| 11 | +- Global settings hold provider API keys + a model catalog; role permissions |
| 12 | + gate who can use it and which models they can use. |
| 13 | +- Scheduled tasks run headlessly, report a verdict, and create alerts. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## 1. Architecture |
| 18 | + |
| 19 | +``` |
| 20 | +Browser (portal) |
| 21 | + AgentActionMenu → "Pi.dev" → popup /pichat/:agent_id |
| 22 | + POST /agents/<id>/pi/session/ (Knox auth + PiPerms → short-lived redis token) |
| 23 | + WebSocket wss://<api>/pi/ws/<token>/ |
| 24 | + │ |
| 25 | + nginx location ~ ^/pi/ → 127.0.0.1:8787 |
| 26 | + │ |
| 27 | + pi-trmm-bridge (Node, systemd, /opt/pi-trmm-bridge) |
| 28 | + • reads redis pi_session:<token> (written by Django; never exposed to browser) |
| 29 | + • one AI session per chat, scoped to the agent |
| 30 | + • built-in shell/edit/write DISABLED; custom tools call the TRMM REST API |
| 31 | + • streams events → WebSocket → chat window |
| 32 | + │TRMM REST (service API key) │LLM provider (key from settings) |
| 33 | + Django (/rmm) → NATS → device Anthropic / OpenAI / … |
| 34 | +``` |
| 35 | + |
| 36 | +Scheduled tasks use the same bridge via a headless `POST /pi/run` endpoint driven |
| 37 | +by a Celery beat poller. |
| 38 | + |
| 39 | +### Components added |
| 40 | + |
| 41 | +| Layer | What | |
| 42 | +|-------|------| |
| 43 | +| Backend (`api/tacticalrmm`) | `AIProvider`, `AIModel`, `AITask`, `AITaskRun` models; `CoreSettings` AI toggles; `Role.can_use_ai` / `can_use_ai_autoapprove` / `ai_allowed_models`; `PiPerms`; endpoints under `/core/ai/…` and `/agents/<id>/pi/…`; Celery tasks `dispatch_due_ai_tasks` + `run_ai_task`; redis token helper `agents/pi_session.py` | |
| 44 | +| Bridge (`pibridge/`) | Node service embedding the AI runtime; deployed to `/opt/pi-trmm-bridge` by `pibridge/setup.sh` | |
| 45 | +| Frontend (`tacticalrmm-web`) | "Pi.dev" menu item + `PiChat.vue`; AI settings tab; role permissions; `AIHistoryTab`, `AITasksTab` (per-device + company aggregate) | |
| 46 | +| Install/Update | `install.sh` and `update.sh` call `pibridge/setup.sh`; nginx `/pi/` block added | |
| 47 | + |
| 48 | +--- |
| 49 | + |
| 50 | +## 2. Installation & updates (automatic) |
| 51 | + |
| 52 | +Fresh installs and upgrades set everything up with **no manual steps**: |
| 53 | + |
| 54 | +- `install.sh` builds the nginx `/pi/` location block into `rmm.conf` and calls |
| 55 | + `pibridge/setup.sh`. |
| 56 | +- `update.sh` calls `pibridge/setup.sh` (idempotent) after migrations/restarts. |
| 57 | + |
| 58 | +`pibridge/setup.sh` (idempotent) does: |
| 59 | + |
| 60 | +1. Deploys `pibridge/` → `/opt/pi-trmm-bridge` and runs `npm install` (incl. the |
| 61 | + AI runtime SDK). |
| 62 | +2. Ensures a TRMM **service API key** (`pi-bridge`, role `pi-bridge-service`) the |
| 63 | + bridge uses to act on devices. |
| 64 | +3. Writes `/etc/pi-trmm-bridge.env` (port, redis, API url, key, session dir). |
| 65 | +4. Writes/enables `pi-trmm-bridge.service` (systemd). |
| 66 | +5. Injects the nginx `/pi/` block on existing installs if missing. |
| 67 | +6. Restarts the bridge. |
| 68 | + |
| 69 | +Manual run if ever needed: |
| 70 | + |
| 71 | +```bash |
| 72 | +bash /rmm/pibridge/setup.sh |
| 73 | +sudo systemctl status pi-trmm-bridge |
| 74 | +curl -s http://127.0.0.1:8787/pi/health # {"ok":true} |
| 75 | +tail -f /var/log/pi-trmm-bridge.log |
| 76 | +``` |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +## 3. Configure providers & models |
| 81 | + |
| 82 | +**Global Settings → Pi.dev AI** (requires `can_edit_core_settings`): |
| 83 | + |
| 84 | +- **Enable Pi.dev module** (master switch), **Require approval for device |
| 85 | + actions**, **Persist chat history**. |
| 86 | +- **Providers** — add a provider (Anthropic / OpenAI / Google / xAI / OpenRouter |
| 87 | + / custom) and paste its API key (stored server-side, write-only). |
| 88 | +- **Models** — pick from a dropdown that lists **exactly the models available for |
| 89 | + your configured keys**, set a display name, thinking level, enable, and mark |
| 90 | + one **default**. |
| 91 | + |
| 92 | +Example: add Anthropic with your key, add model `claude-sonnet-4-5` as default. |
| 93 | + |
| 94 | +Under the hood: |
| 95 | + |
| 96 | +``` |
| 97 | +POST /core/ai/providers/ {"name":"anthropic","api_key":"sk-...","enabled":true} |
| 98 | +GET /core/ai/available-models/ → models the current keys can use |
| 99 | +POST /core/ai/models/ {"provider":1,"model_id":"claude-sonnet-4-5", |
| 100 | + "display_name":"Claude Sonnet 4.5","is_default":true,"enabled":true} |
| 101 | +``` |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +## 4. Who can use it (roles) |
| 106 | + |
| 107 | +**Accounts → Roles → (role) → Pi.dev AI**: |
| 108 | + |
| 109 | +- **Use Pi.dev AI Assistant** (`can_use_ai`) — required to open chats and to see / |
| 110 | + manage scheduled tasks. |
| 111 | +- **Allow auto-approve of device actions** (`can_use_ai_autoapprove`) — lets that |
| 112 | + role toggle auto-approve in a chat. |
| 113 | +- **Allowed AI models** — multiselect; empty = the global default only. |
| 114 | + |
| 115 | +Model access is enforced **server-side**: the session token only carries the |
| 116 | +models the role may use, and the bridge refuses anything else. Superusers get all |
| 117 | +enabled models. |
| 118 | + |
| 119 | +**Scope:** chats and scheduled tasks are further limited by the role's normal |
| 120 | +agent access — a tech only sees/manages tasks for devices in the clients/sites |
| 121 | +they're allowed to view (task lists are filtered by `Agent.filter_by_role`, and |
| 122 | +per-device actions are checked with `_has_perm_on_agent`). Configuring providers / |
| 123 | +models is separate and still requires core-settings admin |
| 124 | +(`can_view/edit_core_settings`). |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +## 5. Interactive chat |
| 129 | + |
| 130 | +Right-click a device → **Pi.dev** opens a popup scoped to that device. |
| 131 | + |
| 132 | +- The system prompt is seeded with the device facts (hostname, client/site, OS, |
| 133 | + logged-in user, IPs…). The assistant has an effective **root/console shell** on |
| 134 | + the device via `run_command_on_device` (each call is a fresh non-interactive |
| 135 | + shell; it batches steps with `;`/`&&`). |
| 136 | +- **Mutating actions** (run command/script, kill process, reboot) require an |
| 137 | + in-window **Approve** click by default. Roles with auto-approve can flip a |
| 138 | + per-session toggle. |
| 139 | +- **Switch models mid-conversation** from the dropdown — the model changes on the |
| 140 | + **same session**, history preserved (no reset). |
| 141 | +- Everything the assistant runs shows as a tool card with the exact command and |
| 142 | + its output. |
| 143 | + |
| 144 | +Example prompts: |
| 145 | + |
| 146 | +- *"What's using all the memory right now?"* |
| 147 | +- *"List the docker containers and tell me if any are unhealthy."* |
| 148 | +- *"The disk is filling up — find the biggest directories under /var."* |
| 149 | + |
| 150 | +### 6.1 AI History |
| 151 | + |
| 152 | +Device view → **AI History** tab shows a unified log of AI activity on that |
| 153 | +device, with a **Source** column: |
| 154 | + |
| 155 | +- **Chat** — interactive sessions; **Continue** resumes one (survives a dropped |
| 156 | + connection), **New chat** starts fresh. |
| 157 | +- **Task: <name>** — a scheduled task run; **View** shows its transcript. |
| 158 | +- **Bulk: <name>** — a run from a Bulk AI Command (§7); **View** shows its |
| 159 | + transcript. |
| 160 | + |
| 161 | +Chat sessions persist per `agent_id`; task/bulk runs come from `AITaskRun`. |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 6. Scheduled AI Tasks |
| 166 | + |
| 167 | +Device view → **AI Tasks** tab (or the company aggregate, §7). |
| 168 | + |
| 169 | +Create a task with: |
| 170 | + |
| 171 | +- **Prompt** — what to check, e.g. *"Check SQL Server performance. Alert if any |
| 172 | + data file write latency is over 1s or wait stats look bad; warn over 500ms; |
| 173 | + otherwise ok."* |
| 174 | +- **Model** (or global default), **Alert threshold** (never / warning+alert / |
| 175 | + alert-only). |
| 176 | +- **When to run**: **Now** (one-shot — runs when you click Run now, then |
| 177 | + auto-disables itself, kept with its results) or **Scheduled**. |
| 178 | +- **Scheduled** options: **Every N minutes**, **Daily**, **Weekly** (pick days), |
| 179 | + or **Monthly** (day of month). (Legacy one-time tasks still run at their target |
| 180 | + and disable afterward.) |
| 181 | +- **Allow changes** — off by default. Off = read-only diagnostics (can run shell |
| 182 | + commands described in the prompt, but destructive tools are blocked). On = |
| 183 | + allows run-script / kill / reboot for unattended remediation. |
| 184 | + |
| 185 | +### How a task decides "something is wrong" |
| 186 | + |
| 187 | +The scheduled run has a `report_result` tool it must call once with a verdict: |
| 188 | + |
| 189 | +``` |
| 190 | +report_result(status: "ok" | "warning" | "alert", summary, details) |
| 191 | +``` |
| 192 | + |
| 193 | +Django maps the verdict to a TRMM alert severity and, if it meets the threshold, |
| 194 | +creates a **custom Alert** on the device (dashboard + alert-template routing): |
| 195 | + |
| 196 | +| Verdict | Severity | Alerts when threshold is… | |
| 197 | +|---------|----------|---------------------------| |
| 198 | +| `ok` | — | never | |
| 199 | +| `warning` | WARNING | "warning" | |
| 200 | +| `alert` | ERROR | "warning" or "alert" | |
| 201 | +| run error | WARNING | "warning" or "alert" | |
| 202 | + |
| 203 | +### Scheduling |
| 204 | + |
| 205 | +A Celery beat poller (`dispatch_due_ai_tasks`, every minute) queues due tasks; |
| 206 | +`run_ai_task` calls the bridge's headless `POST /pi/run`, records an `AITaskRun`, |
| 207 | +updates the task, and raises the alert if needed. One-time tasks store a computed |
| 208 | +`run_at` target and set `enabled=False` after their single run. |
| 209 | + |
| 210 | +### Run history & live tracing |
| 211 | + |
| 212 | +- **Run now** starts the task immediately and opens a **live trace** window that |
| 213 | + streams each tool call, its output, and the final verdict in real time. |
| 214 | +- The **history** view is master-detail: every run on the left (status + brief |
| 215 | + summary + when); click one to see the full transcript of everything it did. |
| 216 | + |
| 217 | +--- |
| 218 | + |
| 219 | +## 7. Bulk AI Commands |
| 220 | + |
| 221 | +**Tools → Bulk AI Command** runs one AI prompt across **many devices**, on demand |
| 222 | +or on a schedule. Offline agents are skipped at run time. |
| 223 | + |
| 224 | +- **Targeting** mirrors Bulk Command: **All / Client / Site / Agents / Filter**. |
| 225 | + - The Agents picker lists machines with their **client / site** shown faded, |
| 226 | + and filters as you type (by hostname, client, or site). |
| 227 | + - **Filter** mode builds dynamic rules (a `+` adds conditions, all ANDed): |
| 228 | + e.g. *client contains "Lehigh" AND hostname contains "VM"*. Fields: hostname, |
| 229 | + client, site, description, OS, platform, monitoring type. Operators: contains, |
| 230 | + does-not-contain, equals, does-not-equal, starts-with. A live preview shows |
| 231 | + how many online devices match. |
| 232 | + - Type/OS quick filters apply to All/Client/Site targets. |
| 233 | +- **When to run**: **Now** (one-shot — runs on the online targets then disables |
| 234 | + itself, kept with its results) or **Scheduled** (Every N hours / Daily / Weekly |
| 235 | + / Monthly). |
| 236 | +- Same options as tasks: model, alert threshold, read-only vs allow-changes. |
| 237 | + |
| 238 | +Each per-device execution is recorded as an `AITaskRun` tagged to the bulk |
| 239 | +command, so it appears in that device's **AI History** (§6.1) with a |
| 240 | +**"Bulk: <name>"** source, and raises alerts per the threshold. |
| 241 | + |
| 242 | +Endpoints (`BulkAIPerms` = `can_use_ai` + `can_run_bulk`): |
| 243 | + |
| 244 | +``` |
| 245 | +GET/POST /core/ai/bulk/ , PUT/DELETE /core/ai/bulk/<id>/ |
| 246 | +POST /core/ai/bulk/<id>/run/ (run now) |
| 247 | +POST /core/ai/bulk/preview/ (count online agents a target would hit) |
| 248 | +``` |
| 249 | + |
| 250 | +Scheduler: `dispatch_due_bulk_ai_commands` (beat, every minute) fans a due |
| 251 | +command out to `run_bulk_ai_agent` per online target. |
| 252 | + |
| 253 | +## 8. Company-wide view (Client / Site) |
| 254 | + |
| 255 | +Select a **Client** or **Site** in the tree, then open the **AI Tasks** tab in |
| 256 | +the bottom panel: instead of "No agent selected" it shows **every task across all |
| 257 | +devices in that company** with a **Hostname** column and an **aggregate status |
| 258 | +summary** (counts of OK / Warning / Alert / Error / Never-run), worst-status |
| 259 | +sorted, filterable. Run-now and history work per row. |
| 260 | + |
| 261 | +--- |
| 262 | + |
| 263 | +## 9. API reference |
| 264 | + |
| 265 | +Device-scoped (Knox auth, `PiPerms`): |
| 266 | + |
| 267 | +``` |
| 268 | +POST /agents/<agent_id>/pi/session/ → { token, url, model_id, allowed_models, ... } |
| 269 | +GET /agents/<agent_id>/pi/history/ → { sessions: [...] } |
| 270 | +DELETE /agents/<agent_id>/pi/history/ { "session_id": "..." } |
| 271 | +WS /pi/ws/<token>/ (via the bridge) |
| 272 | +``` |
| 273 | + |
| 274 | +Provider / model config (Knox auth, core-settings admin |
| 275 | +`can_view/edit_core_settings`): |
| 276 | + |
| 277 | +``` |
| 278 | +GET/POST /core/ai/providers/ , PUT/DELETE /core/ai/providers/<id>/ |
| 279 | +GET /core/ai/available-models/ |
| 280 | +GET/POST /core/ai/models/ , PUT/DELETE /core/ai/models/<id>/ |
| 281 | +``` |
| 282 | + |
| 283 | +Scheduled tasks & runs (Knox auth, `AITaskPerms` = `can_use_ai` + per-agent |
| 284 | +access; lists are scoped to the role's visible clients/sites): |
| 285 | + |
| 286 | +``` |
| 287 | +GET/POST /core/ai/tasks/ , PUT/DELETE /core/ai/tasks/<id>/ |
| 288 | + (filter: ?agent_id= | ?site=<id> | ?client=<id>) |
| 289 | +POST /core/ai/tasks/<id>/run/ (run now) |
| 290 | +GET /core/ai/runs/?task_id=<id> (run history) |
| 291 | +GET /core/ai/runs/<run_id>/live/ (live progress from redis) |
| 292 | +``` |
| 293 | + |
| 294 | +Bridge (localhost only): |
| 295 | + |
| 296 | +``` |
| 297 | +GET /pi/health |
| 298 | +POST /pi/run (headless scheduled run → {status, summary, transcript}) |
| 299 | +POST /pi/models (available models for provider keys) |
| 300 | +GET /pi/history/<agent_id> (session index) |
| 301 | +WS /pi/ws/<token>/ (interactive chat) |
| 302 | +``` |
| 303 | + |
| 304 | +--- |
| 305 | + |
| 306 | +## 10. Reliability & troubleshooting |
| 307 | + |
| 308 | +Several safeguards keep a chat or task from appearing "stuck": |
| 309 | + |
| 310 | +- **Command timeout guard** — on Linux, `run_command_on_device` wraps the command |
| 311 | + in `timeout`, so a hung command (e.g. a stuck Proxmox `qm list`/pmxcfs) is |
| 312 | + terminated and returns partial output plus a clear note instead of blocking the |
| 313 | + session. |
| 314 | +- **Chat stall watchdog** — the chat window shows an elapsed timer while working, |
| 315 | + and after ~45s with no activity it warns that the model/device may be slow and |
| 316 | + offers a **Stop** button. |
| 317 | +- **Connection recovery** — if the WebSocket drops mid-response, the window shows |
| 318 | + a **Reconnect** button that resumes the same conversation (via session id). |
| 319 | +- **Auto-retry visibility** — transient provider errors are retried and surfaced |
| 320 | + in the chat (“provider was busy; retrying…”). |
| 321 | +- **WebSocket heartbeat** — the bridge pings clients and drops zombie connections. |
| 322 | +- **Per-session logging** — the bridge logs each tool call, retries, and errors to |
| 323 | + `/var/log/pi-trmm-bridge.log` for diagnosis. |
| 324 | +- **Long scheduled runs** — the runner waits up to `PI_RUN_TIMEOUT` (default |
| 325 | + 3600s) for a task to finish, so lengthy investigations/remediations aren't cut |
| 326 | + off. If the HTTP call still times out, it recovers the verdict from the |
| 327 | + bridge's redis progress when available. Raise `PI_RUN_TIMEOUT` in |
| 328 | + `local_settings.py` for tasks that need even longer. |
| 329 | + |
| 330 | +## 11. Security model |
| 331 | + |
| 332 | +1. Gated by **module enabled** (global) + **`can_use_ai`** (role) + agent scope, |
| 333 | + enforced when the session token is minted. |
| 334 | +2. Model choice enforced server-side against the role's allowed list. |
| 335 | +3. The bridge has **no shell on the RMM server** (built-in shell/edit/write |
| 336 | + disabled); it only acts on the target device through TRMM's REST API, hard- |
| 337 | + bound to the one `agent_id` in the token. |
| 338 | +4. Mutating device actions require in-window approval by default; unattended |
| 339 | + tasks are read-only unless explicitly allowed. |
| 340 | +5. Provider keys and the TRMM service key stay server-side; the browser only ever |
| 341 | + receives a short-lived (8h) redis token over TLS. |
| 342 | +6. Device data and command output are sent to the configured LLM provider — this |
| 343 | + is inherent to the feature and should be acceptable under your data policy |
| 344 | + before enabling the module. |
| 345 | + |
| 346 | +--- |
| 347 | + |
| 348 | +## 12. Bridge environment (`/etc/pi-trmm-bridge.env`) |
| 349 | + |
| 350 | +``` |
| 351 | +PORT=8787 |
| 352 | +HOST=127.0.0.1 |
| 353 | +REDIS_URL=redis://127.0.0.1:6379 |
| 354 | +TRMM_API_URL=https://<your-api-domain> |
| 355 | +TRMM_API_KEY=<service key, auto-created by setup.sh> |
| 356 | +PI_SESSIONS_ROOT=/opt/pi-trmm-bridge/sessions |
| 357 | +IDLE_TIMEOUT_MS=1800000 |
| 358 | +MAX_SESSIONS=10 |
| 359 | +``` |
0 commit comments