1. 从一次终端输入说起Claude Code 的 Agentic CLI 到底是什么你可能已经用过grep、curl、git这类传统命令行工具它们的共同点是一条命令对应一个确定动作输入什么就输出什么中间没有“思考”。而 Claude Code 这类 Agentic CLI 完全不是这个逻辑——你输入一句自然语言比如“给登录函数加错误处理”它会自己决定先读哪个文件、再改哪一行、要不要跑测试然后根据结果继续下一步直到任务完成或你按下 CtrlC。这个“自己决定下一步”的能力来自一个叫 Query Loop 的核心循环。它不是一个函数调用而是一个异步生成器async generator源码在query.ts里大约 1700 行。整个 Claude Code 的架构可以拆成六个核心抽象Query Loop、Tool System、Tasks、State、Memory、Hooks。其中 Query Loop 是心跳工具是手脚权限是刹车状态是神经记忆是经验钩子是纪律。这篇文章的目标不是让你读完就“懂了”而是让你能真正在本地跑起来、看到事件流、验证每一层的行为。我会先带你把环境配好用 TaoToken 作为模型接入层然后逐层拆解 Query Loop 的调用链路最后给出常见报错的排查方法。适合谁看适合已经用过 Claude Code 但想搞清楚它内部怎么运转的开发者也适合想自己搭一个 Agentic CLI 原型的工程师。2. 前置准备用 TaoToken 接入 Claude Code 的本地运行环境在拆架构之前你得先有一个能跑起来的 Claude Code 实例。Claude Code 本身是一个 TypeScript 单体应用通过 npm 安装后以 CLI 形式运行。它默认走 Anthropic 官方 API但国内开发者直接调用会遇到网络和计费问题。我试过用 TaoToken 作为统一接入层它提供兼容 Anthropic 协议的 API 端点配置简单适合做本地实验。TaoToken 的官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。你需要先在控制台创建一个 API Key然后把它写进 Claude Code 的配置里。Claude Code 读取配置的方式有几种环境变量、~/.claude/settings.json、项目级.claude/settings.json。我推荐用项目级配置这样不同项目可以隔离。先安装 Claude Codenpm install -g anthropic-ai/claude-code安装完成后验证版本claude --version接下来创建项目目录并初始化配置mkdir claude-code-arch cd claude-code-arch mkdir -p .claude然后在.claude/settings.json里写入以下内容。注意 Base URL 要指向 TaoToken 的 API 地址Key 换成你自己的{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-your-taotoken-key-here, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }如果你更习惯用环境变量也可以直接 exportexport ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-your-taotoken-key-here export ANTHROPIC_MODELclaude-sonnet-4-20250514这里有个坑要注意Claude Code 在启动时会读取ANTHROPIC_BASE_URL但如果你同时装了多个版本或者之前配过官方 Key可能会被覆盖。建议用claude config list检查当前生效的配置。另外Model ID 必须写完整不能只写claude-sonnet否则会报 404。配置完成后进入项目目录运行claude如果看到交互式 REPL 界面说明接入成功。你可以先输入一句“你好”测试连通性。如果返回正常文本就可以进入下一步——用无头模式观察 Query Loop 的事件流。3. 可复制配置用 settings.json 固定 Base URL、Key 与 Model ID上一节我们用了环境变量和项目级 settings.json这一节把配置固化下来方便后续反复实验。Claude Code 的配置优先级是命令行参数 环境变量 项目级 settings.json 用户级 settings.json。所以如果你在项目里写了 settings.json它会覆盖全局配置。先看一个完整的.claude/settings.json示例包含权限模式和工具白名单{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-your-taotoken-key-here, ANTHROPIC_MODEL: claude-sonnet-4-20250514 }, permissions: { allow: [ Read, Grep, Glob ], deny: [ Bash(rm -rf *), Bash(curl *) ], defaultMode: default } }这里的defaultMode对应源码里的权限模式之一。Claude Code 源码层面有七种权限模式bypassPermissions、dontAsk、auto、acceptEdits、default、plan、bubble。你在 REPL 里能切换的通常只有default、acceptEdits、plan这几种。bubble是 sub-agent 专用的遇到危险动作会向上抛给父 Agent。如果你想让 Claude Code 在读取文件时自动批准但写操作仍然询问可以设成{ permissions: { defaultMode: acceptEdits } }注意acceptEdits只自动批准文件编辑Bash 命令仍然会问。我见过很多国内开发者直接开bypassPermissions这在实验环境无所谓但如果你在真实项目里跑建议至少用acceptEdits。配置写好后用claude config list验证claude config list你应该能看到ANTHROPIC_BASE_URL指向https://taotoken.net/apiANTHROPIC_MODEL是你设置的 Model ID。如果没生效检查是不是在错误的目录下运行或者被 shell 里的 export 覆盖了。另外如果你用 Cline 或 CC Switch 这类工具管理多个模型端点需要确保三件套一致Base URL、API Key、Model ID。任何一个对不上都会导致 401 或 404。TaoToken 的 API Key 可以在控制台的 API Keys 页面创建地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。4. 验证请求用 claude -p 观察 Query Loop 的事件流配置好之后最直接的验证方式是用无头模式claude -p跑一条简单指令然后观察它输出的 JSON 事件流。这能让你直观看到 Query Loop 是怎么一轮一轮跑的。先跑一个最简单的claude -p 你好 --output-format stream-json你会看到类似下面的事件序列数据已脱敏{type:system,subtype:init,cwd:redacted,session_id:redacted,tools:[Read,Edit,Bash],model:model} {type:system,subtype:status,status:requesting} {type:stream_event,event:{type:message_start}} {type:stream_event,event:{type:content_block_delta,delta:{type:text_delta,text:你好}}} {type:assistant,message:{role:assistant,content:[{type:text,text:你好}]}} {type:stream_event,event:{type:message_delta,delta:{stop_reason:end_turn}}} {type:result,subtype:success,result:你好,stop_reason:end_turn}逐条解释一下system/init表示会话初始化里面带了当前工作目录、session ID、可用工具列表和模型名。system/status requesting表示开始请求模型。stream_event/message_start是模型开始返回流式响应。stream_event/content_block_delta是模型在持续输出文本。assistant是 SDK 整理出来的 assistant Message方便上层消费。stream_event/message_delta是消息级别的更新这里带了stop_reason。最后的result/success表示整个query()执行完成。这个事件流就是 Query Loop 的输出。它是一个 async generator外面用for await一段一段消费。伪代码大概是这样for await (const event of query(input)) { render(event); }这种设计的好处是控制权在外层。外层处理完一条消息才继续拉下一条。如果外层是终端 UI用户按 CtrlC 时可以沿着这条链路通知正在运行的任务中断并把停止原因标记为用户取消。现在跑一个会触发工具调用的指令观察工具事件claude -p 读取当前目录下的 package.json 并告诉我 name 字段 --output-format stream-json你会看到中间多了tool_use和tool_result类型的事件。这说明 Query Loop 在模型返回工具调用后交给了 Tool System 执行然后把结果追加回上下文再继续下一轮。整个循环直到模型不再请求工具为止。如果你想看更详细的调用链路可以加--debug参数claude -p 读取 package.json --debug这会打印出 Query Loop 每一轮的输入消息和输出事件适合对照源码理解。5. 常见报错排查401、local proxy failed、reading choices、OAuth配置和验证过程中最容易遇到几类报错这里逐个拆解。401 Unauthorized最常见的原因是 API Key 写错或过期。先检查.claude/settings.json里的ANTHROPIC_API_KEY是否和 TaoToken 控制台里的一致。注意 Key 通常以sk-开头不要有多余空格。如果用的是环境变量用echo $ANTHROPIC_API_KEY确认。另外Base URL 必须指向https://taotoken.net/api不能带尾部斜杠也不能写成https://taotoken.net/api/v1否则会 404 或 401。local proxy failed这个报错通常出现在你本地配了 HTTP 代理但代理没启动或端口不对。Claude Code 会读取HTTP_PROXY和HTTPS_PROXY环境变量。如果你不需要代理直接 unsetunset HTTP_PROXY unset HTTPS_PROXY然后重新运行。如果你确实需要走本地代理确保代理进程在监听并且端口和配置一致。reading choices这个报错一般出现在模型返回的响应格式不符合预期时。常见原因是 Model ID 写错了比如写成了claude-sonnet而不是完整的claude-sonnet-4-20250514。另一个原因是 Base URL 指向了一个不兼容 Anthropic 协议的端点。检查ANTHROPIC_MODEL是否和 TaoToken 支持的模型列表一致。OAuth 相关报错如果你之前用claude login登录过官方账号配置里可能残留了 OAuth token。Claude Code 会优先使用 OAuth 而不是 API Key。解决办法是删除~/.claude/下的凭证文件或者运行claude logout然后重新用 API Key 配置。如果你用的是 CC Switch 或 Cline 管理多端点确保没有同时启用 OAuth 和 API Key。还有一个容易忽略的点如果你在.claude/settings.json里配了permissions.deny但工具调用被拒绝了事件流里会出现permission_denied。这时候检查 deny 列表是不是写得太宽比如Bash(curl *)会阻止所有 curl 命令。排查完这些重新跑claude -p 你好 --output-format stream-json如果能看到完整的result/success说明接入层已经通了。6. 继续深入从 Query Loop 到多 Provider 架构的下一步到这里你已经有了一个能跑的 Claude Code 实例也看到了 Query Loop 的事件流。接下来可以沿着这条链路继续往下拆。Query Loop 的核心是一个for await循环它调用模型、接收流式响应、收集工具调用、执行工具、把结果追加回上下文然后继续下一轮。工具系统里有一个 StreamingToolExecutor它不会等模型整段回复结束才执行工具只要看到并发安全的工具比如 Read、Grep就会提前启动。这叫 speculative execution代价是有可能重跑但换来了更低的延迟。Tasks 是递归的 Query Loop。当 AgentTool fork 出一个 sub-agent 时sub-agent 走的是同一套 query loop只是有自己的上下文、工具集合和权限模式。sub-agent 默认走 bubble 模式遇到危险动作不能自己批准要向上抛给父 Agent。State 分两层一层是会话级的可变单例 STATE保存 cwd、sessionId、model、permissionMode 等大约 80 个字段另一层是 UI 状态用 Zustand 做响应式管理。Memory 是跨会话的持久上下文通过~/.claude/CLAUDE.md、项目级CLAUDE.md、模块级CLAUDE.md三层加载。Hooks 是生命周期拦截器在工具调用前后、prompt 提交、会话结束等 27 个事件上触发。多 Provider 架构通过工厂函数getAnthropicClient()在启动时决定用哪个 provider然后适配器模式让外层用同一套接口调用。Query Loop 不关心底层是 Direct API 还是其他云厂商职责分离得很干净。下一篇我会从启动流程开始拆解 Claude Code 从 CLI 入口到 REPL 初始化的完整链路。如果你想先自己动手可以用claude -p 读取 package.json --debug观察每一轮的消息流转对照query.ts的源码理解。遇到问题可以查接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 或者直接在模型对话里问https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。如果你打算长期跑编码任务或 Agent 实验Coding Plan 会更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。