AI Development

What Is an Agent Harness in an AI Coding Agent? Model, Harness, Tools, and JSON State (2026)

About 16 min read

The DeepSeek piece covered one product host. This one splits the generic four layers: Model, Harness, Tools, JSON State. The model fills arguments. The host runs the loop. Tools pass Schema. State can be replayed.

Before you read

Layer What it locks Who executes
Model tool_use / arguments The model fills JSON; it does not run side effects
Harness loop / permission / compact The host assembles context, then executes
Tools inputSchema Schema is the gate; the host then calls
State session / turn / JSONL If you cannot rebuild it, it is not state

Four layers are objects. A prompt is not an architecture.

A harness is the loop, not the model

A 2026 coding agent looks like “one model editing a repo.” Split it and you get four objects. The model only emits the next action as JSON. The harness is what loads context, checks Schema, evaluates permission, runs tools, writes results back, and compacts history when the window fills. Without that loop, the model is a chat. Claude Code’s official loop is the same fact: receive a prompt, evaluate, execute tools, evaluate again, until there are no tool calls. Codex writes continuity into a Thread and a session record. Cursor is also a host loop. The model does not touch the disk.

  • Model: fills lookup_order and A-1042. It never sees the socket and never spawns a process.
  • Harness: system prompt, tool table, permissions, compaction, budget, max_turns. That is host state, not model weights.
  • Tools and State: tools have Schema. The session has inspectable JSON. Chat text is not state.

How built-in tools fill JSON: AI coding agents and JSON tool calling. The MCP hop: How coding agents call MCP. Why Schema is a gate: Why AI needs JSON Schema. One product’s plugin host: DeepSeek Harness piece. This text does not retell that product. It only stacks the four layers.

Layer one: the model only fills JSON

The model sees the tool table the host published and the messages so far. Its output is text, a tool call, or both. Fictional lookup — no real secrets:

{
  "type": "tool_use",
  "id": "call_3c91",
  "name": "lookup_order",
  "input": { "order_id": "A-1042" }
}

This object is not HTTP and not MCP tools/call. It is the model telling the host “look up A-1042.” The host checks shape, then allows or blocks. Treat the model as the executor and you miss that hop.

Layer two: the harness runs the loop

A session is a state machine, not a single completion. The official loop fits in one object: immutable model name and tool table; mutable message count, pending tool, budget. Turn 2 of 8 is the host counting. The model is not keeping a diary:

{
  "session_id": "sess_7f2a",
  "turn": 2,
  "max_turns": 8,
  "immutable": {
    "model": "coding-agent",
    "tools": ["lookup_order", "Read", "Bash"]
  },
  "mutable": {
    "messages": 6,
    "pending_tool": "lookup_order",
    "budget_tokens": 128000
  }
}

One Claude Code turn: the model emits a reply with tools, the host runs them, writes tool_result back, and asks again. When there are no tool calls, the loop ends and yields usage plus a session_id. Hooks can intercept before execution. Codex picks a sandbox after approval, then runs. How to write permission as rules: Coding agent permission model. The harness calls permission. It is not permission.

Layer three: tools pass Schema

The tool table is the model’s menu. Each row needs a name, a description, and a JSON Schema. A wide object invites invented keys. A lookup must not carry refund fields:

{
  "name": "lookup_order",
  "description": "Look up an order by ID. Confirm before any refund.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" }
    },
    "required": ["order_id"],
    "additionalProperties": false
  }
}

After the gate, the host executes and feeds the result back as an object. The model reads the write-back, not the terminal scrollback. Failure is an object too. Do not drop a prose sentence:

{
  "type": "tool_result",
  "tool_use_id": "call_3c91",
  "content": "{\"order_id\":\"A-1042\",\"status\":\"paid\"}"
}

Layer four: JSON State can be replayed

State is not “what we just chatted.” State is messages, tool results, a session id, compaction boundaries. Codex appends the session as JSONL and rebuilds on resume. One line should point at one lookup:

{"ts":"2026-09-28T01:12:04.221Z","type":"tool_result","id":"call_3c91","name":"lookup_order","ok":true,"order_id":"A-1042"}

Context fills up. Claude Code compacts automatically near the limit and emits compact_boundary on the stream. Early details may be summarized away. The project-root CLAUDE.md is injected again, so durable rules live in a file, not only in the first prompt. Compaction is a state change, not silent amnesia:

{
  "type": "system",
  "subtype": "compact_boundary",
  "session_id": "sess_7f2a"
}

Five steps to a harness you can point at

Do not start from “swap in a stronger model.” Write objects:

  1. List the tool table first. Every tool has a Schema. additionalProperties: false. Do not hand the model a tool without Schema.
  2. Write the stop condition. max_turns or a budget. Do not run open-ended tasks bare.
  3. Write permission as rules. Read-only lookup allow. Writes ask. Refunds deny. “Approve everything” is not a daily default.
  4. Land state as JSON. Keep at least tool_use, tool_result, session_id. If the ids line up, the loop is connected.
  5. Treat compact as an event. A compact means a summary replaced older messages. Rules that must survive live on disk.

How Schema acts as a gate: Agents API + JSON Schema. This text only requires four layers as objects, with the host running side effects.

To debug an agent, spread four JSON objects

Most failures are a tool table, a permission, or state out of line — not a model that “isn’t smart enough.”

  • format inputSchema and input. Look for missing fields and extra keys.
  • Use JSONPath extract to pull $.name, $.input.order_id, $.session_id.
  • JSON Diff a successful write-back and a denial. Fix Schema and rules first. Prompts later.
  • Check that call_3c91 is the same id on tool_use and tool_result.
  • Do not paste session JSONL, tokens, or repo paths into a public formatter. JSONSort runs in the browser.

When all four objects rebuild against Schema, you are collecting an architecture — not the sentence “it seemed to edit files.”

Common traps

  • Treat the harness as a new model. Models rotate. The loop, the gates, and the state should stabilize first. Mis-aimed evals start here.
  • Call a longer system prompt an architecture. Prompts get compacted. The tool table, permissions, and session record do not.
  • Skip Schema and execute. The harness owns the loop. It does not skip a second check. A wide object sends invented keys into side effects.
  • Treat the chat window as state. The window is a render. State is JSON. Resume reads the record, not a screenshot.
  • Treat compact as nothing happened. Official clients emit a boundary event. Nested rules may need a file re-read before they return.
  • Use one product’s plugin words as generic words. dsh and Capability Seam belong to that product piece. The generic four layers are Model / Harness / Tools / State.

FAQ

Is a harness the same as an agent framework?

Not the same layer. A framework often orchestrates nodes inside a business repo. A harness is the host loop: load context, pass gates, execute, persist state. You can write a business agent on a harness. Do not mash the two layers.

Does the model edit files by itself?

No. The model only fills tool JSON. Reads, writes, Shell, and MCP are executed by the host. No host, no repo change.

Must JSON State be JSONL?

No. Codex uses session JSONL as the authoritative record. Claude Code uses session transcripts and an event stream. The requirement is that you can rebuild what the model saw, by id — not a file suffix.

Does compaction drop Schema?

Tool definitions are usually re-injected as a stable prefix. What drops is early conversation detail. Rules that must survive go in a project file, not only in the first user message.

Do I rewrite the harness when I swap models?

You should not. The model adapter can change. Tool Schema, the permission table, and the state shape should hold. Swapping a model swaps the layer that fills JSON, not the four-layer architecture.

Conclusion

A coding agent’s architecture is not “a bigger model.” The model fills JSON. The harness runs the loop. Tools pass Schema. State can be replayed.

Write down which tool, which turn, and which state record A-1042 will enter. If you can point at four objects, you have a harness. Models rotate. Stabilize the four layers first.

Further reading

Changelog: First published