Kin / Maintainers
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.
Read as MarkdownThe 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; DS4/Qwen live gates use the same stable Tailnet contracts from either machine. |
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:
uv sync— creates.venv, installs all deps fromuv.lock.uv tool install --editable .— installs thekincommand on$PATH.- Copies
examples/settings.toml.shared-template.toml→~/.kin/settings.toml(only if not already present). Secrets stay out — enter viakin→/providers. - Appends a one-liner to
~/.bashrcthat auto-attaches (or creates) a tmux session namedkinon 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 dedicated build worktrees — separate git worktrees
of the same repo, checked out at the deploy SHA. The deploy tasks operate
there; your dev working tree at ~/kin/ never gets reset --hard. The
worktrees share git history (one .git/) but have independent file state.
~/kin/ ← dev tree, on branch dev, your WIP commits live here
~/srv/web/kin-outpost/ ← outpost deploys: detached HEAD, reset each deploy
~/srv/net/rig-vm/ ← rig/rig-vm deploys: detached HEAD, reset each deploy
The deploy tasks (task outpost:deploy-dev, task rig:vm-deploy-dev, …)
create their build worktree on first run, then
fetch + reset --hard origin/<branch> it each subsequent run. Your dev tree
is untouched — the silent-wipe footgun is gone.
The rig worktree runs ONLY the rig services and carries two untracked,
reset-surviving deployment files under container/: a
compose.override.yaml that splits the compose project off the tracked
name: outpost (keeping the pre-split volume names and joining the outpost
network so the dashboard still reaches rig), and a .env pinning
OUTPOST_BIND_IP to the tailnet address (net tier). The rig deploy tasks
therefore run docker compose with auto-discovery from that directory —
never -f — so the override participates, and they only fall back to their
BIND_IP variable when .env does not set the bind address.
Container secrets (oauth-client-id, oauth-client-secret, oauth-cookie-secret,
kin-settings, kin-credentials) 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 public get docroot is durable host state, independent of the disposable
build worktree:
~/.local/share/kinra-publish/get/ # nginx source by default
~/srv/web/kin-outpost/container/installer-www/ # frozen legacy emergency mount
Before the first deploy carrying this topology, copy the currently live legacy root without exposing the new directory:
task outpost:publish-init
The command refuses an existing destination, validates the old docroot, copies
it atomically, and does not touch nginx. This explicit step preserves every
live Kin URL before Paddock is staged or the bind source changes. If neither
root exists, a dev deploy refuses to create a public site and directs the
operator to deploy verified main; verified main may bootstrap a genuinely
new host.
On task outpost:deploy-main, before compose up:
uv run --group docs mkdocs buildrefreshes the build-worktreesite/served as docs.kinra.ai.scripts/publish-kin-get.shatomically replaces only Kin’s literal allowlist in the durable root:/,/kin/,/paddock/index.html,/assets/, the Kin/Outpost installers and their checksums. It never deletes or rewrites Paddock’s installer, manifest, assets, or immutable releases.- Only when HEAD carries the release tag matching
pyproject.toml(v<version>),uv build --wheelpublishes the Kin release channel: the real PEP 427 wheel, stablekin-latest.whlalias and checksum, static PEP 503 index, andversion.jsonlast. The newest five wheels remain available for rollback. An untagged main deploy leaves that channel pointer unchanged.
Paddock publishes independently into the same durable root. Its publisher owns
only /paddock/install.sh, its checksum, /paddock/version.json,
/paddock/assets/, and /paddock/releases/; it verifies immutable paths and
activates version.json last. Neither product release requires the other
repository to release or become public.
compose up --profile publish adds two nginx:alpine services:
get on ${OUTPOST_BIND_IP}:7018 and docs on :7019. The get bind source
defaults to the durable path above and is configurable with
KINRA_GET_PUBLISH_ROOT. For an emergency rollback, set that variable to the
absolute legacy worktree path and redeploy; Kin recognizes that mount as frozen
and skips publication. Remove the override to return to the durable root.
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 kin-outpost). A stranger’s
outpost-install.sh runs plain compose up with no profile, so these publishers
never start on a non-kloud host. Dev deploys do not mutate either public
docroot; a missing docs root also fails with verified-main recovery guidance.
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:
- CLI flag (
kin --base-url ... --model ...) — one-off, for a single launch. - Env var (
KIN_BASE_URL=... KIN_MODEL=...) — set in shell for a session. ~/.kin/settings.toml— durable per-machine config.
The checked-in shared template now describes the current private topology:
DS4 Responses at https://kinra-inference-001.mist-hake.ts.net:8001/v1 and one-slot Qwen
vision/utility at http://100.87.84.40:18081/v1. Both machines can use it
unchanged on the Tailnet. The hosted https://api.kinra.ai/v1 profile is the
authenticated client/Outpost route and should be installed with kin connect
so its bearer lands in credentials.toml, never this shared file.
The Taskfile verbs
All under the existing OUTPOST_SSH (default kloud) + OUTPOST_DIR
(default ~/kin) 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/credentials.toml via Kin’s key prompts, 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 # 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.