深入 Claude Code 源码

Inside
Claude Code

从记忆体系、交互架构到安全管线,全方位剖析这个 AI 编程智能体的内部运作机制

"一千个读者就有一千个哈姆雷特" — 本文只是提供一些解读 cc 源码、窥探智能体开发的视角

claude-code — src/
01 — 宏观把握

输入到输出的全景

先从两个角度直观简略感受 cc 的工程体系:时序角度与目录架构角度

输入到输出 — 时序角度

main.tsx
init.ts
配置/权限/插件
REPL.tsx
handlePromptSubmit
⌨️

\ 开头的命令

分流到 commands.ts。其中 local 命令直接执行;local-jsx 渲染交互 UI;prompt 型命令(如 /review、/diff)先展开提示词再走 query 逻辑。

commands.ts
🧠

提示词输入 → query.ts

组织上下文、当前消息、工具列表,请求大模型。返回纯文本则直接显示;返回 tool_use 则进入 toolOrchestration 编排执行。

query.ts · claude.ts
Agentic Loop 核心机制:tool_result 不会直接返回 REPL 展示,而是作为 while 循环的下一轮推理入参。只有当大模型不再调用 tool 时,才会 break 出循环展示在 REPL 中。这就是经典的 "推理 → 行动 → 观察 → 再推理" 循环。

目录架构角度 — 六层封装

cc 是一个以 agent 运行内核为中心,外挂了命令、工具、任务、UI、协议等功能的架构体系。从大模型一层层封装、由内而外分析如下:

UI / CLI / Remote 外壳
命令控制面 (commands / skills / plugins)
模型能力面 (tools / tasks / MCP)
上下文与约束层 (memory / compact / policy / state)
模型调用内核 (query / api)

1. 大模型调用层

最内层,驱动请求大模型。文本走 HTTP 流,语音走 WebSocket。

query.ts · service/api

2. 上下文控制层

控制暴露什么信息给大模型:上下文压缩、记忆、策略限制、远程配置、全局状态。

services/compact · memdir/

3. 模型能力层

拓展大模型能力的 Function Calling 层。抽象定义在 Tool.ts/Task.ts,由 toolOrchestration 调度。

tools/ · tasks/

4. 命令控制层

将内建命令、skills、plugins、工作流命令统一装配为用户入口。

commands/

5. 宿主环境层

通过 bridge、server、cli、bootstrap 桥接运行环境,管理会话生命周期,抹平 OS 差异。

bridge/ · cli/

6. 用户交互层

通过 Ink 实现终端实时渲染,包含组件库、屏幕、键绑定和 Vim 模式。

components/ · ink/
02 — 记忆体系

四阶段的时间主线

记忆在智能体开发中最为关键——直接关系到大模型对使用者的了解程度、上下文拼接和注意力机制。cc 的记忆会随着时间在不同阶段以不同姿势渗入上下文。

区别于早期智能体通过文件静态管理记忆的方式,cc 通过作用范围生命周期对记忆进行精细处理,将 durable memory 和 session memory 分家,并用 session memory 为 compact 续航。
1
对话开始前 — 静态装载
入口层:发现 → 加工 & 排序 → 注入,将静态、可预装的内容塞进上下文。
发现getMemoryFiles() 扫描所有可用记忆:Managed(组织级)→ User(全局)→ Project/Local(从根向当前目录递归扫描 CLAUDE.md)→ AutoMem/TeamMem(长期记忆 MEMORY.md)

加工processMemoryFile() 像一条流水线:候选路径 → 去重/排除 → 解析内容 → 提取 @include → DFS 递归展开 include 树(MAX_DEPTH=5,倡导扁平化可预测的记忆体系)→ 输出 MemoryFileInfo[]

排序 — 越通用的越早进,越贴近当前目录的越晚进。后面的上下文对模型输出的控制力更强(Transformer 注意力机制 + 语言模型生成偏置)

注入 — 将「记忆机制说明」和「记忆内容」拆分注入,先告诉模型"什么是记忆"再输入内容
2
对话进行时 — 动态补充
不重建整包 memory,而是以 attachment 方式将"当前最相关的记忆"动态渗进上下文。两条并行链路。
输入相关 — 每轮开始时异步预取,扫描 memory 文件的 frontmatter 和描述,用 Sonnet side query 挑最多 5 个相关 memory 注入

文件触发 — IDE 打开的、@ 提到的文件都会触发 getNestedMemoryAttachmentsForFile(),按文件路径召回相关 rules 和 nested memory
3
每轮对话结束后 — 反向持久化
聚焦于本轮交互是否有值得记忆的内容。本质是一条触发 → 筛选 → 提炼 → 写回的反向持久化链。
触发 — query loop 结束后 handleStopHooks() 启动后台 bookkeeping。subAgent 无法直接影响主记忆,只有回流主线程后才能被沉淀

筛选 — feature gate → auto memory enabled → 避免并发(竞态安全)→ 增量提炼(游标 lastMemoryMessageUuid

提炼 — 扫描已有 memory 做 manifest 防重复,拼 extraction prompt 限定边界,然后 runForkedAgent()后台 agent,复用主会话 cache 但限制权限,不阻塞主任务、防止污染上下文
4
上下文快满时 — Session Memory 续航
不是临时现做摘要,而是优先拿"平时已经维护好的 session memory" 来做 compact。
平时维护sessionMemory.ts 在主线程每轮结束后按阈值后台维护"当前会话笔记文件"。权限极窄,只允许对唯一的 notes 文件做 Edit,保持模板结构(Current State、Files and Functions、Errors & Corrections)

调用trySessionMemoryCompaction 优先使用 session memory,只有在其无法使用或为空时才 fallback 到传统摘要式压缩
设计哲学总结:四阶段时间主线 · 规则与内容分离 · 启动前装配线思维 · 运行中双通道 recall · 回合后反向持久化链 · 预算意识 · 不阻塞主线、失败就降级 · subAgent 隔离
03 — 交互系统

CLI 产品的三层架构

将 cc 视为 CLI 产品,从三层出发:命令与模式分流层、终端渲染层、状态层。

关注如何从外部进入 cc 并被分流到正确执行模式。核心学习点:如何尽可能多实现复用

参数改写 + 暂存态

实现入口收敛复用。在 main() 前期解析特殊输入,把信息存进 _pendingConnect / _pendingSSH / _pendingAssistantChat,必要时改写 process.argv,然后继续走默认主流程。

生命周期钩子

program.hook('preAction', ...) 把所有外层命令共享的前置步骤挂成统一生命周期:init()、logging、migration、remote settings 预热、entrypoint 标记。

统一命令容器 + 委托处理器

整个外层 CLI 只有一个 CommanderCommand 根对象,命令层只做组织和分发,复杂执行交给专门处理器(handler 模块)。Single 单一职责原则。

共享状态载体 + 启动契约

跨阶段共享数据装进统一对象(Pending* / sessionConfig / resumeContext),多模式分支共用同一套启动接口:createRoot → showSetupScreens → launchRepl

从终端进入对话后的渲染管线。cc 的 TUI 走的是:

React
FiberTree / Reconcile
Ink DOM
HostConfig 适配
Yoga Layout
几何树计算
Screen Buffer
内存双缓冲
Terminal
最小 Patch 输出

React → Ink DOM

在 reconcile 阶段 React 用同一套 Fiber 机制计算更新;commit 阶段由 Ink 的 HostConfig 作为适配器,把 mutation 映射为 Ink DOM 操作并同步 Yoga 节点状态。

HostConfig 方法:createInstance / appendChild / commitUpdate / commitTextUpdate → 维护 Ink DOM 树 + Yoga child 列表。commit 收尾触发 onComputeLayout() → onRender()

Yoga → Screen Buffer

通过 dirty 标记 + measure 函数细化重渲染粒度。dirty=false 允许 blit 快路径(块拷贝复用上一帧像素),只有文本节点变动才"打穿"触发昂贵的 measure。

核心优化:children 不参与 attribute 更新避免无意义 dirty;style 做 shallowEqual 比较;只对 ink-text/ink-raw-ansi 叶子触发 Yoga dirty

Screen → Terminal

LogUpdate 计算 prevFrame → frame.screen 的最小 patch 序列。writeDiffToTerminal 按终端能力决定是否包裹 DEC 2026 同步输出(BSU/ESU)避免闪烁/撕裂。

性能哲学

核心思想和虚拟树一样:先在内存中处理避免频繁触碰终端 I/O。diff 副作用压缩成少量 write,支持时用同步输出保证原子性,不支持时跳过避免徒增开销。

cc 将状态按频率和职责拆分为三级,实现高频脏数据与低频共享壳层的区分。

全局 Store

AppState — 会话级共享壳层

承载权限模式、MCP、插件、任务视图、footer、通知等。AppStateProvider 放入 Context 的是稳定的 store 引用而非不断变化的 state,避免整棵树级联重渲。底层基于观察者模式:setState → 通知订阅者 → useSyncExternalStore + selector → 只有切片真正变化的组件才重渲。

REPL 本地

高频强时序状态

messages、streaming text、输入框、overlay、滚动等。通过 useState + useRef 双重维护:messages 给 React 渲染,messagesRef 给"同步立即读取最新值"的逻辑用。确保高频更新时不必等 React batching。

外部 Store

跨 React / 非 React 的流程状态

命令队列、QueryGuard、文件 watcher 等。通过 createSignal() 维护 listener 集合(观察者模式),再包一层自己的状态和 getSnapshot()。React 侧用 useSyncExternalStore 订阅,非 React 代码直接调用同步 API。

signal.ts — 外部 Store 基础设施TypeScript
export function createSignal<Args>() { const listeners = new Set<(...args: Args) => void>() return { subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener) }, emit(...args) { for (const l of listeners) l(...args) }, clear() { listeners.clear() }, } }
04 — 安全体系

真实环境下的精密防线

cc 不是"把模型关进隔离盒子",而是"在真实环境里,让每次动作都经过足够细粒度、可回退、可审计的权限决策"。

cc 在防什么? 不只是 rm -rf / 这种直观危险,更在防"安全策略本身被架空"——命令注入、规则绕过、危险路径写入、组合命令上下文风险、子代理失控、远程审批竞态、headless 场景失控。

DAG 式权限决策管线

不是"六层线性防御",而是一个包含若干可提前返回的 gate 的 DAG 式决策系统。

规则 Gate
带 provenance 的策略系统
工具语义
tool-specific 权限检查
模式转换
PermissionMode 状态机
自动裁决
Classifier / Hook
人工审批
多通道并发竞争

带 Provenance 的规则系统

权限规则不只是布尔表,还记录来源。规则来源不同后续行为也不同:有的可持久化编辑、有的是 policy 下发不能删、有的只在 session 内临时生效。用户可见的 shell 规则形态为 exact / prefix / wildcard 三种。

工具内语义层

外层规则只知道"该不该放行",真正理解工具语义的是工具自己的 checkPermissions()。Bash 是最复杂的——继续分析 subcommand、operator、redirection、path、sandbox、compound command。通用规则层 + 工具内语义层的组合。

safetyCheck — bypass-immune 硬防线

某些风险即使在 bypass 模式下也不能绕过:.git/、.claude/、shell config、危险删除路径。有些风险属于系统级硬边界,模式只能影响大多数流程,不能抹掉全部边界。

Mode 状态机切换

进入 auto mode 时主动剥离会绕过 classifier 的危险 allow 规则(如 Bash(*)、Agent(*)),离开后恢复。系统不仅防模型,也防"过去为了方便留下的过宽策略"

Bash 双轨安全分析

Bash 是整个系统里最接近"把宿主机直接交给模型"的能力,所以用了双轨分析。

AST 路径优先

核心问题不是"解析语法树",而是"能否可信提取 argv[]"。如果能可信提取,做更强的安全分析;如果不能,直接走 too-complex → ask。关键词:allowlist、fail-closed、trustworthy argv extraction。

Legacy Validator 兜底

AST 不可用或 shadow mode 下不生效时,继续使用 bashSecurity.ts 的 regex / shell quote / pattern battery。先让新路径成为主判断,再用 shadow mode 和 fallback 保持迁移安全性。

防的不只是"危险命令",还防"拆分分析带来的认知偏差":
echo hi | xargs printf '%s' >> file — 局部子命令安全 ≠ 整体命令安全(危险在原始命令的 >> file)
cd malicious && git status — 组合命令上下文改变了原本工具语义

审批 ≠ 一个弹窗

审批过程更像一个并发编排系统——本地终端 UI、CCR remote bridge、channel relay、PermissionRequest hooks、异步 Bash classifier。这些通道不是串行排队而是 race:谁先给出有效决策谁就赢。

Headless Agent 安全

没法弹 prompt 时:先跑 PermissionRequest hooks 看有没有自动 allow/deny,如果 hooks 也没结论就直接 fail-closed。不是"尽量让 agent 跑下去",而是"在不越线的前提下让 agent 跑下去"

做源码学习时的方法论:要区分"我看到的实现"与"我推测的产品内部细节"。例如 AI 分类器的具体打分形式、训练集规模、固定阈值表——这些当前源码不能直接证实。
05 — 彩蛋

藏在源码里的惊喜

🐧

宠物系统 /buddy

召唤独属于你的宠物!支持抚摸(飘爱心)、改名、戴帽子(皇冠、巫师帽等8种)、切换物种。输入宠物名字即可对话。

/buddy pet · /buddy hat crown · /buddy rename
🌙

KAIROS 模式

持久化助手模式——长会话中记忆存在按日期的追加式日志中。/dream 会在低活跃期将原始日志蒸馏成结构化主题文件。

😤

用户情绪检测

检测用户负面情绪的方式不是通过 side agent,而是直接进行最原始的正则匹配关键词。简单但有效。

🚀

/ultraplan

深度、长期规划命令。做复杂任务时可以用它生成超详细执行计划,包含子任务分解和依赖分析。

🔥

/torch

分布式任务执行命令——将任务分配到多个智能体并行处理。需要 TORCH feature flag。

🎤

/voice

语音输入模式,支持语音交互。需要 VOICE_MODE feature flag。

未公开 Slash 指令列表

指令功能所需 Flag
/proactive切换主动模式,AI 主动发起对话和建议PROACTIVE / KAIROS
/brief生成项目简报,总结当前会话关键信息KAIROS_BRIEF
/assistantKairos 助理模式,后台持续运行KAIROS
/subscribe-pr订阅 GitHub PR,更新时自动通知KAIROS_GITHUB_WEBHOOKS
/fork分叉子智能体处理独立任务FORK_SUBAGENT
/ultraplan超详细执行计划 + 子任务分解ULTRAPLAN
/torch分布式多智能体并行执行TORCH
/peers查看和管理对等智能体实例UDS_INBOX
/workflows管理和执行自定义工作流脚本WORKFLOW_SCRIPTS
/web远程环境配置,支持 Web 界面访问CCR_REMOTE_SETUP
/buddyAI 伙伴精灵模式BUDDY
/voice语音输入模式VOICE_MODE
/ctx-viz上下文可视化,展示 Token 分布内部调试
/force-snip强制裁剪历史对话HISTORY_SNIP
/force-compact强制压缩上下文REACTIVE_COMPACT
/clear-skill-cache清理技能搜索缓存EXPERIMENTAL_SKILL_SEARCH
06 — 使用技巧

知其然后的实战心法

根据源码得到的特性,启发的日常使用技巧。宗旨:规则写小、任务锚到文件、记忆显式说、长会话尽早主动 compact。

配置层
CLAUDE.md 写到"只保留 Claude 真会做错的事"这个程度。/init 就是按"删掉这一行会不会让 Claude 犯错"来生成的,太大会被标 warning。
团队共享规则和个人偏好分开。团队级放项目 CLAUDE.md / .claude/rules/,个人习惯放 CLAUDE.local.md~/.claude/CLAUDE.md
monorepo 或多模块项目给子目录加自己的 CLAUDE.md,或把 .claude/rules/*.md 做成按 paths 命中的局部规则。
长文档、易变文档、API 参考不要硬塞CLAUDE.md,改成 @path/to/file 按需引入。
提问层
围绕"具体文件/目录/模块"工作。运行中会按文件路径补 nested memory,"改 src/foo.ts 里的 X" 比 "帮我改下这块逻辑" 更容易触发对的局部规则。
真想跨会话记住,直接说"记住这个";想撤销直接说"忘掉这个"。turn-end extraction 对这种显式信号有专门处理。
手工修记忆用 /memory,它会打开 memory 文件让你改,走的是 $EDITOR / $VISUAL
少让它吐大块输出。源码专门把大 bash/read/grep/webfetch 结果视为 context bloat。多说"先定位,再只读相关片段"。
长会话层
保持 autocompact 开启。session memory 的后台维护尊重 auto-compact 开关,关掉后连续性能力会弱很多。
优先用 /compact。不给自定义总结指令时会走 session-memory compaction;写成 /compact 请重点总结... 就会回退到传统 compact。
80% 容量就开始提示你尽快 /compact,长任务最好在掌控上下文时主动压一次。
大量用 subAgent 后让主线程再收口一次。subAgent 无法直接影响主记忆,需要回流主线程后再总结。
交互层
默认交互模式适合日常结对,claude -p 适合脚本和一次性任务。要接别的程序时优先用 --output-format json
会话要像分支一样管理。claude -c 继续最近会话,claude -r 搜索恢复,--fork-session 分叉新思路。
给重要会话起名字。claude -n "支付链路排查" 后面 /resume 时非常省时间。
一次会话只做一个主题。任务彻底切换时宁可 /clear 或新开会话。