Skip to content

Multi-machine dev setup (MacOS ↔ kloud)

The repo is designed so you can switch between MacOS (laptop) and kloud (Docker host) without losing state. The same commands work on either side; each machine owns a role and the seam between them is just git + SSH.

Roles

Machine Primary role Why
MacOS Harness + TUI iteration. The day-to-day dev loop. Best TUI rendering, all local tools, fastest iteration on src/kin/harness/ + src/kin/tui/.
kloud Outpost + container + live-verifies. The Docker-host seat. task outpost:build/task outpost:up and task live-drive run against the real Docker host + the real tailnet vLLM — coverage the laptop can't match.

Either machine can do the other's job — task run works fine over SSH + tmux, task verify is fast on both — but each is best at its lane.

Bootstrap a fresh machine

From inside the checkout:

./scripts/setup-dev.sh     # idempotent — safe to re-run

setup-dev.sh is the dev-machine bootstrap — it assumes you already have a checkout and want the tmux/.bashrc dev-loop plumbing. It's a different script from the public one-liner at get.kinra.ai (scripts/install.sh), which clones the repo itself and skips the tmux step — see Install for that consumer-device path.

Does four things, none of which need sudo:

  1. uv sync — creates .venv, installs all deps from uv.lock.
  2. uv tool install --editable . — installs the kin command on $PATH.
  3. Copies examples/settings.toml.shared-template.toml~/.kin/settings.toml (only if not already present). Secrets stay out — enter via kin/providers.
  4. Appends a one-liner to ~/.bashrc that auto-attaches (or creates) a tmux session named kin on every SSH login.

The verify gate (details):

task verify    # thousands of headless asserts, no model/terminal needed

kin doctor is a complementary, lighter-weight check for a freshly bootstrapped machine — it verifies the install itself (uv/kin/git on PATH), that a provider actually resolves (mirroring make_backend's config chain), and that the resolved endpoint is reachable, without needing a checkout or task at all. See Install.

Re-running the script on a machine that's already set up is a no-op — each step checks for its precondition.

The build-worktree split (deploy safety)

The kloud checkout has a second worktree at ~/kin-textual-build/ — a separate git worktree of the same repo, checked out at the deploy SHA. The deploy task operates there; your dev working tree at ~/kin-textual/ never gets reset --hard. The two worktrees share git history (one .git/) but have independent file state.

~/kin-textual/        ← dev tree, on branch dev, your WIP commits live here
~/kin-textual-build/  ← detached HEAD at the deploy SHA, gets reset each deploy

The deploy task (task outpost:deploy-dev) creates the build worktree on first run, then fetch + reset --hard origin/<branch>es it each subsequent run. Your dev tree is untouched — the silent-wipe footgun is gone.

Container secrets (oauth-client-id, oauth-client-secret, oauth-cookie-secret, kin-settings) live in the dev tree under container/secrets/ — gitignored fail-closed, mode 600, only you drop them. The deploy script symlinks container/secrets/ from the dev tree into the build worktree before building, so docker compose resolves the same paths regardless of which worktree owns the compose file.

The publish profile (get.kinra.ai + docs.kinra.ai)

This section documents Blake's own reference deployment (kloud + kloud-vps) as a worked example — substitute your own host/SSH-alias/DNS if you're standing up a similar pair of public docroots elsewhere. For the consumer-facing side of this (what a visitor to get.kinra.ai actually runs), see Install; for the Outpost container itself, see Outpost.

The same deploy also builds the two public static docroots in the build worktree, before compose up (any build failure aborts the deploy before anything is published):

  1. uv run --group docs mkdocs buildsite/ (served as docs.kinra.ai).
  2. uv build --wheel + assemble container/installer-www/ — the extras/install-page/ page, scripts/install.sh + scripts/outpost-install.sh, the wheel (real PEP 427 filename for uv plus a stable kin-latest.whl curl alias), a static PEP 503 simple/kin/ index, and a .sha256 per artifact (served as get.kinra.ai).

compose up then runs with --profile publish, which adds two nginx:alpine services from container/compose.yaml: get on ${OUTPOST_BIND_IP}:7018 and docs on :7019 (both read-only mounts of the docroots above). The kloud-vps Traefik routes get.kinra.ai / docs.kinra.ai to those ports over WireGuard (routers kin-get / kin-docs in ~/kloud/traefik/dynamic/routes.yml, mirroring the kin-outpost block). A stranger's outpost-install.sh runs plain compose up with no profile, so those services never start on a non-kloud host. All three docroot dirs (site/, dist/, container/installer-www/) are gitignored and survive between deploys (the reset --hard leaves untracked dirs alone) — each deploy just rebuilds them.

Per-host overrides

The shared template is the baseline — the non-secret defaults both machines start from. Per-host overrides happen three ways, in precedence order:

  1. CLI flag (kin --base-url ... --model ...) — one-off, for a single launch.
  2. Env var (KIN_BASE_URL=... KIN_MODEL=...) — set in shell for a session.
  3. ~/.kin/settings.toml — durable per-machine config.

So MacOS and kloud can both start from the same template, then diverge on a single KIN_* env var — e.g., MacOS uses the tailnet's MagicDNS (kin-one:8000), kloud uses the tailnet IP directly, or a different model alias on one side.

The Taskfile verbs

All under the existing OUTPOST_SSH (default kloud) + OUTPOST_DIR (default ~/kin-textual) vars — so they pick up the same overrides as the deploy task:

Task What it does
task kloud:setup Run scripts/setup-dev.sh on kloud over SSH (bootstrap a fresh box).
task kloud:dev SSH into kloud, attach (or create) the kin tmux session.
task kloud:verify Run task verify on kloud — headless suites against the real Docker host.
task kloud:status One-shot summary: git state, active tmux sessions, kin tool install.

What stays out of this repo

This page is the boundary. Per-host concerns don't belong here:

Concern Where it goes
~/.ssh/config (aliases, multiplexing, keys) Per-host shell/dotfiles
~/.zshrc, aliases, completions Per-host shell/dotfiles
~/.gitconfig includeIf host Per-host git config
mosh, Tailscale SSH, devcontainers Per-host preference
VSCode / Neovim configs Per-host IDE
BRAVE_API_KEY, provider secrets ~/.kin/settings.toml [provider_keys] OR env vars

If you want any of those synced across machines, they go in a separate dotfiles repo — keep the dev-workflow concerns here, the personal-config concerns there.

Flipping between machines

A typical day:

# Morning on MacOS — harness work
task run
task verify
git push origin dev

# Afternoon on kloud — Outpost-specific work
task kloud:dev            # ssh + tmux auto-attach
cd ~/kin-textual          # dev tree, safe from deploys
task outpost:build        # real Docker host image build
task outpost:up           # smoke-test the image locally
git push origin dev

# Back to MacOS — pick up the kloud commits
git pull --ff-only
task run

The repo state stays identical because both machines are symmetric peers of origin/dev. Whoever makes the commit pushes; whoever pulls next gets it.