The core agent loop is a simple while-loop. Most of the code lives in the systems around it.
This chapter describes Claude Code v2.1.88, the source snapshot analyzed in our paper. For later developments and broader design choices, see the builder's guide and source notes.
| Design Question | Claude Code's Answer | Alternatives |
|---|---|---|
| Where does reasoning live? | Model reasons; harness enforces. ~1.6% AI decision logic, 98.4% infrastructure. | LangGraph: explicit state graphs. Devin: multi-step planners. |
| How many execution engines? | One queryLoop for all interfaces (CLI, SDK, IDE). |
Mode-specific engines per surface. |
| What is the default safety posture? | Deny-first: deny > ask > allow. Strictest rule wins. | Container isolation (SWE-Agent), git rollback (Aider). |
| What is the binding resource constraint? | A finite context window. Five pre-model stages apply according to configuration and context pressure. | Compute budget, explicit scratchpad. |
- User -- Submits prompts, approves permissions, reviews output
- Interfaces -- Interactive CLI, headless CLI (
claude -p), Agent SDK, IDE/Desktop/Browser - Agent Loop --
queryLoopasync generator inquery.ts: model call → tool dispatch → result collection → repeat - Permission System -- Deny-first rules + auto-mode ML classifier + hook interception
- Tools -- Up to 54 built-in + MCP-provided, assembled via
assembleToolPool - State & Persistence -- Append-only JSONL transcripts, prompt history, subagent sidechains
- Execution Environment -- Shell (with sandbox), filesystem, web fetching, MCP connections
All interfaces converge on the same queryLoop -- the interactive CLI, headless mode, SDK, and IDE all share the same code path. QueryEngine is a conversation wrapper, not the engine itself.
| Layer | Responsibility | Key Components |
|---|---|---|
| Surface | Entry points & rendering | CLI, headless, SDK, IDE (React + Ink terminal UI) |
| Core | Context assembly & agent loop | queryLoop, 5-stage compaction pipeline, subagent spawning |
| Safety/Action | Permissions & tools | 7 permission modes, auto-mode classifier, 27 hook events, tool pool, shell sandbox |
| State | Runtime state & persistence | JSONL transcripts, CLAUDE.md hierarchy, auto-memory, sidechain files |
| Backend | Execution environments | Shell execution, MCP connections (7 transport types), 42 tool subdirectories |
The paper groups protection mechanisms into seven layers. The checks that apply depend on the tool, mode, and configuration; separate layers can still share failure modes.
- Tool pre-filtering -- Blanket-denied tools removed from model's view entirely
- Deny-first rule evaluation -- Deny always overrides allow, even when allow is more specific
- Permission mode constraints -- Active mode determines baseline handling
- Auto-mode ML classifier -- Separate LLM call evaluating safety independently
- Shell sandboxing -- Filesystem + network isolation for shell commands
- Session-scoped permission state -- The session app allowlist is not restored on resume; the session bypass flag is not persisted.
- Hook-based interception -- PreToolUse hooks can modify or block actions
In the legacy Bash parsing path, commands that split into more than 50 subcommands return an ask decision, keeping per-subcommand analysis from overwhelming the event loop.
Each turn follows a 9-step pipeline:
- Settings resolution → 2. State initialization → 3. Context assembly → 4. Applicable pre-model context stages → 5. Model call → 6. Tool dispatch → 7. Permission gate → 8. Tool execution → 9. Stop condition check
The query loop considers these stages in order before a model call. Each changes the context only when its conditions are met:
| Stage | Strategy | Trigger |
|---|---|---|
| Budget Reduction | Per-message tool-result size caps | Budget state enabled and limits exceeded |
| Snip | Trim older history | Feature-gated (HISTORY_SNIP) |
| Microcompact | Clear older tool results | Time threshold on eligible main-thread requests, or an optional cache-editing path |
| Context Collapse | Read-time virtual projection (non-destructive) | Feature-gated (CONTEXT_COLLAPSE) |
| Auto-Compact | Model-generated summary | Enabled and above threshold, subject to mode and failure guards |
- Max output token escalation (up to 3 retries per turn)
- Reactive compaction (fires at most once per turn)
- Prompt-too-long: tries context-collapse overflow → reactive compaction → terminate
- Streaming fallback and fallback model switching
| Mode | Behavior | Trust Level |
|---|---|---|
plan |
User approves all plans before execution | Lowest |
default |
Standard interactive approval | Low |
acceptEdits |
File edits + filesystem shell auto-approved | Medium |
auto |
ML classifier evaluates tool safety | High |
dontAsk |
No prompting, deny rules still enforced | Higher |
bypassPermissions |
Skips most prompts, safety-critical checks remain | Highest |
bubble |
Internal: subagent escalation to parent | Special |
4-stage flow: Pre-filtering (strip denied tools from model's view) → PreToolUse hooks (can return permissionDecision) → Rule evaluation (deny-first) → Permission handler (4 branches: coordinator, swarm worker, speculative classifier, interactive)
yoloClassifier.ts: loads base system prompt + permission templates (separate internal/external). Two-stage evaluation: fast-filter + chain-of-thought. Races pre-computed classification against a timeout.
Context cost depends on the text the model receives and when it is loaded. Hooks can inject context, and model-based hooks can also incur separate inference cost.
| Mechanism | What Can Enter the Model's Context | Key Capability |
|---|---|---|
| Hooks | Optional context returned by a hook | 27 events, 4 execution types (shell, LLM, webhook, subagent verifier) |
| Skills | Descriptions and relevant skill instructions | SKILL.md with 15+ YAML frontmatter fields, injected via SkillTool meta-tool |
| Plugins | Context from the components they enable | 10 component types (commands, agents, skills, hooks, MCP, LSP, styles...) |
| MCP Servers | Tool schemas and results, depending on discovery and loading | External tools via 7 transport types (stdio, SSE, HTTP, WebSocket, SDK, IDE) |
Base enumeration (up to 54 tools) → Mode filtering → Deny rule pre-filtering → MCP integration → Deduplication
- assemble() -- What the model sees: CLAUDE.md, skill descriptions, MCP resources, hook-injected context
- model() -- What the model can reach: built-in tools, MCP tools, SkillTool, AgentTool
- execute() -- Whether/how an action runs: permission rules, PreToolUse/PostToolUse hooks, Stop hooks
System prompt → Environment info → CLAUDE.md hierarchy → Path-scoped rules → Auto-memory → Tool metadata → Conversation history → Tool results → Compact summaries
| Level | Path | Scope |
|---|---|---|
| Managed | /etc/claude-code/CLAUDE.md |
System-wide (enterprise) |
| User | ~/.claude/CLAUDE.md |
Per-user |
| Project | CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md |
Per-project |
| Local | CLAUDE.local.md |
Personal (gitignored) |
Instructions and enforcement are separate. CLAUDE.md is injected as user context. Both this context and the system prompt guide the model; instruction priority does not make compliance deterministic. Permission checks and sandbox restrictions constrain actions at execution time.
- No embeddings, no vector DB -- uses LLM-based scan of memory-file headers
- Selects up to 5 relevant files on demand
- Fully inspectable, editable, version-controllable by the user
Built-in: Explore, Plan, General-purpose, Claude Code Guide, Verification, Statusline-setup.
Custom: .claude/agents/*.md with YAML frontmatter supporting tools, model, permissions, hooks, skills, and more.
- SkillTool: Injects instructions into the current conversation.
- AgentTool: Runs a subtask in a separate conversation, with model and coordination costs that depend on the task.
Conversation separation limits how much intermediate work enters the parent context. Filesystem access and permissions depend on the execution backend and configuration.
| Mode | Mechanism | Default |
|---|---|---|
| Worktree | Git worktree (filesystem isolation) | No |
| Remote | Remote execution (internal-only) | No |
| In-process | Shared filesystem, isolated conversation | Yes |
Subagent transcripts use separate .jsonl files. The parent receives result content through AgentTool; transcript storage and the returned result serve different purposes. Multi-instance coordination via POSIX flock() -- zero external dependencies.
| Channel | Format | Purpose |
|---|---|---|
| Session transcripts | Append-only JSONL | Full conversation, chain-patched compaction boundaries |
| Global prompt history | history.jsonl |
Cross-session prompt recall (reverse-read for Up-arrow) |
| Subagent sidechains | Separate JSONL per subagent | Isolated subagent histories |
In the v2.1.88 snapshot, the session app allowlist is not restored on resume, and the session bypass flag is not persisted. These specific rules concern temporary session state; they do not establish a general rule for restoring persistent policies or operating modes.
Append-only JSONL favors auditability and simplicity over query power. Every event is human-readable, version-controllable, and reconstructable without specialized tooling.






