Studio Notebook

Claude Code Atlas

Overall Architecture

Build the mental model of Claude Code before reading any one file in isolation.

Why this matters

Claude Code is easier to learn if you separate orchestration from the long-lived runtime it launches. main.tsx wires the boot path together, primes context before the query loop is live, and then hands control to the REPL that keeps the session interactive.

Big picture first

The top-level files in claude-code already expose the main shape of the system: main.tsx starts the app, context.ts prepares context, query.ts and QueryEngine.ts drive the turn lifecycle, Tool.ts and tools.ts define the action layer, and commands.ts defines user-facing slash commands.

Claude Code in one pass

One user turn moves from startup and context gathering into the query engine, tool execution, and REPL updates.

Boot And Session Setup Context And Prompting Query Engine Tools And Tasks REPL UI

From CLI Entry To Live REPL

The boot path is easiest to understand if you treat main.tsx as the orchestration layer. It does not contain every subsystem’s implementation; it coordinates setup, warms the context, and prepares the state that the session needs before interactive work begins.

This is the interactive path, not the non-interactive --print path. The boot code still primes tools, commands, and agent definitions up front, but the interactive session keeps the live handoff separate from the headless case.

profileCheckpoint('main_tsx_entry');

maybeActivateProactive(options);
let tools = getTools(toolPermissionContext);

const setupPromise = setup(
  preSetupCwd,
  permissionMode,
  allowDangerouslySkipPermissions,
  worktreeEnabled,
  worktreeName,
  tmuxEnabled,
  sessionId ? validateUuid(sessionId) : undefined,
  worktreePRNumber,
  messagingSocketPath,
);
const commandsPromise = worktreeEnabled ? null : getCommands(preSetupCwd);
const agentDefsPromise = worktreeEnabled ? null : getAgentDefinitionsWithOverrides(preSetupCwd);
await setupPromise;

const [commands, agentDefinitionsResult] = await Promise.all([
  commandsPromise ?? getCommands(currentCwd),
  agentDefsPromise ?? getAgentDefinitionsWithOverrides(currentCwd),
]);

Once boot work is done, await launchRepl(root, { is the handoff point. main.tsx stops owning the startup choreography and passes control into the long-lived interactive session.

const initialMessages = deepLinkBanner
  ? [deepLinkBanner, ...hookMessages]
  : hookMessages.length > 0
    ? hookMessages
    : undefined;

await launchRepl(root, {
  getFpsMetrics,
  stats,
  initialState,
}, {
  ...sessionConfig,
  initialMessages,
  pendingHookMessages,
}, renderAndRun);

The first render is not the end of warmup. renderAndRun() renders the REPL, starts deferred prefetches, and then waits for the session to exit. That is where context warmup continues after the UI is already live.

export async function renderAndRun(root: Root, element: React.ReactNode): Promise<void> {
  root.render(element);
  startDeferredPrefetches();
  await root.waitUntilExit();
  await gracefulShutdown(0);
}

export function startDeferredPrefetches(): void {
  void initUser();
  void getUserContext();
  prefetchSystemContextIfSafe();
  void getRelevantTips();
}

The Action And Command Registries

Claude Code does not hard-code every tool and command directly inside main.tsx. Instead, the boot path asks registry files to assemble the concrete tool and command sets from smaller pieces, which keeps the orchestration layer thin and the subsystem boundaries readable.

tools.ts assembles the action layer for the model. It defines the base tools the model can call, including the core agent and shell actions, and it can add or skip embedded-search-related tools based on runtime capability.

export function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    // Ant-native builds have bfs/ugrep embedded in the bun binary (same ARGV0
    // trick as ripgrep). When available, find/grep in Claude's shell are aliased
    // to these fast tools, so the dedicated Glob/Grep tools are unnecessary.
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    ExitPlanModeV2Tool,
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    NotebookEditTool,
    WebFetchTool,
    TodoWriteTool,
    WebSearchTool,
    TaskStopTool,
    AskUserQuestionTool,
    SkillTool,
    EnterPlanModeTool,

commands.ts assembles the user-facing slash-command layer. It loads the available commands, filters them for the current workspace, and keeps the interactive command surface separate from the model-facing tool pool.

what to notice in the list: fixed built-ins first, then conditional spreads, then the internal-only tail.

const COMMANDS = memoize((): Command[] => [
  addDir,
  advisor,
  agents,
  branch,
  btw,
  chrome,
  clear,
  color,
  compact,
  config,
  copy,
  desktop,
  context,
  contextNonInteractive,
  cost,
  diff,
  doctor,
  effort,
  exit,
  fast,
  files,
  heapDump,
  help,
  ide,
  init,
  keybindings,
  installGitHubApp,
  installSlackApp,
  mcp,
  memory,
  mobile,
  model,
  outputStyle,
  remoteEnv,
  plugin,
  pr_comments,
  releaseNotes,
  reloadPlugins,
  rename,
  resume,
  session,
  skills,
  stats,
  status,
  statusline,
  stickers,
  tag,
  theme,
  feedback,
  review,
  ultrareview,
  rewind,
  securityReview,
  terminalSetup,
  upgrade,
  extraUsage,
  extraUsageNonInteractive,
  rateLimitOptions,
  usage,
  usageReport,
  vim,
  ...(webCmd ? [webCmd] : []),
  ...(forkCmd ? [forkCmd] : []),
  ...(buddy ? [buddy] : []),
  ...(proactive ? [proactive] : []),
  ...(briefCommand ? [briefCommand] : []),
  ...(assistantCommand ? [assistantCommand] : []),
  ...(bridge ? [bridge] : []),
  ...(remoteControlServerCommand ? [remoteControlServerCommand] : []),
  ...(voiceCommand ? [voiceCommand] : []),
  thinkback,
  thinkbackPlay,
  permissions,
  plan,
  privacySettings,
  hooks,
  exportCommand,
  sandboxToggle,
  ...(!isUsing3PServices() ? [logout, login()] : []),
  passes,
  ...(peersCmd ? [peersCmd] : []),
  tasks,
  ...(workflowsCmd ? [workflowsCmd] : []),
  ...(torch ? [torch] : []),
  ...(process.env.USER_TYPE === 'ant' && !process.env.IS_DEMO
    ? INTERNAL_ONLY_COMMANDS
    : []),
])

getCommands() turns that registry into the workspace-specific command list. It applies availability checks, folds in dynamic skills, and then returns the final interactive surface.

export async function getCommands(cwd: string): Promise<Command[]> {
  const allCommands = await loadAllCommands(cwd)

  // Get dynamic skills discovered during file operations
  const dynamicSkills = getDynamicSkills()

  // Build base commands without dynamic skills
  const baseCommands = allCommands.filter(
    _ => meetsAvailabilityRequirement(_) && isCommandEnabled(_),
  )

  if (dynamicSkills.length === 0) {
    return baseCommands
  }

  // Dedupe dynamic skills - only add if not already present
  const baseCommandNames = new Set(baseCommands.map(c => c.name))
  const uniqueDynamicSkills = dynamicSkills.filter(
    s =>
      !baseCommandNames.has(s.name) &&
      meetsAvailabilityRequirement(s) &&
      isCommandEnabled(s),
  )

  if (uniqueDynamicSkills.length === 0) {
    return baseCommands
  }

  // Insert dynamic skills after plugin skills but before built-in commands
  const builtInNames = new Set(COMMANDS().map(c => c.name))
  const insertIndex = baseCommands.findIndex(c => builtInNames.has(c.name))

  if (insertIndex === -1) {
    return [...baseCommands, ...uniqueDynamicSkills]
  }

  return [
    ...baseCommands.slice(0, insertIndex),
    ...uniqueDynamicSkills,
    ...baseCommands.slice(insertIndex),
  ]
}

Read together, these registries show the split in the runtime. tools.ts answers the question “what can the model do right now?”, while commands.ts answers the question “what can the person type at the prompt?”

Code Layout At A Glance

main.tsx Boots the application, prepares the session, and hands control to the REPL.

context.ts, constants/prompts.ts, utils/queryContext.ts Shape the system and user context before a turn runs.

query.ts, QueryEngine.ts Own the turn lifecycle and model interaction.

Tool.ts, tools.ts, tools/ Define the shared tool contract and the concrete tool pool.

commands.ts, commands/ Define the slash-command surface and dynamic command loading.

tasks/ Holds longer-lived or background work that does not fit a single tool call.

state/, screens/ Hold the interactive shell state and REPL-facing UI flow.

services/ Contains support systems like MCP, analytics, compacting, API access, and more.

bridge/, remote/ Hold non-local or connected-mode behavior that extends the same core runtime.

How this part breaks down

  1. startup-and-boot-sequence Start with how main.tsx gets a session off the ground.
  2. context-and-prompt-construction See how repo state, memory, and prompt sections become model-visible context.
  3. the-query-engine-and-turn-lifecycle Follow one full model turn from prepared messages to streamed output.
  4. tools-and-permission-model Learn how Claude Code turns model intent into executable actions.
  5. tasks-agents-and-background-execution See how long-running work and subagents fit around the main loop.
  6. interaction-shell-and-app-state Learn how the REPL and app state hold the interactive session together.
  7. commands-and-user-control See how slash commands expose user-facing control paths.
  8. extensions-skills-plugins-and-mcp Learn how Claude Code grows beyond its built-in behavior.
  9. remote-bridge-and-connected-modes See how non-local and connected modes plug into the same architecture.
  10. services-behind-the-scenes Learn which support layers keep the runtime working.
  11. special-features-and-memorable-corners Leave memorable extras like buddy and voice for after the main model is clear.

Takeaways

  • The architecture chapter is a map, not a replacement for the deeper subtree chapters.
  • main.tsx orchestrates, but it does not own every subsystem.
  • The registries in tools.ts and commands.ts bridge architecture to concrete behavior.

Fun Facts

  • The codebase has memorable side systems like buddy and bridge, but they make more sense once the main loop is clear.