Skip to content

Latest commit

 

History

History
1537 lines (1278 loc) · 79.1 KB

install.md

File metadata and controls

1537 lines (1278 loc) · 79.1 KB

Installing and Running Kiro Crew

This guide covers every way to install Kiro Crew, the first-run setup, how to verify the install, and how to troubleshoot the failures that actually happen.

Builds use plain pip + npm/Vite + pytest, driven by the repo-root Makefile. There is no proprietary build tooling.

Platforms: macOS, Linux, and Windows. macOS and Linux use the Makefile / setup.sh paths below. Windows supports both the signed desktop installer and a native Python source install (.\make.ps1 build, launched via .\.venv\Scripts\python.exe -m kiro_crew gateway); all POSIX-only process, signal, file-lock and metrics calls route through kiro_crew.platform_compat. See windows-install.md for the Windows walkthrough.



Prerequisites

Requirement Needed for Floor
Python Backend >= 3.12 (requires-python in pyproject.toml; make build provisions a 3.12 .venv by default)
Node.js + npm Building the dashboard >= 22.12.0 (website/package.json engines; Node 24 LTS recommended); on x86_64 Amazon Linux 2, the glibc-217 fallback installs Node 24 because official builds need a newer glibc (no glibc-217 arm64 build is available)
kiro-cli Driving the LLM Required; see below

Node is only needed to build the dashboard. The prebuilt wheel, the DMG, the AppImage, and the Linux .deb / .rpm packages all ship the dashboard already bundled, so end users of those artifacts need neither Node nor a compiler.

Default agent backend: kiro-cli

Kiro Crew drives the default agent through kiro-cli over the Agent Client Protocol (ACP). Other ACP backends can be selected with agent.acp_backend. A fresh configuration uses the KAS harness: it runs through kiro-cli's own ACP relay (kiro-cli acp --agent-engine v3) when kiro-cli is installed and can sign in, and through Kiro Crew's own bundled KAS engine otherwise. The route: line in the KAS backend section of kirocrew doctor names the one this host uses. Setting agent.acp_backend to kiro keeps kiro-cli: it still uses the relay when kiro-cli offers v3, and kiro-cli acp --agent <name> when it does not.

Install kiro-cli per its own docs, put it on your PATH, and log in:

kiro-cli login

With agent.acp_backend set to kiro, a missing kiro-cli makes spawning a session fail with kiro-cli not found (<searched directories>), followed by the install link and the KIROCREW_KIRO_BIN override for a binary outside those directories; the default backend runs the bundled engine instead. On the first dashboard launch the Set up Kiro page detects the missing prerequisite, shows copyable installer commands for the host's platform and the login commands to run yourself, and offers a Use other coding agents picker so another configured harness can finish setup instead. Kiro Crew does not run the install or the login itself. kirocrew doctor reports both the binary and the login state.

Embeddings: nothing to install

Semantic memory and the knowledge library need no setup step. Embeddings run in-process through the vendored llama-cpp-python runtime, so there is no separate server and no HTTP hop. On first start the gateway downloads the Qwen3-Embedding-0.6B GGUF (about 610 MB) in the background over HTTPS, verifies it against a pinned sha256, and installs it under ~/.kiro/crew/models/.

While the model is absent (first boot, download in flight, or a failed download), memory search degrades to keyword/FTS search and picks embeddings up automatically once the model lands, with no gateway restart. Two escape hatches exist for mirrored or airgapped installs:

  • KIROCREW_EMBED_MODEL_URL (or memory.embed_model_url) points the download at a mirror. The sha256 pin still verifies whatever it fetches.
  • KIROCREW_EMBED_MODEL_PATH (or memory.embed_model_path) runs a local GGUF of your own instead. In that mode the default model is never downloaded.

memory.embedding_provider accepts only llama_cpp; any other value in an old config is coerced to it on load.

Global V1 retains its existing session-start memory retrieval. Crew Member V2 injects current persona, permanent rules and admitted project guides every turn; facts and past experiences are retrieved through the explicit memory_recall tool. All stores share one model and inference worker. The interactive memory.embedding_threads default is 4, capped at one core below the CPUs the process may run on -- which a CPU-set restriction (--cpuset-cpus, taskset) narrows below the host's core count -- and never below one thread, so the event loop keeps a core wherever there is one to spare; where the process may use 12 or more CPUs the default is 6; memory.embedding_bulk_threads remains 1. The value 4 means that default policy, so 4 cannot be pinned where the process may use 4 or fewer, or 12 or more CPUs (there a 4 runs 6; set 3 or 5 to stay low); any other explicit setting is honored up to that same count. Bulk threads may be 0 to inherit the normal setting. Background jobs share the configured bulk duty cycle, while waiting interactive queries take priority. A full inference queue leaves new rows pending and permits keyword retrieval, so additional members do not create unbounded native work.

Private V2 execution requires agent.sandbox=auto and working Linux/WSL namespaces or macOS outer Seatbelt. Native Windows, unconfined execution, unsupported MCP backends and Kiro internal delegation refuse private member turns with a reason. The owner can still manage memory in the dashboard. A member's memory store is chosen when the member is created and cannot be rebound afterwards. Existing members, including the default assistant selection, keep their declared V1 memory after upgrading. To give a crewmate private V2 memory, create a new crew member: new members receive V2 automatically, starting empty, and V1 stores remain available for an explicit copy of selected starting knowledge. Check the execution requirements above first. A V2 member never falls back to V1 on failure.

Install paths

Which path on Linux

Start with the one-line install below. It is the smoothest Linux path and the one that needs the fewest decisions: it puts kirocrew on your PATH at a stable location, which is what makes kirocrew service install — and therefore the AppArmor profile the agent sandbox needs on Ubuntu 23.10+ — reachable in the first place. You work in the dashboard through your browser at localhost:5476.

Install a desktop package (.deb / .rpm) in addition when you want the things only the Electron shell provides: an application-menu entry and icon, a native window with persisted geometry, a dock/taskbar badge, a system-wide hotkey to summon the dashboard, a gateway that starts and stops with the app, and in-app updates. The packages install to a fixed path under /opt, so the same PATH and AppArmor mechanics that make the one-line install work apply to them too.

The AppImage stays available for hosts where you cannot install a system package (no root, an unsupported distro). It needs FUSE present, and it provides no kirocrew launcher on PATH. On a distro that restricts unprivileged user namespaces, keep the AppImage at a durable path and attach the manual AppArmor profile described in the sandbox section; moving or renaming the AppImage breaks that path binding. Prefer a package where you can.

You want Use
The dashboard, a terminal, scheduled work, a server One-line install (a)
A desktop app on Debian/Ubuntu .deb (d)
A desktop app on Fedora / RHEL / CentOS Stream / Amazon Linux 2023 .rpm (d)
A desktop app with no root and no package manager AppImage (d)
A container host Docker (guide)
To work on Kiro Crew itself (follows a branch; main is Nightly) From source (b)

a. One-line install (fastest)

Installs a prebuilt, sha256-verified wheel from the release CDN. No clone, no npm, no local build:

curl -fsSL https://download.crew.kiro.dev/cli.sh | sh

Once the wheel is in, the installer runs the new kirocrew kas install to download the built-in KAS engine this release pins (about 55-70 MB for your platform). If that download fails, for example on a host with no access to the CDN, the install still succeeds and the installer prints the command to retry. Pass --no-kas (or set KIROCREW_INSTALL_KAS=0) to skip it:

curl -fsSL https://download.crew.kiro.dev/cli.sh | sh -s -- --no-kas

stable is the default channel. Track a faster one, or pin an exact version:

curl -fsSL https://download.crew.kiro.dev/cli.sh | sh -s -- --channel insider
curl -fsSL https://download.crew.kiro.dev/cli.sh | sh -s -- --version 0.6.0

stable suits everyone, insider is for power users who want features days to weeks early and accept the new bugs that arrive with them, and nightly is untested main HEAD for us and contributors. The Release channels table has the full comparison; re-running the installer with a different --channel is how a CLI install moves between lanes.

Pinning an exact version

The minimum pinnable release is 0.1.2. --version resolves an immutable per-version signed manifest, and a pinned install fails closed when that manifest does not exist. Manifest signing was enabled during the 0.1.x line, so 0.1.0 and 0.1.1 are published but carry no signed manifest and cannot be installed by the installer. Every release from 0.1.2 onward can be pinned.

These two releases will not be backfilled. Signing an already-published digest today would create a fresh attestation for bytes that no signing pipeline produced, which asserts a provenance the project cannot re-establish after the fact. SECURITY.md already limits active support to the latest release, so the trust surface would widen for two releases that are several minor versions behind current stable and are supported by nobody. Their artifacts stay published and their SHA256SUMS stays fetchable for archival inspection, but the installer has no checksum-only path, so it will not install them.

If a rollback runbook pins 0.1.0 or 0.1.1, change it to 0.1.2 or later, or drop --version to take the current stable release. A pinned run that cannot resolve a manifest prints this policy and that remedy rather than only the URL it tried, because the same failure also covers a version string that was never published at all.

The installer verifies the wheel's digest against the signed manifest and refuses to install on a mismatch; there is no checksum-only fallback. It uses pipx when available, otherwise it creates a managed venv beside the data home (~/.kiro/crew-venv, override with KIROCREW_VENV) and symlinks ~/.local/bin/kirocrew at it. The venv is deliberately not nested inside the data home, so no whole-home operation can ever delete the live interpreter. The selected channel is recorded to ~/.kiro/crew/channel.

Updates to a managed venv (kirocrew update, an approved in-app update, and the gateway's automatic update) build each new version as its own tree beside it, crew-venv-<version> (about 350 MiB with its dependencies), and switch the crew-venv-current link to it. After each update the engine keeps the current tree, the previous one (a recovery target), and any tree a running kirocrew process still uses; older ones are removed. A direct installer run still rebuilds the fixed crew-venv in place (and a pipx install reinstalls into its own venv), so a gateway that is running during the re-run keeps the old version loaded while it reads the new files. The installer never restarts it: when it finds one running (an active kirocrew systemd unit, the dev.kirocrew.gateway launchd agent, or a kirocrew gateway process holding the data home's gateway.lock), it ends with a warning that names the restart command for that setup, such as systemctl --user restart kirocrew. Run it straight away. On a host that restricts unprivileged user namespaces and uses the kirocrew-userns AppArmor profile, run kirocrew service install again after every update so the profile follows the new launcher; the gateway's automatic update waits for you instead of applying there, and kirocrew doctor reports the attachment under Sandbox.

The installer provisions its own Python by default instead of depending on the system one: it downloads a SHA-256-pinned uv binary (an installed uv on PATH is deliberately never executed -- PATH commonly leads with agent-writable directories), then installs a python-build-standalone CPython 3.12 into a user-owned directory beside the data home (~/.kiro/crew-python, override with KIROCREW_PYTHON_DIR). No package manager, no sudo, and the prebuilt interpreter runs on old-glibc distros (CentOS 7) whose base repos never reach 3.10. Pass --system-python (or set KIROCREW_MANAGED_PYTHON=0) to run on a system Python 3.12+ instead — the choice is sticky: it is recorded in the data home (python-mode, next to channel), so later installer runs keep it without the flag; opt back in with --managed-python. Installs that predate the managed default migrate onto it at their next direct installer run — on a managed venv, an update applied from the dashboard, by the CLI's update command or by the gateway's automatic update keeps its current interpreter — unless they recorded the --system-python opt-out. A re-run resolves the interpreter through the pinned uv binary; an already-provisioned interpreter is reused rather than re-downloaded. If the managed interpreter cannot be downloaded and a usable system Python 3.12+ exists, the run falls back to it with a warning (and retries the managed default next time); air-gapped hosts can point KIROCREW_UV_URL at a mirror of the uv release tree and UV_PYTHON_INSTALL_MIRROR at a mirror of the interpreter archives — the pinned SHA-256 digests are enforced either way. The signed installer never pipes an unsigned third-party script into a shell: uv is fetched as a tarball and verified against pinned digests, exactly like the wheel itself. On a terminal the wheel download draws curl's progress bar and the slow steps (venv creation, pip) draw a single live progress line. KIROCREW_INSTALL_PLAIN=1 turns that off, which is also what a piped or logged run gets: the download is silent, a slow step prints a still running line every 30 seconds, and each step ends with one done line. When it finishes it prints the next step: kirocrew gateway to start now, or kirocrew service install to run it as a service.

Dependencies are installed from prebuilt wheels only (pip --only-binary=:all:), so the install never needs a C compiler or -dev headers on the host. pip picks the newest release of each dependency that publishes a wheel the host can run; on a host where no release does (its glibc is older than every candidate's manylinux floor, or the architecture has no wheel), the installer stops before any build starts and names the platform and the packages, instead of failing deep inside a compiler run. Use a newer host, or — on a host that does have a toolchain and the headers — opt back into compiling with KIROCREW_ALLOW_SOURCE_BUILDS=1. The same policy applies to install.sh's editable install (the dependency set only; the local kirocrew tree is still built) and to the update engine that builds the shadow venv on a managed-venv install. The opt-in is not remembered. To compile a dependency during an update, run KIROCREW_ALLOW_SOURCE_BUILDS=1 kirocrew update from a shell where the toolchain is on PATH: that command builds with the shell's own environment, while the gateway's automatic and approved updates run their build steps on the trusted system PATH only.

b. From source (development)

Build the dashboard, install the backend into a local virtualenv (.venv), and run the gateway straight out of src/:

make build                                   # npm build + editable backend install into .venv
PYTHONPATH=src python -m kiro_crew gateway   # -> http://localhost:5476

This is the contributor layout, not an end-user install. make build and install.sh both build the clone they run in, so the install follows the clone's checked-out branch rather than a release lane. On main that is the Nightly lane: the version reads 0.X.0.devYYYYMMDD, ahead of the Stable release the desktop app and the one-line install follow. install.sh says which lane it is building, at its start and at its end.

Updating follows the same branch. kirocrew update (and the dashboard Update button) fetches origin/<branch> and hard-resets the clone to it when the clone is only behind. A clone that is both ahead and behind — local commits origin/<branch> lacks, plus new ones from it — is refused, because the reset would discard the local commits; kirocrew update --force discards them deliberately. A clone that is only ahead is reported up to date. A detached checkout, such as a release tag, is on no branch: kirocrew update moves it to the first of main, mainline and master that origin has, and refuses when origin has none of them. After the reset the update rebuilds the dashboard (npm ci + npm run build in website/) and writes that output to logs/frontend-build.log in the data home. If the build fails, the update still installs the new revision's dependencies, then stops before restarting, names that file, and the gateway keeps running its current version: a restart would pair the new backend with the previous dashboard bundle. The checkout already holds the new revision, so a second update reports it up to date: fix the cause the log names, then run the frontend and stage steps below before restarting. A clone with no website/ or no npm skips the build and keeps its shipped bundle, as before. To follow a release lane on a laptop or server, use the one-line install: it updates from the channel's signed release feed and has no git history to diverge.

On Windows the same targets run through make.ps1, because make is not part of a Windows install and the Makefile's recipes are POSIX-shaped (.venv/bin/pip, rm -rf, bash ensure-*.sh):

.\make.ps1 build                             # same steps, same artifacts
$env:PYTHONPATH="src"; .\.venv\Scripts\python.exe -m kiro_crew gateway

The venv interpreter is named explicitly rather than a bare python: the dependencies live only in .venv, and on Windows a bare python resolves to the system interpreter (or the Microsoft Store alias stub), which would fail at import. .\.venv\Scripts\Activate.ps1 first is the other way, after which python and kirocrew both resolve inside the venv.

The two drivers expose the same target set, and test/test_build_target_parity.py fails the build if one gains a target the other lacks. Differences are confined to what the platform forces: a Windows venv puts its executables in .venv\Scripts\, and the macOS-only resign-macos-libs.sh step has no Windows counterpart.

make build runs three steps:

  1. frontend: npm ci (or npm install) + npm run build in website/, and installs website/electron's own deps last — it is a separate npm package the website/ install never reaches, and npm test in website/ needs it.
  2. backend: creates .venv and runs an editable install with the dev extra (pip install -e ".[dev]").
  3. stage: .venv/bin/python -m kiro_crew.frontend stage . makes website/dist the served src/kiro_crew/static/dist: a link to website/dist, or for an edition a link to a fresh private copy. It waits at most 30 s for the staging lock a Kiro Crew build holds. make wheel stages the same way; make backend-bin does not, since its bundle copies website/dist itself, so it needs no Python for this. make frontend alone does not stage either: a checkout whose static/dist is still a real directory keeps serving that old copy until a stage turns it into the link. A gateway already running when that happens, one whose build routes were resolved from the directory, needs a restart; the stage says so.

Both targets bootstrap their toolchain first (ensure-node.sh, ensure-python.sh) and fall back to whatever is on PATH if that fails. The backend target refuses to build a venv from an interpreter older than 3.12, matching pyproject.toml's requires-python, rather than letting dependency resolution backtrack or the package fail at import. ensure-python.sh also skips any interpreter whose throwaway --without-pip venv cannot run python, such as a shared-libpython build behind a launcher wrapper (Toolbox's kirocrew Python), and moves on to the next candidate or mise.

make.ps1 resolves the same toolchain but installs none of it: the bootstrap scripts' install paths are curl … | sh, so on Windows it searches (py launcher first, then PATH, skipping the Microsoft Store alias stub) and prints the winget command to run if nothing usable is found. It honors the same <data-home>/python-bin and node-bin-dir markers those scripts record. The desktop and backend-bin targets are the exception: they delegate to packaging/build-desktop.sh, which provisions its own uv and python-build-standalone interpreter on every platform.

After the backend target runs, bin/kirocrew resolves its real install root, sets KIROCREW_PROJECT_DIR, and delegates to .venv/bin/kirocrew. That console script comes from the editable package metadata (kiro_crew._bootstrap:main), so the virtual environment makes src/kiro_crew importable without the wrapper modifying PYTHONPATH; caller-provided entries pass through unchanged.

Any CLI subcommand works the same way, for example PYTHONPATH=src python -m kiro_crew setup or ... doctor.

The equivalent by hand:

git clone https://github.com/kirodotdev/KiroCrew.git
cd KiroCrew
cd website && npm install && npm run build && cd ..
pip install -e ".[dev]"      # contributor tooling, matching `make backend`

c. Self-contained pip wheel

Produce a wheel that bundles the pre-built dashboard, then install it anywhere with a suitable Python:

make wheel                # builds the frontend, then python -m build --wheel -> dist/
pip install dist/*.whl
kirocrew gateway          # -> http://localhost:5476

The published wheel is tagged py3-none-any and carries the supported platforms' vendored llama.cpp libraries in its package data, so one wheel serves every supported OS and architecture: dist/kirocrew-<version>-py3-none-any.whl (for example kirocrew-0.1.2-py3-none-any.whl). The dashboard is folded in by the custom BuildWithFrontend build step in setup.py, which also bundles CHANGELOG.md so the dashboard's changelog view works on a wheel install with no source tree.

The pip install name is kirocrew; the import package is kiro_crew.

Installing a PUBLISHED wheel with pip

Kiro Crew is not on PyPI, so pip reaches it through the release CDN. Two forms are published, and they serve different needs:

# 1. Track a channel. A PEP 503 simple index per channel, so pip resolves the
#    newest version itself. --pre is required: every published version carries a
#    prerelease suffix on nightly and insider.
#    NOTE: --extra-index-url ADDS the channel index beside default PyPI, so pip
#    pools `kirocrew` candidates across both and a higher-versioned public-PyPI
#    package of the name would win. Prefer form 2 (or verify the installed
#    artifact) where that exposure matters.
pip install --pre kirocrew --extra-index-url https://updates.crew.kiro.dev/feed/stable/simple/

# 2. Pin one exact wheel by hash. Every version directory publishes a SHA256SUMS
#    file beside the wheel; take your wheel's hash from there. pip verifies it and
#    consults no index for Kiro Crew itself — so there is no name to squat.
pip install "https://download.crew.kiro.dev/cli/stable/<version>/kirocrew-<version>-py3-none-any.whl#sha256=<sha256>"

Swap stable for insider or nightly in either URL. The two names split by class as a convention — updates.crew.kiro.dev for mutable pointers and indexes, download.crew.kiro.dev for the bytes — and today both alias the same distribution, which is why KIROCREW_CDN_BASE (or cli.sh --cdn) overrides both at once. Use the documented name for each class rather than relying on the aliasing.

Form 2 pins a byte-exact artifact and consults no index for the kirocrew name, so it is the one to use where a public-PyPI squat of the name is a concern, or when a deployment must pin a byte-exact artifact anyway — a locked requirements file, an airgapped mirror, or a reproducible image build. The in-place upgrade command Kiro Crew emits for a plain-pip install uses the form-2 pinned wheel for exactly this reason, and when it cannot fetch or verify the signed wheel it reports that and points at the channel's artifact directory rather than fall back to the form-1 name-resolving command. Form 1 tracks a channel hands-free when that exposure is acceptable.

Installed console script:

Command Entry point
kirocrew kiro_crew._bootstrap:main

pyproject.toml's [project.scripts] declares kirocrew and nothing else. Because a [project] table exists, setuptools reads the entry points from there and ignores setup.cfg's console_scripts, so kirocrew is the only command installed on PATH.

Optional extras. Kiro Crew is not on PyPI, so pip install "kirocrew[voice]" cannot resolve — install an extra's own distributions into the environment that runs the gateway instead (e.g. pip install 'boto3>=1.34,<2' 'amazon-transcribe>=0.6.3,<1' 'pywhispercpp>=1.5,<2' for voice). The dashboard prints the exact command, already pointed at the right interpreter.

Extra Adds For
voice-aws boto3, amazon-transcribe AWS Transcribe provider without the local recognizer
voice voice-aws, pywhispercpp Full speech-to-text transcription
otlp opentelemetry-exporter-otlp-proto-http OTLP/HTTP metrics export. Installing it does not enable egress; that still needs an explicit telemetry.otlp_endpoint
perf py-spy Out-of-process profiling (kirocrew perf sample --pid). The in-process sampler needs nothing extra
teams PyJWT[crypto] Microsoft Teams channel (validates the inbound Bot Framework RS256 JWT)
whatsapp neonize QR-linked WhatsApp channel
feishu lark-oapi Feishu/Lark long-connection channel
bedrock boto3 Amazon Bedrock Knowledge Base source (bedrock_kb)
dev pytest, black, isort, flake8, mypy, ... Contributor tooling; what make build installs

make desktop and make backend-bin need no extra: both run packaging/build-desktop.sh, which provisions a python-build-standalone interpreter and pip-installs the project into it.

d. Bundled desktop app

A double-clickable app that embeds a python-build-standalone (PBS) interpreter plus uv-installed deps inside an Electron shell. End users need no Python, pip, npm, or Node:

make desktop

Output is a DMG (plus a zip) on macOS, an AppImage plus a .deb and an .rpm on Linux, and an assisted NSIS Setup.exe on Windows, under website/electron/dist/. The macOS DMG opens to a branded drag-to-Applications layout carrying the opening animation's artwork. The Windows wizard keeps native controls and its per-user default while carrying matching Kiro Crew artwork through its sidebar and header. Its Finish page links to the external Kiro CLI setup guide and names the login command required by the default agent before offering to launch Kiro Crew. On macOS the default is ONE universal DMG: the Electron shell is lipo-merged, and the backend, which cannot be lipo-merged, ships as two complete PBS trees selected at launch by process.arch. The x86_64 backend is built under Rosetta 2, so a universal build needs an Apple-Silicon host; UNIVERSAL=0 forces a faster host-arch-only build. Linux is always host-arch, and the three Linux formats come from one backend tree packaged three times.

The published, signed apps (prebuilt downloads) update themselves, and the gateway bundled inside them, through the app's own updater. By default a new release downloads in the background and installs the next time you quit the app (by default closing the window only hides it to the tray; quit from the tray or the menu bar, or turn off Keep running in the background after closing the window in Settings → Display → View → Desktop app so the close button quits). To be asked first, turn off Install app updates automatically on the About page (an Update the gateway automatically switch beside it sets only an attached gateway's auto_update); the app then offers each release and downloads it when you click Download, in the update popup or on the About page. A locally built app is stamped ahead of the stable channel, so on stable it gets no update until a newer release ships. The updater is also off when the app runs from the DMG or another read-only volume, from a macOS translocated copy (move it to Applications), as an AppImage in a directory it cannot write, as an unknown package format, under a bare EXTERNALLY-MANAGED marker, or as an unpackaged build; About then says automatic updates are unavailable, or managed elsewhere, instead of showing the switch. Details: release.md → Client auto-update and Updates.

Installing a Linux desktop package

sudo apt install ./KiroCrew-x86_64.deb     # Debian, Ubuntu
sudo dnf install ./KiroCrew-x86_64.rpm     # Fedora, RHEL, CentOS Stream, AL2023

Either one installs to /opt/KiroCrew, registers the application-menu entry and MIME database, refreshes the icon cache, links /usr/bin/kirocrew-desktop, and — on a host whose AppArmor supports the bundled profile — installs and loads the userns profile the agent sandbox needs, so no manual sandbox install-profile step is required. The fixed install path is what makes all of that durable.

Updates arrive through the app's updater described above (or Check for updates on the About page), which hands the package to dpkg / rpm. That needs root, so expect one elevation prompt (pkexec or sudo) at install time — it is the package manager doing the write, not the app. sudo apt remove kirocrew / sudo dnf remove kirocrew uninstalls; see Uninstalling for what happens to your data.

The AppImage needs no root and no package manager, which is the reason to pick it, but it also needs FUSE present (sudo dnf install fuse on Amazon Linux 2023, which ships without it; --appimage-extract-and-run is the escape hatch) and it runs from a randomized temporary mount, so it carries the manual sandbox-profile step and the move-breaks-it caveat documented in the sandbox section.

On first launch, the AppImage installs its freedesktop launcher and hicolor icons under your XDG data directory. Later launches repair Kiro Crew-managed files after an update or AppImage move. Repair requires a filesystem that supports Linux write leases. If your XDG data directory is on NFS, FUSE, or another filesystem that rejects leases, Kiro Crew leaves managed files untouched and defers repair. Keep the AppImage at its prior path or use a desktop package on that host.

A launcher or icon that existed before this feature, or whose contents or metadata you changed, remains user-owned and is not replaced. Deleting a managed launcher causes the next AppImage launch to restore it. To keep launching the AppImage without desktop integration, set KIROCREW_DISABLE_DESKTOP_INTEGRATION=1 in its launch environment.

All three Linux formats are built from the same glibc floor as the build runner. Verified requirement at the time of writing is glibc 2.34, which covers Ubuntu 22.04+, Debian 12+, Fedora, CentOS Stream 9 and Amazon Linux 2023, and excludes Ubuntu 20.04, Debian 11 and Amazon Linux 2 — on those, use the one-line install instead.

Prebuilt downloads for the release channels are linked from the README. The Windows NSIS installer is published on nightly, insider, and stable when its build succeeds, is Authenticode-signed, and participates in auto-update; SmartScreen can still show a first-download reputation warning for a new file hash. See windows-install.md for the current signing, publishing, and fallback source-install details.

See desktop-app.md for the full pipeline (frontend, PBS provisioning, pip install, pruning, electron-builder) and how the app locates and launches the bundled backend.

e. Docker

For always-on servers the gateway also ships as a public multi-arch image on GHCR. See docker.md.

Build targets

Every target has the same name on both drivers: make <target> on macOS and Linux, .\make.ps1 <target> on Windows.

Target What it does
make build Frontend (npm/Vite) + backend into .venv, then stage website/dist as src/kiro_crew/static/dist
make frontend Frontend only: npm build into website/dist, plus website/electron deps. It does not stage; run python -m kiro_crew.frontend stage . (or make build) for that
make backend Backend only: .venv + editable install with the dev extra
make wheel Self-contained pip wheel with the dashboard bundled, into dist/
make backend-bin Frozen standalone backend binary (host arch only)
make desktop Full desktop app: DMG on macOS, AppImage on Linux, NSIS installer on Windows
make test Build, then run the pytest suite
make clean Remove build artifacts, dists, and caches

Override the Python interpreter with make PY=python3.12 build, or .\make.ps1 build -Py C:\path\to\python.exe. Either one takes precedence over the <data-home>/python-bin marker ensure-python.sh records; a PY exported in the environment does not, so the marker still wins over it.

make wheel, make backend-bin and make desktop take EDITION=oss or EDITION=enterprise, stamped into the built package's kiro_crew/edition.py. Only EDITION= on the make command line counts; an exported shell EDITION is ignored, even under make -e. Without it, the value comes from the KIROCREW_BUILD_EDITION environment variable, and then defaults to oss. Both set to different values, or any other value (wildcards such as % included), fails the build before it starts. On Windows, set KIROCREW_BUILD_EDITION before .\make.ps1. See platform-context.

Both desktop targets run packaging/build-desktop.sh on every platform, including Windows, where make.ps1 invokes it through the Git for Windows bash: the script already normalizes MSYS uname output and has a Windows PBS branch, and CI's Windows lane calls it the same way. The plain build path needs no bash. Note that a locally built Windows installer is unsigned — only CI's signing lane has the signing identity — so SmartScreen shows an "unrecognized app" interstitial.

First run

After any install, the desktop app included, install Kiro CLI from https://kiro.dev/cli/ and sign in for the default agent:

kiro-cli login

The desktop app does not carry its own kiro-cli: it detects the one you installed, and the Set up Kiro page shows the install and sign-in commands until it finds one. (A build made with BUNDLE_KIRO_CLI=1 stages a copy inside the app; that copy is not on your shell PATH, so the page then serves the login command with the bundled binary's absolute path.) If you updated from a version that included kiro-cli and never installed your own, install it from https://kiro.dev/cli/; your sign-in carries over, since both copies read the same account store.

The built-in KAS engine is an alternative that needs no Kiro CLI. It is in shadow testing: the switch is off by default on every platform and nothing turns it on for you. The one-line install (cli.sh) and bash install.sh download the engine version this release pins by default, by running kirocrew kas install once Kiro Crew is installed; --no-kas skips that, and a failed download only warns. After any other install, or to retry, run kirocrew kas install yourself. The desktop app ships that engine inside the app (BUNDLE_KAS=1 at build time), so there is nothing to download. To try the route, turn on Run KAS on Kiro Crew's own engine under Settings → Developer → Feature Previews (or kirocrew config set agent.kas_bundle true) and restart; you then sign in from the Kiro sign-in card on Settings → Agent Harness → KAS, and kiro-cli login is not used.

Then start Kiro Crew:

kirocrew setup            # interactive wizard
kirocrew doctor           # verify everything is wired up
kirocrew gateway          # start the server, then open http://localhost:5476

From a source checkout, use PYTHONPATH=src python -m kiro_crew <subcommand> in place of kirocrew.

What kirocrew setup asks

The wizard installs the agent config, then walks through the workspace directory, timezone, dashboard URL, and (on macOS) the desktop app. Messaging channels are opt-in: pass --slack for guided Slack credentials and slash commands, or --whatsapp to check the optional dependency and pairing state before enabling WhatsApp. It also does NOT install a browser: the agent's browser engine is installed from Settings → Browser (see Browser).

Messaging channels are optional. The default wizard configures none, and the web dashboard is fully functional without any messaging credentials. Connect a channel later -- Slack (kirocrew setup --slack or slack-setup.md), Discord, Telegram, Teams, Webex, WeCom, Weixin, Feishu, iMessage, or WhatsApp -- when you want to reach the same agent away from your desk.

These flags narrow the wizard:

Flag Effect
--agent-only Install the agent config and stop, skipping the workspace and every credential prompt
--slack Run the guided Slack credential + slash-command setup (opt-in)
--whatsapp Check the optional WhatsApp dependency and pairing state, then enable the channel (opt-in)
--clean Fresh agent config: ignore the existing kirocrew.json and regenerate from defaults instead of merging your MCP servers and tools forward
--electron-only Install only the macOS desktop app

The two combine: kirocrew setup --agent-only --clean rebuilds the agent config from scratch and touches nothing else. That is the fix for a broken or stale MCP configuration, because without --clean the existing file is used as the base so all user customizations survive.

Browser

Kiro Crew's browser tool drives one browser through Browser Harness, run locally by the gateway with its telemetry and cloud path switched off. Settings → Browser shows one card for it, whose single button follows the state:

  1. The engine. The pinned driver lives in Kiro Crew's own data home (<data-home>/browser-harness). No npm, no Node.js, no sudo. While it installs the card says so; if it is not installed the card offers Install, and a failed install offers Retry with the reason.
  2. Your own Chrome (macOS, Linux desktop, Windows): open chrome://inspect/#remote-debugging once and turn remote debugging on; the first time the agent connects, Chrome asks Allow remote debugging? and you click Allow (the card turns yellow until you do). Your logins are used where they exist; the agent never types a password. On Windows without Chrome the agent uses Edge the same way. Or set Which browser to Kiro's Chromium, and under More turn on Run with no window for a server or a remote gateway with no display.
  3. Try it. Ask the agent to open a page. The page appears in the chat as a live card; click it to take over (a CAPTCHA, a 2FA prompt, a login), press Esc to hand it back.

The side panel keeps a Preview tab for your own local dev server; the agent does not drive it. A browser CLI directory left under the data home by an earlier version is no longer used; kirocrew doctor names it and you can delete it. Details: browser module spec.

Configuration

  • Config file: ~/.kiro/crew/config.json, managed with kirocrew config get/set/edit.
  • Credentials: ~/.kiro/crew/.env holding messaging-channel tokens (for Slack: SLACK_APP_TOKEN, SLACK_BOT_TOKEN, KIROCREW_OWNER_ID; other channels use their own keys). See slack-setup.md for creating the Slack app.

Environment variables

Variable Default Purpose
KIROCREW_HOME ~/.kiro/crew Data directory (config, credentials, databases)
KIROCREW_PORT 5476 Port the gateway / dashboard listens on
KIROCREW_EMBED_MODEL_URL CDN default Mirror for the embedding model download
KIROCREW_EMBED_MODEL_PATH unset Run a local GGUF instead of the bundled model

KIROCREW_PORT is an environment variable validated at CLI entry, not a config key. --port on the CLI overrides it (--port auto binds an OS-assigned ephemeral port). Without either, the port in the dashboard.url config key is used, and 5476 when that URL names none. For the installed service the port is baked into the unit at install time — see Running as a service for how to set and later change it.

The data home lives under ~/.kiro/

Kiro Crew stores its data in ~/.kiro/crew, sharing the ~/.kiro/ base with other Kiro-family apps. An existing top-level ~/.kirocrew install migrates automatically on first launch: its data (config, credentials, session history, databases) is copied into ~/.kiro/crew, overwriting any file already at the same relative path, then verified, then the legacy data is deleted. There is no rollback copy and no backup of anything overwritten.

Details worth knowing before you upgrade:

  • Re-downloadable bulk content (models/, cache/) is not copied; the new home regenerates it on first start, exactly as a fresh install does.
  • Virtual environments at the legacy root (venv, .venv, venvs) are neither copied nor deleted, because a venv is not relocatable and may be the very interpreter running the migration. The legacy root survives to hold them.
  • If a live gateway holds either home's gateway.lock, the move is skipped for that run and completes on the next clean cold start.
  • The migration only runs on the default path. Setting KIROCREW_HOME skips it entirely, so set it before upgrading if you want the two homes to stay separate.

There is no rollback. Once the move completes, ~/.kirocrew is gone, and an older release knows nothing of ~/.kiro/crew, so it would start empty. Back up first if you need to be able to go back:

cp -a ~/.kirocrew ~/.kirocrew.manual-backup

Verify the install

kirocrew doctor

doctor reports, section by section: the composed platform edition, the data home (including a legacy-home conflict warning), Linux pod session bus, the project directory, the agent config, configuration values (provider, model, approval mode, dashboard URL), the managed MCP servers and their tool counts, the runtime, vector memory and the in-process embedding model (including whether the model URL is reachable), speech-to-text, the Slack integration, loop-stall crash dumps, and connectivity.

Running as a service

For always-on operation (channel bots, cron jobs, background tasks):

kirocrew service install    # systemd on Linux, launchd on macOS
kirocrew service status
kirocrew service uninstall

On Linux this writes /etc/systemd/system/kirocrew.service (sudo is prompted for the unit file and the systemctl calls; the gateway itself runs as your own user, never under sudo). When you are already root — a minimal container or root login — no sudo binary is required. On macOS it writes a launchd plist and needs no sudo. That plist is a per-user LaunchAgent, so on a Mac the gateway starts when you log in, not at boot; see The gateway does not start after a reboot until someone logs in.

The gateway runs untrusted agent tools, so it must run as a non-root user: the installer sets User= to the account behind sudo ($SUDO_USER, else $USER), and refuses to install a User=root service. From a bare root login (or sudo with no $SUDO_USER), first create or pick a normal account and install as it, e.g. sudo -u <user> KIROCREW_KIRO_BIN=... kirocrew service install (the official Docker image already runs as the kirocrew user).

On Linux the agent runtimes run under the service account's own user manager, so enable linger for that account:

sudo loginctl enable-linger <user>

Without linger, systemd stops that manager at the account's last logout: running agent runtimes die, and later spawns start without their memory and fork-count ceilings. kirocrew service install prints a non-fatal warning naming this command when linger is off.

When another gateway already serves the same data home, the unit exits 78 and goes failed once instead of restarting, because the unit's RestartPreventExitStatus= names that code. A unit file without that directive keeps relaunching against the refusal; re-run kirocrew service install to rewrite it. See Service Management in the CLI spec for the details.

SELinux-enforcing hosts with kirocrew under $HOME

On an SELinux-enforcing host whose kirocrew lives under $HOME — the default on Bazzite, Fedora Silverblue/Kinoite and other atomic desktops — a system service cannot start at all. systemd (PID 1) runs in the init_t domain, the policy does not allow that domain to execute a file carrying a home label (user_home_t), and the unit fails every start with status=203/EXEC until it exhausts its restart limit.

The binary is fine. test -x on it succeeds, the shebang is correct, and the permissions are right — the policy's execute check is the only thing that fails, which is why 203/EXEC here looks identical to a genuinely missing or non-executable path. kirocrew service install therefore asks the loaded policy before writing anything, and if the unit provably cannot start it refuses up front and prints a ready-to-paste per-user unit instead of leaving a crash-looping service enabled at every boot.

A per-user unit is not affected, because the per-user systemd manager does not run in PID 1's domain. Follow the commands the refusal prints, then manage the service with systemctl --user status|restart kirocrew and journalctl --user -u kirocrew -f. kirocrew service status reports it as the user scope (the output names both scopes: system scope: not installed, then user scope: active (running) with the systemctl --user status block; it exits 0 only while the unit is active, so a unit stuck in activating (auto-restart) exits 1 with that state in the headline), kirocrew stop / kirocrew restart act on it through your own manager (restart confirms the unit stays up and, if it lands in activating (auto-restart) instead, says so and prints that unit's own journal read, journalctl --user -u kirocrew.service -n 50 --no-pager, rather than a sudo systemctl command that would not find the unit), kirocrew logs tails its user journal, kirocrew doctor's service checks read its unit file, and kirocrew service uninstall removes it and says which scope it removed. It only ever removes what it installed: the unit file it wrote, or one carrying its Environment="KIROCREW_SERVICE_MANAGED=1" line (a copy of ours you systemctl --user linked loses the link and keeps the file); a unit of your own, or a distribution's, that holds the name is left untouched and is not stopped. Run these as your own account, not under sudo: a root shell cannot reach your user manager, and the commands then report user scope: not reachable from this shell rather than guessing. service install itself still writes a system unit — #10813 tracks preferring user units at install time. Installing kirocrew onto a system-labelled path such as /usr/local/bin also avoids the problem.

SELinux labels on files the service writes

On an SELinux host (enforcing or permissive) the system unit can carry a SELinuxContext= line set to the context of the shell that ran kirocrew service install, for example unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023. A new file takes its SELinux user from the process that creates it, so without that line the gateway runs as system_u and every cache it writes under your home (~/.npm/_cacache, ~/.gradle, ~/.cache/pip) is labelled system_u. Your own shell is then refused hardlinks inside it, which npm reports as EPERM / syscall link and "root-owned files" even though ownership is correct.

The line is written only when the loaded policy says the unit still starts with it: PID 1 may switch into that context, and the context may start the kirocrew binary (and the interpreter its shebang names). Otherwise it is left out and the unit runs exactly as before (the reason is logged at INFO). On AL2023's targeted policy, for example, a kirocrew under /usr/local/bin (bin_t) gets the line, while one under ~/.local/bin (home_bin_t) does not and keeps the system_u caches; the per-user unit above avoids them there. The line is also left out when SELinux is off, when the context cannot be read, and when the installing shell is itself system_u.

A unit installed by an earlier build has no such line: re-run kirocrew service install, then fix an already-poisoned cache with restorecon -RF ~/.npm (or delete it).

Setting the service port

A system service inherits none of your shell environment, so export KIROCREW_PORT=… in your shell does not reach it. Set the port when you install so it is baked into the unit:

KIROCREW_PORT=5477 kirocrew service install

To change it later without reinstalling, edit the overrides file the installer creates and restart:

sudo sed -i 's/^#\?KIROCREW_PORT=.*/KIROCREW_PORT=5477/' /etc/kirocrew/kirocrew.env
sudo systemctl restart kirocrew

/etc/kirocrew/kirocrew.env is read by the unit via EnvironmentFile=, so its values override the install-time snapshot and survive a reinstall. Use this to move the service off the default 5476 when that port is already taken (for example by a local crew you also run on this host — there is one kirocrew.service unit, so re-running service install updates it in place rather than creating a second service).

The EnvironmentFile= directive only exists in units written by v0.2.0 or later. Upgrading the package never rewrites an already-installed unit, so a unit installed by an older release (v0.1.3 and earlier) silently ignores /etc/kirocrew/kirocrew.env — editing it changes nothing. Check which kind you have:

grep EnvironmentFile /etc/systemd/system/kirocrew.service

No output means the directive is missing. Either re-run kirocrew service install (it rewrites the unit in place, keeping the same service), or set variables with a systemd drop-in, which works on any unit version.

Setting other environment variables (systemd drop-in)

For variables the seeded overrides file does not cover — proxy settings are the common case — use a systemd drop-in. Drop-ins are systemd's own override mechanism: they apply to the unit no matter which release wrote it, and they survive reinstalls, service install re-runs, and even kirocrew service uninstall (which removes the unit but never touches /etc/systemd/system/kirocrew.service.d/).

sudo mkdir -p /etc/systemd/system/kirocrew.service.d
sudo tee /etc/systemd/system/kirocrew.service.d/proxy.conf > /dev/null <<'EOF'
[Service]
Environment="HTTPS_PROXY=http://proxy.example.com:3128"
Environment="HTTP_PROXY=http://proxy.example.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1"
EOF
sudo systemctl daemon-reload
sudo systemctl restart kirocrew

A new or edited drop-in is not picked up until systemctl daemon-reload runs — restarting alone is not enough. Verify what the unit resolved to with systemctl cat kirocrew (drop-ins are printed below the unit) or systemctl show kirocrew --property=Environment.

For remote hosts, see remote-and-mobile.md.

Linux: the agent sandbox and unprivileged user namespaces

On Linux, Kiro Crew isolates the agent by entering a user namespace and then a mount namespace, over-mounting credential paths with empty directories so the agent cannot read them. Which paths depends on the tier: the default agent.sandbox: "auto" runs the standard tier, which hides ~/.gnupg, ~/.docker, ~/.azure, ~/.config/gcloud and Kiro Crew's own secret vault but deliberately leaves ~/.aws, ~/.ssh and ~/.kube visible so the aws CLI, credential_process, git-over-SSH and kubectl keep working inside the agent. agent.sandbox: "strict" additionally hides ~/.aws (including the sso/cache grant store remote-MCP OAuth uses), ~/.ssh (except known_hosts), ~/.kube, ~/.config/gh and the credential files ~/.npmrc, ~/.pypirc, ~/.netrc and ~/.git-credentials, and the tools that read them stop working inside the agent as a result; it applies to sessions started after the change. See the Sandbox section of the configuration guide. If the sandbox cannot be built, Kiro Crew refuses to run the agent rather than run it unisolated: spawns fail closed. This is deliberate and is not something to work around casually.

Ubuntu 23.10 and newer ship kernel.apparmor_restrict_unprivileged_userns=1, which moves any process that creates a user namespace into a restricted AppArmor profile with no CAP_SYS_ADMIN. The first unshare succeeds, the second fails with EPERM, and you see:

sandbox: unshare(NEWNS) failed: errno 1

The remedy: let service install add an AppArmor profile

kirocrew service install

Where, and only where, this mechanism is the one in play, the installer also writes /etc/apparmor.d/kirocrew-userns and loads it. The profile grants exactly one permission (userns) and is attached to the resolved kirocrew launcher script (the same absolute path service install uses as ExecStart, typically something like ~/.kiro/crew-venv/bin/kirocrew) — the same approach stock Ubuntu already uses for chrome and brave. The unit carries no AppArmorProfile= directive: that directive labels only the unit's own top-level process, and the gateway's sandbox probe runs in a child reached through a fork the directive's labelling never reaches.

This uses the sudo prompt service install already needs for the unit file, so it costs no additional privilege, and it cannot fail your install: if the profile cannot be written, loaded, or verified, you get a warning and the install continues. kirocrew service uninstall unloads and removes it, so a host is left as it was found rather than carrying an orphaned userns permission.

The installer skips the profile silently, with a reason it can print, when AppArmor is not an active LSM, when the sysctl is not 1, when apparmor_parser is absent, or when the parser is older than 4.x (the userns rule needs 4.x or newer). So on Debian, Arch, RHEL and Amazon Linux nothing changes.

Running the gateway outside systemd (for example kirocrew gateway in a terminal) is covered only when the launch goes through the attached launcher path: the kernel applies a path-attached profile at every execve() of that exact file, unit or no unit, so once service install has attached the profile, a foreground kirocrew gateway typed at a shell that resolves to the same launcher script runs confined too. A launch that does not go through that path — python -m kiro_crew, a different venv's entry point, a re-created venv the profile has not been re-pointed at — stays unconfined, and kirocrew doctor reports the attachment as stale in the re-created-venv case. Prefer running the gateway as the service: it pins ExecStart to the attached path and restarts on boot.

The AppImage (desktop app) needs its own profile

A .deb or .rpm install needs none of this section: the package's post-install step writes and loads a userns profile attached to its own fixed path under /opt, so the sandbox works on a stock Ubuntu 23.10+ host with no manual step and nothing to re-point later. That fixed path is the whole difference — everything below exists because an AppImage does not have one.

The service-installed profile is attached to the resolved kirocrew launcher path. An AppImage launch does not execute that path: the app starts its bundled backend from a randomized temporary mount, so the service profile cannot match it and agent spawns fail closed. Attach a profile to the durable AppImage path instead:

kirocrew sandbox install-profile --path ~/Applications/kirocrew.AppImage

If you only ever downloaded the AppImage, you have no kirocrew on your PATH — the CLI is bundled inside the app, which is the whole point of that download. Use the bundled copy instead. The sandbox error message in the app prints the exact absolute path for you; it looks like this, and it is valid while the app is running:

'/tmp/.mount_XXXXXX/resources/backend-dist/kirocrew-backend/bin/kirocrew' \
  sandbox install-profile --path ~/Applications/kirocrew.AppImage

Do not prefix that with sudo. The command elevates only the three steps that need it (install, apparmor_parser, aa-exec) and prompts you for a password when it does; running the whole thing as root would execute application code with privilege for no reason.

Then restart the app. To check whether the launch you are looking at is covered:

kirocrew sandbox status

This writes /etc/apparmor.d/kirocrew-launcher, granting the same single userns permission — but attached to that executable path, which is how the kernel can apply it at exec time with no cooperation from the process. The backend the app spawns inherits it. It is the same mechanism stock Ubuntu uses for /etc/apparmor.d/chrome, brave, 1password and Discord. kirocrew sandbox remove-profile unloads and removes it.

Three things the command refuses to do, because an attachment is a permission grant keyed on a path:

  • A path you do not own. An AppImage you downloaded is owned by you, which is the case this serves. A root-owned binary in a system location is shared with every user of the machine, so attaching there would hand the grant to all of them - and no blocklist of shared runtimes can be complete (java, mono, dotnet, php, wine and friends are all in the same position as /usr/bin/python3). If you need to confine a system-wide install, ship a packaged profile the way the distro does for chrome and brave.
  • A world-writable location (/tmp, /var/tmp, /dev/shm, /run, or any directory in the path whose permissions let others write). Anyone with a local account could put their own file at that path and inherit the grant. Keep the AppImage somewhere durable such as ~/Applications. This also rules out the AppImage's own /tmp/.mount_XXXXXX runtime directory, which is a fresh random path on every launch and could never match twice.
  • A shared interpreter such as /usr/bin/python3. That would grant unprivileged user namespaces to every program on the host that runs it.

Because the profile is attached to a path, moving or renaming the AppImage silently stops it applying — the kernel reports no error, the profile just never matches. kirocrew sandbox status detects that and names the stale path; re-running install-profile re-points it. Replacing the file in place (an in-place update) keeps working, since the path is unchanged.

Running the gateway in a terminal (kirocrew gateway) is covered only when the shell resolves the exact launcher path to which service install attached its profile. python -m kiro_crew, another venv's entry point, or a recreated launcher at a different path is not covered; kirocrew doctor reports a stale attachment. Do not attach the profile to a shared interpreter such as /usr/bin/python3, because that would grant unprivileged user namespaces to every program on the host that runs it.

Do not run aa-exec -p kirocrew-userns -- kirocrew gateway: it does not apply the profile. Entering a named profile requires aa_change_onexec, which an unprivileged unconfined process is not permitted to do, and aa-exec does not fail loudly when it cannot transition — it execs the command unconfined, so the gateway appears to start under the profile while running without it. Running it under sudo aa-exec does transition, but then the gateway runs as root.

Please do not "fix" this by setting the sysctl to 0. That disables a kernel-wide protection for every application on the machine to satisfy one app-scoped need. The per-application profile exists precisely so you do not have to.

Other reasons user namespaces can be denied

The AppArmor profile addresses only the Ubuntu restriction. These are different mechanisms with different remedies, and they report different errnos. The sandbox probe names the failing step so you can tell them apart:

Symptom Mechanism Remedy
unshare(CLONE_NEWNS) fails EPERM, sysctl is 1 Ubuntu >= 23.10 AppArmor userns restriction kirocrew service install, or kirocrew sandbox install-profile for the AppImage (this page)
unshare(CLONE_NEWUSER) fails ENOSPC / EUSERS user.max_user_namespaces=0 (CIS-hardened host) Raise that sysctl
unshare fails and kernel.unprivileged_userns_clone=0 Debian-family legacy knob (defaults to 1 since Debian 11) Set it to 1
unshare fails EINVAL / ENOSYS Kernel built without CONFIG_USER_NS None short of a different kernel
Fails inside Docker/Podman The container's seccomp filter denies unshare Container run flags, not host config
Both unshare steps pass, mount(MS_REC|MS_PRIVATE) on / fails EACCES (or EPERM) The container runtime's default AppArmor profile (deny mount), or a seccomp filter without mount; the Kubernetes default on AppArmor nodes --security-opt apparmor=unconfined / Pod appArmorProfile: Unconfined plus a seccomp profile permitting unshare and mount, or agent.sandbox_allow_unsandboxed_exec=true — see Kubernetes and AppArmor
RHEL/Fedora/Rocky/AL2023 SELinux, not AppArmor userns is enabled there; the profile is inert

To see which step is failing on your host:

python3 -c "
import kiro_crew.sandbox as sb
sb.reset_backend(); print(sb.detect_backend(), sb._last_unshare_failure)"

kirocrew doctor reports the same verdict without the one-liner, and the dashboard's Sandbox unavailable screen names the mechanism and the command for it directly — the probe classifies the failing step into one of apparmor_userns, max_user_namespaces, userns_denied, no_user_ns or mount_denied, which is the row of the table above that applies to you.

Troubleshooting

Always start with kirocrew doctor.

AcpTimeoutError: ACP prompt timed out

The kiro-cli backend did not answer in time. Five common causes:

  1. kiro-cli is not installed. With agent.acp_backend set to kiro, the gateway raises kiro-cli not found (<searched directories>). Install it, point KIROCREW_KIRO_BIN at the binary, or use the dashboard's Set up Kiro page.
  2. Not logged in. Run kiro-cli login. An expired session normally surfaces as the distinct, non-retryable "kiro-cli is not logged in" error rather than a timeout, so check this even when the message differs.
  3. A broken or stale MCP config. Rebuild it with kirocrew setup --agent-only --clean. A single unreachable MCP server can consume the whole initialization window.
  4. First launch is genuinely slow. MCP servers can be slow to initialize, so the handshake allows up to 4 minutes before giving up. Later timeouts have their own watchdogs: a turn that streams text and then goes quiet for 90 seconds is treated as finished, and a dispatched tool that returns nothing at all for 10 minutes is treated as a dead stall and the agent is killed to recover the slot.
  5. The host needs a proxy and the service does not have one. On a corporate network, kiro-cli must reach its backend through your proxy. A systemd service inherits none of your shell's HTTPS_PROXY/HTTP_PROXY exports, so a gateway that works when run from your terminal can still time out as a service. Set the proxy variables on the unit with a systemd drop-in, then sudo systemctl daemon-reload && sudo systemctl restart kirocrew. Agent sessions inherit the service environment, so this fixes them. One known gap: the first-run setup gate's login probe currently filters proxy variables out even when the unit carries them, so it can stay stuck on "Sign in to Kiro CLI" on a proxied host — that is issue #2648.

Memory or knowledge search returns nothing

The embedding model is probably still downloading. Check the Vector Memory section of kirocrew doctor, which reports the model state and whether the model URL is reachable, and look for the GGUF under ~/.kiro/crew/models/. Search falls back to keyword matching until the model lands, then switches over on its own with no restart. For an airgapped or firewalled host, point KIROCREW_EMBED_MODEL_URL at a mirror; the sha256 pin still verifies the file.

The gateway will not start

kirocrew doctor
kirocrew gateway --port auto   # bind an OS-assigned port if 5476 is taken

Uninstalling

Uninstalling removes the Kiro Crew binary and its runtime but preserves your data home (~/.kiro/crew) — configuration, credentials, memory, sessions, apps, and the audit chain remain intact. This is intentional: reinstalling picks up where you left off without re-running setup or losing history.

Before uninstalling, stop any running gateway and remove the system service if one is installed:

kirocrew stop                # stop a running foreground gateway
kirocrew service uninstall   # removes the systemd unit / launchd plist

Then follow the section matching your install method.

One-line install via cli.sh (pipx)

If cli.sh used pipx (the default when pipx is on PATH):

pipx uninstall kirocrew

One-line install via cli.sh (managed venv)

If pipx was not available, cli.sh created a managed venv and a symlink. Updates add versioned trees beside it (crew-venv-<version>), the stable link crew-venv-current that names the live one, the update lock crew-venv.update.lock, and, after an interrupted update, a hidden .crew-venv-<version>.deleting-<pid> tree or a crew-venv-current.<pid>.new link. Remove them (this works the same in sh, bash and zsh, and removes nothing when a pattern matches nothing):

VENV="${KIROCREW_VENV:-${KIROCREW_HOME:-$HOME/.kiro/crew}-venv}"
VENV="${VENV%/}"
PARENT="$(cd "$(dirname "$VENV")" && pwd -P)"
NAME="$(basename "$VENV")"
rm -f ~/.local/bin/kirocrew "$VENV-current" "$VENV.update.lock"
rm -rf "$VENV"
find "$PARENT" -maxdepth 1 -type l -name "$NAME-current.*.new" -exec rm -f {} +
find "$PARENT" -maxdepth 1 -type d \( -name "$NAME-[0-9]*" -o -name ".$NAME-*.deleting-*" \) \
  -exec sh -c '
    venv=$1 real=$2; shift 2
    for tree; do
      owner="$(cat "$tree/.kirocrew-tree" 2>/dev/null)"
      if [ -n "$owner" ] && { [ "$owner" = "$venv" ] || [ "$owner" = "$real" ]; }; then
        rm -rf "$tree"
      else
        echo "left in place, check and remove by hand: $tree"
      fi
    done' sh "$VENV" "$PARENT/$NAME" {} +

A versioned tree is removed only when its .kirocrew-tree marker names this install's venv. Any other match is listed instead: a neighbouring directory that merely shares the name prefix (a backup, another install's trees), or a tree an earlier release built before it wrote the marker. Remove the ones that are this install's by hand. If you set KIROCREW_VENV to a custom path, verify its contents before removing it — cli.sh overlays that directory with venv files, and an rm -rf on a path you already used for something else will take that too.

pip / pip wheel install

pip uninstall kirocrew

If installed in a dedicated virtualenv:

rm -rf /path/to/your/venv

From source (development)

Remove the editable install and the build artifacts:

# Chained so a failed `cd` (wrong path) can never run `rm -rf` in your current directory:
cd /path/to/KiroCrew \
  && pip uninstall kirocrew \
  && rm -rf .venv build dist   # editable install, local venv, and build outputs

macOS desktop app (DMG / zip)

  1. Quit Kiro Crew from the menu bar or Dock.
  2. Drag KiroCrew.app from /Applications to the Trash (or rm -rf).

There is no installer package or receipt to clean — the DMG is a drag-install.

Linux AppImage

Delete the AppImage file wherever you placed it:

rm -f ~/Applications/KiroCrew-x86_64.AppImage   # or wherever you saved it

The launcher the AppImage installed carries a TryExec key, so desktop menus hide it once the AppImage is gone. To remove the launcher and icons as well, run the script below. It reads the integration's manifest and deletes only the files Kiro Crew created: a launcher or icon is removed only while its content still matches what Kiro Crew wrote, so a file you replaced or edited is kept. It then removes the manifest directory. For the nightly build, set APP=kirocrew-desktop-nightly.

APP=kirocrew-desktop
STATE="${XDG_CONFIG_HOME:-$HOME/.config}/$APP/linux-desktop-integration"
python3 - "$STATE/state.json" <<'EOF'
import hashlib, json, os, sys
for path, rec in json.load(open(sys.argv[1]))["artifacts"].items():
    try:
        digest = hashlib.sha256(open(path, "rb").read()).hexdigest()
        if digest in rec.get("owned", []):
            os.unlink(path)
            print("removed", path)
        else:
            print("kept (not Kiro Crew's)", path)
    except FileNotFoundError:
        pass
    pending = rec.get("pending") or {}
    sides = [(rec.get("anchorPath"), rec.get("owned", []), None),
             (pending.get("tempPath"), [pending.get("hash")], pending.get("ino"))]
    for group in (rec.get("recovery"), pending.get("previous")):
        group = group or {}
        sides += [(p, [group.get("hash")], group.get("ino"))
                  for p in group.get("paths", [])]
    for side, hashes, ino in sides:
        if not side or not side.startswith(
                (path + ".tmp-", path + ".kirocrew-displaced-")):
            continue
        try:
            same_inode = ino is None or os.lstat(side).st_ino == ino
            digest = hashlib.sha256(open(side, "rb").read()).hexdigest()
            if same_inode and digest in hashes:
                os.unlink(side)
            else:
                print("kept (changed since Kiro Crew wrote it)", side)
        except FileNotFoundError:
            pass
EOF
rm -rf "$STATE"

The side files the script removes are the .tmp- and .kirocrew-displaced- copies the repair logic keeps next to a managed file. Each one is removed only while its content, and its inode where the manifest records one, still match what Kiro Crew wrote. Setting KIROCREW_DISABLE_DESKTOP_INTEGRATION=1 stops future repairs without removing anything.

Windows (NSIS installer)

Use Settings → Apps → Installed apps, find "Kiro Crew", and click Uninstall. The NSIS uninstaller removes the application directory and shortcuts but does not touch ~/.kiro/crew.

Docker

docker stop kirocrew && docker rm kirocrew   # graceful stop, then remove
docker rmi ghcr.io/kirodotdev/kirocrew:stable # remove the image (match the tag you pulled)

docker stop sends SIGTERM and gives the gateway time to run its shutdown flush; docker rm -f sends SIGKILL immediately, which can drop up to one 5-second flush interval of recent session state.

Removing user data

Uninstalling via any method above leaves your data home intact. To also remove all user data:

# Back up first — this is irreversible. `kirocrew snapshot` captures memory,
# crons, config, notifications, security, workspace, skills, crew-teams,
# artifacts and uploads, but not `.env`, credentials, or installed apps,
# so a full copy of the data home is the only complete backup. Chained with
# `&&` so a failed copy (e.g. disk full) blocks the delete rather than racing
# ahead of it. Point the copy at a location you control OUTSIDE the data home:
cp -a "${KIROCREW_HOME:-$HOME/.kiro/crew}" ~/kirocrew-backup \
  && rm -rf "${KIROCREW_HOME:-$HOME/.kiro/crew}"

The backup is a full copy of your credentials (.env), signing keys, and session data, and it does NOT inherit the agent's path protections that guard the live data home — store it somewhere the agent cannot reach and delete it once the reinstall is confirmed good.

This deletes configuration, credentials (.env), session history, memory databases, installed apps, and the embedding model cache.

Docker: the commands above target a host data home. A Docker install keeps everything in the kirocrew-home named volume instead, so remove that rather than ~/.kiro/crew: docker volume rm kirocrew-home (or docker compose down -v).

Note: App Kit data is preserved per-app by default. To remove an individual app's data before or instead of purging the whole home: kirocrew app uninstall NAME --purge-data

Clean reinstall

A clean reinstall removes both the binary and the data home, then installs fresh. Use this when kirocrew doctor reports issues that setup cannot fix, or when you want a completely fresh start.

# 1. Stop any running gateway, then remove the service
kirocrew stop                        # stop a foreground gateway (service uninstall won't)
kirocrew service uninstall 2>/dev/null

# 2. Uninstall the binary (use the matching command from above)
pipx uninstall kirocrew          # or: pip uninstall kirocrew, rm the AppImage, etc.

# 3. Back up and remove the data home (chained so a failed copy blocks the delete)
cp -a "${KIROCREW_HOME:-$HOME/.kiro/crew}" ~/kirocrew-backup \
  && rm -rf "${KIROCREW_HOME:-$HOME/.kiro/crew}"

# 4. Reinstall
curl -fsSL https://download.crew.kiro.dev/cli.sh | sh

# 5. Run first-time setup
kirocrew setup
kirocrew doctor

Replace step 4 with your preferred install method (pip wheel, source build, desktop app) as described in the Install paths section above.

Data retention details

For reference, the data home structure and what each uninstall path touches:

  • kirocrew service uninstall removes only the systemd unit — the system unit and, when your own user manager has one loaded, the per-user unit, naming each scope it touched — (plus the AppArmor profile it installed) or the launchd plist.
  • Python and npm package removal has no preuninstall or postuninstall cleanup hook.
  • The macOS DMG/zip and the Linux AppImage have no cleanup hook, so removing the application bundle or image leaves the data home intact.
  • A Linux .deb / .rpm removal DOES run the package's own post-remove step, which drops /usr/bin/kirocrew-desktop and the installed AppArmor profile. It deliberately leaves the data home alone: ~/.kiro/crew holds your sessions, memory and credentials, so it is yours to remove (see Removing user data), not the package manager's.
  • App Kit uninstall preserves apps/<name>/data/ by default. Deleting that app data is a separate, explicit action: kirocrew app uninstall NAME --purge-data, or unchecking Keep app data in the confirmation dialog.

Windows NSIS uninstaller behavior. nsis.oneClick is false and nsis.deleteAppDataOnUninstall is left false, so the uninstaller removes only the application install directory and its shortcuts; it never resolves or removes the Kiro Crew home, which lives outside the install directory. Each signed Windows installer must pass an install, create-sentinel-under-~/.kiro/crew, uninstall, verify-sentinel smoke test before release. A separate Kiro-family uninstaller could remove the parent ~/.kiro/ directory; it must exclude ~/.kiro/crew or prompt explicitly. That release-blocking cross-product sign-off is tracked in issue #355.

Next steps