Prerequisites & install

A from-scratch install and configuration reference. Every command is taken from the repository's own scripts and docs. Replace placeholders like <org>, <engine>, and your.email@example.com with your own values.

Budget roughly 60–90 minutes for a first install. The canonical path uses uv for the Python toolchain; a venv + pip fallback is documented at the end.

1. System & OS

HEADING OS runs inside a Linux userland. Three supported shapes:

Python version. pyproject.toml declares requires-python = ">=3.11", so Python 3.11 or newer is the floor. uv installs a conforming Python for you — a system Python is not required.

WSL-only constraint (Windows installs) Once inside WSL, keep all work WSL-native — never invoke PowerShell or cmd.exe to work around a Linux problem. Keep all repositories on the Linux filesystem (~/...), never on /mnt/c: the Windows mount is far slower for git/Python and has permission quirks. A PS C:\> prompt means you are in PowerShell; enter Ubuntu with wsl -d Ubuntu.

Windows-only WSL bootstrap (one time, Administrator PowerShell)

wsl --install        # reboot when prompted; create a Linux user + password on first launch

If WSL already exists this reports "distribution already exists" — skip it, then run wsl -d Ubuntu.

2. Python toolchain

pyproject.toml is the source of truth for dependencies; uv.lock hash-pins the transitive set. Use uv, not system pip — recent Ubuntu ships only python3 with no pip, by design. Every script runs as uv run python scripts/....

System packages + toolchain (inside Ubuntu / macOS / Linux)

# system packages
sudo apt update && sudo apt install -y build-essential curl git ca-certificates gh

# Node via nvm (needed for the Claude CLI)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
export NVM_DIR="$HOME/.nvm" && . "$NVM_DIR/nvm.sh"
nvm install --lts

# uv (Python manager — installs Python for you)
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv --version

# Claude CLI
npm install -g @anthropic-ai/claude-code
which claude    # must resolve under ~/.nvm/..., NOT /mnt/c/...

Authenticate inside Linux (Windows logins do not carry into WSL)

gh auth login     # GitHub.com -> HTTPS -> Yes -> Login with a web browser
claude            # pick a theme, then "Claude account with subscription"

Install dependencies (canonical path)

cd <engine>
uv sync               # Python + all dependencies into the project environment

Runtime stack of note (exact pins in pyproject.toml): anthropic, exchangelib (Exchange mail), Telethon (Telegram), playwright, weasyprint + liteparse + markitdown (documents/PDF), the Google API clients, fastapi + uvicorn + watchdog + apscheduler (bridge daemon), plus firecrawl-py, youtube-transcript-api, yt-dlp, replicate, openai, google-genai, langfuse. Dev tooling: pytest, bandit, ruff, pre-commit, detect-secrets, pip-audit.

3. .env configuration

Create your env file from the template at the engine root — it is gitignored and never committed:

cd <engine>
cp .env.example .env

Every variable, with purpose. Feature-gated variables are optional — the feature simply stays dark until the key is set.

VariablePurposeRequired?
ANTHROPIC_API_KEYClaude API key (alternative to a subscription login via the Claude CLI).One of the two
GH_TOKENGitHub token used for all git push / backup auth. Without it, backups cannot authenticate.Required for backup
HEADING_OS_DATAAbsolute path to the data overlay. Only needed if the sibling .heading-os-data is not auto-discovered.Optional
HEADING_OS_TZIANA timezone for all timestamps, calendar, and daemon scheduling. Engine default is UTC.Recommended
OLLAMA_API_KEYAuth for Ollama cloud models. Local embeddings do not need it.Optional
EXCHANGE_EMAIL / _PASSWORD / _SERVER / _USERNAME / _AUTH_TYPE / _TIMEZONEOn-prem Microsoft Exchange mailbox (email + calendar skills).Optional*
TELEGRAM_API_ID / _API_HASH / _PHONETelegram client credentials (/telegram, /viraid).Optional*
PERPLEXITY_API_KEYPerplexity — deep-research web acquisition.Optional
TAVILY_API_KEY / BRAVE_API_KEYWeb search (Tavily primary, Brave Search fallback) for /osint.Optional
FIRECRAWL_API_KEYFirecrawl web scraping.Optional
CONTEXT7_API_KEYContext7 library-docs lookups.Optional
REPLICATE_API_TOKENReplicate image generation (/flux-image, /design).Optional
DEHASHED_* / HIBP_API_KEY / HUNTER_API_KEY / VIRUSTOTAL_API_KEYOSINT data sources.Optional

* Feature-gated: the matching skill stays inactive until the credentials are present.

Never commit secrets All credentials live only in the gitignored .env (or a password manager). The commit hooks and an unbypassable push-time content scan are designed to block secrets, but the first line of defense is you. Never pass git commit --no-verify.

4. Ollama — local semantic recall

Ollama is a local LLM runtime used for on-machine embeddings that power the workspace's semantic recall and memory index. Zero API cost; nothing leaves the machine on the local path.

Model used and why. config/memory-index.yaml sets model: bge-m3 — a multilingual (1024-dim) embedding model used by scripts/memory-index.py and the /recall skill (hybrid dense bge-m3 cosine + BM25 lexical). It is embeddings-only, not a chat model.

# install ollama (official installer)
curl -fsSL https://ollama.com/install.sh | sh

# pull the embedding model used by recall / memory-index
ollama pull bge-m3

# (optional) a cloud reasoning model for /council + /deep-research-advance
ollama pull kimi-k2.6:cloud     # auth via OLLAMA_API_KEY in the engine .env
Cloud-model privacy rule Never route private data (CRM, the ODIN brain, the data repo) through any :cloud model — those prompts leave the machine. The third-party clouds are treated as one privacy tier; cloud is for non-sensitive work only.
First memory-index build is slow — and that is expected A full memory-index.py build embeds the whole corpus through bge-m3 and takes roughly 85 minutes on CPU (~7,300 chunks at ~0.7s each). It is not a hang — it commits per file with live + path [NN%] progress and is resumable; later runs are incremental and fast. Run it with a long/no timeout, ideally in the background. A GPU host cuts the time sharply but is not required.
cd <engine>
uv run python scripts/memory-index.py build      # ~85 min first run, resumable

5. Brave — the automation browser

A dedicated Brave browser with a separate ClaudeCode profile is the canonical browser for all workspace browser work: CDP-driven automation (scripts/browser.py attaches over CDP), cookie extraction (scripts/utils/chromium_cookies.py), yt-dlp --cookies-from-browser brave:ClaudeCode, and the /setup-browser-cookies skill.

Why a dedicated profile, not your personal browser. The automation never uses your personal browser, and never asks you to log in from it — keeping the assistant's authenticated sessions isolated from your own. Why CDP-attach: launching Brave under Playwright's persistent-context mode is unstable (the controlled tab closes immediately); attaching over CDP to an externally launched browser is stable. Never call browser.close() on a CDP-attached session — it kills the whole browser.

# Brave via its official apt repo (Linux/WSL)
sudo apt install brave-browser
which brave-browser     # /usr/bin/brave-browser

Brave is machine-local — not carried by the repos. Reinstall after every fresh clone or relocation (same class as plugins and gitignored credentials).

WSL "open URL" routing (Windows installs)

WSL has no working default browser, and a WSL-native Brave window under WSLg is unreliable for OAuth logins. Route URL opens to the Windows browser via wslview (user-level, no sudo):

xdg-settings set default-web-browser wslview.desktop
xdg-mime default wslview.desktop x-scheme-handler/http x-scheme-handler/https

This makes Claude Code MCP / connector OAuth logins hand off cleanly to the Windows browser. The canonical automation browser stays the CDP-driven Brave (no visible window).

Playwright MCP gotcha (WSL) If you wire the Playwright MCP server, it must launch via an absolute Linux Node + cli.js path, never npx — on WSL npx/node on PATH resolve to the Windows install and the JSON-RPC stdio dies on initialize. (The local /playwright skill uses the Python CDP path and is unaffected.) See Skills, MCP & plugins.

6. VPN preflight (browsing skills)

A runtime gate (.claude/rules/vpn-preflight.md) fires before browsing skills that hit services blacklisting datacenter IPs — YouTube transcripts (/yt-pulse), Google/LinkedIn scraping, some OSINT sources. Datacenter exits (e.g. Mullvad) get blocked; a residential-friendly exit like Proton passes. On Linux the gate can switch directly:

protonvpn-cli connect --cc NL     # residential-friendly exit for YouTube/Google
protonvpn-cli status

On Windows/macOS you switch manually in the GUI. This is a runtime gate, not an install step — it exists so users of browsing skills understand why a run pauses.

7. Git hooks / pre-commit (per clone)

.git/hooks is machine-local and not cloned, so the commit-time secret gate must be armed once per fresh clone:

cd <engine>
pre-commit install           # writes into .git/hooks; run once per clone
pre-commit run --all-files   # optional: run the whole suite immediately
uv run python scripts/install-hooks.py --check   # verify the framework is the active manager

The hooks enforce detect-secrets, bandit, ruff security rules, standard checks, a workspace secret scanner, a real-entity/PII content guard, the engine/data leak guards, and a pip-audit CVE scan on dependency changes.

The commit hook is a warning; the push wall is the wall git commit --no-verify skips every commit hook — never pass it. The authoritative, unbypassable gate is the push-time content scan in scripts/push-all.py (pure code, no skip flag, scans both repos before anything leaves the machine).

8. Plugins (reinstall per clone path)

Claude Code plugins are enabled in .claude/settings.json (travels with git), but the installs are tracked per project path (machine-local, never synced). After any clone or relocation you get "enabled but not installed" until you reinstall under the new path:

cd <engine>
claude plugin marketplace add anthropics/claude-plugins-official
claude plugin install superpowers@claude-plugins-official --scope project
claude plugin install skill-creator@claude-plugins-official --scope project
claude plugin install claude-md-management@claude-plugins-official --scope project
claude plugin install frontend-design@claude-plugins-official --scope project
Gotcha Never use @latest. The form is always <plugin>@<marketplace> (e.g. @claude-plugins-official) — plugin@latest makes Claude look for a marketplace named "latest" and fail. List marketplaces with claude plugin marketplace list.

See Skills, MCP & plugins for the full inventory.

9. The data overlay (two-part topology)

The engine is code only and resolves a separate private data repository at runtime. They sit as siblings under one parent so the engine auto-discovers the data sibling:

~/ai/claude-workspaces/
├── .heading-os/            ← engine (code; eventually public)
├── .heading-os-data/       ← your data (CRM, knowledge, outputs, context — writable)
└── .heading-os-corporate/  ← corporate content (optional; populated by sync-corporate.py)

On a clone without a data overlay, the engine runs on its defaults and *.example.* templates — features needing data stay dark; nothing breaks. If auto-discovery fails, set HEADING_OS_DATA=/abs/path/.heading-os-data in .env.

Create your own data repo (solo deploy, one command)

cd <engine>
uv run python scripts/create-data-repo.py    # creates the repo + prints the HEADING_OS_DATA wiring line

Clone existing repos (managed / migration)

cd ~/ai/claude-workspaces
git clone https://github.com/<org>/heading-os.git .heading-os
git clone https://github.com/<org>/heading-os-data-<slug>.git .heading-os-data
git clone https://github.com/<org>/heading-os-corporate.git .heading-os-corporate   # optional

Backup (human-gated, with [0 0] verification + pre-push secret scan)

uv run python scripts/push-all.py            # commit + push
uv run python scripts/push-all.py --dry-run  # preview only

See Data overlay structure for the full layout.

First-run verification

cd <engine>
uv run python scripts/workspace-health.py
uv run python -c "from scripts.utils.paths import get_data_root; print(get_data_root())"   # prints your data path
claude        # trust the engine folder, then:
/prime

Appendix — venv + pip fallback

The classic route works where uv is unavailable. Note that on stock Ubuntu without pip it will not work out of the box; prefer uv sync.

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt          # production deps
pip install -r requirements-dev.txt      # dev tooling
pytest -q                                # verify the suite