Rebuilding Claude Code with Harness Engineering: The Hidden Architecture of AI Agents

·AI for Work·10 min read

Translated from the original Korean post. 한국어 원문 보기 →

It's the harness, not the prompt

As of early 2026, Claude Code has crossed $1B in annual revenue six months after launch. My first reaction was "how good do your prompts have to be?" Turns out that wasn't it at all. What Anthropic got right wasn't feeding the model smarter instructions. It was building the right harness around the right model.

Someone recently reverse-engineered Claude Code's execution traces, and it boils down to three pieces: a streaming agent loop, a permission-gated tool dispatch system, and a context management layer that keeps the model focused across arbitrarily long sessions. The interesting part is that the harness is fully reproducible. You can't copy the model. You can copy the structure around it.

I've moved from writing code to architecture to ops to consulting, and I've watched this same scene play out again and again. Same engine, different infrastructure underneath, and the output splits completely. So this is me taking Claude Code apart as a system.

Four principles of harness engineering

Harness engineering isn't about the model. It's about everything around the model. The model reasons and decides. The harness executes those decisions, constrains them, and wires them to the outside world. The separation of roles is the whole point.

Four principles hold it together.

  1. The model is the only source of decisions — the harness never looks at model output and branches on its own. It runs what the model asked for, nothing else.
  2. Tools are the only interface between model and world — every action goes through a typed, schema-validated tool call.
  3. Context is a managed resource — what the model sees each turn isn't whatever piled up. It's deliberately selected, compacted, and injected.
  4. Permissions are declarative — allow, deny, ask. That distinction lives in configuration, not in code.

Read individually, these sound obvious. But anyone who's run systems in production knows: the moment that separation breaks is the moment something goes wrong. Once the harness starts looking at model output and injecting its own judgment — "let's handle this one differently" — debugging becomes impossible. You can no longer trace who decided what.

Layer 1: the core agent loop

Everything sits on a plain perception-action-observation cycle. The agent takes a task, tries to solve it with tools, observes the result. Then the model decides whether to keep going or stop.

def agent_loop(messages: List[Dict], dispatch: Dict):
    while True:
        response = client.messages.create(
            model=MODEL,
            system=DEFAULT_SYSTEM,
            messages=messages,
            tools=BASIC_TOOLS,
            max_tokens=8000
        )
        
        messages.append({"role": "assistant", "content": response.content})
        
        if response.stop_reason != "tool_use":
            break
            
        results = dispatch_tools(response.content, dispatch)
        messages.append({"role": "user", "content": results})

The same loop handles a one-line fix and a full-codebase refactor. All the task-specific intelligence lives inside the model; the loop just governs flow. That's the design: the loop never needs to get smarter.

The tool dispatch map

What's elegant about Claude Code isn't the number of tools. It's that adding a new tool requires touching zero lines of the core loop. The dispatch map is what buys you that.

DISPATCH = {
    "bash": lambda inp: run_bash(inp["command"]),
    "read": lambda inp: run_read(inp["path"]),
    "write": lambda inp: run_write(inp["path"], inp["content"]),
    "grep": lambda inp: run_grep(inp["pattern"])
}

The loop has no idea which tools exist. It only knows how to call dispatch[tool_name](input). This is classic plugin architecture. Keep the core and the extensions separate and the core stays stable no matter how many extensions pile up. Same instinct as a microservice gateway that handles routing and knows nothing about business logic.

Layer 2: knowledge and context management

Claude Code's 92% prompt prefix reuse rate is not an accident. The system is built to load domain knowledge only when it's needed. Nothing is held from the start; it's fetched at the moment of use.

On-demand skill loading

The system prompt carries a one-line description of each available skill. Nothing more. When the model decides "I need that expertise right now," it calls load_skill(), and only then does the full instruction get injected straight into the conversation at exactly the right moment.

code-review:
  description: Use when reviewing code or auditing a file for bugs
  
pdf:
  description: Use when processing and extracting from PDF documents
  
agent-builder:
  description: Use when designing a new agent or harness component

Install a hundred skills and your system prompt grows to a hundred lines. Not a hundred pages. That difference protects your token bill and the model's focus at the same time. Preloading context is no different from filling your cache with data you're never going to read.

Three-tier context compaction

Every long session hits the same wall: the context window fills up with tool output and intermediate results. Claude Code triggers compaction automatically at roughly 92% context window usage.

  1. Recent messages — active reasoning context, kept verbatim
  2. Older messages — compacted into a single summary block via a dedicated compaction API call
  3. The summary — written to .agent_memory.md and reloaded next session

This is log management. Recent logs stay raw, older logs get aggregated down, and anything older than that goes to archive. You see this pattern anywhere a system has to handle data that accumulates without bound.

Layer 3: multi-agent coordination

Once you push past what a single agent can do, parallelism and specialization become the story.

Persistent teammates and an FSM protocol

Beyond ephemeral subagents, Claude Code runs specialists that survive across multiple tasks. Each teammate has its own specialization and its own JSONL inbox, and keeps running on a background thread.

Communication is coordinated by a finite state machine.

  • IDLE: can accept new work
  • REQUESTING: sending a request to another agent
  • WAITING: waiting on a response
  • RESPONDING: processing work

There's one rule that matters. You cannot issue a new request while in a waiting state. That single rule eliminates deadlock entirely. In distributed systems, deadlock almost always comes from a cycle — I'm waiting on you, you're waiting on me. Forbid a waiting node from opening new requests and the cycle can never form. Nice trick.

Git worktree isolation

Parallel agents writing to the same file will collide. Git worktrees solve it. Each agent gets its own directory, its own branch, its own complete working tree over the repository.

def create_worktree(task_id: str) -> tuple[str, str]:
    branch = f"task/{task_id}"
    path = f".worktree-{task_id[:8]}"
    
    subprocess.run(["git", "worktree", "add", "-b", branch, path])
    return path, branch

Two agents can both edit "core.py" at the same time, but they're writing to different files in different directories. The files are physically distinct, so a write conflict isn't possible in the first place. Nobody's locking anything — the structure just removed the conflict. Another reminder that the best way to solve a concurrency problem is usually to stop sharing.

Layer 4: production hardening

There's a gap between an agent that works and an agent you can ship. This layer closes it.

Real-time token streaming

In Claude Code, streaming isn't a feature. It's the default. Every token flows out to the terminal as it's generated. On a long reasoning chain spanning dozens of tool calls, a blocking agent goes silent for minutes. A streaming agent spends that time showing you its thinking.

Sounds minor. From an ops perspective it's huge. Users can't tell "stuck" apart from "slow." Freeze the screen for five seconds and people hit refresh. Keep progress flowing and the exact same wait becomes tolerable.

YAML-rule permission governance

Claude Code's permission system uses a three-tier model.

always_deny:
  - pattern: "rm -rf /"
    reason: "Unconditional recursive root deletion"
    
always_allow:
  - pattern: "^ls( |$|-)"
    reason: "Listing files is always safe"
    
ask_user:
  - pattern: "^rm "
    reason: "File deletion needs confirmation"

Security policy lives in configuration, not code. Why does that matter? Because a change that needs sign-off can be shipped by editing a config file, with no deploy. If you've ever had to run an entire deployment process through financial-sector IT just to land a one-line permission change, you feel the value of that separation immediately. Bolt policy to code and every policy change drags code risk along with it.

Layer 5: a high-performance async runtime

Parallel tool execution

One of the most important performance characteristics the trace analysis turned up: Claude Code doesn't serialize tool calls when it doesn't have to.

If a turn comes back with three greps and two reads, all five run at once. The turn finishes in the time of the slowest single call, not the sum of five. Stack that up over a long session and the perceived speed is a different product.

async def agent_loop(messages):
    tool_blocks = [b for b in response.content if b.type == "tool_use"]
    
    if len(tool_blocks) > 1:
        print(f"Running {len(tool_blocks)} tools in parallel...")
    
    # fire every tool call at once
    pairs = await asyncio.gather(*[_dispatch_one(b) for b in tool_blocks])

Prompt caching

The system prompt and tool definitions are the most stable content in any agent session. They basically never change. Mark them cacheable and every call after the first gets those tokens at roughly 10% of the cost.

SYSTEM_BLOCKS = [
    {
        "type": "text",
        "text": "You are a coding agent...",
        "cache_control": {"type": "ephemeral"}
    }
]

Across a session with hundreds of round trips, that's not a rounding error. Don't recompute what doesn't change — caching is the same idea everywhere you find it.

Layer 6: enterprise scale

Redis pub/sub mailboxes

Swap the teaching-grade JSONL mailbox for a Redis pub/sub channel with instant delivery and cross-machine support. An agent publishes to a channel, every subscriber has it within milliseconds. No polling loop, no file locks, no filesystem dependency. The moment you leave the single-machine assumption behind, file-based messaging becomes the bottleneck. This swap attacks that limit head-on.

MCP runtime integration

Claude Code supports MCP natively. Tools from any compatible server become first-class citizens in the agent's tool registry. A filesystem server adds file tools, a git server adds git operations, a database server adds query tools.

The model calls all of them exactly like built-ins. It has no idea whether a given tool is a local Python function or a remote server process. That's the sign the abstraction is right. When the caller doesn't care where the implementation lives, extension gets cheap.

Where this differs from the real Claude Code

The reproduction and the real thing aren't identical.

Reproduction Real Claude Code
Tools 23 components 18 registered tools
Cache hit rate 83% (test) 92% (production)
Compaction trigger 40K tokens dynamic threshold
Session persistence JSON file distributed store

The numbers differ, but the architectural principles and the harness engineering approach are the same. The gap between production and reproduction is mostly in the details that survive scale. It isn't a structural difference.

Wrapping up

Trace Claude Code's success back far enough and one thing gets clear. The starting point wasn't a smarter model or better prompts. It was the right harness.

I've run into the same lesson from a lot of different chairs. Trying to grind the engine yourself gets you less distance than building a structure where that engine runs consistently and predictably. Models improve fast, and this one may well get swapped out. But the loop around it, the tool interface, the context management, the permission governance — design those well once and they stick around.

Maybe the real asset accumulates on the harness side, not the model side. That's roughly where I landed after pulling Claude Code apart.

Was this post helpful?

One click helps me write the next one

#harness engineering#Claude#AI agents#architecture#production