Skip to content

Instantly share code, notes, and snippets.

@wayou
Created April 13, 2026 12:46
Show Gist options
  • Select an option

  • Save wayou/a94f2bc6e2d2a3f87148f6a38bc3db0b to your computer and use it in GitHub Desktop.

Select an option

Save wayou/a94f2bc6e2d2a3f87148f6a38bc3db0b to your computer and use it in GitHub Desktop.
Claude Code Architecture Report & Best Practices — comprehensive analysis with Mermaid diagrams

Claude Code — Architecture Report & Best Practices

Comprehensive analysis of the cc-src repository
Date: 2026-04-13


Table of Contents

  1. Executive Summary
  2. System Architecture Overview
  3. Core Components Deep Dive
  4. Key Engineering Best Practices
  5. Critical Success Factors
  6. Technology Stack
  7. Lessons Learned

1. Executive Summary

Claude Code is Anthropic's official CLI for Claude — a production-grade AI agent built on top of the Claude API. Unlike most "chatbot wrappers," Claude Code is a fully autonomous software engineering agent: it reads files, runs shells, edits code, searches the web, spawns sub-agents, manages long-running tasks, and integrates with external tools via MCP — all within a streaming, turn-based loop designed for safety and extensibility.

The source reveals a codebase of extraordinary sophistication: ~4,683 lines in main.tsx alone, 290+ utility modules, 43+ built-in tools, 50+ commands, 87+ React hooks, and a plugin/MCP integration layer. Yet it maintains coherent architecture through disciplined abstractions.

The #1 insight from this codebase: a great agent is not just a "model + tools" — it's a carefully engineered system for trust, extensibility, context management, and graceful failure.


2. System Architecture Overview

2.1 High-Level System Diagram

graph TB
    subgraph "User Interfaces"
        CLI[CLI REPL<br/>Ink/React]
        SDK[Agent SDK<br/>Programmatic]
        Bridge[Claude.ai Bridge<br/>iframe sync]
        IDE[IDE Extension<br/>VS Code / JetBrains]
    end

    subgraph "Entry & Initialization"
        INIT[init.ts<br/>15-phase startup pipeline]
        MAIN[main.tsx<br/>CLI orchestrator]
    end

    subgraph "Core Agentic Loop"
        QUERY[query.ts<br/>AsyncGenerator loop]
        CTX[context.ts<br/>System prompt builder]
        HIST[history.ts<br/>Conversation history]
        ATTACH[Attachment System<br/>File changes, memory, skills]
    end

    subgraph "Intelligence Layer"
        API[Anthropic API<br/>claude-sonnet/opus/haiku]
        CACHE[Prompt Cache<br/>cache_control blocks]
        TOKEN[Token Budget<br/>tracking & compaction]
        COMPACT[Context Compactor<br/>message pruning]
    end

    subgraph "Tool Execution"
        TOOLS[Tool Registry<br/>43+ tools]
        PERM[Permission Engine<br/>rules + classifier]
        HOOKS[Hook Runner<br/>PreToolUse, PostToolUse]
        EXEC[Concurrent Executor<br/>parallel tool calls]
    end

    subgraph "State & Tasks"
        APPSTATE[AppState Store<br/>DeepImmutable]
        TASKS[Task Manager<br/>background agents]
        TASKOUT[Task Output<br/>disk-streamed]
    end

    subgraph "Extensions"
        MCP[MCP Layer<br/>stdio/SSE/HTTP/WS]
        PLUGINS[Plugin System]
        SKILLS[Skill Discovery]
        CMDS[Command Registry<br/>50+ commands]
    end

    subgraph "Infrastructure"
        GIT[Git Integration]
        BASH[Shell Execution]
        LSP[Language Server]
        FS[File State Cache]
        OAUTH[OAuth Flows]
        GB[GrowthBook<br/>Feature Flags]
        OTEL[OpenTelemetry<br/>Observability]
    end

    CLI --> MAIN
    SDK --> MAIN
    Bridge --> MAIN
    IDE --> MAIN

    MAIN --> INIT
    INIT --> QUERY
    QUERY --> CTX
    QUERY --> HIST
    QUERY --> ATTACH
    QUERY --> API
    API --> CACHE
    QUERY --> TOKEN
    TOKEN --> COMPACT

    API -->|tool_use blocks| TOOLS
    TOOLS --> PERM
    PERM --> HOOKS
    HOOKS --> EXEC
    EXEC -->|results| QUERY

    APPSTATE --> QUERY
    TASKS --> APPSTATE
    EXEC --> TASKS
    TASKS --> TASKOUT

    TOOLS --> MCP
    TOOLS --> PLUGINS
    TOOLS --> SKILLS
    MAIN --> CMDS

    EXEC --> BASH
    EXEC --> GIT
    EXEC --> LSP
    EXEC --> FS
    INIT --> OAUTH
    INIT --> GB
    QUERY --> OTEL
Loading

2.2 Request Lifecycle

sequenceDiagram
    participant User
    participant REPL
    participant Query
    participant API as Anthropic API
    participant Tools
    participant Perm as Permissions
    participant Hooks

    User->>REPL: Enter prompt
    REPL->>REPL: UserPromptSubmit hook
    REPL->>Query: query(params)
    
    Query->>Query: normalizeMessagesForAPI()
    Query->>Query: inject attachments (memory, file changes, skills)
    Query->>Query: build system prompt via context.ts
    Query->>API: POST /messages (streaming)
    
    loop Streaming turn
        API-->>Query: stream delta (text / tool_use)
        Query-->>REPL: yield StreamEvent (live display)
    end

    API-->>Query: stop_reason: tool_use
    
    loop For each tool call (concurrent)
        Query->>Perm: checkPermissions(tool, input)
        alt Permission denied
            Perm-->>User: prompt dialog
            User-->>Perm: approve/deny
        end
        Perm->>Hooks: PreToolUse event
        Hooks->>Tools: call(input, context)
        Tools-->>Hooks: result
        Hooks->>Hooks: PostToolUse event
        Hooks-->>Query: tool_result
    end

    Query->>Query: checkTokenBudget()
    alt Near context limit
        Query->>Query: compact(messages)
    end

    Query->>API: continue with tool_results
    
    API-->>Query: stop_reason: end_turn
    Query-->>REPL: final AssistantMessage
    REPL-->>User: display response
Loading

3. Core Components Deep Dive

3.1 The Agentic Query Loop

File: query.ts (~1,729 lines)

The heart of Claude Code is an AsyncGenerator-based loop that orchestrates every turn of the conversation.

flowchart TD
    START([Start query]) --> NORM[normalizeMessagesForAPI]
    NORM --> ATTACH[Inject attachments\nmemory · file changes · skills]
    ATTACH --> CTXBUILD[Build system prompt\ncontext.ts]
    CTXBUILD --> APISTREAM[Stream API call\nclaude.ts]

    APISTREAM --> DELTA{delta type?}
    DELTA -->|text| YIELD_TEXT[yield TextEvent]
    DELTA -->|tool_use| COLLECT[Collect tool calls]
    DELTA -->|stop| STOP_REASON{stop_reason?}

    COLLECT --> PERM_CHECK[checkPermissions per tool]
    PERM_CHECK --> CONCURRENT[Run tools concurrently]
    CONCURRENT --> RESULTS[Collect tool results]
    RESULTS --> TOKEN_CHECK{Token budget OK?}
    TOKEN_CHECK -->|over budget| COMPACT[compact messages]
    TOKEN_CHECK -->|OK| CONTINUE[Append tool_results]
    COMPACT --> CONTINUE
    CONTINUE --> APISTREAM

    STOP_REASON -->|tool_use| PERM_CHECK
    STOP_REASON -->|end_turn| YIELD_MSG[yield AssistantMessage]
    STOP_REASON -->|max_tokens| AUTO_CONTINUE[Auto-continue\nvia synthetic tool]
    AUTO_CONTINUE --> APISTREAM
    YIELD_MSG --> END([End])

    YIELD_TEXT --> APISTREAM
Loading

Key design decisions:

  • Generator-based (AsyncGenerator<Message | StreamEvent>) — callers can consume events in real-time without buffering
  • Depth tracking prevents infinite recursion in sub-agents
  • Automatic continuation on max_tokens — model never truly "runs out"
  • Token budgets enforced per-turn, not just cumulative

3.2 Tool System

Files: Tool.ts, tools.ts, /tools/

Every capability in Claude Code is a Tool. This single abstraction unifies file I/O, shell execution, web search, sub-agent spawning, MCP invocations, and more.

classDiagram
    class Tool {
        +name: string
        +description: string
        +inputSchema: ZodSchema
        +isEnabled: () => boolean
        +isReadOnly: () => boolean
        +shouldDefer: boolean
        +checkPermissions(input, ctx): PermissionResult
        +validateInput(input): ValidationResult
        +call(input, ctx): AsyncGenerator~ToolResult~
        +renderToolResultMessage(result, input): ReactNode
    }

    class ToolResult {
        +type: "text" | "image" | "resource" | ...
        +content: string | Buffer
        +metadata?: object
    }

    class PermissionResult {
        +result: "allow" | "deny" | "ask"
        +reason?: string
        +updatedInput?: object
    }

    Tool --> ToolResult
    Tool --> PermissionResult

    class BashTool
    class FileEditTool
    class FileReadTool
    class GlobTool
    class GrepTool
    class AgentTool
    class MCPTool
    class WebSearchTool
    class TaskCreateTool
    class EnterPlanModeTool
    class SleepTool
    class MonitorTool
    class AskUserQuestionTool

    Tool <|-- BashTool
    Tool <|-- FileEditTool
    Tool <|-- FileReadTool
    Tool <|-- GlobTool
    Tool <|-- GrepTool
    Tool <|-- AgentTool
    Tool <|-- MCPTool
    Tool <|-- WebSearchTool
    Tool <|-- TaskCreateTool
    Tool <|-- EnterPlanModeTool
    Tool <|-- SleepTool
    Tool <|-- MonitorTool
    Tool <|-- AskUserQuestionTool
Loading

Tool Lifecycle:

stateDiagram-v2
    [*] --> Registered: buildTool() + getTools()
    Registered --> Selected: model emits tool_use block
    Selected --> Validated: validateInput()
    Validated --> PermChecked: checkPermissions()
    PermChecked --> UserPrompted: result = "ask"
    UserPrompted --> Approved: user approves
    UserPrompted --> Denied: user denies
    PermChecked --> Approved: result = "allow"
    PermChecked --> Denied: result = "deny"
    Approved --> HookPre: PreToolUse hook
    HookPre --> Executing: call(input, ctx)
    Executing --> Streaming: yield partial results
    Streaming --> Executing: more results
    Executing --> HookPost: PostToolUse hook
    HookPost --> Rendered: renderToolResultMessage()
    Rendered --> [*]
    Denied --> [*]
Loading

Best practice: deferred tools. Non-critical tools are marked shouldDefer: true. The model first receives a stub ToolSearch tool and must explicitly fetch deferred tools before calling them. This keeps the initial context window lean.


3.3 Permission System

Files: /utils/permissions/, Tool.checkPermissions()

Claude Code has a four-layer permission model that balances automation with safety:

flowchart LR
    subgraph "Layer 1: Static Rules"
        AR[alwaysAllowRules\ne.g. Bash(git status)]
        AD[alwaysDenyRules\ne.g. Bash(rm -rf /)]
        AA[alwaysAskRules\ne.g. Bash(curl *)]
    end

    subgraph "Layer 2: Tool-Level"
        TC[Tool.checkPermissions\nper-tool logic]
    end

    subgraph "Layer 3: Hooks"
        PRE[PreToolUse hook\ncustom policy code]
    end

    subgraph "Layer 4: User Dialog"
        DLG[Interactive prompt\napprove/deny/always]
    end

    subgraph "Layer 5: ML Classifier"
        CLS[async classifier\nhigh-risk detection]
    end

    INPUT[Tool call] --> AR
    AR -->|match allow| EXEC[Execute]
    AR -->|match deny| BLOCK[Block]
    AR -->|no match| AD
    AD -->|match deny| BLOCK
    AD -->|no match| TC
    TC -->|allow| PRE
    TC -->|deny| BLOCK
    TC -->|ask| AA
    AA -->|match ask| DLG
    AA -->|no match| PRE
    PRE -->|allow| EXEC
    PRE -->|block| BLOCK
    PRE -->|ask| DLG
    DLG -->|high risk| CLS
    CLS -->|approved| EXEC
    CLS -->|rejected| BLOCK
    DLG -->|user approves| EXEC
    DLG -->|user denies| BLOCK
Loading

Modes:

Mode Behavior
default Ask user for each new tool call pattern
plan Require plan approval before any execution
bypass Auto-approve everything (CI/scripts)
auto ML classifier decides automatically

3.4 State Management

Files: /state/AppStateStore.ts, /state/AppState.tsx

Claude Code uses a custom Zustand-like store built on useSyncExternalStore. The entire application state is DeepImmutable<AppState>, preventing accidental mutation.

graph TD
    subgraph "AppState (DeepImmutable)"
        UI[Display State\nmodel · verbose · spinnerTip]
        PERM_STATE[Permission Context\ntoolPermissionContext]
        MCP_STATE[MCP State\nclients · tools · resources]
        TASK_STATE[Task Registry\ntaskId → TaskState]
        AGENT_STATE[Agent Registry\nname → agentId]
        SETTINGS[Settings\nJSON config]
        BRIDGE_STATE[Bridge State\nreplBridgeEnabled · connected]
        REMOTE_STATE[Remote Session\nurl · connectionStatus]
        PLUGIN_STATE[Plugin State\nenabled · errors]
    end

    subgraph "Store Pattern"
        CREATE[createStore(initialState)]
        UPDATE[store.update(fn)]
        SUBSCRIBE[store.subscribe(listener)]
        GET[store.getState()]
    end

    subgraph "React Integration"
        HOOK[useAppState(selector)]
        SYNC[useSyncExternalStore]
        CONTEXT[React Context Provider]
    end

    CREATE --> UPDATE
    UPDATE -->|immutable diff| SUBSCRIBE
    SUBSCRIBE --> SYNC
    SYNC --> HOOK
    CONTEXT --> HOOK
    GET --> HOOK
Loading

Why DeepImmutable? Prevents the class of bugs where a deeply nested mutation goes undetected because object identity didn't change — critical in an async streaming environment.


3.5 MCP Integration

Files: /services/mcp/

MCP (Model Context Protocol) is Claude Code's extensibility backbone — allowing any external process to expose tools and resources.

graph TB
    subgraph "Config Sources"
        LOCAL[Local: .claude/mcp.json]
        USER[User: ~/.claude/config.json]
        PROJECT[Project: .claude-code/claude.json]
        ENTERPRISE[Enterprise: MDM / remote settings]
        MANAGED[Managed: remote registry]
    end

    subgraph "Transport Layer"
        STDIO[stdio\nsubprocess]
        SSE[SSE\nServer-Sent Events]
        HTTP[HTTP\nREST streaming]
        WS[WebSocket\nbidirectional]
        SDK_TRANS[SDK\ninternal agent]
    end

    subgraph "MCP Client"
        DISCOVER[Tool Discovery\nlistTools()]
        RESOURCE[Resource Discovery\nlistResources()]
        INVOKE[Tool Invocation\ncallTool()]
        AUTH[OAuth / XAA\nper-server auth]
    end

    subgraph "Claude Integration"
        WRAP[MCPTool wrapper\nfor each MCP tool]
        RDTOOL[ReadMcpResourceTool]
        LSTOOL[ListMcpResourcesTool]
    end

    LOCAL --> STDIO
    USER --> SSE
    PROJECT --> HTTP
    ENTERPRISE --> WS
    MANAGED --> SDK_TRANS

    STDIO --> DISCOVER
    SSE --> DISCOVER
    HTTP --> DISCOVER
    WS --> DISCOVER
    SDK_TRANS --> DISCOVER

    DISCOVER --> WRAP
    RESOURCE --> RDTOOL
    RESOURCE --> LSTOOL
    INVOKE --> AUTH
    WRAP --> INVOKE
Loading

Transport comparison:

Transport Use case Notes
stdio Local CLI tools Subprocess spawned by Claude Code
sse Remote HTTP services One-way push from server
http REST-based MCP Stateless per request
ws Real-time remote Bidirectional streaming
sdk Internal agents Agent SDK controlled

3.6 Hook System

Files: /utils/hooks/, /types/hooks.ts

Hooks provide event-driven extensibility without tight coupling — the extension model for Claude Code.

sequenceDiagram
    participant Config as settings.json / hooks/
    participant Runner as HookRunner
    participant Event as Hook Event Bus
    participant Tool as Tool Execution
    participant Handler as User Hook Script

    Config->>Runner: register hook handlers
    
    Note over Runner,Tool: On every tool call:
    
    Tool->>Event: emit PreToolUse(toolName, input, messages)
    Event->>Handler: execute handler subprocess/function
    Handler-->>Event: { decision: "approve" | "block", updatedInput? }
    Event-->>Tool: permission result + possible input transform

    Tool->>Tool: execute tool
    
    Tool->>Event: emit PostToolUse(toolName, input, output)
    Event->>Handler: execute handler
    Handler-->>Event: { additionalContext? }
    Event-->>Tool: optional result augmentation
Loading

Available hook events:

Event Trigger Can Do
SessionStart Session initialization Setup, env validation
UserPromptSubmit Before message → query Rewrite, augment, block
PreToolUse Before tool execution Approve/block/transform input
PostToolUse After tool execution Transform output, log
FileChanged File system change Trigger actions, invalidate cache

3.7 Command System

Files: commands.ts, /commands/

Commands are user-facing slash actions (/config, /plan, /commit, etc.):

graph LR
    subgraph "Command Sources"
        BUNDLED[Bundled commands\n50+ built-in]
        SKILL[Skills\n.claude-commands.yml]
        MCP_CMD[MCP commands\nfrom connected servers]
        PLUGIN_CMD[Plugin commands]
    end

    subgraph "Command Types"
        PROMPT[prompt\nExpanded into message\nsent to Claude]
        LOCAL[local\nSync execution\nreturns text]
        LOCAL_JSX[local-jsx\nRenders React UI\nin terminal]
    end

    subgraph "Examples"
        EX1[/commit → prompt\nAI writes commit msg]
        EX2[/config → local-jsx\nSettings UI rendered]
        EX3[/plan → local\nEnter plan mode]
        EX4[/doctor → local\nHealth check output]
    end

    BUNDLED --> PROMPT
    BUNDLED --> LOCAL
    BUNDLED --> LOCAL_JSX
    SKILL --> PROMPT
    MCP_CMD --> PROMPT
    PLUGIN_CMD --> LOCAL

    PROMPT --> EX1
    LOCAL_JSX --> EX2
    LOCAL --> EX3
    LOCAL --> EX4
Loading

3.8 Multi-Agent Coordination

Files: /tools/AgentTool/, /coordinator/, /tasks/

Claude Code supports spawning sub-agents for parallel or isolated work:

graph TB
    subgraph "Orchestrator Agent"
        ORC[Main query loop]
        AGENT_TOOL[AgentTool.call]
        TASK_REG[Task Registry\nAppState.tasks]
    end

    subgraph "Sub-agent: Worktree Isolated"
        WT[Git Worktree\nisolated branch]
        SUB_LOOP[Sub-agent query loop]
        SUB_TOOLS[Own tool set\nOwn file state cache]
        SUB_PERM[Own permission context]
    end

    subgraph "Sub-agent: In-process Teammate"
        TP[In-process thread]
        TP_LOOP[Teammate query loop]
        TP_MAIL[Mailbox\nbidirectional messages]
    end

    subgraph "Remote Agent"
        RA[Remote session URL]
        RA_WS[WebSocket connection]
        RA_LOOP[Remote query loop]
    end

    ORC -->|spawn| AGENT_TOOL
    AGENT_TOOL -->|isolation=worktree| WT
    AGENT_TOOL -->|isolation=thread| TP
    AGENT_TOOL -->|isolation=remote| RA

    WT --> SUB_LOOP
    SUB_LOOP --> SUB_TOOLS
    SUB_LOOP --> SUB_PERM

    TP --> TP_LOOP
    TP_LOOP <-->|SendMessage| TP_MAIL
    TP_MAIL <-->|SendMessage| ORC

    RA --> RA_WS
    RA_WS --> RA_LOOP

    TASK_REG -.->|monitor| SUB_LOOP
    TASK_REG -.->|monitor| TP_LOOP
    TASK_REG -.->|monitor| RA_LOOP
Loading

Isolation guarantees per mode:

Isolation File State Git State Permission Context Communication
Worktree Own copy Own branch Inherited Via task output
Thread (teammate) Shared Shared Inherited Bidirectional mailbox
Remote Own process Own env Independent WebSocket

4. Key Engineering Best Practices

4.1 Unified Tool Abstraction

Practice: Every capability is a Tool with a consistent interface: validateInput → checkPermissions → call → renderResult.

Why it works: New capabilities don't require changes to the core loop. The model discovers and calls tools via function calling — adding a tool is adding one object.

Takeaway: Define a single, composable capability interface from day one. Don't have "special" capabilities that bypass the abstraction.


4.2 Streaming-First Architecture

Practice: The query loop is an AsyncGenerator<Message | StreamEvent>. Tool results are yielded incrementally, not batched. UI updates in real-time.

Why it works: Users see progress immediately. Long-running tools don't block the UI. Network latency is hidden by streaming partial results.

Takeaway: Build streaming into your core types, not as an afterthought. AsyncGenerator is the right primitive for agent loops.


4.3 Token Budget Management

Practice: Every query tracks token usage. Auto-compaction triggers before the context limit. The model can "continue" when it hits max_tokens via a synthetic tool call.

Why it works: Long agentic tasks don't silently fail mid-way. Users running 50-turn sessions have their context managed transparently.

Diagram:

graph LR
    TRACK[Track tokens per turn] --> CHECK{Over budget?}
    CHECK -->|Yes| COMPACT[Compact messages\n- tombstone old user msgs\n- summarize assistant turns]
    CHECK -->|No| PROCEED[Proceed normally]
    COMPACT --> PROCEED
    PROCEED --> MAX{stop_reason\n= max_tokens?}
    MAX -->|Yes| CONTINUE[Auto-continue\nvia continuation tool]
    CONTINUE --> TRACK
    MAX -->|No| DONE[End turn]
Loading

Takeaway: Token management is not optional for production agents. Build compaction and continuation into the loop core.


4.4 Layered Permission Model

Practice: Permissions are checked at five layers (static rules → tool logic → hooks → dialog → ML classifier), with each layer able to short-circuit.

Why it works: Power users can set alwaysAllow patterns for productivity. Cautious users get prompted for every action. Enterprise deployments use deny rules for compliance. All on the same code path.

Takeaway: Ship with deny-by-default permissions and multiple layers of override, not a single boolean "trust" flag.


4.5 Deferred Tool Loading

Practice: Non-critical tools are marked shouldDefer: true. They don't appear in the model's initial context — the model must call ToolSearch to discover and load them.

Why it works: Keeps the initial prompt lean (fewer tokens). Model only loads tools it actually needs. Reduces cognitive overhead in tool selection.

Takeaway: Don't give the model 100 tools upfront. Use lazy discovery for rarely-needed capabilities.


4.6 Feature Flag Dead Code Elimination

Practice: Capabilities are wrapped in feature('flagName', () => import('./module')). The bundler eliminates disabled features at build time.

Why it works: Different build targets (OSS CLI, IDE extension, enterprise) can enable/disable capabilities without runtime overhead. No dead code ships.

Takeaway: Use feature flags + build-time tree shaking for capability sets, not runtime if-else chains.


4.7 Hook-Based Extensibility

Practice: Key lifecycle events emit hooks (PreToolUse, PostToolUse, UserPromptSubmit, etc.) that external scripts can handle.

Why it works: Custom permission policies, audit logging, input sanitization, and output transformation don't require forking the codebase. Power users automate their workflows.

Takeaway: Design extensibility as observable events + transformation points, not plugin APIs that need versioning.


4.8 Immutable Application State

Practice: AppState is typed as DeepImmutable<T>. Updates go through a controlled store.update(fn) function that creates a new state snapshot.

Why it works: In a streaming async environment with concurrent tool calls, mutable shared state is a landmine. Immutability makes state transitions auditable.

Takeaway: In an agentic system, deep immutability prevents entire classes of async race condition bugs.


4.9 Startup Pipeline Sequencing

Practice: The 15-phase startup in init.ts has an explicit order: early side effects → config → MDM → trust dialog → environment → API preconnect → feature flags → MCP.

Why it works: Trust-sensitive operations (reading env vars, connecting to services) don't run until the user has consented. Security posture is established before capability.

Diagram:

graph LR
    P1[Profiling\n& MDM read] --> P2[Config\nvalidation]
    P2 --> P3[Safe env vars\nbefore trust]
    P3 --> P4[Shutdown\nhandlers]
    P4 --> P5[MTLS\n& proxy]
    P5 --> P6[OAuth\npopulate]
    P6 --> P7[Policy\nlimits]
    P7 --> P8[Remote settings\nfetch]
    P8 --> P9[Trust dialog\nif first run]
    P9 --> P10[Full env vars\nafter trust]
    P10 --> P11[API\npreconnect]
    P11 --> P12[GrowthBook\nfeature flags]
    P12 --> P13[Bootstrap\ndata fetch]
    P13 --> P14[MCP server\nconnect]
    P14 --> P15[Session\nrecovery]
    P15 --> READY[Ready]
Loading

Takeaway: Enumerate your startup phases explicitly. Put trust-sensitive initialization after consent, not before.


4.10 Context Injection Architecture

Practice: Before every API call, the system auto-injects: memory files (.claude.md), file change events, skill discovery, and tool prefetches as attachment messages.

Why it works: The model always has up-to-date context without the user having to re-explain the codebase, open files, or recent changes.

Takeaway: Invest heavily in automatic context enrichment. The model's quality is bounded by the quality of its context.


4.11 Speculative Execution

Practice: While the user is typing, Claude Code pre-runs background agent guesses at what the next intent might be (speculation / pipelining).

Why it works: Responses feel instant because work was done before the user hit Enter. The timeSavedMs metric measures actual impact.

Takeaway: Precompute probable next states. Agents feel fast not just through streaming, but through predicting what comes next.


5. Critical Success Factors

5.1 Safety as a First-Class Citizen

Claude Code treats safety as a primary engineering concern, not an afterthought:

  • Multiple permission layers (rules, hooks, dialogs, ML classifier)
  • alwaysDenyRules for catastrophically dangerous patterns (rm -rf /)
  • Trust dialog at first run (no side effects before consent)
  • Sub-agent isolation via git worktrees
  • Immutable state to prevent race conditions

Key insight: Safety mechanisms are composable. A user can configure alwaysAllow for their workflow without weakening safety for others.


5.2 Extensibility Without Core Changes

The MCP + Hook + Plugin architecture means the community can extend Claude Code without touching Anthropic's code:

graph TB
    CORE[Claude Code Core\nAnthropic maintained]
    
    subgraph "Community Extensions"
        MCP_EXT[MCP Servers\next tools & resources]
        HOOK_EXT[Hook Scripts\ncustom policy & logic]
        SKILL_EXT[Skills\ncustom workflows]
        PLUGIN_EXT[Plugins\ncustom commands & UI]
    end
    
    CORE -->|event bus| HOOK_EXT
    CORE -->|protocol| MCP_EXT
    CORE -->|discovery| SKILL_EXT
    CORE -->|API| PLUGIN_EXT
Loading

Key insight: The extensibility surface area is small and stable (tool interface, hook events, MCP protocol). This is what makes the ecosystem grow.


5.3 Context as a Product

The system invests enormously in context quality:

  • Automatic memory file injection (.claude.md at project + user level)
  • File change tracking and injection
  • Skill discovery auto-attached to prompts
  • Speculative prefetch of likely-needed tools
  • Context compaction to maximize usable window
  • Prompt caching for repeated content

Key insight: For a coding agent, 80% of quality comes from "does the model have the right context?" not "is the model smart enough?".


5.4 Graceful Degradation

Claude Code degrades gracefully at every layer:

  • Rate limits: Automatic fallback to cheaper/smaller models
  • Max tokens: Auto-continuation via synthetic tool call
  • Context overflow: Intelligent message compaction
  • MCP server failure: Continues with remaining tools
  • Tool failure: Error surfaced as tool_result, not crash
  • Network failure: Retry with exponential backoff

Key insight: Agent systems interact with the real world. Plan for every external dependency to fail.


5.5 Observable & Debuggable

  • OpenTelemetry integration for distributed tracing
  • Startup profiler tracks initialization timing
  • Token usage reported per-turn in verbose mode
  • Task output disk-streamed for forensics
  • /doctor command for health diagnostics
  • Analytics events for every tool call, approval, and compaction

Key insight: You cannot improve an agent you cannot observe. Ship telemetry before shipping features.


5.6 Terminal UX Quality

Claude Code is built with Ink (React for terminals) and has 87+ custom React hooks for terminal UX:

  • Vim keybinding mode
  • Arrow-key command history
  • Real-time streaming with diff display
  • Progress spinners for background tasks
  • Interactive permission dialogs
  • /compact view for long sessions

Key insight: For a CLI agent, terminal UX is product quality. Users who get frustrated abandon the tool regardless of AI capability.


6. Technology Stack

graph TB
    subgraph "Language & Runtime"
        TS[TypeScript\nstatic typing + generics]
        BUN[Bun\nbundler + runtime]
        TSX[TSX / React\nfor Ink UI]
    end

    subgraph "AI & Protocol"
        SDK[Anthropic SDK\n@anthropic-ai/sdk]
        MCP_SDK[MCP SDK\n@modelcontextprotocol/sdk]
        OTEL_SDK[OpenTelemetry SDK]
    end

    subgraph "UI"
        INK[Ink\nReact for terminals]
        REACT[React 18\nuseS yncExternalStore]
    end

    subgraph "Schema & Validation"
        ZOD[Zod v4\nruntime validation]
        ZOD_LAZY[Zod lazy schemas\ncircular dependency]
    end

    subgraph "Utilities"
        LODASH[lodash-es\nmemoize, debounce, etc.]
        GLOB[glob\nfile pattern matching]
        DIFF[diff\npatch generation]
    end

    subgraph "External Services"
        ANTHROPIC[Anthropic API]
        GB_SVC[GrowthBook\nfeature flags]
        OAUTH_SVC[OAuth providers]
        BEDROCK[AWS Bedrock]
        VERTEX[GCP Vertex AI]
    end

    TS --> BUN
    TSX --> INK
    SDK --> ANTHROPIC
    SDK --> BEDROCK
    SDK --> VERTEX
    ZOD --> ZOD_LAZY
Loading

Why Bun? Faster startup, built-in bundler with dead code elimination via feature flags, TypeScript support without transpile step.

Why Ink? React's component model maps naturally to a streaming terminal UI — components re-render on state changes, just like in a browser.

Why Zod v4? Schema-based validation for tool inputs prevents entire classes of runtime errors. z.lazy() solves circular schema dependencies in recursive tool definitions.


7. Lessons Learned

What makes Claude Code different from a "chatbot wrapper"

Dimension Chatbot Wrapper Claude Code
Tool calling Optional, demo-quality Core abstraction, 43+ tools, concurrent
Context management Manual, user-driven Automatic injection, compaction, caching
Safety Single approve/deny 5-layer permission system
Extensibility Fork the code MCP + Hooks + Plugins
Multi-agent Single thread Worktrees, threads, remote agents
State management Mutable object DeepImmutable store
Streaming Optional bolt-on AsyncGenerator core type
Observability Logs OpenTelemetry + startup profiler + analytics
Startup Eager init 15-phase sequenced pipeline
UX Raw terminal output Ink React components, vim mode, history

The 10 Keys to Building a Successful Agent Like Claude Code

  1. Make Tool the core abstraction. Everything the agent can do is a Tool. No special cases.

  2. Stream everything. Use AsyncGenerator from the protocol layer to the UI. Users tolerate slow; they don't tolerate silent.

  3. Manage context proactively. Auto-inject memory, track file changes, compact old messages. The model's quality is bounded by context quality.

  4. Build safety in layers. Static rules → tool logic → hooks → user dialog → ML classifier. Each layer composable and independently configurable.

  5. Defer non-critical tools. Keep the initial tool list small. Use lazy discovery (ToolSearch) for the long tail.

  6. Make state immutable. DeepImmutable<AppState> + controlled update functions. Async agent systems with mutable state will corrupt state eventually.

  7. Sequence your startup. Trust/consent before capability. Security posture before feature availability.

  8. Design for graceful degradation. Every external dependency will fail. Plan for rate limits, network errors, context overflow, model refusals.

  9. Build extensibility via events, not APIs. Hook events + MCP protocol scales better than plugin APIs with versioning.

  10. Ship telemetry first. You cannot improve what you cannot observe. OpenTelemetry, startup profiling, and per-turn analytics are not optional.


Final Architecture Synthesis

mindmap
    root((Claude Code\nArchitecture))
        Core Loop
            AsyncGenerator query
            Streaming turns
            Token budgeting
            Auto-compaction
            Auto-continuation
        Tool System
            Unified Tool interface
            Lazy / deferred loading
            Concurrent execution
            Schema validation
            Result rendering
        Safety
            5-layer permissions
            alwaysDeny rules
            PreToolUse hooks
            ML classifier
            Worktree isolation
        Context Quality
            Memory injection
            File change tracking
            Skill discovery
            Prompt caching
            Speculative prefetch
        Extensibility
            MCP protocol
            Hook events
            Plugin system
            Skills
            Commands
        State
            DeepImmutable store
            useSyncExternalStore
            Task registry
            Agent registry
        UX
            Ink React terminal
            87+ React hooks
            Vim keybindings
            Real-time streaming
            Permission dialogs
        Observability
            OpenTelemetry
            Startup profiler
            Token reporting
            Analytics events
            /doctor command
Loading

Report generated from source analysis of /Users/liuyong/code/cc-src.
Technology stack confirmed: TypeScript, Bun, React/Ink, Anthropic SDK, MCP SDK, Zod v4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment