Claude Code源码泄漏,拆解一个工业级 AI Coding Agent 是如何工作的
一、先讲结论
Claude Code 并不是一个简单的”LLM + 命令行包装”。它是一个用 TypeScript + React (Ink) 构建的、运行在终端里的完整 Agent 操作系统。
几个关键判断:
-
技术栈:主要基于 Bun 运行时(构建期依赖
bun:bundle做死代码消除),同时部分路径兼容 Node(如容器环境),UI 用 React Ink,API 用 Anthropic SDK,整体约 60+ 内置工具、60+ 用户命令,代码量非常大。此外还用纯 TypeScript 重写了三个原本的 native 依赖(syntax-highlighted diff、fuzzy file search、yoga flexbox layout),显著缩小了 NAPI 依赖面——但并未彻底消除,仓库中仍有audio-capture-napi(语音录制)、url-handler-napi(macOS URL scheme)、image-processor-napi(处理/剪贴板)、modifiers-napi(键盘修饰键检测)等 NAPI 模块在用 -
核心循环:经典的 ReAct 循环(Query → LLM → ToolUse → ToolResult → LLM → …),但在此基础上做了大量工程优化——流式执行、并行工具调用、自动上下文压缩
-
权限模型:这是整个系统最重的部分之一,多阶段权限检查(deny rules → ask rules → 工具自检 → permission mode → 白名单 → 用户确认 / classifier 并发竞速),安全性设计得很扎实
-
多 Agent 架构:内置 Coordinator 模式(多 Agent 协作)和 Agent Swarms(团队创建/删除),已经不是单 Agent 产品了
-
Bridge 模式:支持远程执行,通过 WebSocket 实现桌面端 ↔ 远程服务器的会话桥接
-
记忆与梦境:可以看作三层渐进式记忆管线(Auto Memory Extraction → Session Memory → Auto Dream 跨会话记忆整合),各层都有独立 gate 和运行条件,后台 forked agent 模式实现,不阻塞主对话
-
插件/技能/Hooks 扩展体系:不只是工具扩展,还有 marketplace 安装、skill frontmatter 配置、pre/post 执行 hooks、MCP skill builders —— 已经是完整的扩展平台
如果你在造 Coding Agent,这份源码是最好的参考实现之一。
二、启动流程:极致的并行初始化
看 main.tsx的前 20 行就知道,Anthropic 在启动性能上下了很大功夫:
// 这三个 side-effect 必须在所有其他 import 之前运行:import { profileCheckpoint } from './utils/startupProfiler.js';profileCheckpoint('main_tsx_entry');import { startMdmRawRead } from './utils/settings/mdm/rawRead.js';startMdmRawRead(); // 并行:MDM 配置子进程import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js';startKeychainPrefetch(); // 并行:macOS keychain 预读(OAuth + API key)
这里的设计思路是:把昂贵的 I/O 操作(钥匙串读取、MDM 配置读取)提前到 import 阶段并行执行。因为 Node/Bun 的 import 本身需要 ~135ms 来加载模块,这段时间正好可以用来做异步预热。 整个交互式启动序列大致如下:
注意:-p / –print 非交互模式下不会弹出 Trust Dialog——源码注释明确写了 “trust is implicit in -p mode”。
关键点:
-
Trust Dialog 是安全门:在用户确认信任之前,不会加载完整的环境变量和配置。GrowthBook 的 reset/reinit 也发生在 trust 之后
-
两套 Feature Flag 机制不要混淆:feature(‘COORDINATOR_MODE’) 来自 bun:bundle,是构建期死代码消除,编译后不存在;GrowthBook/Statsig 是运行期远程配置,走 getFeatureValue_CACHED_MAY_BE_STALE() / checkStatsigFeatureGate_CACHED_MAY_BE_STALE() 代码路径
-
懒加载 + 条件导入:大量使用 require() 延迟加载来打破循环依赖
三、Agent 核心循环:QueryEngine 是大脑
Claude Code 的核心循环有两条路径:
交互模式:REPL → query()(直接调用)Headless/SDK 模式:QueryEngine → query()
一个容易误解的点:REPL 并不经过 QueryEngine。QueryEngine 的注释明确写了 “供 headless/SDK 使用,REPL 是 future phase”。从 screens/REPL.tsx 第 2793 行可以看到,REPL 直接 for await (const event of query({…})) 调用 query()。
QueryEngine:Headless/SDK 的会话管理
QueryEngine 是 headless/SDK 模式下每个对话的状态持有者,负责:
export class QueryEngine {private mutableMessages: Message[] // 消息历史private abortController: AbortController // 中断控制private permissionDenials: SDKPermissionDenial[] // 权限拒绝记录private totalUsage: NonNullableUsage // 累计 token 用量private readFileState: FileStateCache // 文件状态缓存private discoveredSkillNames = new Set<string>() // 技能发现追踪}
它的核心方法是 submitMessage(),每次用户发送消息时被调用,触发一轮完整的 Agent 循环。
query():单轮执行的核心
query() 函数是真正的执行引擎,实现了一个 generator 模式的 Agent 循环:
几个工程亮点:
1. 流式工具执行(StreamingToolExecutor)
Claude Code 不是等 LLM 完整输出后再执行工具,而是在流式响应的过程中就开始准备工具调用:
import { StreamingToolExecutor } from './services/tools/StreamingToolExecutor.js'
2. 工具并行 vs 串行的智能分区(两套实现)
这里有一个很容易忽略的点:源码中有两套并行执行实现,分别用于不同场景。
旧路径:toolOrchestration.ts(批量分区)
function partitionToolCalls(toolUseMessages, toolUseContext): Batch[] {// 对每个工具调用,根据 tool.isConcurrencySafe(parsedInput) 判定:// 1. 连续的 concurrency-safe 工具 → 并行执行(最多 10 并发)// 2. 单个非 concurrency-safe 工具 → 串行执行}
新路径:StreamingToolExecutor(流式并发) 当 streamingToolExecution gate 开启时,工具不再等 LLM 一次性返回所有 tool_use 再分区,而是一边流式接收 tool_use 块,一边立即开始执行:
class StreamingToolExecutor {addTool(block: ToolUseBlock, assistantMessage: AssistantMessage) {// 流式接收到一个 tool_use 就立即入队// 如果当前没有非并发安全的工具在执行,立即启动}private canExecuteTool(isConcurrencySafe: boolean): boolean {const executingTools = this.tools.filter(t => t.status === 'executing')return executingTools.length === 0 ||(isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))}}
还有一个精巧的错误传播机制:当某个并发 Bash 工具出错时,StreamingToolExecutor 会通过 siblingAbortController(父 AbortController 的子控制器)立即取消兄弟进程,而不会中断父 query 循环。
注意判定依据不是简单的”只读 vs 写入”,而是每个工具自己实现的 isConcurrencySafe(input) 方法,会根据具体输入参数判断。比如 FileReadTool 通常返回 true,但 BashTool 会根据命令内容决定。
3. 自动上下文压缩(Auto Compact)
当对话的 token 数接近上下文窗口时,系统会自动触发压缩:
// 阈值 = 有效上下文窗口 - 13000 tokens 缓冲export const AUTOCOMPACT_BUFFER_TOKENS = 13_000// 连续失败超过 3 次就停止重试(避免浪费 API 调用)const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
但 auto compact 只是压缩策略之一。源码中至少还有这些路径:
-
Auto Compact:标准的摘要压缩,调用 Claude 对历史消息做摘要替换
-
Session Memory Compaction:把重要信息沉淀到 session memory
-
Reactive Compact:在
prompt_too_long错误时被动触发的紧急压缩 -
Microcompact:更轻量的增量压缩,通过 cache editing 实现
-
History Snip:按 snip boundary 截断历史,在启用
HISTORY_SNIPfeature flag 时参与请求前压缩 -
Context Collapse:折叠大块上下文(feature flag
CONTEXT_COLLAPSE)
这些路径不是互斥的——snip 和 microcompact 可以同时运行,autocompact 在它们之后触发。
从 query.ts 的循环体可以看到完整的压缩流水线执行顺序:
每次循环迭代 → applyToolResultBudget(裁剪工具结果大小)→ snipCompactIfNeeded(按边界截断)→ microcompact(时间维度清理旧工具结果)→ contextCollapse(折叠大块上下文)→ autocompact(摘要压缩)
4. max_output_tokens 恢复循环
当 Claude 的回复被截断(达到 max_output_tokens 限制)时:
const MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3// 最多重试 3 次,每次自动 continue
四、工具系统:60+ 工具的注册与分发
工具注册表
tools.ts 是内置工具(built-in tools) 的注册中心。MCP 工具(MCPTool、McpAuthTool)不在这里注册,而是在 MCP client 层(services/mcp/client.ts)动态创建,最后由 assembleToolPool() 合并进统一的工具池。
从源码可以看到 built-in 工具列表(不含 MCP 动态工具):

Feature Flag 驱动的条件编译
工具注册大量使用了 Bun 的 feature() 做编译期死代码消除:
const WorkflowTool = feature('WORKFLOW_SCRIPTS')? (() => {require('./tools/WorkflowTool/bundled/index.js').initBundledWorkflows()return require('./tools/WorkflowTool/WorkflowTool.js').WorkflowTool})(): nullconst MonitorTool = feature('MONITOR_TOOL')? require('./tools/MonitorTool/MonitorTool.js').MonitorTool: null
当某个 feature flag 关闭时,对应的 require() 不会执行,相关代码会被 Bun 的 bundler 彻底剔除。这不是运行时检查,而是构建期优化。
工具权限过滤
在工具到达 LLM 之前,会经过 deny rules 过滤:
export function filterToolsByDenyRules(tools, permissionContext) {return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))}
被 deny 的工具连 schema 都不会发送给模型——模型根本不知道这些工具的存在。
五、权限系统:多阶段防线
这是 Claude Code 最值得学习的部分之一。真实的权限检查流程比”四道关”复杂得多,大致如下:
权限模式(PermissionMode)
源码中的模式比”三种”多得多:
// types/permissions.tsexport const EXTERNAL_PERMISSION_MODES = ['acceptEdits', // 自动接受编辑操作'bypassPermissions', // 旁路,所有操作自动通过'default', // 默认,敏感操作需确认'dontAsk', // 不弹确认框(后台 agent 用)'plan', // 计划模式,只读自动通过] as const// 内部还有:export type InternalPermissionMode =| ExternalPermissionMode| 'auto' // feature flag 开启时的自动模式| 'bubble' // 权限冒泡(子 agent → 父 agent)
Bash 工具的安全检查
BashTool 的权限检查特别复杂,单独有一个 bashSecurity.ts:
-
AST 解析:用 parseForSecurity() 解析 bash 命令的 AST,而不是简单的字符串匹配
-
sed 编辑检测:单独的 sedEditParser.ts 来判断 sed 命令是否在做文件编辑
-
破坏性命令警告:destructiveCommandWarning.ts 检测 rm -rf 等危险命令
-
沙箱执行:shouldUseSandbox() 判断是否需要在 sandbox 中执行
-
路径验证:pathValidation.ts 确保不会操作项目外的文件
Classifier 辅助判断
源码中有 TRANSCRIPT_CLASSIFIER feature flag:
const classifierDecisionModule = feature('TRANSCRIPT_CLASSIFIER')? require('./classifierDecision.js'): null
这意味着 Anthropic 在权限判断中还引入了一个分类器模型,来辅助判断工具调用是否安全。
六、任务系统:7 种任务类型
Task.ts 定义了任务的类型系统:
export type TaskType =| 'local_bash' // 本地 Shell 命令| 'local_agent' // 本地子 Agent| 'remote_agent' // 远程 Agent| 'in_process_teammate' // 进程内队友(Agent Swarms)| 'local_workflow' // 本地工作流| 'monitor_mcp' // MCP 监控| 'dream' // "做梦"(后台思考?)export type TaskStatus =| 'pending' | 'running' | 'completed' | 'failed' | 'killed'
每个任务都有一个密码学安全的 ID:
// 前缀 + 8字节随机 = 36^8 ≈ 2.8 万亿种组合// 足以抵御符号链接攻击export function generateTaskId(type: TaskType): string {const prefix = getTaskIdPrefix(type) // b/a/r/t/w/m/dconst bytes = randomBytes(8)// ...}
七、多 Agent 协作:Coordinator Mode 与 Agent Swarms
Coordinator Mode
当 CLAUDE_CODE_COORDINATOR_MODE=1 时,Claude Code 进入协调者模式:
export function isCoordinatorMode(): boolean {if (feature('COORDINATOR_MODE')) {return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)}return false}
在这个模式下:
-
协调者只能使用 AgentTool、TaskStopTool、SendMessageTool、SyntheticOutputTool(不能直接操作文件)
-
工作者(子 Agent)才能使用 Bash/Read/Edit 等工具
-
通过 SendMessageTool 在 Agent 间传递消息
// constants/tools.tsexport const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([AGENT_TOOL_NAME,TASK_STOP_TOOL_NAME,SEND_MESSAGE_TOOL_NAME,SYNTHETIC_OUTPUT_TOOL_NAME,])
Agent Swarms(团队)
// 源码中的团队管理工具TeamCreateTool // 创建团队成员TeamDeleteTool // 删除团队成员SendMessageTool // 向团队成员发消息ListPeersTool // 列出所有 Agent 同伴
这就是 Anthropic 的”多 Agent 协作”实现——不是通过外部编排框架,而是直接内建在工具系统里。
子 Agent 执行流程
runAgent.ts 揭示了子 Agent 的运行方式:
-
创建独立的 FileStateCache(文件状态隔离)
-
编译子 Agent 专属的 System Prompt
-
连接必要的 MCP 服务器
-
启动独立的 query() 循环
-
记录侧链 transcript(用于观察和调试)
子 Agent 有自己的工具限制(CUSTOM_AGENT_DISALLOWED_TOOLS)。外部构建中,AgentTool 默认在禁止列表里,防止无限递归;但 Anthropic 内部构建(USER_TYPE === ‘ant’)允许嵌套 agent。此外,in-process teammate 在特定条件下也能获得 AgentTool。
Fork Subagent:一种更轻量的子 Agent
除了上面通过 AgentTool 显式创建的子 Agent,源码中还有一种 Fork Subagent 模式(FORK_SUBAGENT feature flag):
-
继承完整上下文:fork child 拿到父对话的所有消息 + 字节精确相同的 system prompt
-
Prompt Cache 共享:因为 system prompt 字节完全一致,fork child 可以命中父对话的 prompt cache,大幅降低成本
-
权限冒泡:fork child 的权限模式是 ‘bubble’,权限请求会上浮到父 agent 由用户确认
-
防递归:isInForkChild() 检测并阻止嵌套 fork
这个设计非常聪明——Fork Subagent 本质上是”用当前对话上下文开一个分支去做某件事”,而不是 AgentTool 那种”创建一个新 agent 从零开始”。适用于”帮我同时验证这三个文件”这类场景。
八、上下文系统:你看到的 System Prompt 背后
context.ts 只负责产出 systemContext / userContext 两个片段——前者主要是 git 状态快照,后者主要是 CLAUDE.md 内容。
// context.ts → getSystemContext() 收集:const [branch, mainBranch, status, log, userName] = await Promise.all([getBranch(),getDefaultBranch(),execFileNoThrow(gitExe(), ['status', '--short']),execFileNoThrow(gitExe(), ['log', '--oneline', '-n', '5']),execFileNoThrow(gitExe(), ['config', 'user.name']),])// → 打包成 git 状态快照
真正的 System Prompt 组装分散在多个文件中:
getSystemPrompt() ← constants/prompts.ts:基础提示词模板fetchSystemPromptParts() ← utils/queryContext.ts:协调 systemPrompt + userContext + systemContextbuildEffectiveSystemPrompt() ← REPL 层的最终组装
最终发送给模型的请求 payload 包括:
systemPrompt(以下内容拼接而成):基础 System Prompt(prompts.ts)+ 环境信息(OS、shell、日期)+ systemContext(git 状态快照)+ userContext(CLAUDE.md 内容)+ Memory 文件(用户记忆)+ Coordinator 上下文(如果是协调者模式)+ customSystemPrompt / appendSystemPrompt(用户自定义)+ 技能(Skills)上下文tools(独立字段,不属于 system prompt):built-in tools + MCP tools 的 schema 定义
CLAUDE.md:多层级配置
Claude Code 的 CLAUDE.md 不只是”项目根目录的一个文件”,而是一个多层级配置系统。claudemd.ts 开头的注释写得很清楚:
加载顺序(从低优先级到高优先级):1. Managed memory(/etc/claude-code/CLAUDE.md)— 全局管理员指令2. User memory(~/.claude/CLAUDE.md)— 用户级全局指令3. Project memory — 项目级指令,包括:- CLAUDE.md- .claude/CLAUDE.md- .claude/rules/*.md(从 git root 到当前目录递归查找,越近优先级越高)4. Local memory(CLAUDE.local.md)— 本地私有项目指令(不应提交到 git)
还支持 @include 指令引用其他文件,形成组合配置。
Memory 系统:三层记忆 + Auto Dream
这是博客首次发现时容易低估的部分。Claude Code 的记忆不是一个简单的 MEMORY.md 文件——从代码结构来看,可以归纳为一个三层渐进式记忆管线(我的分析框架,非源码官方分层)。需要注意的是,这三层都带有独立的 gate 或运行条件,不是无条件常驻能力:Auto Memory 要检查 isAutoMemoryEnabled() 且 remote 模式会跳过;Session Memory 由 tengu_session_memory feature flag 控制;Auto Dream 在 KAIROS 和 remote 模式下直接关闭:

第一层:Auto Memory Extraction
每轮 query 结束后(不再有 tool_use 时),后台 forked agent 自动提取对话中的关键洞察,写入项目记忆目录。
-
使用 runForkedAgent() + Prompt Cache 共享(和主对话共用 cache prefix,避免双倍 API 成本)
-
有 cursor 追踪(sinceUuid):如果主 agent 自己已经写了记忆,forked agent 会跳过,避免重复
-
工具受限:只能用 Read/Grep/Glob + Edit/Write(仅 auto-memory 路径)
第二层:Session Memory
当对话足够长时(三重阈值:10K tokens 初始化 + 5K tokens 增量 + 3 次工具调用),自动提取 session 级笔记。
这个 session memory 还有一个精巧的用途:Session Memory Compaction 可以用 session memory 的内容来替代 autocompact 的摘要——因为 session memory 是增量提取的,质量可能比一次性摘要更好。
第三层:Auto Dream(记忆整合)
这是最有趣的设计。当满足两个条件:
-
距离上次整合 ≥ 24 小时
-
期间积累了 ≥ 5 个 session transcript
Claude Code 会启动一个 “dream” 任务(TaskType = ‘dream’),用 forked agent 遍历多个 session 的 transcript,把分散的记忆整合沉淀到项目记忆中。
const DEFAULTS: AutoDreamConfig = {minHours: 24,minSessions: 5,}
Dream 任务有完整的生命周期管理(register → phase tracking → complete/fail),可以被 kill 并回滚 consolidation lock 的 mtime。这解释了 Task.ts 中那个神秘的 ‘dream’ 类型。
MEMORY.md 入口文件
所有记忆内容通过 MEMORY.md 入口文件注入 system prompt:
export const MAX_ENTRYPOINT_LINES = 200export const MAX_ENTRYPOINT_BYTES = 25_000
有行数和字节数双重截断保护——因为用户可能写出单行超长的 MEMORY.md(实际观测到 197KB 在 200 行以下的情况)。
九、MCP 集成:不只是客户端
Claude Code 对 MCP(Model Context Protocol)的支持非常深入:
// 支持所有 MCP 传输方式import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
-
四种传输协议:Stdio、SSE、StreamableHTTP、WebSocket
-
OAuth 认证:内建 OAuth 流程支持 MCP 服务器认证
-
资源缓存:prefetchAllMcpResources() 预加载 MCP 资源
-
工具合并:MCP 工具和内置工具在 assembleToolPool() 中统一合并,内置工具优先
-
官方注册表:prefetchOfficialMcpUrls() 从官方注册表获取 MCP 服务器列表
十、Bridge 模式:远程执行架构
bridge/ 目录揭示了 Claude Code 的远程执行能力:
bridgeMain.ts ← 入口bridgeMessaging.ts ← 消息序列化(ndjson)bridgePermissionCallbacks.ts ← 远程权限委派replBridge.ts ← REPL 桥接replBridgeTransport.ts ← WebSocket 传输层sessionRunner.ts ← 远程会话运行器trustedDevice.ts ← 设备信任jwtUtils.ts ← JWT 认证
这套架构允许 Claude Code 在这种场景下工作:
-
桌面 VS Code 连接远程服务器
-
权限弹窗在本地桌面显示
-
实际工具执行在远程服务器
-
通过 WebSocket 多路复用传输
十一、扩展体系:Plugin + Skill + Hooks
Claude Code 不只是一个封闭的工具集——它已经构建了一套完整的扩展体系。
Plugin 系统
Built-in Plugins(内建插件)├── 随 CLI 发布,{name}@builtin 格式├── 用户可 enable/disable(/plugin UI)└── 可提供 skills + hooks + MCP serversMarketplace Plugins(市场插件)├── 声明在 settings(可信源)├── 缓存在 ~/.claude/marketplaces/├── 后台安装不阻塞启动└── 安装后自动 cache 清理 + MCP 重连
插件安装管理器(PluginInstallationManager.ts)实现了一个完整的 diff-reconcile 流程:先算出 missing/updated/up-to-date/failed 的差异,再异步并行安装,带进度回调到 UI。
Skill 系统
Skill 是比 Plugin 更轻量的扩展单元——本质上是带 YAML frontmatter 的 Markdown 文件:
-
支持 @include 引用、argument 替换、shell 执行指令
-
来源多样:bundled / user / project / plugin / MCP
-
MCP Skill Builders:MCP 服务器可以动态注册 skill builder,在运行时创建新的 skill
-
发现机制:EXPERIMENTAL_SKILL_SEARCH gate 下,每轮对话都会预取可能相关的 skill
Hooks 系统(不是 React Hooks)
utils/hooks/ 下有一套独立的执行生命周期 hook 系统:
Hook 不只是 shell command——源码定义了四种 hook 类型:command(shell 命令)、prompt(LLM prompt)、agent(多轮 Agent 查询)、http(HTTP POST)。各类型的默认超时也不同:shell/tool hook 默认 10 分钟,prompt hook 默认 30 秒,agent hook 默认 60 秒,http hook 默认 10 分钟,只有 AsyncHookRegistry 的 asyncTimeout 默认 15 秒。所有 hook 支持进度增量输出(stdout/stderr delta 实时推送 UI)。
十二、UI 层:终端里的 React
Claude Code 用 Ink(React for CLI)构建终端 UI。以下是高层示意的组件结构(非实际渲染树):
App (AppStateProvider + KeybindingSetup)├── REPL (主循环)│ ├── VirtualMessageList (虚拟滚动消息列表)│ ├── PromptInput (文本输入,支持 Vim 模式)│ └── StatusLine (底部状态栏)└── Dialogs (信任对话框、设置、入职引导)
几个有趣的点:
-
Vim 模式:完整的 vim/ 目录,实现了 normal/insert/visual/command 四种模式、motion/operator/text-object 全套
-
Voice:语音输入/输出,但要求 Anthropic OAuth 认证(不支持 API key),走 claude.ai 的 voice_stream 端点
-
虚拟滚动:100+ 组件的消息列表不是全量渲染
-
Buddy 系统:隐藏的聊天伙伴精灵系统。用 FNV-1a 哈希 + Mulberry32 PRNG 从用户配置确定性生成,有 5 级稀有度(common → legendary),有属性点分配。纯 fun feature
native-ts 纯 TS 实现:native-ts/ 目录包含三个原本是 native NAPI 模块的纯 TypeScript 重写:
-
color-diff/:语法高亮 diff(替代 Rust syntect,改用 highlight.js)
-
file-index/:模糊文件搜索(替代 Rust nucleo,异步构建 index,每 4ms yield 事件循环)
-
yoga-layout/:Flexbox 布局引擎(替代 Meta 的 C++ yoga-layout,单 pass 实现 Ink 所需的子集)
十三、值得注意的工程模式
1. 两套 Feature Flag:构建期 vs 运行期
这是整个项目最独特的模式。通过 Bun 的 feature() 函数:
import { feature } from 'bun:bundle'const coordinatorModeModule = feature('COORDINATOR_MODE')? require('./coordinator/coordinatorMode.js'): null
这不是运行时 if-else——Bun bundler 在构建时会根据 feature flag 配置,把关闭的分支整个删掉。这意味着不同的构建产物可以有完全不同的功能集。
但同时还有一套运行期远程配置,通过 GrowthBook/Statsig 下发:
// 运行期检查,走远程配置getFeatureValue_CACHED_MAY_BE_STALE('some_feature')checkStatsigFeatureGate_CACHED_MAY_BE_STALE('tengu_scratch')
这两套系统服务于不同目的:构建期消除维度是”产品形态”(CLI vs KAIROS vs Coordinator),运行期配置维度是”灰度发布”和”A/B 测试”。
2. 懒加载打破循环依赖
项目中大量使用 require() 延迟加载:
// Lazy require to avoid circular dependencyconst getTeammateUtils = () =>require('./utils/teammate.js') as typeof import('./utils/teammate.js')
这是大型 TypeScript 项目的常见痛点解决方案。
3. Memoize 无处不在
import memoize from 'lodash-es/memoize.js'export const getGitStatus = memoize(async () => { ... })
系统上下文、git 状态等昂贵操作都被 memoize,避免同一次对话中重复计算。
4. 内部用户 vs 外部用户
process.env.USER_TYPE === 'ant' // Anthropic 内部员工
内部员工有额外的工具(REPLTool、ConfigTool、TungstenTool、SuggestBackgroundPRTool),外部用户看不到。
5. Forked Agent 模式:Prompt Cache 共享的后台执行
这是整个项目中最被低估的工程模式之一。Auto Memory Extraction、Agent Summary、Magic Docs、Prompt Suggestion、Auto Dream 等后台功能都基于同一个模式:
// runForkedAgent() + CacheSafeParams// 关键:fork 出的 agent 和主对话共享 system prompt 前缀// → 命中 prompt cache → fork 的 API 成本极低
为了保证 cache 命中,fork agent 甚至会特意保持 system prompt 字节完全一致——GrowthBook 冷/热启动时可能返回不同值,所以渲染好的 system prompt 通过 toolUseContext.renderedSystemPrompt 显式传递,而不是让 fork child 自己重新渲染。
6. 状态管理:36 行的极简 Store
全局状态管理的核心是 state/store.ts——只有 36 行代码:
export function createStore<T>(initialState: T, onChange?: OnChange<T>): Store<T> {let state = initialStateconst listeners = new Set<Listener>()return {getState: () => state,setState: (updater) => { /* ... */ },subscribe: (listener) => { listeners.add(listener); return () => listeners.delete(listener) },}}
通过 React 的 useSyncExternalStore 集成到 Ink 组件树。没有 Redux、Zustand 或任何状态管理库。证明了对于这种确定性状态流的应用,最简单的 pub-sub store 就够了。
十四、局限与坑
-
上下文窗口仍然是硬约束:尽管有 auto compact,但压缩必然丢失信息。连续失败 3 次后直接放弃压缩。
-
权限系统复杂度高:多阶段权限检查 + 分类器 + hook 系统 + 并发竞速,维护成本一定很高。
-
循环依赖问题:大量 lazy require 说明模块边界设计还有改进空间。
-
Feature Flag 膨胀:COORDINATOR_MODE、KAIROS、PROACTIVE、AGENT_TRIGGERS、HISTORY_SNIP、REACTIVE_COMPACT、CONTEXT_COLLAPSE、WEB_BROWSER_TOOL… flag 数量已经很多了。
-
Bun 锁定:深度使用 bun:bundle 的 feature(),意味着与 Bun 强绑定。
-
Forked Agent 的隐性 API 成本:Auto Memory、Session Memory、Agent Summary、Prompt Suggestion、Auto Dream、Magic Docs —— 每个后台 forked agent 都是一次 API 调用。虽然有 prompt cache 共享优化,但长对话中后台调用的累计成本是可观的。
-
native-ts 的功能子集:纯 TS 重写的三个 native 模块都是功能子集(yoga 不支持 RTL 和 aspect-ratio,highlight.js 不像 syntect 那样 scope plain identifiers),可能在极端场景下表现不一致。
相关代码:https://github.com/guanshan/ClaudeCode
夜雨聆风