Files
peardata/docs/QVAC.md
T
Raven Scott 2007ff6ad3
CI / test (push) Successful in 2m40s
Release rolling / release (push) Successful in 11m42s
Fix QVAC Worker
2026-07-30 16:01:34 -04:00

231 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QVAC integration (local AI)
PearDatas **QVAC** tab is a local-first SRE copilot. Inference runs on the **desktop** (Electron / Pear); the agent only serves metrics over existing P2P RPC / REST. No cloud LLM is required.
Upstream: [tetherto/qvac](https://github.com/tetherto/qvac) · [docs.qvac.tether.io](https://docs.qvac.tether.io) · [Electron packaging tutorial](https://docs.qvac.tether.io/tutorials/electron)
## Architecture
```
Desktop QVAC tab
→ ui/qvac (engine + tools + onboarding)
→ @qvac/sdk (+ Bare worker) OR tools-only fallback
→ manager.request(Methods.*)
→ PearMonitor agent
```
| Layer | Responsibility |
|-------|----------------|
| Desktop | Model download/load, chat UI, tool loop, Bare worker |
| Agent | Lean composite reads (`getHostSnapshot`, `searchCharts`, `summarizeChart`) + existing metrics RPCs |
| REST | `/api/v3/ai/*` mirrors for scripts |
Agent overhead is unchanged when the QVAC tab is unused (no model load on the agent).
## Why `pear run` worked but CI releases did not
| Environment | How `@qvac/sdk` was resolved |
|-------------|------------------------------|
| **`pear run -d .` on a dev machine** | Often found a **hoisted** install (e.g. `~/node_modules/@qvac/sdk`) even when the project did not declare the dependency |
| **CI Electron client** | Clean `npm ci` in the repo — **no `@qvac/sdk`**, no Bare worker entry, asar packing blocked natives |
CI / rolling releases only ship what is in **this** packages `dependencies` + forge output. Dev must not rely on parent `node_modules`.
## Packaging (Electron / CI)
### Dependencies
```json
"dependencies": {
"@qvac/sdk": "^0.16.0"
},
"devDependencies": {
"@electron-forge/plugin-base": "^7.11.2"
}
```
`@electron-forge/plugin-base` is the peer for `@qvac/sdk/electron-forge`.
### Config
`qvac.config.json` lists **only** the plugins PearData needs (keeps the worker smaller):
```json
{
"plugins": ["@qvac/sdk/llamacpp-completion/plugin"]
}
```
### Forge plugin
`forge.config.cjs` loads `QvacForgePlugin` when the SDK is installed. On package it:
1. Runs `bundleSdk` + `verifyBundle` for the target `platform-arch`
2. Forces **`asar: false`** (Bare cannot load `.bare` addons from asar)
3. Tree-shakes unused `@qvac/*` addons and prunes foreign prebuilds
Official caveats: [Electron tutorial → packaging](https://docs.qvac.tether.io/tutorials/electron#step-4-package-for-distribution).
### Escape hatch
```bash
PEARDATA_SKIP_QVAC=1 npm run make:client:linux-x64
```
Produces a **tools-only** client (no LLM natives). QVAC tab still works via RPC tools.
### Bare runtime binary
QVAC spawns a **Bare** worker via `bare-runtime` + a platform package such as `bare-runtime-darwin-arm64`.
These are listed under **optionalDependencies** (os/cpu-filtered). On a Linux CI runner
`npm ci` will **not** install the darwin/win32 packages, so packaging must force-install them
first (see `scripts/ensure-qvac-bare-runtimes.cjs` and [CI.md](./CI.md)).
Forge keeps `bare-runtime` + `bare-runtime-<targetHost>` in the app and **fails the build**
if the binary is missing when QVAC packaging is enabled.
If you still see:
```text
Could not load the Bare runtime binary for darwin-arm64
… bare-runtime-darwin-arm64 … missing
```
on a local or CI client:
```bash
npm ci
npm run ensure:qvac-bare-runtimes # force-install all QVAC host binaries
npm run make:client:darwin-arm64
```
### Worker RPC timeout (`RPC initialization timed out after 30000ms`)
`bundleSdk` writes **absolute** `file:///build-machine/...` imports into
`qvac/worker.entry.mjs`. Off the build host Bare cannot load them, so the worker
never handshakes.
Fix (already wired into forge + `build-qvac-worker`):
```bash
node scripts/rewrite-qvac-worker-entry.cjs --force-portable
```
That rewrites the entry to **relative** `../node_modules/@qvac/sdk/...` imports
so the packaged app is self-contained.
### Native host matrix (LLM prebuilds)
`@qvac/llm-llamacpp` ships Bare prebuilds for:
| Host | Full QVAC | Notes |
|------|-----------|--------|
| `linux-x64` | yes | |
| `linux-arm64` | yes | |
| `darwin-x64` | yes | CPU-oriented |
| `darwin-arm64` | yes | Metal |
| `win32-x64` | yes | Vulkan / CPU |
| `win32-arm64` | **no** | No published prebuild — CI packages **tools-only** automatically |
`scripts/make.cjs` and `forge.config.cjs` set `PEARDATA_SKIP_QVAC=1` for unsupported hosts so the release matrix still emits a client zip.
### Local parity with CI
```bash
npm ci # installs @qvac/sdk into THIS project
npm run build:client-bundle
npm run build:qvac-worker # optional local bare-pack
npm run make:client:darwin-arm64 # or any host from scripts/hosts.cjs
```
Increase client package timeout if needed: `PEARDATA_CLIENT_TIMEOUT_MS=1800000`.
### Runtime paths (packaged Electron)
`electron/main.cjs` sets `QVAC_WORKER_PATH` to the first existing:
- `app/qvac/worker.entry.mjs`
- `app/qvac/worker.bundle.js`
- `resources/qvac/...` / asar.unpacked variants
SDK also searches those locations itself ([node-rpc-client](https://github.com/tetherto/qvac)).
## Pear desktop
| Piece | Role |
|-------|------|
| `qvac/worker.pear.entry.mjs` | Bare worker entry (LLM plugin only) |
| `package.json``pear.stage.entrypoints` | Stages the worker for pear |
| `pear.pre` | Stays `pear-electron/pre` (single pipe owner) |
Install deps in the **project** (`npm i` / `npm ci`), not only in a parent folder:
```bash
npm ci
pear run -d .
```
Optional combined pre (advanced): `scripts/pear-pre.mjs` — only if you understand pear-pipe ownership; default remains `pear-electron/pre`.
## Model profiles
| Profile | Chat constant | Tools | Typical use |
|---------|---------------|-------|-------------|
| Lite | `QWEN3_600M_INST_Q4` | limited | ≤8 GB RAM |
| **Recommended** | `QWEN3_1_7B_INST_Q4` | yes | Default |
| Strong | `QWEN3_4B_INST_Q4_K_M` | yes | ≥16 GB RAM |
| Tool-tiny | `LLAMA_TOOL_CALLING_1B_INST_Q4_K` | yes | Fallback |
Without the SDK (or if load fails), the tab stays in **tools-only** mode: live agent RPCs, no generative model.
## Agent RPCs (viewer)
| Method | Purpose |
|--------|---------|
| `getHostSnapshot` | Health + KPIs + anomalies/alerts + catalog/storage summary |
| `investigateHost` | One-shot diagnosis pack: findings, hot charts, top processes |
| `hotMetrics` | Charts with strongest recent change / anomaly signal |
| `relatedCharts` | Related charts for a seed id (catalog + weights) |
| `compareChartWindows` | Baseline vs highlight window comparison on one chart |
| `summarizeCharts` | Batch summarize up to 12 charts |
| `searchCharts` | `{ q, limit }` catalog search |
| `summarizeChart` | `{ chart, after?, points? }` min/avg/max/last per dim |
REST: `GET /api/v3/ai/snapshot`, `/api/v3/ai/investigate`, `/api/v3/ai/hot`, `/api/v3/ai/related`, `/api/v3/ai/compare`, `/api/v3/ai/summarize`, `/api/v3/ai/charts`, `/api/v3/ai/chart/:id/summary`.
## Tools the chat can call
Tools are **tiered** so small models stay within context:
| Tier | When | Tools |
|------|------|-------|
| **core** | All tool-enabled profiles | `investigate_host`, `host_snapshot`, `search_charts`, `summarize_chart`, `list_anomalies`, `list_alerts`, `list_processes`, `local_knowledge`, `open_chart`, `open_view` |
| **deep** | Recommended / Strong | `hot_metrics`, `related_charts`, `compare_chart_windows`, `summarize_charts`, `query_metric`, `get_weights`, `query_logs`, `fleet_health`, `list_child_peers`, `storage_info`, `agent_health`, `node_info`, `db_info`, `list_contexts`, `get_chart`, `list_jobs`, `get_alert` |
| **write** | Operator role + confirm | `silence_alert`, `ack_alert`, `run_job` |
On context overflow the engine first drops to **core** tools, then drops tool schemas entirely and retries.
## Settings
| Key | Default | Meaning |
|-----|---------|---------|
| `qvacOnboarded` | `false` | Setup wizard completed |
| `qvacProfile` | `recommended` | Model profile id |
| `qvacMode` | `''` | `qvac` \| `fallback` after last load |
| `qvacRag` | `true` | Inject guide + catalog snippets |
| `qvacIdleUnloadMin` | `30` | Unload LLM after idle minutes (`0` = never) |
## Related files
| Path | Role |
|------|------|
| `ui/qvac/*` | Desktop UI + engine |
| `qvac.config.json` | Plugin allow-list for bundling |
| `qvac/worker.entry.mjs` | Electron/Node worker entry |
| `qvac/worker.pear.entry.mjs` | Pear worker entry |
| `forge.config.cjs` | `QvacForgePlugin` + ignore rules |
| `scripts/build-qvac-worker.cjs` | Local `bundleSdk` |
| `server/services/ai-tools.js` | Agent composite reads |
| `user-guide/qvac.md` | Operator walkthrough |