Skip to content

Wire mode

Wire mode is Kimi Code CLI's low-level communication protocol for structured bidirectional communication with external programs.

What is Wire

Wire is the message-passing layer used internally by Kimi Code CLI. When you interact via terminal, the Shell UI receives AI output through Wire and displays it; when you integrate with IDEs via ACP, the ACP server also communicates with the agent core through Wire.

Wire mode (--wire) exposes this communication protocol, allowing external programs to interact directly with Kimi Code CLI. This is suitable for building custom UIs or embedding Kimi Code CLI into other applications.

sh
kimi --wire

Use cases

Wire mode is mainly used for:

  • Custom UI: Build web, desktop, or mobile frontends for Kimi Code CLI
  • Application integration: Embed Kimi Code CLI into other applications
  • Automated testing: Programmatic testing of agent behavior

TIP

If you only need simple non-interactive input/output, print mode is simpler. Wire mode is for scenarios requiring full control and bidirectional communication.

Wire protocol

Wire uses a JSON-RPC 2.0 based protocol for bidirectional communication via stdin/stdout. The current protocol version is 1.4. Each message is a single line of JSON conforming to the JSON-RPC 2.0 specification.

Protocol type definitions

typescript
/** JSON-RPC 2.0 request message base structure */
interface JSONRPCRequest<Method extends string, Params> {
  jsonrpc: "2.0"
  method: Method
  id: string
  params: Params
}

/** JSON-RPC 2.0 notification message (no id, no response needed) */
interface JSONRPCNotification<Method extends string, Params> {
  jsonrpc: "2.0"
  method: Method
  params: Params
}

/** JSON-RPC 2.0 success response */
interface JSONRPCSuccessResponse<Result> {
  jsonrpc: "2.0"
  id: string
  result: Result
}

/** JSON-RPC 2.0 error response */
interface JSONRPCErrorResponse {
  jsonrpc: "2.0"
  id: string
  error: JSONRPCError
}

interface JSONRPCError {
  code: number
  message: string
  data?: unknown
}

initialize

Added

Added in Wire 1.1. Legacy clients can skip this request and send prompt directly.

  • Direction: client → agent
  • Type: Request (requires response)

Optional handshake request for negotiating protocol version, submitting external tool definitions, and retrieving the slash command list.

typescript
/** initialize request parameters */
interface InitializeParams {
  /** Protocol version */
  protocol_version: string
  /** Client info, optional */
  client?: ClientInfo
  /** External tool definitions, optional */
  external_tools?: ExternalTool[]
  /** Client capabilities, optional */
  capabilities?: ClientCapabilities
}

interface ClientCapabilities {
  /** Whether the client can handle QuestionRequest messages */
  supports_question?: boolean
}

interface ClientInfo {
  name: string
  version?: string
}

interface ExternalTool {
  /** Tool name, must not conflict with built-in tools */
  name: string
  /** Tool description */
  description: string
  /** Parameter definition in JSON Schema format */
  parameters: JSONSchema
}

/** initialize response result */
interface InitializeResult {
  /** Protocol version */
  protocol_version: string
  /** Server info */
  server: ServerInfo
  /** Available slash commands */
  slash_commands: SlashCommandInfo[]
  /** External tool registration result, only returned when request includes external_tools */
  external_tools?: ExternalToolsResult
  /** Server capabilities */
  capabilities?: ServerCapabilities
}

interface ServerCapabilities {
  /** Whether the server supports sending QuestionRequest messages */
  supports_question?: boolean
}

interface ServerInfo {
  name: string
  version: string
}

interface SlashCommandInfo {
  name: string
  description: string
  aliases: string[]
}

interface ExternalToolsResult {
  /** Successfully registered tool names */
  accepted: string[]
  /** Failed tool registrations with reasons */
  rejected: Array<{ name: string; reason: string }>
}

Request example

json
{"jsonrpc": "2.0", "method": "initialize", "id": "550e8400-e29b-41d4-a716-446655440000", "params": {"protocol_version": "1.4", "client": {"name": "my-ui", "version": "1.0.0"}, "capabilities": {"supports_question": true}, "external_tools": [{"name": "open_in_ide", "description": "Open file in IDE", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}]}}

Success response example

json
{"jsonrpc": "2.0", "id": "550e8400-e29b-41d4-a716-446655440000", "result": {"protocol_version": "1.4", "server": {"name": "Kimi Code CLI", "version": "1.14.0"}, "slash_commands": [{"name": "init", "description": "Analyze the codebase ...", "aliases": []}], "capabilities": {"supports_question": true}, "external_tools": {"accepted": ["open_in_ide"], "rejected": []}}}

If the server does not support the initialize method, the client will receive a -32601 method not found error and should automatically fall back to no-handshake mode.

prompt

  • Direction: Client → Agent
  • Type: Request (requires response)

Send user input and run an agent turn. After calling, the agent starts processing and sends event notifications and request messages during execution, returning a response only when the turn completes.

typescript
/** prompt request parameters */
interface PromptParams {
  /** User input, can be plain text or array of content parts */
  user_input: string | ContentPart[]
}

/** prompt response result */
interface PromptResult {
  /** Turn end status */
  status: "finished" | "cancelled" | "max_steps_reached"
  /** Number of steps executed when status is max_steps_reached */
  steps?: number
}

Request example

json
{"jsonrpc": "2.0", "method": "prompt", "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "params": {"user_input": "Hello"}}

Success response example

json
{"jsonrpc": "2.0", "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "result": {"status": "finished"}}

Error response example

json
{"jsonrpc": "2.0", "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "error": {"code": -32001, "message": "LLM is not set"}}
codeDescription
-32000A turn is already in progress
-32001LLM not configured
-32002Specified LLM not supported
-32003LLM service error

replay

Added

Added in Wire 1.3.

  • Direction: Client → Agent
  • Type: Request (requires response)

Trigger a history replay. The server reads wire.jsonl from the session directory and re-sends the recorded event and request messages in order. Replay is read-only; clients should not respond to replayed request messages. If there is no history, the server returns events: 0 and requests: 0.

typescript
/** replay request has no parameters, params can be empty object or omitted */
type ReplayParams = Record<string, never>

/** replay response result */
interface ReplayResult {
  /** Replay end status */
  status: "finished" | "cancelled"
  /** Number of replayed events */
  events: number
  /** Number of replayed requests */
  requests: number
}

Request example

json
{"jsonrpc": "2.0", "method": "replay", "id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8"}

Success response example

json
{"jsonrpc": "2.0", "id": "6ba7b812-9dad-11d1-80b4-00c04fd430c8", "result": {"status": "finished", "events": 42, "requests": 3}}

steer

Added

Added in Wire 1.4.

  • Direction: Client → Agent
  • Type: Request (requires response)

Inject a user message into an active agent turn. Unlike prompt, steer does not start a new turn but injects the message into the currently running turn. The injected message is inserted into the context as a synthetic tool call result, allowing you to "steer" the AI's behavior while it is processing.

typescript
/** steer request parameters */
interface SteerParams {
  /** User input, can be plain text or array of content parts */
  user_input: string | ContentPart[]
}

/** steer response result */
interface SteerResult {
  /** Fixed as "steered" */
  status: "steered"
}

Request example

json
{"jsonrpc": "2.0", "method": "steer", "id": "7ca7c810-9dad-11d1-80b4-00c04fd430c8", "params": {"user_input": "Use Python"}}

Success response example

json
{"jsonrpc": "2.0", "id": "7ca7c810-9dad-11d1-80b4-00c04fd430c8", "result": {"status": "steered"}}

Error response example

If no turn is in progress:

json
{"jsonrpc": "2.0", "id": "7ca7c810-9dad-11d1-80b4-00c04fd430c8", "error": {"code": -32000, "message": "No agent turn is in progress"}}

cancel

  • Direction: Client → Agent
  • Type: Request (requires response)

Cancel the currently running agent turn or replay. After calling, the in-progress prompt request will return {"status": "cancelled"}, and replay will return {"status": "cancelled"} with the message counts sent so far.

typescript
/** cancel request has no parameters, params can be empty object or omitted */
type CancelParams = Record<string, never>

/** cancel response result is empty object */
type CancelResult = Record<string, never>

Request example

json
{"jsonrpc": "2.0", "method": "cancel", "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8"}

Success response example

json
{"jsonrpc": "2.0", "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "result": {}}

Error response example

If no turn is in progress:

json
{"jsonrpc": "2.0", "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "error": {"code": -32000, "message": "No agent turn is in progress"}}

event

  • Direction: agent → client
  • Type: Notification (no response needed)

Events emitted by the agent during a turn. No id field, client doesn't need to respond.

typescript
/** event notification parameters, contains serialized Wire message */
interface EventParams {
  type: string
  payload: object
}

Example

json
{"jsonrpc": "2.0", "method": "event", "params": {"type": "ContentPart", "payload": {"type": "text", "text": "Hello"}}}

request

  • Direction: agent → client
  • Type: Request (requires response)

Requests from the agent to the client, used for approval confirmation or external tool calls. The client must respond before the agent can continue execution.

typescript
/** request parameters, contains serialized Wire message */
interface RequestParams {
  type: "ApprovalRequest" | "ToolCallRequest" | "QuestionRequest"
  payload: ApprovalRequest | ToolCallRequest | QuestionRequest
}

Approval request example

json
{"jsonrpc": "2.0", "method": "request", "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "params": {"type": "ApprovalRequest", "payload": {"id": "approval-1", "tool_call_id": "tc-1", "sender": "Shell", "action": "run shell command", "description": "Run command `ls`", "display": []}}}

Approval response example

json
{"jsonrpc": "2.0", "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "result": {"request_id": "approval-1", "response": "approve"}}

External tool call request example

json
{"jsonrpc": "2.0", "method": "request", "id": "a3bb189e-8bf9-3888-9912-ace4e6543002", "params": {"type": "ToolCallRequest", "payload": {"id": "tc-1", "name": "open_in_ide", "arguments": "{\"path\":\"README.md\"}"}}}

External tool call response example

json
{"jsonrpc": "2.0", "id": "a3bb189e-8bf9-3888-9912-ace4e6543002", "result": {"tool_call_id": "tc-1", "return_value": {"is_error": false, "output": "Opened", "message": "Opened README.md in IDE", "display": []}}}

Standard error codes

All requests may return JSON-RPC 2.0 standard errors:

codeDescription
-32700Invalid JSON format
-32600Invalid request (e.g., missing required fields)
-32601Method not found
-32602Invalid method parameters
-32603Internal error

Wire message types

Wire messages are transmitted via event and request methods, in format {"type": "...", "payload": {...}}. The following describes all message types using TypeScript-style type definitions.

typescript
/** Union type of all Wire messages */
type WireMessage = Event | Request

/** Events: sent via event method, no response needed */
type Event =
  | TurnBegin
  | TurnEnd
  | StepBegin
  | StepInterrupted
  | CompactionBegin
  | CompactionEnd
  | StatusUpdate
  | ContentPart
  | ToolCall
  | ToolCallPart
  | ToolResult
  | ApprovalResponse
  | SubagentEvent

/** Requests: sent via request method, require response */
type Request = ApprovalRequest | ToolCallRequest | QuestionRequest

TurnBegin

Turn started.

typescript
interface TurnBegin {
  /** User input, can be plain text or array of content parts */
  user_input: string | ContentPart[]
}

TurnEnd

Added

Added in Wire 1.2.

Turn ended. This event is sent after all other events in the turn. If the turn is interrupted, this event may be omitted.

typescript
interface TurnEnd {
  // No additional fields
}

StepBegin

Step started.

typescript
interface StepBegin {
  /** Step number, starting from 1 */
  n: number
}

StepInterrupted

Step interrupted, no additional fields.

CompactionBegin

Context compaction started, no additional fields.

CompactionEnd

Context compaction ended, no additional fields.

StatusUpdate

Status update.

typescript
interface StatusUpdate {
  /** Context usage ratio, float between 0-1, may be absent in JSON */
  context_usage?: number | null
  /** Token usage stats for current step, may be absent in JSON */
  token_usage?: TokenUsage | null
  /** Message ID for current step, may be absent in JSON */
  message_id?: string | null
}

interface TokenUsage {
  /** Input tokens excluding input_cache_read and input_cache_creation */
  input_other: number
  /** Total output tokens */
  output: number
  /** Cached input tokens */
  input_cache_read: number
  /** Input tokens used for cache creation, currently only Anthropic API supports this field */
  input_cache_creation: number
}

ContentPart

Message content part. Serialized with type as "ContentPart", specific type distinguished by payload.type.

typescript
type ContentPart =
  | TextPart
  | ThinkPart
  | ImageURLPart
  | AudioURLPart
  | VideoURLPart

interface TextPart {
  type: "text"
  /** Text content */
  text: string
}

interface ThinkPart {
  type: "think"
  /** Thinking content */
  think: string
  /** Encrypted thinking content or signature, may be absent in JSON */
  encrypted?: string | null
}

interface ImageURLPart {
  type: "image_url"
  image_url: {
    /** Image URL, can be data URI (e.g., data:image/png;base64,...) */
    url: string
    /** Image ID for distinguishing different images, may be absent in JSON */
    id?: string | null
  }
}

interface AudioURLPart {
  type: "audio_url"
  audio_url: {
    /** Audio URL, can be data URI (e.g., data:audio/aac;base64,...) */
    url: string
    /** Audio ID for distinguishing different audio, may be absent in JSON */
    id?: string | null
  }
}

interface VideoURLPart {
  type: "video_url"
  video_url: {
    /** Video URL, can be data URI (e.g., data:video/mp4;base64,...) */
    url: string
    /** Video ID for distinguishing different video, may be absent in JSON */
    id?: string | null
  }
}

ToolCall

Tool call.

typescript
interface ToolCall {
  /** Fixed as "function" */
  type: "function"
  /** Tool call ID */
  id: string
  function: {
    /** Tool name */
    name: string
    /** JSON-format argument string, may be absent in JSON */
    arguments?: string | null
  }
  /** Extra info, may be absent in JSON */
  extras?: object | null
}

ToolCallPart

Tool call argument fragment (streaming).

typescript
interface ToolCallPart {
  /** Argument fragment for streaming tool call arguments, may be absent in JSON */
  arguments_part?: string | null
}

ToolResult

Tool execution result.

typescript
interface ToolResult {
  /** Corresponding tool call ID */
  tool_call_id: string
  return_value: ToolReturnValue
}

interface ToolReturnValue {
  /** Whether this is an error */
  is_error: boolean
  /** Output content returned to model */
  output: string | ContentPart[]
  /** Explanatory message for model */
  message: string
  /** Display blocks shown to user */
  display: DisplayBlock[]
  /** Extra debug info, may be absent in JSON */
  extras?: object | null
}

ApprovalResponse

Changed

Renamed in Wire 1.1. Formerly ApprovalRequestResolved. The old name is still accepted for backwards compatibility.

Approval response event, indicates an approval request has been completed.

typescript
interface ApprovalResponse {
  /** Approval request ID */
  request_id: string
  /** Approval result */
  response: "approve" | "approve_for_session" | "reject"
}

SubagentEvent

Subagent event.

typescript
interface SubagentEvent {
  /** Associated Task tool call ID */
  task_tool_call_id: string
  /** Event from subagent, nested Wire message format */
  event: { type: string; payload: object }
}

ApprovalRequest

Approval request, sent via request method, client must respond before agent can continue.

typescript
interface ApprovalRequest {
  /** Request ID, used when responding */
  id: string
  /** Associated tool call ID */
  tool_call_id: string
  /** Sender (tool name) */
  sender: string
  /** Action description */
  action: string
  /** Detailed description */
  description: string
  /** Display blocks shown to user, may be absent in JSON, defaults to [] */
  display?: DisplayBlock[]
}

Response format

Client needs to return ApprovalResponse as the response result:

typescript
interface ApprovalResponse {
  request_id: string
  response: "approve" | "approve_for_session" | "reject"
}
responseDescription
approveApprove this operation
approve_for_sessionApprove similar operations for this session
rejectReject operation

ToolCallRequest

External tool call request, sent via request method. When the agent calls an external tool registered via initialize, this request is sent. The client must execute the tool and return a ToolResult.

typescript
interface ToolCallRequest {
  /** Tool call ID */
  id: string
  /** Tool name */
  name: string
  /** JSON-format argument string, may be absent in JSON */
  arguments?: string | null
}

Response format

Client needs to return ToolResult as the response result:

typescript
interface ToolResult {
  tool_call_id: string
  return_value: ToolReturnValue
}

QuestionRequest

Added

Added in Wire 1.4.

Structured question request, sent via request method. When the agent uses the AskUserQuestion tool, this request is sent. The client must respond before the agent can continue execution.

This feature requires capability negotiation: the client must declare capabilities.supports_question: true during initialize for the agent to send QuestionRequest. If the client does not declare support, the agent will ask questions directly in its text response instead.

typescript
interface QuestionRequest {
  /** Request ID, used when responding */
  id: string
  /** Associated tool call ID */
  tool_call_id: string
  /** Questions list (1–4 questions) */
  questions: QuestionItem[]
}

interface QuestionItem {
  /** Question text */
  question: string
  /** Short label, max 12 characters */
  header?: string
  /** Available options (2–4) */
  options: QuestionOption[]
  /** Whether multiple options can be selected */
  multi_select?: boolean
}

interface QuestionOption {
  /** Option label */
  label: string
  /** Option description */
  description?: string
}

Request example

json
{"jsonrpc": "2.0", "method": "request", "id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890", "params": {"type": "QuestionRequest", "payload": {"id": "q-1", "tool_call_id": "tc-1", "questions": [{"question": "Which language should I use?", "header": "Lang", "options": [{"label": "Python", "description": "Widely used, large ecosystem"}, {"label": "Rust", "description": "High performance, memory safe"}], "multi_select": false}]}}}

Response format

Client needs to return QuestionResponse as the response result:

typescript
interface QuestionResponse {
  /** Corresponding request ID */
  request_id: string
  /** Answer mapping, key is question text, value is selected option label(s) (comma-separated for multi-select) */
  answers: Record<string, string>
}

Response example

json
{"jsonrpc": "2.0", "id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890", "result": {"request_id": "q-1", "answers": {"Which language should I use?": "Python"}}}

If the client does not support structured questions or the user dismisses the question panel, return empty answers:

json
{"jsonrpc": "2.0", "id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890", "result": {"request_id": "q-1", "answers": {}}}

DisplayBlock

Display block types used in the display field of ToolResult and ApprovalRequest.

typescript
type DisplayBlock =
  | UnknownDisplayBlock
  | BriefDisplayBlock
  | DiffDisplayBlock
  | TodoDisplayBlock
  | ShellDisplayBlock

/** Fallback for unrecognized display block types */
interface UnknownDisplayBlock {
  /** Any type identifier */
  type: string
  /** Raw data */
  data: object
}

interface BriefDisplayBlock {
  type: "brief"
  /** Brief text content */
  text: string
}

interface DiffDisplayBlock {
  type: "diff"
  /** File path */
  path: string
  /** Original content */
  old_text: string
  /** New content */
  new_text: string
}

interface TodoDisplayBlock {
  type: "todo"
  /** Todo list items */
  items: TodoDisplayItem[]
}

interface TodoDisplayItem {
  /** Todo item title */
  title: string
  /** Status */
  status: "pending" | "in_progress" | "done"
}

interface ShellDisplayBlock {
  type: "shell"
  /** Language identifier for syntax highlighting (e.g., "sh", "powershell") */
  language: string
  /** Shell command content */
  command: string
}

Kimi Agent (Rust) Wire server

Note

Kimi Agent is currently experimental. APIs and behavior may change in future releases.

Kimi Agent (Rust) is the Rust implementation of the Kimi Code CLI kernel, designed specifically for Wire mode. If you only need the Wire protocol service, Kimi Agent (Rust) offers a more lightweight alternative. The Rust implementation lives in MoonshotAI/kimi-agent-rs.

Features

  • Full Wire protocol compatibility: Uses the same Wire protocol as Python's kimi --wire, existing clients need no modifications
  • Smaller footprint: Single statically-linked binary, no Python runtime required
  • Faster startup: Native compilation provides faster startup times
  • Same configuration: Uses the same config file (~/.kimi/config.toml) and session directories

Limitations

  • Wire mode only: No Shell/Print/ACP UI
  • Kimi provider only: Does not support OpenAI, Anthropic, or other providers
  • No Kimi account login: No login/logout subcommands or /login, /logout slash commands; requires manual API key configuration
  • No --prompt/--command: Wire server does not accept initial prompts
  • Local execution only: No SSH Kaos support
  • Different MCP OAuth storage: Kimi Agent stores credentials in ~/.kimi/credentials/mcp_auth.json, while Python version uses ~/.fastmcp/oauth-mcp-client-cache/; they are incompatible

Installation

Download pre-built binaries from GitHub Releases:

sh
# macOS (Apple Silicon)
curl -L https://github.com/MoonshotAI/kimi-agent-rs/releases/latest/download/kimi-agent-aarch64-apple-darwin.tar.gz | tar xz
sudo mv kimi-agent /usr/local/bin/

# Linux (x86_64)
curl -L https://github.com/MoonshotAI/kimi-agent-rs/releases/latest/download/kimi-agent-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv kimi-agent /usr/local/bin/

Usage

Kimi Agent runs in Wire mode by default:

sh
kimi-agent

Common options are the same as the kimi command:

sh
# Specify work directory
kimi-agent --work-dir /path/to/project

# Continue previous session
kimi-agent --continue

# Use specific session
kimi-agent --session <session-id>

# Use specific model
kimi-agent --model k2

# YOLO mode (skip approvals)
kimi-agent --yolo

Subcommands:

sh
# Show version and environment info
kimi-agent info

# Manage MCP servers
kimi-agent mcp list
kimi-agent mcp add <name> <command> [args...]
kimi-agent mcp remove <name>

Version synchronization

Kimi Agent is released independently from Kimi Code CLI. See MoonshotAI/kimi-agent-rs release notes for compatibility and sync status.