Repository navigation
Service LlamaCpp
Canonical source:
docs/04-backend/service-guide-llamacpp.md— this page is rendered from it on every publish; edit the source.
Codename: Local Frontier
Port: 4017
DB: claw_llamacpp (PostgreSQL)
Volume: llamacpp-data mounted at /var/lib/claw/llamacpp
Status: v0.2 — all foundation phases delivered AND fully wired across the stack: dynamic GitHub-API binary resolver (no manual SHA pinning), per-vendor GPU passthrough overlays auto-detected by claw.sh, audit consumer for all 11 events, connector adapter (LLAMACPP provider), chat-execution dispatch (callLlamacpp), routing health probe + runtime fallback, and a complete frontend page with hardware panel / filters / live download SSE / 3 dialogs across 9 i18n locales.
| Area | Change |
|---|---|
| Binary install | Replaced static BINARY_RELEASES map with dynamic resolution against https://api.github.com/repos/ggml-org/llama.cpp/releases/latest; PLATFORM_ASSET_PATTERNS matches the right archive per platform (incl. new linux-x64-rocm, linux-arm64-vulkan). The 0×64 SHA placeholders are no longer load-bearing — kept only as fallback when the API is unreachable. |
| GPU detection (host) |
scripts/claw.sh cross-platform probe: NVIDIA (nvidia-smi -L), AMD ROCm (/dev/kfd), Intel/Vulkan (/dev/dri/render*), macOS warn. Picks the matching docker/docker-compose.{dev,prod}.gpu-{nvidia,rocm,vulkan}.yml overlay automatically. |
| GPU detection (container) | Service-side detectGpuBackend() cascades NVIDIA → ROCm → Vulkan → CPU; new rocm-smi.utility.ts + dri.utility.ts; GpuBackend.ROCM enum added. Once GPU passthrough is on, the binary installer picks the matching CUDA / ROCm / Vulkan archive. |
| Catalog | 12 entries seeded (was 9): added 3 dev-class (phi-4-mini Q4 ~2 GB, qwen3-coder Q4 ~5 GB, llama-3.3 70 B IQ2_XS ~22 GB) so contributors with consumer GPUs can validate the full pull → load → inference pipeline. |
| Audit | New LlamacppAuditConsumer in claw-audit-service subscribes to all 11 LLAMACPP_* events, writes audit rows under entityType='llamacpp_model'. Severity mapping: HIGH for crash/fail/delete, MEDIUM for binary-update/preflight-override, LOW for the rest. pull.progress is intentionally suppressed (high-frequency). |
| Connector |
LLAMACPP enum value added to shared-types ConnectorProvider; new LlamacppAdapter in claw-connector-service maps /api/v1/catalog?downloadStatus=READY rows to NormalizedModels with per-model capability flags from entry.capabilities. Prisma migration 20260501000000_add_llamacpp_provider. |
| Routing |
LlamacppHealthManager registered in RoutingModule; polls /api/v1/health every 30 s and subscribes to llamacpp.model.{loaded,unloaded,crashed}; populates runtimeHealth['LLAMACPP'] consumed by RoutingManager.isRuntimeHealthy() for fallback decisions. |
| Chat |
ChatExecutionManager.callLlamacpp dispatches both local-llamacpp and LLAMACPP providers to ${LLAMACPP_SERVICE_URL}/api/v1/v1/chat/completions; bypasses connector-config fetch (no API key needed). Inference endpoints take a user JWT or the inter-service token since ADR-146 (they were @Public()); chat-service sends Authorization: Service <token>. LLAMACPP added to PROVIDER_BASE_URLS for symmetry. |
| Frontend | New /models/local-frontier page with HardwarePanel (RAM/disk/CPU/GPU/binary), FilterBar (category + tier + compatible-only + refresh), DownloadsDrawer (live SSE progress, cancel, retry), 3 dialogs (delete-weights, hardware-gate override, runtime config). local-llamacpp group appears in chat ModelSelector once a model is READY. Sidebar nav entry added. ~80 i18n keys added across all 9 locales. |
| Nginx | 3 location blocks now strip the /llamacpp prefix via rewrite ^/api/v1/llamacpp(/.*)$ /api/v1$1 break; — controllers in this service do not use a /llamacpp/ namespace prefix (unlike ollama-service). |
| Compose | The legacy single compose files (docker-compose.dev.yml, docker-compose.yml) were deleted; claw.sh up is THE entrypoint. Per-vendor GPU overlays added (6 files: dev/prod × nvidia/rocm/vulkan). Service is in docker/docker-compose.dev.services.yml + docker/docker-compose.prod.services.yml; DB is in docker/docker-compose.dev.databases.yml + prod mirror. |
| Tests | 450 backend + 45 frontend = 495 tests passing for new code paths (publisher, dynamic resolver, audit consumer, adapter, health manager, callLlamacpp dispatch, controller hook with override/delete/config flows, SSE hook, ModelSelector frontier branch, compat utilities). New CI workflow .github/workflows/claw-sh-gpu-detection.yml exercises all 4 GPU scenarios on Linux + macOS Metal-warn path. |
ClawAI's claw-ollama-service runs models comfortably up to ~70B parameters via the Ollama daemon. Frontier open-weight models — Kimi K2.6 (1T MoE), GLM-5.1 (754B MoE), DeepSeek V3.2 (671B MoE), DeepSeek V4 Pro (1.4T MoE) — exceed Ollama's practical envelope. They require:
- A different inference backend (
llama.cppllama-server) capable of CPU-MoE offload + 4-bit/5-bit quantization. - 96–512 GB system RAM and explicit hardware gating.
- Multi-shard
.ggufdownloads from HuggingFace (200–750 GB per model).
claw-llamacpp-service provides a fully-managed "click to download, click to chat" experience for these models.
Explicitly NOT in scope (deferred):
-
ik_llama.cpp/KTransformersaccelerated MoE backends (v1.1). - Multiple resident models simultaneously (v1.2).
- Mac M3 Ultra polished support (v1.0 documents Mac as best-effort).
- Custom GGUF upload (v1.1, security review needed).
┌──────────────────────────────────────────────────────────────────────────────┐
│ Frontend (3000) │
│ /models/local-frontier → uses TanStack Query hooks + SSE for live progress │
└─────────────────────────┬────────────────────────────────────────────────────┘
│ HTTP
┌─────────────────────────▼────────────────────────────────────────────────────┐
│ Nginx (4000) │
│ /api/v1/llamacpp/pull-jobs/:id/progress → SSE (proxy_buffering off) │
│ /api/v1/llamacpp/v1/... → SSE (proxy_buffering off) │
│ /api/v1/llamacpp/... → standard proxy │
└─────────────────────────┬────────────────────────────────────────────────────┘
│
┌─────────────────────────▼────────────────────────────────────────────────────┐
│ claw-llamacpp-service (4017) │
│ BinaryService — auto-installs llama-server on bootstrap │
│ CatalogService — 9 frontier seed entries; HF metadata refresh │
│ HardwareService — RAM/disk/GPU snapshot, 60s cache │
│ PreflightValidator — RAM_INSUFFICIENT / DISK_INSUFFICIENT / GPU_… │
│ PullJobsService — HF multi-file download + SHA-256 verify + SSE │
│ ModelsLifecycleService — single-resident mutex, supervisor, runtime config │
│ InferenceService+Proxy — OpenAI-compatible chat / completions pass-through │
│ HealthService — DB + binary + active model rollup │
└─────────┬─────────────────────────────────┬──────────────────────────────────┘
│ │
┌──────▼─────────┐ ┌────────▼─────────────────────────────┐
│ Postgres │ │ llama-server (127.0.0.1:48500-48999) │
│ claw_llamacpp │ │ child process (single resident) │
└─────────────────┘ └──────────────────────────────────────┘
| File | Responsibility |
|---|---|
| src/main.ts | NestJS bootstrap (helmet, CORS, RabbitMQ logger, prefix /api/v1) |
| src/app/app.module.ts | Root module — wires Binary, Catalog, Hardware, PullJobs, ModelsLifecycle, Inference, Health |
| src/app/config/app.config.ts | Zod-validated AppConfig with all LLAMACPP_* env vars |
| src/modules/binary/ |
BinaryService + BinaryInstallerManager — pinned llama.cpp release |
| src/modules/catalog/ |
CatalogService + CatalogRefreshManager — 9 seed entries + HF refresh |
| src/modules/pull-jobs/ |
PullJobsService + PullJobRunnerManager + PullJobProgressEmitterManager (SSE) |
| src/modules/models-lifecycle/ |
ModelsLifecycleService + LlamaServerLauncherManager + ProcessSupervisorManager
|
| src/modules/inference/ |
InferenceService + InferenceProxyManager (OpenAI-compatible passthrough) |
| src/modules/hardware/ |
HardwareService + HardwareDetectorManager + PreflightValidatorManager
|
| src/modules/health/ |
HealthService — rolls up DB + binary + active model |
| prisma/schema.prisma | 7 models: FrontierCatalogEntry, PullJob, ModelLoadEvent, RuntimeConfig, HardwareSnapshot, BinaryRelease, PreflightOverrideAudit |
| prisma/seed-catalog.ts | 9 seed entries: Kimi K2.6, Kimi K2-Thinking, GLM-5.1, DeepSeek V3.2/V4 |
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
DB + binary + active model rollup |
| GET | /runtime/info |
Binary version, platform, GPU backend, capabilities |
| POST | /runtime/update |
Re-run installer (admin) |
| GET | /hardware |
Cached hardware snapshot (60s TTL) |
| POST | /hardware/refresh |
Force re-detection (ADMIN_MODELS_MANAGE, ADR-146) |
| GET | /catalog |
List with category, qualityTier, compatibleOnly, limit
|
| GET | /catalog/:id |
Single entry |
| POST | /catalog/refresh |
Re-fetch HF metadata (admin/operator) |
| POST | /catalog/:id/pull |
Initiate download — returns {jobId, sseUrl}
|
| GET | /pull-jobs |
Paginated list with status filter |
| GET | /pull-jobs/:id |
Single job |
| GET | /pull-jobs/:id/progress |
SSE stream — emits pull-progress events |
| DELETE | /pull-jobs/:id |
Cancel running job |
| POST | /pull-jobs/:id/retry |
Retry failed/cancelled job |
| POST | /models/:id/load |
Spawn llama-server, wait for /health ready, returns {port, pid}
|
| POST | /models/:id/unload |
SIGTERM → SIGKILL after 30s, clears resident state |
| GET | /models/loaded |
Currently resident model (204 if none) |
| PUT | /models/:id/config |
Update RuntimeConfig (admin/operator) |
| DELETE | /models/:id/weights |
Delete weights — requires confirmName body, refuses if resident (admin) |
| POST | /v1/chat/completions |
OpenAI-compatible — supports stream:true SSE |
| POST | /v1/completions |
Legacy completions, also SSE-capable |
Published by claw-llamacpp-service, consumed by claw-audit-service (and claw-routing-service for model.loaded/unloaded/crashed):
| Event | Payload |
|---|---|
llamacpp.binary.installed |
{version, platform, binaryPath} |
llamacpp.binary.updated |
{oldVersion, newVersion, platform} |
llamacpp.pull.started |
{jobId, modelId, totalBytes, totalFiles} |
llamacpp.pull.progress |
{jobId, bytesDownloaded, completedFiles} (throttled 5s) |
llamacpp.pull.completed |
{jobId, modelId, totalBytes} |
llamacpp.pull.failed |
{jobId, modelId, reasonCode, errorMessage} |
llamacpp.model.loaded |
{modelId, port, pid} |
llamacpp.model.unloaded |
{modelId} |
llamacpp.model.crashed |
{modelId, code, signal} |
llamacpp.weights.deleted |
{modelId, sizeBytes} |
llamacpp.preflight.overridden |
{userId, modelId, modelName, reasons[], hardwareSnapshot} |
| Variable | Purpose | Default |
|---|---|---|
LLAMACPP_DATABASE_URL |
Postgres connection | required |
LLAMACPP_SERVICE_URL |
Inter-service URL | http://llamacpp-service:4017 |
LLAMACPP_PORT |
Listen port | 4017 |
LLAMACPP_DATA_PATH |
Filesystem root for binary + weights | required |
LLAMACPP_BINARY_VERSION |
Pinned llama.cpp release tag | b4123 |
LLAMACPP_GPU_BACKEND |
auto / cuda12 / vulkan / cpu / metal
|
auto |
LLAMACPP_DEFAULT_CTX_SIZE |
Default --ctx-size
|
32768 |
LLAMACPP_AUTO_INSTALL_BINARY |
Run installer on bootstrap | true |
LLAMACPP_PREFLIGHT_OVERRIDE_ALLOWED |
Allow RAM/GPU override path | true |
LLAMACPP_LOAD_TIMEOUT_MS |
Max load time before FAILED | 600000 |
LLAMACPP_BIND_HOST |
llama-server bind host | 127.0.0.1 |
LLAMACPP_PROCESS_PORT_MIN/MAX
|
Random allocation range | 48500–48999 |
HUGGINGFACE_TOKEN |
Optional HF token for gated repos | none |
HUGGINGFACE_API_BASE |
HF API base URL | https://huggingface.co |
- Service boots →
BinaryService.onApplicationBootstrapruns. -
BinaryInstallerManager.ensureInstalled()detects platform (e.g.linux-x64-cuda12). -
resolveRelease(platformKey)— primary path is dynamic:GET https://api.github.com/repos/ggml-org/llama.cpp/releases/latest- Filter
release.assets[].nameagainstPLATFORM_ASSET_PATTERNS[platformKey](multiple regex patterns in priority order) - Returns
{ tag, archiveUrl, assetName, binaryName }
- Fallback to static
BINARY_RELEASES[platformKey]only if API fails (network down, rate-limit). - If existing DB record's
version === resolved tagAND binary present on disk → no-op (idempotent). - Otherwise: download → SHA-256 verify (skipped with WARN log when no real SHA pinned — accepted for dev) → extract to
${LLAMACPP_DATA_PATH}/bin/→ recursivelocateBinary()finds the unpackedllama-server[.exe](release archives nest binaries under subdirs). - Persist
BinaryReleaserow withisActive=true. Publishllamacpp.binary.installed(or.updatedif previousVersion existed). - Health endpoint flips
binary.installedtotrue.
Verified live: container today reports {"installed":true,"version":"b8994","platform":"linux-x64-cpu","path":"data/llamacpp/bin/llama-server"} — installed entirely via the dynamic resolver, no static URL update required.
-
POST /models/:id/load→ModelsLifecycleService.load(id). - Acquire global
lifecycleMutex. - If another model resident → unload it first (SIGTERM → SIGKILL after 30s).
- Assert catalog entry's
downloadStatus === READY. - Resolve
RuntimeConfig(per-model overrides + defaults fromLLAMACPP_DEFAULT_CTX_SIZE). -
LlamaServerLauncherManager.spawn→ builds argv, allocates random port from[48500, 48999], child_process.spawn. - Poll
http://127.0.0.1:<port>/healthevery 1s (timeoutLLAMACPP_LOAD_TIMEOUT_MS). - On READY: persist
ModelLoadEvent('LOADED'), attach supervisor, publishllamacpp.model.loaded, return{port, pid}. - On crash:
ProcessSupervisorManager.attachcallback → mark CRASHED → publishllamacpp.model.crashed.
-
POST /catalog/:id/pull {overrideHardwareGate:false}→PullJobsService.create. - Hardware preflight (
PreflightValidatorManager.validate) — refuses onDISK_INSUFFICIENTalways; allows override forRAM/GPU_INSUFFICIENTifLLAMACPP_PREFLIGHT_OVERRIDE_ALLOWED=true. - Reject if active job exists for same model (409).
- Insert PullJob row in PENDING.
- Async
PullJobRunnerManager.run(jobId):- Mark RUNNING, mark catalog
downloadStatus=PULLING. - List HF files via
HuggingFaceClient.listFiles(repo, pattern). - For each: stream-download with resume, retry up to 5× exponential backoff, SHA-256 verify, atomic rename.
- DB writes throttled to 5s; SSE
pull-progressevents on every chunk. - On success: catalog
downloadStatus=READY, publishllamacpp.pull.completed.
- Mark RUNNING, mark catalog
- Frontend subscribes to
GET /pull-jobs/:id/progress(SSE).
-
Logs: NestJS pino logger with structured fields.
pino-http autoLogging.ignoreexcludes SSE routes (/v1/*,/pull-jobs/*/progress). -
Audit events: 11 event patterns published to RabbitMQ exchange
claw.events→claw-audit-service. -
Health:
GET /api/v1/llamacpp/healthaggregated byclaw-health-service. -
Process supervision:
ProcessSupervisorManagerrecords everyLOAD_REQUESTED,LOADED,UNLOAD_REQUESTED,UNLOADED,CRASHED,FAILEDtoModelLoadEvent.
-
JWT auth on all endpoints except
/health(Public decorator). -
Inference and pull progress (
/v1/chat/completions,/v1/completions,/pull-jobs/:id/progress) take a user JWT ORAuthorization: Service <INTER_SERVICE_AUTH_TOKEN>(@AllowServiceToken(), constant-time compare). They were@Public()until ADR-146. A service token on any other route is a 401. -
/internal/llamacpp/loaded-snapshotis service-token only (ServiceTokenGuard). It was reachable from outside as/api/v1/llamacpp/internal/llamacpp/loaded-snapshotbecause nginx strips the/llamacppprefix. -
RBAC:
/runtime/update,/catalog/refresh,/models/:id/config,/models/:id/weightsrequireADMINorADMIN+OPERATOR. -
Argv injection prevented:
LlamaServerLauncherManager.parseCustomArgsenforces allowlist (--n-batch,--n-ubatch,--mlock,--no-mmap,--numa,--rope-freq-base,--rope-freq-scale,--cache-type-k,--cache-type-v,--keep,--main-gpu,--tensor-split) and rejects shell metacharacters (;,&&,|,`,$(). -
Path traversal prevented:
resolveSafePath(LLAMACPP_DATA_PATH, ...)asserts every weight/binary path stays under the data root. -
Network binding: llama-server always binds
127.0.0.1(configLLAMACPP_BIND_HOST); never0.0.0.0. - SHA verification: every downloaded weight file SHA-256-verified against HF manifest before catalog entry flips to READY.
-
Hard hardware gate:
DISK_INSUFFICIENTis non-overridable;RAM/GPU_INSUFFICIENToverrides logged toPreflightOverrideAudittable.
See docs/11-runbooks/frontier-troubleshooting.md for symptom-driven recovery procedures (download stuck, model won't load, model crashed, antivirus quarantine, etc.).
| Host GPU | Probe | Overlay file | Container gets |
|---|---|---|---|
| NVIDIA (Linux/WSL2/Win) |
nvidia-smi -L succeeds |
docker/docker-compose.{dev,prod}.gpu-nvidia.yml |
deploy.resources.reservations.devices.driver=nvidia, NVIDIA_VISIBLE_DEVICES=all
|
| AMD ROCm (Linux only) |
/dev/kfd exists |
docker/docker-compose.{dev,prod}.gpu-rocm.yml |
devices: [/dev/kfd, /dev/dri], group_add: [video, render], ipc: host
|
| Intel iGPU / Arc / Vulkan (Linux) |
/dev/dri/render* exists |
docker/docker-compose.{dev,prod}.gpu-vulkan.yml |
devices: [/dev/dri], group_add: [video, render]
|
| Apple Silicon Metal | uname -s = Darwin |
(none — warns) | CPU-only inside container; for Metal, run service natively |
| None | (no probe matches) | (none) | CPU-only |
CI matrix at .github/workflows/claw-sh-gpu-detection.yml shimmies nvidia-smi and faked /dev/kfd, /dev/dri/renderD128 to assert all 4 Linux scenarios + the macOS warn path.
| Tier | Models | Hardware envelope |
|---|---|---|
Dev-class (3 entries, isRecommended=true) |
phi-4-mini Q4_K_M, qwen3-coder Q4_K_M, llama-3.3 70b-IQ2_XS |
4-24 GB VRAM, 6-32 GB RAM, 4-30 GB disk |
| Frontier-class (9 entries) | Kimi K2.6 Q4 / UD-Q2, Kimi K2-Thinking INT4, GLM-5.1 Q4 / UD-Q2, DeepSeek V3.2 Q4 / UD-IQ1, V4 Pro Q4, V4 Flash Q5 | 96-512 GB RAM, 24+ GB VRAM, 200-680 GB disk |
Dev-class entries exist so contributors with consumer GPUs can validate the full pull → load → inference pipeline without renting an H100.
- ADR-0010 / ADR-033 — claw-llamacpp-service is a separate service (not folded into claw-ollama-service)
- ADR-0011 / ADR-034 — Vanilla
llama.cpponly in v1.0; ik_llama.cpp / KTransformers deferred to v1.1 - ADR-0012 / ADR-035 — Single resident model in v1.0; concurrent slots deferred to v1.2
- ADR-036 — Hard hardware gate:
DISK_INSUFFICIENTnon-overridable, RAM/GPU overridable with audit - ADR-037 —
LLAMACPPregistered as aConnectorProviderenum value so users can attach the service as a managed connector - ADR-0013 — Hard hardware gate; override requires explicit body flag + audit trail
- ADR-0014 —
LLAMACPPis a distinct connector provider type (not OPENAI_COMPATIBLE)
(See docs/13-adr/ for full text.)
ClawAI Wiki · source-controlled under wiki/ · generated/mirrored from the current repository knowledge layer.
ClawAI Wiki
Product
- Product-Vision
- Flagship-Features
- Business-Overview
- Requirements-Register
- Drift-Log
- Runtime-Managed-Connector-Providers
- Threads
- ADR-Index
Architecture
- System-at-a-Glance
- Architecture-Map
- System-Architecture
- Message-Flow
- End-to-End-Data-Flow
- Routing-Engine
- Event-Bus
- Security-Architecture
Platform
- Backend-Services
- Frontend-Architecture
- Shared-Packages
- Database-Reference
- API-Reference
- Provider-Catalog
- Memory-and-Context-Architecture
- File-Support-Architecture
- Research-and-Web-Crawling
Runtime & Ops
- Docker-Guide
- Nginx-Reference
- CI-CD-Pipeline
- Logging-and-Observability
- Operational-Runbooks
- Troubleshooting
Quality
Engineering OS
- AI-Native-Engineering-OS
- Generated-Manifests
- Rules-Catalog
- Skills-Catalog
- Context-Layer
- Engineering-Memory
- Tooling-and-Automation
- Scripts-and-Operations
- ESLint-and-Architecture-Enforcement
- Extended-Work-Skills
Agents
Reference