Agent Architecture & Harnesses · 2026

Dive into Claude Code: The Design Space of Today's and Future AI Agent Systems

Agent Architecture & Harnesses Dive into Claude Code 2026 · arXiv 2604.14228
Topic
Agent Architecture & Harnesses
Venue
Apr 2026
Read
22 min
Source
arXiv:2604.14228

In one line

A line-by-line reverse-engineering of Claude Code's TypeScript source that shows a production coding agent is not a clever model loop — it is a thin reasoning core (~1.6% of the code) wrapped in a dense deterministic harness for permissions, context compaction, extensibility, delegation, and persistence, and that harness is where all the real engineering (and your build/moat opportunity) lives.

The breakdown

TL;DR

Everyone building agents obsesses over prompts and model choice. This paper studies the publicly-released Claude Code source and finds the opposite emphasis: the agent loop itself is a boring while-true that calls the model, runs tools, and repeats — roughly 1.6% of the codebase. The other 98.4% is operational infrastructure: a seven-mode permission system with an ML safety classifier, a five-layer context-compaction pipeline, four distinct extension mechanisms graded by context cost, an isolated-subagent delegation system, and append-only session storage. The authors trace five human “values” through thirteen design principles down to specific source files, contrast every choice with an open-source alternative (OpenClaw), and end with six open directions. The single most useful takeaway for a builder: separate reasoning from enforcement, and invest your effort in the harness, not the scaffolding — that is what makes an agent safe, recoverable, and extensible in production.

Problem & Motivation

If you have built a multi-agent system, you know the gap between “the demo works” and “I would let this run rm on a client’s repo.” The pain is concrete:

  • Approval fatigue makes the human a fake safety layer. Anthropic’s own data: users approve ~93% of permission prompts. A confirmation dialog everyone clicks “yes” on protects no one.
  • Context windows are the real bottleneck, not intelligence. A long coding session blows past 200K/1M tokens; naive truncation loses the very state the agent needs.
  • A compromised or jailbroken model can do real damage if the model can directly touch the filesystem or shell.
  • Frameworks over-scaffold. Tools like LangGraph encode control flow as explicit state graphs; as models get smarter, those rigid graphs become a straitjacket.
  • Nobody publishes the architecture. Anthropic ships great user docs but no design doc. Builders are left guessing how a system that actually works at scale is wired.

The authors’ move is unusual and valuable: instead of theorizing, they read the shipped source (v2.1.88) and report what the production system actually does, file by file. This is a systems case study, not a new model or algorithm — and that is exactly why it is useful to someone deciding what to build.

What’s New (Core Contribution)

This is a survey/architecture paper, so “novelty” means the analysis and the framing, not a new method:

  1. A source-grounded design-space map. Before: practitioner blog posts and reverse-engineering threads. Now: a structured account that names the recurring design questions every coding agent must answer (where reasoning lives, loop structure, safety posture, extension surface, context strategy, delegation, persistence) and ties each answer to specific source files (query.ts, permissions.ts, yoloClassifier.ts, etc.).
  2. The values → principles → implementation chain. Before: “Anthropic cares about safety.” Now: five explicit values (human decision authority, safety/security/privacy, reliable execution, capability amplification, contextual adaptability) traced through thirteen named design principles down to code. This turns vibes into a checklist you can apply to your own system.
  3. The “thin reasoning, fat harness” finding, quantified. Before: a hunch that “the magic is in the model.” Now: a documented split — ~1.6% AI decision logic vs. ~98.4% operational infrastructure — that reframes where engineering effort actually goes.
  4. A controlled architectural contrast (OpenClaw). Before: single-system descriptions. Now: the same design questions answered differently because the deployment context differs (ephemeral CLI vs. persistent multi-channel gateway), which isolates which choices are fundamental vs. context-driven.
  5. Six open directions grounded in current literature — the observability/eval gap, cross-session persistence, harness-boundary evolution, horizon scaling, governance, and the “long-term human capability” lens.

Be honest about the hype: there is no new algorithm here and no benchmark win. The value is a clear, sourced mental model of a system you are probably already paying to use — and a set of design questions you can reuse verbatim when architecting your own agents for clients.

How It Works (Technically)

The whole system is best understood as one tiny loop surrounded by guardrail subsystems. Let me build it up from the center.

The core: a reactive ReAct loop

At the heart is queryLoop() (an async generator in query.ts). One turn of the loop:

  1. Settings resolution — pull immutable params (system prompt, model config, permission callback).
  2. Mutable state init — one State object holds everything (messages, tool context, compaction tracking, recovery counters). Critically, the loop’s seven “continue sites” replace the whole object in a single assignment rather than mutating fields. This is a deliberate choice for reasoning about correctness — there is exactly one place state changes per branch.
  3. Context assemblygetMessagesAfterCompactBoundary() grabs history from the last compaction point forward (so summarized content shows up as its summary, not the raw messages).
  4. Pre-model shapers — five context-compaction passes run before every model call (more below).
  5. Model call — stream the model’s response.
  6. Tool dispatch — if the response has tool_use blocks, route them.
  7. Permission gate — every tool request must pass the permission system.
  8. Execute + collect — results become tool_result messages appended to history.
  9. Stop condition — if the model returns text with no tool calls, the turn is done.

This is the ReAct pattern (reason → act → observe → repeat). The key design stance: the model decides, the harness enforces and executes. The model emits structured tool_use blocks and never directly touches the filesystem, shell, or network. That single boundary is the security thesis of the whole system — a jailbroken model still cannot bypass the permission checks because reasoning and enforcement live in separate code paths.

Architecture & data flow

flowchart TB
  U[User / Interfaces<br/>CLI · headless · SDK · IDE] --> AL
  subgraph CORE[Core Layer]
    AL[Agent Loop · queryLoop&#40;&#41;]
    CA[Context Assembly]
    CP[Compaction Pipeline<br/>5 shapers]
    CA --> AL
    CP --> AL
  end
  AL -->|tool_use| PERM
  subgraph SAFETY[Safety / Action Layer]
    PERM[Permission System<br/>7 modes · deny-first]
    CLF[Auto-mode ML Classifier]
    HOOK[Hook Pipeline · 27 events]
    SBX[Shell Sandbox]
    PERM --> CLF
    PERM --> HOOK
  end
  PERM -->|allow| TOOLS[Tools · up to 54 built-in + MCP]
  TOOLS --> SBX
  SBX --> ENV[Execution Environment<br/>shell · files · web · MCP]
  ENV -->|tool_result| AL
  PERM -.->|deny + reason| AL
  AL --> SUB[Subagent Spawn<br/>isolated context · summary-only return]
  SUB --> AL
  AL --> STATE[State &amp; Persistence<br/>append-only JSONL · sidechains]

The seven-component spine: User → Interfaces → Agent Loop → Permission System → Tools → Execution Environment, with State & Persistence alongside. Every interface (interactive CLI, headless claude -p, Agent SDK, IDE) feeds the same loop — only rendering differs.

Schematic walkthrough of one agentic turn. Click "Step" to advance: context is assembled, the model proposes a tool call, the permission gate decides allow/ask/deny, the tool runs, the result feeds back, and compaction kicks in under context pressure. A denial is shown as a *routing signal* (the model retries) rather than a hard stop.

The permission system (the heart of “safety”)

Seven layers, any one of which can block a request:

  1. Tool pre-filtering (tools.ts) — blanket-denied tools are stripped from the model’s view before any call, so the model never even sees them.
  2. Deny-first rule evaluation (permissions.ts) — deny always beats allow, even a more specific allow. A broad “deny all shell” cannot be overridden by a narrow “allow npm test.”
  3. Permission mode (types/permissions.ts) — seven modes from plan (approve every plan) through default, acceptEdits, auto, dontAsk, bypassPermissions, to the internal bubble. This is the graduated trust spectrum.
  4. Auto-mode ML classifier (yoloClassifier.ts) — when enabled, an LLM-based classifier evaluates the proposed tool call against the conversation transcript and a permissions template, returning allow / deny / ask-human. A two-stage design: a fast filter then a chain-of-thought safety evaluation. For Bash specifically, a speculative classifier races a pre-started classification against a timeout — if it returns high-confidence-safe, the tool runs instantly with no dialog.
  5. Shell sandboxing (shouldUseSandbox.ts) — an orthogonal axis: an approved command can still run in a filesystem/network-isolated sandbox. Authorization ≠ isolation.
  6. No permission restore on resume (conversationRecovery.ts) — session-scoped grants are deliberately not serialized; a resumed session re-earns trust via deny-first prompting. This is a safety feature, not a bug.
  7. HooksPreToolUse can deny/ask/rewrite-input; PermissionRequest can resolve asynchronously.

The crucial behavioral insight: a denial is a routing signal, not a halt. The model gets the denial reason, revises, and tries a safer path next iteration. Permission enforcement shapes behavior rather than stopping it.

The five-layer compaction pipeline (the heart of “context management”)

No single compaction strategy handles all context pressure, so five run in sequence (cheap first), every turn:

  1. Budget reduction — caps individual tool-result sizes, replacing oversized output with a content reference (recoverable on resume).
  2. Snip — lightweight removal of older history segments.
  3. Microcompact — fine-grained, time-based (and optionally cache-aware) compression keyed by tool_use_id.
  4. Context collapse — a read-time projection: it does not mutate stored history; it swaps in a collapsed view so the model sees less while the full history stays on disk for reconstruction.
  5. Auto-compact — last resort: an actual model-generated summary of the conversation (compactConversation() in compact.ts), fired only if pressure remains after the first four.

This is the operational definition of the principle “context as a scarce resource with progressive management.” Earlier, cheaper layers run before costlier ones.

Subagent delegation (the heart of “multi-agent”)

The Agent tool (AgentTool.tsx, legacy alias Task) spawns a subagent by re-entering the same queryLoop() with an isolated context window. Six built-in types (Explore, Plan, General-purpose, Guide, Verification, Statusline-setup) plus user-defined .claude/agents/*.md files whose markdown body is the system prompt and whose YAML frontmatter declares tools, model, permissions, hooks, memory scope, isolation mode, etc.

Three isolation modes: worktree (a temporary git worktree — filesystem separation with zero container infrastructure), remote (internal only), and in-process (shared filesystem, separate conversation). The defining choice: summary-only return. The subagent writes its full transcript to a sidechain .jsonl file (for debugging/audit), but only its final response text re-enters the parent context. This is the deliberate antidote to context explosion — conversation-based frameworks (AutoGen-style) that share full histories grow context with the number of agents. Even so, agent teams cost ~7× a normal session’s tokens, which is exactly why summary-only return matters.

Multi-instance coordination uses file locking, not a message broker — tasks claimed from a shared list via lock files at predictable paths. Trades throughput for zero-dependency deployment and full debuggability (any agent’s state is plain-text JSON you can cat).

The algorithm, simplified

# One turn of Claude Code's agent loop. The whole "agent" is this;
# everything else in the paper is a subsystem this loop calls into.
def query_loop(state, system_prompt, permission, model_cfg):
    while not state.stopped:
        # 1. assemble what the model sees, running 5 compaction shapers cheap->expensive
        msgs = get_messages_after_compact_boundary(state.history)
        for shaper in [budget_reduce, snip, microcompact, context_collapse, auto_compact]:
            msgs = shaper(msgs)               # each only fires under enough context pressure

        # 2. model reasons and proposes actions as structured tool_use blocks
        resp = model(system_prompt, msgs, state.tools, cfg=model_cfg)

        if not resp.tool_uses:                # text-only response => turn is done
            state.stopped = run_stop_hooks(resp)
            yield resp; continue

        # 3. gate + execute each proposed tool call (concurrent reads, serial writes)
        for call in resp.tool_uses:
            decision = permission(call)        # deny-first: deny > ask > allow; classifier + hooks
            if decision.denied:
                # a denial is a ROUTING SIGNAL: feed the reason back, model retries safer
                state.history.append(deny_feedback(call, decision.reason))
                continue
            call = run_pre_tool_hooks(call)    # may rewrite input or block
            result = execute(call)             # harness touches shell/fs/web; model never does
            result = run_post_tool_hooks(result)
            state.history.append(call, result) # append-only; persisted to JSONL on disk

The contribution is not this loop (it is ~standard ReAct). The contribution is recognizing that this is the easy part and the hard, valuable part is permission(), the shapers, the hooks, and persistence.

Built on Prior Work

Prior ideaWhat it gaveWhat this paper adds / how Claude Code changes it
ReAct (Yao 2022)Reason→act→observe loopThe core loop is ReAct; the paper shows the loop is the small part
Toolformer (Schick 2023)Models can learn to call toolsClaude Code uses ~54 built-in tools behind a layered permission gate
LangGraph (LangChain 2024)Control flow as explicit state graphClaude Code rejects scaffolding — reactive loop, no planning graph
SWE-Agent / OpenHands (2024)Docker container isolation as the safety boundaryClaude Code uses per-action deny-first rules + optional sandbox instead
Aider (Gauthier 2024)Git rollback as the primary safety netClaude Code uses deny-first evaluation; git worktrees only for subagent isolation
AutoGen (Wu 2024)Conversation-based multi-agent, shared transcriptsClaude Code uses isolated subagents with summary-only return to bound context
LATS (Zhou 2023)Tree-search over action trajectoriesClaude Code’s plan mode is a simpler plan-then-execute, no backtracking
MemGPT (Packer 2023)LLM-as-OS with paged memoryCited as a future direction for cross-session persistence (not yet in CC)
Reflexion (Shinn 2023)Verbal self-feedback across attemptsCited for the cross-session memory gap
“Building Effective Agents” (Anthropic)Simple composable patterns > frameworksThe paper shows CC instantiates the orchestrator-workers pattern for subagents

Results & Evidence

This is a descriptive paper, so “results” are findings about the system plus supporting empirical citations — not a benchmark table.

What the evidence establishes:

  • The architecture is real and coherent: every claim traces to a named source file in v2.1.88. This is strong, falsifiable, source-grounded analysis.
  • The thin-reasoning/fat-harness split (~1.6% / ~98.4%) — though this specific number comes from community analysis of the extracted source, not the authors’ own count, so treat it as directional.
  • Supporting external numbers: ~27% of Claude-Code-assisted tasks were work “that would not have been attempted” (internal survey, n=132); 93% permission-approval rate (motivating deny-first); auto-approve climbs from ~20% (<50 sessions) to >40% (750 sessions), evidencing co-constructed trust; agent teams cost ~7× tokens.
  • The OpenClaw contrast cleanly isolates context-driven vs. fundamental choices.

What it does NOT establish (caveats — read these before you cite it to a client):

  • No causal or comparative performance claims. Nothing here says Claude Code’s architecture produces better code than LangGraph or OpenHands. It is “here is how it’s built,” not “here is why it wins.”
  • It is a snapshot of one version. Feature flags gate much of the behavior (TRANSCRIPT_CLASSIFIER, BASH_CLASSIFIER, REACTIVE_COMPACT, etc.), and the source moves fast. Some described paths may be off by default or already changed.
  • The reverse-engineered source is unofficial. Anthropic did not bless this reading; some interpretations of intent are inference.
  • The “long-term human capability” lens is the authors’ framing, not Anthropic’s design driver — and the supporting studies (e.g., 17% lower comprehension under AI assistance) are external and contested.
  • No reproducible artifact you run — you cannot re-execute “the experiment”; you can only re-read the source.

The honest read: this is an excellent map, not a scoreboard.

How You’d Use It

For someone running an AI services company, this paper is essentially a free architecture spec for the agent you keep getting asked to build. Concrete uses:

  • Adopt the reasoning/enforcement split as your security story. When a client asks “how do you stop the agent from rm -rf-ing prod?”, your answer is: the model only emits structured tool requests; a separate deterministic layer (deny-first rules + sandbox) decides and executes. That is a sellable, auditable design — far stronger than “we prompt it to be careful.”
  • Steal the seven design questions as a discovery checklist. For any client agent project, answer: Where does reasoning live? How many execution engines? Default safety posture? Binding resource constraint? Extension surface? Delegation model? Persistence model? This turns a vague “build us an agent” into a scoped statement of work.
  • Treat context as the bottleneck, not the model. Bill for and build a compaction strategy. Even a two-layer version (per-result budget + summarize-on-pressure) is a differentiator most DIY client agents lack.
  • Use the four-mechanism extension model to scope integrations. Hooks (zero context) for lifecycle/guardrails, skills (low) for domain instructions, MCP (high) for real tool integrations. This maps directly onto “what does this client integration actually need” and prevents context bloat.
  • Subagents with summary-only return is your pattern for “the agent needs to explore a big codebase without poisoning its own context.” You have built MAS before — this is the production-grade version of role isolation you already understand, with the context-explosion problem already solved.
  • Append-only JSONL + sidechains gives you audit logs clients in regulated industries will demand, for nearly free.

The build-vs-buy read: for coding tasks, buy Claude Code (or its SDK) and extend it via MCP/skills/hooks — re-implementing the harness is months of work. For non-coding vertical agents, build using these patterns as the blueprint.

Build Your Own (Minimal Recipe)

The smallest agent that captures ~80% of the value and the spirit of this architecture:

Components (build in this order):

  1. The loop. A while loop: assemble context → call model with a tool schema → parse tool_use → execute → append result → stop on text-only. ~50 lines. Use any model with tool-calling; the Anthropic or OpenAI SDK both work.
  2. The enforcement boundary. The one non-negotiable: the model never executes anything directly. A permit(tool_call) -> allow|ask|deny function sits between proposal and execution. Start with static deny-first rules (regex/prefix match on tool + args). This is the part that makes it safe.
  3. Append-only transcript. Write every message/tool-call/result to a JSONL file as it happens. Free audit log + resume.
  4. A two-layer compaction. (a) cap each tool result to N chars; (b) when total tokens > threshold, summarize the oldest half with a model call and replace it with the summary. That is 80% of the five-layer pipeline’s benefit.
  5. Subagent-by-recursion. A spawn(prompt, allowed_tools) that calls your own loop with a fresh context and returns only the final text. Write its full transcript to a sidechain file.

The 1–2 genuinely hard parts:

  • The classifier / auto-mode. A reliable “is this tool call safe to auto-run?” judge is the real research problem. Start with rules; add an LLM-judge classifier (with a strict deny-by-default fallback on low confidence) only when approval fatigue bites. The speculative-race trick (start the classifier early, race a timeout) is a nice latency optimization but optional.
  • Compaction that doesn’t lose load-bearing detail. Summarization silently drops the one constraint the agent needed. Mitigate by keeping file references recoverable (Claude Code’s “content reference” trick) rather than discarding content outright.

Reach for: Anthropic Agent SDK or a thin custom loop; MCP for tool integrations; git worktree for subagent filesystem isolation (no Docker needed); plain JSONL for state.

How to Improve It

Limitations the paper itself surfaces, reframed as things you could build and possibly sell:

  1. Cross-session memory substrate. Today CLAUDE.md (static instructions) and the JSONL transcript (one session) bracket a gap: durable, accumulating state that is neither. Build a file-based, version-controllable memory layer (MemGPT-style paging, or Reflexion-style accumulated self-critiques) that survives across sessions without re-introducing the resume-trust problem. This is an open, valuable, buildable gap.
  2. External auditability for compliance. Deny-first decisions are internally logged but not externally auditable in the form the EU AI Act / GPAI Code of Practice will want. A “regulator-facing audit interface” over the transcript is a concrete product for clients facing the Aug 2026 AI Act applicability.
  3. Fix the documented defense-in-depth failure mode. Researchers found commands with >50 subcommands fall back to a single generic approval (per-subcommand parsing froze the UI). A smarter incremental/streamed parser that preserves per-subcommand deny checks under load is a real, testable safety fix.
  4. Generator–evaluator separation applied to verification. The paper notes agents “confidently praise their own work.” Add a separate verification subagent (different prompt, maybe different model) that must independently confirm success before the turn declares done. Cheap, high-value, measurable on a test suite.
  5. Proactivity timing (the “when” axis). KAIROS-style proactive suggestions raised task pass rate +12–18% but tanked preference at high frequency. A tunable proactivity controller (suggest only above a confidence/value threshold) is an open design problem with a clear metric.
  6. Comprehension-preserving surfaces. The evaluative lens: short-term amplification may atrophy long-term skill. A “teach-mode” that surfaces why the agent did something, on demand, is an unbuilt differentiator — and arguably an ethical one.

Glossary

  • Agentic loop / ReAct — the reason→act→observe→repeat cycle where a model proposes tool calls and a harness executes them.
  • Harness — everything around the model: the code that assembles context, enforces permissions, runs tools, compacts, persists. The paper’s thesis is that this is ~98% of the work.
  • Deny-first — default-deny policy: deny rules override allow rules; unrecognized actions are escalated to the human, never silently run.
  • Auto-mode classifier (yoloClassifier) — an LLM-based judge that decides whether a tool call is safe to auto-approve, so the human is not prompted for everything.
  • Speculative classification — starting the safety classifier before the human dialog and racing it against a timeout; if confident-safe, skip the dialog.
  • Compaction / shaper — a pass that shrinks the context before a model call (cap a result, drop old messages, summarize). Five run in sequence, cheap before expensive.
  • Context collapse — a read-time compaction: the model sees a collapsed view while the full history stays on disk for reconstruction.
  • Subagent / sidechain — a delegated agent running in an isolated context; its full transcript goes to a separate “sidechain” file and only a summary returns to the parent.
  • Worktree isolation — using a temporary git worktree to give a subagent its own repo copy — filesystem separation without containers.
  • MCP (Model Context Protocol) — the standard, multi-transport way external tools/servers plug into the agent; high context cost (tool schemas).
  • Hook — a lifecycle interceptor (27 event types) that can block, rewrite, or annotate tool calls; zero context cost by default.
  • Skill — domain-specific instructions injected on demand; only the short description stays in context (low cost).
  • Append-only JSONL — the session transcript format: events written as they happen, never edited — gives audit + resume for free.
  • Graduated trust spectrum — the seven permission modes from “approve every plan” to “skip most prompts,” that a user moves along as trust builds.
  • Orchestrator-workers — the multi-agent pattern Claude Code uses: a parent delegates scoped subtasks to isolated workers.