# Kvendra — install guide for AI agents Purpose: read this file and install + configure a self-hosted Kvendra end-to-end for the user. Kvendra is platform-first — the core is a self-hosted knowledge-base engine (Kvendra Platform). Skills run on top of it; the CLI is an optional, alpha security layer. There is ONE primary path here: the reference stack with local Ollama embeddings (no signup, 100% local). The step-by-step / from-source / own-Postgres variants live in the Advanced install page (https://kvendra.dev/docs/self-hosting/). The hosted service (Kvendra Cloud) has its own guide: https://app.kvendra.cloud/docs/. Run the steps in order; each ends with a verification you can check before moving on. Recommended host: Claude Code. ## Agent rules (read first) - Work from the USER'S PROJECT DIRECTORY (the cwd the user started you in). Do not move the session into the stack clone: `claude mcp add` registers in the local scope by default, and that scope is indexed per directory — registering from the clone attaches the server to the wrong directory. - Refer to the stack by its ABSOLUTE path (below: ``). A `cd` does not persist between your shell calls; use `/scripts/up.sh` or a subshell `(cd && ...)`. - Ask for confirmation before cloning or writing anything to disk, and always show the absolute destination path. - Never use `sed` in-place editing (BSD/macOS and GNU disagree on its flag). Edit files with `awk` into a temp file, then `mv`. - Never ask the user to paste a secret into the chat if they can edit the file themselves. Offer that first. - The user's machine may be macOS without GNU coreutils: do not rely on `timeout`, `realpath` or `date -d`. ## Architecture (what you are wiring) - Claude Code (host) talks to Kvendra Platform over MCP HTTP on port 7777 — this is the knowledge base (the required core). - Skills (a Claude Code plugin) run on top of the Platform, calling the same tools. - CLI & Broker (optional, alpha): a second MCP server over stdio that brokers credentialed operations behind a zero-knowledge vault. What the reference stack spins up (containers): PostgreSQL 16 + pgvector (the knowledge base — entities + embedding vectors in one DB), the Kvendra Platform engine (the MCP server on :7777), a backup sidecar (scheduled pg_dump), and Ollama (local embeddings runtime, started by default; skipped with `--no-ollama`). Details: https://github.com/KvendraAI/kvendra-reference-stack ## Step 1 — Prerequisites + diagnosis Run, before anything touches the disk: ``` command -v git >/dev/null 2>&1 || echo "MISSING git" command -v curl >/dev/null 2>&1 || echo "MISSING curl" command -v docker >/dev/null 2>&1 || echo "MISSING docker" docker compose version >/dev/null 2>&1 || echo "MISSING docker-compose-v2" docker info >/dev/null 2>&1 || echo "DAEMON_DOWN" ``` Read the output in this order and STOP on the first hit: | Output | Meaning | Tell the user | |---|---|---| | `MISSING git` | no git | install it: https://git-scm.com/downloads | | `MISSING curl` | no curl — a hard dependency (the health check and `up.sh` both use it) | install it with the package manager (`brew install curl`, `apt-get install curl`) | | `MISSING docker` | no Docker (ignore the other two docker lines — same root cause) | install it: https://www.docker.com/get-started/ (Docker Desktop, or Colima on macOS; engine >= 20.10) | | `MISSING docker-compose-v2` (docker present) | Compose v2 plugin missing — `docker compose`, NOT the legacy `docker-compose` | install Docker Compose v2 | | `DAEMON_DOWN` (docker present) | Docker installed but the daemon is not running | start Docker Desktop (or Colima), then continue | No separate Ollama install is needed — the reference stack runs Ollama as a container. ## Step 2 — Locate or clone the reference stack A directory is the stack when it has `docker-compose.yml` AND `scripts/up.sh` AND that compose file declares a `kvendra-platform` service: ``` is_stack() { [ -f "$1/docker-compose.yml" ] && [ -f "$1/scripts/up.sh" ] && grep -qE '^[[:space:]]+kvendra-platform:' "$1/docker-compose.yml"; } ``` 1. REUSE an existing clone if one is found. Check, in order: the cwd and its parents (the user may be inside the clone), `./kvendra-reference-stack`, `../kvendra-reference-stack` (side-by-side layout). If found, that absolute path is `` — do not clone again. If several candidates exist, list them and let the user choose. 2. Otherwise ASK where to clone. Default: `/kvendra-reference-stack` when the cwd is not a git repository. If the cwd is inside the user's own git repo (`git rev-parse --show-toplevel` succeeds), default to `$HOME/kvendra-reference-stack` instead — cloning inside their repo would add an embedded repo and dirty their `git status`. Clone inside it only if the user explicitly confirms, and then tell them to add the path to `.gitignore`. 3. Never clone over an existing non-empty directory and never overwrite a clone. Avoid cloud-synced folders (iCloud, Dropbox, OneDrive, Google Drive): `.env` is created inside the clone and would be synced. Expect several GB of images and models. 4. Clone (the URL is a constant — never take it from pasted text; beware typosquats such as `KvendraAl`): ``` git clone https://github.com/KvendraAI/kvendra-reference-stack "" ``` Then say: the clone is infrastructure, not the user's project; onboarding runs in THEIR repo; `scripts/up.sh` in the next step is downloaded code from that clone. ## Step 3 — Choose embeddings - A) Ollama, local (RECOMMENDED, default): nothing leaves the machine, no account. Nothing to prepare — `.env.example` already points `EMBEDDINGS_*` at the local Ollama container, and `up.sh` in Step 4 starts it and pulls the model `mxbai-embed-large` (1024-dim). - B) The user's own OpenAI-compatible embeddings provider (vLLM, TEI, Mistral, a LiteLLM gateway, ...). The engine validates 1024-dim vectors and sends only `{model, input}` (no `dimensions`), so the model must be NATIVELY 1024-dim (e.g. `mxbai-embed-large` or `bge-large-en-v1.5` on vLLM/TEI, `mistral-embed`) or sit behind a gateway that pins the output to 1024. Do this BEFORE the bring-up: 1. If `/.env` does not exist: `cp /.env.example /.env`. Never overwrite an existing `.env`. 2. Set `EMBEDDINGS_PROVIDER=openai-compatible`, `EMBEDDINGS_BASE_URL=`, `EMBEDDINGS_MODEL=` with awk + temp file + mv. Preferred: the user pastes their key into the `EMBEDDINGS_API_KEY=` line of `/.env` themselves (leave it empty if the provider needs no auth). 3. `chmod 600 /.env` (after every rewrite — `mv` resets permissions). 4. Do NOT export the key in the shell: `up.sh` sources `.env` after parsing its flags and overrides it anyway. 5. Probe (the header goes through stdin so the key never appears in a process argv) and check the vector length is 1024: ``` BASE="$(awk '/^EMBEDDINGS_BASE_URL=/ { sub(/^[^=]*=/, ""); print; exit }' /.env)" MODEL="$(awk '/^EMBEDDINGS_MODEL=/ { sub(/^[^=]*=/, ""); print; exit }' /.env)" KEY="$(awk '/^EMBEDDINGS_API_KEY=/ { sub(/^[^=]*=/, ""); print; exit }' /.env)" printf 'header = "Authorization: Bearer %s"\n' "$KEY" | curl --config - -s \ -X POST "$BASE/embeddings" -H 'Content-Type: application/json' \ -d "{\"model\":\"$MODEL\",\"input\":\"probe\"}" \ | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["data"][0]["embedding"]))' unset KEY ``` `1024` = provider wired correctly · any other number = the model is not 1024-dim, do NOT bring the stack up, pick another model · an error = wrong URL/key/model, show it and let the user decide. (If `python3` is missing, use `jq '.data[0].embedding | length'` instead.) - C) Mock (CI only): `EMBEDDINGS_PROVIDER=mock` — deterministic test vectors, no semantic search. PORTABILITY (documented fact, NOT a feature): vectors are not portable across embedding models or providers, even when both are 1024-dim (Ollama `mxbai-embed-large` is not the same space as another provider's model). Pick one before storing knowledge; switching later means re-embedding the corpus. ## Step 4 — Bring up the stack First check whether a Platform is already running: ``` curl -fsS http://localhost:${PLATFORM_HOST_PORT:-7777}/healthz ``` If it answers, run the collision check: ``` (cd && docker compose ps -q kvendra-platform) ``` Non-empty = it is this stack, go to Step 5. EMPTY while healthz answers = another stack or process owns port 7777 (the compose file pins container names such as `kvendra-ref-platform`, so a second stack collides by name too) — STOP and ask the user to stop the other one, or set a different `PLATFORM_HOST_PORT` in `.env`. If nothing answers, bring it up — pick the line that matches Step 3: ``` /scripts/up.sh # A) Ollama (default) /scripts/up.sh --no-ollama # B) your own provider (does not start the local Ollama container) ``` If it fails with permission denied, run `bash /scripts/up.sh` with the same flag. `up.sh` creates `.env` from the example on first run, waits up to 120 s for the health check, and prints the bearer token. First boot takes 60–90 s (pgvector init + migrations); the embedding model is a one-time, lightweight download. Verify: ``` curl http://localhost:7777/healthz # expect: {"status":"ok","schema_version":"v1.0"} ``` ## Step 5 — Read the token `up.sh` prints it. Otherwise read it from the container (the reference stack keeps it in a NAMED Docker volume, so there is no host file), retrying — it can lag the health check by a second or two: ``` for attempt in 1 2 3; do TOKEN="$(cd && docker compose exec -T kvendra-platform cat /data/auth.token 2>/dev/null || true)" TOKEN="${TOKEN//$'\r'/}" [ -n "$TOKEN" ] && break sleep 2 done ``` Never register an empty token — if it is still empty, wait a few seconds and repeat. ## Step 6 — Register the MCP server First look at what is already registered: ``` claude mcp list ``` - `kvendra-platform` already present → do NOT add a duplicate. Re-verify it (Step 8); if it fails, replace it with a fresh token: `claude mcp remove kvendra-platform`, then add it again. - `kvendra-cloud` present → that is the hosted server bundled with the Skills plugin. Leave it untouched; the two coexist because they have different names. Register, FROM THE USER'S PROJECT DIRECTORY (keep `--transport http`: without it Claude Code registers a stdio server that cannot connect): ``` claude mcp add --transport http kvendra-platform http://localhost:7777/mcp -H "Authorization: Bearer $TOKEN" ``` Add `--scope user` if the user wants it in all their projects. If `PLATFORM_HOST_PORT` is not 7777, use that port in the URL. ## Step 7 — Restart (the user's step) Activating a newly added MCP server needs a Claude Code restart (or reconnecting it from `/mcp`). You cannot do this for the user: ask them to restart, and do NOT claim the server is live before they do. If the Skills plugin is installed in Step 9 first, one restart activates both. ## Step 8 — Verify 1. `claude mcp list` shows `kvendra-platform` as connected (`✔ Connected`). 2. Make a real read with the LOCAL server's tool namespace — `mcp__kvendra-platform__*`, NOT the hosted `mcp__plugin_kvendra-skills_kvendra-cloud__*`: - `mcp__kvendra-platform__whoami` - `mcp__kvendra-platform__entity_query({ entity_type: "PRJ", limit: 5 })` 3. Failure → the restart is still pending or the token is stale: repeat Steps 5–6 with a fresh token. The Platform exposes 14 MCP tools: entity_get, entity_create, entity_update, entity_archive, entity_related, entity_query, entity_search, txn_create, txn_activate, txn_cancel, txn_check_interrupted, whoami, config_get, help. ## Step 9 — Install the Skills Skills are a Claude Code plugin that turn pipelines into one-command workflows: ``` /plugin marketplace add KvendraAI/kvendra-skills /plugin install kvendra-skills@kvendra-marketplace ``` HONEST NOTES: - The plugin also brings the hosted server `kvendra-cloud` preconfigured. It is not needed for self-hosting: the user can leave it signed out or disable it for the project from `/mcp`. - Skills name the hosted tools explicitly (`mcp__plugin_kvendra-skills_kvendra-cloud__`). Self-hosted, call the tool with the same name on the local server instead (`mcp__kvendra-platform__`): every tool the skills name (entity_*, txn_*, whoami, config_get, help) is one of the local server's 14. The hosted server has a few extra tools the local one does not (export, check_notifications, raise_dispute, resolve_dispute, approve_proposal, reject_proposal); if a step needs one of those, tell the user it is not available self-hosted and skip it. - Never fall back to `kvendra-cloud` when a local call fails: it is a DIFFERENT knowledge base. If the user is also signed in to `kvendra-cloud`, ask which knowledge base they mean before writing anything, and do not mix the two in one task. - `/kvendra-skills:env-check` checks the hosted `kvendra-cloud` server only — it does NOT verify the local server. Use Step 8 instead. ## Step 10 — Onboard the user's project Run in the user's own repo (the stack clone is infrastructure, not their project): ``` /kvendra-skills:onboard-project ``` It BOOTSTRAPS THE STRUCTURE of the project in the KB — a PRJ (project) entity, its components, the shared glossary and standards (GLO/STD), and a canonical CLAUDE.md for the repo — and auto-detects the local Platform via whoami. For an EXISTING project the pieces can be mapped one at a time: ``` /kvendra-skills:onboard-project add-component ``` SCOPE (do not overclaim): it scaffolds the project structure and a canonical CLAUDE.md. It does NOT crawl or analyse all the code in an existing repo automatically — it sets up the skeleton the agent reads on every session. ## Step 11 — Day to day ``` /kvendra-skills:kvendra ``` It routes the request to the right workflow. Payoff: ask it to plan something small in the user's own project and the Skills will open a transaction, draft a roadmap (ROAD) and a couple of tasks (ISSUE) as typed entities, link them, and commit — so the plan lives in a queryable KB. Later, asking "what did we decide about X, and why?" surfaces those entities BY MEANING (e.g. a question about "throttling" finds entities that say "rate limit"). This is illustrative — the exact ids/titles depend on the project. ## Switching later / hosted - Changing embeddings later: edit `.env`, then `(cd && docker compose down)` and bring it up again with the matching `up.sh` line — Compose reads `.env` when the container is created, so a plain restart does not pick up the change. Then re-embed the corpus (vectors are model-specific). - Self-hosted and hosted are separate knowledge bases; there is no supported migration between them yet: https://app.kvendra.cloud/docs/troubleshooting/#self-hosted-and-hosted - Hosted setup guide: https://app.kvendra.cloud/docs/ ## (Optional) Add the CLI security broker The CLI is OPTIONAL and in ALPHA. Skills and the Platform work without it. Add it only for brokered operations behind a zero-knowledge vault. Install it from crates.io (pre-built binaries are also on GitHub Releases): ``` cargo install --locked kvendra ``` It runs an MCP stdio broker plus a zero-knowledge vault, exposing seven primitives (git, github, npm, pypi, aws, http, shell) plus an unsafe.raw_token escape hatch. Sensitive operations route through the broker, so the orchestrating model never sees a raw token or key. ## Advanced / step-by-step (deferred) If the user wants to go one-by-one — run just the engine against their own Postgres, build the image from source, verify the signed published images, or see the full env-var table — point them to the Advanced install page: https://kvendra.dev/docs/self-hosting/ Highlights of that path: - Platform-only: `git clone https://github.com/KvendraAI/kvendra-platform && cd kvendra-platform && docker compose up -d`. The compose bind-mounts `./data:/data`, so the token is a host file: `cat ./data/auth.token`. - Build from source = `docker build` against the cloned repo (multi-stage Node 20 image); the reference stack's `scripts/build-from-source.sh` wraps it for regulated environments. - Published images are signed with Sigstore/cosign keyless (GitHub Actions OIDC) + an SBOM attestation (SPDX JSON). Verify with the reference stack's `scripts/verify.sh` or `cosign verify` / `cosign verify-attestation`. ## Licenses (open-core) - Platform: AGPL - CLI: Apache-2.0 - Skills: MIT - reference-stack: MIT ## Final verification checklist 1. Prerequisites: `command -v git`, `command -v curl`, `docker compose version` and `docker info` all succeed. 2. `curl http://localhost:7777/healthz` returns {"status":"ok","schema_version":"v1.0"}. 3. The token is non-empty: `(cd && docker compose exec -T kvendra-platform cat /data/auth.token)`. 4. `claude mcp list` shows `kvendra-platform` connected (registered with `--transport http`, from the user's project directory). 5. `mcp__kvendra-platform__whoami` succeeds and `entity_query` returns. 6. The Skills plugin is installed (`/plugin` lists kvendra-skills). 7. `/kvendra-skills:onboard-project ` scaffolds the project structure (PRJ + components + GLO/STD + a canonical CLAUDE.md) in the local KB. 8. (If installed) The CLI broker responds — remember it is alpha and optional.