#!/usr/bin/env python3 """Basecamp wiring recipes — per-service knowledge for fixing and wiring the AI services Basecamp discovers. Single source of truth for both the stack skill (inline recipes for discovered services) and the full references/wiring-recipes.md written into the container's HERMES_HOME. Each recipe has: where — where the service's configuration lives (env / file / UI / API) keys — the exact config keys / env vars / API endpoints involved fix — the one-line (or one-step) fix to wire it correctly verify — how to confirm the wiring actually works """ WIRING_RECIPES = { # ── Inference engines ── "ollama": { "where": "Container env vars + CLI. Clients connect to :11434 (OpenAI-compatible /v1).", "keys": "OLLAMA_HOST, OLLAMA_MODELS, OLLAMA_FLASH_ATTENTION, OLLAMA_KV_CACHE_TYPE; API: GET /api/tags, POST /api/copy, /v1/chat/completions", "fix": "Set OLLAMA_HOST=http://0.0.0.0:11434 to accept remote clients. PERF (verified 2026-08-10): do NOT set OLLAMA_CONTEXT_LENGTH globally — a forced 64K pre-allocates a ~6GB KV cache on EVERY load, starving weights of VRAM and dropping speed ~9x (8B: 9 tok/s vs 80). Use OLLAMA_FLASH_ATTENTION=1 + OLLAMA_KV_CACHE_TYPE=q8_0; ollama sizes KV per-request. Hermes's context_length config is an advertised ceiling, not a per-request demand.", "verify": "curl http://:11434/api/tags returns {\"models\": [...]}; benchmark: ollama run --verbose shows eval rate (expect 60-100 tok/s for an 8B on a 12GB GPU with FA).", "pitfalls": "VERIFIED 2026-08-10: forced 64K = 6GB KV cache = 8B mostly on CPU (9 tok/s). Removing it + FA + q8 KV = 80 tok/s, 33/33 layers on GPU, KV 272MB. Check offload: docker logs | grep offloaded. Keep models resident with keep_alive to avoid reload penalties.", }, "tabbyapi": { "where": "config.yml (model_dir, model_name, max_seq_len) + api_tokens.yml (api_key, admin_key), both mounted from the host.", "keys": "config.yml: model.model_name, model.max_seq_len, model.cache_8bit; api_tokens.yml: api_key, admin_key; API: /v1/models, /v1/chat/completions (Bearer)", "fix": "Edit config.yml to point model.model_name at a model present in model_dir, restart the container. Clients use Bearer against http://:5000/v1. max_seq_len is VRAM-bound (64K KV cache needs ~16GB for 8B).", "verify": "curl -H 'Authorization: Bearer ' http://:5000/v1/models returns the loaded model list.", }, "vllm": { "where": "CLI launch flags (no config file).", "keys": "--model, --served-model-name, --host, --port, --max-model-len; API: /v1/models, /v1/chat/completions", "fix": "Relaunch with --host 0.0.0.0 --port 8000 --served-model-name so the served name matches what clients request. Hermes needs --max-model-len >= 65536.", "verify": "curl http://:8000/v1/models lists the served model id.", }, "litellm": { "where": "config.yaml (model_list) + master key env var.", "keys": "model_list: [{model_name, litellm_params: {model, api_base, api_key}}]; env: LITELLM_MASTER_KEY; API: /v1/models, /v1/chat/completions (Bearer)", "fix": "Add the upstream model to model_list with correct api_base, restart. Clients use Bearer against http://:4000/v1.", "verify": "curl -H 'Authorization: Bearer ' http://:4000/v1/models lists configured model_names.", }, "localai": { "where": "Env vars + model files in the models directory.", "keys": "MODELS_PATH, THREADS, DEBUG; API: /v1/models, /v1/chat/completions (OpenAI-compatible)", "fix": "Drop GGUF models into the models dir (or set MODELS_PATH), restart. Clients use http://:8080/v1 (or the mapped port).", "verify": "curl http://:8080/v1/models lists models.", }, "llamacpp": { "where": "CLI flags (llama-server).", "keys": "-m , --host, --port, -c , --api-key; API: /health, /v1/models, /v1/chat/completions", "fix": "Relaunch with --host 0.0.0.0 so other containers can reach it; -c 65536 for Hermes. If --api-key is set, clients must send it.", "verify": "curl http://:/health returns 'ok'.", }, "text-generation-webui": { "where": "Settings files + runtime API (model loaded per-session).", "keys": "API: GET/POST /api/v1/model (load model), /v1/chat/completions; settings in settings.yaml", "fix": "POST /api/v1/model with {\"model_name\": \"\"} to load a model; clients use http://:5000/v1.", "verify": "curl http://:5000/api/v1/model returns the loaded model_name.", }, "koboldcpp": { "where": "CLI flags.", "keys": "--model, --host, --port, --contextsize; API: /api/v1/model, /v1/models, /v1/chat/completions", "fix": "Relaunch with --host 0.0.0.0 and --contextsize 65536 for Hermes. Clients use http://:5001/v1.", "verify": "curl http://:5001/api/v1/model returns a result with the model name.", }, "lmstudio": { "where": "In-app GUI only (no server-side config file for the API).", "keys": "Settings → Developer → Local Server (port 1234); API: /v1/models, /v1/chat/completions", "fix": "The user must enable the local server in the app GUI (Settings → Developer → Start Server). No remote fix exists — explain the 3 clicks.", "verify": "curl http://:1234/v1/models lists loaded models.", }, "sglang": { "where": "CLI flags.", "keys": "--model-path, --host, --port, --served-model-name; API: /v1/models, /v1/chat/completions", "fix": "Relaunch with --host 0.0.0.0 --served-model-name matching what clients request; --context-length >= 65536 for Hermes.", "verify": "curl http://:8002/v1/models (response contains 'sglang').", }, "llamafile": { "where": "CLI/env of the single-file binary.", "keys": "-ngl (GPU offload), --host, --port; API: /v1/models, /v1/chat/completions", "fix": "Launch with --host 0.0.0.0 (default is localhost only). Clients use http://:8080/v1.", "verify": "curl http://:8080/v1/models lists the model.", }, "exo": { "where": "config.yaml + peer discovery.", "keys": "config.yaml: api_port, peers; API: /v1/models, /v1/chat/completions", "fix": "Ensure api_port is reachable on the docker network; add peer IPs to config.yaml. Clients use http://:8000/v1.", "verify": "curl http://:8000/v1/models lists the cluster's models.", }, "text-generation-inference": { "where": "CLI flags (launcher).", "keys": "--model-id, --port, --hostname, --max-total-tokens; API: /v1/models, /info", "fix": "Relaunch with --hostname 0.0.0.0 --model-id . Clients use http://:8080/v1.", "verify": "curl http://:8080/info returns model_id.", }, "aphrodite": { "where": "CLI flags (vLLM-style).", "keys": "--model, --served-model-name, --host, --port; API: /v1/models, /v1/chat/completions", "fix": "Relaunch with --host 0.0.0.0 and --served-model-name matching client requests.", "verify": "curl http://:8000/v1/models (response contains 'aphrodite').", }, "llamapool": { "where": "config.toml.", "keys": "[routes] entries mapping route names to upstream endpoints", "fix": "Add/edit a [routes] entry pointing at the upstream inference server, restart. Clients use the route name as the model id.", "verify": "curl http://:/v1/models lists route names.", }, # ── UIs / chat frontends ── "open-webui": { "where": "Container env vars (primary) + admin Settings → Connections (runtime override).", "keys": "OLLAMA_BASE_URL, OPENAI_API_BASE_URL, OPENAI_API_KEY; admin UI: Settings → Connections; API: GET /api/config (public), /api/models (auth), /api/v1/auths/signin", "fix": "Set OLLAMA_BASE_URL=http://ollama:11434 (and/or OPENAI_API_BASE_URL + key) in the container env, OR have the admin set it in Settings → Connections. This is THE buried needle-in-a-haystack setting — name it explicitly.", "verify": "curl http://:8080/api/config returns status:true; authenticated GET /api/models lists engine models.", }, "sillytavern": { "where": "config.yaml (config dir mounted) for server auth; per-user API config in the UI.", "keys": "config.yaml: listen, whitelist, basicAuthUser, basicAuthPassword; UI: extensions → connection settings → API URL + model; API: /api/status (401 = alive behind auth)", "fix": "Server auth lives in config.yaml (default admin:). The API URL is per-user: UI → extensions → connection settings → set to http://:/v1 and pick the model.", "verify": "curl -u admin: http://:8000/api/status returns JSON.", }, "librechat": { "where": "librechat.yaml (endpoints/agents) + .env.", "keys": "librechat.yaml: endpoints.agents. {provider, baseURL, apiKey}; .env: END_USER_* vars; API: /api/health", "fix": "Add an endpoints.agents block with provider=openai (or custom), baseURL=http://:/v1, apiKey. Restart. Set in the UI's Admin → Providers as an alternative.", "verify": "curl http://:3080/api/health returns ok; the model appears in the UI model list.", }, "lobechat": { "where": "In-app settings (model providers) + env vars.", "keys": "UI: Settings → Language Model → providers; env: OPENAI_API_KEY etc; API: /api/status", "fix": "In the UI: Settings → Language Model → add provider with base URL http://:/v1 + key (if any). No server-side config file.", "verify": "curl http://:3001/api/status returns 200; model selectable in chat.", }, "anythingllm": { "where": "In-app Settings (LLM provider + vector DB) + .env.", "keys": "UI: Settings → LLM Preference → provider + base URL + key; Settings → Vector Database; API: /api/system/endpoints", "fix": "In the UI: Settings → LLM Preference → pick Ollama/OpenAI, set base URL to the engine, set the key. Vector DB similar (defaults to LanceDB — fine for noobs).", "verify": "curl http://:3001/api/system/endpoints lists configured endpoints.", }, "dify": { "where": ".env file (backend) + per-app model config in UI.", "keys": ".env: OPENAI_API_KEY, OPENAI_API_BASE; UI: Settings → Model Provider; API: /health", "fix": "Set OPENAI_API_BASE + OPENAI_API_KEY in .env (or use the UI Model Provider page) to point at the engine, restart. Model per-app: Settings → Model Provider → add.", "verify": "curl http:///health returns healthy.", }, "flowise": { "where": "UI credentials store + env.", "keys": "UI: Settings → Credentials; API: /api/v1/ping, /api/v1/credentials", "fix": "UI: Settings → Credentials → add the provider credential with base URL + key. Nodes then reference it. No server-side wiring file.", "verify": "curl http://:3000/api/v1/ping returns pong.", }, "n8n": { "where": "UI credentials store + env (auth).", "keys": "env: N8N_BASIC_AUTH_USER, N8N_BASIC_AUTH_PASSWORD; UI: Credentials → OpenAI/Ollama; API: /healthz", "fix": "UI: Credentials → add the AI provider credential (base URL + key) and reference it in nodes. Set basic auth envs if exposed publicly.", "verify": "curl http://:5678/healthz returns ok.", }, "langflow": { "where": ".env + UI components.", "keys": "UI: Settings → Global Variables; env: OPENAI_API_KEY etc; API: /api/v1/configs", "fix": "UI: Settings → Global Variables → add the provider base URL + key; use them in component nodes.", "verify": "curl http://:7860/api/v1/configs returns 200 (or auth wall).", }, "ragflow": { "where": "service_conf.yaml + .env; models configured in UI.", "keys": ".env: MODEL providers; service_conf.yaml: es/storage; UI: Settings → Model providers; API: /api/v1/version", "fix": "UI: Settings → Model providers → add the engine (base URL + key). Backend needs the configured embedding+chat models.", "verify": "curl http:///api/v1/version returns a version.", }, "koboldai": { "where": "Settings files in the data dir.", "keys": "API: /api/v1/config/status, /api/v1/model", "fix": "Legacy app; connect by setting the API URL in the client to http://:5000/v1 and loading a model via its UI.", "verify": "curl http://:5000/api/v1/config/status returns config.", }, # ── Vector DBs ── "qdrant": { "where": "config.yaml or QDRANT__* env vars.", "keys": "QDRANT__SERVICE__API_KEY, QDRANT__SERVICE__GRPC_PORT; API: /readyz, /collections", "fix": "Set API key via env if auth needed; ensure clients use http://:6333. Point your RAG app's vector DB URL here.", "verify": "curl http://:6333/readyz returns ok.", }, "milvus": { "where": "milvus.yaml + etcd dependency.", "keys": "milvus.yaml: etcd.endpoints, proxy port 19530; API: /healthz (http :9091)", "fix": "Milvus needs etcd running and reachable; point your RAG app at grpc://:19530.", "verify": "curl http://:9091/healthz returns {\"status\":\"OK\"}.", }, "chroma": { "where": "Env vars.", "keys": "CHROMA_SERVER_HOST, CHROMA_SERVER_PORT, CHROMA_AUTH_*; API: /api/v2/health", "fix": "Set CHROMA_SERVER_HOST=0.0.0.0 to accept remote clients; point your RAG app at http://:8000.", "verify": "curl http://:8000/api/v2/health returns 200.", }, "weaviate": { "where": "Env vars.", "keys": "PERSISTENCE_DATA_PATH, AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED, AUTHENTICATION_APIKEY_ENABLED + APIKEY_ALLOWED_KEYS; API: /v1/meta", "fix": "Enable anonymous access or set an API key via env; point your RAG app at http://:8081.", "verify": "curl http://:8081/v1/meta returns version + model info.", }, # ── Image / media ── "comfyui": { "where": "extra_model_paths.yaml (model folder locations).", "keys": "extra_model_paths.yaml: checkpoints, loras, vae paths; API: /system_stats, /prompt (POST)", "fix": "Edit extra_model_paths.yaml to point at the real model dirs (or use the default structure), restart. Clients POST workflows to /prompt.", "verify": "curl http://:8188/system_stats returns GPU/RAM stats.", }, "stable-diffusion-webui": { "where": "webui-user.sh (COMMANDLINE_ARGS) + models dir.", "keys": "webui-user.sh: COMMANDLINE_ARGS=--api; API: /sdapi/v1/options, /sdapi/v1/txt2img", "fix": "Add --api to COMMANDLINE_ARGS and restart, or the API endpoints 404. Model picker via /sdapi/v1/options (sd_model_checkpoint).", "verify": "curl http://:7860/sdapi/v1/options returns options JSON.", }, "invokeai": { "where": "invokeai.yaml + UI model manager.", "keys": "invokeai.yaml: host, port; API: /api/v1/app/version", "fix": "Ensure host is 0.0.0.0 in invokeai.yaml; models added via the UI Model Manager (Auto-scan a folder).", "verify": "curl http://:9090/api/v1/app/version returns a version.", }, # ── Speech ── "whisper": { "where": "CLI flags of the whisper server (faster-whisper-server / whisper.cpp).", "keys": "--host, --port, --model (size); API: /v1/audio/transcriptions (OpenAI-compatible)", "fix": "Launch with --host 0.0.0.0; point clients at http://:/v1. Pick model size for the GPU.", "verify": "curl http://:/health returns ok.", }, "piper": { "where": "CLI flags of the TTS server.", "keys": "--host, --port; API: / (synthesize GET/POST)", "fix": "Launch with --host 0.0.0.0; point clients at http://: with the voice model.", "verify": "curl http://:/health returns ok.", }, # ── Gateways / proxies / hubs ── "openrouter": { "where": "Cloud API — key only.", "keys": "API key; base https://openrouter.ai/api/v1", "fix": "Clients send Bearer to https://openrouter.ai/api/v1. No local config.", "verify": "curl -H 'Authorization: Bearer ' https://openrouter.ai/api/v1/models works.", }, "kobold-horde": { "where": "Cloud crowd API — key optional.", "keys": "API key (optional); API: /api/v1/status, /api/v1/generate", "fix": "Clients set the horde URL to http://:2323 (or the public horde) and optionally their kudos key.", "verify": "curl http://:2323/api/v1/status returns queue stats.", }, # ── Basecamp's own companions ── "mcpo": { "where": "config.json (mounted) — the MCP server list.", "keys": "config.json: servers [{name, transport, route, api_key}]; API: /openapi.json, /mcp (SSE)", "fix": "Edit mcpo/config.json to add/point MCP servers (name + route + key), restart the container. Basecamp connects to it via SSE at /mcp.", "verify": "curl http://:8000/openapi.json lists the proxied MCP server routes.", }, "searxng": { "where": "settings.yml (mounted) — search engines + formats.", "keys": "settings.yml: search.formats (json), engines list; API: /search?q=...&format=json", "fix": "Ensure 'json' is in search.formats in settings.yml or the JSON API returns 400. Engines added/removed in the engines section.", "verify": "curl http://:8080/ returns the SearXNG page.", }, # ── Coding starter pack ── "code-server": { "where": "Container env + entrypoint extension installs. The IDE itself is VS Code in the browser.", "keys": "env: PASSWORD (login); entrypoint: code-server --install-extension Continue.continue / TabbyML.vscode-tabby; API: /healthz, / (web UI)", "fix": "Login with the PASSWORD env at http://:8443. Continue.dev and the Tabby extension are pre-installed in the Basecamp pack — configure Continue's model to http://ollama:11434/v1 (or tabbyapi:5000/v1) in its config.json, and point the Tabby extension at http://:8082.", "verify": "curl http://:8443/healthz returns 200; the browser UI loads at :8443.", "pitfalls": "Extension installs run on first container start — give it ~60s before the IDE is fully ready. Login password comes from the PASSWORD env (default basecamp) — change it.", }, "tabby": { "where": "CLI flags at container start + /data volume for models.", "keys": "command: serve --device cpu|--device cuda --model --chat-model ; env: TABBY_MODEL_CACHE_DIR; API: /v1/health, /v1/completions (code completion)", "fix": "DO NOT USE in CPU-only containers — BROKEN as of 2026-08-09 (verified on 20260330 pin): tabby's bundled llama-server dlopens libcuda.so.1 at startup EVEN with --device cpu (binary is CUDA-compiled), and crashes with exit 127 'cannot open shared object file' when the container lacks NVIDIA libs. Needs --gpus all + CUDA libs. Removed from the starter pack; re-add only with GPU access or a true CPU-only tabby build.", "verify": "curl http://:8082/v1/health — expected to FAIL in CPU-only containers until tabby ships a CPU-only build.", "pitfalls": "VERIFIED 2026-08-09: (1) 'latest' images spawn Nomic-Embed with -ngl 9999 → exit 127 on constrained GPUs; (2) even the 20260330 pin crash-loops in CPU-only containers: llama-server dlopens libcuda.so.1 → 'cannot open shared object file' → exit 127. (3) StarCoder2-3B is completion-only (no chat template) — a separate instruct model is required for --chat-model. Ripped out of the pack.", }, "meilisearch": { "where": "Env vars.", "keys": "MEILI_MASTER_KEY, MEILI_ENV; API: /health, /indexes, /search", "fix": "Set MEILI_MASTER_KEY so clients can authenticate; index your code/docs via the API and search at /search with the key.", "verify": "curl http://:7700/health returns {\"status\":\"available\"}.", }, "librechat": { "where": "Env vars + REQUIRES MongoDB (not self-contained).", "keys": "MONGO_URI (required), OPENAI_API_KEY, OPENAI_API_BASE; API: /api/health", "fix": "LibreChat CRASHES on boot without MONGO_URI (verified 2026-08): 'Please define the MONGO_URI environment variable'. It needs a mongo container — the Basecamp pack ships one (starter-mongo, mongo:7) and sets MONGO_URI=mongodb://mongo:27017/LibreChat. Image lives on GHCR (ghcr.io/danny-avila/librechat), NOT Docker Hub.", "verify": "curl http://:3080/api/health returns ok AFTER mongo is up.", "pitfalls": "Image is ghcr.io/danny-avila/librechat:latest — the Docker Hub 'danny-avila/librechat' repo 404s. First boot takes ~60s. Depends on mongo; start mongo first (depends_on in compose).", }, "flowise": { "where": "Env vars (PORT, FLOWISE_USERNAME, FLOWISE_PASSWORD) + /root/.flowise volume.", "keys": "PORT=3000, FLOWISE_USERNAME, FLOWISE_PASSWORD; API: /api/v1/ping", "fix": "DO NOT USE — BROKEN UPSTREAM as of 2026-08-09 (verified on latest AND 3.1.4): crashes at boot with ERR_PACKAGE_PATH_NOT_EXPORTED — @langchain/langgraph-checkpoint requires './utils/uuid' which @langchain/core doesn't export. Removed from the starter pack. Re-add only when upstream fixes their dependency tree.", "verify": "curl http://:7860/api/v1/ping — expected to FAIL until upstream fixes the langchain dep.", "pitfalls": "KNOWN BUG (verified 2026-08-09, both latest and 3.1.4): ERR_PACKAGE_PATH_NOT_EXPORTED './utils/uuid' in @langchain/core crashes node at boot (ReActAgentChat/ReActAgentLLM nodes). Not a compose problem — the image's dependency tree is broken. Ripped out of the pack.", }, "postgres": { "where": "Container env (db/user/password) + data volume.", "keys": "POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD; port 5432; pgvector extension for embeddings", "fix": "This is Hermes's long-term memory backend (Postgres + pgvector). Point hermes at it: hermes memory configure --backend postgres --dsn 'postgresql://:@starter-postgres:5432/'. The pgvector image ships the extension — create it with: docker exec starter-postgres psql -U hermes -d hermes -c 'CREATE EXTENSION IF NOT EXISTS vector;'", "verify": "docker exec starter-postgres pg_isready -U hermes -d hermes returns 'accepting connections'.", }, "mongo": { "where": "Container env + /data/db volume. Database for LibreChat (and other apps).", "keys": "MONGO_URI=mongodb://mongo:27017/; port 27017; image mongo:7", "fix": "Point dependent apps at mongodb://:27017/. LibreChat requires MONGO_URI or it crashes on boot (verified 2026-08). No auth by default on the pack's instance — add a root user for anything public.", "verify": "docker exec starter-mongo mongosh --quiet --eval 'db.runCommand({ping:1}).ok' returns 1.", }, } def wiring_recipe_markdown(service_types=None): """Render WIRING_RECIPES as markdown, optionally filtered to a type list. Returns a list of markdown lines (no trailing newline joining) ready to be embedded in a SKILL.md or written as a standalone reference file. """ types = service_types or list(WIRING_RECIPES.keys()) lines = [] for t in sorted(types): r = WIRING_RECIPES.get(t) if not r: continue lines.append(f"### {t}") lines.append(f"- **Config lives in:** {r['where']}") lines.append(f"- **Keys/endpoints:** {r['keys']}") lines.append(f"- **Fix:** {r['fix']}") lines.append(f"- **Verify:** {r['verify']}") if r.get("pitfalls"): lines.append(f"- **Pitfalls (verified):** {r['pitfalls']}") lines.append("") return lines