Inside Claude Code: What 510,000 Lines of TypeScript Reveal About AI Agent Architecture

The article dissects Anthropic's open‑source Claude Code—an AI coding agent built on half a million lines of TypeScript—by walking through its agent loop, tool registry, permission system, context‑window management, hierarchical CLAUDE.md configuration, and comparing its agent‑first design to IDE‑first tools like Cursor.

James' Growth Diary
James' Growth Diary
James' Growth Diary
Inside Claude Code: What 510,000 Lines of TypeScript Reveal About AI Agent Architecture

Claude Code Overview

Claude Code is Anthropic's AI coding agent runtime written in TypeScript (~510k lines). It consists of ClaudeCodeRuntime exposing agentLoop, toolRegistry, permissionSystem, contextManager, and configLoader for hierarchical CLAUDE.md files.

interface ClaudeCodeRuntime {
  agentLoop: AgentLoop;
  toolRegistry: ToolRegistry;
  permissionSystem: PermissionManager;
  contextManager: ContextManager;
  configLoader: ConfigLoader;
}
async function main() {
  const runtime = await initializeRuntime();
  const config = await runtime.configLoader.loadHierarchical();
  const tools = await runtime.toolRegistry.registerAll(config);
  await runtime.agentLoop.run({ tools, permissions: runtime.permissionSystem, context: runtime.contextManager });
}

Agent Loop

The core loop follows a Perceive‑Decide‑Act‑Observe cycle and repeats until the LLM returns end_turn, the user aborts (Ctrl+C), the max iteration count (default 200) is reached, or the context window is exhausted.

async function agentLoop(messages: Message[], tools: Tool[], maxIterations: number = 200): Promise<Message[]> {
  let iteration = 0;
  while (iteration < maxIterations) {
    const context = await buildContext(messages);               // Perceive
    const response = await callLLM({                           // Decide
      system: context.systemPrompt,
      messages: context.messages,
      tools: tools.map(t => t.toSchema())
    });
    const toolCalls = extractToolCalls(response);
    if (toolCalls.length === 0) {
      messages.push({ role: "assistant", content: response.content });
      break;
    }
    for (const call of toolCalls) {                           // Act
      const permitted = await checkPermission(call);
      if (!permitted) { messages.push(createDeniedResult(call)); continue; }
      const result = await executeTool(call);
      messages.push(createToolResult(call, result));
    }
    iteration++;                                                // Observe
  }
  return messages;
}

Tool System

More than 20 built‑in tools are registered via a JSON schema, a risk level, and a requiresPermission flag. Example for the high‑risk Bash tool:

interface ToolDefinition {
  name: string;
  description: string;
  inputSchema: JSONSchema;
  requiresPermission: boolean;
  execute: (params: Record<string, unknown>) => Promise<ToolResult>;
  metadata: { readOnly: boolean; needsSandbox: boolean; riskLevel: "low" | "medium" | "high" };
}
const bashTool: ToolDefinition = {
  name: "Bash",
  description: "Executes a given bash command and returns its output.",
  inputSchema: {
    type: "object",
    required: ["command"],
    properties: {
      command: { type: "string", description: "The command to execute" },
      timeout: { type: "number", description: "Timeout in milliseconds" }
    }
  },
  requiresPermission: true,
  execute: async (params) => {
    const { command, timeout = 120000 } = params;
    return await sandboxedExec(command, { timeout });
  },
  metadata: { readOnly: false, needsSandbox: true, riskLevel: "high" }
};

High‑risk tools run inside sandbox‑exec on macOS or equivalent Linux sandboxes with a default 2‑minute timeout.

Context Management

Claude Code treats the LLM context window as a scarce resource and applies a three‑layer strategy:

Truncate large tool results when token usage exceeds ~70 %.

Summarise older messages around 85 % usage.

Discard the earliest messages once the window reaches 100 %.

// Truncate large tool result
function truncateToolResult(result: ToolResult, maxTokens: number): ToolResult {
  if (estimateTokens(result.content) <= maxTokens) return result;
  const head = result.content.slice(0, maxTokens * 0.4);
  const tail = result.content.slice(-maxTokens * 0.4);
  return { ...result, content: `${head}

... [truncated ${estimateTokens(result.content) - maxTokens} tokens] ...

${tail}` };
}

// Summarise old messages
async function summarizeOldMessages(messages: Message[], threshold: number): Promise<Message[]> {
  if (estimateTokens(messages) < threshold) return messages;
  const recentCount = 10;
  const recent = messages.slice(-recentCount);
  const old = messages.slice(0, -recentCount);
  const summary = await generateSummary(old);
  return [{ role: "system", content: `[Previous conversation summary]: ${summary}` }, ...recent];
}

Hierarchical CLAUDE.md Configuration

Configuration is loaded from three levels—global ( ~/.claude/CLAUDE.md), project root ( <project>/CLAUDE.md), and directory ( <cwd>/CLAUDE.md)—with later levels overriding earlier ones.

interface ClaudeConfig {
  source: "global" | "project" | "directory";
  path: string;
  content: string;
  priority: number; // higher = higher priority
}
async function loadClaudeConfigs(cwd: string): Promise<ClaudeConfig[]> {
  const configs: ClaudeConfig[] = [];
  const globalPath = path.join(os.homedir(), ".claude", "CLAUDE.md");
  if (await fileExists(globalPath)) configs.push({ source: "global", path: globalPath, content: await readFile(globalPath), priority: 0 });
  const projectRoot = await findProjectRoot(cwd);
  const projectPath = path.join(projectRoot, "CLAUDE.md");
  if (await fileExists(projectPath)) configs.push({ source: "project", path: projectPath, content: await readFile(projectPath), priority: 10 });
  const localPath = path.join(cwd, "CLAUDE.md");
  if (localPath !== projectPath && await fileExists(localPath)) configs.push({ source: "directory", path: localPath, content: await readFile(localPath), priority: 20 });
  return configs.sort((a, b) => a.priority - b.priority);
}
function buildSystemPrompt(configs: ClaudeConfig[]): string {
  const sections = configs.map(c => `# Instructions from ${c.source} config (${c.path}):
${c.content}`);
  return sections.join("

");
}

Permission System

Three permission levels control tool execution:

AUTO_ALLOW – read‑only tools are allowed automatically.

REQUIRES_CONFIRMATION – write‑type tools require explicit user confirmation.

STRICT – high‑risk operations such as shell execution require explicit approval.

enum PermissionLevel { AUTO_ALLOW = "auto_allow", REQUIRES_CONFIRMATION = "requires_confirmation", STRICT = "strict" }

class PermissionManager {
  private rules: PermissionRule[] = [];
  private sessionAllowList: Set<string> = new Set();

  async checkPermission(toolCall: ToolCall): Promise<PermissionResult> {
    if (toolCall.tool.metadata.readOnly) return { allowed: true, reason: "read-only operation" };
    const pattern = this.matchSessionRule(toolCall);
    if (pattern) return { allowed: true, reason: `session rule: ${pattern}` };
    const configRule = this.matchConfigRule(toolCall);
    if (configRule) return { allowed: true, reason: `config rule: ${configRule}` };
    const userDecision = await this.promptUser(toolCall);
    if (userDecision.rememberForSession) this.sessionAllowList.add(userDecision.pattern);
    return userDecision;
  }
}

Example prompt shown to the user:

Claude wants to run: git commit -m "fix: resolve null pointer"
  Allow?
    > Yes (y)
      Yes, and don't ask again for git commands (!)
    > No (n)

Claude Code vs Cursor

Architectural contrast:

Entry point : Claude Code runs from the terminal; Cursor is a VS Code extension.

Agent loop : Claude Code implements a native autonomous loop; Cursor relies on IDE events.

Tool execution : Claude Code uses sandboxed tools with a permission system; Cursor uses editor APIs.

Context source : Claude Code reads files and CLAUDE.md; Cursor consumes LSP data and opened files.

Best use case : Claude Code excels at complex, multi‑file refactoring; Cursor is suited for daily coding and completion.

Programmability : Claude Code offers high CLI/SDK programmability; Cursor is primarily GUI‑driven.

Design Principles

Safety First – every tool runs in a sandbox; dangerous commands are black‑listed; time‑outs prevent runaway processes.

User in Control – write‑operations always require confirmation; the user can abort the loop at any time.

Predictable Behavior – identical inputs and configuration produce identical outputs; errors are wrapped in ToolExecutionError with actionable suggestions.

class SafetyGuard {
  private static DANGEROUS_PATTERNS = [
    /rm\s+-rf\s+\//,
    /git\s+push\s+--force/,
    /chmod\s+777/,
    />(\/dev\/sda|\/dev\/disk)/,
    /mkfs\./
  ];
  static assess(command: string): RiskAssessment {
    for (const pattern of this.DANGEROUS_PATTERNS) {
      if (pattern.test(command)) return { level: "critical", message: "Potentially destructive command detected", requiresExplicitConfirmation: true };
    }
    return { level: "normal", requiresExplicitConfirmation: false };
  }
}
class ToolExecutionError extends Error {
  constructor(public toolName: string, public originalError: Error, public suggestion: string) {
    super(`Tool "${toolName}" failed: ${originalError.message}`);
  }
}

Common Pitfalls

Assuming the agent loop is synchronous – tool calls are asynchronous and may run in parallel.

Only truncating context without summarising – leads to loss of critical information.

Implementing a binary allow/deny permission model – a graded system with session memory is required.

Writing overly long CLAUDE.md files – they consume token budget; keep them under ~500 tokens.

Ignoring tool idempotency – repeated calls can cause duplicate writes or side‑effects.

Key Architectural Decisions

The Agent Loop provides autonomous task execution via a perceiving‑deciding‑acting‑observing cycle.

The Tool System enforces secure execution through registration, schema validation, sandboxing, and permission checks.

Context‑window management uses a three‑stage compress‑summarise‑discard pipeline to stay within token limits.

Hierarchical CLAUDE.md loading offers a reusable configuration pattern for AI agents.

Safety‑first, user‑control, and predictability are universal design tenets for AI agents.

Claude Code and Cursor complement each other; selection depends on task complexity and integration needs.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AI AgentPermission SystemContext ManagementClaude CodeAgent LoopTool RegistryCLAUDE.md
James' Growth Diary
Written by

James' Growth Diary

I am James, focusing on AI Agent learning and growth. I continuously update two series: “AI Agent Mastery Path,” which systematically outlines core theories and practices of agents, and “Claude Code Design Philosophy,” which deeply analyzes the design thinking behind top AI tools. Helping you build a solid foundation in the AI era.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.