# 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 an encrypted local 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 an encrypted local 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 an encrypted local vault, exposing seven primitives (git, github, npm, pypi, aws, http, shell) plus an opt-in, audited unsafe.raw_token escape hatch. Sensitive operations route through the broker, so by default the orchestrating model does not see 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`. ## Design decisions and manuals - Each component's design decisions and full manual are published at https://app.kvendra.cloud/hub/ (kvendra-platform, kvendra-cli, kvendra-skills). ## Licenses (open-core) - Platform: AGPL-3.0-only - 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.