Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
644f521
Add kimi-cli agent driver
simone-stacks Jul 21, 2026
d319034
Add kimi-cli Dockerfile layer and kimi_cli_version pin
simone-stacks Jul 21, 2026
4778400
Map kimi-cli in the dashboard driver column
simone-stacks Jul 21, 2026
44ed227
Document the kimi-cli driver
simone-stacks Jul 21, 2026
f2a9758
Add kimi-cli driver tests and fixtures
simone-stacks Jul 21, 2026
b335e18
Add upstream clone retry helper
simone-stacks Jul 30, 2026
654422c
Fix upstream clone race condition
radubahmata Jul 30, 2026
ece72b2
Do not exit 0 on failed session end push
radubahmata Aug 2, 2026
96d42b7
Merge pull request #1 from BowTiedRadone/fix/kimi-driver
simone-stacks Aug 4, 2026
a980ddf
Add qwen-cli agent driver
simone-stacks Aug 4, 2026
f6ad31e
Add qwen-cli Dockerfile layer and qwen_cli_version pin
simone-stacks Aug 4, 2026
8ba651e
Map qwen-cli in the dashboard driver column
simone-stacks Aug 4, 2026
a5678ce
Document the qwen-cli driver
simone-stacks Aug 4, 2026
8aed016
Add qwen-cli driver tests and fixtures
simone-stacks Aug 4, 2026
22b150c
Forward QWEN_CONTEXT_WINDOW into agent containers
simone-stacks Aug 4, 2026
0702ba6
Install qwen-cli from the official standalone archive
simone-stacks Aug 4, 2026
1ed734f
Detect qwen quota exhaustion reported as successful results
simone-stacks Aug 4, 2026
53403af
Read kimi token usage from the session wire.jsonl
simone-stacks Aug 4, 2026
968077c
Fix kimi wire mark portability on macOS
simone-stacks Aug 18, 2026
24b3ae8
Subtract folded cache reads from qwen input tokens
simone-stacks Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@

## Unreleased

- **Qwen Code CLI driver.** New `qwen-cli` driver
(`lib/drivers/qwen-cli.sh`) implements the full interface for
Alibaba's Qwen Code CLI: headless mode with
`qwen -p --output-format stream-json --yolo`, activity parsing
from stream-json tool calls, fatal/retriable error detection,
and reasoning effort support via `model.reasoningEffort`.
stream-json carries usage but no cost, so cost comes from the
swarmfile `pricing` map. The CLI folds cache reads into
`input_tokens`; the driver subtracts them so `tok_in` matches
Claude's disjoint buckets and cache reads are not billed twice.
- **Qwen auth modes.** Qwen agents authenticate via
`"auth": "apikey"` (host `QWEN_API_KEY`/`DASHSCOPE_API_KEY`
forwarded as `DASHSCOPE_API_KEY`; the driver synthesizes
`~/.qwen/settings.json` with an openai-protocol provider entry,
so no login state is needed — the CI-friendly path) or
`"auth": "oauth"` (read-only mount of the host `~/.qwen` home
dir after `qwen` `/auth` or `bl config agent`; each container
copies it to a writable location on startup). Auto-detection
when both are present. Set the per-group `base_url` to match
the account region: ModelStudio keys are region-scoped and the
CLI's built-in default endpoint is cn-beijing, which returns
401 for international accounts.
- **Build: `qwen_cli_version` swarmfile field.** Pins the Qwen
Code CLI version installed in the agent image, mirroring
`codex_cli_version`.
- **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; token usage is summed
from the session `wire.jsonl` the CLI persists (one
`usage.record` per LLM step), attributed by run-start mark so a
staged host home or an earlier retry is never counted. Cache
creation folds into `tok_in` (billed as a cache miss upstream);
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.
- **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
26 changes: 26 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,31 @@ 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

# --- Qwen Code CLI ---
ARG QWEN_CLI_VERSION=
# Official standalone archive (no Node required). Installs to
# /usr/local/bin so the agent user finds qwen on PATH without the
# script editing anyone's shell rc; "latest" when unpinned.
RUN if echo ",$SWARM_AGENTS," | grep -q ",qwen-cli,"; then \
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh -o /tmp/qwen-install.sh \
&& QWEN_INSTALL_ROOT=/usr/local QWEN_NO_MODIFY_PATH=1 \
QWEN_INSTALL_METHOD=standalone \
QWEN_INSTALL_VERSION="${QWEN_CLI_VERSION:-latest}" \
bash /tmp/qwen-install.sh \
&& rm /tmp/qwen-install.sh; \
fi
USER agent

# Trust mounted bare repos and allow file:// transport for submodules.
Expand All @@ -60,6 +85,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`, `qwen-cli`, `fake` (test double). See
[USAGE.md](USAGE.md#writing-a-new-driver) for the full interface
and guide to writing a new driver.
118 changes: 116 additions & 2 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ 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). |
| `QWEN_API_KEY` | | ModelStudio/DashScope API key (for Qwen Code CLI driver; `DASHSCOPE_API_KEY` also accepted). |
| `QWEN_HOME` | `~/.qwen` | Path to Qwen home dir (login state and `settings.json`). |
| `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 +81,20 @@ 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.
- Qwen Code CLI: `low`, `medium`, `high`, `xhigh`, `max` (written to
`model.reasoningEffort`; on DashScope-backed qwen models any set tier
currently collapses to `enable_thinking: true`).
Comment on lines +87 to +89

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, it says every effort tier on DashScope-backed Qwen models collapses to enable_thinking: true. Qwen Code CLI explicitly special-cases qwen3.8-max, the driver's default model, and passes all five tiers through as reasoning_effort. Other Qwen models may use the boolean enable_thinking mapping.

Document the qwen3.8-max pass-through behavior separately from the fallback behavior used by other Qwen models. Update the matching comment at https://github.com/simone-stacks/claude-swarm/blob/24b3ae8ad55c59ebd39e50e0693d478dc42f0ee1/lib/drivers/qwen-cli.sh#L318-L323 at the same time.

- 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`,
`qwen_cli_version`, `title`, `tag`, `pricing`, `docker_args`,
`post_process`.

### Interactive profiles

Expand Down Expand Up @@ -603,6 +614,78 @@ 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`.

### Qwen Code CLI

| `auth` value | Credential injected |
|---|---|
| `apikey` | `QWEN_API_KEY`/`DASHSCOPE_API_KEY` forwarded as `DASHSCOPE_API_KEY` |
| `oauth` | Mounts `~/.qwen` (after `qwen` + `/auth` or `bl config agent` on the host) |
| omit | API key if set + home dir if found |

With `apikey` auth the driver synthesizes `~/.qwen/settings.json`
inside the container: one openai-protocol provider entry whose id is
the swarmfile `model` (provider prefix stripped), key read from
`DASHSCOPE_API_KEY`, `security.auth.selectedType: openai` so no
interactive `/auth` is needed. This is the CI-friendly path — no
login state to mount:

```json
{
"driver": "qwen-cli",
"agents": [{
"model": "qwen3.8-max",
"auth": "apikey",
"base_url": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
}]
}
```

Mind the region: ModelStudio keys are region-scoped, and qwen-code's
built-in default endpoint is China (cn-beijing). International
accounts must set `base_url` (as above for a Token Plan key, or
`https://dashscope-intl.aliyuncs.com/compatible-mode/v1` for a
standard pay-as-you-go key) or every request fails with
`401 Invalid API-key`. Optional host-side override:
`QWEN_CONTEXT_WINDOW` (forwarded into the container; adds
`generationConfig.contextWindowSize` to the synthesized provider
entry — e.g. `1000000` for qwen3.8-max's 1M window).

For OAuth auth, configure `~/.qwen` on the host (run `qwen` and use
`/auth`, or `bl config agent --agent qwen-code` for Alibaba Cloud
ModelStudio), then set `"auth": "oauth"` in your swarm config. The
home dir is bind-mounted read-only; each container copies it to a
writable location on startup. Override the source path with
`QWEN_HOME=/path/to/qwen-home`.

### General rules

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

Set the driver globally in `swarm.json`:
Expand Down Expand Up @@ -710,6 +795,35 @@ 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.

### Pinning Qwen Code CLI version

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

```json
{ "qwen_cli_version": "0.21.5" }
```

The value is passed to the official standalone installer
(`install-qwen-standalone.sh`, no Node.js required) 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
2 changes: 2 additions & 0 deletions dashboard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ short_driver() {
claude-code) printf 'claude' ;;
gemini-cli) printf 'gemini' ;;
codex-cli) printf 'codex' ;;
kimi-cli) printf 'kimi' ;;
qwen-cli) printf 'qwen' ;;
*) printf '%s' "${1:-}" ;;
esac
}
Expand Down
12 changes: 10 additions & 2 deletions launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,23 +121,31 @@ 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 qwen_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)
qwen_version=$(jq -r '.qwen_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}"} \
${qwen_version:+--build-arg "QWEN_CLI_VERSION=${qwen_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