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.shpaths 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 throughkiro_crew.platform_compat. See windows-install.md for the Windows walkthrough.
- Prerequisites
- Install paths
- Build targets
- First run
- Configuration
- Verify the install
- Running as a service
- Linux: the agent sandbox and unprivileged user namespaces
- Troubleshooting
- Uninstalling
- Removing user data
- Clean reinstall
- Data retention details
- Next steps
| 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.
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 loginWith 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.
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(ormemory.embed_model_url) points the download at a mirror. The sha256 pin still verifies whatever it fetches.KIROCREW_EMBED_MODEL_PATH(ormemory.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.
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) |
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 | shOnce 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-kasstable 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.0stable 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.
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.
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:5476This 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 gatewayThe 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:
frontend:npm ci(ornpm install) +npm run buildinwebsite/, and installswebsite/electron's own deps last — it is a separate npm package thewebsite/install never reaches, andnpm testinwebsite/needs it.backend: creates.venvand runs an editable install with thedevextra (pip install -e ".[dev]").- stage:
.venv/bin/python -m kiro_crew.frontend stage .makeswebsite/distthe servedsrc/kiro_crew/static/dist: a link towebsite/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 wheelstages the same way;make backend-bindoes not, since its bundle copieswebsite/distitself, so it needs no Python for this.make frontendalone does not stage either: a checkout whosestatic/distis 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`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:5476The 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.
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.
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 desktopOutput 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.
sudo apt install ./KiroCrew-x86_64.deb # Debian, Ubuntu
sudo dnf install ./KiroCrew-x86_64.rpm # Fedora, RHEL, CentOS Stream, AL2023Either 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.
For always-on servers the gateway also ships as a public multi-arch image on GHCR. See docker.md.
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.
After any install, the desktop app included, install Kiro CLI from https://kiro.dev/cli/ and sign in for the default agent:
kiro-cli loginThe 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:5476From a source checkout, use PYTHONPATH=src python -m kiro_crew <subcommand>
in place of kirocrew.
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.
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:
- The engine. The pinned driver lives in Kiro Crew's own data home
(
<data-home>/browser-harness). Nonpm, no Node.js, nosudo. 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. - Your own Chrome (macOS, Linux desktop, Windows): open
chrome://inspect/#remote-debuggingonce 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. - 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.
- Config file:
~/.kiro/crew/config.json, managed withkirocrew config get/set/edit. - Credentials:
~/.kiro/crew/.envholding 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.
| 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.
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_HOMEskips 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-backupkirocrew doctordoctor 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.
For always-on operation (channel bots, cron jobs, background tasks):
kirocrew service install # systemd on Linux, launchd on macOS
kirocrew service status
kirocrew service uninstallOn 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.
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.
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).
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 installTo 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.serviceNo 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.
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 kirocrewA 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.
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
kirocrew service installWhere, 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.
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.AppImageIf 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.AppImageDo 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 statusThis 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,wineand 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 forchromeandbrave. - 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_XXXXXXruntime 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 requiresaa_change_onexec, which an unprivileged unconfined process is not permitted to do, andaa-execdoes 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 undersudo aa-execdoes 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.
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.
Always start with kirocrew doctor.
The kiro-cli backend did not answer in time. Five common causes:
kiro-cliis not installed. Withagent.acp_backendset tokiro, the gateway raiseskiro-cli not found (<searched directories>). Install it, pointKIROCREW_KIRO_BINat the binary, or use the dashboard's Set up Kiro page.- 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. - 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. - 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.
- The host needs a proxy and the service does not have one. On a
corporate network,
kiro-climust reach its backend through your proxy. A systemd service inherits none of your shell'sHTTPS_PROXY/HTTP_PROXYexports, 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, thensudo 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.
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.
kirocrew doctor
kirocrew gateway --port auto # bind an OS-assigned port if 5476 is takenUninstalling 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 plistThen follow the section matching your install method.
If cli.sh used pipx (the default when pipx is on PATH):
pipx uninstall kirocrewIf 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 uninstall kirocrewIf installed in a dedicated virtualenv:
rm -rf /path/to/your/venvRemove 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- Quit Kiro Crew from the menu bar or Dock.
- Drag KiroCrew.app from
/Applicationsto the Trash (orrm -rf).
There is no installer package or receipt to clean — the DMG is a drag-install.
Delete the AppImage file wherever you placed it:
rm -f ~/Applications/KiroCrew-x86_64.AppImage # or wherever you saved itThe 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.
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 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.
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-homenamed volume instead, so remove that rather than~/.kiro/crew:docker volume rm kirocrew-home(ordocker 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
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 doctorReplace step 4 with your preferred install method (pip wheel, source build, desktop app) as described in the Install paths section above.
For reference, the data home structure and what each uninstall path touches:
kirocrew service uninstallremoves 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
preuninstallorpostuninstallcleanup 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/.rpmremoval DOES run the package's own post-remove step, which drops/usr/bin/kirocrew-desktopand the installed AppArmor profile. It deliberately leaves the data home alone:~/.kiro/crewholds 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.
- Slack setup: create and configure the Slack app.
- Other channels: Discord, Telegram, Teams, Webex, WeCom, Weixin, Feishu, iMessage, and WhatsApp.
- Remote and mobile access: 24/7 operation on a remote host, and reaching the dashboard from a phone.
- Architecture overview: system diagrams and the component map.
- Security deep dive: sandbox, governance, and denied commands.
- App Kit guide: build apps that extend Kiro Crew.
- CONTRIBUTING.md: development setup and the PR workflow.