本文端到端追踪一个用户 Prompt 在 kimi-code 中的完整生命周期:从用户在终端敲下第一行文字,到引擎层层处理、LLM 生成回复、最后将结果流式渲染回终端。理解这条数据流是掌握 kimi-code 内部机制的关键。
1. 用户输入阶段
一切从用户敲下回车开始。kimi-code 提供了三种主要的交互入口:CLI 非交互模式(kimi -p "prompt")、TUI 交互模式(kimi 直接启动),以及 SDK 程序化调用。无论哪个入口,数据流的起点都是同一个——一个 ContentPart[] 数组,其中每个元素可以是文本、图片 URL 或视频 URL。
1.1 pi-tui 如何处理用户输入
pi-tui 是 kimi-code 的终端 UI 层(包名 @moonshot-ai/pi-tui),基于 Node.js 的 raw mode terminal 实现。其核心架构包含以下几个关键组件:
- tui.ts — 顶层 TUI 管理器,持有事件循环、渲染逻辑和组件树
- terminal.ts — 底层终端抽象,处理 raw mode 切换、光标定位和 ANSI 转义序列
- editor-component.ts — 多行编辑器组件,负责文本输入、光标移动和历史记录
- keybindings.ts — 全局快捷键绑定(Ctrl+C 取消、Esc 返回等)
当用户在 TUI 中输入文本并按下回车,事件链如下:
raw stdin byte stream
→ terminal.ts 读取输入字符
→ editor-component.ts 维护编辑状态(字符数组、光标位置)
→ 用户按回车触发 submit
→ tui.ts 将文本封装为 ContentPart[]
→ 通过 RPC 发送给 node-sdk
同时,pi-tui 通过差分渲染(delta rendering)来更新终端显示:只重绘发生变化的行,避免全屏刷新。当新的流式内容到达时,增量追加到输出区域,确保在低带宽场景下也能保持流畅。
1.2 CLI 路径
在 CLI 非交互模式(kimi -p)下,路径更短:
$ kimi -p "帮我写一个快速排序函数"
→ CLI 入口解析参数
→ 将字符串转换为 ContentPart[]: [{ type: "text", text: "帮我写一个快速排序函数" }]
→ 调用 SDK: session.prompt(...)
2. SDK 层
node-sdk(包名 @moonshot-ai/node-sdk)是面向外部消费者的编程接口层。它提供类型安全的 TypeScript API,同时负责认证、会话生命周期管理和 RPC 通信。其核心架构如下:
┌─────────────────────────────────────────────────┐
│ node-sdk │
│ ┌───────────┐ ┌─────────┐ ┌───────────────┐ │
│ │ KimiHarness│→ │ Session │→ │ SDKRpcClient │ │
│ │ (编排器) │ │ (会话) │ │ (RPC 客户端) │ │
│ └───────────┘ └─────────┘ └───────┬───────┘ │
│ │ │
└───────────────────────────────────────┼──────────┘
│ JSON-RPC 2.0
┌────────▼────────┐
│ agent-core 引擎 │
└─────────────────┘
2.1 KimiHarness 编排器
KimiHarness 是 SDK 的顶层协调者。它负责:
- 认证管理:OAuth 登录、Token 刷新、凭证持久化
- 会话创建与恢复:新建 Session 或从磁盘恢复已有会话
- 遥测初始化:配置遥测客户端,上报使用数据
- 配置加载:读取
.kimi/config配置,包括模型选择、权限模式等
典型调用流程:
const harness = new KimiHarness({ workDir: './my-project' });
const session = await harness.createSession();
// 发送第一条 prompt
await session.prompt('帮我重构 UserService 类');
// 接收流式响应
session.onEvent((event) => {
if (event.type === 'assistant.delta') {
process.stdout.write(event.delta);
}
});
2.2 Session.prompt() 方法
Session 是用户操作的主要入口。其 prompt() 方法(源码位置:node-sdk/src/session.ts:107)做三件事:
- 输入规范化:将字符串或
PromptInput统一转换为ContentPart[],包含空值和类型校验 - 会话活性检查:确保 Session 未被 close
- RPC 转发:调用
this.rpc.prompt({ sessionId, input })将请求通过 JSON-RPC 发送给引擎
2.3 SDKRpcClient — RPC 通道
SDKRpcClient 是 SDK 与 agent-core 引擎之间的桥梁。它基于 JSON-RPC 2.0 协议,提供了类型安全的请求/响应模型:
- 请求路径:SDK → RPC Client → agent-core(同步或异步)
- 事件路径:agent-core → RPC Client → Event Emitter → SDK Session.onEvent() 回调
- 双向通信:SDK 可以同时发送多个请求,引擎通过
turnId将响应路由回正确的会话
RPC 消息本身是纯 JSON 对象,不包含二进制数据。图片、视频等媒体通过 URL 引用传递,实际数据传输由底层 HTTP 处理。
3. Agent 核心引擎
agent-core(包名 @moonshot-ai/agent-core)是整个系统的智能核心。它接收 SDK 层传入的请求,管理对话上下文、编排多轮对话循环、协调工具调用,并最终产生回复。引擎设计遵循 依赖注入(DI) 模式,所有外部依赖(日志、遥测、存储)通过构造函数注入,核心逻辑保持纯函数式、可测试。
引擎内部由三个关键层次构成:Session 层(生命周期管理)、Agent 层(子系统组合)、TurnFlow 层(对话循环执行)。
3.1 Session 层
Session 是 agent-core 侧的最外层容器。它拥有一个或多个 Agent 实例,并负责:
- 生命周期管理:创建、挂起、恢复、销毁
- 生命周期钩子:提供
SessionStart、SessionEnd、UserPromptSubmit、Stop、PostToolUse等钩子,允许插件和外部系统介入关键节点 - 持久化:将会话元数据存储到磁盘(通过
AgentRecordPersistence),支持跨进程恢复 - Provider 管理:管理 LLM 模型提供商的配置和认证(通过
ModelProvider)
Session 通过 session.prompt() 接收用户输入,内部路由到主 Agent 的 Agent.rpcMethods.prompt(),从而进入 Agent 层的编排逻辑。
3.2 Agent 类
Agent(源码位置:agent-core/src/agent/index.ts)是整个引擎的核心编排器。它通过 组合模式 组装了 15+ 个子系统,每个子系统各司其职:
| 子系统 | 职责 |
|---|---|
ContextMemory |
对话历史管理与 token 计数 |
ConfigState |
运行时配置(模型、权限、思考模式等) |
TurnFlow |
多轮对话循环控制 |
ToolManager |
工具注册、发现、工具声明生成 |
PermissionManager |
权限模式管理(yolo / manual / auto) |
PlanMode |
计划模式:先生成执行计划再执行 |
SwarmMode |
Swarm 并行 Agent 模式 |
GoalMode |
自主目标驱动模式 |
BackgroundManager |
后台任务管理(长期运行的子进程) |
CronManager |
定时任务调度 |
SkillManager |
Skill 加载与激活 |
Compaction |
对话压缩(Full / Micro) |
InjectionManager |
上下文注入(插件提醒、Skill 提示等) |
UsageRecorder |
Token 使用量统计 |
AgentRecords |
事件日志记录到 wire.jsonl |
Agent 的 rpcMethods 是一个方法路由表,将外部 RPC 调用映射到内部操作。其中最核心的两条路径是:
// prompt: 启动新对话轮次
prompt: (payload) => { this.turn.prompt(payload.input); },
// cancel: 取消当前正在执行的轮次
cancel: (payload) => { this.turn.cancel(payload.turnId); },
Agent 还持有 generate 的封装函数。该封装在调用底层 kosong.generate 之前,自动注入认证信息、记录请求日志和遥测数据(LLM 请求追踪):
get generate(): typeof generate {
return async (provider, systemPrompt, tools, history, callbacks, options) => {
// 记录请求日志
this.llmRequestLogger.logRequest({ provider, systemPrompt, tools, messages: history });
// 记录遥测
this.llmRequestRecorder.record({ provider, systemPrompt, tools, messages: history });
// 注入认证后调用底层 generate
return this.rawGenerate(provider, systemPrompt, tools, history, callbacks, requestOptions);
};
}
3.3 TurnFlow — 对话循环(核心)
TurnFlow 是整个 kimi-code 数据流中最核心的组件。 它在 agent-core/src/agent/turn/index.ts 中实现,负责将一次用户输入转化为多步 Agent-LLM 交互循环。理解 TurnFlow 就理解了 kimi-code 的"心脏"是如何跳动的。
主循环概览

TurnFlow 通过 Agent.llm getter 创建 KosongLLM 实例,将 provider、systemPrompt、completion budget 和上下文 token 计数封装为一个统一的 LLM 调用接口。这个设计使得 loop 层(agent-core/src/loop/)完全无状态,只关心"给定 input 返回 output"。
主循环详细分解
以下按时间顺序逐步拆解一次 Turn 的完整执行过程:
Turn 启动
当 Agent 收到 RPC prompt 调用后,TurnFlow.prompt() 被触发。首先执行图片格式门控(gateImageFormatParts),将不被支持的图片格式(如 AVIF、HEIC)转换为文本提示,防止无效数据污染会话历史。然后分配一个单调递增的 turnId,创建 AbortController 开始异步执行 turnWorker。
如果此时 Agent 有活跃的 Goal,turnWorker 会委托给 driveGoal,在一个循环中依次执行多个 continuation turn。否则,只执行一次 runOneTurn。每个 turn 的元数据通过 turn.started 事件通知 SDK 层。
Step 循环入口(runStepLoop):在进入 LLM 调用循环前,执行以下初始化操作:
- 等待 MCP 连接管理器完成初始加载(
mcp.waitForInitialLoad(signal)) - Goal 注入:如果有激活的 Goal,将目标提醒注入到对话上下文中
- Tools Diff 注入:如果可加载工具集发生变化,注入变化通知
- 创建
mediaStripSnapshot,用于构建剥离媒体数据的消息(媒体降级恢复用)
每步预处理(beforeStep 钩子)
在每次 LLM 调用之前,loop 触发 beforeStep 钩子:
- Micro Compaction 检测:检查是否需要进行微压缩(大于 token 阈值)
- Full Compaction 检测:检查是否触发自动全量压缩
- Steer Buffer 刷新:将缓冲的 steer 消息(来自后台任务通知、cron 触发等)追加到上下文
- 上下文注入:通过
InjectionManager.inject()注入插件提醒、Skill 提示等系统消息
消息构建与 LLM 调用
loop 层通过 buildMessages 回调获取当前上下文的 Message[] 数组。ContextMemory.messages getter 在返回前执行 投影(projection) 管道:
- Micro Compaction:压缩最近的消息以控制 token 数量
- Dynamic Tool 变形:如果启用了
toolSelect,对动态工具 schema 消息进行变形处理 - Projector 矫正:修复孤儿 tool result、合并连续 assistant 消息、去除空消息等
- Media Strip(恢复路径):如果需要,构建剥离媒体的替代消息列表
然后调用 LLM.generate()(即 KosongLLM),内部将 system prompt、tools 数组和消息历史传给 this.agent.generate,最终进入 kosong 的 generate() 函数。
响应解析与工具调用
LLM 返回的 assistant 消息包含 文本内容(content: ContentPart[])和 工具调用列表(toolCalls: ToolCall[])。loop 层通过 dispatchEvent 回调逐个处理:
text.delta→ 流式文本增量,最终映射为assistant.delta事件推送到 UIthinking.delta→ 思维链增量,映射为thinking.delta事件tool.call→ 每个工具调用启动事件,映射为tool.call.startedtool.result→ 每个工具执行结果事件,映射为tool.result
工具调用去重:ToolCallDeduplicator 在每个 step 内检测同名、同参数的重复调用,避免模型陷入调用循环时重复执行相同操作。同一步内检测到重复时直接返回缓存结果(syntheticResult),跨步重复上报遥测事件但正常执行。
工具调度:tool-scheduler.ts 负责有状态的工具执行调度。具有冲突资源访问的工具调用被序列化执行,而无冲突的调用可以并行执行,遵循 provider 顺序边界。
权限检查
工具调用在执行前必须通过 PermissionManager.beforeToolCall() 的权限检查。三种权限模式下的行为:
| 模式 | 行为 |
|---|---|
yolo |
自动批准所有工具调用,无需用户干预 |
manual |
每个工具调用都需要用户明确批准(通过 SDK 的 ApprovalHandler 回调) |
auto |
自动批准"安全"的工具调用(如读取文件),敏感操作(如执行 shell 命令)仍需确认 |
如果权限检查不通过,工具调用被标记为 denied,引擎向模型注入一条说明(如"Tool call was blocked by the user"),然后继续下一轮循环。
结果处理与循环继续
工具执行完成后:
- 结果预算(
budgetToolResultForModel):对过大的工具输出进行截断,防止超出模型上下文限制 - PostToolUse 钩子:触发插件回调,允许外部系统检视或修改工具结果
- Goal 状态更新:如果工具调用是
UpdateGoal且状态为 complete/blocked,标记 Goal 已终结 - 结果追加:通过
ContextMemory.appendLoopEvent将 tool result 追加到对话历史
afterStep 钩子在每个 step 结束后执行:记录 token 使用量、触发 Full Compaction afterStep 检查、结束去重周期。如果 Goal 预算超限,返回 { stopTurn: true } 终止当前 turn。
循环终止
Step 循环在以下条件之一满足时终止:
- 模型不再调用工具:assistant 消息中
toolCalls.length === 0 - 达到最大步数限制:
maxStepsPerTurn(默认无限制,可通过KIMI_LOOP_MAX_STEPS_PER_TURN环境变量配置) - 用户主动中断:
AbortController.abort()被触发(Esc 键或 SDK cancel 调用) - Provider 过滤:LLM 返回
finishReason === 'filtered'(内容安全拦截) - 上下文溢出恢复:发生 context overflow 错误后自动压缩重试;如果压缩无效则终止
循环终止后,shouldContinueAfterStop 钩子检查以下条件是否允许继续:
- 是否有新的 steer 消息注入(steer 消息优先于 Stop 钩子)
- print 模式下是否还有后台 subagent 在运行(drain 等待其完成)
- UpdateGoal 标记为 terminal 后是否需要最后一条用户可见消息
- Stop 钩子是否请求一次额外 continuation
如果以上条件均不满足,turn 结束,发出 turn.ended 事件。
错误恢复
TurnFlow 内置了多层错误恢复机制:
- Context Overflow 恢复:捕获
APIContextOverflowError,触发 FullCompaction 压缩历史后重试 - Step 重试:每次 LLM 调用最多重试
maxRetriesPerStep次(通过KIMI_LOOP_MAX_RETRIES_PER_STEP配置),重试期间发射step.retrying事件 - Strict Messages 回退:当 LLC 提供者拒绝正常消息格式时,使用
strictMessages投影重建完全合规的请求 - Media Degrade 回退:413 错误时构建剥离媒体的降级消息列表重试
- Turn 级别失败处理:未恢复的错误导致
turn.endedwith reasonfailed
4. LLM 抽象层(kosong)
kosong(包名 @moonshot-ai/kosong,印度尼西亚语"零/空")是 kimi-code 的统一 LLM 抽象层。它将 Kimi、OpenAI、Anthropic 等不同提供商的 API 差异封装在 ChatProvider 接口之后,为上层提供一致的调用体验。
4.1 ChatProvider 接口
interface ChatProvider {
readonly name: string;
readonly modelName: string;
generate(
systemPrompt: string,
tools: Tool[],
history: Message[],
options?: GenerateOptions
): Promise<StreamedMessage>;
}
每个 provider 实现内部处理:认证头组装、URL 构造、请求体格式转换(OpenAI chat/completions vs Anthropic messages vs Kimi 自有格式)、响应流解析。上游调用方只需传入统一的 systemPrompt、tools 和 history。
4.2 generate() 函数
generate()(源码:kosong/src/generate.ts)是实际执行 LLM 调用的函数,其核心职责:
- Tool 过滤:从 tools 数组中剥离
deferred: true的工具(这些工具通过 message-level declarations 发送,以保持 prompt caching 稳定性) - Pre-flight abort 检查:如果 signal 已 abort,直接抛出而不发起网络请求
- 流式消费:通过
for await (const part of stream)逐个处理流式消息分段,处理并行工具调用的交叉 delta(通过toolCallIndexMap路由到正确的 tool call) - Part 合并:连续的同类型 part 被合并(如两个 TextPart 拼接),并行工具调用的 interleaved delta 路由正确
- 时间统计:分别记录 server decode time 和 client consume time,为性能分析提供数据
- 空响应检测:如果响应既无文本也无工具调用,抛出
APIEmptyResponseError
4.3 取消支持
取消通过 AbortController 信号实现。当用户按下 Esc 或 SDK 调用 cancel(),信号被 abort。generate 在每个异步边界检查 signal.aborted,一旦检测到中止就调用 stream.cancel() 关闭底层 HTTP 连接,然后抛出 AbortError。TurnFlow 的 runOneTurn 捕获该错误,将 turn 标记为 cancelled。
5. 响应输出
LLM 生成的响应需要一路传回用户的终端。这一过程通过多级事件转发完成:
kosong.stream → loop.dispatchEvent → Agent.emitEvent → RPC → SDK.onEvent → pi-tui.render
5.1 事件路由链
loop → agent-core:loop 层通过 createLoopEventDispatcher 回调记录 transcript 事件(appendTranscriptRecord)和发射实时事件(emitLiveEvent)。mapLoopEvent 函数(turn/index.ts:1285)将内部 loop 事件类型映射为外部 Agent 事件类型:
| Loop 事件 | Agent 事件 |
|---|---|
text.delta |
assistant.delta |
thinking.delta |
thinking.delta |
tool.call |
tool.call.started |
tool.result |
tool.result |
step.begin |
turn.step.started |
step.end |
turn.step.completed |
agent-core → SDK:Agent 通过 this.rpc?.emitEvent?.(event) 将事件推送到 RPC 通道。如果 Agent.records.restoring 为 true(正在重放历史记录),事件被静默跳过,防止重放污染实时 UI。
SDK → pi-tui:Session.onEvent() 的回调在 SDK 端接收事件。pi-tui 注册了事件监听器,对不同类型的响应做不同处理:文本 delta 被追 �� 到终端输出,工具调用事件触发权限审批 UI。
5.2 pi-tui 差分渲染
pi-tui 采用 差分渲染(delta rendering)策略更新终端显示:
- 维护一个 虚拟屏幕缓冲区(字符串矩阵)
- 接收新文本内容后,与当前缓冲区对比,只生成 变更区域 的 ANSI 转义序列
- 通过
process.stdout.write()写入仅更新实际变化的行,避免全屏刷新
这种方式在低带宽(如 SSH 连接)和高延迟场景下至关重要,确保流式输出在人眼感知上是流畅的。
6. 持久化
kimi-code 的持久化层确保会话状态在进程重启后可以完整恢复。核心组件是 AgentRecords 和 Transcript 系统。
6.1 AgentRecords — wire.jsonl
AgentRecords(agent-core/src/agent/records/)负责将所有 Agent 事件以 追加写入 的方式持久化到 <sessionDir>/wire.jsonl。每条记录是一个 JSON 对象,包含事件类型和完整数据:
{"type":"turn.prompt","input":[{"type":"text","text":"帮我写一个快速排序"}],"origin":{"kind":"user"}}
{"type":"turn.started","turnId":0,"origin":{"kind":"user"}}
{"type":"context.append_loop_event","event":{"type":"step.begin","uuid":"abc123",...}}
...
写入路径:
- Turn 级事件:
turn.prompt、turn.cancel、context.clear等由 TurnFlow 直接记录 - Step 级事件:
step.begin、step.end、tool.call、tool.result等通过ContextMemory.appendLoopEvent()记录 - 压缩事件:
context.apply_compaction记录压缩前后的 token 计数和保留消息统计
持久化写入是同步追加(appendFileSync),不在关键路径上引入异步开销。写入失败时通过 error 事件上报,但不会阻塞 Agent 的正常运行。
6.2 Transcript 系统
Agent.resume() 通过 AgentRecords.replay() 读取 wire.jsonl 并重建完整的 Agent 状态:
- Context 重建:重放
context.append_loop_event和context.append_message记录,逐步填充ContextMemory._history - 工具状态恢复:
turn.prompt记录触发的 turn 被标记为resuming,后续通过observeRestoredTurnId提升 turn 计数器 - Goal 恢复:
agent-core/src/agent/goal/index.ts在 resume 后调用normalizeAfterReplay()重建 Goal 状态 - Background 恢复:
BackgroundManager.loadFromDisk()读取<sessionDir>/tasks/下的任务文件 - Cron 恢复:
CronManager.loadFromDisk()恢复定时任务
Transcript 系统的设计原则是:wire.jsonl 是最终的真实来源(source of truth)。内存中的任何状态都可以从 wire.jsonl 重新推导出来。这使得进程崩溃后的恢复不丢失任何对话历史。
至此,我们完成了一条 Prompt 从用户指尖到 LLM 再到终端显示的完整旅程。理解这条数据通路是深入 kimi-code 内部机制的基础。下一篇将探讨 kimi-code 的关键架构决策——为什么选择某些模式而放弃另一些,以及这些决策如何塑造了整个系统的形态。
浙公网安备 33010602011771号