devv-eve 9e47b83f02 feat(agent): add Kimi CLI as agent runtime (#1400)
* feat(agent): add Kimi CLI as agent runtime

Adds support for Moonshot AI's Kimi Code CLI (https://github.com/MoonshotAI/kimi-cli)
as a new agent runtime, alongside Claude, Codex, OpenCode, OpenClaw, Hermes,
Gemini, Pi, Cursor and Copilot.

Kimi Code CLI implements the standard Agent Client Protocol (ACP) via the
`kimi acp` subcommand, so the new `kimiBackend` reuses the existing
hermesClient JSON-RPC transport in the agent package — only the binary,
client identity, log prefix, and tool-name extraction differ.

Wiring:
- server/pkg/agent: new kimiBackend + kimi_test.go; registered in New(),
  LaunchHeader map, and the supported-types coverage test.
- server/internal/daemon/config.go: probes `kimi` (overridable via
  MULTICA_KIMI_PATH / MULTICA_KIMI_MODEL).
- server/internal/daemon/execenv: writes AGENTS.md as the runtime context
  file (Kimi reads AGENTS.md natively via /init), and writes skills under
  `.kimi/skills/` so they are auto-discovered by the project-level skill
  loader.
- packages/views/runtimes: ProviderLogo gains a Kimi mark.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(agent/kimi): support per-agent model selection via ACP set_model

Wire Kimi into the model dropdown introduced in #1399:

- ListModels gets a 'kimi' case that drives the same ACP
  initialize + session/new handshake as Hermes; both share a new
  discoverACPModels helper and parseACPSessionNewModels parser
  so future ACP backends only need a small provider entry.
- kimiBackend now issues session/set_model after session/new when
  opts.Model is non-empty, mirroring the Hermes flow. Failures
  fail the task instead of silently falling back to Kimi's
  default model — silent fallback would hide that the dropdown
  pick wasn't honoured.

Verified: go build ./..., go test ./pkg/agent/... ./internal/daemon/... ./internal/handler/..., pnpm typecheck and pnpm test (138 passed).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(agent): address code review feedback on Kimi runtime

- Share ACP provider-error sniffer between hermes and kimi. Previously
  only hermes promoted stderr-observed 4xx/5xx into a failed task;
  kimi would report "completed + empty output" when the Moonshot
  upstream rejected a request (expired token, rate limit, …). Rename
  hermesProviderErrorSniffer → acpProviderErrorSniffer and parameterise
  the provider name; wire it into kimiBackend.Execute the same way.
- Rename extractHermesSessionID → extractACPSessionID (shared by all
  ACP backends) so the name matches parseACPSessionNewModels.
- Drop the redundant second argument to kimiToolNameFromTitle; the
  Message struct has only one relevant field (Tool), so passing it
  twice was a dead fallback. Document that the function normalises
  residual capitalised kimi titles not caught by hermesToolNameFromTitle.
- Remove kimi-only cmd.WaitDelay override; the hermes baseline is
  fine for both and divergence adds noise.
- Add TestKimiBackendSetModelFailureFailsTask: fake `kimi acp` binary
  that returns a JSON-RPC error for session/set_model, asserts that
  the task result surfaces status=failed with the model name + upstream
  message and preserves the session id.
- Fix stale agent listings in agent.go / daemon/config.go doc comments
  (missing cursor, gemini, copilot).

All: `go build ./...`, `go vet ./...`, `go test ./pkg/agent/...
./internal/daemon/... ./internal/handler/...` green.

* fix(agent/kimi): pass --yolo so Shell tools don't hang on approval

Kimi's default config has `default_yolo = false`. Every Shell/file-mutating
tool call causes kimi acp to send a `session/request_permission` request
and block (up to 300s) waiting for a response. The daemon's hermesClient
only handles `session/update` notifications — permission requests go
unanswered, the tool call times out, and the UI loop eventually dies
("UI loop timed out"). Observed with the first real kimi task: agent sat
as Live for ~7 minutes before the daemon killed it.

The fix mirrors hermes' HERMES_YOLO_MODE=1 override: pass `--yolo` to
`kimi` so it auto-approves everything. `--yolo` is a top-level flag on
the `kimi` CLI (not a flag on `kimi acp`), so it must come before the
`acp` subcommand in argv. Added to kimiBlockedArgs so user custom_args
can't strip it.

While here, fix a related bug that made kimi tool names show up empty
in the daemon log ("tool #1: "): hermesToolNameFromTitle's fallback
returned `kind` when neither title-with-colon nor kind matched a known
tool. Kimi's ACP `tool_call` emits bare titles like "Shell" or "Read
file" with no `kind` at all, so we'd drop the title on the floor before
kimiToolNameFromTitle ever got a chance to map it. Now: preserve the
title when kind is unclassified; hermes titles always carry a colon so
this branch never fires for hermes.

Tests:
- TestKimiBackendPassesYoloFlag — fake binary that records its argv,
  asserts --yolo comes before acp.
- TestHermesToolNameFromTitle rows for bare kimi-style titles.
- Existing suite green: go build, go vet, full pkg/agent + daemon +
  handler test packages.

* fix(agent/acp): auto-approve session/request_permission from agent

The previous attempt (`kimi --yolo acp`) was a no-op. Inspected the
kimi-cli source: the `acp` Typer subcommand takes no parameters, so
flags on the root `kimi` command are dropped before `acp_main()` runs
— it's impossible to opt into YOLO mode through CLI flags for ACP.

The real fix is on our side: respond to session/request_permission.

ACP is bidirectional. When kimi runs a Shell or file-write tool, it
sends `session/request_permission` (agent → client, JSON-RPC request
with id + method) and waits up to 300s for a response. Our existing
hermesClient.handleLine only dispatched: (id + result/error) →
handleResponse, and (no id + method) → handleNotification. A request
with BOTH id and method fell through and got silently dropped — kimi
timed out, UI loop died, task sat stuck for 7 minutes.

Add handleAgentRequest: for session/request_permission, echo the id
and respond with outcome=selected, optionId=approve_for_session. The
daemon is headless; there's no user to prompt. `approve_for_session`
lets the agent remember the action so subsequent identical calls
(every Shell, every file write) skip the round-trip entirely. For any
other agent → client method, reply with standard -32601 method-not-
found so the agent doesn't block.

Also:
- Add writeMu so request() (main goroutine) and handleAgentRequest
  (reader goroutine) don't interleave JSON frames on stdin.
- Revert the `--yolo acp` flag — it's a no-op, and carrying it in
  kimiBlockedArgs gives the wrong impression that it does something.
  Comment in kimi.go now points at handleAgentRequest as the real fix.

Tests:
- TestHermesClientAutoApprovesPermissionRequest: inject a
  session/request_permission, assert the reply echoes the id and
  carries {outcome: selected, optionId: approve_for_session}.
- TestHermesClientReplesMethodNotFoundForUnknownAgentRequest: confirm
  unknown agent → client methods get JSON-RPC -32601 instead of silence.
- TestKimiBackendInvokesACPSubcommand replaces the yolo-flag assertion
  with a negative assertion: no dead --yolo / --auto-approve / -y on
  argv, since they'd pretend to do something they can't.

All: go build ./..., go vet ./..., go test ./pkg/agent/... green.

* fix(agent/acp): surface kimi tool input/output via content blocks

Kimi-cli emits tool_call and tool_call_update ACP frames with the
input/output inside a `content` array of ContentToolCallContent
blocks (shape: {type:"content", content:{type:"text", text:"..."}}),
not in the hermes-style `rawInput` map / `rawOutput` string. Our
parser only looked at rawInput/rawOutput, so the daemon recorded
empty Input and Output for every kimi tool — the execution-history
UI showed blank terminal panels even for commands that ran fine.

Add extractACPToolCallText() and a fallback in handleToolCallStart /
handleToolCallUpdate: when rawInput is nil / rawOutput is empty, pull
the text out of the content blocks. rawInput / rawOutput still take
precedence so hermes' behaviour is untouched. Terminal /
FileEditToolCallContent blocks are skipped (we have nothing to render
them as — kimi only emits TerminalToolCallContent when the client
advertises terminal capability, which we don't).

Tests:
- TestHermesClientHandleToolCallStartKimiContent — content array →
  Input.text populated.
- TestHermesClientHandleToolCallCompleteKimiContent — multi-block
  content → Output concatenated with newline separator.
- TestHermesClientHandleToolCallRawOutputTakesPrecedence — hermes
  rawOutput still wins when both are present.
- TestExtractACPToolCallText — unit coverage for the helper
  (single/multiple text blocks, terminal-block skip, empty input).

* fix(agent/acp): buffer streaming tool args so Input isn't empty in UI

kimi-cli streams tool args token-by-token via tool_call_update frames
— the initial tool_call carries an empty content block and each
subsequent in_progress update carries the cumulative JSON so far
(`{`, `{"comma`, `{"command": "echo`, …). The final completed update
then carries the tool's stdout, not the args. Observed per kimi-cli
acp/session.py::_send_tool_call{,_part,_result} and confirmed by
driving a real Shell call end-to-end: 10 in_progress frames, last
with `{"command": "echo hello world"}`, then completed with `hello
world\n`.

Our previous handleToolCallStart emitted MessageToolUse on the first
tool_call frame, capturing the empty content — so every kimi tool
appeared in the execution-history UI with a blank input. Output was
correct (fix 4335c198) but command was missing.

Changes:
- hermesClient now tracks pending tool calls per toolCallId. Hermes
  path is unchanged — rawInput is present at tool_call time, so
  emit-immediately-then-flag-emitted still fires on the initial frame.
- kimi path defers MessageToolUse until status=completed / failed.
  tool_call_update in_progress frames update the buffered argsText
  (cumulative, so overwrite); on completion we parse the accumulated
  JSON into Message.Input. Malformed JSON falls back to `{"text": …}`
  so non-JSON tool args still render.
- Orphan completion frames (no matching tool_call seen — e.g. daemon
  restarted mid-task) synthesise ToolUse from the update's own
  title/kind/rawInput so the UI still gets a header.
- extractACPToolCallText now also renders FileEditToolCallContent
  blocks as a compact header ("--- path / +++ path / (edited: N → M
  bytes)"). kimi emits these for Write / StrReplaceFile / Patch when
  the tool's display block is a DiffDisplayBlock.

Tests:
- TestHermesClientKimiStreamingToolCall: empty tool_call + 5 streaming
  in_progress + completed. Asserts no emission until complete, then
  [ToolUse(Input.command="echo hi"), ToolResult(Output="hi\n")].
- TestHermesClientKimiMalformedArgsFallback: non-JSON argsText → falls
  back to Input.text.
- TestHermesClientHandleToolCallCompleteOrphan: completed frame
  without a start → ToolUse synthesised from update's rawInput.
- TestExtractACPToolCallText: diff + new-file-diff cases.

All agent / daemon / handler test packages green.

---------

Co-authored-by: Eve <8b0578a3-cf72-4394-9e38-b328eca92463@users.noreply.multica.ai>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Eve <eve@multica.ai>
Co-authored-by: Lambda <f252c2c5-7d1d-4f3c-b394-a61abfe673fc@users.noreply.multica.ai>
2026-04-21 02:18:30 +08:00

Multica — humans and agents, side by side

Multica

Multica

Your next 10 hires won't be human.

The open-source managed agents platform.
Turn coding agents into real teammates — assign tasks, track progress, compound skills.

CI GitHub stars

Website · Cloud · X · Self-Hosting · Contributing

English | 简体中文

What is Multica?

Multica turns coding agents into real teammates. Assign issues to an agent like you'd assign to a colleague — they'll pick up the work, write code, report blockers, and update statuses autonomously.

No more copy-pasting prompts. No more babysitting runs. Your agents show up on the board, participate in conversations, and compound reusable skills over time. Think of it as open-source infrastructure for managed agents — vendor-neutral, self-hosted, and designed for human + AI teams. Works with Claude Code, Codex, OpenClaw, OpenCode, Hermes, Gemini, Pi, and Cursor Agent.

Multica board view

Features

Multica manages the full agent lifecycle: from task assignment to execution monitoring to skill reuse.

  • Agents as Teammates — assign to an agent like you'd assign to a colleague. They have profiles, show up on the board, post comments, create issues, and report blockers proactively.
  • Autonomous Execution — set it and forget it. Full task lifecycle management (enqueue, claim, start, complete/fail) with real-time progress streaming via WebSocket.
  • Reusable Skills — every solution becomes a reusable skill for the whole team. Deployments, migrations, code reviews — skills compound your team's capabilities over time.
  • Unified Runtimes — one dashboard for all your compute. Local daemons and cloud runtimes, auto-detection of available CLIs, real-time monitoring.
  • Multi-Workspace — organize work across teams with workspace-level isolation. Each workspace has its own agents, issues, and settings.

Quick Install

brew install multica-ai/tap/multica

Use brew upgrade multica-ai/tap/multica to keep the CLI current.

macOS / Linux (install script)

curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash

Use this if Homebrew is not available. The script installs the Multica CLI on macOS and Linux by using Homebrew when it is on PATH, otherwise it downloads the binary directly.

Windows (PowerShell)

irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex

Then configure, authenticate, and start the daemon in one command:

multica setup          # Connect to Multica Cloud, log in, start daemon

Self-hosting? Add --with-server to deploy a full Multica server on your machine:

curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash -s -- --with-server
multica setup self-host

Requires Docker. See the Self-Hosting Guide for details.


Getting Started

1. Set up and start the daemon

multica setup           # Configure, authenticate, and start the daemon

The daemon runs in the background and auto-detects agent CLIs (claude, codex, openclaw, opencode, hermes, gemini, pi, cursor-agent) on your PATH.

2. Verify your runtime

Open your workspace in the Multica web app. Navigate to Settings → Runtimes — you should see your machine listed as an active Runtime.

What is a Runtime? A Runtime is a compute environment that can execute agent tasks. It can be your local machine (via the daemon) or a cloud instance. Each runtime reports which agent CLIs are available, so Multica knows where to route work.

3. Create an agent

Go to Settings → Agents and click New Agent. Pick the runtime you just connected and choose a provider (Claude Code, Codex, OpenClaw, OpenCode, Hermes, Gemini, Pi, or Cursor Agent). Give your agent a name — this is how it will appear on the board, in comments, and in assignments.

4. Assign your first task

Create an issue from the board (or via multica issue create), then assign it to your new agent. The agent will automatically pick up the task, execute it on your runtime, and report progress — just like a human teammate.


Multica vs Paperclip

Multica Paperclip
Focus Team AI agent collaboration platform Solo AI agent company simulator
User model Multi-user teams with roles & permissions Single board operator
Agent interaction Issues + Chat conversations Issues + Heartbeat
Deployment Cloud-first Local-first
Management depth Lightweight (Issues / Projects / Labels) Heavy governance (Org chart / Approvals / Budgets)
Extensibility Skills system Skills + Plugin system

TL;DR — Multica is built for teams that want to collaborate with AI agents on real projects together.


CLI

The multica CLI connects your local machine to Multica — authenticate, manage workspaces, and run the agent daemon.

Command Description
multica login Authenticate (opens browser)
multica daemon start Start the local agent runtime
multica daemon status Check daemon status
multica setup One-command setup for Multica Cloud (configure + login + start daemon)
multica setup self-host Same, but for self-hosted deployments
multica issue list List issues in your workspace
multica issue create Create a new issue
multica update Update to the latest version

See the CLI and Daemon Guide for the full command reference.


Architecture

┌──────────────┐     ┌──────────────┐     ┌──────────────────┐
│   Next.js    │────>│  Go Backend  │────>│   PostgreSQL     │
│   Frontend   │<────│  (Chi + WS)  │<────│   (pgvector)     │
└──────────────┘     └──────┬───────┘     └──────────────────┘
                            │
                     ┌──────┴───────┐
                     │ Agent Daemon │  runs on your machine
                     └──────────────┘  (Claude Code, Codex, OpenCode,
                                        OpenClaw, Hermes, Gemini,
                                        Pi, Cursor Agent)
Layer Stack
Frontend Next.js 16 (App Router)
Backend Go (Chi router, sqlc, gorilla/websocket)
Database PostgreSQL 17 with pgvector
Agent Runtime Local daemon executing Claude Code, Codex, OpenClaw, OpenCode, Hermes, Gemini, Pi, or Cursor Agent

Development

For contributors working on the Multica codebase, see the Contributing Guide.

Prerequisites: Node.js v20+, pnpm v10.28+, Go v1.26+, Docker

make dev

make dev auto-detects your environment (main checkout or worktree), creates the env file, installs dependencies, sets up the database, runs migrations, and starts all services.

See CONTRIBUTING.md for the full development workflow, worktree support, testing, and troubleshooting.

Star History

Star History Chart
Description
No description provided
Readme 274 MiB
Languages
Go 48.6%
TypeScript 42.8%
MDX 7.1%
PLpgSQL 0.4%
CSS 0.4%
Other 0.6%