kimi · code
多 Agent 编程引擎 TypeScript Monorepo · MIT

可重放的 多 agent 内核

Kimi Code 是 Moonshot AI 开源的 TypeScript Monorepo 编程 agent:一个 Session 持多个隔离 Agent、每个动作写成可重放的 Wire Record、MCP 一等公民、Kosong 把 6 类 LLM 协议压平成一个接口。

# curl -fsSL https://kimi.moonshot.cn/install.sh | bash · Node ≥ 24

向下探索
01
心智模型 · the mental model

事实是日志,状态是重演

理解 kimi-code 的支点:系统里没有一个可变的「会话状态对象」被直接持久化。每个状态变化都先写成一行结构化 JSONL,内存状态是把日志重演出来的结果。

// 唯一事实源

AgentRecord(wire.jsonl)

每个动作 append 一条记录:turn.prompt / config.update / usage.record / context.append_message / full_compaction.complete … 20+ 种类型,每行一个 JSON。这是真正落盘的东西。

// 重演产物

内存状态

重启时 AgentRecords.restore() 把 _restoring 置 true,逐行回放 restoreAgentRecord。恢复期硬契约:不得有任何副作用——不调 LLM、不跑工具、不碰文件,纯粹把日志重演成内存状态。

图 01 · 一个动作如何成为可重放的事实
agent 动作
prompt / tool / usage
append
AgentRecord
结构化一行
wire.jsonl
restore()
逐行回放
内存状态
可恢复 pending turn
三个直接好处:① 崩溃后能精确恢复到包括 pending turn 的状态;② 跨客户端重放同一会话;③ 协议升级用 migrateWireRecord 逐条迁移(v1.1 / v1.2 迁移器在 records/migration/)。apps/vis 调试器就吃这个格式。
02
架构分层 · the stack

一条单向依赖链

kimi-code 是清晰的单向依赖:从 CLI 到执行环境,每层只依赖它下面那层。点击任意一层看它的职责与边界。

L0 @moonshot-ai/kaos · 执行环境 FS / 进程 / 路径 · Local / SSH
L1 @moonshot-ai/kosong · LLM 抽象 ChatProvider · 5 adapter / 6 type
L2 @moonshot-ai/agent-core · agent 引擎 Session · turn loop · MCP · wire · hooks
L3 node-sdk + apps · SDK / CLI KimiHarness · TUI · apps/vis
03
回合循环 · the turn loop

把一次 turn 拆给你看

turn loop 被刻意拆成两层无状态函数,状态全部外置到 Agent,所以 loop 本身可独立单测。按下播放,看 runTurn / executeLoopStep 逐帧收敛。

READOUT0 / 0
待机
按「播放」开始
事件会逐条点亮。左侧时间线展示完整序列,右侧解读当前正在发生的事件。
Session / Turn 生命周期 Turn Loop 消息 / 流式 工具 / subagent / wire
tool_use = 控制信号

不是终止

stopReason==='tool_use' 时 continue 进下一 step;只有非工具停止才进入「要不要收尾」判断。这是两层 loop 的分界点。

recordUsage

立即记账

usage 在 LLM 返回后立即记账,而不是等工具跑完——即使工具执行期间 abort,已消费 token 也不丢,计费/预算永远准确。

steerBuffer

turn 进行中插话

turn 正在跑时新输入走 steer() 被 buffer,turn 间隙注入;无活跃 turn 时 steer 等价 prompt 直接 launch。

04
工具 · tools & scheduler

并行,但不无脑并行

同一 step 里 LLM 可能一次发多个 tool_call。ToolScheduler 不是全并行,而是基于 资源冲突检测——读同一文件可并行,写同一文件排队。

tools/scheduler.ts — 冲突感知调度
// 同 step 多个 tool_call:按资源冲突决定并行 / 排队
for (const call of batch) {
  const conflict = ToolAccesses.conflict(call.access, running);
  if (conflict) {
    queuedTasks.push(call);        // 写同一文件 → 排队
  } else {
    running.add(call.access);
    runToolCall(call).finally(() => {
      running.delete(call.access);
      startQueuedTasks();         // 唤醒被挡住的任务
    });
  }
}
错误处理:工具失败要 throw,被捕获后作为 isError: true 的 tool result 回给 LLM——而不是把错误塞进正常 content。

ToolManager 关注点

字段 / 能力作用
name模型可见的工具名(唯一)
parametersschema 自动校验入参
execute干活;带 signal 与流式 onUpdate
access声明读/写资源,供冲突检测
ToolScheduler无冲突并行、有冲突排队唤醒
顺序保证:tool_execution_end 按完成顺序发出,但持久化的 toolResult 消息仍按 assistant 源顺序——UI 看到的进度与落盘的事实分离。
表 01 · turn 的失败场景与归一
失败场景表现 / 归一处理
LLM 网络抖动isRetryableGenerateErrorchatWithRetry 指数退避(300ms→5s,×2,≤3 次)
turn 中途 abortsignal.aborted返回 {stopReason:'aborted'},usage 已记不丢
步数超限max_stepscreateMaxStepsExceededError → turn.interrupted
安全过滤 / 截断filtered / max_tokens非工具停止,不继续循环,提前收尾
step.end 前 abortenvelope 不完整runTurn 捕获 → 发 turn.interrupted
05
多 Agent · session & subagent

一个 Session,N 个隔离 Agent

kimi-code 的「多 agent」不是切一个大上下文,而是真正实例化多个独立 Agent 对象。Session 是容器,持有一张 agents: Map<id, Agent>。

一次 spawn 的时间线 →
↓
spawn()
解析 profile(coder / explore / plan),创建全新 Agent 实例
↑
linkAbortSignal
父信号挂到子 controller,父 abort 时子级联 abort
↓
summary 回传
子只回 lastAssistantText;不足 200 字触发续写
独立上下文

窗口零污染

每个 Agent 自带独立 Kaos / ToolManager / PermissionManager / AgentRecords(独立 wire.jsonl)/ context.history。子 agent 读 50 个文件,不污染主 agent 窗口——主只收一段摘要。

冷启动优化

explore 注入仓库快照

explore agent 从空上下文出发,runChild 在 prompt 前注入 collectGitContext()(当前分支、最近 commit、改动文件),让它「落地即知身在何处」。别的 subagent 没有这个待遇。

四道防线:子在 max_tokens 处停 → 拒绝把半截结果当成功;并发 resume 同一子 → 抛 already running;归属校验 parentAgentId !== ownerAgentId → 子不能被别的父认领;resume 时强制子模型对齐父当前模型。
06
LLM 抽象 · kosong

把 6 类协议压成一个接口

kosong 是纯粹的数据转换层,不含业务逻辑——这是它能独立发布、独立测试的原因。每个 adapter 把自家 SDK 的流式事件,翻译成统一的 StreamedMessagePart 序列。

Provider底层 SDK关键差异 / 坑
kimi.tsopenai(Moonshot 兼容端点)支持 uploadVideo();强制只发 max_completion_tokens(发 max_tokens 会被 reasoning 预算吃掉 → 空响应)
anthropic.ts@anthropic-ai/sdk原生 thinking block;pause_turn → 归一为 paused;refusal → filtered
openai-common.tsopenai(共享转换)承载 convertContentPart / toolToOpenAI / finish_reason 归一,被 kimi & legacy 复用
openai-responses.tsopenai(Responses API)新 Responses 协议,事件流形状与 Chat Completions 不同
google-genai.ts@google/genai同时服务 google-genai 与 vertexai 两种 type;Gemini 专用 STOP/MAX_TOKENS 归一
kosong/src/provider.ts — finish_reason 6 值
export type FinishReason =
  | 'completed'   // stop · end_turn · STOP
  | 'tool_calls'  // tool_use · tool_calls
  | 'truncated'   // length · max_tokens · MAX_TOKENS
  | 'filtered'    // refusal / 安全策略
  | 'paused'      // Anthropic pause_turn(仅此家)
  | 'other';      // 未分类原生值

交错 delta 的难点

generate() 的核心难点是并行工具调用的参数 delta 会交错:tool-call A 的参数片段和 B 的可能交替到达。

  • toolCallIndexMap碎片路由回正确 toolCall
  • onToolCall仅在流结束、参数合并后触发
  • isEmptyMessage无 text 无 tool → APIEmptyResponseError
reasoning 模型的坑:把预算全耗在 reasoning_content 上、content 为空 → 正是 isEmptyMessage 这道闸要拦的 200 空响应。
07
扩展机制 · hooks

13 种事件 × 外部命令门控

HookEngine 把外部 shell 命令挂到 agent 生命周期。注册时按 event 归桶,每个 HookDef 带可选 matcher 正则,触发时过滤。

triggerBlock

阻塞型

任一命令返回 action:'block' 就否决该操作——这是权限门控、危险命令拦截的实现。

fireAndForget

通知型

用于 PostCompact / SubagentStop 这类不要结果的场景,promise 进 pendingTriggers 但不 await。

表 02 · HOOK_EVENT_TYPES(13 种)
事件语义归约
PreToolUse工具执行前,可拦截block / 放行
PostToolUse · PostToolUseFailure工具完成 / 失败后观察
UserPromptSubmit用户提交 prompt观察 / 注入
Stop · StopFailureturn 收尾 / 失败收尾观察
SessionStart · SessionEnd会话首尾观察
SubagentStart · SubagentStop子 agent 首尾fire-and-forget
PreCompact · PostCompact压缩前后观察
Notification桌面通知等—
对自身故障极度宽容:trigger 外层 .catch(() => [])、matcher 正则编译失败返回 false、监听器异常被吞——hook 是「增强」而非关键路径,绝不能因为一个坏 hook 让 agent 卡死。匹配命令并行执行(Promise.all),默认 30s 超时。
08
持久化 · wire records & compaction

每个动作都是一行 JSONL

Wire Records 是全部持久化的 agent 状态——append-only。配上自动压缩:用量逼近上限时把旧消息总结成 summary,保留最近若干步。

AgentRecord 记录类型(节选)

type记录什么
turn.prompt · turn.steer一次用户输入 / 插话
config.updatemodel / cwd / thinking 变更
context.append_message一条消息入 history
context.append_loop_eventtool.call / tool.result
usage.record一次 LLM 用量(立即记账)
full_compaction.begin / complete压缩起止 + 前后 token
permission.set_mode · tools.register_user_tool权限 / 工具注册

压缩阈值(DefaultCompactionStrategy)

triggerRatio = 0.85

何时压

用量达上下文 85% 触发;reserved = 50_000,used + reserved ≥ max 也触发;一个 turn 内最多压 3 次,超了抛 CONTEXT_OVERFLOW。

computeCompactCount

怎么切

保护最近 3 步 + 尾部连续 user 消息;只在角色边界切分,绝不拆散 tool_call 与它的 tool_result。压缩 worker 跑完逐条比对 history 是否被改过,变了就 cancel() 放弃。

replay

精确恢复

恢复到包括 pending turn 的状态;恢复期零副作用。

migrate

协议演进

migrateWireRecord 逐条迁移;v1.1 / v1.2 迁移器在 records/migration/。

vis

可视化调试

apps/vis 直接吃 wire 格式,回放一次会话的全过程。

09
可观测性 · observability

会话即遥测

因为每个动作都已是结构化 wire 记录,可观测性几乎是「免费」的:wire.jsonl 本身就是一条完整的、可重放的执行轨迹。

~/.kimi/sessions/<id>/agent-<id>/wire.jsonl
{"type":"config.update","modelAlias":"kimi-k2"}
{"type":"turn.prompt","input":[{"type":"text","text":"修复登录 bug"}]}
{"type":"context.append_loop_event","event":{"kind":"tool.call","name":"edit"}}
{"type":"usage.record","usage":{"input":8200,"output":410}}
{"type":"full_compaction.complete","tokensBefore":95000,"tokensAfter":31000}

一份 wire = 一条轨迹

不需要外接 APM 也能回答「这次会话花了多少 token、调了哪些工具、何时压缩」。

  • turn.prompt一次用户输入
  • context.append_loop_event每次工具调用 / 结果
  • usage.record每次 LLM 用量
  • full_compaction.*每次压缩前后
  • config.update每次配置变更
apps/vis:把同一份 wire 喂给可视化调试器,逐帧回看 turn / step / tool / 压缩——线上线下用同一份事实。
10
快速开始 · quick start

三十秒跑起第一个会话

单 Binary 发布,零配置起步。装好就能在 TUI 里对话,并用 /mcp-config 管理 MCP 服务器。

install.sh · 单 Binary
# macOS / Linux
curl -fsSL https://kimi.moonshot.cn/install.sh | bash

# Windows (PowerShell)
irm https://kimi.moonshot.cn/install.ps1 | iex

# 启动 TUI
kimi-code            # 进入交互式会话
kimi-code --resume   # 从 wire 恢复上次会话
会话内 · MCP 一等公民
# 在对话里直接管理 MCP 服务器
/mcp-config add github   # 添加
/mcp-config login github # 触发 OAuth 浏览器授权
/mcp-config list         # 查看五态:connected /
                         # needs-auth / failed / …

# 切模型(写入 wire: config.update)
/model kimi-k2
OAuth 精髓:没 token 时故意不挂 provider,让 401 干净冒成 needs-auth,用户即可见 /mcp-config login 的可操作提示。
11
速查表 · cheat sheet

随手翻的一页纸

把高频概念、记录类型、provider 与源码位置压在一处——卡壳时回这里。

核心概念

Session
会话容器,持 N 个 Agent
Agent
独立 Kaos/Tools/Permission/wire
TurnFlow
activeTurn + steerBuffer
runTurn
外层 turn 收敛
executeLoopStep
内层单 step 原子操作
SubagentHost
spawn coder/explore/plan

Wire 记录类型

turn.prompt / steer
用户输入 / 插话
config.update
model / cwd / thinking
context.append_*
消息 / loop event
usage.record
LLM 用量(立即)
full_compaction.*
压缩起止
permission.set_mode
权限模式

Kosong Provider

kimi.ts
Moonshot(uploadVideo)
anthropic.ts
thinking / pause_turn
openai-common
共享转换
openai-responses
Responses API
google-genai
genai + vertexai

压缩阈值

triggerRatio
0.85
reserved
50_000
maxRecentSteps
3

源码地图 · packages/

  • agent-core/agent/turn loop · Agent
  • agent-core/session/Session · subagent
  • agent-core/records/wire · migration
  • agent-core/mcp/connection · oauth
  • agent-core/hooks/HookEngine
  • kosong/src/ChatProvider
  • kaos/src/Local / SSH
apps/ · kimi-code (TUI) · vis (调试器)
现在,去读源码吧。
MoonshotAI/kimi-code · TypeScript Monorepo
“Multi-agent Session · Wire Records · MCP first-class.”
MIT · Moonshot AI