# QVAC integration (local AI) PearData’s **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** package’s `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-` 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 |