Install the native package (no Docker, single user)
このコンテンツはまだ日本語訳がありません。
Agent Fleet is a self-hosted web console for running AI coding agents (Claude Code, Codex CLI, GitHub Copilot CLI, Antigravity CLI, Cursor CLI, Kiro, OpenCode, Muse Code, and the fleet’s own llama.cpp harness) as a managed fleet. This native package runs it without Docker on a single-user Linux host (including WSL2): the control plane and console run as host processes, and the workspace runs inside a bubblewrap (user-namespace) sandbox on a downloaded rootfs. For multi-user service with container isolation and cgroup CPU/memory quotas, use the Docker-based editions (Compose / AWS ECS) instead.
It runs on plain WSL2 (or any Linux, single-user operation) without installing anything on the host. Workspaces run on the bundled rootfs (the same userland as the workspace image) via unprivileged bubblewrap, so there is no need to install tmux / git / node / the agent CLIs on the host.
Quick start
Section titled “Quick start”One-liner install from the distribution repo
(k-k1/agent-fleet-dist) — extracts to
~/.local/opt/agent-fleet/<v>/ and symlinks ~/.local/bin/af:
curl -fsSL https://raw.githubusercontent.com/k-k1/agent-fleet-dist/main/install.sh | bashaf start # first run only: fetch, verify, and extract the rootfs (~200MB)# open http://localhost:8099 in a browserIf you received the tar directly (<arch> is amd64 or arm64, matching uname -m
x86_64 / aarch64):
tar xzf agent-fleet-native-<v>-linux-<arch>.tar.gzcd agent-fleet-native-<v>-linux-<arch>./af start # first run only: fetch, verify, and extract the rootfs (~200MB)# open http://localhost:8099 in a browser- The first
af startdownloads the rootfs pinned in rootfs.json (URL + sha256). Later starts work offline (the extracted rootfs is reused). - The agent CLIs (claude / codex / opencode / cursor / copilot / agy / rtk) are not baked in. On a workspace’s first start, the entrypoint auto-installs the versions pinned in versions.json (verified-working versions) into the virtual HOME (network is needed only then). kiro (~855MB) is the exception: it is installed on demand the first time a Kiro session starts, not at workspace start. Muse Code is not installed at all until you ask for it: its connection card’s “Install” button fetches the pinned version into your home.
- Stop with Ctrl-C (runs in the foreground). For running as a service, see the systemd user unit below.
Host requirements
Section titled “Host requirements”| Requirement | Notes |
|---|---|
| Linux kernel (unprivileged user namespaces) | The stock WSL2 kernel has AppArmor disabled, so it should work as-is. See the note below |
| bash / coreutils / tar | Standard userland only. zstd is bundled as bin/zstd, so it is not needed on the host |
| curl or wget | Used only for the initial rootfs download (see air-gap below) |
| (optional) CPU exposing RDRAND | Only for the agy (Antigravity CLI) agent kind — its FIPS build aborts without RDRAND. Where the kernel has withdrawn the instruction the Agent masks it out of OpenSSL’s detection and agy runs anyway, its randomness coming from the kernel; set AF_AGY_RDRAND_MASK=0 to refuse that and have the Console hide agy instead (0008) |
On bare-metal Ubuntu 23.10+ (non-WSL), unprivileged userns may be restricted by AppArmor. In that case, run once:
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0# make it permanent:echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/60-agent-fleet.confOn kernels with userns disabled entirely, the native package cannot run (use the
Docker setup, agent-fleet-<v>.tar.gz, instead).
macOS has neither bubblewrap nor Linux user namespaces, and the rootfs is Linux, so
there is no macOS package and install.sh refuses to run there. On an Apple Silicon Mac
run the linux-arm64 package inside a Linux VM (Lima, an OrbStack machine, …): install
and af start inside the VM, and open http://localhost:8099 through the VM’s port
forward. Checked on an M1 Mac mini (macOS 26.7) with Lima 2.2.1 and its Ubuntu LTS template:
install, start, workspace start and stop → start passed, after the AppArmor setting above
(the Ubuntu VM restricts unprivileged user namespaces like any Ubuntu 23.10+), and the same
with an OrbStack machine (orb create ubuntu), whose ports also reach the Mac’s
localhost (#2961). The Docker edition on a Mac is described in
deploy/compose/README.md.
Data locations
Section titled “Data locations”| Contents | Location |
|---|---|
| All data (DB / workspace homes / claude-config / extracted rootfs) | ~/.local/share/agent-fleet (override with WS_DATA) |
| The package itself | This extracted directory (holds no data — safe to delete) |
The layout is compatible with the Docker setup (the same WS_DATA can move between
docker ⇄ native). From Windows Explorer it is reachable at
\\wsl.localhost\<distro>\home\<user>\.local\share\agent-fleet.
Updating
Section titled “Updating”tar xzf agent-fleet-native-<v'>-linux-<arch>.tar.gz # extract the new versioncd agent-fleet-native-<v'>-linux-<arch> && ./af start- Data (
WS_DATA) lives outside the package and is untouched. DB migrations run automatically at startup and are forward-only (no downgrade support). A tar backup ofWS_DATAbefore updating is recommended. - Releases that keep the same rootfs version
<r>do not re-download it. - Delete the old package directory once you no longer need it.
Air-gap / manual file transfer
Section titled “Air-gap / manual file transfer”- The self-contained variant (the
-bundletar built with--bundle-rootfs) bundles the rootfs and starts without any download. - With the regular variant, you can pass a rootfs tar fetched on another host via
./af start --rootfs <tar.zst>(the same sha256 verification applies). - Only the first-time CLI auto-install needs network access. If you need the CLIs
fully offline, use a Docker setup you built yourself from the repo’s Dockerfile
with
BAKE_AGENT_CLIS=1(licensing forbids redistributing builds with the CLIs baked in — docs/log/35 §35.4.1).
Git provider OAuth (GitHub / Bitbucket) — optional
Section titled “Git provider OAuth (GitHub / Bitbucket) — optional”To clone/push private repos, each user connects their own GitHub or Bitbucket from the Console (⚙ Settings → Git hosting). Pasting a Personal Access Token / app token works with no configuration at all. To also enable the one-click “Connect via OAuth” buttons, register your OAuth app in the Console:
Tenant settings → Integrations → Git provider OAuth
There is no environment variable for this any more (docs/log/71). GITHUB_OAUTH_CLIENT_ID
and BITBUCKET_OAUTH_KEY/_SECRET are not read: the app is a per-tenant row, so it
takes effect immediately and does not need workspaces restarted.
| Provider | What to enter | Setup |
|---|---|---|
| GitHub (device flow) | client_id only |
Create an OAuth App (GitHub → Settings → Developer settings → OAuth Apps) with “Enable Device Flow” ON. The client_id is not a secret, and device flow needs no callback URL, so it works as-is on localhost. |
| Bitbucket (auth code grant) | Key + Secret | Create an OAuth consumer whose Callback URL exactly equals <PUBLIC_BASE_URL>/api/oauth/bitbucket/callback. Set PUBLIC_BASE_URL to how you reach the Console (e.g. http://localhost:8099) — the screen shows the exact URL to paste. |
A native install runs AUTH=dev (one fixed user, no sign-in), and that user is a
super_admin, so the tenant settings screen is reachable straight after af start
with nothing else to configure.
Token paste keeps working whether or not you do this, so OAuth is purely a convenience. GitHub’s device flow is the easiest path for a single-user native install because it needs no callback.
Text-to-speech (TTS / Zundamon) — optional
Section titled “Text-to-speech (TTS / Zundamon) — optional”Reading chat replies aloud (enable it in the Console under Settings > “Read aloud”;
off by default) needs no audio setup on the WSL/Linux side: playback happens in
your browser (on the Windows side under WSL2). All that is required is that the CP
can reach the VOICEVOX synthesis engine over HTTP (default
http://127.0.0.1:50021). The engine is not bundled with the package or the
rootfs. Provide it in either of the following ways (leaving it out does not affect
any other feature).
A. Run VOICEVOX on the Windows side (WSL2)
Section titled “A. Run VOICEVOX on the Windows side (WSL2)”Launching the VOICEVOX Windows app exposes the
same HTTP API at 127.0.0.1:50021 on the Windows side.
-
With WSL2 in mirrored networking mode (
[wsl2]→networkingMode=mirroredin%UserProfile%\.wslconfig, Windows 11 22H2+), WSL reaches it atlocalhost:50021, so it works as-is withaf start— no configuration. -
In the default NAT mode, WSL cannot reach the Windows loopback. Run the standalone engine (the Windows build of VOICEVOX ENGINE) with
run.exe --host 0.0.0.0, and point at the Windows host IP as seen from WSL (ip route | awk '/^default/{print $3}') — Windows Firewall must allow inbound connections from the vEthernet (WSL) interface:Terminal window AF_VOICEVOX_URL=http://<windows-host-ip>:50021 af start
B. Run the engine directly inside WSL/Linux
Section titled “B. Run the engine directly inside WSL/Linux”Download and extract the Linux (CPU) build of
VOICEVOX ENGINE and start it with
./run (default 127.0.0.1:50021, ~1GB resident, no Docker needed). The default
URL matches, so af start needs no changes.
Changing the URL / port
Section titled “Changing the URL / port”The engine location is passed to the CP via the AF_VOICEVOX_URL env var (a full
URL including the port). af passes your shell environment through to the CP, so
just set it and start:
AF_VOICEVOX_URL=http://127.0.0.1:50022 af startWhen running under systemd (below), add Environment=AF_VOICEVOX_URL=... to the
unit’s [Service] section.
- While the engine is down, only speech fails (with the
autosetting it falls back to AWS Polly if configured), and it recovers from the next sentence once the engine is up. - To use AWS Polly, set
AF_POLLY_REGIONplus standard AWS credentials visible to the CP process (e.g.~/.aws).
Image generation on a ComfyUI on your network — optional
Section titled “Image generation on a ComfyUI on your network — optional”generate_image normally runs on the member’s own CLI plan (Codex / Antigravity),
and the fleet’s own GPU engines exist on the AWS ecs-ec2 target only. On native
you can instead point the CP at a ComfyUI you already run on your network — the
GPU machine, the gaming PC, the Windows side of WSL2. The CP relays to it; no session
needs to know where it is. (Nothing is bundled: leaving this out affects nothing
else.)
Opening ComfyUI to the network
Section titled “Opening ComfyUI to the network”The server build takes --listen, which is what makes it answer on more than
loopback:
python main.py --listen 0.0.0.0 --port 8188The Windows firewall (or the host’s) must allow inbound 8188 from the machine
running the CP. ComfyUI Desktop (Windows) is untested here — the server build’s
--listen is the path this page describes; the Desktop app’s equivalent setting has
not been confirmed.
Pointing the CP at it
Section titled “Pointing the CP at it”AF_COMFY_URL=http://<host>:8188 af startA full URL including the port, as the CP sees it (e.g.
http://192.168.1.20:8188). Under systemd, add
Environment=AF_COMFY_URL=... to the unit’s [Service] section. It is read once
at startup, so changing it is a restart. A super admin can instead set the URL and key
under Admin → Inference engines → LAN ComfyUI in the Console. That needs no restart, and a
URL saved there wins over AF_COMFY_URL and never receives AF_COMFY_API_KEY
(guide/operate/08-image-engine.md).
Optionally set AF_COMFY_API_KEY as well. The CP then sends it as
Authorization: Bearer — on generation calls and on the health check. ComfyUI
has no authentication of its own, so this is only worth anything with a reverse
proxy in front of it that checks the value, and that proxy must let
/system_stats through with the same bearer or the engine never looks healthy.
For an external engine declared some other way than AF_COMFY_URL, the same bearer
is AF_ENGINE_API_KEY_<KEY> — the row’s key upper-cased, e.g.
AF_ENGINE_API_KEY_LLM (ADR 0079 decision 11). It is read for external rows only:
an engine this deployment runs keeps its key in SSM, and a borrowed one buys its
own per session. On the row AF_COMFY_URL creates, AF_COMFY_API_KEY wins.
More than one engine row
Section titled “More than one engine row”AF_COMFY_URL writes exactly one row, keyed image. A second ComfyUI, an
OpenAI-compatible image server, or a LAN ComfyUI kept next to a borrowed
image role each need a row of their own, declared in the inline engine table —
one JSON document with an engines array, read once at startup like the
variables above:
AF_ENGINES_JSON='{"engines":[ {"key":"comfy-lan","api":"images","provider":"comfy", "url":"http://192.168.1.20:8188","health":"/system_stats","lifecycle":"external"}, {"key":"oai-image","api":"images","provider":"openai-compat", "url":"http://192.168.1.21:8000","health":"/v1/models","lifecycle":"external"}]}' af startThe providers a CP can drive are comfy and openai-compat; a row naming any
other is shown marked in the admin panel, not dropped. A bearer for a row is
AF_ENGINE_API_KEY_<KEY> (the key upper-cased, non-alphanumerics folded to _).
Which row draws is the member’s Settings › Agents › “Image provider order”, or
the provider a request names. The procedure and the caveats are in
guide/operate/08-image-engine.md.
Registering the models by hand
Section titled “Registering the models by hand”There is no ingest job here — nothing copies files for you. What the CP can do is
read the names: “Look at this engine’s files” on the row asks ComfyUI once for
the checkpoints, LoRAs and VAEs its loaders list and offers each as a candidate;
the row is still written by your press, and the family is still yours to choose.
Register each model in the Console under Admin → Inference engines with its id,
its base_model and its file names, spelled exactly as ComfyUI’s loaders list them.
🔴 Put the files directly under ComfyUI’s type folders — checkpoints/,
diffusion_models/, clip/, vae/, loras/. A file in a subfolder
(checkpoints/sdxl/x.safetensors) is handed to the loader as x.safetensors and
fails with Value not in list. Subfolders are not supported yet.
While it is down
Section titled “While it is down”The CP does not own this engine: it never starts or stops it, and it does not wait
for it. A request while ComfyUI is down is refused immediately with
503 engine_unavailable, naming the URL it tried. Health is GET /system_stats,
which is also what the Admin panel’s “warm” mark checks the moment you open it —
there is no uptime history for an engine the CP does not run, and no cost is
attributed to it.
The reader-facing version of all of this, including the network caveat, is guide/operate/08-image-engine.md.
Borrowing another deployment’s engines — optional
Section titled “Borrowing another deployment’s engines — optional”If you already run an Agent Fleet on AWS with GPU engines, this native
deployment can use them instead of running anything locally. That deployment goes
on doing the work — deciding a GPU is wanted, buying the instance, loading the model,
letting it go — and this CP relays. Both roles are available (llm and image),
and nothing has to be installed or opened on the far side.
Pointing the CP at the far fleet
Section titled “Pointing the CP at the far fleet”AF_REMOTE_ENGINE_URL=https://af.example.com \AF_REMOTE_ENGINE_TOKEN=afei_... \ af startAF_REMOTE_ENGINE_URL— the far fleet’s base URL, with no path. Setting it is what turns borrowing on; unset, nothing about this deployment changes.AF_REMOTE_ENGINE_TOKEN— anafei_…issuing token from the far deployment (below). Both or neither: a URL with no token borrows nothing and says which half is missing at boot.AF_REMOTE_ENGINE_KEYS— optional, comma-separated role names (imagefor just that one). Empty borrows every role the far fleet offers.
Under systemd, add them as Environment= lines in the unit’s [Service] section.
All three are read once at startup, so changing one is a restart.
🔴 Do not point AF_COMFY_URL at the far fleet’s image route instead. That row
is health-checked from here; the check lands on the far gateway, records demand
and buys a GPU instance, and then fails anyway — it allows five seconds against a cold
start of minutes.
The token
Section titled “The token”The far deployment mints a per-membership issuing token that opens two routes over there and nothing else: buy a session-scoped engine token, and list the engines on offer. No git, no MCP, no memos, no API.
🔴 Never use a person’s issuing token, your own included. It is derived deterministically from the far deployment’s signing master, so a single one cannot be invalidated — revoking it means rotating that master, which is shared with the git, memo and schedule tokens and therefore logs out that entire fleet.
Use a membership that exists for nothing else. Today, reading its token is:
- On the far deployment, invite a member with a real address its sign-in provider will authenticate, used only for this.
- Sign in as that member once and open a terminal in its workspace.
- Read
AF_ENGINE_ISSUE_TOKENfrom the container’s environment. - Stop that workspace; it never needs to run again.
There is no shortcut: the value exists only inside that membership’s own container, and no administrative route starts another member’s workspace or opens a session in one. Revoking the borrower is deleting that membership — it is resolved live on every request, so access stops at the next one.
What you can and cannot do from here
Section titled “What you can and cannot do from here”Which engines exist and which models they offer is read from the far deployment’s
catalogue every 10 minutes, so the rows appear on the first fetch that succeeds
rather than at boot. A far fleet that is unreachable when the CP starts leaves the
launch menu without those models, and the only signal is a CP log line
(is borrowed from … on success, reading the borrowed catalogue from … failed
otherwise). A role this CP already serves is not borrowed.
The catalogue is read-only here. Enabling or disabling a model, registering
one, forgetting one, ingesting one and editing “excluded from every image” all
answer 400 engine_not_ours; they are done in the far deployment’s own Admin
panel. This side keeps on / off only — “off” closes the route here and does
nothing to the far fleet’s instance — and “on demand” is refused.
The first request is a cold start over there
Section titled “The first request is a cold start over there”The far deployment buys the instance on demand, so the first request after that pays
for the whole start. Its own measured cold starts are 527 s for llm and 165 s
for image (ADR 0071); the borrowed figure has not been measured — two holds,
two retry loops and an internet round trip sit in between.
While that runs, a non-streaming request is held about 45 s by the far side, which
then answers 503 engine_waking with a Retry-After; this CP holds a borrowed row
for 75 s and, if its own hold expires first, still answers engine_waking rather
than engine_unavailable — the image tool retries the first for up to 16 minutes
and never retries the second. Streaming requests are covered by a heartbeat every
10 s, bounded by AF_ENGINE_WAKE_TIMEOUT (900 s).
The reader-facing version — the panel’s fields, who pays, and what is recorded for a borrowed picture — is guide/operate/09-borrowed-engine.md.
systemd user unit (run as a service; systemd is on by default in WSL2)
Section titled “systemd user unit (run as a service; systemd is on by default in WSL2)”Instead of keeping af start in the foreground, run it as a systemd user
service (auto-restart, survives your shell). ~/.config/systemd/user/agent-fleet.service:
[Unit]Description=Agent Fleet (native)
[Service]ExecStart=%h/.local/bin/af startRestart=on-failure# Lets the Console's "restart to apply" button (auto-update, below) find this# unit; %N expands to the unit name (agent-fleet).Environment=AF_SYSTEMD_UNIT=%N
[Install]WantedBy=default.target%h expands to your home directory. The ExecStart path above matches the
one-liner install (which symlinks ~/.local/bin/af). If you extracted the
tar by hand instead, point it at that copy — e.g.
ExecStart=%h/agent-fleet-native-<v>-linux-<arch>/af start (use
readlink -f "$(command -v af)" to find the real path).
systemctl --user daemon-reload && systemctl --user enable --now agent-fleetsystemctl --user status agent-fleet # expect: Active: active (running)- Stop the foreground
af startfirst if it is still running — otherwise the service fails to bind port 8099 (double start). - To keep it running after you close the WSL session:
loginctl enable-linger "$USER". - Logs:
journalctl --user -u agent-fleet -f. - If
systemctl --userreportsFailed to connect to bus, user systemd is not running — on WSL2 enable it via/etc/wsl.conf([boot]/systemd=true), thenwsl.exe --shutdownand reopen.
Automatic updates
Section titled “Automatic updates”af update fetches the latest release, verifies it (sha256) and stages it beside
the current version, then re-points ~/.local/bin/af. It never restarts a
running control-plane — the new version takes effect only when you restart, so
live agent sessions are never dropped without your say-so. User data under
WS_DATA is untouched; a new workspace image (rootfs) is fetched lazily on the
next start.
af update --check # report whether a newer release exists (no download)af update # download + verify + stage the latest (or AF_VERSION-pinned)The one-liner installer enables a daily user timer that runs af update
automatically (stage only). Opt out at install time with AF_NO_AUTOUPDATE=1, or
later:
systemctl --user disable --now agent-fleet-update.timer # stop auto-stagingsystemctl --user list-timers agent-fleet-update.timer # when it next runsApplying a staged update (picking up the new version) is a restart of the main service, which you trigger when convenient:
- From the Console — when a newer version is staged, a “restart to apply” control appears (Settings → Toolchains). It warns how many sessions are running before it restarts, so you can wait until the fleet is idle.
- From the shell —
systemctl --user restart agent-fleet(or Ctrl-C +af startfor a foreground run).
To pin a version (and stop auto-updates advancing past it), set
Environment=AF_VERSION=<v> in both the agent-fleet and
agent-fleet-update units; af update then treats that version as the target
and does nothing once you are on it.
Restarting the service stops the control-plane and the workspaces it supervises (under systemd the whole unit cgroup is stopped), so in-flight agent sessions are interrupted. That is why applying is a deliberate action, not automatic.
Limitations (differences from the Docker setup)
Section titled “Limitations (differences from the Docker setup)”- Single user only (AUTH=dev fixed). There is no container isolation; all workspaces run in bubblewrap sandboxes under the same OS user.
- No memory limits (no cgroups are used, so
WS_MEMORYhas no effect). Heavy builds can take down the whole host. - The browser pane’s chromium is downloaded on first use (~200MB, pinned
version). The SUID sandbox is unavailable under bubblewrap, and on some systems
chromium’s namespace sandbox does not work either. In that case start with
WS_ENV=AF_CHROMIUM_NO_SANDBOX=1 af start— the variable must be forwarded into the workspace viaWS_ENV(a comma-separatedKEY=VALlist); setting it bare onaf startonly reaches the control plane, not the workspace. (A trade-off acceptable for the pane use case, which only connects to localhost; do not use it to browse untrusted sites.)
./af reset # dev user's data only (DB and rootfs are kept)./af reset --all # all of WS_DATA (full wipe incl. the extracted rootfs)Uninstall
Section titled “Uninstall”# 1. If you set up the systemd user unit, stop and remove it firstsystemctl --user disable --now agent-fleet 2>/dev/nullsystemctl --user disable --now agent-fleet-update.timer 2>/dev/null # auto-update timerrm -f ~/.config/systemd/user/agent-fleet.service \ ~/.config/systemd/user/agent-fleet-update.service \ ~/.config/systemd/user/agent-fleet-update.timer# otherwise just stop `af start` with Ctrl-C
# 2. Wipe all data (skip to keep it — data is separate from the program)./af reset --all
# 3. Remove the program# installed via install.sh:rm -f ~/.local/bin/af && rm -rf ~/.local/opt/agent-fleet# extracted from a tar directly: just delete the extracted directoryIf af is already gone, remove the data manually — Go module caches inside
workspace homes are write-protected, so restore write permission first:
chmod -R u+w ~/.local/share/agent-fleet && rm -rf ~/.local/share/agent-fleet
(use your $WS_DATA path if you overrode it).
Disclaimer — autonomous agent execution
Section titled “Disclaimer — autonomous agent execution”The agents run commands, edit files, and commit/push on your behalf — including
unattended (scheduled runs that wake a stopped workspace), in
permission-bypassing modes, and through shell / SSM sessions that run the
strings you send verbatim. Such actions can be destructive or irreversible and can
incur charges on your AI-provider and cloud accounts. You are solely responsible for
the workspaces, credentials, repositories, and infrastructure you connect, and for
reviewing what the agents do; use least-privilege credentials, keep backups, and
prefer the approval gates for destructive actions. This software is distributed under
the Apache License 2.0 and is provided “AS IS”, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND; the authors accept no liability for any damage, data loss, or cost
arising from its use. See LICENSE, and NOTICE for bundled-OSS attribution.
Official releases come only from https://github.com/k-k1/agent-fleet-dist. If you
pass this package on, Apache-2.0 §4(d) requires you to keep the notices in NOTICE
with it — that URL included — so the next recipient can find the original.
エージェント向け
この指示をエージェントに送ると、このページを読ませられます。
https://agent-fleet.org/docs/ja/operate/runbooks/native/index.md を読んで