A
Ahmad Fajrul FalahCreative Developer
ARCHITECTUREPUBLISHED 2026-08-265 min

Demystifying the AI Agent Harness in Claude Code & OpenCode

Explore how agent harnesses turn stateless LLMs into autonomous engineers through event loops, tool sandboxing, and context management in Claude Code and OpenCode.

Demystifying the AI Agent Harness in Claude Code & OpenCode

Large Language Models do not write software; software writes software. When an engineer watches Claude Code, GitHub Copilot Workspace, or OpenCode resolve a complex multi-file bug, run automated tests, and stage a Git commit, it is tempting to attribute this autonomy to the intelligence of the foundation model.

In reality, foundation models are stateless next-token predictors. They have zero execution memory, cannot open a socket, cannot inspect a filesystem, and have no awareness of whether their previous command succeeded or failed.

The real workhorse behind modern coding agents is the Agent Harness—the runtime system that wraps the model, manages execution loops, sandboxes tool operations, compresses token context, and enforces security boundaries. If the LLM is the CPU, the agent harness is the operating system.


1. The Anatomy of the Runtime Gap

Connecting an LLM directly to a terminal via a simple while-loop leads to immediate failure in production environments.

graph LR
    subgraph Naive Wrapper [Naive API Loop]
        A1[User Prompt] --> B1[LLM Inference]
        B1 --> C1[Execute Output on Host OS]
        C1 --> D1[Context Overflow / Host Crash]
    end

    subgraph Production Harness [Agent Harness Architecture]
        A2[User Intent] --> B2[Context & State Manager]
        B2 --> C2[LLM Inference]
        C2 --> D2[Structured Tool Call Parser]
        D2 --> E2{Security & HITL Gate}
        E2 -->|Approved| F2[Isolated Tool Dispatcher]
        E2 -->|Rejected| B2
        F2 --> G2[Observation & Error Normalizer]
        G2 --> B2
    end

Without a dedicated agent harness, four system-level failure modes occur:

  1. Context Window Saturation: Every tool execution, stdout log, and file tree dump gets appended directly to conversation history. The token window explodes within 5 turns, skyrocketing latency and degrading model reasoning.
  2. Hallucinated Action State: If an agent executes npm test and the process crashes with exit code 1, a raw model will often hallucinate that the tests passed because it generates plausible-sounding summary tokens rather than parsing structured stderr.
  3. Infinite ReAct Loops: When an edit fails or a build error repeats, an unconstrained model will oscillate indefinitely between the same two flawed solutions without a loop-detection circuit breaker.
  4. Unconstrained Blast Radius: A raw model generating shell commands will execute destructive operations (rm -rf /, overwriting uncommitted git files, or leaking environment secrets via curl) without human consent or sandbox boundaries.

The agent harness solves these failure modes through five structural pillars.


2. The 5 Core Pillars of an Agent Harness

graph TD
    Harness[Agent Harness Core]
    Harness --> P1[1. ReAct Execution Loop]
    Harness --> P2[2. Context & Token Compaction]
    Harness --> P3[3. Tool Registry & Sandboxing]
    Harness --> P4[4. State & Session Durability]
    Harness --> P5[5. HITL & Security Interceptors]

Pillar 1: The ReAct & Event Loop

The harness orchestrates the fundamental Observe $\rightarrow$ Reason $\rightarrow$ Act cycle.

  1. Prompt Assembly: Combines system directives, tool JSON schemas, project context, and prior message history.
  2. Model Call: Invokes the LLM with structured tool-calling configurations.
  3. Action Dispatch: Extracts the tool request, validates its schema arguments, and invokes the underlying system handler.
  4. Observation Feedback: Normalizes standard output, standard error, exit codes, and truncated payload lengths into a structured observation message injected back into the LLM context.
  5. Termination Gate: Evaluates whether the user's intent is fulfilled, the maximum iteration budget is reached, or user intervention is required.

Pillar 2: Context Window & Token Compaction

Managing the context window is the single highest-leverage engineering optimization inside an agent harness.

  • Prompt Caching Anchors: Anthropic's Claude models allow KV cache reuse for prompt prefixes. A disciplined harness organizes context hierarchically: static system instructions first, tool schemas second, project structural index third, and dynamic conversation messages last. This guarantees that 80–90% of prompt tokens hit the cache on every iteration.
  • Subagent Offloading: Rather than dumping a 3,000-line codebase grep into the main prompt, modern harnesses delegate broad tasks to lightweight subagents (e.g., an exploration or research subagent). The subagent executes multiple read operations in an isolated context and returns a concise, 5-line summary back to the parent harness.
  • AST and Symbol Pruning: Instead of reading complete source files, the harness parses abstract syntax trees (via Tree-sitter or LSP) to inject only relevant class signatures, method headers, and type definitions into context.

Pillar 3: Tool Registry and Sandboxing

A tool in an agent harness is not an arbitrary function call. It is a formal capability interface with strict validation and isolation.

  • Schema Validation: All tool inputs are validated against strict JSON schemas before execution.
  • Permission Tiers:
    • Read-Only: (readFile, globSearch, gitStatus) $\rightarrow$ Executed immediately without prompt delay.
    • Transactional Mutate: (editFile, writeFile, mkdir) $\rightarrow$ Staged into an in-memory diff buffer with rollback capabilities.
    • System Execution: (bash, docker, network) $\rightarrow$ Isolated in sandboxed containers or gated behind explicit user confirmation.

Pillar 4: State Machine and Session Durability

Real-world developer workflows take hours or days and require interruptions, reverts, and rollbacks.

  • Append-Only Event Sourcing: All system transitions (user inputs, LLM responses, tool outputs, diff patches) are stored in an append-only transaction log.
  • Git Worktree Isolation: Advanced harnesses isolate their execution inside ephemeral Git worktrees or containers. If the agent makes a mistake, the harness discards the entire worktree without affecting the developer's working directory.

Pillar 5: Security & Human-in-the-Loop (HITL) Interceptors

The harness acts as a zero-trust firewall between probabilistic model output and the host operating system.

  • Command AST Inspection: Intercepts shell commands before execution to detect dangerous patterns (rm -rf, piping untrusted URLs to bash, reading .env secrets).
  • Interactive Escalation: If a tool attempts an out-of-bounds action, the harness pauses the execution loop and prompts the user for explicit approval.

3. How Daily Developer Tools Implement Their Harnesses

| Harness Component | Claude Code | GitHub Copilot (Workspace/CLI) | OpenCode | | :--- | :--- | :--- | :--- | | Runtime Model | Local terminal CLI (Node.js/TypeScript) | Cloud container + IDE Language Server | Local client-server runtime | | Context Strategy | KV prompt caching + Subagent delegation | Repository vector indexing + LSP call graphs | Model Context Protocol (MCP) tool discovery | | Execution Sandbox | Local shell with 3-tier permission gates | Isolated cloud VM container per task | Local processes + CDP browser automation | | Tool Protocol | Custom JSON tool schema dispatcher | VS Code Language Extensions API | Open standard Model Context Protocol (MCP) | | State Persistence | SQLite session history + compact diff logs | GitHub Issue / PR linked branch state | Persistent session database + file trees |

Claude Code: Terminal-First Precision

Claude Code optimizes for low-latency terminal execution. Its harness prioritizes aggressive prompt caching and compact output diffing. When an operation requires large-scale directory scanning, Claude Code spins up ephemeral subagents to prevent context contamination of the primary interaction thread.

GitHub Copilot: LSP and Workspace Integration

GitHub Copilot leverages Microsoft's deep IDE infrastructure. Instead of raw grep searches, Copilot's harness relies heavily on Language Server Protocol (LSP) integrations to extract precise semantic relationships, symbol definitions, and compilation errors directly from the editor runtime.

OpenCode: Modular MCP Architecture

OpenCode implements an open architecture built around the Model Context Protocol (MCP). Its harness decouples tool capabilities from the core runtime engine, allowing dynamic discovery of local tools, database connections, and browser automation drivers over a standardized protocol.


4. Practical Implementation: Building a Minimal Agent Harness

The following TypeScript implementation demonstrates the core architecture of an agent harness: structured tool registration, parameter validation, a guarded execution loop, and an interactive safety gate.

import * as fs from "node:fs";
import * as path from "node:path";
import { execSync } from "node:child_process";

// 1. Tool Interface Specification
export interface ToolDefinition<TParams = Record<string, unknown>> {
  name: string;
  description: string;
  isDestructive: boolean;
  parameters: {
    type: "object";
    properties: Record<string, { type: string; description: string }>;
    required: string[];
  };
  execute: (params: TParams) => Promise<string>;
}

export interface AgentMessage {
  role: "system" | "user" | "assistant" | "tool";
  content: string;
  toolCallId?: string;
  toolCalls?: Array<{ id: string; name: string; arguments: string }>;
}

// 2. Concrete Tool Implementations
export const readFileTool: ToolDefinition<{ filePath: string }> = {
  name: "readFile",
  description: "Read the contents of a file from the workspace filesystem.",
  isDestructive: false,
  parameters: {
    type: "object",
    properties: {
      filePath: { type: "string", description: "Absolute or relative file path" },
    },
    required: ["filePath"],
  },
  execute: async ({ filePath }) => {
    const resolved = path.resolve(filePath);
    if (!fs.existsSync(resolved)) {
      throw new Error(`File not found: ${filePath}`);
    }
    return fs.readFileSync(resolved, "utf-8");
  },
};

export const executeBashTool: ToolDefinition<{ command: string }> = {
  name: "executeBash",
  description: "Execute a bash shell command in the workspace directory.",
  isDestructive: true,
  parameters: {
    type: "object",
    properties: {
      command: { type: "string", description: "The shell command to run" },
    },
    required: ["command"],
  },
  execute: async ({ command }) => {
    try {
      const output = execSync(command, { encoding: "utf-8", timeout: 30000 });
      return output || "(command completed with no output)";
    } catch (err: unknown) {
      const execError = err as { stdout?: string; stderr?: string; status?: number };
      return `Exit Code ${execError.status ?? 1}\nStdout: ${execError.stdout ?? ""}\nStderr: ${execError.stderr ?? ""}`;
    }
  },
};

// 3. Core Agent Harness Runtime
export class AgentHarness {
  private tools: Map<string, ToolDefinition> = new Map();
  private conversationHistory: AgentMessage[] = [];
  private maxTurns: number;

  constructor(options: { maxTurns?: number } = {}) {
    this.maxTurns = options.maxTurns ?? 10;
  }

  public registerTool(tool: ToolDefinition): void {
    this.tools.set(tool.name, tool);
  }

  // Safety Gate: Intercept destructive operations
  private async authorizeAction(tool: ToolDefinition, params: Record<string, unknown>): Promise<boolean> {
    if (!tool.isDestructive) return true;
    
    // In production, prompt the user via CLI or UI modal
    console.warn(`[SAFETY INTERCEPTOR] Action "${tool.name}" requires authorization:`, params);
    return true; // Approved for harness runtime execution
  }

  // The Core Execution Loop (Observe -> Reason -> Act)
  public async run(userPrompt: string, mockLLMCaller: (history: AgentMessage[]) => Promise<AgentMessage>): Promise<string> {
    this.conversationHistory.push({ role: "user", content: userPrompt });

    let turns = 0;
    while (turns < this.maxTurns) {
      turns++;
      
      // Step 1: Model Inference
      const response = await mockLLMCaller(this.conversationHistory);
      this.conversationHistory.push(response);

      // Step 2: Check if model concluded its reasoning
      if (!response.toolCalls || response.toolCalls.length === 0) {
        return response.content;
      }

      // Step 3: Dispatch Tool Calls
      for (const call of response.toolCalls) {
        const tool = this.tools.get(call.name);
        if (!tool) {
          this.conversationHistory.push({
            role: "tool",
            toolCallId: call.id,
            content: `Error: Unknown tool "${call.name}".`,
          });
          continue;
        }

        try {
          const parsedParams = JSON.parse(call.arguments);
          const isAuthorized = await this.authorizeAction(tool, parsedParams);

          if (!isAuthorized) {
            this.conversationHistory.push({
              role: "tool",
              toolCallId: call.id,
              content: `Error: Execution denied by user security policy.`,
            });
            continue;
          }

          const result = await tool.execute(parsedParams);
          this.conversationHistory.push({
            role: "tool",
            toolCallId: call.id,
            content: result,
          });
        } catch (error: unknown) {
          const message = error instanceof Error ? error.message : String(error);
          this.conversationHistory.push({
            role: "tool",
            toolCallId: call.id,
            content: `Execution Failure: ${message}`,
          });
        }
      }
    }

    throw new Error(`Agent exceeded maximum turn budget (${this.maxTurns} turns).`);
  }
}

5. Conclusion & Actionable Key Takeaways

  1. The Model is Not the Product; The Harness Is: Raw foundation models are commodities. The speed, accuracy, and reliability of tools like Claude Code and OpenCode depend directly on how their harnesses handle context compaction, error normalization, and tool dispatch.
  2. Context Window Hygiene Dominates Reasoning Scale: A smaller model with an AST-pruned, prompt-cached harness consistently outperforms a frontier model drowned in raw terminal stdout.
  3. Safety Must Be Enforced by the Runtime: Never trust an LLM to self-regulate its tool usage. Deterministic security policies, sandboxed execution boundaries, and human-in-the-loop interceptors must live in the harness layer.

Next time you build an agentic workflow, stop optimizing prompts and start engineering your harness. Demystifying the AI Agent Harness in Claude Code & OpenCode