8.4 KiB
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 · docs.qvac.tether.io · Electron packaging tutorial
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
"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):
{
"plugins": ["@qvac/sdk/llamacpp-completion/plugin"]
}
Forge plugin
forge.config.cjs loads QvacForgePlugin when the SDK is installed. On package it:
- Runs
bundleSdk+verifyBundlefor the targetplatform-arch - Forces
asar: false(Bare cannot load.bareaddons from asar) - Tree-shakes unused
@qvac/*addons and prunes foreign prebuilds
Official caveats: Electron tutorial → packaging.
Escape hatch
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).
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:
Could not load the Bare runtime binary for darwin-arm64
… bare-runtime-darwin-arm64 … missing
on a local or CI client:
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):
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
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.mjsapp/qvac/worker.bundle.jsresources/qvac/.../ asar.unpacked variants
SDK also searches those locations itself (node-rpc-client).
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:
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 |