Part 1: Runtime 内核

从CLI到Agent

Claude Code的核心定位:外壳是CLI,但内核是一整套本地的Agent。CLI只是入口,真正干活的是后面这条完整的执行链。

程序入口链图

  1. CLI.tsx 启动
  2. 进入main.tsx
  3. 执行init/up安全初始化
  4. 接入REPL交互环境
  5. 进入任务队列执行

分支逻辑:

  • main.tsx:负责整体主流程调度
  • 安全初始化:搭建基于ln与react的TTY交互界面

main.tsx伪代码的执行流程:

  1. 安全初始化(确认目录可信、加载用户配置)
  2. 根据启动参数分流
    • --print: 走headless后台模式
    • --ratio: 走CI模式
    • --remote: 走远程模式
    • 默认路径:装配工具池(MCP工具、内置skills等所有Agent定义) → 打开REPL

设计原则:所有运行态(REPL/SDK/后台)共用一条queue内核,不存在两套实现

不管是在终端里直接聊天,还是通过SDK嵌入别的程序调用,走的都是同一段执行循环

Queue循环:Agent的本质形态

Agent循环的本质是一个While循环,反复调用模型,模型每轮决定是继续用工具还是停下来

┌──────────────────────────────────────┐
│         Agent执行循环                 │
│                                      │
│   start → API Core → 判断stop reason │
│                      ↓               │
│              是 tool use?            │
│              ↓         ↓             │
│             是       否              │
│              ↓         ↓             │
│         execute tools   end turn     │
│              ↓         ↓             │
│         append result               │
│              ↓                      │
│      回到 API Core                   │
└──────────────────────────────────────┘

Claude Code把每一轮拆成了6个阶段,比简单的While循环更复杂:

  1. preAPIface: 装配attachment、skill、memory
  2. API request phase: 流式调用API并重试
  3. process response: 把模型输出解析成transition状态字
  4. execute tools: 按工具并发性分批执行,加权限,加hooks
  5. run stop hooks: 轮次结束后的钩子
  6. 检查list: 判断要不要收工,否则turn count加一进入下一轮

特性:整条循环底层做成流式生成模型还在生成时,界面就已经流式显示,下游也开始消化和执行工具,不用等完整输出。

Transition状态字

  • continue: 正常进下一轮
  • tool use: 要执行工具
  • turn: 模型主动停止
  • stop sequence: 等等

7级错误恢复机制

Agent跑长任务过程中会有各种失败:网络中断、上下文塞满、模型话说一半被截断、token预算耗尽等

Claude Code把这些失败按严重程度从轻到重分成7级,每一级都有专门的恢复策略:

级别 场景 恢复策略
L1 流式连接中断 自动切成等诊断答完再统一处理的非流式模式,并重试
L2 上下文快塞满(预防) 主动把旧的非重要工具输出折叠成摘要(claude context压缩
L3 上下文真的爆了(抢救) 完整压缩,把历史压成一条摘要加几个关键附件
L4 模型输出配额不够 把输出配额往上再,让它接着说
L5 仍在半句截断 在下一轮明确告诉它"接着说"
L6 stop hook否决退出 拦住不让停,执行用户/插件配置的stop hook检查
L7 整体token预算耗尽 直接抛error,但在都token消耗时写好状态,给一个可以稍后review的接口

设计思想把长任务可能死的所有方式都铺成了梯子,不只是处理一两种异常。这也是Claude Code能跑半小时或1小时以上复杂长任务的前提


Part 2: 状态连续性

Tool协议对象

在很多Agent框架里,tool就是一个函数加一段description,模型说调就调。但Claude Code把tool做成了一个协议对象

interface Tool {
  // 基本属性
  name: string;
  description: string;
  input_schema: any;
  
  // 扩展函数
  execute: Function;
  
  // 关键属性(必须自己声明)
  read_only?: boolean;        // 是否只读
  concurrency_safe?: boolean;  // 是否并发安全
  permission_callback?: Function;  // 权限检查回调
}

tool_build_factory是这套协议的工厂
设计

  • 默认:非并发、非只读、非破坏性,权限默认放行
  • 未声明就视为:非并发、非只读
  • 新工具默认进入**串行执行 + 权限检查**的通道,绕不开治理

理念:安全与并发不是外包给管理层去管,而是写进协议里,变成每一个工具作者都必须面对的设计责任

总结:tool不是函数的映射,而是标准化的协议对象

同一份定义同时服务:模型调用、权限判断、并发调度、UI呈现、结果回流

Tool执行管线与并发策略

模型输出 assistant message

tool orchestration(按并发现分批)

tool execution(逐个执行)

├── sma校验
├── validate input
├── tool hooks
└── permission询问/拒绝

tool call(工具调用)

result回流 → 规范成用户端的tool result message

进入下一轮API

并发策略:模型一次可能输出7个tool use(A、B、C、D、E、F、G)

┌────────────────────────────────────────────────┐
│  A(读) ─┐                                      │
│  B(读) ─┼─→ 并发执行(都是只读,并发安全)       │
│  C(图) ─┘                                      │
│                                                 │
│  D(写) ──→ 独占串行(edit是写操作,并发不安全)  │
│                                                 │
│  E(写) ─┐                                      │
│  F(写) ─┼─→ 并发执行(都是只读,并发安全)       │
│  G(读) ─┘                                      │
└────────────────────────────────────────────────┘

Streaming Tool Exec

4个状态(pendingexecutingcompletedfailed)追踪每个tool,同时接受tool use边接受边启动,不需要等模型完整输出

这就是Claude Code跑起来快的原因——底层是流式调度在帮模型抢时间

System Prompt的架构

Claude Code把给模型的输入分成三类:

类型 说明
System Prompt 主为镜策的长期行为协议(身份、基础规则、工具使用方式、输出风格、记忆等),几乎不变
User/System Context 运行态里附加的上下文(如claudeude.md、当前日期、git状态等),不是用户当前输入,而是runtime自己塞进去的现场信息
Task-specific Prompts 后台任务的专用协议(如compact session memory),每个任务都有自己的工具版名单、轮格式约束、轮次限制

System Prompt组装器优先级链

override(最高优先级) → coordinatoragentcustomdefault

  • override: 外部显式传入,直接强制覆盖
  • coordinator: 多Agent协调模式的专用身份
  • agent: Agent自带的身份定义
  • custom: 用户在配置里自定义的
  • default: Claude Code出厂自带的身份规则

优先级逻辑:有override就用override,没有就看coordinator,意思往下走。

这解释了为什么coordinator模式或者subagent能换一条身份运行——它就是替换了system prompt这个槽,不是模型变了,是模型看到自己是谁变了

上下文压缩策略

长任务跑着跑着token窗口会被工具输出挤爆

Claude Code的答案是5级压缩策略

级别 名称 策略
1 claude dream 把价值低的旧工具输出直接删掉,不做摘要(做摘要本身也要花token)
2 microcomp 主要清理旧的tool result和cached edit,目标减少体积同时保住前缀稳定
3 context collapse 把多轮相似操作折叠成一段结构化摘要(如连续读了十几个文件,折成"读了哪些文件,关键发现是什么")
4 auto compact阈值触发 达到阈值后主循环停一下,用专门的compact prompt单独发一次请求,让模型总结当前会话历史,然后替换
5 session memory compact 复用后台已经抽好的session memory,避免再调一次总结模型

执行时机:每一轮的API request phase开始前都会做一次上下文治理,按梯子顺序依次尝试:claude dream → microcomp → context collapse → auto compact

有session memory就直接复用之后才进API request

设计

  1. 轻量策略优先:前面的策略如果已经把上下文占用降到阈值以下,就不会进入完整的auto compact

  2. 完整compact不是删历史,而是用summary + post compact attachment重建工作台:压缩后要重新选模型的一些信息(工具声明、文件上下文、计划状态),如果只是简单截段,模型就会忘了自己刚干到哪一步

System Prompt的缓存优化

┌────────────────────────────────────────────┐
│           默认的System Prompt              │
├──────────────────────┬─────────────────────┤
│      静态主干        │       动态边界       │
│                      │                     │
│ • 系统规则           │ • actions           │
│ • 任务规则           │ • 环境信息          │
│ • 工具使用方式       │ • 语言偏好          │
│ • 语气等            │ • 输入风格         │
│                      │                     │
│     几乎不变         │      每次会变        │
└──────────────────────┴─────────────────────┘
         ↓                      ↓
    放最前面                  放后面

为什么这么排:服务器端的system prompt cache是按前缀匹配的,前缀越稳定越长,在重复请求时命中率越高,token费用越便宜。稳定的放前面会变的放后面。

Claude Code同时维护本地section cache服务器端system prompt cache

  • 本地这份:避免每次请求都重新组装一遍prompt
  • 服务器那份:省钱降延迟

两层cache一起治理才能把成本压下来

Memory体系

Claude Code把长期状态拆分成了文件索引、作用率和召回机制

┌─────────────────────────────────────────────────────────────────┐
│                        Memory三大分类                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Auto Memory(自动记忆)                                     │
│     ├── 存:用户编号、项目背景、跨会话的长期约束                  │
│     ├── 写入时机:轮次结束后,后台调用extract memories自动抽取    │
│     └── 注入方式:不是全量灌,在queue请求前按相关性召回少量       │
│                                                                 │
│  2. Session Memory(会话记忆)                                   │
│     ├── 存:当前会话的滚动摘要(不是长期偏好)                    │
│     ├── 触发条件:会话达到token或tool call的阈值时               │
│     └── 优先复用,避免再总结一次                                 │
│                                                                 │
│  3. Memory Agent(记忆Agent)                                   │
│     ├── 存:某一类agent的长期经验                               │
│     └── 条件:只有定义里声明了memory字段的agent才有             │
│                                                                 │
│  补充:Team Memory(团队同步层)                                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Memory.md不是正文仓库,而是入口索引

  • 每条记忆只记一个链接加一行描述
  • 整个文件硬限制:200行、25KB以内

设计:不是全量注入,按需召回

请求前扫描每个记忆文件的文件头元数据,生成清单,让一个轻量模型从中选最多5个,把这5个的正文代入本次请求。这个流程在Claude Code里叫**relevant recall**

Transcript与Resume机制

Transcript和resume是支撑长任务能够继续执行、关闭后第二天还能打开从中断位置接着跑的底座。

Transcript概念:不是聊天记录数组,而是一个append-only事件流日志

每一个事件追加到文件,带有唯一ID(UUID)并指向前一个event ID,所有事件靠这两个字段连成一条链

三个特点

  1. 主链有边界:只有四类事件能进transcript(用户输入、模型输出、附加内容、系统消息)。其余如工具执行进度消息等不参与主链

  2. 追加日志:一行一个event,批量flush到磁盘(不是每条消息同步写盘,而是先进内存队列,由后台批量刷到磁盘文件)

  3. 尾部重挂元数据:标题、标签、模式、work tree、agent设置等信息会周期性重挂到transcript文件尾部

反复重挂的原因是REPL列表页用的是轻量读取,只扫文件尾部一小段窗口。会话越变越长,早期写下的元数据会被新内容挤出尾部窗口

Resume四步重建管线

  1. 加载日志:读取整个JSON文件,按事件类型分列到不同数组
  2. 重建主链:把最新消息当做叶子节点,沿着parent ID一路向前回溯,得到当前可继续的对话链
  3. 修复断点:处理链被打断的典型场景
    ├── 早期版本残留的进度状态信息要清理掉
    └── 比如把中间信息删了之后,它的parent UID就指向一个空洞,要往前找还活着的祖先重新挂上
  4. 恢复运行态:plan计划状态、文件读取历史、context状态、agent和当前模式都要全部挂回内存

    控制权交还给REPL

Part 3: 安全与拓展

Sandbox与Permission的关系

每一条bash命令落到宿主机之前要经过几道关卡:能不能执行、怎么执行、执行后怎么清理,串成一条执行链:

Sandbox管线四步

  1. 逐条路由:每条bash命令判断是否进入sandbox,不符合则走普通路径
  2. 配置翻译:整合用户配置,转化为底层隔离环境可执行规则
  3. 隔离运行:底层将命令封装至隔离环境中执行
  4. 收尾清理:清除临时文件与影响宿主机的残留状态

为什么已有sandbox还需要permission

  1. 互补不是替代permission先回答"这条命令能不能执行",sandbox处理"已经允许的命令怎么被进一步限制"。两层各干各的

  2. 配置翻译是核心:用户和项目设置里写的allow/directory读写目录、运名单白名单,被**翻译成隔离环境真实可执行的限制**。这意味着sandbox不是软规则,而是硬边界

  3. 保护控制平面:把各类setting文件(.claude/、.config/目录等)都列入禁止写入范围,防止一个被污染的命令去改agent自己的配置。不让坏命令通过修改自我来扩大影响

补充:sandbox不是唯一防线

真正执行命令的其实是tool,它在进入sandbox之前自己还有一层前置检查:命令语法和危险模式识别、基于规则的permission判断、以及危险命令的识别和分流

MCP生态接入

MCP(Model Context Protocol)是外部工具入职流程。MCP server提供的工具,Claude Code把这些tool标准化成自己内建的tool对象,然后进入工具池再进入执行链。

设计:MCP工具不走旁路。它进来之后继续走schema、permission、做result回流这一整套。不为MCP单独开一条执行通道。

意味着

  • MCP工具受到和内置工具一样的治理
  • 一样要声明并发性
  • 一样要走权限检查
  • 一样要写进transcript

Skills任务协议

Skill不是tool,它是一个可复用的prompt package。本质上就是:markdown + 元数据 + 可选脚本。

Front Matter声明:名称、描述、可用工具、触发路径等

调用方式:把这些拼入任务上下文

按需激活:skill不是全部常驻到prompt里等模型用,而是有3条入口:

入口 说明
用户主动调用 用户明确要求使用某个skill
模型自主选择 模型根据上下文判断需要
条件触发 声明了path字段的skill会在操作匹配的文件时自动注入(如改.py文件时自动激活python相关skill)

两条重要设计

  1. tool search延迟加载:当工具数量太多时(如装了一堆MCP),不会把所有接口的描述一次性塞进prompt,而是先暴露一个精简的发现入口,再按任务需要展开具体工具的边界。这直接降低了prompt体积,提高了cache命中

  2. 统一治理:MCP把外部能力接入tool pool,skills把任务方法直接注入上下文。两者的入口不同,但依然受到同一套权限和上下文预算的约束。真正要执行外部动作时仍然沿用这一条工具执行力。

多Agent分层架构

┌─────────────────────────────────────────────────────────────────┐
│                    多Agent分层架构                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  第三层:Swarm模式                                               │
│  ├── team boxes:靠inbox文件实现teammate之间的通信               │
│  ├── task board:autonomous时teammate自己去扫未认领的任务         │
│  ├── team file:记录团队信息的花名册                            │
│  └── 权限回流:teammate使用敏感工具时权限请求统一回到leader确认    │
│                                                                 │
│  第二层:Coordinator                                             │
│  ├── 不是把sub agent多开几个,而是把主agent的system prompt换掉   │
│  ├── 让主agent自己变成coordinator,负责派工、综合续写多个worker   │
│  ├── 结果以task notification(任务通知)形式回流                  │
│  └── 典型流程:research并行 + implementation分派                 │
│                                                                 │
│  第一层:Subagent(单入测链)                                    │
│  ├── 复用queue kernel,半隔离transcript                         │
│  ├── 子上下文只包含一条task prompt                              │
│  ├── 独立完成工具调用和结果回收                                 │
│  └── 返回的不是自己的上下文,而是一条摘要                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Swarm模式下的Autonomous机制

当teammate空闲时自己找活的流程:

┌─────────────────────────────────────────────────────────────────┐
│                      Autonomous生命周期                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  IDLE状态:timer倒数,没有人叫他                                 │
│      ↓                                                         │
│  超时后自己启动,去taskboard上读任务列表                         │
│      ↓                                                         │
│  把自己的名字写进任务的own字段(避免多个teammate抢同一个任务)     │
│      ↓                                                         │
│  开始处理这个task → 进入WORK状态                                │
│      ↓                                                         │
│  干完后变灰,回到IDLE                                           │
│      ↓                                                         │
│  下一轮timer重新开始                                            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Subagent的context隔离原理:子上下文检测到task后,拿到的不是父的全部历史,而是一份全新的message数组,只包含一条task prompt。它在自己的上下文里调用tool,独立完成工具调用和结果回收。干完后返回的不是自己的上下文,而是一条摘要。然后子上下文被抛弃。父拿到了这一条摘要。

设计价值:用一份干净的子上下文做隔离,防止主链被工具产物申报。

Coordinated Agent(do agent)是多agent的统一入口:本质上就是Claude Code暴露给模型的那个发起agent委派的tool。同一个tool通过参数不同,可以决定走普通的subagent、coordinator派出的worker,或者swarm里的teammate这几种形态。虽然入口分了层,底下真正执行时并没有换一套内核,最后还是回到同一个runtime、同一条queue执行链。


三条设计原则

1. 把模型能力交给runtime承接

模型输出本身只是一段文字,它怎么变成真实的工程行动是runtime层的queue looptool协议permission流式执行共同决定的。模型不是总指挥,runtime才是。

2. 把长任务状态做成可治理的对象

prompt、context、memory、transcript以及resume各自都要有明确的分工:怎么保存、怎么压缩、怎么召回、怎么恢复。不是事后补丁。

3. 把拓展与协作纳入同一治理面

MCP、skills、subagent、coordinator、swarm入口形态各不相同,但进来之后都不能绕过权限、执行与权限边界

Runtime Map

┌─────────────────────────────────────────────────────────────────┐
│                      Claude Code Runtime Map                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                 能力入口(最上层)                        │    │
│  │  ┌──────────┐  ┌──────────┐  ┌───────────────────────┐  │    │
│  │  │   Tool   │  │   MCP    │  │   Skills & Subagent   │  │    │
│  │  └──────────┘  └──────────┘  └───────────────────────┘  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                              ↓                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                  运行治理(中间层)                       │    │
│  │  ┌────────────┐  ┌────────────┐  ┌──────────────────┐   │    │
│  │  │    Queue   │  │  Tool Exec │  │  Prompt/Context  │   │    │
│  │  │   Loop     │  │  Pipeline  │  │    Management    │   │    │
│  │  └────────────┘  └────────────┘  └──────────────────┘   │    │
│  │  ┌────────────┐  ┌────────────┐  ┌──────────────────┐   │    │
│  │  │ Permission │  │  Sandbox   │  │   Transition    │   │    │
│  │  │            │  │            │  │    State        │   │    │
│  │  └────────────┘  └────────────┘  └──────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                              ↓                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                 状态底座(最下层)                       │    │
│  │  ┌────────────┐  ┌────────────┐  ┌──────────────────┐   │    │
│  │  │   Memory   │  │ Transcript │  │     Resume       │   │    │
│  │  │            │  │            │  │                 │   │    │
│  │  └────────────┘  └────────────┘  └──────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

运转逻辑

模型意图 → runtime通过tool协议权限sandbox → 变成真正可执行但不会越界的本地操作

这些操作被状态底座完整记下来

任务即使中断,后面还能接着跑

Harness Engineering

Claude Code这类系统的定位是Harness Engineering,由早期的Prompt Engineering上升到Context Engineering,再过渡到现在的Harness Engineering。

所有设计的核心围绕两件事

  1. 安全可靠:让大语言模型的自主性行为是安全的,不会随便删重要文件、不会搞乱系统

  2. 承认上下文始终有限:在上下文有限的约束下,用最好的机制实现最优的prompt送给模型。所有设计都围绕这个约束进行,而且这个约束不会变——即使上下文变长,计算成本注意力分散问题依然存在。

Subagent的本质:几乎只是为了解决有限上下文的问题。subagent走的还是同一个API,只是prompt被大幅精简了

中间产生的大量上下文累积对主context无影响,所以只需要拿到结果就可以。subagent是为有限上下文的设计服务的

Engineer和Research的融合:Harness Engineering既是一个engineering对象,也可以变成research的对象:

  • Auto Harnessness:如何自动化harness
  • 为harness的agent构建Benchmark
  • 用强模型协助弱模型在harness体系下表现更好
  • 甚至调整小模型权重,让它在harness范式里表现更好

Claude Code、Open AI API的关系:两者都是Harness Engineering,都是包在语言模型API上的一层结构,解决安全可靠、稳定执行、完成任务的问题。Claude Code能接所有AI模型,因为它最后就是把prompt塞给API

  1. Subagent的多Agent关系不同subagent最终掉的还是同一个API,它并不是因为是一个子Agent就会掉别的一片

  2. Claude Code的模型无关性:它不存在只能接特定模型的问题,搞清楚原理就知道逻辑

  3. Engineering和Research应该浑然一体,做有影响力的事情让社区共创

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐