Skip to content
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

## Unreleased

- **Kimi Code CLI driver.** New `kimi-cli` driver
(`lib/drivers/kimi-cli.sh`) implements the full interface for
Moonshot AI's Kimi Code CLI: headless mode with
`kimi -p --output-format stream-json`, activity parsing from
stream-json tool calls, fatal/retriable error detection, and
thinking effort support via `KIMI_MODEL_THINKING_EFFORT`.
stream-json carries no usage summary, so cost comes from the
swarmfile `pricing` map.
- **Kimi auth modes.** Kimi agents authenticate via
`"auth": "apikey"` (host `KIMI_API_KEY` forwarded as
`KIMI_MODEL_API_KEY`, which makes the CLI synthesize an in-memory
provider) or `"auth": "oauth"` (read-only mount of the host
`~/.kimi-code` data dir after `kimi login`; each container copies
it to a writable location on startup). Auto-detection when both
are present. A host `KIMI_MODEL_API_KEY` is accepted directly as
the apikey source.
- **Build: `kimi_cli_version` swarmfile field.** Pins the Kimi
Code CLI version installed in the agent image, mirroring
`codex_cli_version`.

## 0.22.0 — 2026-06-08

- **Build: base the agent image on Debian trixie.** Replaces
Expand Down
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ RUN if echo ",$SWARM_AGENTS," | grep -q ",codex-cli,"; then \
&& mkdir -p /home/agent/.codex \
&& chown agent:agent /home/agent/.codex; \
fi

# --- Kimi Code CLI ---
ARG KIMI_CLI_VERSION=
# Installs to /usr/local/bin so the agent user finds kimi on PATH
# without the script editing anyone's shell rc.
RUN if echo ",$SWARM_AGENTS," | grep -q ",kimi-cli,"; then \
curl -fsSL https://code.kimi.com/kimi-code/install.sh -o /tmp/kimi-install.sh \
&& KIMI_INSTALL_DIR=/usr/local KIMI_NO_MODIFY_PATH=1 \
KIMI_VERSION="$KIMI_CLI_VERSION" bash /tmp/kimi-install.sh \
&& rm /tmp/kimi-install.sh; \
fi
USER agent

# Trust mounted bare repos and allow file:// transport for submodules.
Expand All @@ -60,6 +71,7 @@ RUN git config --global --add safe.directory '*' \

COPY --chmod=755 lib/harness.sh /harness.sh
COPY --chmod=755 lib/interactive.sh /interactive.sh
COPY --chmod=644 lib/upstream-clone.sh /upstream-clone.sh
COPY --chmod=755 lib/signing.sh /signing.sh
COPY --chmod=755 lib/activity-filter.sh /activity-filter.sh
COPY --chmod=644 lib/agent-system-prompt.md /agent-system-prompt.md
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,6 @@ Each driver implements a fixed role interface:
| `agent_activity_jq` | jq filter for activity display |

Built-in drivers: `claude-code` (default), `gemini-cli`,
`codex-cli`, `fake` (test double). See
`codex-cli`, `kimi-cli`, `fake` (test double). See
[USAGE.md](USAGE.md#writing-a-new-driver) for the full interface
and guide to writing a new driver.
53 changes: 51 additions & 2 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Credentials stay as env vars (not in shell history).
| `OPENAI_API_KEY` | | OpenAI API key (for Codex CLI driver). |
| `CODEX_AUTH_JSON` | `~/.codex/auth.json` | Path to Codex auth file (ChatGPT subscription). |
| `GEMINI_API_KEY` | | Google API key (for Gemini CLI driver). |
| `KIMI_API_KEY` | | Moonshot API key (for Kimi Code CLI driver). |
| `KIMI_CODE_HOME` | `~/.kimi-code` | Path to Kimi data dir (OAuth login state). |
| `SWARM_CONFIG` | | Path to swarmfile (or place `swarm.json` in repo root). |
| `SWARM_TITLE` | | Dashboard title override. |
| `SWARM_SKIP_DEP_CHECK` | | Set to `1` to silence dependency version warnings. |
Expand Down Expand Up @@ -77,13 +79,16 @@ Per-group fields in `swarm.json` `agents` array:

- Claude Code: `low`, `medium`, `high`, `max` (Opus only).
- Codex CLI: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`.
- Kimi Code CLI: `low`, `medium`, `high`, `xhigh`, `max`, but the set a
model actually accepts is model-dependent — `kimi-for-coding` rejects
`medium` and `xhigh` with a 400; use `low`, `high`, or `max` there.
- Gemini CLI: ignored.

Top-level fields: `prompt`, `setup`, `max_idle` (default: `3`),
`max_retry_wait`, `driver`, `inject_git_rules`,
`git_user` (`name`, `email`, `signing_key`),
`claude_code_version`, `codex_cli_version`, `title`, `tag`,
`pricing`, `docker_args`, `post_process`.
`claude_code_version`, `codex_cli_version`, `kimi_cli_version`,
`title`, `tag`, `pricing`, `docker_args`, `post_process`.

### Interactive profiles

Expand Down Expand Up @@ -603,6 +608,35 @@ then set `"auth": "chatgpt"` in your swarm config:
The auth file is bind-mounted read-only into containers.
Override the path with `CODEX_AUTH_JSON=/path/to/auth.json`.

### Kimi Code CLI

| `auth` value | Credential injected |
|---|---|
| `apikey` | `KIMI_API_KEY` via the `KIMI_MODEL_*` env provider |
| `oauth` | Mounts `~/.kimi-code` (after `kimi login` on the host) |
| omit | API key if set + data dir if found |

The CLI does not read `KIMI_API_KEY` from the shell environment.
The driver forwards it as `KIMI_MODEL_API_KEY` instead, which makes
the CLI synthesize an in-memory provider for the configured model.
With `apikey` auth the swarmfile `model` is used as the API model
id (the part after `/` in aliases like `kimi-code/kimi-for-coding`),
not looked up as a config alias.

For OAuth auth, run `kimi login` on the host, then set
`"auth": "oauth"` in your swarm config:

```json
{
"driver": "kimi-cli",
"agents": [{ "model": "kimi-code/kimi-for-coding", "auth": "oauth" }]
}
```

The data dir is bind-mounted read-only; each container copies it to
a writable location on startup. Override the source path with
`KIMI_CODE_HOME=/path/to/kimi-home`.

### General rules

Groups with `api_key` or `auth_token` ignore the `auth`
Expand Down Expand Up @@ -664,6 +698,7 @@ Built-in drivers:
| `claude-code` | `claude` | Yes |
| `gemini-cli` | `gemini` | |
| `codex-cli` | `codex` | |
| `kimi-cli` | `kimi` | |
| `fake` | (none) | Test double for unit testing |

Set the driver globally in `swarm.json`:
Expand Down Expand Up @@ -710,6 +745,20 @@ The value is forwarded to `npm install -g @openai/codex@<ver>`
inside the image build. Leave the field unset (or empty) to
keep the default "latest published release" behavior.

### Pinning Kimi Code CLI version

By default the Docker image installs the latest Kimi Code CLI.
To pin a specific version, set `kimi_cli_version` in the
swarmfile:

```json
{ "kimi_cli_version": "0.28.0" }
```

The value is passed to the official install script
(`code.kimi.com/kimi-code/install.sh`) inside the image build.
Leave the field unset (or empty) to install the latest release.

### Writing a new driver

Create `lib/drivers/<name>.sh` implementing these functions:
Expand Down
1 change: 1 addition & 0 deletions dashboard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ short_driver() {
claude-code) printf 'claude' ;;
gemini-cli) printf 'gemini' ;;
codex-cli) printf 'codex' ;;
kimi-cli) printf 'kimi' ;;
*) printf '%s' "${1:-}" ;;
esac
}
Expand Down
10 changes: 8 additions & 2 deletions launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,23 +121,29 @@ compute_swarm_agents() {
# produces exit-127 on first session -- see harness's `agent exited with code
# 127` retry path).
build_image() {
local swarm_agents cc_version codex_version
local swarm_agents cc_version codex_version kimi_version
swarm_agents=$(compute_swarm_agents "$CONFIG_FILE")
cc_version=$(jq -r '.claude_code_version // empty' "$CONFIG_FILE" 2>/dev/null || true)
codex_version=$(jq -r '.codex_cli_version // empty' "$CONFIG_FILE" 2>/dev/null || true)
kimi_version=$(jq -r '.kimi_cli_version // empty' "$CONFIG_FILE" 2>/dev/null || true)
echo "--- Building agent image (agents: ${swarm_agents}) ---"
docker build -t "$IMAGE_NAME" \
--build-arg "SWARM_AGENTS=${swarm_agents}" \
${cc_version:+--build-arg "CLAUDE_CODE_VERSION=${cc_version}"} \
${codex_version:+--build-arg "CODEX_CLI_VERSION=${codex_version}"} \
${kimi_version:+--build-arg "KIMI_CLI_VERSION=${kimi_version}"} \
-f "$SWARM_DIR/Dockerfile" "$SWARM_DIR"
}

create_bare_repo() {
local label="${1:-bare repo}"
echo "--- Creating ${label} ---"
rm_docker_dir "$BARE_REPO"
git clone --bare "$REPO_ROOT" "$BARE_REPO"
# Local clones hardlink packed objects by default. The bare repo is mounted
# through Docker Desktop while the source stays on the host. Keep its
# object storage independent so receive-pack does not depend on inodes
# shared across the bind-mount boundary.
git clone --bare --no-hardlinks "$REPO_ROOT" "$BARE_REPO"
git -C "$BARE_REPO" branch agent-work HEAD 2>/dev/null || true
git -C "$BARE_REPO" symbolic-ref HEAD refs/heads/agent-work
git -C "$BARE_REPO" config core.sharedRepository world
Expand Down
Loading
Loading