Merge d62995e2ff into 4b9d30f795
This commit is contained in:
commit
a07dd5e8c5
268
learn/01-system-overview.md
Normal file
268
learn/01-system-overview.md
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
# 1. System Overview
|
||||||
|
|
||||||
|
> The entire Claude Code architecture in one diagram, then unpacked layer by layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Big Picture
|
||||||
|
|
||||||
|
Claude Code is structured in **8 distinct layers**, each with a clear responsibility. Understanding these layers is the key to navigating the 512K-line codebase.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'primaryBorderColor': '#4a9eff', 'lineColor': '#4a9eff', 'secondaryColor': '#16213e', 'tertiaryColor': '#0f3460', 'edgeLabelBackground': '#1a1a2e'}}}%%
|
||||||
|
graph TB
|
||||||
|
CLI["CLI Entry<br/>main.tsx — 804KB"]:::entry
|
||||||
|
SDK["SDK Entry<br/>Programmatic API"]:::entry
|
||||||
|
MCP_S["MCP Server<br/>Expose as MCP"]:::entry
|
||||||
|
|
||||||
|
REPL["REPL.tsx — 896KB<br/>Interactive Terminal Shell"]:::ui
|
||||||
|
Comps["113 Components<br/>Messages, Diffs, Dialogs"]:::ui
|
||||||
|
Hooks["83 React Hooks<br/>Permissions, Input, IDE"]:::ui
|
||||||
|
|
||||||
|
QE["QueryEngine.ts<br/>Session Lifecycle Owner"]:::core
|
||||||
|
QL["query.ts — 1730 lines<br/>Agentic Loop"]:::core
|
||||||
|
CL["claude.ts — 3420 lines<br/>Anthropic API Client"]:::core
|
||||||
|
|
||||||
|
TD["Tool Interface<br/>Tool.ts"]:::tool
|
||||||
|
BT["42 Built-in Tools"]:::tool
|
||||||
|
MT["MCP Tools — dynamic"]:::tool
|
||||||
|
TO["Tool Orchestration<br/>Parallel Execution"]:::tool
|
||||||
|
|
||||||
|
Compact["Compaction Pipeline<br/>snip / micro / auto /<br/>reactive / collapse"]:::ctx
|
||||||
|
|
||||||
|
Rules["Allow + Deny Rules"]:::perm
|
||||||
|
HK["PreToolUse Hooks"]:::perm
|
||||||
|
Classifier["Auto-mode Classifier"]:::perm
|
||||||
|
|
||||||
|
AS["AppState Store<br/>Immutable — 50+ fields"]:::state
|
||||||
|
SS["Session Storage<br/>Transcripts + Resume"]:::state
|
||||||
|
Cfg["Config Layer<br/>Global / Project / CLAUDE.md"]:::state
|
||||||
|
|
||||||
|
Skills["Skills"]:::ext
|
||||||
|
Plugins["Plugins"]:::ext
|
||||||
|
Agents["Sub-agents + Swarms"]:::ext
|
||||||
|
|
||||||
|
API["Anthropic Messages API"]:::external
|
||||||
|
MCP_Ext["External MCP Servers"]:::external
|
||||||
|
GrowthBook["GrowthBook + Statsig"]:::external
|
||||||
|
|
||||||
|
CLI --> REPL
|
||||||
|
SDK --> QE
|
||||||
|
MCP_S --> QE
|
||||||
|
|
||||||
|
REPL --> Comps
|
||||||
|
REPL --> Hooks
|
||||||
|
REPL --> QE
|
||||||
|
REPL --> AS
|
||||||
|
|
||||||
|
QE --> QL
|
||||||
|
QL --> CL
|
||||||
|
QL --> Compact
|
||||||
|
QL --> TO
|
||||||
|
|
||||||
|
CL --> API
|
||||||
|
CL --> GrowthBook
|
||||||
|
|
||||||
|
TO --> TD
|
||||||
|
TD --> BT
|
||||||
|
TD --> MT
|
||||||
|
BT --> Rules
|
||||||
|
BT --> HK
|
||||||
|
MT --> Rules
|
||||||
|
|
||||||
|
Rules --> Classifier
|
||||||
|
|
||||||
|
QE --> SS
|
||||||
|
REPL --> Cfg
|
||||||
|
|
||||||
|
Skills --> TD
|
||||||
|
Plugins --> TD
|
||||||
|
Plugins --> MCP_Ext
|
||||||
|
Agents --> QL
|
||||||
|
MCP_Ext --> MT
|
||||||
|
|
||||||
|
classDef entry fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ui fill:#1a1a4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef core fill:#2d1b4e,stroke:#e83e8c,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef tool fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ctx fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef perm fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef state fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ext fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef external fill:#333,stroke:#888,color:#aaa,stroke-width:1px,stroke-dasharray: 5 5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 1: Entry Points
|
||||||
|
|
||||||
|
There are **three ways** into Claude Code:
|
||||||
|
|
||||||
|
| Entry | File | How It Works |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| **CLI** | `src/main.tsx` (804KB) | Commander.js parses args → boots React/Ink → renders `REPL.tsx` |
|
||||||
|
| **SDK** | `src/entrypoints/sdk/` | Programmatic API → creates `QueryEngine` directly |
|
||||||
|
| **MCP Server** | `src/entrypoints/mcp.ts` | Exposes Claude Code itself as an MCP server |
|
||||||
|
|
||||||
|
### Key Insight: Parallel Prefetch
|
||||||
|
|
||||||
|
Startup time is critical for a CLI tool. Claude Code parallelizes heavy work *before* any module evaluation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// main.tsx — fired as side-effects before other imports
|
||||||
|
startMdmRawRead() // MDM settings (enterprise)
|
||||||
|
startKeychainPrefetch() // API key from macOS Keychain
|
||||||
|
```
|
||||||
|
|
||||||
|
Heavy modules like OpenTelemetry (~400KB) and gRPC (~700KB) are loaded lazily via dynamic `import()` only when needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 2: UI — React in a Terminal
|
||||||
|
|
||||||
|
This is where it gets wild. Claude Code uses **React** (yes, the web framework) to render a terminal UI via [Ink](https://github.com/vadimdemedes/ink).
|
||||||
|
|
||||||
|
| What | Count | Examples |
|
||||||
|
|------|-------|---------|
|
||||||
|
| Components | 113 files | Messages, Diffs, Dialogs, Settings, Spinners |
|
||||||
|
| React Hooks | 83 files | `useCanUseTool`, `useVoice`, `useReplBridge`, `useTypeahead` |
|
||||||
|
|
||||||
|
The crown jewel is `REPL.tsx` at **896KB** — a single React component that is the entire interactive terminal experience. It handles:
|
||||||
|
|
||||||
|
- Message rendering and virtual scrolling
|
||||||
|
- Permission dialogs
|
||||||
|
- Tool progress indicators
|
||||||
|
- Keyboard shortcuts and vim mode
|
||||||
|
- Voice input
|
||||||
|
- IDE bridge integration
|
||||||
|
- Background task management
|
||||||
|
|
||||||
|
### Why React for a CLI?
|
||||||
|
|
||||||
|
React's component model gives you:
|
||||||
|
- **Declarative UI** — Describe what to render, not how
|
||||||
|
- **Hooks** — Share stateful logic across 83 hooks
|
||||||
|
- **State management** — AppState drives re-renders
|
||||||
|
- **Composability** — 113 components snap together
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 3: Core Engine
|
||||||
|
|
||||||
|
The engine has three key files forming a pipeline:
|
||||||
|
|
||||||
|
```
|
||||||
|
QueryEngine.ts → query.ts → claude.ts
|
||||||
|
(session owner) (loop) (API client)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **`QueryEngine.ts`** — Owns the session lifecycle. Creates a conversation, manages transcripts, tracks usage, and handles resume.
|
||||||
|
|
||||||
|
2. **`query.ts`** — The **agentic loop**. This is the beating heart. It cycles between calling the model and executing tools until the model says `end_turn`. (See [Guide 2: The Agentic Loop](./02-agentic-loop.md))
|
||||||
|
|
||||||
|
3. **`claude.ts`** — The Anthropic API client. Handles streaming SSE responses, retry logic (429/529), prompt caching, and model fallback. (See [Guide 8: API Client](./08-api-client.md))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 4: Tool System
|
||||||
|
|
||||||
|
Every capability Claude Code has — reading files, running bash, searching the web — is implemented as a **Tool**. There are 42 built-in tools, plus dynamic MCP tools loaded at runtime.
|
||||||
|
|
||||||
|
Tools are self-contained modules with a standard interface defined in `Tool.ts` (793 lines):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type Tool = {
|
||||||
|
name: string
|
||||||
|
inputSchema: ZodSchema // Validate inputs
|
||||||
|
checkPermissions(input, ctx) // Permission check
|
||||||
|
call(input, ctx) // Execute
|
||||||
|
prompt(options) // Describe to model
|
||||||
|
renderToolUseMessage(input) // Terminal rendering
|
||||||
|
// ... 30+ more methods
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Full breakdown in [Guide 3: Tool System](./03-tool-system.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 5: Context Management
|
||||||
|
|
||||||
|
LLMs have finite context windows. Claude Code has a sophisticated **5-stage compaction pipeline** to keep conversations within limits:
|
||||||
|
|
||||||
|
1. **Snip Compact** — Sliding window, drop oldest turns
|
||||||
|
2. **Micro Compact** — Truncate oversized individual tool results
|
||||||
|
3. **Auto Compact** — Summarize via a separate API call
|
||||||
|
4. **Context Collapse** — Read-time projection with archived views
|
||||||
|
5. **Reactive Compact** — Emergency trigger on API 413 errors
|
||||||
|
|
||||||
|
Full breakdown in [Guide 5: Context Management](./05-context-management.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 6: Permission System
|
||||||
|
|
||||||
|
Claude Code can run arbitrary bash commands and write to any file. The permission system is a multi-layered defense:
|
||||||
|
|
||||||
|
```
|
||||||
|
Deny Rules → Allow Rules → Tool Check → Hooks → Classifier → User Dialog
|
||||||
|
```
|
||||||
|
|
||||||
|
Four distinct permission modes: Default, Plan, Auto, and Bypass.
|
||||||
|
|
||||||
|
Full breakdown in [Guide 4: Permission System](./04-permission-system.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 7: State Management
|
||||||
|
|
||||||
|
All application state lives in a **single immutable store** (`AppState`) with 50+ fields, following a simplified Redux pattern:
|
||||||
|
|
||||||
|
- **Consumers** read state via `getAppState()` or React's `useAppState(selector)`
|
||||||
|
- **Mutators** update via `setAppState(prev => newState)` (functional updates)
|
||||||
|
- **Side effects** fire via `onChangeAppState` listeners
|
||||||
|
- The `DeepImmutable<T>` type wrapper enforces immutability at the type level
|
||||||
|
|
||||||
|
Full breakdown in [Guide 6: State Management](./06-state-management.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 8: Extensions
|
||||||
|
|
||||||
|
Claude Code is extensible through four mechanisms:
|
||||||
|
|
||||||
|
| Mechanism | What | Where |
|
||||||
|
|-----------|------|-------|
|
||||||
|
| **Skills** | Markdown instruction files | `.claude/skills/*.md` |
|
||||||
|
| **Plugins** | Bundles of tools + MCP servers | Managed or user-installed |
|
||||||
|
| **Hooks** | Pre/PostToolUse scripts | `settings.json` or CLAUDE.md |
|
||||||
|
| **Agents** | Sub-agents, coordinators, swarms | AgentTool, tmux-based swarms |
|
||||||
|
|
||||||
|
Full breakdown in [Guide 7: Extension Model](./07-extension-model.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## External Dependencies
|
||||||
|
|
||||||
|
| Dependency | Role |
|
||||||
|
|-----------|------|
|
||||||
|
| **Anthropic Messages API** | The LLM backend — all model calls go here |
|
||||||
|
| **External MCP Servers** | Third-party tools exposed via Model Context Protocol |
|
||||||
|
| **GrowthBook** | Feature flags — gates like `VOICE_MODE`, `PROACTIVE`, `CONTEXT_COLLAPSE` |
|
||||||
|
| **Statsig** | Additional analytics and experimentation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
Looking at the codebase, several design principles emerge:
|
||||||
|
|
||||||
|
1. **Feature flags for dead code elimination** — `feature('X')` from `bun:bundle` strips unused code at build time
|
||||||
|
2. **Lazy loading** — Heavy modules are `import()`ed only when needed
|
||||||
|
3. **Immutable state** — `DeepImmutable<T>` enforces no mutation
|
||||||
|
4. **Generator-based streaming** — `async function*` throughout for backpressure-aware streaming
|
||||||
|
5. **Self-contained tools** — Each tool is a module with schema, permissions, execution, and rendering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next:** [The Agentic Loop →](./02-agentic-loop.md)
|
||||||
413
learn/02-agentic-loop.md
Normal file
413
learn/02-agentic-loop.md
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
# 2. The Agentic Loop
|
||||||
|
|
||||||
|
> How Claude Code cycles between the model and tools — the core of the entire system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Is the "Agentic Loop"?
|
||||||
|
|
||||||
|
The agentic loop is the mechanism that makes Claude Code more than a chatbot. Instead of:
|
||||||
|
|
||||||
|
```
|
||||||
|
User → Model → Response (done)
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude Code does:
|
||||||
|
|
||||||
|
```
|
||||||
|
User → Model → Tool Call → Model → Tool Call → ... → Response (done)
|
||||||
|
```
|
||||||
|
|
||||||
|
The model can call tools, see results, and decide what to do next — in a **loop** — until it decides it's finished. This loop lives in **`query.ts`** (1,730 lines), and it's the most important file in the entire codebase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Full Sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'actorTextColor': '#e0e0e0', 'actorBorder': '#4a9eff', 'signalColor': '#4a9eff', 'noteBkgColor': '#16213e', 'noteTextColor': '#e0e0e0', 'activationBkgColor': '#2d1b4e', 'activationBorderColor': '#e83e8c'}}}%%
|
||||||
|
sequenceDiagram
|
||||||
|
participant U as User
|
||||||
|
participant QE as QueryEngine
|
||||||
|
participant PI as processUserInput
|
||||||
|
participant Q as query.ts
|
||||||
|
participant CP as Compaction
|
||||||
|
participant C as claude.ts
|
||||||
|
participant API as Anthropic API
|
||||||
|
participant T as Tool Executor
|
||||||
|
participant H as Hooks
|
||||||
|
|
||||||
|
U->>QE: submitMessage(prompt)
|
||||||
|
activate QE
|
||||||
|
QE->>PI: parse slash commands, @mentions, attachments
|
||||||
|
PI-->>QE: messages[], shouldQuery
|
||||||
|
|
||||||
|
alt Slash command — no API call needed
|
||||||
|
QE-->>U: return local result
|
||||||
|
else Model query required
|
||||||
|
QE->>QE: persist transcript to disk
|
||||||
|
QE->>Q: query(messages, systemPrompt, tools)
|
||||||
|
activate Q
|
||||||
|
|
||||||
|
loop Agentic Loop — until end_turn or max_turns
|
||||||
|
Q->>CP: snip compact
|
||||||
|
CP->>CP: micro compact
|
||||||
|
CP->>CP: auto compact
|
||||||
|
CP->>CP: context collapse
|
||||||
|
CP-->>Q: compacted messages
|
||||||
|
|
||||||
|
Q->>C: queryModel(messages, tools, thinking)
|
||||||
|
activate C
|
||||||
|
C->>C: build request: betas, cache, effort, budget
|
||||||
|
C->>API: POST /v1/messages — SSE stream
|
||||||
|
activate API
|
||||||
|
|
||||||
|
API-->>C: message_start — usage
|
||||||
|
API-->>C: content_block — thinking / text / tool_use
|
||||||
|
API-->>C: message_delta — stop_reason, final usage
|
||||||
|
deactivate API
|
||||||
|
|
||||||
|
C-->>Q: yield AssistantMessage + StreamEvents
|
||||||
|
deactivate C
|
||||||
|
|
||||||
|
alt stop_reason = end_turn
|
||||||
|
Q->>H: postSamplingHooks
|
||||||
|
Q->>Q: handleStopHooks
|
||||||
|
Q-->>QE: return Terminal result
|
||||||
|
else stop_reason = tool_use
|
||||||
|
Q->>T: runTools(toolUseBlocks)
|
||||||
|
activate T
|
||||||
|
|
||||||
|
T->>T: validate input against schema
|
||||||
|
T->>H: PreToolUse hooks
|
||||||
|
T->>T: check permissions
|
||||||
|
T->>T: call(input, context)
|
||||||
|
T->>H: PostToolUse hooks
|
||||||
|
T-->>Q: yield tool_result messages
|
||||||
|
deactivate T
|
||||||
|
|
||||||
|
Q->>Q: inject CLAUDE.md attachments
|
||||||
|
Note over Q: push tool_results, continue loop
|
||||||
|
else stop_reason = max_tokens
|
||||||
|
Q->>Q: truncation retry — up to 3x
|
||||||
|
Q->>CP: reactive compact — emergency
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
deactivate Q
|
||||||
|
QE->>QE: accumulate usage, persist transcript
|
||||||
|
QE-->>U: yield SDKMessage stream
|
||||||
|
end
|
||||||
|
deactivate QE
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anatomy of `query.ts`
|
||||||
|
|
||||||
|
The file exports a single async generator function:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export async function* query(params: QueryParams):
|
||||||
|
AsyncGenerator<StreamEvent | Message | ToolUseSummaryMessage, Terminal> {
|
||||||
|
// ... 1,730 lines of agentic loop
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why an Async Generator?
|
||||||
|
|
||||||
|
This is a crucial design decision. An async generator (`async function*`) lets query.ts:
|
||||||
|
|
||||||
|
1. **Yield messages as they arrive** — The consumer (REPL or SDK) sees each message in real-time
|
||||||
|
2. **Maintain backpressure** — The loop pauses if the consumer isn't ready
|
||||||
|
3. **Support cancellation** — `.return()` on the generator cleanly tears down the loop
|
||||||
|
4. **Compose generators** — `yield*` delegates to sub-generators seamlessly
|
||||||
|
|
||||||
|
### The Loop State
|
||||||
|
|
||||||
|
Each loop iteration carries mutable state:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type State = {
|
||||||
|
messages: Message[] // Conversation history
|
||||||
|
toolUseContext: ToolUseContext // Tool execution context
|
||||||
|
autoCompactTracking: AutoCompactTrackingState // Compact progress
|
||||||
|
maxOutputTokensRecoveryCount: number // Truncation retry counter
|
||||||
|
hasAttemptedReactiveCompact: boolean // Emergency compact flag
|
||||||
|
turnCount: number // Loop iteration counter
|
||||||
|
pendingToolUseSummary: Promise<...> // Async summary generation
|
||||||
|
transition: Continue | undefined // Why we're in this iteration
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Pre-Processing — Before the API Call
|
||||||
|
|
||||||
|
Before every API call, query.ts runs a **compaction pipeline** on the message history:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 1. Apply per-message tool result budgets
|
||||||
|
messagesForQuery = await applyToolResultBudget(messagesForQuery, ...)
|
||||||
|
|
||||||
|
// 2. Snip compact — sliding window over old turns
|
||||||
|
if (feature('HISTORY_SNIP')) {
|
||||||
|
const snipResult = snipModule.snipCompactIfNeeded(messagesForQuery)
|
||||||
|
messagesForQuery = snipResult.messages
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Micro compact — truncate oversized tool results
|
||||||
|
const microcompactResult = await deps.microcompact(messagesForQuery, ...)
|
||||||
|
messagesForQuery = microcompactResult.messages
|
||||||
|
|
||||||
|
// 4. Context collapse — read-time projection
|
||||||
|
if (feature('CONTEXT_COLLAPSE') && contextCollapse) {
|
||||||
|
const collapseResult = await contextCollapse.applyCollapsesIfNeeded(...)
|
||||||
|
messagesForQuery = collapseResult.messages
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Auto compact — full summarization via API
|
||||||
|
const { compactionResult } = await deps.autocompact(messagesForQuery, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Each stage is feature-gated and runs **independently**. They compose — snip reduces history, micro truncates individual results, auto summarizes the whole thing, collapse archives old views.
|
||||||
|
|
||||||
|
### Blocking Limit Check
|
||||||
|
|
||||||
|
After compaction, the loop checks if we're at the **blocking limit** (>98% context used):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const { isAtBlockingLimit } = calculateTokenWarningState(
|
||||||
|
tokenCountWithEstimation(messagesForQuery),
|
||||||
|
toolUseContext.options.mainLoopModel,
|
||||||
|
)
|
||||||
|
if (isAtBlockingLimit) {
|
||||||
|
yield createAssistantAPIErrorMessage({ content: PROMPT_TOO_LONG_ERROR_MESSAGE })
|
||||||
|
return { reason: 'blocking_limit' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents the API call entirely if we know it'll fail.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: The API Call
|
||||||
|
|
||||||
|
The actual model call uses `claude.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
for await (const message of deps.callModel({
|
||||||
|
messages: prependUserContext(messagesForQuery, userContext),
|
||||||
|
systemPrompt: fullSystemPrompt,
|
||||||
|
thinkingConfig: toolUseContext.options.thinkingConfig,
|
||||||
|
tools: toolUseContext.options.tools,
|
||||||
|
signal: toolUseContext.abortController.signal,
|
||||||
|
options: {
|
||||||
|
model: currentModel,
|
||||||
|
fallbackModel,
|
||||||
|
effortValue: appState.effortValue,
|
||||||
|
taskBudget: params.taskBudget,
|
||||||
|
// ... 20+ more options
|
||||||
|
},
|
||||||
|
})) {
|
||||||
|
// Process each streamed message
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Responses stream in as SSE events. The loop processes three content block types:
|
||||||
|
|
||||||
|
| Block Type | What Happens |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `thinking` | Rendered in UI, not sent back to model |
|
||||||
|
| `text` | Rendered as markdown in terminal |
|
||||||
|
| `tool_use` | Triggers tool execution (next phase) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Tool Execution
|
||||||
|
|
||||||
|
When the model responds with `tool_use` blocks, the loop executes them:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Parallel tool execution
|
||||||
|
const toolResults = yield* runTools(toolUseBlocks, canUseTool, toolUseContext)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Streaming Tool Execution
|
||||||
|
|
||||||
|
A performance optimization: tools can begin executing **while the model is still streaming**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const useStreamingToolExecution = config.gates.streamingToolExecution
|
||||||
|
let streamingToolExecutor = useStreamingToolExecution
|
||||||
|
? new StreamingToolExecutor(tools, canUseTool, toolUseContext)
|
||||||
|
: null
|
||||||
|
```
|
||||||
|
|
||||||
|
The `StreamingToolExecutor` starts validating and permission-checking tool calls as their blocks arrive, before the full response is complete.
|
||||||
|
|
||||||
|
### Tool Lifecycle
|
||||||
|
|
||||||
|
Each tool goes through:
|
||||||
|
|
||||||
|
1. **Schema Validation** — Zod validates the input against `tool.inputSchema`
|
||||||
|
2. **PreToolUse Hooks** — User-defined scripts can approve, deny, or modify the input
|
||||||
|
3. **Permission Check** — Deny rules → Allow rules → Tool-specific check → Classifier → User dialog
|
||||||
|
4. **Execution** — `tool.call(input, context)` runs the actual operation
|
||||||
|
5. **PostToolUse Hooks** — Scripts run after execution with the result
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Loop Continuation
|
||||||
|
|
||||||
|
After tools execute, the loop decides what to do next based on the `stop_reason`:
|
||||||
|
|
||||||
|
### `end_turn` — Model is done
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (stop_reason === 'end_turn') {
|
||||||
|
// Run post-sampling hooks
|
||||||
|
await executePostSamplingHooks(assistantMessage, toolUseContext)
|
||||||
|
// Check stop hooks (can force continuation)
|
||||||
|
const stopResult = await handleStopHooks(assistantMessage, messages)
|
||||||
|
if (stopResult.shouldContinue) {
|
||||||
|
// Inject hook feedback and continue loop
|
||||||
|
} else {
|
||||||
|
return { reason: 'end_turn' } // Terminal — loop exits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `tool_use` — Model wants to use tools
|
||||||
|
|
||||||
|
The tool results are pushed to messages and the loop continues:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
messages.push(...toolResults)
|
||||||
|
// Inject CLAUDE.md attachments for newly-discovered memory files
|
||||||
|
const attachments = await getAttachmentMessages(messages, toolUseContext)
|
||||||
|
messages.push(...attachments)
|
||||||
|
// Continue to next iteration (back to Phase 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `max_tokens` — Response was truncated
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (maxOutputTokensRecoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {
|
||||||
|
// Retry with increased max_tokens
|
||||||
|
state.maxOutputTokensRecoveryCount++
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// Trigger reactive compact as last resort
|
||||||
|
state.hasAttemptedReactiveCompact = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QueryEngine: The Session Wrapper
|
||||||
|
|
||||||
|
`QueryEngine.ts` wraps `query()` in a session lifecycle:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class QueryEngine {
|
||||||
|
private mutableMessages: Message[]
|
||||||
|
private totalUsage: NonNullableUsage
|
||||||
|
private readFileState: FileStateCache
|
||||||
|
|
||||||
|
async *submitMessage(prompt): AsyncGenerator<SDKMessage> {
|
||||||
|
// 1. Parse user input (slash commands, @mentions)
|
||||||
|
const { messages, shouldQuery } = await processUserInput({ input: prompt })
|
||||||
|
|
||||||
|
// 2. Persist transcript to disk
|
||||||
|
await recordTranscript(messages)
|
||||||
|
|
||||||
|
// 3. Run the agentic loop
|
||||||
|
if (shouldQuery) {
|
||||||
|
for await (const message of query({ messages, systemPrompt, tools })) {
|
||||||
|
// 4. Map internal messages to SDK format
|
||||||
|
yield normalizedSDKMessage(message)
|
||||||
|
// 5. Persist each message
|
||||||
|
await recordTranscript(messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Return final result with usage stats
|
||||||
|
yield { type: 'result', total_cost_usd, usage, duration_ms }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Patterns to Understand
|
||||||
|
|
||||||
|
### 1. Generator Composition
|
||||||
|
|
||||||
|
The codebase uses `yield*` heavily to compose generators:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// query.ts delegates to sub-generators
|
||||||
|
yield* runTools(toolUseBlocks, canUseTool, toolUseContext)
|
||||||
|
|
||||||
|
// QueryEngine delegates to query
|
||||||
|
for await (const message of query(params)) {
|
||||||
|
yield* normalizeMessage(message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Feature-Gated Loading
|
||||||
|
|
||||||
|
Code paths are gated by build-time feature flags:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const reactiveCompact = feature('REACTIVE_COMPACT')
|
||||||
|
? require('./services/compact/reactiveCompact.js')
|
||||||
|
: null
|
||||||
|
|
||||||
|
// Later...
|
||||||
|
if (reactiveCompact?.isReactiveCompactEnabled()) {
|
||||||
|
// This entire branch is eliminated in builds where REACTIVE_COMPACT is false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Tombstone Messages
|
||||||
|
|
||||||
|
When a streaming fallback occurs (model switch mid-stream), orphaned messages are tombstoned:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
for (const msg of assistantMessages) {
|
||||||
|
yield { type: 'tombstone', message: msg } // Removed from UI + transcript
|
||||||
|
}
|
||||||
|
assistantMessages.length = 0 // Reset
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Task Budget Tracking
|
||||||
|
|
||||||
|
API-level task budgets track spend across compaction boundaries:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (params.taskBudget) {
|
||||||
|
const preCompactContext = finalContextTokensFromLastResponse(messagesForQuery)
|
||||||
|
taskBudgetRemaining = Math.max(
|
||||||
|
0,
|
||||||
|
(taskBudgetRemaining ?? params.taskBudget.total) - preCompactContext,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Debugging Scenarios
|
||||||
|
|
||||||
|
| Symptom | Where to Look |
|
||||||
|
|---------|--------------|
|
||||||
|
| Loop never stops | Check `maxTurns` limit, `handleStopHooks` |
|
||||||
|
| Tool not executing | Permission system — check deny rules, hooks, classifier |
|
||||||
|
| Context too large | Compaction pipeline — which stage is failing? |
|
||||||
|
| Model fallback | `withRetry` in claude.ts — 529 overloaded triggers |
|
||||||
|
| Truncation errors | `MAX_OUTPUT_TOKENS_RECOVERY_LIMIT` (3 retries) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [← System Overview](./01-system-overview.md) · **Next:** [Tool System →](./03-tool-system.md)
|
||||||
445
learn/03-tool-system.md
Normal file
445
learn/03-tool-system.md
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
# 3. Tool System
|
||||||
|
|
||||||
|
> How 42 built-in tools are defined, validated, orchestrated, and rendered.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Every capability Claude Code has — reading files, running bash, editing code, searching the web — is a **Tool**. Tools are the bridge between the model's intentions and the real world.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'primaryBorderColor': '#28a745', 'lineColor': '#28a745', 'secondaryColor': '#16213e', 'tertiaryColor': '#0f3460'}}}%%
|
||||||
|
graph TB
|
||||||
|
subgraph Interface["Tool Interface — Tool.ts"]
|
||||||
|
direction LR
|
||||||
|
IS["inputSchema<br/>Zod validation"]
|
||||||
|
CP["checkPermissions"]
|
||||||
|
CALL["call — execute"]
|
||||||
|
PROMPT["prompt — model instructions"]
|
||||||
|
RENDER["render — terminal UI"]
|
||||||
|
end
|
||||||
|
|
||||||
|
IS --> CP --> CALL --> PROMPT --> RENDER
|
||||||
|
|
||||||
|
subgraph FileOps["File Operations"]
|
||||||
|
FR["FileRead"]
|
||||||
|
FW["FileWrite"]
|
||||||
|
FE["FileEdit"]
|
||||||
|
GL["Glob"]
|
||||||
|
GR["Grep"]
|
||||||
|
NE["NotebookEdit"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Exec["Execution"]
|
||||||
|
BA["Bash"]
|
||||||
|
PS["PowerShell"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Web["Web"]
|
||||||
|
WF["WebFetch"]
|
||||||
|
WS["WebSearch"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph AgentTools["Agent and Task"]
|
||||||
|
AG["Agent — spawn sub-agent"]
|
||||||
|
TC["TaskCreate"]
|
||||||
|
TG["TaskGet"]
|
||||||
|
TU["TaskUpdate"]
|
||||||
|
TL["TaskList"]
|
||||||
|
TS["TaskStop"]
|
||||||
|
SM["SendMessage"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Meta["Meta Tools"]
|
||||||
|
AQ["AskUserQuestion"]
|
||||||
|
SK["SkillTool"]
|
||||||
|
TW["TodoWrite"]
|
||||||
|
EP["EnterPlanMode"]
|
||||||
|
XP["ExitPlanMode"]
|
||||||
|
TSR["ToolSearch"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Dynamic["Dynamic — Runtime Loaded"]
|
||||||
|
MCP_T["MCP Tools<br/>from external servers"]
|
||||||
|
LSP_T["LSP Tool<br/>language server queries"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Orchestration["Orchestration Layer"]
|
||||||
|
RUN["toolOrchestration.ts<br/>runTools — parallel dispatch"]
|
||||||
|
STE["StreamingToolExecutor<br/>execute as blocks stream in"]
|
||||||
|
TEX["toolExecution.ts — 60KB<br/>single tool lifecycle"]
|
||||||
|
THK["toolHooks.ts<br/>Pre/Post hook dispatch"]
|
||||||
|
end
|
||||||
|
|
||||||
|
Interface --> FileOps
|
||||||
|
Interface --> Exec
|
||||||
|
Interface --> Web
|
||||||
|
Interface --> AgentTools
|
||||||
|
Interface --> Meta
|
||||||
|
Interface --> Dynamic
|
||||||
|
|
||||||
|
FileOps --> Orchestration
|
||||||
|
Exec --> Orchestration
|
||||||
|
Web --> Orchestration
|
||||||
|
AgentTools --> Orchestration
|
||||||
|
Meta --> Orchestration
|
||||||
|
Dynamic --> Orchestration
|
||||||
|
|
||||||
|
RUN --> STE
|
||||||
|
RUN --> TEX
|
||||||
|
TEX --> THK
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Tool Interface — `Tool.ts` (793 lines)
|
||||||
|
|
||||||
|
Every tool implements the `Tool<Input, Output, Progress>` type. Here are the key methods:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
subgraph Definition["Tool Definition"]
|
||||||
|
NAME["name: string"]
|
||||||
|
SCHEMA["inputSchema: Zod"]
|
||||||
|
ALIASES["aliases?: string array"]
|
||||||
|
HINT["searchHint?: string"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Lifecycle["Lifecycle Methods"]
|
||||||
|
VAL["validateInput<br/>pre-execution check"]
|
||||||
|
PERM["checkPermissions<br/>allow / deny / prompt"]
|
||||||
|
CALL["call<br/>execute the tool"]
|
||||||
|
DESC["description<br/>model-facing summary"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Rendering["Rendering Methods"]
|
||||||
|
RUM["renderToolUseMessage<br/>show input in terminal"]
|
||||||
|
RRM["renderToolResultMessage<br/>show output in terminal"]
|
||||||
|
RPM["renderToolUseProgressMessage<br/>spinner / progress bar"]
|
||||||
|
GRP["renderGroupedToolUse<br/>parallel display"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Metadata["Metadata Methods"]
|
||||||
|
RO["isReadOnly<br/>does it write?"]
|
||||||
|
CS["isConcurrencySafe<br/>parallel safe?"]
|
||||||
|
EN["isEnabled<br/>available now?"]
|
||||||
|
DS["isDestructive<br/>irreversible?"]
|
||||||
|
AC["toAutoClassifierInput<br/>safety classifier text"]
|
||||||
|
end
|
||||||
|
|
||||||
|
Definition --> Lifecycle --> Rendering
|
||||||
|
Definition --> Metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
### The `buildTool` Factory
|
||||||
|
|
||||||
|
All tools go through `buildTool()` which provides safe defaults:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const TOOL_DEFAULTS = {
|
||||||
|
isEnabled: () => true,
|
||||||
|
isConcurrencySafe: () => false, // Assume not safe
|
||||||
|
isReadOnly: () => false, // Assume writes
|
||||||
|
isDestructive: () => false,
|
||||||
|
checkPermissions: (input) => // Defer to general system
|
||||||
|
Promise.resolve({ behavior: 'allow', updatedInput: input }),
|
||||||
|
toAutoClassifierInput: () => '', // Skip classifier
|
||||||
|
userFacingName: () => '',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildTool(def) {
|
||||||
|
return { ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This "fail-closed" design means a tool that forgets to implement `isConcurrencySafe` defaults to `false` (not safe for parallel execution).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The 42 Built-in Tools
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
|
||||||
|
graph TB
|
||||||
|
subgraph FileOps["File Operations — Read + Write + Search"]
|
||||||
|
FR["FileRead<br/>Read files, images,<br/>PDFs, notebooks"]
|
||||||
|
FW["FileWrite<br/>Create or overwrite<br/>entire files"]
|
||||||
|
FE["FileEdit<br/>Partial string<br/>replacement edits"]
|
||||||
|
GL["Glob<br/>File pattern<br/>matching search"]
|
||||||
|
GR["Grep<br/>ripgrep content<br/>search"]
|
||||||
|
NE["NotebookEdit<br/>Jupyter notebook<br/>cell editing"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Exec["Execution — Run Commands"]
|
||||||
|
BA["Bash<br/>Shell command<br/>execution"]
|
||||||
|
PS["PowerShell<br/>Windows shell<br/>execution"]
|
||||||
|
REPL["REPL<br/>Persistent JS/TS<br/>runtime context"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Web["Web — Fetch and Search"]
|
||||||
|
WF["WebFetch<br/>HTTP GET to URLs<br/>HTML to markdown"]
|
||||||
|
WS["WebSearch<br/>Web search via<br/>Brave or similar"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph AgentTask["Agent and Task Management"]
|
||||||
|
AG["Agent<br/>Spawn sub-agent<br/>with forked context"]
|
||||||
|
TC["TaskCreate<br/>Background task"]
|
||||||
|
TG["TaskGet<br/>Check task status"]
|
||||||
|
TU["TaskUpdate<br/>Update task state"]
|
||||||
|
TL["TaskList<br/>List all tasks"]
|
||||||
|
TS["TaskStop<br/>Terminate task"]
|
||||||
|
SM["SendMessage<br/>Inter-agent<br/>messaging"]
|
||||||
|
TmC["TeamCreate<br/>Create agent team"]
|
||||||
|
TmD["TeamDelete<br/>Remove agent team"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Meta["Meta Tools — Control Claude's Behavior"]
|
||||||
|
AQ["AskUserQuestion<br/>Interactive prompt"]
|
||||||
|
SK["SkillTool<br/>Execute skills"]
|
||||||
|
TW["TodoWrite<br/>Manage task lists"]
|
||||||
|
EP["EnterPlanMode<br/>Switch to read-only"]
|
||||||
|
XP["ExitPlanMode<br/>Resume full access"]
|
||||||
|
TSR["ToolSearch<br/>Find deferred tools"]
|
||||||
|
BF["Brief<br/>Toggle concise mode"]
|
||||||
|
SL["Sleep<br/>Idle wait for<br/>proactive mode"]
|
||||||
|
SO["SyntheticOutput<br/>Structured JSON<br/>output"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Dynamic["Dynamic — Loaded at Runtime"]
|
||||||
|
MCP["MCP Tools<br/>From external<br/>MCP servers"]
|
||||||
|
LSP["LSP Tool<br/>Language server<br/>queries"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Special["Special Purpose"]
|
||||||
|
EW["EnterWorktree<br/>Git worktree<br/>isolation"]
|
||||||
|
XW["ExitWorktree<br/>Leave worktree"]
|
||||||
|
RT["RemoteTrigger<br/>Remote execution"]
|
||||||
|
SC["ScheduleCron<br/>Timed triggers"]
|
||||||
|
CF["Config<br/>Settings management"]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tool Orchestration — Parallel Execution
|
||||||
|
|
||||||
|
When the model returns multiple `tool_use` blocks, Claude Code can execute them **in parallel**:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
sequenceDiagram
|
||||||
|
participant Q as query.ts
|
||||||
|
participant O as toolOrchestration.ts
|
||||||
|
participant STE as StreamingToolExecutor
|
||||||
|
participant T1 as Tool 1 — FileRead
|
||||||
|
participant T2 as Tool 2 — Grep
|
||||||
|
participant T3 as Tool 3 — Bash
|
||||||
|
participant P as Permission System
|
||||||
|
|
||||||
|
Q->>O: runTools(3 tool_use blocks)
|
||||||
|
activate O
|
||||||
|
|
||||||
|
Note over O: Check concurrency safety
|
||||||
|
|
||||||
|
O->>STE: FileRead — isConcurrencySafe = true
|
||||||
|
O->>STE: Grep — isConcurrencySafe = true
|
||||||
|
O->>STE: Bash — isConcurrencySafe = false
|
||||||
|
|
||||||
|
par Parallel Execution
|
||||||
|
STE->>P: checkPermissions(FileRead)
|
||||||
|
P-->>STE: allow
|
||||||
|
STE->>T1: call(input)
|
||||||
|
T1-->>STE: result
|
||||||
|
|
||||||
|
STE->>P: checkPermissions(Grep)
|
||||||
|
P-->>STE: allow
|
||||||
|
STE->>T2: call(input)
|
||||||
|
T2-->>STE: result
|
||||||
|
end
|
||||||
|
|
||||||
|
Note over STE: Wait for parallel tools
|
||||||
|
|
||||||
|
STE->>P: checkPermissions(Bash)
|
||||||
|
P-->>STE: prompt user
|
||||||
|
STE->>T3: call(input)
|
||||||
|
T3-->>STE: result
|
||||||
|
|
||||||
|
O-->>Q: yield all tool_result messages
|
||||||
|
deactivate O
|
||||||
|
```
|
||||||
|
|
||||||
|
Key files in the orchestration layer:
|
||||||
|
|
||||||
|
- **`toolOrchestration.ts`** — `runTools()`: dispatches tools, handles parallel vs. sequential
|
||||||
|
- **`StreamingToolExecutor`** — Starts permission checks while model is still streaming
|
||||||
|
- **`toolExecution.ts`** (60KB) — Single tool lifecycle: validate → permissions → execute → hooks
|
||||||
|
- **`toolHooks.ts`** — Dispatches PreToolUse and PostToolUse hooks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Single Tool Lifecycle
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
BLOCK["tool_use block arrives<br/>from model stream"]:::start
|
||||||
|
|
||||||
|
PARSE["Parse + validate input<br/>via Zod inputSchema"]:::step
|
||||||
|
VAL{"validateInput?"}:::check
|
||||||
|
|
||||||
|
DENY_VAL["Return error to model<br/>with validation message"]:::deny
|
||||||
|
|
||||||
|
PRE_HOOK["Run PreToolUse hooks<br/>user-defined scripts"]:::hook
|
||||||
|
HOOK_R{"Hook result?"}:::check
|
||||||
|
|
||||||
|
PERM["Check permissions<br/>deny → allow → tool → hooks → classifier → dialog"]:::step
|
||||||
|
PERM_R{"Permission?"}:::check
|
||||||
|
|
||||||
|
EXEC["tool.call(input, context)<br/>execute the operation"]:::step
|
||||||
|
RESULT["Map output to tool_result<br/>via mapToolResultToToolResultBlockParam"]:::step
|
||||||
|
|
||||||
|
SIZE{"Result exceeds<br/>maxResultSizeChars?"}:::check
|
||||||
|
PERSIST["Persist to disk<br/>return file path + preview"]:::step
|
||||||
|
|
||||||
|
POST_HOOK["Run PostToolUse hooks"]:::hook
|
||||||
|
RENDER["Render in terminal<br/>renderToolResultMessage"]:::step
|
||||||
|
|
||||||
|
YIELD["Yield tool_result<br/>to query loop"]:::done
|
||||||
|
|
||||||
|
DENY_PERM["Return permission_denied<br/>error to model"]:::deny
|
||||||
|
|
||||||
|
BLOCK --> PARSE --> VAL
|
||||||
|
VAL -->|"pass"| PRE_HOOK
|
||||||
|
VAL -->|"fail"| DENY_VAL
|
||||||
|
|
||||||
|
PRE_HOOK --> HOOK_R
|
||||||
|
HOOK_R -->|"approve"| PERM
|
||||||
|
HOOK_R -->|"deny"| DENY_PERM
|
||||||
|
HOOK_R -->|"modify input"| PERM
|
||||||
|
|
||||||
|
PERM --> PERM_R
|
||||||
|
PERM_R -->|"allow"| EXEC
|
||||||
|
PERM_R -->|"deny"| DENY_PERM
|
||||||
|
|
||||||
|
EXEC --> RESULT --> SIZE
|
||||||
|
SIZE -->|"within limit"| POST_HOOK
|
||||||
|
SIZE -->|"exceeds limit"| PERSIST --> POST_HOOK
|
||||||
|
|
||||||
|
POST_HOOK --> RENDER --> YIELD
|
||||||
|
|
||||||
|
classDef start fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef step fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef hook fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef deny fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef done fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ToolSearch — Deferred Tool Loading
|
||||||
|
|
||||||
|
With 42+ tools, sending all schemas to the model wastes tokens. **ToolSearch** defers tools that aren't immediately needed:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#ffc107', 'primaryBorderColor': '#ffc107'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
ALL["42+ Tools"]:::input
|
||||||
|
|
||||||
|
SPLIT{"shouldDefer?"}:::check
|
||||||
|
|
||||||
|
EAGER["~15 Eager Tools<br/>Always in prompt<br/>FileRead, Bash, Grep..."]:::eager
|
||||||
|
DEFER["~27 Deferred Tools<br/>Schema not sent initially<br/>TaskCreate, WebSearch..."]:::defer
|
||||||
|
ALWAYS["alwaysLoad Tools<br/>Forced eager by MCP meta"]:::eager
|
||||||
|
|
||||||
|
SEARCH["ToolSearch Tool<br/>Model searches by keyword<br/>using searchHint"]:::tool
|
||||||
|
|
||||||
|
FOUND["Tool schema injected<br/>into next request"]:::result
|
||||||
|
|
||||||
|
ALL --> SPLIT
|
||||||
|
SPLIT -->|"no"| EAGER
|
||||||
|
SPLIT -->|"yes"| DEFER
|
||||||
|
SPLIT -->|"alwaysLoad"| ALWAYS
|
||||||
|
|
||||||
|
DEFER --> SEARCH
|
||||||
|
SEARCH --> FOUND
|
||||||
|
|
||||||
|
classDef input fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef eager fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef defer fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef tool fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef result fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dynamic Tools — MCP and LSP
|
||||||
|
|
||||||
|
Beyond built-in tools, Claude Code loads tools dynamically at runtime:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph MCP["MCP Tools — Model Context Protocol"]
|
||||||
|
SRV["External MCP Servers<br/>configured in settings"]
|
||||||
|
CONN["MCPConnectionManager<br/>stdio / SSE transport"]
|
||||||
|
DISC["Discover tools<br/>via tools/list"]
|
||||||
|
WRAP["Wrap as Tool objects<br/>name: mcp__server__tool"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph LSP["LSP Tool — Language Server Protocol"]
|
||||||
|
LS["Language Server<br/>runtime type info"]
|
||||||
|
QUERY_LSP["Query definitions,<br/>references, diagnostics"]
|
||||||
|
end
|
||||||
|
|
||||||
|
SRV --> CONN --> DISC --> WRAP
|
||||||
|
LS --> QUERY_LSP
|
||||||
|
|
||||||
|
MERGE["Merged into tool pool<br/>via useMergedTools hook"]:::merge
|
||||||
|
|
||||||
|
WRAP --> MERGE
|
||||||
|
QUERY_LSP --> MERGE
|
||||||
|
|
||||||
|
classDef merge fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
MCP tools are prefixed with `mcp__<server>__<tool>` unless running in SDK no-prefix mode. They go through the same permission system as built-in tools.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### 1. Self-Contained Modules
|
||||||
|
|
||||||
|
Each tool directory (`src/tools/<ToolName>/`) contains everything:
|
||||||
|
- `index.ts` — Tool definition via `buildTool()`
|
||||||
|
- `prompt.ts` — Model-facing instructions
|
||||||
|
- `*.test.ts` — Tests
|
||||||
|
- Additional helpers as needed
|
||||||
|
|
||||||
|
### 2. Fail-Closed Defaults
|
||||||
|
|
||||||
|
`buildTool()` defaults are conservative:
|
||||||
|
- `isConcurrencySafe = false` — Won't run in parallel unless explicitly safe
|
||||||
|
- `isReadOnly = false` — Assumed to write unless stated otherwise
|
||||||
|
- `checkPermissions` defaults to `allow` — But the general permission system still applies
|
||||||
|
|
||||||
|
### 3. Result Size Budgets
|
||||||
|
|
||||||
|
Each tool has `maxResultSizeChars`. Oversized results are persisted to disk and the model gets a truncated preview + file path. This prevents single tool results from consuming the entire context window.
|
||||||
|
|
||||||
|
### 4. Observable Input Backfilling
|
||||||
|
|
||||||
|
`backfillObservableInput()` adds derived fields to tool inputs for SDK consumers and transcripts, without mutating the API-bound input (which would break prompt caching):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// The API sees: { file_path: "src/foo.ts" }
|
||||||
|
// SDK/transcript sees: { file_path: "src/foo.ts", resolved_path: "/abs/path/src/foo.ts" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [← The Agentic Loop](./02-agentic-loop.md) · **Next:** [Permission System →](./04-permission-system.md)
|
||||||
339
learn/04-permission-system.md
Normal file
339
learn/04-permission-system.md
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
# 4. Permission System
|
||||||
|
|
||||||
|
> How Claude Code prevents an AI from doing dangerous things — a multi-layered defense.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why Permissions Matter
|
||||||
|
|
||||||
|
Claude Code can run **arbitrary bash commands**, **write to any file**, and **make network requests**. Without a permission system, a single misguided model response could `rm -rf /` your entire system.
|
||||||
|
|
||||||
|
The permission system is a chain of checks — if any link denies, the tool doesn't run.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Permission Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#dc3545', 'primaryBorderColor': '#dc3545'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
ENTRY["Tool call arrives"]:::start
|
||||||
|
|
||||||
|
DR{"Deny rules?<br/>blanket deny, pattern match"}
|
||||||
|
AR{"Allow rules?<br/>always-allow from settings"}
|
||||||
|
TSP{"tool.checkPermissions?<br/>tool-specific logic"}
|
||||||
|
HOOK{"PreToolUse hooks?<br/>user-defined scripts"}
|
||||||
|
CLASS{"Auto-mode classifier?<br/>transcript safety analysis"}
|
||||||
|
DIALOG{"User permission dialog<br/>Y / n / always-allow"}
|
||||||
|
|
||||||
|
ALLOW["ALLOW<br/>execute tool"]:::allow
|
||||||
|
DENY["DENY<br/>return error to model"]:::deny
|
||||||
|
|
||||||
|
ENTRY --> DR
|
||||||
|
|
||||||
|
DR -->|"matched deny rule"| DENY
|
||||||
|
DR -->|"no match"| AR
|
||||||
|
|
||||||
|
AR -->|"matched allow rule"| ALLOW
|
||||||
|
AR -->|"no match"| TSP
|
||||||
|
|
||||||
|
TSP -->|"tool says allow"| HOOK
|
||||||
|
TSP -->|"tool says deny"| DENY
|
||||||
|
|
||||||
|
HOOK -->|"hook approves"| ALLOW
|
||||||
|
HOOK -->|"hook denies"| DENY
|
||||||
|
HOOK -->|"no decision"| CLASS
|
||||||
|
|
||||||
|
CLASS -->|"classified safe"| ALLOW
|
||||||
|
CLASS -->|"classified unsafe"| DIALOG
|
||||||
|
CLASS -->|"not in auto-mode"| DIALOG
|
||||||
|
|
||||||
|
DIALOG -->|"user accepts"| ALLOW
|
||||||
|
DIALOG -->|"user rejects"| DENY
|
||||||
|
DIALOG -->|"always allow"| AR
|
||||||
|
|
||||||
|
classDef start fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef allow fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef deny fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 1: Deny Rules
|
||||||
|
|
||||||
|
**First check. Highest priority. Cannot be overridden.**
|
||||||
|
|
||||||
|
Deny rules are pattern-matched against tool name and input. If a deny rule matches, the tool is **immediately rejected** — no further checks run.
|
||||||
|
|
||||||
|
Sources of deny rules:
|
||||||
|
- `settings.json` — User-configured
|
||||||
|
- CLAUDE.md — Project-level rules
|
||||||
|
- Organization policy — Enterprise MDM settings
|
||||||
|
|
||||||
|
Example deny rules:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"alwaysDenyRules": {
|
||||||
|
"settings": [
|
||||||
|
{ "tool": "Bash", "pattern": "rm -rf" },
|
||||||
|
{ "tool": "FileWrite", "pattern": "/etc/*" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Matching
|
||||||
|
|
||||||
|
Tools can implement `preparePermissionMatcher()` for custom pattern matching:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Bash tool: "git *" matches any git command
|
||||||
|
preparePermissionMatcher(input) {
|
||||||
|
return async (pattern) => minimatch(input.command, pattern)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 2: Allow Rules
|
||||||
|
|
||||||
|
If no deny rule matched, check if an **allow rule** grants automatic approval.
|
||||||
|
|
||||||
|
Allow rules come from:
|
||||||
|
- User clicking "always allow" in the permission dialog
|
||||||
|
- `settings.json` configuration
|
||||||
|
- Slash command grants (e.g., `/plan` exit grants specific operations)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
subgraph Sources["Allow Rule Sources"]
|
||||||
|
S1["settings.json<br/>user config"]
|
||||||
|
S2["CLAUDE.md<br/>project rules"]
|
||||||
|
S3["User dialog<br/>always-allow choice"]
|
||||||
|
S4["Command grants<br/>plan mode exit"]
|
||||||
|
end
|
||||||
|
|
||||||
|
MERGE["ToolPermissionContext<br/>alwaysAllowRules"]:::merge
|
||||||
|
|
||||||
|
CHECK{"Pattern match<br/>against tool + input"}:::check
|
||||||
|
|
||||||
|
ALLOW["Auto-approved"]:::allow
|
||||||
|
NEXT["Continue to<br/>next layer"]:::next
|
||||||
|
|
||||||
|
S1 --> MERGE
|
||||||
|
S2 --> MERGE
|
||||||
|
S3 --> MERGE
|
||||||
|
S4 --> MERGE
|
||||||
|
|
||||||
|
MERGE --> CHECK
|
||||||
|
CHECK -->|"match"| ALLOW
|
||||||
|
CHECK -->|"no match"| NEXT
|
||||||
|
|
||||||
|
classDef merge fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef allow fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef next fill:#333,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 3: Tool-Specific Permissions
|
||||||
|
|
||||||
|
Each tool implements `checkPermissions(input, context)`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example: FileRead defaults to allow (it's read-only)
|
||||||
|
checkPermissions: () => Promise.resolve({ behavior: 'allow' })
|
||||||
|
|
||||||
|
// Example: Bash checks if the command is read-only
|
||||||
|
checkPermissions: (input) => {
|
||||||
|
if (isReadOnlyCommand(input.command)) {
|
||||||
|
return { behavior: 'allow' }
|
||||||
|
}
|
||||||
|
return { behavior: 'askUser', message: `Run: ${input.command}` }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The result can be:
|
||||||
|
- `{ behavior: 'allow' }` — Approved
|
||||||
|
- `{ behavior: 'deny', message }` — Rejected with reason
|
||||||
|
- `{ behavior: 'askUser', message }` — Escalate to user prompt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 4: PreToolUse Hooks
|
||||||
|
|
||||||
|
User-defined scripts that run before tool execution. Configured in `settings.json` or CLAUDE.md:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"command": "/path/to/safety-check.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Hook scripts receive the tool name and input as JSON on stdin. They can:
|
||||||
|
- **Approve** (exit 0, no output)
|
||||||
|
- **Deny** (exit non-zero, stderr has reason)
|
||||||
|
- **Modify input** (exit 0, stdout has modified JSON)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 5: Auto-Mode Classifier
|
||||||
|
|
||||||
|
In `--auto` mode, a **classifier** examines the conversation transcript to determine if a tool call is safe:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
TC["Tool call in auto-mode"]:::start
|
||||||
|
|
||||||
|
BUILD["Build classifier input<br/>tool.toAutoClassifierInput(input)"]:::step
|
||||||
|
TRANSCRIPT["Append recent transcript<br/>for context"]:::step
|
||||||
|
CLASSIFY["Run safety classifier<br/>is this operation safe?"]:::step
|
||||||
|
|
||||||
|
SAFE{"Classified as?"}:::check
|
||||||
|
|
||||||
|
ALLOW["Auto-approved<br/>no user prompt"]:::allow
|
||||||
|
PROMPT["Escalate to<br/>user dialog"]:::deny
|
||||||
|
|
||||||
|
TC --> BUILD --> TRANSCRIPT --> CLASSIFY --> SAFE
|
||||||
|
SAFE -->|"safe"| ALLOW
|
||||||
|
SAFE -->|"unsafe"| PROMPT
|
||||||
|
|
||||||
|
classDef start fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef step fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef allow fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef deny fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
Each tool provides `toAutoClassifierInput()` which returns a compact representation for the classifier. Security-irrelevant tools return `''` to skip classification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 6: User Permission Dialog
|
||||||
|
|
||||||
|
The last resort — ask the human:
|
||||||
|
|
||||||
|
```
|
||||||
|
╭────────────────────────────────────────╮
|
||||||
|
│ Claude wants to run: │
|
||||||
|
│ │
|
||||||
|
│ $ npm install lodash │
|
||||||
|
│ │
|
||||||
|
│ (Y)es · (n)o · (a)lways allow │
|
||||||
|
╰────────────────────────────────────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
Choosing "always allow" adds a permanent allow rule.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Permission Modes
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#fd7e14', 'primaryBorderColor': '#fd7e14'}}}%%
|
||||||
|
flowchart TB
|
||||||
|
START(["Session Start"]):::neutral --> DEFAULT
|
||||||
|
|
||||||
|
DEFAULT["DEFAULT MODE<br/>Prompt user on every write tool"]:::mode1
|
||||||
|
PLAN["PLAN MODE<br/>Read tools auto-approved<br/>Write tools require approval"]:::mode2
|
||||||
|
AUTO["AUTO MODE<br/>Classifier decides safety<br/>Safe = allow, Unsafe = prompt"]:::mode3
|
||||||
|
BYPASS["BYPASS MODE<br/>Everything auto-approved<br/>No permission checks"]:::mode4
|
||||||
|
|
||||||
|
DEFAULT -->|"/plan command<br/>or model enters plan"| PLAN
|
||||||
|
PLAN -->|"model exits<br/>plan mode"| DEFAULT
|
||||||
|
DEFAULT -->|"--auto flag<br/>user opts in"| AUTO
|
||||||
|
AUTO -->|"denial limit<br/>exceeded"| DEFAULT
|
||||||
|
DEFAULT -->|"--dangerously-<br/>skip-permissions"| BYPASS
|
||||||
|
|
||||||
|
classDef neutral fill:#333,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
classDef mode1 fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef mode2 fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef mode3 fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef mode4 fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default Mode
|
||||||
|
- Every write operation prompts the user
|
||||||
|
- Read operations (FileRead, Glob, Grep) auto-approved
|
||||||
|
- Most secure, most friction
|
||||||
|
|
||||||
|
### Plan Mode
|
||||||
|
- Entered via `/plan` command or model's `EnterPlanMode` tool
|
||||||
|
- All read tools auto-approved
|
||||||
|
- All write tools require explicit user approval
|
||||||
|
- Model can plan freely, execute cautiously
|
||||||
|
|
||||||
|
### Auto Mode
|
||||||
|
- Enabled via `--auto` flag
|
||||||
|
- Safety classifier decides per-tool
|
||||||
|
- Falls back to prompting if classifier says "unsafe"
|
||||||
|
- Has a **denial limit** — too many denials drops back to Default
|
||||||
|
|
||||||
|
### Bypass Mode
|
||||||
|
- Enabled via `--dangerously-skip-permissions`
|
||||||
|
- **Everything auto-approved** — no checks at all
|
||||||
|
- Named to be scary because it IS scary
|
||||||
|
- No permission system protection whatsoever
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The `ToolPermissionContext` Type
|
||||||
|
|
||||||
|
All permission state lives in `AppState.toolPermissionContext`:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#dc3545', 'primaryBorderColor': '#dc3545'}}}%%
|
||||||
|
graph TB
|
||||||
|
subgraph TPC["ToolPermissionContext — Immutable"]
|
||||||
|
MODE["mode<br/>default / plan / auto / bypass"]
|
||||||
|
AWD["additionalWorkingDirectories<br/>extra safe paths"]
|
||||||
|
ALLOW["alwaysAllowRules<br/>by source: settings, command, etc."]
|
||||||
|
DENY["alwaysDenyRules<br/>by source"]
|
||||||
|
ASK["alwaysAskRules<br/>force prompt even if allowed"]
|
||||||
|
BPM["isBypassPermissionsModeAvailable<br/>can user enable bypass?"]
|
||||||
|
AUTO_A["isAutoModeAvailable<br/>can user enable auto?"]
|
||||||
|
AVOID["shouldAvoidPermissionPrompts<br/>background agents that cannot show UI"]
|
||||||
|
AWAIT["awaitAutomatedChecksBeforeDialog<br/>coordinator workers"]
|
||||||
|
PRE["prePlanMode<br/>mode to restore after plan exits"]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This is wrapped in `DeepImmutable<T>` — TypeScript enforces that nobody mutates this in place. Updates go through `setAppState(prev => ({ ...prev, toolPermissionContext: { ... } }))`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Denial Tracking
|
||||||
|
|
||||||
|
Auto mode tracks denials to prevent runaway unsafe operations:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#dc3545', 'primaryBorderColor': '#dc3545'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
START["Auto mode active"]:::start
|
||||||
|
D1["Denial 1"]:::deny
|
||||||
|
D2["Denial 2"]:::deny
|
||||||
|
DN["Denial N<br/>limit exceeded"]:::deny
|
||||||
|
FALLBACK["Fall back to<br/>Default mode"]:::result
|
||||||
|
|
||||||
|
START --> D1 --> D2 -->|"..."| DN --> FALLBACK
|
||||||
|
|
||||||
|
classDef start fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef deny fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef result fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
This is stored in `DenialTrackingState` — for async subagents that can't show UI, a local tracking copy is used since their `setAppState` is a no-op.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [← Tool System](./03-tool-system.md) · **Next:** [Context Management →](./05-context-management.md)
|
||||||
106
learn/05-context-management.md
Normal file
106
learn/05-context-management.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# 5. Context Management — The Compaction Pipeline
|
||||||
|
|
||||||
|
> How Claude Code keeps conversations within the model's context window.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Pipeline
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#fd7e14', 'primaryBorderColor': '#fd7e14'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
RAW["Raw message<br/>history"]:::input
|
||||||
|
|
||||||
|
S1["SNIP COMPACT<br/>Sliding window<br/>Drop oldest turns<br/>Preserve recent N"]:::stage1
|
||||||
|
S2["MICRO COMPACT<br/>Truncate individual<br/>tool results exceeding<br/>size thresholds"]:::stage2
|
||||||
|
S3["AUTO COMPACT<br/>Summarize full conversation<br/>via separate API call<br/>Circuit breaker on failure"]:::stage3
|
||||||
|
S4["CONTEXT COLLAPSE<br/>Read-time projection<br/>Archived collapsed views<br/>Granular preservation"]:::stage4
|
||||||
|
|
||||||
|
FINAL["Messages ready<br/>for API call"]:::output
|
||||||
|
|
||||||
|
S5["REACTIVE COMPACT<br/>Emergency trigger<br/>on API 413 error<br/>Last resort"]:::emergency
|
||||||
|
|
||||||
|
RAW ==> S1 ==> S2 ==> S3 ==> S4 ==> FINAL
|
||||||
|
FINAL -. "API returns prompt_too_long" .-> S5
|
||||||
|
S5 ==> FINAL
|
||||||
|
|
||||||
|
classDef input fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef stage1 fill:#0d3d0d,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef stage2 fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef stage3 fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef stage4 fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef output fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef emergency fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stage Details
|
||||||
|
|
||||||
|
### Stage 1: Snip Compact
|
||||||
|
Sliding window that drops the oldest turns. The REPL keeps full history for UI scrollback — snip is a *read-time projection* only affecting what's sent to the API. Feature-gated via `HISTORY_SNIP`.
|
||||||
|
|
||||||
|
### Stage 2: Micro Compact
|
||||||
|
Truncates individual tool results exceeding size thresholds. Results are cached by `tool_use_id` so subsequent iterations reuse cached truncations.
|
||||||
|
**Key file:** `src/services/compact/microCompact.ts` (19KB)
|
||||||
|
|
||||||
|
### Stage 3: Auto Compact
|
||||||
|
Summarizes the full conversation via a **separate API call**. Has a circuit breaker — too many consecutive failures stops retrying.
|
||||||
|
**Key files:** `autoCompact.ts` (13KB), `compact.ts` (60KB), `prompt.ts` (16KB)
|
||||||
|
|
||||||
|
### Stage 4: Context Collapse
|
||||||
|
Read-time projection that archives older segments with granular preservation. Exists in a separate store — the REPL's message array is never modified.
|
||||||
|
|
||||||
|
### Stage 5: Reactive Compact
|
||||||
|
Emergency trigger when the API returns `prompt_too_long` (413). Last resort — only runs after a real API failure. Feature-gated via `REACTIVE_COMPACT`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Token Budget State Machine
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#ffc107', 'primaryBorderColor': '#ffc107'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
N["NORMAL<br/>within limits"]:::green
|
||||||
|
W["WARNING<br/>context > 80%"]:::yellow
|
||||||
|
C["CRITICAL<br/>context > 95%"]:::orange
|
||||||
|
B["BLOCKING<br/>context > 98%<br/>auto-compact OFF"]:::red
|
||||||
|
AC["AUTO COMPACT<br/>fires automatically"]:::blue
|
||||||
|
RC["REACTIVE<br/>emergency on 413"]:::darkred
|
||||||
|
M["MANUAL<br/>user runs /compact"]:::gray
|
||||||
|
|
||||||
|
N -->|"grows"| W
|
||||||
|
W -->|"grows"| C
|
||||||
|
C -->|"auto-compact ON"| AC
|
||||||
|
C -->|"auto-compact OFF"| B
|
||||||
|
AC -->|"success"| N
|
||||||
|
AC -->|"fails + API 413"| RC
|
||||||
|
RC -->|"success"| N
|
||||||
|
B -->|"user: /compact"| M
|
||||||
|
M -->|"success"| N
|
||||||
|
|
||||||
|
classDef green fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef yellow fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef orange fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef red fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef darkred fill:#3a0a0a,stroke:#a30000,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef blue fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef gray fill:#2a2a2a,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transitions
|
||||||
|
- **NORMAL → WARNING** at 80% — UI shows warning indicator
|
||||||
|
- **WARNING → CRITICAL** at 95% — compaction should fire
|
||||||
|
- **CRITICAL → AUTO COMPACT** — if enabled, fires summarization API call
|
||||||
|
- **CRITICAL → BLOCKING** — if auto-compact OFF, blocks new API calls
|
||||||
|
- **BLOCKING → MANUAL** — user runs `/compact` to recover
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tool Result Budget
|
||||||
|
|
||||||
|
Separate from conversation compaction — a per-message budget for aggregate tool result size. Runs **before** the pipeline every iteration. Oversized results are persisted to disk, replaced with a file path + truncated preview. Tools with `maxResultSizeChars = Infinity` (e.g., FileRead) are exempt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [← Permission System](./04-permission-system.md) · **Next:** [State Management →](./06-state-management.md)
|
||||||
148
learn/06-state-management.md
Normal file
148
learn/06-state-management.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# 6. State Management
|
||||||
|
|
||||||
|
> A single immutable store with 50+ fields — how Claude Code manages application state.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
graph TB
|
||||||
|
subgraph Store["AppState — Single Immutable Store"]
|
||||||
|
direction TB
|
||||||
|
subgraph Core_S["Core Session State"]
|
||||||
|
Model["mainLoopModel"]
|
||||||
|
Think["thinkingEnabled"]
|
||||||
|
Fast["fastMode"]
|
||||||
|
Effort["effortValue"]
|
||||||
|
Settings["settings: SettingsJson"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Perm_S["Permission State"]
|
||||||
|
TPC["toolPermissionContext"]
|
||||||
|
Mode["mode: default / plan / auto / bypass"]
|
||||||
|
Allow["alwaysAllowRules"]
|
||||||
|
Deny["alwaysDenyRules"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph MCP_S["MCP State"]
|
||||||
|
MCPCli["clients: MCPServerConnection array"]
|
||||||
|
MCPTool["tools: Tool array"]
|
||||||
|
MCPCmd["commands: Command array"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Task_S["Background Tasks"]
|
||||||
|
Tasks["tasks: taskId to TaskState map"]
|
||||||
|
AgentReg["agentNameRegistry"]
|
||||||
|
FG["foregroundedTaskId"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph UI_S["UI State"]
|
||||||
|
Spec["speculation: predictive execution"]
|
||||||
|
Suggest["promptSuggestion: autocomplete"]
|
||||||
|
Notif["notifications queue"]
|
||||||
|
Bridge["replBridge: remote control state"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph History_S["History and Tracking"]
|
||||||
|
FH["fileHistory: snapshots for rewind"]
|
||||||
|
Attr["attribution: commit metadata"]
|
||||||
|
Todos["todos: per-agent lists"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
REPL_C["REPL.tsx<br/>reads + subscribes"]:::consumer
|
||||||
|
QE_C["QueryEngine<br/>reads via getAppState"]:::consumer
|
||||||
|
Tools_C["Tools<br/>reads via ToolUseContext"]:::consumer
|
||||||
|
|
||||||
|
SET["setAppState<br/>functional update"]:::mutator
|
||||||
|
ONCHANGE["onChangeAppState<br/>side effect reactions"]:::mutator
|
||||||
|
|
||||||
|
Store --> REPL_C
|
||||||
|
Store --> QE_C
|
||||||
|
Store --> Tools_C
|
||||||
|
|
||||||
|
SET --> Store
|
||||||
|
ONCHANGE --> Store
|
||||||
|
|
||||||
|
classDef consumer fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef mutator fill:#2d1b4e,stroke:#e83e8c,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
### Immutability via `DeepImmutable<T>`
|
||||||
|
|
||||||
|
The `AppState` type is wrapped in `DeepImmutable<T>` — TypeScript enforces that no consumer can mutate state in place:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type AppState = DeepImmutable<{
|
||||||
|
settings: SettingsJson
|
||||||
|
mainLoopModel: ModelSetting
|
||||||
|
toolPermissionContext: ToolPermissionContext
|
||||||
|
// ... 50+ fields
|
||||||
|
}>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Functional Updates
|
||||||
|
|
||||||
|
State is updated via `setAppState(prev => newState)`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
setAppState(prev => ({
|
||||||
|
...prev,
|
||||||
|
toolPermissionContext: {
|
||||||
|
...prev.toolPermissionContext,
|
||||||
|
mode: 'plan',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Side Effects via `onChangeAppState`
|
||||||
|
|
||||||
|
After state changes, `onChangeAppState.ts` fires reactive side effects — persisting settings, updating UI, notifying remote sessions, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key State Groups
|
||||||
|
|
||||||
|
### Session State
|
||||||
|
`mainLoopModel`, `thinkingEnabled`, `fastMode`, `effortValue` — control how the model behaves each turn.
|
||||||
|
|
||||||
|
### Permission State
|
||||||
|
`toolPermissionContext` — contains mode, allow/deny rules, and bypass availability. See [Guide 4](./04-permission-system.md).
|
||||||
|
|
||||||
|
### MCP State
|
||||||
|
`mcp.clients`, `mcp.tools`, `mcp.commands` — dynamically connected MCP servers and their exposed tools/commands.
|
||||||
|
|
||||||
|
### Background Tasks
|
||||||
|
`tasks` — a map of `taskId → TaskState` for background agent tasks. `foregroundedTaskId` controls which task's messages appear in the main view.
|
||||||
|
|
||||||
|
### UI State
|
||||||
|
`speculation` — predictive execution state for pre-computing responses. `promptSuggestion` — autocomplete suggestions. `notifications` — queued UI notifications.
|
||||||
|
|
||||||
|
### History
|
||||||
|
`fileHistory` — snapshots for `/rewind`. `attribution` — commit metadata for git attribution. `todos` — per-agent task lists.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
graph LR
|
||||||
|
subgraph StateDir["src/state/"]
|
||||||
|
AS["AppState.tsx<br/>React context + hooks"]
|
||||||
|
ASS["AppStateStore.ts<br/>Type definition + defaults"]
|
||||||
|
OC["onChangeAppState.ts<br/>Side effect reactions"]
|
||||||
|
SEL["selectors.ts<br/>Derived state"]
|
||||||
|
ST["store.ts<br/>Store type"]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [← Context Management](./05-context-management.md) · **Next:** [Extension Model →](./07-extension-model.md)
|
||||||
248
learn/07-extension-model.md
Normal file
248
learn/07-extension-model.md
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# 7. Extension Model
|
||||||
|
|
||||||
|
> Skills, plugins, hooks, sub-agents, and swarms — how Claude Code is extended.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#ffc107', 'primaryBorderColor': '#ffc107'}}}%%
|
||||||
|
graph TB
|
||||||
|
subgraph Skills["Skills"]
|
||||||
|
BS["Bundled Skills<br/>shipped with CLI"]:::skill
|
||||||
|
US["User Skills<br/>.claude/skills/*.md"]:::skill
|
||||||
|
PS["Project Skills<br/>.claude/skills/ in repo"]:::skill
|
||||||
|
SL["loadSkillsDir.ts<br/>discover + parse frontmatter"]:::loader
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Plugins["Plugins"]
|
||||||
|
MP["Managed Plugins<br/>org-level policy"]:::plugin
|
||||||
|
IP["Installed Plugins<br/>user choice"]:::plugin
|
||||||
|
BP["Built-in Plugins<br/>shipped with CLI"]:::plugin
|
||||||
|
PL["pluginLoader.ts<br/>cache-only, versioned"]:::loader
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Agents["Agents"]
|
||||||
|
SA["Sub-agents via AgentTool<br/>forked context, own query loop"]:::agent
|
||||||
|
CO["Coordinator Mode<br/>leader dispatches tasks,<br/>workers get limited tools"]:::agent
|
||||||
|
SW["Swarms<br/>multi-process via tmux,<br/>mailbox message passing"]:::agent
|
||||||
|
FA["Forked Agents<br/>share parent prompt cache,<br/>overlay filesystem"]:::agent
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph HookSys["Hooks"]
|
||||||
|
PRE["PreToolUse<br/>before tool execution"]:::hook
|
||||||
|
POST["PostToolUse<br/>after tool execution"]:::hook
|
||||||
|
SESS["Session Hooks<br/>lifecycle events"]:::hook
|
||||||
|
HC["Configured in<br/>settings.json or CLAUDE.md"]:::hook
|
||||||
|
end
|
||||||
|
|
||||||
|
CMD["commands.ts — Command Registry<br/>merges all sources"]:::registry
|
||||||
|
TOOL["Tool.ts — Tool Interface"]:::registry
|
||||||
|
QUERY["query.ts — Agentic Loop"]:::registry
|
||||||
|
|
||||||
|
BS --> SL
|
||||||
|
US --> SL
|
||||||
|
PS --> SL
|
||||||
|
SL --> CMD
|
||||||
|
|
||||||
|
MP --> PL
|
||||||
|
IP --> PL
|
||||||
|
BP --> PL
|
||||||
|
PL --> CMD
|
||||||
|
PL -->|"plugin MCP servers"| TOOL
|
||||||
|
|
||||||
|
CMD --> TOOL
|
||||||
|
|
||||||
|
SA --> QUERY
|
||||||
|
CO --> QUERY
|
||||||
|
SW --> QUERY
|
||||||
|
FA --> QUERY
|
||||||
|
|
||||||
|
PRE --> TOOL
|
||||||
|
POST --> TOOL
|
||||||
|
HC --> PRE
|
||||||
|
HC --> POST
|
||||||
|
|
||||||
|
classDef skill fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef plugin fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef agent fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef hook fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef loader fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef registry fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:3px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
Skills are **markdown instruction files** with YAML frontmatter. They teach Claude Code how to do specific tasks.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Sources["Skill Sources"]
|
||||||
|
BUNDLED["Bundled<br/>shipped with CLI"]:::bundled
|
||||||
|
USER[".claude/skills/<br/>user-defined"]:::user
|
||||||
|
PROJECT["repo .claude/skills/<br/>project-specific"]:::project
|
||||||
|
end
|
||||||
|
|
||||||
|
LOADER["loadSkillsDir.ts<br/>Discover + parse<br/>YAML frontmatter"]:::loader
|
||||||
|
|
||||||
|
TOOL["SkillTool<br/>Model invokes via tool call"]:::tool
|
||||||
|
CMD["Slash commands<br/>/skills to manage"]:::cmd
|
||||||
|
|
||||||
|
BUNDLED --> LOADER
|
||||||
|
USER --> LOADER
|
||||||
|
PROJECT --> LOADER
|
||||||
|
LOADER --> TOOL
|
||||||
|
LOADER --> CMD
|
||||||
|
|
||||||
|
classDef bundled fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef user fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef project fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef loader fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef tool fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef cmd fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key file:** `src/skills/loadSkillsDir.ts` (34KB)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
Plugins are bundles of tools, MCP servers, and commands. They extend Claude Code at a deeper level than skills.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#ffc107', 'primaryBorderColor': '#ffc107'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Types["Plugin Types"]
|
||||||
|
MANAGED["Managed Plugins<br/>org-level policy<br/>enterprise MDM"]:::managed
|
||||||
|
INSTALLED["User Plugins<br/>installed via marketplace<br/>or manual"]:::installed
|
||||||
|
BUILTIN["Built-in Plugins<br/>shipped with CLI"]:::builtin
|
||||||
|
end
|
||||||
|
|
||||||
|
CACHE["pluginLoader.ts<br/>cache-only loading<br/>versioned artifacts"]:::loader
|
||||||
|
|
||||||
|
subgraph Provides["Plugin Provides"]
|
||||||
|
TOOLS["Tools<br/>via MCP servers"]:::tool
|
||||||
|
CMDS["Slash Commands"]:::cmd
|
||||||
|
SKILLS_P["Skills"]:::skill
|
||||||
|
end
|
||||||
|
|
||||||
|
MANAGED --> CACHE
|
||||||
|
INSTALLED --> CACHE
|
||||||
|
BUILTIN --> CACHE
|
||||||
|
|
||||||
|
CACHE --> TOOLS
|
||||||
|
CACHE --> CMDS
|
||||||
|
CACHE --> SKILLS_P
|
||||||
|
|
||||||
|
classDef managed fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef installed fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef builtin fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef loader fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef tool fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef cmd fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef skill fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key files:** `src/plugins/builtinPlugins.ts`, `src/utils/plugins/pluginLoader.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent System
|
||||||
|
|
||||||
|
Claude Code can spawn **sub-agents** — each gets its own query loop, forked context, and limited tool set.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#6f42c1', 'primaryBorderColor': '#6f42c1'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph AgentTypes["Agent Types"]
|
||||||
|
SUB["Sub-agent<br/>AgentTool spawns in-process<br/>forked context, own query loop"]:::sub
|
||||||
|
COORD["Coordinator<br/>Leader dispatches tasks<br/>workers get limited tools"]:::coord
|
||||||
|
SWARM["Swarm<br/>Multi-process via tmux<br/>mailbox message passing"]:::swarm
|
||||||
|
FORK["Forked Agent<br/>Share parent prompt cache<br/>overlay filesystem"]:::fork
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SubAgent["Sub-agent Details"]
|
||||||
|
CONTEXT["Forked ToolUseContext<br/>cloned file cache<br/>separate abort controller"]
|
||||||
|
LOOP["Own query loop<br/>independent agentic cycle"]
|
||||||
|
RESULTS["Results flow back<br/>to parent as tool_result"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SwarmDetails["Swarm Details"]
|
||||||
|
TMUX["tmux sessions<br/>separate processes"]
|
||||||
|
MAILBOX["Mailbox system<br/>JSON message passing"]
|
||||||
|
LEADER["Leader process<br/>dispatches and coordinates"]
|
||||||
|
WORKER["Worker processes<br/>limited tool access"]
|
||||||
|
end
|
||||||
|
|
||||||
|
SUB --> SubAgent
|
||||||
|
SWARM --> SwarmDetails
|
||||||
|
|
||||||
|
classDef sub fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef coord fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef swarm fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef fork fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key files:** `src/tools/AgentTool/`, `src/coordinator/coordinatorMode.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
User-defined scripts that run at specific points in the tool execution lifecycle:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#fd7e14', 'primaryBorderColor': '#fd7e14'}}}%%
|
||||||
|
sequenceDiagram
|
||||||
|
participant M as Model
|
||||||
|
participant Q as query.ts
|
||||||
|
participant PRE as PreToolUse Hook
|
||||||
|
participant T as Tool
|
||||||
|
participant POST as PostToolUse Hook
|
||||||
|
|
||||||
|
M->>Q: tool_use block
|
||||||
|
Q->>PRE: Run hook script
|
||||||
|
|
||||||
|
alt Hook approves
|
||||||
|
PRE-->>Q: exit 0
|
||||||
|
Q->>T: Execute tool
|
||||||
|
T-->>Q: result
|
||||||
|
Q->>POST: Run hook script
|
||||||
|
POST-->>Q: done
|
||||||
|
Q->>M: tool_result
|
||||||
|
else Hook denies
|
||||||
|
PRE-->>Q: exit non-zero
|
||||||
|
Q->>M: error: denied by hook
|
||||||
|
else Hook modifies input
|
||||||
|
PRE-->>Q: exit 0 + modified JSON
|
||||||
|
Q->>T: Execute with modified input
|
||||||
|
T-->>Q: result
|
||||||
|
Q->>POST: Run hook script
|
||||||
|
POST-->>Q: done
|
||||||
|
Q->>M: tool_result
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Hooks are configured in `settings.json` with matchers:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{ "matcher": "Bash", "command": "./check-safety.sh" }
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{ "matcher": "FileWrite", "command": "./format-on-save.sh" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [← State Management](./06-state-management.md) · **Next:** [API Client →](./08-api-client.md)
|
||||||
220
learn/08-api-client.md
Normal file
220
learn/08-api-client.md
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
# 8. API Client — `claude.ts`
|
||||||
|
|
||||||
|
> Streaming, retries, caching, and model fallback — how Claude Code talks to the Anthropic API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Request Lifecycle
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'actorTextColor': '#e0e0e0', 'actorBorder': '#28a745', 'signalColor': '#28a745', 'noteBkgColor': '#16213e', 'noteTextColor': '#e0e0e0', 'activationBkgColor': '#1b3a1b', 'activationBorderColor': '#28a745'}}}%%
|
||||||
|
sequenceDiagram
|
||||||
|
participant Q as query.ts
|
||||||
|
participant C as claude.ts
|
||||||
|
participant R as withRetry
|
||||||
|
participant K as AnthropicClient
|
||||||
|
participant A as Anthropic API
|
||||||
|
|
||||||
|
Q->>C: queryModel(messages, tools, options)
|
||||||
|
activate C
|
||||||
|
|
||||||
|
C->>C: resolve model — runtime override, plan-mode swap
|
||||||
|
C->>C: normalize messages — strip internal fields
|
||||||
|
C->>C: build tool schemas — filter by deny, defer via ToolSearch
|
||||||
|
C->>C: configure betas, cache_control, effort, task_budget
|
||||||
|
C->>C: add prompt cache breakpoints
|
||||||
|
C->>C: compute metadata — user_id, session_id, device_id
|
||||||
|
|
||||||
|
C->>R: withRetry(clientFactory, requestFn)
|
||||||
|
activate R
|
||||||
|
|
||||||
|
loop Retry on 429, 529, timeouts
|
||||||
|
R->>K: getAnthropicClient(apiKey, model)
|
||||||
|
K->>A: beta.messages.stream(params)
|
||||||
|
activate A
|
||||||
|
|
||||||
|
alt 200 OK
|
||||||
|
A-->>R: SSE event stream
|
||||||
|
else 429 Rate Limited
|
||||||
|
R->>R: exponential backoff
|
||||||
|
else 529 Overloaded
|
||||||
|
R->>R: backoff + optional model fallback
|
||||||
|
else 401 Auth Error
|
||||||
|
R-->>C: CannotRetryError — abort
|
||||||
|
end
|
||||||
|
deactivate A
|
||||||
|
end
|
||||||
|
|
||||||
|
deactivate R
|
||||||
|
|
||||||
|
C->>C: parse stream into AssistantMessage
|
||||||
|
C->>C: update usage tracking and cost
|
||||||
|
C->>C: detect prompt cache breaks
|
||||||
|
C-->>Q: yield AssistantMessage + StreamEvents
|
||||||
|
deactivate C
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Request Building
|
||||||
|
|
||||||
|
Before each API call, `claude.ts` builds the request through several steps:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph ModelRes["1. Model Resolution"]
|
||||||
|
RUNTIME["Runtime override<br/>from AppState"]
|
||||||
|
PLAN_SWAP["Plan-mode model<br/>swap for 200K+ contexts"]
|
||||||
|
FALLBACK_M["Fallback model<br/>on 529 overload"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph MsgNorm["2. Message Normalization"]
|
||||||
|
STRIP["Strip internal fields<br/>uuid, timestamp, etc."]
|
||||||
|
THINKING["Preserve thinking blocks<br/>within trajectory boundaries"]
|
||||||
|
SIGNS["Strip signature blocks"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph ToolBuild["3. Tool Schema Building"]
|
||||||
|
FILTER_DENY["Filter denied tools"]
|
||||||
|
DEFER["Defer tools via<br/>ToolSearch deferred loading"]
|
||||||
|
EAGER["Eager tools always<br/>in prompt"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Config["4. Request Configuration"]
|
||||||
|
BETAS["Beta features<br/>prompt caching, token counting"]
|
||||||
|
CACHE_CTL["cache_control breakpoints<br/>system prompt caching"]
|
||||||
|
EFFORT_V["effort parameter<br/>controls thinking depth"]
|
||||||
|
TASK_BUD["task_budget<br/>agentic turn spend limit"]
|
||||||
|
METADATA["metadata<br/>user_id, session_id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
ModelRes --> MsgNorm --> ToolBuild --> Config
|
||||||
|
|
||||||
|
API_REQ["POST /v1/messages<br/>SSE stream"]:::api
|
||||||
|
Config --> API_REQ
|
||||||
|
|
||||||
|
classDef api fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Retry Logic — `withRetry`
|
||||||
|
|
||||||
|
The retry wrapper handles transient API failures:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#dc3545', 'primaryBorderColor': '#dc3545'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
REQUEST["API Request"]:::start
|
||||||
|
|
||||||
|
RESPONSE{"Response<br/>status?"}:::check
|
||||||
|
|
||||||
|
OK["200 OK<br/>Stream response"]:::success
|
||||||
|
RATE["429 Rate Limited"]:::error
|
||||||
|
OVER["529 Overloaded"]:::error
|
||||||
|
AUTH["401 Auth Error"]:::fatal
|
||||||
|
TIMEOUT["Timeout"]:::error
|
||||||
|
|
||||||
|
BACKOFF["Exponential backoff<br/>wait and retry"]:::retry
|
||||||
|
FALLBACK_SWITCH["Switch to fallback model<br/>if configured"]:::retry
|
||||||
|
ABORT["CannotRetryError<br/>abort immediately"]:::fatal
|
||||||
|
|
||||||
|
REQUEST --> RESPONSE
|
||||||
|
RESPONSE -->|"200"| OK
|
||||||
|
RESPONSE -->|"429"| RATE --> BACKOFF --> REQUEST
|
||||||
|
RESPONSE -->|"529"| OVER --> FALLBACK_SWITCH --> REQUEST
|
||||||
|
RESPONSE -->|"401"| AUTH --> ABORT
|
||||||
|
RESPONSE -->|"timeout"| TIMEOUT --> BACKOFF
|
||||||
|
|
||||||
|
classDef start fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef success fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef error fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef fatal fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef retry fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Streaming Fallback
|
||||||
|
|
||||||
|
A unique feature: if the model is overloaded mid-stream (529), Claude Code can:
|
||||||
|
1. **Tombstone** the partial assistant messages
|
||||||
|
2. Switch to a fallback model
|
||||||
|
3. Restart the stream from scratch
|
||||||
|
4. The user sees no interruption — orphaned messages are removed from UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt Caching
|
||||||
|
|
||||||
|
Claude Code uses Anthropic's prompt cache to avoid re-processing unchanged context:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
SYS["System prompt<br/>cache_control breakpoint"]:::cached
|
||||||
|
TOOLS["Tool schemas<br/>cache_control breakpoint"]:::cached
|
||||||
|
HISTORY["Conversation history<br/>bytes must match exactly"]:::uncached
|
||||||
|
|
||||||
|
API["API Request"]:::api
|
||||||
|
|
||||||
|
HIT["Cache HIT<br/>~90% cheaper<br/>~10x faster"]:::hit
|
||||||
|
MISS["Cache MISS<br/>full processing<br/>new cache created"]:::miss
|
||||||
|
|
||||||
|
SYS --> API
|
||||||
|
TOOLS --> API
|
||||||
|
HISTORY --> API
|
||||||
|
|
||||||
|
API --> HIT
|
||||||
|
API --> MISS
|
||||||
|
|
||||||
|
classDef cached fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef uncached fill:#333,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
classDef api fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef hit fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef miss fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
Cache breaks are detected and logged. The `backfillObservableInput()` pattern exists specifically to avoid breaking the cache — the original API-bound input is never mutated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SSE Stream Events
|
||||||
|
|
||||||
|
The API returns Server-Sent Events in this order:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
sequenceDiagram
|
||||||
|
participant API as Anthropic API
|
||||||
|
participant C as claude.ts
|
||||||
|
|
||||||
|
API->>C: message_start — model, usage, id
|
||||||
|
|
||||||
|
loop For each content block
|
||||||
|
API->>C: content_block_start — type, index
|
||||||
|
loop Delta events
|
||||||
|
API->>C: content_block_delta — text / thinking / tool_use JSON
|
||||||
|
end
|
||||||
|
API->>C: content_block_stop
|
||||||
|
end
|
||||||
|
|
||||||
|
API->>C: message_delta — stop_reason, final usage
|
||||||
|
API->>C: message_stop
|
||||||
|
|
||||||
|
Note over C: Parse into AssistantMessage<br/>Track usage + cost<br/>Yield to query.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cost Tracking
|
||||||
|
|
||||||
|
Every API call's usage is tracked in `cost-tracker.ts`:
|
||||||
|
- Input tokens (including cache reads/writes)
|
||||||
|
- Output tokens
|
||||||
|
- Per-model pricing
|
||||||
|
- Session totals exposed via `/cost` command
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [← Extension Model](./07-extension-model.md) · **Next:** [UI Architecture →](./09-ui-architecture.md)
|
||||||
109
learn/09-ui-architecture.md
Normal file
109
learn/09-ui-architecture.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# 9. UI Architecture
|
||||||
|
|
||||||
|
> Building a complex, interactive terminal UI with React and Ink.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Claude Code uses React (via [Ink](https://github.com/vadimdemedes/ink)) to render its terminal interface. This allows it to use React's declarative component model, hooks, and state management in a CLI context.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
graph TB
|
||||||
|
CLI["main.tsx<br/>Parses args, boots React"]:::entry
|
||||||
|
INK["Ink rendered<br/>Terminal output"]:::external
|
||||||
|
|
||||||
|
subgraph ReactApp["React Application"]
|
||||||
|
APP["App.tsx<br/>Providers and Routing"]
|
||||||
|
REPL["REPL.tsx (896KB)<br/>Main Interface"]
|
||||||
|
|
||||||
|
subgraph Components["113 UI Components"]
|
||||||
|
MESSAGES["Message Rendering<br/>User, Assistant, Tool"]
|
||||||
|
DIALOGS["Permission Dialogs<br/>Y/n Prompts"]
|
||||||
|
INPUT["Input Area<br/>Text, Voice, Paste"]
|
||||||
|
INDICATORS["Spinners & Progress Bars"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Hooks["83 Custom Hooks"]
|
||||||
|
USE_STATE["useAppState"]
|
||||||
|
USE_TOOL["useCanUseTool"]
|
||||||
|
USE_INPUT["useInput"]
|
||||||
|
USE_VOICE["useVoice"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CLI --> APP
|
||||||
|
APP --> REPL
|
||||||
|
REPL --> Components
|
||||||
|
REPL --> Hooks
|
||||||
|
Components --> INK
|
||||||
|
|
||||||
|
classDef entry fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef external fill:#333,stroke:#888,color:#aaa,stroke-width:1px,stroke-dasharray: 5 5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The REPL
|
||||||
|
|
||||||
|
The core of the interface is `REPL.tsx` (896KB). It manages the main interaction loop from the UI perspective, orchestrating input, rendering the conversation history, and handling background tasks.
|
||||||
|
|
||||||
|
### Key Responsibilities
|
||||||
|
|
||||||
|
1. **Virtual Scrolling**: The terminal can't hold infinite text. The REPL implements virtual scrolling, only rendering the messages that fit in the viewport plus a small buffer, while maintaining the illusion of a continuous scrollback.
|
||||||
|
2. **Input Handling**: Manages the text input area, handling multiline input, pasting files/images, keyboard shortcuts (including vim mode), and voice input.
|
||||||
|
3. **Task Foregrounding**: Only one agent task can write to the terminal at a time. The REPL manages which task is "foregrounded" and visible.
|
||||||
|
4. **State Synchronization**: Subscribes to the single immutable `AppState` store to trigger re-renders when data changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Architecture
|
||||||
|
|
||||||
|
Claude Code breaks down the UI into 113 specialized components.
|
||||||
|
|
||||||
|
### Message Rendering
|
||||||
|
|
||||||
|
Each message type has a dedicated component:
|
||||||
|
|
||||||
|
- `UserMessage`: Renders user input.
|
||||||
|
- `AssistantMessage`: Renders markdown text from the model, handling streaming updates gracefully.
|
||||||
|
- `ToolUseMessage`: Displays the execution in progress (e.g., a spinner and the command being run).
|
||||||
|
- `ToolResultMessage`: Shows the outcome, often truncating long outputs and providing "Show More" functionality.
|
||||||
|
|
||||||
|
### Layout and Styling
|
||||||
|
|
||||||
|
Ink uses Yoga (a Flexbox engine) for layout. Components are composed using Flexbox principles, allowing for responsive terminal designs that adapt to the window size.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State and Hooks
|
||||||
|
|
||||||
|
The UI is deeply integrated with the `AppState` store.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
STORE["AppState<br/>Immutable Store"]:::state
|
||||||
|
|
||||||
|
REPL["REPL Component"]:::ui
|
||||||
|
COMP["Child Components"]:::ui
|
||||||
|
|
||||||
|
HOOKS["Selectors<br/>useAppState(selector)"]:::hook
|
||||||
|
|
||||||
|
STORE --> HOOKS
|
||||||
|
HOOKS --> REPL
|
||||||
|
HOOKS --> COMP
|
||||||
|
|
||||||
|
classDef state fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef hook fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ui fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom hooks encapsulate complex logic:
|
||||||
|
- `useInput`: Handles raw stdin data, necessary for keyboard shortcuts that bypass normal text input.
|
||||||
|
- `useCanUseTool`: Connects a UI prompt to the permission system, pausing the engine until the user responds.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [<- API Client](./08-api-client.md) | **Next:** [Configuration and System Prompt ->](./10-configuration-and-system-prompt.md)
|
||||||
253
learn/10-configuration-and-system-prompt.md
Normal file
253
learn/10-configuration-and-system-prompt.md
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
# 10. Configuration and System Prompt
|
||||||
|
|
||||||
|
> How settings cascade from enterprise policy down to project CLAUDE.md, and how the system prompt is assembled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Hierarchy
|
||||||
|
|
||||||
|
Claude Code loads settings from **5 layers**, each with strict priority. Higher layers override lower ones.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Sources["Configuration Sources — Highest to Lowest Priority"]
|
||||||
|
direction TB
|
||||||
|
ENT["Enterprise / MDM Policy<br/>managed-settings.json<br/>Org-level, cannot override"]:::ent
|
||||||
|
USER["User Settings<br/>~/.claude/settings.json<br/>Per-user global defaults"]:::user
|
||||||
|
PROJ["Project Settings<br/>.claude/settings.json<br/>Per-repo configuration"]:::proj
|
||||||
|
MCP_CFG[".mcp.json<br/>Project MCP servers<br/>Committed to repo"]:::proj
|
||||||
|
CLAUDE_MD["CLAUDE.md files<br/>Instructions, rules,<br/>memory for the model"]:::md
|
||||||
|
end
|
||||||
|
|
||||||
|
ENT --> MERGE
|
||||||
|
USER --> MERGE
|
||||||
|
PROJ --> MERGE
|
||||||
|
MCP_CFG --> MERGE
|
||||||
|
CLAUDE_MD --> MERGE
|
||||||
|
|
||||||
|
MERGE["getInitialSettings<br/>Merge all layers<br/>Higher priority wins"]:::merge
|
||||||
|
|
||||||
|
APPSTATE["AppState.settings<br/>Runtime configuration"]:::state
|
||||||
|
|
||||||
|
MERGE --> APPSTATE
|
||||||
|
|
||||||
|
classDef ent fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef user fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef proj fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef md fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef merge fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef state fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enterprise / MDM Policy
|
||||||
|
|
||||||
|
Highest priority. Set by organization admins via Mobile Device Management (macOS profiles). Stored at a managed file path. Users **cannot** override these settings. Controls things like:
|
||||||
|
- Allowed/denied MCP servers
|
||||||
|
- Permission mode restrictions
|
||||||
|
- Feature availability
|
||||||
|
- Analytics opt-out
|
||||||
|
|
||||||
|
### User Settings (`~/.claude/settings.json`)
|
||||||
|
|
||||||
|
Per-user defaults. Controls preferences like allowed tools, custom deny rules, and hooks.
|
||||||
|
|
||||||
|
### Project Settings (`.claude/settings.json`)
|
||||||
|
|
||||||
|
Per-repository settings. Committed to the repo so all collaborators share the same configuration.
|
||||||
|
|
||||||
|
### `.mcp.json`
|
||||||
|
|
||||||
|
MCP server definitions for the project. Lives at the repo root. Defines which MCP servers are available. Validated via Zod schema (`McpServerConfigSchema`).
|
||||||
|
|
||||||
|
### CLAUDE.md
|
||||||
|
|
||||||
|
Markdown instruction files that the model reads as part of its system prompt. These are the "memory" files that tell Claude about this specific project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CLAUDE.md Discovery
|
||||||
|
|
||||||
|
CLAUDE.md files are discovered from multiple locations and merged:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#6f42c1', 'primaryBorderColor': '#6f42c1'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Discovery["CLAUDE.md Discovery — getMemoryFiles"]
|
||||||
|
GLOBAL["~/.claude/CLAUDE.md<br/>Global instructions"]:::global
|
||||||
|
CWD_WALK["Walk from cwd up to git root<br/>Check each dir for CLAUDE.md<br/>and .claude/CLAUDE.md"]:::walk
|
||||||
|
ADDITIONAL["--add-dir paths<br/>Extra directories to scan"]:::additional
|
||||||
|
end
|
||||||
|
|
||||||
|
FILTER["filterInjectedMemoryFiles<br/>Remove duplicates,<br/>check setting sources"]:::filter
|
||||||
|
|
||||||
|
PARSE["getClaudeMds<br/>Read and concatenate<br/>all discovered files"]:::parse
|
||||||
|
|
||||||
|
CACHE["setCachedClaudeMdContent<br/>Cache for auto-mode<br/>classifier to read"]:::cache
|
||||||
|
|
||||||
|
INJECT["Injected into getUserContext<br/>becomes part of system prompt"]:::inject
|
||||||
|
|
||||||
|
Discovery --> FILTER --> PARSE --> CACHE --> INJECT
|
||||||
|
|
||||||
|
classDef global fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef walk fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef additional fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef filter fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef parse fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef cache fill:#333,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
classDef inject fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--bare` flag skips auto-discovery but still honors explicit `--add-dir` paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System Prompt Assembly
|
||||||
|
|
||||||
|
The system prompt is what the model "sees" before any conversation messages. It's assembled from multiple pieces:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#e83e8c', 'primaryBorderColor': '#e83e8c'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Priority["System Prompt Priority — buildEffectiveSystemPrompt"]
|
||||||
|
OVERRIDE["1. Override System Prompt<br/>Set via loop mode<br/>REPLACES everything"]:::override
|
||||||
|
COORD["2. Coordinator System Prompt<br/>If coordinator mode active"]:::coord
|
||||||
|
AGENT["3. Agent System Prompt<br/>If mainThreadAgentDefinition set<br/>In proactive mode: APPENDED<br/>Otherwise: REPLACES default"]:::agent
|
||||||
|
CUSTOM["4. Custom System Prompt<br/>Via --system-prompt flag"]:::custom
|
||||||
|
DEFAULT["5. Default System Prompt<br/>The standard Claude Code prompt"]:::default
|
||||||
|
end
|
||||||
|
|
||||||
|
APPEND["appendSystemPrompt<br/>Always appended at end<br/>except when override is set"]:::append
|
||||||
|
|
||||||
|
EFFECTIVE["Effective System Prompt<br/>Sent as system param<br/>in API request"]:::result
|
||||||
|
|
||||||
|
OVERRIDE -->|"if set"| EFFECTIVE
|
||||||
|
COORD -->|"if coordinator"| EFFECTIVE
|
||||||
|
AGENT -->|"if agent defined"| EFFECTIVE
|
||||||
|
CUSTOM -->|"if --system-prompt"| EFFECTIVE
|
||||||
|
DEFAULT -->|"fallback"| EFFECTIVE
|
||||||
|
APPEND --> EFFECTIVE
|
||||||
|
|
||||||
|
classDef override fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef coord fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef agent fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef custom fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef default fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef append fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef result fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Prompt Sections
|
||||||
|
|
||||||
|
Individual sections of the system prompt are defined via `systemPromptSection()`:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
subgraph Sections["System Prompt Sections"]
|
||||||
|
CACHED["Cached Sections<br/>Computed once at start<br/>Reused every turn<br/>Cleared on /clear or /compact"]:::cached
|
||||||
|
VOLATILE["DANGEROUS Uncached Sections<br/>Recomputed every turn<br/>BREAKS prompt cache"]:::volatile
|
||||||
|
end
|
||||||
|
|
||||||
|
RESOLVE["resolveSystemPromptSections<br/>Check cache, compute if needed"]:::resolve
|
||||||
|
|
||||||
|
PROMPT["Final system prompt string"]:::result
|
||||||
|
|
||||||
|
CACHED --> RESOLVE
|
||||||
|
VOLATILE --> RESOLVE
|
||||||
|
RESOLVE --> PROMPT
|
||||||
|
|
||||||
|
classDef cached fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef volatile fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef resolve fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef result fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
Most sections are **cached** (computed once, reused every turn) to preserve prompt cache hits. Volatile sections that recompute every turn are explicitly named `DANGEROUS_uncachedSystemPromptSection` as a warning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context Injection
|
||||||
|
|
||||||
|
Beyond the system prompt, two context objects are injected into every conversation:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
subgraph SystemCtx["getSystemContext — memoized"]
|
||||||
|
GIT["Git status snapshot<br/>branch, status, log,<br/>default branch, user"]
|
||||||
|
INJECT_SYS["System prompt injection<br/>internal debugging only"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph UserCtx["getUserContext — memoized"]
|
||||||
|
CMD["CLAUDE.md content<br/>all discovered files<br/>concatenated"]
|
||||||
|
DATE["Current date<br/>ISO format"]
|
||||||
|
end
|
||||||
|
|
||||||
|
PREPEND["prependUserContext<br/>Prepended to messages<br/>before API call"]:::merge
|
||||||
|
|
||||||
|
SystemCtx --> PREPEND
|
||||||
|
UserCtx --> PREPEND
|
||||||
|
|
||||||
|
classDef merge fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
Both are **memoized** and cached for the duration of the conversation. They're cleared on `/clear` and `/compact`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Markdown Config Discovery
|
||||||
|
|
||||||
|
Commands, agents, skills, and workflows are all loaded via `markdownConfigLoader.ts`:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#fd7e14', 'primaryBorderColor': '#fd7e14'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Dirs["Search Directories — Priority Order"]
|
||||||
|
MANAGED["Managed (enterprise)<br/>Policy-level configs"]:::ent
|
||||||
|
USER_DIR["User (~/.claude/)<br/>Personal configs"]:::user
|
||||||
|
PROJ_DIRS["Project (.claude/)<br/>Walk cwd up to git root<br/>Check each ancestor"]:::proj
|
||||||
|
end
|
||||||
|
|
||||||
|
LOADER["loadMarkdownFilesForSubdir<br/>Discover + parse YAML frontmatter<br/>+ markdown content"]:::loader
|
||||||
|
|
||||||
|
DEDUP["Deduplicate by inode<br/>Handle symlinks gracefully"]:::dedup
|
||||||
|
|
||||||
|
FILES["MarkdownFile array<br/>filePath, frontmatter,<br/>content, source"]:::result
|
||||||
|
|
||||||
|
MANAGED --> LOADER
|
||||||
|
USER_DIR --> LOADER
|
||||||
|
PROJ_DIRS --> LOADER
|
||||||
|
LOADER --> DEDUP --> FILES
|
||||||
|
|
||||||
|
classDef ent fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef user fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef proj fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef loader fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef dedup fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef result fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
This same loader powers:
|
||||||
|
- Slash commands (`.claude/commands/`)
|
||||||
|
- Agents (`.claude/agents/`)
|
||||||
|
- Skills (`.claude/skills/`)
|
||||||
|
- Workflows (`.claude/workflows/`)
|
||||||
|
- Output styles (`.claude/output-styles/`)
|
||||||
|
|
||||||
|
Each entry is parsed with YAML frontmatter for metadata (description, tools, etc.) and the markdown body becomes the instructions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `src/utils/systemPrompt.ts` — `buildEffectiveSystemPrompt()` logic
|
||||||
|
- `src/constants/systemPromptSections.ts` — Section caching system
|
||||||
|
- `src/context.ts` — `getSystemContext()`, `getUserContext()`, git status
|
||||||
|
- `src/utils/markdownConfigLoader.ts` — CLAUDE.md and `.claude/` directory discovery
|
||||||
|
- `src/utils/claudemd.ts` — Memory file reading and concatenation
|
||||||
|
- `src/utils/settings/settings.ts` — Settings merge logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [<- UI Architecture](./09-ui-architecture.md) | **Next:** [MCP Deep Dive ->](./11-mcp-deep-dive.md)
|
||||||
290
learn/11-mcp-deep-dive.md
Normal file
290
learn/11-mcp-deep-dive.md
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
# 11. MCP Deep Dive
|
||||||
|
|
||||||
|
> The Model Context Protocol subsystem -- transports, tool wrapping, authentication, and server lifecycle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is MCP?
|
||||||
|
|
||||||
|
MCP (Model Context Protocol) is an open protocol for connecting AI models to external tools and data sources. Claude Code is both an **MCP client** (connecting to external MCP servers) and can act as an **MCP server** (exposing its own tools to other systems).
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
graph TB
|
||||||
|
subgraph ClaudeCode["Claude Code"]
|
||||||
|
CLIENT["MCP Client<br/>Connects to external servers"]:::client
|
||||||
|
SERVER["MCP Server Mode<br/>Exposes tools to other systems"]:::server
|
||||||
|
TOOLS["42 Built-in Tools<br/>+ MCP tools merged in"]:::tools
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph External["External MCP Servers"]
|
||||||
|
STDIO_S["stdio servers<br/>Local processes"]:::external
|
||||||
|
SSE_S["SSE/HTTP servers<br/>Remote endpoints"]:::external
|
||||||
|
WS_S["WebSocket servers<br/>Persistent connections"]:::external
|
||||||
|
CLAUDE_AI["claude.ai proxy<br/>Managed connectors"]:::external
|
||||||
|
IDE_S["IDE servers<br/>Editor integration"]:::external
|
||||||
|
end
|
||||||
|
|
||||||
|
CLIENT --> STDIO_S
|
||||||
|
CLIENT --> SSE_S
|
||||||
|
CLIENT --> WS_S
|
||||||
|
CLIENT --> CLAUDE_AI
|
||||||
|
CLIENT --> IDE_S
|
||||||
|
|
||||||
|
STDIO_S --> TOOLS
|
||||||
|
SSE_S --> TOOLS
|
||||||
|
WS_S --> TOOLS
|
||||||
|
CLAUDE_AI --> TOOLS
|
||||||
|
IDE_S --> TOOLS
|
||||||
|
|
||||||
|
classDef client fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef server fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef tools fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef external fill:#333,stroke:#888,color:#aaa,stroke-width:1px,stroke-dasharray: 5 5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Transport Types
|
||||||
|
|
||||||
|
Claude Code supports **6 transport types** for connecting to MCP servers:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Local["Local Transports"]
|
||||||
|
STDIO["stdio<br/>Spawn local process<br/>Communicate via stdin/stdout<br/>Most common type"]:::local
|
||||||
|
SDK_T["sdk<br/>In-process transport<br/>SDK-managed placeholder<br/>Tool calls route to SDK"]:::local
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Remote["Remote Transports"]
|
||||||
|
SSE["sse<br/>Server-Sent Events<br/>Legacy remote protocol<br/>OAuth authentication"]:::remote
|
||||||
|
HTTP["http<br/>Streamable HTTP<br/>Modern remote protocol<br/>Per-request timeouts"]:::remote
|
||||||
|
WS["ws<br/>WebSocket<br/>Persistent bidirectional<br/>TLS/proxy support"]:::remote
|
||||||
|
CLAUDEAI["claudeai-proxy<br/>Via claude.ai OAuth<br/>Managed connectors<br/>Auto token refresh"]:::remote
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph IDE["IDE Transports"]
|
||||||
|
SSE_IDE["sse-ide<br/>IDE SSE connection<br/>No authentication"]:::ide
|
||||||
|
WS_IDE["ws-ide<br/>IDE WebSocket<br/>Auth token in header"]:::ide
|
||||||
|
end
|
||||||
|
|
||||||
|
classDef local fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef remote fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ide fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connection Lifecycle
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'actorTextColor': '#e0e0e0', 'actorBorder': '#28a745', 'signalColor': '#28a745', 'noteBkgColor': '#16213e', 'noteTextColor': '#e0e0e0', 'activationBkgColor': '#1b3a1b', 'activationBorderColor': '#28a745'}}}%%
|
||||||
|
sequenceDiagram
|
||||||
|
participant H as useManageMCPConnections
|
||||||
|
participant C as connectToServer (memoized)
|
||||||
|
participant T as Transport Layer
|
||||||
|
participant S as External Server
|
||||||
|
|
||||||
|
H->>H: getAllMcpConfigs - merge all sources
|
||||||
|
H->>H: Batch servers (local=3, remote=20)
|
||||||
|
|
||||||
|
loop For each server
|
||||||
|
H->>C: connectToServer(name, config)
|
||||||
|
activate C
|
||||||
|
|
||||||
|
alt Auth cached as needed
|
||||||
|
C-->>H: return needs-auth
|
||||||
|
else Server disabled
|
||||||
|
C-->>H: return disabled
|
||||||
|
else Policy blocked
|
||||||
|
C-->>H: return not allowed
|
||||||
|
end
|
||||||
|
|
||||||
|
C->>T: Create transport (stdio/sse/http/ws)
|
||||||
|
T->>S: Connect
|
||||||
|
activate S
|
||||||
|
|
||||||
|
alt auth required (401/403)
|
||||||
|
S-->>C: Auth error
|
||||||
|
C-->>H: return needs-auth
|
||||||
|
else connection timeout
|
||||||
|
S-->>C: Timeout (30s default)
|
||||||
|
C-->>H: return error
|
||||||
|
else success
|
||||||
|
S-->>T: Connected
|
||||||
|
deactivate S
|
||||||
|
C->>S: tools/list
|
||||||
|
S-->>C: Available tools
|
||||||
|
C->>C: Wrap as MCPTool objects
|
||||||
|
C-->>H: return connected + tools
|
||||||
|
end
|
||||||
|
deactivate C
|
||||||
|
end
|
||||||
|
|
||||||
|
H->>H: Update AppState.mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batched Connections
|
||||||
|
|
||||||
|
Servers are connected in parallel batches to avoid overwhelming the system:
|
||||||
|
- **Local (stdio) servers**: batch size of 3 (spawning processes is heavy)
|
||||||
|
- **Remote servers**: batch size of 20 (network connections are lighter)
|
||||||
|
|
||||||
|
The batch sizes are configurable via `MCP_SERVER_CONNECTION_BATCH_SIZE` and `MCP_REMOTE_SERVER_CONNECTION_BATCH_SIZE` environment variables.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tool Wrapping
|
||||||
|
|
||||||
|
Each MCP tool is wrapped in a `MCPTool` object that implements the standard `Tool` interface:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#ffc107', 'primaryBorderColor': '#ffc107'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
MCP_TOOL["MCP Server Tool<br/>name, inputSchema,<br/>description"]:::raw
|
||||||
|
|
||||||
|
WRAP["MCPTool wrapper<br/>Implements Tool interface<br/>name: mcp__server__tool"]:::wrap
|
||||||
|
|
||||||
|
subgraph Lifecycle["Tool Call Lifecycle"]
|
||||||
|
VALIDATE["Validate input<br/>via JSON Schema"]:::step
|
||||||
|
PERM["Check permissions<br/>Same system as built-in"]:::step
|
||||||
|
CALL["Forward to MCP server<br/>tools/call RPC"]:::step
|
||||||
|
RESULT["Process result<br/>Truncate if needed<br/>Handle images"]:::step
|
||||||
|
end
|
||||||
|
|
||||||
|
MCP_TOOL --> WRAP --> VALIDATE --> PERM --> CALL --> RESULT
|
||||||
|
|
||||||
|
classDef raw fill:#333,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
classDef wrap fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef step fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Naming Convention
|
||||||
|
|
||||||
|
MCP tools are named `mcp__<server>__<tool>`:
|
||||||
|
- `mcp__slack__send_message`
|
||||||
|
- `mcp__github__create_issue`
|
||||||
|
- `mcp__ide__getDiagnostics`
|
||||||
|
|
||||||
|
The double-underscore separators prevent ambiguity. In SDK no-prefix mode, tools keep their original names.
|
||||||
|
|
||||||
|
### Description Capping
|
||||||
|
|
||||||
|
MCP tool descriptions are capped at **2,048 characters**. OpenAPI-generated servers have been observed dumping 15-60KB of endpoint docs into descriptions, which wastes context tokens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Sources
|
||||||
|
|
||||||
|
MCP server definitions come from multiple sources, merged with priority rules:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#dc3545', 'primaryBorderColor': '#dc3545'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph Sources["MCP Config Sources"]
|
||||||
|
ENT["Enterprise managed-mcp.json<br/>Exclusive control if present"]:::ent
|
||||||
|
GLOBAL["Global ~/.claude/settings.json<br/>mcpServers field"]:::user
|
||||||
|
LOCAL[".claude/settings.local.json<br/>Local project overrides"]:::local
|
||||||
|
PROJECT[".mcp.json at project root<br/>Committed to repo"]:::proj
|
||||||
|
PLUGIN["Plugin MCP servers<br/>From installed plugins"]:::plugin
|
||||||
|
CLAUDEAI["claude.ai connectors<br/>Fetched at startup"]:::claudeai
|
||||||
|
CLI["--mcp-config flag<br/>Runtime override"]:::cli
|
||||||
|
SDK_CFG["SDK mcp_set_servers<br/>Programmatic control"]:::sdk
|
||||||
|
end
|
||||||
|
|
||||||
|
MERGE["getAllMcpConfigs<br/>Merge + dedup + policy filter"]:::merge
|
||||||
|
|
||||||
|
POLICY{"Enterprise policy<br/>allowedMcpServers?<br/>deniedMcpServers?"}:::check
|
||||||
|
|
||||||
|
ALLOWED["Allowed servers<br/>proceed to connect"]:::allow
|
||||||
|
BLOCKED["Blocked servers<br/>silently dropped"]:::deny
|
||||||
|
|
||||||
|
Sources --> MERGE --> POLICY
|
||||||
|
POLICY -->|"allowed"| ALLOWED
|
||||||
|
POLICY -->|"denied"| BLOCKED
|
||||||
|
|
||||||
|
classDef ent fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef user fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef local fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef proj fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef plugin fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef claudeai fill:#1a1a4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef cli fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef sdk fill:#333,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
classDef merge fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef allow fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef deny fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deduplication
|
||||||
|
|
||||||
|
Plugin and claude.ai connector servers are deduplicated against manually-configured servers:
|
||||||
|
- **Signature-based**: Same `command+args` (stdio) or same `url` (remote) = same server
|
||||||
|
- **Manual wins**: If a user manually configured a server, the plugin/connector duplicate is suppressed
|
||||||
|
- **First-loaded wins**: Between plugins, the first one loaded takes priority
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Remote MCP servers may require OAuth authentication:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#dc3545', 'primaryBorderColor': '#dc3545'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
CONNECT["Connect to remote server"]:::start
|
||||||
|
|
||||||
|
AUTH{"Auth required?"}:::check
|
||||||
|
|
||||||
|
PROVIDER["ClaudeAuthProvider<br/>OAuth flow per server"]:::auth
|
||||||
|
TOKENS["Retrieve/refresh tokens"]:::auth
|
||||||
|
|
||||||
|
OK["Connection established"]:::success
|
||||||
|
NEEDS["Status: needs-auth<br/>Cached for 15 min"]:::needsauth
|
||||||
|
|
||||||
|
CALL["MCP tool call"]:::start
|
||||||
|
CALL_AUTH{"401 on call?"}:::check
|
||||||
|
|
||||||
|
RETRY["Refresh token<br/>retry once"]:::auth
|
||||||
|
MCE["McpAuthError<br/>Update status to needs-auth"]:::error
|
||||||
|
|
||||||
|
CONNECT --> AUTH
|
||||||
|
AUTH -->|"no"| OK
|
||||||
|
AUTH -->|"yes"| PROVIDER --> TOKENS
|
||||||
|
TOKENS -->|"success"| OK
|
||||||
|
TOKENS -->|"failure"| NEEDS
|
||||||
|
|
||||||
|
CALL --> CALL_AUTH
|
||||||
|
CALL_AUTH -->|"no"| OK
|
||||||
|
CALL_AUTH -->|"yes"| RETRY
|
||||||
|
RETRY -->|"success"| OK
|
||||||
|
RETRY -->|"failure"| MCE
|
||||||
|
|
||||||
|
classDef start fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef auth fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef success fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef needsauth fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef error fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session Expiry Handling
|
||||||
|
|
||||||
|
MCP servers return HTTP 404 + JSON-RPC error code `-32001` when a session expires. Claude Code detects this, clears the connection cache, and reconnects automatically (`McpSessionExpiredError`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `src/services/mcp/client.ts` (3,349 lines) -- Connection manager, tool wrapping, transport creation
|
||||||
|
- `src/services/mcp/config.ts` (1,579 lines) -- Config merging, policy filtering, deduplication
|
||||||
|
- `src/services/mcp/types.ts` -- Type definitions for server configs and connections
|
||||||
|
- `src/services/mcp/auth.ts` -- OAuth provider and step-up authentication
|
||||||
|
- `src/services/mcp/useManageMCPConnections.ts` -- React hook managing connection lifecycle
|
||||||
|
- `src/tools/MCPTool/MCPTool.ts` -- MCPTool wrapper implementing the Tool interface
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [<- Configuration](./10-configuration-and-system-prompt.md) | **Next:** [Data Flow Walkthrough ->](./12-data-flow-walkthrough.md)
|
||||||
341
learn/12-data-flow-walkthrough.md
Normal file
341
learn/12-data-flow-walkthrough.md
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
# 12. Data Flow Walkthrough
|
||||||
|
|
||||||
|
> Tracing a single user request from keystroke to rendered response -- end to end.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Complete Flow
|
||||||
|
|
||||||
|
This diagram traces exactly what happens when a user types a prompt and presses Enter.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'primaryBorderColor': '#4a9eff'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
TYPE["User types in terminal"]:::input
|
||||||
|
PASTE["Paste: text, image, file"]:::input
|
||||||
|
VOICE["Voice input via STT"]:::input
|
||||||
|
IDE_IN["IDE sends prompt"]:::input
|
||||||
|
SDK_IN["SDK submitMessage"]:::input
|
||||||
|
|
||||||
|
PARSE["Parse input<br/>slash commands, @mentions"]:::process
|
||||||
|
SLASH{"Slash<br/>command?"}:::decision
|
||||||
|
LOCAL["Execute locally<br/>clear, compact, model"]:::process
|
||||||
|
ATTACH["Build attachments<br/>images, PDFs, CLAUDE.md"]:::process
|
||||||
|
UMSG["Create UserMessage"]:::process
|
||||||
|
|
||||||
|
SYS["Build system prompt<br/>tool descriptions +<br/>user rules + CLAUDE.md"]:::context
|
||||||
|
GIT["Inject git context<br/>branch, commits, status"]:::context
|
||||||
|
USR["Inject user context<br/>CLAUDE.md, date"]:::context
|
||||||
|
CMP["Run compaction pipeline"]:::context
|
||||||
|
CACHE["Apply cache_control<br/>prompt cache breakpoints"]:::context
|
||||||
|
|
||||||
|
BUILD["Build API request<br/>model, betas, effort,<br/>task_budget, thinking"]:::api
|
||||||
|
STREAM["Stream SSE response<br/>from Anthropic API"]:::api
|
||||||
|
RETRY["withRetry wrapper<br/>429 backoff, 529 fallback"]:::api
|
||||||
|
|
||||||
|
THINK["Thinking blocks"]:::response
|
||||||
|
TEXT["Text blocks"]:::response
|
||||||
|
TOOL_USE["tool_use blocks"]:::response
|
||||||
|
|
||||||
|
VALIDATE["Validate input"]:::toolexec
|
||||||
|
PERMS["Check permissions"]:::toolexec
|
||||||
|
EXEC["Execute tool"]:::toolexec
|
||||||
|
RESULT["Map to tool_result"]:::toolexec
|
||||||
|
FEEDBACK["Push to messages<br/>LOOP BACK"]:::feedback
|
||||||
|
|
||||||
|
RENDER["Render in terminal"]:::output
|
||||||
|
TRANSCRIPT["Persist transcript"]:::output
|
||||||
|
SDK_OUT["Yield SDK events"]:::output
|
||||||
|
COST["Track cost + usage"]:::output
|
||||||
|
|
||||||
|
TYPE --> PARSE
|
||||||
|
PASTE --> PARSE
|
||||||
|
VOICE --> PARSE
|
||||||
|
IDE_IN --> PARSE
|
||||||
|
SDK_IN --> PARSE
|
||||||
|
|
||||||
|
PARSE --> SLASH
|
||||||
|
SLASH -->|"yes"| LOCAL --> RENDER
|
||||||
|
SLASH -->|"no"| ATTACH --> UMSG
|
||||||
|
|
||||||
|
UMSG --> SYS --> GIT --> USR --> CMP --> CACHE
|
||||||
|
|
||||||
|
CACHE --> BUILD --> STREAM
|
||||||
|
STREAM --> RETRY --> STREAM
|
||||||
|
|
||||||
|
STREAM --> THINK --> RENDER
|
||||||
|
STREAM --> TEXT --> RENDER
|
||||||
|
STREAM --> TOOL_USE
|
||||||
|
|
||||||
|
TOOL_USE --> VALIDATE --> PERMS --> EXEC --> RESULT --> FEEDBACK
|
||||||
|
FEEDBACK ==>|"loop back to<br/>compaction"| CMP
|
||||||
|
|
||||||
|
TEXT -->|"end_turn"| TRANSCRIPT --> SDK_OUT --> COST
|
||||||
|
|
||||||
|
classDef input fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef process fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef decision fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef context fill:#2d1b4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef api fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef response fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef toolexec fill:#1a1a4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef feedback fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:3px
|
||||||
|
classDef output fill:#333,stroke:#888,color:#e0e0e0,stroke-width:1px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase by Phase
|
||||||
|
|
||||||
|
### Phase 1: Input Capture
|
||||||
|
|
||||||
|
The user's input can arrive from **5 different sources**, all converging into `processUserInput()`:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#17a2b8', 'primaryBorderColor': '#17a2b8'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
subgraph Sources["Input Sources"]
|
||||||
|
KB["Keyboard input<br/>Direct typing in terminal"]
|
||||||
|
PASTE["Clipboard paste<br/>Text, images, files"]
|
||||||
|
VOICE["Voice<br/>STT transcription"]
|
||||||
|
IDE["IDE Bridge<br/>Prompt from editor"]
|
||||||
|
SDK["SDK<br/>Programmatic submitMessage"]
|
||||||
|
end
|
||||||
|
|
||||||
|
PUI["processUserInput<br/>Parse slash commands<br/>Resolve @mentions<br/>Build attachments"]:::process
|
||||||
|
|
||||||
|
subgraph Output["Parse Result"]
|
||||||
|
MSGS["messages array"]
|
||||||
|
SHOULD["shouldQuery boolean"]
|
||||||
|
end
|
||||||
|
|
||||||
|
Sources --> PUI --> Output
|
||||||
|
|
||||||
|
classDef process fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
Slash commands (`/clear`, `/compact`, `/model`) are intercepted here and handled locally without an API call. Everything else proceeds to the model.
|
||||||
|
|
||||||
|
### Phase 2: Context Assembly
|
||||||
|
|
||||||
|
Before sending to the API, the system assembles context from multiple sources:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#6f42c1', 'primaryBorderColor': '#6f42c1'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
subgraph SystemPrompt["System Prompt Assembly"]
|
||||||
|
DEFAULT["Default prompt<br/>Claude Code identity"]
|
||||||
|
TOOL_DESC["Tool descriptions<br/>42+ tool schemas"]
|
||||||
|
SECTIONS["System prompt sections<br/>Cached or volatile"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph UserContext["User Context"]
|
||||||
|
CLAUDE_MD["CLAUDE.md files<br/>Project instructions"]
|
||||||
|
DATE["Current date"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SystemContext["System Context"]
|
||||||
|
GIT_STATUS["Git snapshot<br/>Branch, status, log"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Compaction["Compaction Pipeline"]
|
||||||
|
SNIP["Snip compact"]
|
||||||
|
MICRO["Micro compact"]
|
||||||
|
AUTO["Auto compact"]
|
||||||
|
COLLAPSE["Context collapse"]
|
||||||
|
end
|
||||||
|
|
||||||
|
SystemPrompt --> FINAL["Final API request"]:::api
|
||||||
|
UserContext --> FINAL
|
||||||
|
SystemContext --> FINAL
|
||||||
|
Compaction --> FINAL
|
||||||
|
|
||||||
|
classDef api fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: API Call and Streaming
|
||||||
|
|
||||||
|
The request goes through `claude.ts` with retry logic:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#28a745', 'primaryBorderColor': '#28a745'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
REQ["API Request<br/>messages + system + tools"]:::req
|
||||||
|
|
||||||
|
STREAM["SSE Stream"]:::stream
|
||||||
|
|
||||||
|
subgraph Events["Stream Events in Order"]
|
||||||
|
E1["message_start<br/>model, id, usage"]
|
||||||
|
E2["content_block_start<br/>type + index"]
|
||||||
|
E3["content_block_delta<br/>incremental text/tool JSON"]
|
||||||
|
E4["content_block_stop"]
|
||||||
|
E5["message_delta<br/>stop_reason + final usage"]
|
||||||
|
E6["message_stop"]
|
||||||
|
end
|
||||||
|
|
||||||
|
REQ --> STREAM --> Events
|
||||||
|
|
||||||
|
classDef req fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef stream fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
Each content block is one of three types: **thinking** (internal reasoning), **text** (user-facing response), or **tool_use** (triggers tool execution).
|
||||||
|
|
||||||
|
### Phase 4: Tool Execution Loop
|
||||||
|
|
||||||
|
When the model responds with `tool_use`, the agentic loop kicks in:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#e83e8c', 'primaryBorderColor': '#e83e8c'}}}%%
|
||||||
|
flowchart TD
|
||||||
|
TOOL_USE["Model returns tool_use blocks"]:::start
|
||||||
|
|
||||||
|
PAR{"Multiple tools?<br/>isConcurrencySafe?"}:::check
|
||||||
|
|
||||||
|
PARALLEL["Execute in parallel<br/>Read-only tools"]:::exec
|
||||||
|
SEQUENTIAL["Execute sequentially<br/>Write tools"]:::exec
|
||||||
|
|
||||||
|
VALIDATE["Zod schema validation"]:::step
|
||||||
|
HOOKS_PRE["PreToolUse hooks"]:::step
|
||||||
|
PERM_CHECK["Permission check chain<br/>deny - allow - tool -<br/>hooks - classifier - dialog"]:::step
|
||||||
|
EXECUTE["tool.call(input, context)"]:::step
|
||||||
|
HOOKS_POST["PostToolUse hooks"]:::step
|
||||||
|
MAP_RESULT["Map to tool_result message"]:::step
|
||||||
|
|
||||||
|
INJECT["Inject CLAUDE.md attachments<br/>for newly-discovered dirs"]:::step
|
||||||
|
|
||||||
|
PUSH["Push tool_results to messages<br/>Continue loop"]:::feedback
|
||||||
|
|
||||||
|
TOOL_USE --> PAR
|
||||||
|
PAR -->|"yes"| PARALLEL
|
||||||
|
PAR -->|"no"| SEQUENTIAL
|
||||||
|
|
||||||
|
PARALLEL --> VALIDATE
|
||||||
|
SEQUENTIAL --> VALIDATE
|
||||||
|
|
||||||
|
VALIDATE --> HOOKS_PRE --> PERM_CHECK --> EXECUTE --> HOOKS_POST --> MAP_RESULT --> INJECT --> PUSH
|
||||||
|
|
||||||
|
PUSH ==>|"Back to<br/>compaction"| TOOL_USE
|
||||||
|
|
||||||
|
classDef start fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef check fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef exec fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef step fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef feedback fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:3px
|
||||||
|
```
|
||||||
|
|
||||||
|
The loop continues until the model returns `stop_reason: end_turn`, hits `max_turns`, or is cancelled.
|
||||||
|
|
||||||
|
### Phase 5: Response Rendering
|
||||||
|
|
||||||
|
The final response is rendered and persisted:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#fd7e14', 'primaryBorderColor': '#fd7e14'}}}%%
|
||||||
|
flowchart LR
|
||||||
|
RESPONSE["Model final response<br/>stop_reason: end_turn"]:::input
|
||||||
|
|
||||||
|
subgraph Rendering["Terminal Rendering"]
|
||||||
|
MD["Markdown rendering<br/>via marked library"]
|
||||||
|
SYNTAX["Syntax highlighting<br/>for code blocks"]
|
||||||
|
DIFF["Diff rendering<br/>for file changes"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Persistence["Persistence"]
|
||||||
|
TRANSCRIPT["recordTranscript<br/>Persist to disk"]
|
||||||
|
USAGE["Accumulate usage<br/>input + output tokens"]
|
||||||
|
COST["Track cost<br/>per-model pricing"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SDK_Events["SDK Events"]
|
||||||
|
MSG["SDKMessage stream<br/>Normalized events"]
|
||||||
|
RESULT_E["Result event<br/>total_cost, usage, duration"]
|
||||||
|
end
|
||||||
|
|
||||||
|
RESPONSE --> Rendering
|
||||||
|
RESPONSE --> Persistence
|
||||||
|
RESPONSE --> SDK_Events
|
||||||
|
|
||||||
|
classDef input fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A Concrete Example
|
||||||
|
|
||||||
|
Let's trace what happens when a user types: `fix the bug in utils.ts`
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'lineColor': '#4a9eff', 'actorTextColor': '#e0e0e0', 'actorBorder': '#4a9eff', 'signalColor': '#4a9eff', 'noteBkgColor': '#16213e', 'noteTextColor': '#e0e0e0', 'activationBkgColor': '#2d1b4e', 'activationBorderColor': '#e83e8c'}}}%%
|
||||||
|
sequenceDiagram
|
||||||
|
participant U as User
|
||||||
|
participant R as REPL.tsx
|
||||||
|
participant QE as QueryEngine
|
||||||
|
participant Q as query.ts
|
||||||
|
participant C as claude.ts
|
||||||
|
participant T as Tools
|
||||||
|
|
||||||
|
U->>R: Types "fix the bug in utils.ts"
|
||||||
|
R->>QE: submitMessage(prompt)
|
||||||
|
|
||||||
|
Note over QE: Not a slash command, proceed
|
||||||
|
|
||||||
|
QE->>QE: Persist transcript
|
||||||
|
QE->>Q: query(messages, systemPrompt, tools)
|
||||||
|
|
||||||
|
Note over Q: Turn 1 - Model reads code
|
||||||
|
|
||||||
|
Q->>Q: Run compaction pipeline
|
||||||
|
Q->>C: queryModel(messages)
|
||||||
|
C-->>Q: tool_use: FileRead("utils.ts")
|
||||||
|
Q->>T: Execute FileRead
|
||||||
|
T-->>Q: File contents
|
||||||
|
|
||||||
|
Note over Q: Turn 2 - Model analyzes
|
||||||
|
|
||||||
|
Q->>C: queryModel(messages + file contents)
|
||||||
|
C-->>Q: tool_use: Grep("error pattern")
|
||||||
|
Q->>T: Execute Grep
|
||||||
|
T-->>Q: Search results
|
||||||
|
|
||||||
|
Note over Q: Turn 3 - Model fixes
|
||||||
|
|
||||||
|
Q->>C: queryModel(messages + grep results)
|
||||||
|
C-->>Q: tool_use: FileEdit("utils.ts", patch)
|
||||||
|
|
||||||
|
Note over T: Permission check!
|
||||||
|
T->>U: Allow FileEdit? (Y/n/always)
|
||||||
|
U->>T: Y
|
||||||
|
|
||||||
|
T-->>Q: Edit applied
|
||||||
|
|
||||||
|
Note over Q: Turn 4 - Model confirms
|
||||||
|
|
||||||
|
Q->>C: queryModel(messages + edit result)
|
||||||
|
C-->>Q: text: "Fixed the bug" + end_turn
|
||||||
|
|
||||||
|
Q-->>QE: Terminal result
|
||||||
|
QE->>QE: Record transcript, track usage
|
||||||
|
QE-->>R: Render response
|
||||||
|
R->>U: Display formatted answer
|
||||||
|
```
|
||||||
|
|
||||||
|
This shows the typical pattern: **read first, analyze, then write** -- with permission checks only on the write operation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Takeaways
|
||||||
|
|
||||||
|
1. **Multiple entry points, single pipeline**: Whether input comes from keyboard, IDE, or SDK, it all converges into the same `processUserInput -> query -> claude.ts` pipeline.
|
||||||
|
|
||||||
|
2. **Compaction runs every turn**: The 5-stage compaction pipeline runs *before every single API call*, not just when limits are hit.
|
||||||
|
|
||||||
|
3. **Tools loop back**: Tool results are pushed to messages, and the entire pipeline (including compaction) runs again. This is the "agentic" part -- the model keeps going until it's done.
|
||||||
|
|
||||||
|
4. **Permissions only interrupt writes**: Read-only tools (FileRead, Grep, Glob) in default mode flow through silently. Only write operations (FileEdit, Bash, FileWrite) trigger permission dialogs.
|
||||||
|
|
||||||
|
5. **Everything is streamed**: From SSE events to async generators to React state updates, nothing blocks waiting for a full response. The user sees tokens as they arrive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Previous:** [<- MCP Deep Dive](./11-mcp-deep-dive.md)
|
||||||
160
learn/README.md
Normal file
160
learn/README.md
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
# Learn Claude Code Architecture
|
||||||
|
|
||||||
|
> A deep-dive educational guide into the internal architecture of Anthropic's Claude Code CLI — the agentic coding assistant that leaked via a `.map` file in March 2026.
|
||||||
|
|
||||||
|
**Internal Codename:** Tengu
|
||||||
|
**Runtime:** Bun
|
||||||
|
**UI Framework:** React + Ink (React for CLI)
|
||||||
|
**Language:** TypeScript (strict)
|
||||||
|
**Scale:** ~1,900 files, 512,000+ lines of code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why This Guide Exists
|
||||||
|
|
||||||
|
Claude Code is one of the most sophisticated agentic AI systems ever built. Its architecture contains lessons in:
|
||||||
|
|
||||||
|
- **Agentic loop design** — How to build a multi-turn, tool-calling AI agent
|
||||||
|
- **Terminal UI at scale** — 113 React components running in a terminal
|
||||||
|
- **Permission systems** — Layered security for autonomous code execution
|
||||||
|
- **Context management** — Keeping conversations within token limits
|
||||||
|
- **Extension architectures** — Plugins, skills, hooks, and sub-agents
|
||||||
|
|
||||||
|
Whether you're building your own AI agent, contributing to open-source AI tools, or just curious about how the sausage is made — this guide is for you.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guide Structure
|
||||||
|
|
||||||
|
Start from the top and work down, or jump to whatever interests you:
|
||||||
|
|
||||||
|
| # | Guide | What You'll Learn |
|
||||||
|
|---|-------|-------------------|
|
||||||
|
| 1 | [System Overview](./01-system-overview.md) | Bird's-eye view of every layer — entries, UI, core engine, tools, state |
|
||||||
|
| 2 | [The Agentic Loop](./02-agentic-loop.md) | How `query.ts` drives the model → tool → model cycle |
|
||||||
|
| 3 | [Tool System](./03-tool-system.md) | How 42 built-in tools are defined, validated, and executed |
|
||||||
|
| 4 | [Permission System](./04-permission-system.md) | Deny rules, allow rules, hooks, classifier, user prompts |
|
||||||
|
| 5 | [Context Management](./05-context-management.md) | Snip, micro, auto, reactive compact — the full pipeline |
|
||||||
|
| 6 | [State Management](./06-state-management.md) | AppState store, immutability, and reactive side effects |
|
||||||
|
| 7 | [Extension Model](./07-extension-model.md) | Skills, plugins, hooks, sub-agents, and swarms |
|
||||||
|
| 8 | [API Client](./08-api-client.md) | `claude.ts` — streaming, retries, caching, and model fallback |
|
||||||
|
| 9 | [UI Architecture](./09-ui-architecture.md) | React Ink, 113 components, and the 896KB REPL |
|
||||||
|
| 10 | [Configuration and System Prompt](./10-configuration-and-system-prompt.md) | Settings hierarchy, CLAUDE.md discovery, prompt assembly |
|
||||||
|
| 11 | [MCP Deep Dive](./11-mcp-deep-dive.md) | Transports, tool wrapping, auth, server lifecycle |
|
||||||
|
| 12 | [Data Flow Walkthrough](./12-data-flow-walkthrough.md) | End-to-end trace of a single user request |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files Quick Reference
|
||||||
|
|
||||||
|
| File | Size | Role |
|
||||||
|
|------|------|------|
|
||||||
|
| `src/main.tsx` | 804KB | CLI entrypoint — Commander.js parser + React/Ink bootstrap |
|
||||||
|
| `src/screens/REPL.tsx` | 896KB | Interactive terminal shell — the heart of the UI |
|
||||||
|
| `src/query.ts` | 1,730 lines | The agentic loop — model ↔ tool cycling |
|
||||||
|
| `src/QueryEngine.ts` | 1,296 lines | Session lifecycle owner — wraps `query()` |
|
||||||
|
| `src/Tool.ts` | 793 lines | Tool interface definition — every tool implements this |
|
||||||
|
| `src/commands.ts` | ~25K | Slash command registry |
|
||||||
|
| `src/state/AppStateStore.ts` | 570 lines | Immutable store with 50+ fields |
|
||||||
|
| `src/services/compact/` | 11 files | The entire compaction pipeline |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture At A Glance
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#1a1a2e', 'primaryTextColor': '#e0e0e0', 'primaryBorderColor': '#4a9eff', 'lineColor': '#4a9eff', 'secondaryColor': '#16213e', 'tertiaryColor': '#0f3460', 'edgeLabelBackground': '#1a1a2e'}}}%%
|
||||||
|
graph TB
|
||||||
|
CLI["CLI Entry<br/>main.tsx — 804KB"]:::entry
|
||||||
|
SDK["SDK Entry<br/>Programmatic API"]:::entry
|
||||||
|
MCP_S["MCP Server<br/>Expose as MCP"]:::entry
|
||||||
|
|
||||||
|
REPL["REPL.tsx — 896KB<br/>Interactive Terminal Shell"]:::ui
|
||||||
|
Comps["113 Components<br/>Messages, Diffs, Dialogs"]:::ui
|
||||||
|
Hooks["83 React Hooks<br/>Permissions, Input, IDE"]:::ui
|
||||||
|
|
||||||
|
QE["QueryEngine.ts<br/>Session Lifecycle Owner"]:::core
|
||||||
|
QL["query.ts — 1730 lines<br/>Agentic Loop"]:::core
|
||||||
|
CL["claude.ts — 3420 lines<br/>Anthropic API Client"]:::core
|
||||||
|
|
||||||
|
TD["Tool Interface<br/>Tool.ts"]:::tool
|
||||||
|
BT["42 Built-in Tools"]:::tool
|
||||||
|
MT["MCP Tools — dynamic"]:::tool
|
||||||
|
TO["Tool Orchestration<br/>Parallel Execution"]:::tool
|
||||||
|
|
||||||
|
Compact["Compaction Pipeline<br/>snip / micro / auto /<br/>reactive / collapse"]:::ctx
|
||||||
|
|
||||||
|
Rules["Allow + Deny Rules"]:::perm
|
||||||
|
HK["PreToolUse Hooks"]:::perm
|
||||||
|
Classifier["Auto-mode Classifier"]:::perm
|
||||||
|
|
||||||
|
AS["AppState Store<br/>Immutable — 50+ fields"]:::state
|
||||||
|
SS["Session Storage<br/>Transcripts + Resume"]:::state
|
||||||
|
Cfg["Config Layer<br/>Global / Project / CLAUDE.md"]:::state
|
||||||
|
|
||||||
|
Skills["Skills"]:::ext
|
||||||
|
Plugins["Plugins"]:::ext
|
||||||
|
Agents["Sub-agents + Swarms"]:::ext
|
||||||
|
|
||||||
|
API["Anthropic Messages API"]:::external
|
||||||
|
MCP_Ext["External MCP Servers"]:::external
|
||||||
|
GrowthBook["GrowthBook + Statsig"]:::external
|
||||||
|
|
||||||
|
CLI --> REPL
|
||||||
|
SDK --> QE
|
||||||
|
MCP_S --> QE
|
||||||
|
|
||||||
|
REPL --> Comps
|
||||||
|
REPL --> Hooks
|
||||||
|
REPL --> QE
|
||||||
|
REPL --> AS
|
||||||
|
|
||||||
|
QE --> QL
|
||||||
|
QL --> CL
|
||||||
|
QL --> Compact
|
||||||
|
QL --> TO
|
||||||
|
|
||||||
|
CL --> API
|
||||||
|
CL --> GrowthBook
|
||||||
|
|
||||||
|
TO --> TD
|
||||||
|
TD --> BT
|
||||||
|
TD --> MT
|
||||||
|
BT --> Rules
|
||||||
|
BT --> HK
|
||||||
|
MT --> Rules
|
||||||
|
|
||||||
|
Rules --> Classifier
|
||||||
|
|
||||||
|
QE --> SS
|
||||||
|
REPL --> Cfg
|
||||||
|
|
||||||
|
Skills --> TD
|
||||||
|
Plugins --> TD
|
||||||
|
Plugins --> MCP_Ext
|
||||||
|
Agents --> QL
|
||||||
|
MCP_Ext --> MT
|
||||||
|
|
||||||
|
classDef entry fill:#0d4f4f,stroke:#17a2b8,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ui fill:#1a1a4e,stroke:#6f42c1,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef core fill:#2d1b4e,stroke:#e83e8c,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef tool fill:#1b3a1b,stroke:#28a745,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ctx fill:#3d2b00,stroke:#fd7e14,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef perm fill:#4a1a1a,stroke:#dc3545,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef state fill:#1a2d4a,stroke:#4a9eff,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef ext fill:#2d2d0d,stroke:#ffc107,color:#e0e0e0,stroke-width:2px
|
||||||
|
classDef external fill:#333,stroke:#888,color:#aaa,stroke-width:1px,stroke-dasharray: 5 5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To get the most out of these guides, you should be comfortable with:
|
||||||
|
|
||||||
|
- **TypeScript** — The entire codebase is strict TypeScript
|
||||||
|
- **React** — The UI uses React (via Ink for the terminal)
|
||||||
|
- **Async generators** — The agentic loop and streaming are built on `async function*`
|
||||||
|
- **LLM APIs** — Familiarity with the Anthropic Messages API helps
|
||||||
|
|
||||||
|
---
|
||||||
Loading…
Reference in New Issue
Block a user