Kimi Code CLI Agent
Autonomous coding agent, invokable as a model through LiteLLM/Open WebUI, powered by the user's own Kimi/Moonshot subscription (not the Moonshot pay-per-token API).
Pipeline
Open WebUI -> LiteLLM (model: kimi-agent) -> kimi-agent container (HTTP wrapper)
-> kimi-code CLI -> Moonshot cloud (user's Kimi subscription, via `kimi login`)
Location
~/agap_git/openai/kimi-agent/ — built as part of the openai compose project (alongside litellm, open-webui).
| File | Purpose |
|---|---|
Dockerfile |
node:22-slim + npm install -g @moonshot-ai/kimi-code |
server.js |
Minimal OpenAI-compatible wrapper (/v1/models, /v1/chat/completions) — no external deps, built-in http/child_process/crypto/fs only. Maps each Open WebUI conversation to a persistent kimi session (see below). |
Conversation <-> session continuity
Open WebUI (like any OpenAI chat client) re-sends the entire message history on every turn, but the CLI's -p mode is stateless — one prompt in, one answer out. The wrapper bridges the two so an Open WebUI thread maps 1:1 onto a persistent kimi session instead of starting cold each turn.
Per request the wrapper:
- Keys the conversation by SHA-256 hashing the user/assistant history up to but excluding the newest user turn (a "content-hash chain"). This depends only on the standard
messagesarray, so it survives LiteLLM sitting in the middle — no reliance on achat_idthat LiteLLM may strip. - Looks the key up in a persisted
key -> {convId, sessionId, dir}map:- Hit -> resumes the same kimi session with
kimi -r <session_id> -p "<new message only>", in that conversation's own working dir. - No prior history -> brand-new conversation: fresh
kimi -p, new dir +convId. - Miss with prior history (wrapper restarted / thread edited) -> reseeds a fresh session with the full transcript so continuity is preserved.
- Hit -> resumes the same kimi session with
- Captures the
session_idfrom the stream-jsonsession.resume_hintmeta line and stores the forward mapping (history + this reply -> session), which is exactly what the next turn will hash to.
Per-conversation working dirs: each conversation gets its own directory /workspace/conversations/<convId>/, used as the CLI cwd. File state is therefore isolated between threads and persists across turns within a thread.
State file: key -> session map lives at /workspace/.kimi-agent/sessions.json (host-backed, survives restarts; LRU-capped at 1000 entries).
Both the wrapper-level session map and kimi's own session store (/root/.kimi-code, named volume) persist across container rebuilds, so live Open WebUI threads keep working after a redeploy.
Docker Compose
kimi-agent:
build: ./kimi-agent
container_name: kimi-agent
volumes:
- /home/alvis/kimi-workspace:/workspace
- kimi-agent-home:/root/.kimi-code
restart: unless-stopped
- Mount scope is deliberately narrow: only
~/kimi-workspaceis mounted as/workspace, not the full home directory. The agent runs prompts via-pwith no interactive approval gate (see below), so it must never see~/.ssh,~/.claude,~/bin/bw, etc. kimi-agent-homeis a named volume persisting/root/.kimi-code(the OAuth session fromkimi loginand kimi's own session history) across container restarts/rebuilds.- No ports are published — only reachable from other containers on the
openai_defaultnetwork (i.e.litellm), not from the host or LAN.
LiteLLM registration
In litellm-config.yaml:
- model_name: kimi-agent
litellm_params:
model: openai/kimi-agent
api_base: http://kimi-agent:8000/v1
api_key: dummy
Shows up automatically in Open WebUI since OPENAI_API_BASE_URL there already points at http://host.docker.internal:4000/v1 (LiteLLM).
Auth (one-time, manual)
docker exec -it kimi-agent kimi login
Prints a https://www.kimi.com/code/authorize_device?user_code=... URL — open it and authorize with the Kimi subscription account. Session persists in the kimi-agent-home volume. Check status any time with:
docker exec kimi-agent kimi doctor
Key CLI gotchas
-p(non-interactive single-prompt mode) cannot be combined with--yoloor--auto— and doesn't need to be. In-pmode there's no TTY to prompt for approval, so tool calls (file read/write, shell) execute automatically already.- Resume a session with
kimi -r <session_id> -p "...". With--output-format stream-json, the CLI emits a{"role":"meta","type":"session.resume_hint","session_id":"session_..."}line the wrapper parses to learn/track the id. - The
node:22-slimimage has nocurl— test the wrapper with Node'shttpmodule (e.g.docker exec kimi-agent node -e '...').
Rebuild / restart
cd ~/agap_git/openai
docker compose up -d --build kimi-agent