AI coding agent harness like Claude Code0/66 done
Part 3 · System design · Real products

AI coding agent harness like Claude Code

A developer gives a coding task. An AI model reads files, runs commands, edits code and runs tests in a loop until it's done. Make it safe and reliable.

In plain words

You tell an AI coding agent: set x to 2. It can't touch your files directly. It asks the harness, the program around it: please read b.js. There is no b.js, so the harness sends back an error, and the model tries a.js instead. Then it asks to write the change. Then, oddly, it asks to run rm -rf /. The harness says no. The model never gets the final word on what runs.

Then the model says it's done, and the loop ends. If it never says done, a step limit ends it. The harness also saves a copy of each file before changing it, so anything can be undone, and it runs the tests to check the work instead of trusting the model's word.

Examples

  • Claude Code, Codex CLI and Cursor's agent all run this tool loop.
  • Reading files is allowed; writing and shell commands need approval or an allow list.
  • Long command output is trimmed before going back to the model, to save context.
  • Tool errors go back as text, so the model can try another way instead of crashing.

1. Requirements

Functional

Non functional

Questions to ask

2. Estimates

steps per task10 to 50 tool calls
contextabout 200k tokens, filled fast by file contents and command output
costevery step resends the conversation, so cost grows with each step

3. API

model -> harness: { tool: "read", args: { path } }   or   { done: true, text }
harness -> model: tool result text (trimmed if long)
tools: read(path), write(path, text), edit(path, search, replace), search(pattern), shell(cmd)

4. Data model

session log: every message and tool call, in order (for resume and review)
checkpoints: file snapshots before each edit, for undo
permissions: allow list per tool and command pattern
project notes: a file like AGENTS.md loaded at start

5. High level design

Follow one request at a time, top to bottom.

One loop step

  1. Developergives the task
  2. Harnesssends task, tool list and project notes to the model
  3. Modelreplies with a tool call, or done
  4. Permission checkallow list, approval prompt, or deny
  5. Tool runnerruns with a timeout in a sandbox, trims long output
  6. Harnessadds the result to the log and loops

Finish

  1. Modelsays done
  2. Harnessruns the tests
  3. Checkpointsfile snapshots for undo
  4. Developerreviews the diff

6. Deep dives

What does the loop look like?

Send the task and tool list to the model. If it asks for a tool, the harness runs it and sends back the result. Repeat until the model says done or a step limit hits.

How do you keep it safe?

The harness enforces permissions, not the model: reads are free, writes and shell need approval or an allow list, dangerous commands are denied. Run in a sandbox or container when unattended.

What about the context window filling up?

Trim long tool outputs, read files in parts, and when the conversation gets big, summarise older steps and keep the newest in full.

How does it recover from mistakes?

Tool errors go back to the model as text so it can try another way. Checkpoints before each edit allow undo. Tests are the judge of done, not the model's word.

See the core idea run

Core logic (JavaScript, tested)

// An AI coding agent is a loop: the model asks for a tool, the harness runs it and sends back the result,
// until the model says it is done. The harness, not the model, enforces limits and permissions.
async function agentLoop(model, tools, task, { maxSteps = 8, allow = () => true } = {}) {
  const log = [{ role: "user", text: task }];
  for (let step = 1; step <= maxSteps; step++) {
    const reply = await model(log);
    if (reply.done) return { ok: true, answer: reply.text, steps: step, log };
    const { tool, args } = reply;
    let result;
    if (!tools[tool]) result = `error: no tool ${tool}`;
    else if (!allow(tool, args)) result = `denied: ${tool} needs approval`;
    else {
      try {
        result = await tools[tool](args);
      } catch (e) {
        result = `error: ${e.message}`; // errors go back to the model, they don't crash the loop
      }
    }
    log.push({ role: "tool", tool, text: String(result) });
  }
  return { ok: false, answer: "stopped: step limit", steps: maxSteps, log };
}

7. Failures

Model loops foreverStep limit, time limit and cost limit; stop and report.
Command hangsTimeout per command; kill it and tell the model it timed out.
Model tries something destructiveDenied by the permission check, and the denial is shown to the model and the user.

8. Trade-offs

Wrap up

Say out loud: It's a loop: the model asks for a tool, the harness checks permission, runs it with a timeout, and sends back trimmed output, until the model says done or a step limit hits. The harness, not the model, owns safety: allow lists, approvals, sandboxes and checkpoints for undo. Errors go back to the model as text, and tests decide when it's done.