这是 Claude Code 最核心的模块。理解了它,就理解了整个系统的运作原理。
一、第一性原理:什么是 Agentic Loop?
传统 Chatbot:
User → LLM → Answer (一次性)
|
Agentic Loop:
User → LLM → [要用工具] → 执行工具 → [结果] → LLM → [还要用工具] → ... → 最终回答
|
核心区别:LLM 自主决定是否需要更多信息/操作,循环直到任务完成。
二、两个核心文件
| 文件 |
行数 |
角色 |
query.ts |
1729 行 |
REPL 交互模式的查询循环(generator 函数) |
QueryEngine.ts |
1295 行 |
SDK/Headless 模式的查询引擎(class) |
两者共享相同的核心逻辑,但:
query.ts 使用 Generator (function*),便于 REPL 逐步渲染
QueryEngine.ts 使用 Class,便于 SDK 编程控制
三、query() 函数 — 核心循环解析
export async function* query( userMessage: UserMessage, assistantMessages: Message[], systemPrompt: SystemPrompt, tools: Tools, ): AsyncGenerator<StreamEvent> {
while (true) { const messages = normalizeMessagesForAPI(allMessages); const config = buildQueryConfig(model, tools, systemPrompt);
const stream = await claudeAPI.stream(messages, config);
for await (const event of stream) { yield event;
if (event.type === 'content_block_start' && event.content_block.type === 'tool_use') { } }
if (stopReason === 'end_turn') { break; }
if (stopReason === 'tool_use') { const toolResults = await* runTools(toolUseBlocks, context);
allMessages.push(...toolResults);
}
if (exceedsBudget) break;
if (needsCompaction) { await autoCompact(allMessages); } } }
|
四、循环状态机
┌─────────────┐ │ START │ └──────┬──────┘ │ ┌──────▼──────┐ ┌───►│ Call Claude │ │ │ API │ │ └──────┬──────┘ │ │ │ ┌──────▼──────┐ │ │ Process │ │ │ Stream │◄── yield events (UI渲染) │ └──────┬──────┘ │ │ │ ┌──────▼──────┐ │ │ stop_reason │ │ │ ? │ │ └──┬─────┬───┘ │ │ │ │ tool_use end_turn │ │ │ │ ┌─────▼───┐ │ ┌──────────┐ │ │ Execute │ └───►│ DONE │ │ │ Tools │ └──────────┘ │ └─────┬───┘ │ │ │ ┌─────▼─────────┐ │ │ Check Budget │ │ │ Auto-Compact? │ │ └─────┬─────────┘ │ │ └───────┘
|
五、QueryEngine 类 — SDK 模式
export class QueryEngine { private mutableMessages: Message[]; private readFileState: FileStateCache; private totalUsage: NonNullableUsage; private permissionDenials: SDKPermissionDenial[];
constructor(config: QueryEngineConfig) { }
async *submitMessage(userMessage: string): AsyncGenerator<SDKMessage> { }
getMessages(): Message[] { ... }
getUsage(): NonNullableUsage { ... } }
|
QueryEngine 的生命周期
SDK 调用方 │ ├─ new QueryEngine(config) ← 创建实例 │ ├─ engine.submitMessage("修复 bug") ← Turn 1 │ ├─ yield: assistant_message │ ├─ yield: tool_use (read file) │ ├─ yield: tool_result │ ├─ yield: assistant_message │ └─ yield: turn_complete │ ├─ engine.submitMessage("再加测试") ← Turn 2 │ └─ ... (复用之前的消息历史) │ └─ engine.getUsage() ← 获取统计
|
六、工具执行机制
6.1 并行 vs 串行
function partitionToolCalls(toolUseMessages, context): Batch[] { }
|
核心算法:
工具序列: [FileRead, GrepSearch, FileRead, FileEdit, FileRead] 分区结果: Batch 1 (并行): [FileRead, GrepSearch, FileRead] ← 只读工具可并行 Batch 2 (串行): [FileEdit] ← 写入工具必须串行 Batch 3 (并行): [FileRead] ← 只读恢复并行
|
每个工具通过 isConcurrencySafe() 方法声明自己是否可并行:
- 可并行: FileRead, Glob, Grep, WebSearch
- 不可并行: FileEdit, FileWrite, BashTool (可能有副作用)
class StreamingToolExecutor { addTool(block: ToolUseBlock, assistantMessage: AssistantMessage) async *getRemainingResults(): AsyncGenerator<MessageUpdate> }
|
优化原理:当 LLM 输出多个 tool_use block 时,不等全部输出完就开始执行前面的工具,减少等待时间。
6.3 最大并发控制
function getMaxToolUseConcurrency(): number { return parseInt(process.env.CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY || '', 10) || 10; }
|
默认最多 10 个工具并行执行。
七、系统 Prompt 构建
const systemPrompt = await fetchSystemPromptParts({ systemContext: await getSystemContext(), userContext: await getUserContext(), customSystemPrompt, appendSystemPrompt, toolDescriptions, mcpInstructions, });
|
System Prompt 的分层结构:
[1] 核心系统指令 (constants/prompts.ts) ├── 角色定义 ("你是 Claude,一个 AI 编程助手") ├── 工具使用规范 ├── 安全约束 └── 输出格式要求
[2] 环境上下文 (context.ts) ├── 当前日期 ├── Git 状态 (branch, status, recent commits) └── 平台信息
[3] 用户上下文 ├── CLAUDE.md 文件内容 (项目级配置) ├── ~/.claude/CLAUDE.md (全局配置) └── 工作目录的 .claude/CLAUDE.md
[4] 工具定义 ├── 内置工具 schema ├── MCP 工具 schema └── 插件工具 schema
[5] 附加上下文 ├── Memory 文件 └── 用户自定义追加内容
|
八、Token 预算管理
export function createBudgetTracker(config) { return { checkBudget(outputTokens: number): 'continue' | 'stop' { if (outputTokens >= maxBudget) return 'stop'; return 'continue'; } }; }
|
预算系统用于 SDK 场景,防止无限循环消耗 token:
--max-turns <n>: 限制最大 turn 数
--max-budget <usd>: 限制最大花费
- 自动检测输出 token 是否超过上下文窗口
九、自动压缩触发
const warningState = calculateTokenWarningState(tokenUsage, model);
if (warningState.isAboveAutoCompactThreshold) { const result = await compactConversation(messages, context); messages = result.compactedMessages; }
|
阈值计算:
有效上下文窗口 = contextWindow - maxOutputTokens(model) 自动压缩阈值 = 有效上下文窗口 - 13,000 tokens (缓冲) 警告阈值 = 有效上下文窗口 - 20,000 tokens
|
十、错误处理与重试
十一、Generator 模式的优势
query() 使用 async function* (异步 Generator) 的设计是关键:
for await (const event of query(message, messages, ...)) { switch (event.type) { case 'text': updateUI(event.text); break; case 'tool_use': showToolProgress(event); break; case 'tool_result': showToolResult(event); break; } }
|
为什么不用回调/EventEmitter?
- Generator 保持了 顺序控制流,代码可读性好
- 调用方可以 暂停/恢复 消费(背压控制)
- 与 React 的渲染周期完美配合
- 错误可以通过
try/catch 正常捕获(不像 EventEmitter)