# How Claude Code Works: The Fantastic Voyage of a Prompt

> An on-ramp to the agent loop, written for people who are new to computer science.
> This lecture is based on the actual `claude-code` source (a snapshot from around March 2026). Every important claim is cited to a source location so a curious reader can follow the trail.

---

## Contents

1. [Start with the idea: what is an AI agent?](#1-start-with-the-idea-what-is-an-ai-agent)
2. [The map: Claude Code's five layers](#2-the-map-claude-codes-five-layers)
3. [The journey begins: what happens at startup](#3-the-journey-begins-what-happens-at-startup)
4. [The moment you press Enter: how input is routed](#4-the-moment-you-press-enter-how-input-is-routed)
5. [Packing the suitcase: how a request to the model is assembled](#5-packing-the-suitcase-how-a-request-to-the-model-is-assembled)
6. [The heart: the agent loop (the most important chapter)](#6-the-heart-the-agent-loop-the-most-important-chapter)
7. [The model's "hands": the tool system and the permission gate](#7-the-models-hands-the-tool-system-and-the-permission-gate)
8. [Memory and forgetting: context compaction](#8-memory-and-forgetting-context-compaction)
9. [Clones and the emergency brake: sub-agents and interrupt](#9-clones-and-the-emergency-brake-sub-agents-and-interrupt)
10. [The full picture: one diagram of the whole path](#10-the-full-picture-one-diagram-of-the-whole-path)
11. [Appendix: source map](#appendix-source-map)

---

## 1. Start with the idea: what is an AI agent?

### 1.1 The model itself can only "talk"

Start with a common misconception. Many people think ChatGPT, Claude, and similar systems are "smart programs that can do anything." In fact **a large language model (LLM) does exactly one thing: it reads a stretch of text, then continues writing text**.

Picture a super-erudite person locked in a room:

- they have read every book in the world and know a great deal;
- they have **no hands, no eyes, no telephone** — they cannot touch anything;
- the only way you communicate is by sliding a note under the door; they write a note and slide it back.

So a model by itself **cannot "edit a source file" for you** — it can only tell you "you should change line 3 to xxx." The act of changing the file is something it cannot do.

### 1.2 Agent = brain + hands + a loop

An **agent** is the software system that gives that "person in the room" hands and eyes:

| Metaphor | What it is in Claude Code |
|---|---|
| 🧠 Brain | The Claude model (called remotely over an API) |
| ✋ Hands | **Tools**: read/write files, run commands, search the web, … |
| 👀 Eyes | The results those tools return (file contents, command output) |
| 🔁 Nervous system | **The main loop**: feed results back to the brain so it can decide the next step |
| 🛡️ Guard | The permission system: dangerous actions need your consent first |

The core idea of an agent fits in one sentence:

> **The model talks → the software acts for it → the result of that action is told back to the model → the model talks again → until the job is done.**

That "talk → act → feedback" cycle is the **agentic loop**. Claude Code, as a product, is a precision machine built around that loop.

### 1.3 A minimal example

Suppose you say to Claude Code: "*look at the files in `src`, then tell me which file is largest*." Inside the software the conversation looks like this (simplified):

```mermaid
sequenceDiagram
    participant 你
    participant 软件 as Claude Code 软件
    participant 模型 as Claude 大模型

    你->>软件: 看看 src 里有什么文件，哪个最大？
    软件->>模型: 用户问：src 里有什么文件……
    模型-->>软件: 我需要用 Bash 工具执行 `ls src`
    软件->>软件: 真的去执行 ls src（动手！）
    软件->>模型: 工具结果：a.ts b.ts c.ts
    模型-->>软件: 我需要用 Bash 工具执行 `du -h src/*`
    软件->>软件: 执行 du -h src/*
    软件->>模型: 工具结果：a.ts 2K, b.ts 8K, c.ts 1K
    模型-->>软件: 不需要工具了。最终回答：b.ts 最大，8KB
    软件->>你: 📄 b.ts 最大（8KB）
```

Watch: the model speaks three times. The first two times it says "I want a tool." The third time is the actual answer. **The software acted twice in the middle.** That is the whole secret of an agent. Everything else is there to make the loop safer, faster, and cheaper in tokens.

---

## 2. The map: Claude Code's five layers

Before the details, a map. Claude Code, outside in, is five layers:

```mermaid
flowchart TB
    subgraph L1["🖥️ 第 1 层：界面层（终端 UI）"]
        UI["输入框 / 文本渲染 / 权限弹窗<br/><i>src/components · src/screens · src/ink</i>"]
    end
    subgraph L2["🚦 第 2 层：输入分流层"]
        ROUTER["判断输入是命令 / shell / 普通提问<br/><i>src/utils/handlePromptSubmit.ts · processUserInput.ts</i>"]
    end
    subgraph L3["📦 第 3 层：上下文组装层"]
        CTX["系统提示词 + 记忆文件 + git 状态<br/><i>src/constants/prompts.ts · src/context.ts</i>"]
    end
    subgraph L4["🔁 第 4 层：Agent 主循环（心脏）"]
        LOOP["请求模型 → 执行工具 → 追加结果 → 再请求<br/><i>src/query.ts · src/services/tools/</i>"]
    end
    subgraph L5["🌐 第 5 层：API 通信层"]
        API["流式 HTTP 请求 / 重试 / 中断<br/><i>src/services/api/claude.ts</i>"]
    end

    UI --> ROUTER --> CTX --> LOOP --> API
    LOOP -.->|调用| TOOLS["🔧 工具箱：Bash / Read / Write / Edit / Grep ...<br/><i>src/tools/</i>"]
    API -.->|HTTPS| CLAUDE["☁️ Anthropic 服务器上的 Claude 模型"]
```

- **Layer 1 (UI)**: the input box you see in the terminal, the typing effect, the "allow this command?" dialog — all rendered in the terminal with React (a heavily modified Ink library lives in `src/ink/`).
- **Layer 2 (routing)**: after you press Enter, the software first decides whether the text is for the model, or a local command (slash commands like `/help`, or a direct shell like `!ls`).
- **Layer 3 (packing)**: once it is going to the model, pack the system prompt, your memory files (`CLAUDE.md`), git status, and other "background material."
- **Layer 4 (the loop)**: the real heart — repeatedly request the model, run tools, feed results back.
- **Layer 5 (transport)**: talk to Anthropic's servers: streaming, retry on disconnect, interrupt at any time.

Next we follow one prompt from birth to answer, layer by layer.

---

## 3. The journey begins: what happens at startup

Between typing `claude` in the terminal and seeing the input box, the software quietly does a lot:

```mermaid
flowchart LR
    A["敲下 claude 命令"] --> B["分拣员 cli.tsx<br/>特殊参数走快速通道"]
    B --> C["主程序 main.tsx<br/>解析命令行参数"]
    C --> D["全局初始化 init.ts<br/>读配置/预连服务器"]
    D --> E["会话初始化 setup.ts<br/>确定工作目录/加载命令表"]
    E --> F{"目录信任对话框<br/>❓信任此文件夹吗？"}
    F -->|信任| G["渲染交互界面<br/>输入框出现！"]
    F -->|拒绝| H["退出"]
```

(Source path: `src/entrypoints/cli.tsx` → `src/main.tsx` → `src/entrypoints/init.ts` → `src/setup.ts` → `src/replLauncher.tsx`)

A few things worth noticing:

1. **"Sorter" design**: the program entry `cli.tsx` is a parcel sorter. If you type something simple like `claude --version`, it does not load the huge conversation system; it prints a version string and exits — which is why `--version` is instant.
2. **A handshake in advance**: during init the software does a network handshake with Anthropic (`preconnectAnthropicApi()`), like dialing the phone and leaving it off-hook so when you actually speak you do not wait for the ring. That saves 100–200 milliseconds.
3. **The trust dialog is a security boundary**: the first time you run in a folder, it asks "do you trust this directory?" That is deliberate — a project directory can hide other people's config (hooks, `CLAUDE.md`); a malicious project can use those to make you run dangerous commands. Those configs only take effect after you click "trust."
4. **Warm the context**: once trust is granted, the software immediately reads git status and your `CLAUDE.md` memory files **in the background**, rather than waiting until your first question. So the first response is faster.

---

## 4. The moment you press Enter: how input is routed

The input box is up. You type something like `help me see whether this function has a bug`, then press Enter.

### 4.1 From keypress to text

Every key you type takes a journey (source: `src/ink/components/App.tsx` → `src/hooks/useTextInput.ts`):

1. the terminal turns your key into a raw byte stream;
2. the software uses a state machine to parse bytes into "structured key events" (this is a letter, that is an arrow key, that is Enter);
3. each key goes into a "text state machine" that updates the contents of the input box and the cursor;
4. when Enter arrives, it calls `onSubmit(the full text you typed)` — **your text has officially left the input box**.

### 4.2 The sorting desk: who is this text for?

After Enter, the software **does not** immediately send the text to the model. It first decides at a "sorting desk" (source: `onSubmit` in `src/screens/REPL.tsx` → `src/utils/handlePromptSubmit.ts` → `src/utils/processUserInput/processUserInput.ts`):

```mermaid
flowchart TD
    INPUT["你敲下的文本"] --> Q1{"是空白的吗？"}
    Q1 -->|是| DROP1["🗑️ 丢弃"]
    Q1 -->|否| Q2{"是 exit/quit 吗？"}
    Q2 -->|是| EXIT["本地执行退出"]
    Q2 -->|否| Q3{"以 ! 开头？<br/>（如 !ls -la）"}
    Q3 -->|是| BASH["💻 本地直接执行 shell<br/>结果显示给你看<br/><b>不发 AI</b>"]
    Q3 -->|否| Q4{"以 / 开头？<br/>（如 /help、/commit）"}
    Q4 -->|是| CMD{"查命令表，看命令类型"}
    CMD -->|本地型<br/>如 /clear、/config| LOCAL["⚙️ 本地执行<br/><b>不发 AI</b>"]
    CMD -->|提示词型<br/>如 /commit、各种技能| PROMPT["📝 把命令展开成一段提示词<br/><b>发给 AI</b>"]
    CMD -->|查无此命令| UNKNOWN["提示'未知命令'<br/><b>不发 AI</b>"]
    Q4 -->|否| Q5{"AI 正在忙吗？"}
    Q5 -->|在忙| QUEUE["📥 排队，等当前回合结束再处理"]
    Q5 -->|空闲| TEXT["✉️ 包装成'用户消息'<br/><b>发给 AI</b>"]
```

A few points beginners often find confusing, answered together:

- **Why does `/help` not consume model quota?** It is a "local" command. The software already knows how to show help; it never asks the model.
- **What is `!ls` for?** A leading `!` enters bash mode. The command runs on your machine; the output is shown only to you, not sent to the model. Use it when you want to run a command yourself, quickly.
- **"Where did the message I sent while the model was busy go?"** Into a queue. When the current turn ends, queued messages are taken out and run through the same pipeline again.
- **Does history (↑ to recall prior input) get sent to the model?** No. That history lives locally in `~/.claude/history.jsonl` and only serves "up-arrow recall." It is a different object from the conversation history sent to the model.

There is also a user-installable "security checkpoint" before the request actually goes out: the **UserPromptSubmit hook**. If you have a hook script in config, it can intercept your input here, rewrite it, or append extra material.

### 4.3 Attachments: how `@file` is stuffed in

If you wrote `@src/main.ts` in the input, or your IDE has a selection, those are collected at this stage as "attachment messages" and sent after your question (source: `src/utils/attachments.ts`). Pasted images are also compressed here and turned into image blocks the model can read.

### 4.4 Aside: the sorting desk's rules — pure string matching, zero model involvement

Many people assume the sorting desk "intelligently understands" intent. In fact it uses **three hard-coded rules**, all the most naive string comparison:

**Rule 1: look at the first character** (`src/components/PromptInput/inputModes.ts:16`)

```ts
export function getModeFromInput(input: string): HistoryMode {
  if (input.startsWith('!')) {   // 首字符是 ! → bash 模式
    return 'bash'
  }
  return 'prompt'                // 否则 → 候选发给 AI
}
```

Slash commands are the same idea: `inputString.startsWith('/')` at `processUserInput.ts:536`.

**Rule 2: exit words are a hard-coded list** (`src/utils/handlePromptSubmit.ts:196`)

```ts
if (['exit', 'quit', ':q', ':q!', ':wq', ':wq!'].includes(input.trim())) {
  // 改写成 /exit 命令，退出程序
}
```

Note it is `.includes()` — the input must **exactly equal** one of the words. "help me exit this loop" in natural language does not match and is sent to the model as usual. (`:q` and `:wq` are easter eggs for Vim muscle memory.)

**Rule 3: slash commands are a dictionary lookup, also exact match** (`src/commands.ts:688`)

```ts
export function findCommand(commandName: string, commands: Command[]) {
  return commands.find(
    cmd =>
      cmd.name === commandName ||              // 名字精确相等
      getCommandName(cmd) === commandName ||   // 或全名精确相等
      cmd.aliases?.includes(commandName),      // 或命中别名列表
  )
}
```

A miss returns `Unknown command: xxx` honestly. It will never "guess" you meant a different command.

**Why design it this "dumb"?** A deliberate engineering trade-off:

1. **Determinism**: whether your input goes to the model involves privacy and cost, and must be 100% predictable. If you used "smart classification," a mis-fire could upload a private aside;
2. **Speed**: string comparison is nanoseconds, zero latency, zero tokens;
3. **Debuggability**: the rules are in the code; behavior is fully reproducible.

The place that actually "understands" your input is **after** sorting — only text that passes the desk is handed to the model. The sorting desk is a bouncer who follows the book: it does not know people, it only checks IDs.

---

## 5. Packing the suitcase: how a request to the model is assembled

Your question has been confirmed as "send to the model." Next the software packs a huge request. You thought the only thing going to the model was "help me see whether this function has a bug"? Far from it. Every request is a large parcel:

```mermaid
flowchart TB
    subgraph PACKAGE["📦 发往 Anthropic API 的一个完整请求"]
        direction TB
        SYS["1️⃣ 系统提示词（System Prompt）<br/>几千字的'员工手册'：你是谁、怎么做事、<br/>工具使用规范、语气要求、可用技能清单、环境信息"]
        USER_CTX["2️⃣ 用户上下文（伪装成第一条用户消息）<br/>你的 CLAUDE.md 记忆文件内容 + 今天的日期"]
        GIT["3️⃣ 系统上下文（追加在系统提示词末尾）<br/>当前 git 分支、未提交的改动、最近 5 条 commit"]
        HISTORY["4️⃣ 对话历史<br/>你之前说过的每句话 + AI 的每次回复 + 每次工具执行结果"]
        NEW["5️⃣ 你的新消息<br/>'帮我看看这个函数有没有 bug' + 附件"]
        TOOLS["6️⃣ 工具清单<br/>约 40 个工具的说明书：名字、用途、参数格式"]
    end
```

(Source: `getSystemPrompt` in `src/constants/prompts.ts`, `getUserContext`/`getSystemContext` in `src/context.ts`, `prependUserContext`/`appendSystemContext` in `src/utils/api.ts`, request assembly in `src/services/api/claude.ts`)

Piece by piece:

1. **System prompt**: Anthropic's "employee handbook" for the model, attached to every request. It says "you are Claude Code, a programming assistant," "you must read a file before editing it," "keep answers concise," plus dozens of other rules, plus the currently available skill listing and environment facts (OS / shell / model version).
2. **`CLAUDE.md` memory files**: the software walks from your project directory up to the root, collecting every `CLAUDE.md` (and the global `~/.claude/CLAUDE.md`), and concatenates them. The blob is wrapped in a `<system-reminder>` tag and disguised as the earliest "user message" in the conversation — the model can see it; the UI does not show it.
3. **Git status**: current branch, whether there are uncommitted changes, the last 5 commits — so the model knows the state of the repo.
4. **Conversation history**: every prior turn of "what you said + the model's reply + tool results," carried along unmodified. **This is the key to understanding LLMs: the model itself has no memory. "It remembers what you said" is the software resending the entire chat log every turn.**
5. **Your new message**: this turn's actual question, plus `@file` attachments.
6. **Tool list**: one "spec sheet" per tool (name + description + parameter schema). That is how the model knows "Bash exists in the world, and calling it requires a `command` string."

Then the software opens a **streaming** HTTPS request to Anthropic — the server does not wait for the full reply; it sends as it writes, which is why you see the answer "typed" one character at a time (source: `src/services/api/claude.ts`, `messages.create({ stream: true })`).

### 5.1 Depth: the collection rules for `CLAUDE.md` (source-detail edition)

`CLAUDE.md` is not "read one file." It is a collection pipeline with strict priority and order (source: `getMemoryFiles` in `src/utils/claudemd.ts`, from line 790). The software **collects 5 sources in sequence** and concatenates all of them:

```mermaid
flowchart TD
    A["开始收集记忆文件"] --> B["1️⃣ Managed（企业管控）<br/>/etc/claude-code/CLAUDE.md<br/>+ 管控版 rules 目录<br/><i>无条件最先加载，公司IT用来下发强制规范</i>"]
    B --> C["2️⃣ User（用户全局）<br/>~/.claude/CLAUDE.md<br/>+ ~/.claude/rules/*.md<br/><i>你个人对所有项目的偏好</i>"]
    C --> D["3️⃣ Project + Local（项目）<br/>从你的当前目录出发，<b>逐级向上走到根目录</b><br/>每一层都找这 4 个目标："]
    D --> E["&nbsp;&nbsp;&nbsp;· CLAUDE.md（项目规范，会提交进 git）<br/>&nbsp;&nbsp;&nbsp;· .claude/CLAUDE.md（同上，另一种放法）<br/>&nbsp;&nbsp;&nbsp;· .claude/rules/*.md（拆成多条的规则文件）<br/>&nbsp;&nbsp;&nbsp;· CLAUDE.local.md（你的私人项目笔记，不提交 git）"]
    E --> F["4️⃣ --add-dir 额外目录<br/><i>需环境变量开启，默认关闭</i>"]
    F --> G["5️⃣ 自动记忆 / 团队记忆<br/><i>实验特性，需开关</i>"]
    G --> H["全部拼成一大段文字"]
```

A few details worth knowing:

1. **Walking upward means "nearest overlay"**: if you start in `projectA/src/utils`, then the root, projectA, src, and utils each contribute their `CLAUDE.md` — files closer to you are more specific; all of them apply; none overwrite each other (the model decides which clause is more relevant).
2. **Every file gets an "identity tag"** (`getClaudeMds`, line 1153): when concatenated, each file is preceded by its path and a type gloss, e.g. `Contents of /path/CLAUDE.md (project instructions, checked into the codebase):` — so the model knows which clause is a company mandate and which is your private note, and treats them differently.
3. **`@include` syntax**: a `CLAUDE.md` can write `@path/another.md` to include another file (`processMemoryFile`, line 618). The software **recursively expands** includes, with safety limits: at most 5 levels (`MAX_INCLUDE_DEPTH`); only text extensions (so you cannot stuff images or PDFs into memory); project-level files by default cannot include files **outside** the project directory (unless you approved that in the trust dialog) — so a malicious `CLAUDE.md` committed by someone else cannot steal your private files.
4. **Dedup**: every file path goes into a `processedPaths` set; the same file (including via symlink) is loaded once.
5. **Per-file cap of 40000 characters** (`MAX_MEMORY_CHARACTER_COUNT`, line 92); oversize files are marked.
6. **Can be turned off entirely**: env `CLAUDE_CODE_DISABLE_CLAUDE_MDS` or `--bare` mode skips auto-discovery (`src/context.ts:165`).

### 5.2 Depth: where each piece is placed in the request

Once the material is collected, **where** each piece sits in the request is deliberate (source: `src/utils/api.ts`):

| Material | Where it goes | Source | Why there |
|---|---|---|---|
| Employee handbook (system prompt) | the request's `system` field | `getSystemPrompt` | the API's "highest instruction" slot |
| Git status | **appended at the end of the system prompt**, as `gitStatus: <content>` | `appendSystemContext` (`api.ts:437`) | it is "environment state," so it sits with instructions |
| `CLAUDE.md` + today's date | **disguised as the earliest user message**, wrapped in `<system-reminder>` | `prependUserContext` (`api.ts:449`) | see below |

The third row is worth unpacking. `CLAUDE.md` is actually sent in this form:

```
<system-reminder>
As you answer the user's questions, you can use the following context:
# claudeMd
（所有记忆文件的内容）
# currentDate
Today's date is 2026-07-23.

IMPORTANT: this context may or may not be relevant to your tasks.
You should not respond to this context unless it is highly relevant to your task.
</system-reminder>
```

Three design tricks:

1. **`isMeta: true` flag** — the model can see this message, but it **does not render in your terminal**, so the dialog does not grow a mysterious extra paragraph;
2. **`IMPORTANT: ... may or may not be relevant`** — tells the model clearly "this is background, not the user's question," so it does not treat `CLAUDE.md` as something to answer;
3. **Cache-friendly**: git status and `CLAUDE.md` are **read once per session** (`memoize` cache, `src/context.ts:116/155`) and resent identically every turn — which is exactly what hits the API's prompt cache, saving money and time. That is also why we "warm" them: read them in the background at startup (see chapter 3).

**One-sentence summary of the rules**: *managed first, global second, project layered on top; includes may recurse but with depth and permission gates; at assembly time each piece takes its seat — instructions into `system`, status after instructions, memory disguised as the first user message.*

---

## 6. The heart: the agent loop (the most important chapter)

This chapter is the soul of the software. If you understand it, you understand how every AI agent (not just Claude Code) works.

### 6.1 What the loop looks like

The source is `queryLoop` in `src/query.ts`. The core structure is surprisingly simple — a `while (true)` infinite loop:

```mermaid
flowchart TD
    START(["带着打包好的请求进入循环"]) --> COMPACT["① 检查对话历史会不会太长<br/>太长就先压缩（第八章细讲）"]
    COMPACT --> CALL["② 向模型发起流式请求<br/><i>src/services/api/claude.ts</i>"]
    CALL --> COLLECT["③ 边接收边收集：<br/>模型这次回复里有没有'我要用工具'（tool_use）？"]
    COLLECT --> CHECK{"有没有<br/>工具调用？"}
    CHECK -->|"没有（模型直接给了答案）"| DONE["🎉 本回合结束<br/>把答案显示给用户"]
    CHECK -->|"有（比如：我要用 Bash 跑 ls）"| EXEC["④ 执行工具（第七章细讲）<br/>权限检查 → 真的执行 → 拿到结果"]
    EXEC --> APPEND["⑤ 把'AI 的回复 + 工具执行结果'<br/>追加到对话历史末尾"]
    APPEND --> LOOP(["回到 ①，带着更长的历史再请求一次模型"])
    LOOP -.-> COMPACT
```

In one sentence:

> **As long as the model still wants a tool, the loop does not stop. The moment the model stops asking for tools, the answer is out.**

### 6.2 A real loop trace

Back to "which file in `src` is largest." The loop actually ran **3 turns**:

| Turn | History sent to the model | The model's reply | What the software does |
|---|---|---|---|
| Turn 1 | your question | "I want Bash to run `ls src`" | run `ls`, append the output to history |
| Turn 2 | question + ls request + ls result | "I want Bash to run `du -h src/*`" | run `du`, append the output to history |
| Turn 3 | question + two rounds of tool dialogue | "b.ts is largest (8KB)" — no tool call | **loop ends**, display the answer |

Notice history **grows every turn**: turn 3's request contains the entire conversation of the first two turns. That is why long conversations get slower and more expensive, and why we need the "compaction" of a later chapter.

### 6.3 What "the model wants a tool" looks like in the data

The model does not actually pick up a phone. It generates a special structure in the reply stream (a `tool_use` block), roughly:

```json
{ "type": "tool_use", "name": "Bash", "input": { "command": "ls src" } }
```

When the software receives that block, it knows "the model wants to act," and acts for it. The result is wrapped in another special structure (a `tool_result` block) and sent back:

```json
{ "type": "tool_result", "content": "a.ts\nb.ts\nc.ts" }
```

The whole agent loop, said plainly, is the software acting as "translator + runner": **turn the model's "textual intent" into a real operation, then turn the result of that operation back into text.**

### 6.4 A clever optimization: start work while still receiving

The model's reply arrives one character at a time. Claude Code has a smart design (`StreamingToolExecutor`): **the moment a tool call's arguments are complete, start executing the tool without waiting for the rest of the model's words.** For example, the first half of the reply is "I want to run `npm test`"; while the explanatory second half is still in flight, `npm test` is already running on your machine. By the time the model finishes talking, the test result may already be half done.

There is also execution discipline:

- **read-only tools (read file, search) may run in parallel** (up to 10 at once);
- **writes (edit a file, run a command) are strictly queued one at a time**, so they do not step on each other;
- if one Bash command fails, **the other commands in the same batch are cancelled** — because commands often depend on each other (`mkdir` already failed; writing into that directory is pointless).

### 6.5 When does the loop stop?

Besides the normal exit "the model no longer calls tools," the loop has many emergency exits (called `Terminal` states in the source):

| Stop reason | When it happens |
|---|---|
| ✅ `completed` | the model finished normally, no tool call |
| 🛑 user pressed ESC | interrupt at any time (chapter 9) |
| 📏 conversation too long and compaction failed | the context window truly cannot hold it |
| 🔁 hit max turns | the `maxTurns` cap set on an SDK call |
| 💥 API error | network failure etc., retries also failed |
| 🪝 hook blocked it | your configured Stop hook said "stop" |

---

## 7. The model's "hands": the tool system and the permission gate

### 7.1 What is in the toolbox

Every capability the model can use is a "Tool" object (defined in `src/Tool.ts`), about 40 built-in tools (`src/tools/`). The most common:

| Tool | What it does | Danger |
|---|---|---|
| `Read` | read file contents | 🟢 safe |
| `Glob` / `Grep` | search by filename / content | 🟢 safe |
| `Edit` / `Write` | modify / create files | 🟡 will change your files |
| `Bash` | run arbitrary terminal commands | 🔴 in theory can do anything |
| `WebFetch` / `WebSearch` | fetch a page / search the web | 🟢 basically safe |
| `Agent` | dispatch a "clone" sub-agent to work | 🟡 |
| `TodoWrite` | maintain a todo list (the model listing its own plan) | 🟢 |

Each tool is made of several parts (a vivid correspondence):

- **name + spec sheet** (`name`, `prompt`): written for the model, telling it when and how to use the tool;
- **parameter schema** (`inputSchema`, defined with Zod): what arguments a call must supply, e.g. Bash must get a `command` string. Garbage arguments from the model are rejected immediately;
- **body** (`call`): the function that actually does the work;
- **permission check** (`checkPermissions`): the tool itself judges "is this particular operation dangerous";
- **UI render**: how the execution is shown in the terminal (Bash shows `$ command`, Edit shows a code diff).

### 7.2 The full path from "the model wants Bash" to "it actually runs"

This is the most interesting path. Walk it once (source: `runToolUse` → `checkPermissionsAndCallTool` in `src/services/tools/toolExecution.ts`):

```mermaid
flowchart TD
    A["模型说：我要用 Bash 跑<br/>'rm -rf node_modules'"] --> B["1. 按名字找到 Bash 工具<br/><i>找不到？→ 把错误回给模型让它自我纠正</i>"]
    B --> C["2. Zod 校验参数格式<br/><i>command 是字符串吗？</i>"]
    C --> D["3. PreToolUse 钩子<br/><i>用户配置的脚本可拦截/改写命令</i>"]
    D --> E{"4. 权限裁决<br/><i>src/utils/permissions/permissions.ts</i>"}
    E -->|命中'禁止规则'| F["❌ 拒绝<br/>把原因告诉模型"]
    E -->|命中'始终允许'规则<br/>（如你之前点过'始终允许 npm *'）| G["✅ 直接执行"]
    E -->|默认情况| H["❓ 弹窗问你：<br/>允许一次 / 始终允许 / 拒绝"]
    H -->|你选了允许| G
    H -->|你选了拒绝| F
    G --> I["5. 真正执行：<br/>起 shell 进程，收集输出"]
    I --> J["6. PostToolUse 钩子<br/>可对结果追加反馈"]
    J --> K["7. 结果包成 tool_result 消息<br/>追加进对话历史"]
    K --> L["8. 主循环带着新历史<br/>再次请求模型"]
```

Several subtleties worth a beginner's attention:

1. **A model mistake does not crash the system.** If the model invents a tool name or gives wrong arguments, the software does not crash; it feeds "you were wrong, here is why" back as a tool result — the model usually self-corrects and tries again with the right shape.
2. **Permission is a real gate, not a prompt.** The model has no way to bypass the permission system and execute a command directly — it can only "file a request"; approval sits with the software (and you). That is why Claude Code dares to give the model a weapon like Bash.
3. **"Always allow" is remembered.** If you pick "always allow `git *`" in the dialog, that rule is written into config; later commands that start with git stop bothering you.
4. **Even the dialog can be smart.** In some scenes the software lets a small model "pre-score" the command's danger; high-confidence-safe commands are let through, reducing interruption.

### 7.3 Skills and slash commands: teaching the model new tricks

- **Slash commands** (`src/commands/`, about 100): `/commit` (auto-generate a commit message), `/review` (code review), … In essence they are "prefabricated prompt templates" or local functions.
- **Skills** (`src/skills/`): a `SKILL.md` file is a skill; it says "when to use me, how to use me." The software injects only a skill **listing** into the conversation (saves tokens); when the model decides to use a skill, it loads the full spec via the `Skill` tool. That design is called **progressive disclosure** — like a bookshelf that only shows titles, and you pull the book you need.

---

## 8. Memory and forgetting: context compaction

### 8.1 Why compact

As said earlier: **every turn's request carries the entire history.** And the model's "brain capacity" (context window) is finite — say 200,000 tokens (about 150,000 Chinese characters). A large refactor fills the conversation with hundreds of files and endless command output, and you hit the ceiling fast.

Even if you do not hit it, longer history means slower responses, higher cost, and the model is more easily distracted by irrelevant old information.

### 8.2 Layered defenses

At the **start of every loop turn** Claude Code does a "slim-down check" (source: the opening of `src/query.ts` + `src/services/compact/`):

```mermaid
flowchart LR
    A["每圈循环开头"] --> B["① 超大工具结果<br/>落盘存文件，历史里只留'摘要+路径'"]
    B --> C["② 清理陈旧的旧工具结果<br/><i>microcompact</i>"]
    C --> D{"③ token 数接近上限？<br/>（上限 − 13000 的警戒线）"}
    D -->|还没| E["✅ 正常请求模型"]
    D -->|接近| F["④ 自动压缩 autocompact<br/>见下文"]
    F --> E
```

### 8.3 How autocompact works — let the model write its own reading notes

The most interesting part is how compaction is done: **not a crude chop of the first half, but dispatch a "one-shot clone" to read the whole conversation and write a summary** (source: `src/services/compact/compact.ts`; internally it reuses the same query loop, for one turn only).

After compaction the conversation history becomes:

```
[摘要消息：之前对话的要点总结] + [一道"压缩边界"标记] + [边界之后的原始对话]
```

From then on the model only sees content **after** the boundary, plus that summary — like condensing a textbook into one page of notes before an exam, then reading only the notes and the last few chapters.

To keep compaction itself from failing forever (the extreme case), there is a circuit breaker: three consecutive compaction failures and it gives up and errors, rather than burning money retrying forever.

### 8.4 Reactive rescue

If prevention failed and the API actually returned a "content too long" error (413), the software still has **reactive compaction**: quietly hide the error (so you do not see a scary message), compact once in an emergency, and resend. Only when every means has failed does it tell you "the conversation is too long; please `/compact` by hand or start a new conversation."

---

## 9. Clones and the emergency brake: sub-agents and interrupt

### 9.1 Sub-agents: one loop, reused recursively

When a task is large, the model can call the `Agent` tool to dispatch a "clone" onto a subtask (e.g. "go search the whole codebase for every use of this function").

Architecturally there is an elegant point (source: `src/tools/AgentTool/runAgent.ts`): **the sub-agent runs the main loop itself** — the same `query()` function, just with its own system prompt, a shrunk toolbox, and an identity tag. The main agent, the sub-agent, and the clone that writes summaries are all reuse of the same engine.

Sub-agents have two run modes:

```mermaid
flowchart TD
    A["模型调用 Agent 工具"] --> B{"前台还是后台？"}
    B -->|前台（同步）| C["主循环<b>暂停等待</b><br/>子 agent 跑完后，结果作为工具结果回来"]
    B -->|后台（异步）| D["子 agent 独立运行<br/>主循环<b>立刻继续</b>干别的"]
    D --> E["子 agent 干完后<br/>往主循环的消息队列里塞一条'任务完成通知'<br/>主循环下一圈读到"]
```

Why foreground vs background? Imagine asking an assistant to look something up: foreground is you sitting there until they come back; background is you continuing your own work, and they WeChat you when they are done.

### 9.2 ESC: an emergency brake you can pull any time

You can press ESC at any time to interrupt the model. This "emergency brake" is thorough (source: `src/hooks/useCancelRequest.ts`):

1. the interrupt signal travels all the way to the network layer; **the HTTP request is actually cut** — it is not "pretend we did not see the result"; the connection is truly broken, billing stops immediately;
2. the loop has two checkpoints: cut mid-stream receive, or cut mid-tool execution; both wind down cleanly;
3. an easy-to-miss but critical detail: if the model had already issued a tool-call request and then was interrupted, the software **automatically patches in a fake tool result saying "interrupted by the user."** Why must it patch? Because the API requires: every "tool call" must be paired with a "tool result"; a missing pair makes the next request error immediately. Like a letter that must have a reply, even if the reply is "the other party does not want to talk."

There is also a layering: a foreground sub-agent and the main agent share the same brake (ESC stops them together); **a background sub-agent has its own independent brake** — you press ESC to stop the current work, the background task stays alive (anything else would be too counter-intuitive); to kill it you use a dedicated shortcut (Ctrl+X Ctrl+K).

---

## 10. The full picture: one diagram of the whole path

String every previous chapter together and this is the complete journey after you send a prompt:

```mermaid
flowchart TD
    subgraph 你的电脑["💻 你的电脑（Claude Code 软件）"]
        A["⌨️ 你敲字，按下回车"] --> B["分流判断：<br/>/命令？!shell？普通提问？"]
        B -->|本地命令| C["⚙️ 本地执行，直接出结果"]
        B -->|普通提问| D["📦 打包请求：<br/>系统提示词 + CLAUDE.md + git状态<br/>+ 全部对话历史 + 工具清单"]
        D --> E{"🔁 主循环 while(true)"}
        E --> F["检查历史长度，必要时压缩"]
        F --> G["流式请求模型"]
        G --> H{"模型回复里有<br/>工具调用吗？"}
        H -->|有| I["权限检查 ❓<br/>（可能弹窗问你）"]
        I -->|被拒| J["把'用户拒绝了'<br/>回给模型"]
        I -->|放行| K["🔧 真的执行工具"]
        K --> L["结果追加进历史"]
        J --> L
        L --> E
        H -->|没有| M["🎉 显示最终答案"]
        N["ESC 急刹车"] -.随时.-> G
        N -.随时.-> K
    end
    G -.->|HTTPS 流式| O["☁️ Anthropic 服务器<br/>Claude 大模型"]
    O -.->|逐字返回| G
    P["🤖 子 agent<br/>（复用同一个循环）"] -.被 Agent 工具唤起.-> E
```

---

## Appendix: source map

If you want to read the source yourself, here is a map of the files this lecture cites:

| Topic | File | What to look at |
|---|---|---|
| Program entry | `src/entrypoints/cli.tsx` | the "sorter" `main()` |
| Main program | `src/main.tsx` | CLI parse, startup flow |
| Global init | `src/entrypoints/init.ts` | config, preconnect API |
| UI render | `src/ink/`, `src/components/`, `src/screens/REPL.tsx` | terminal UI, input box |
| Input routing | `src/utils/handlePromptSubmit.ts`, `src/utils/processUserInput/` | dispatch of command / shell / question |
| Context assembly | `src/constants/prompts.ts`, `src/context.ts` | system prompt, `CLAUDE.md`, git status |
| **Main loop (the heart)** | **`queryLoop` in `src/query.ts`** | the `while (true)` agentic loop |
| API transport | `src/services/api/claude.ts` | streaming request, SSE parse |
| Tool definitions | `src/Tool.ts`, `src/tools/` | about 40 tools |
| Tool execution | `src/services/tools/toolExecution.ts`, `toolOrchestration.ts` | dispatch, serial/parallel scheduling |
| Permission system | `src/utils/permissions/permissions.ts` | deny/allow/ask adjudication |
| Hook system | `src/utils/hooks.ts`, `src/services/tools/toolHooks.ts` | user-script intervention points |
| Context compaction | `src/services/compact/` | auto / reactive compaction |
| Sub-agents | `src/tools/AgentTool/`, `src/Task.ts`, `src/tasks/` | clones and background tasks |
| Interrupt | `src/hooks/useCancelRequest.ts`, `src/utils/abortController.ts` | ESC emergency brake |

> **A note about this source tree**: this is a TypeScript snapshot of an internal build (restored via sourcemap). Function names are mostly preserved; comments are thorough. You may see compile-time switches like `feature('XXX')` and a few missing files; those are normal traces of the build system and do not block understanding the main flow.

---

## Takeaway: three sentences to leave with

1. **The model can only talk; the agent software is its hands and eyes** — Claude Code's entire job is "translate the model's intent into operations, translate the results of those operations back into text."
2. **The heart is a `while` loop**: request the model → if there is a tool call, execute it and append the result → request again → until the model no longer wants a tool, and the answer is out.
3. **Safety is architecture, not virtue**: the permission gate, argument validation, and an interrupt you can pull at any time are hard constraints at the software layer. Whatever the model wants to do, it has to "apply for approval."

Once you have those three, look at any AI agent product on the market (Cursor, Devin, various automation assistants) and you will find they do not escape this basic frame — the differences are how rich the toolbox is, how fine-grained the loop is, and how strict the safety mechanisms are.
