AI 개발

AI Coding Agent의 Agent Harness란 무엇인가: Model, Harness, Tools, JSON State로 읽는 2026 Agent 구조

약 16분

DeepSeek 글은 한 제품의 호스트를 다뤘다. 이 글은 범용 네 층만 나눈다: Model, Harness, Tools, JSON State. 모델이 인자를 채운다. 호스트가 고리를 돌린다. 도구는 Schema를 통과한다. 상태는 재생된다.

읽기 전 요약

층 무엇을 잠그나 누가 실행하나
Model tool_use / arguments 모델이 JSON을 채운다. 부작용은 실행하지 않는다
Harness 고리 / 권한 / 압축 호스트가 문맥을 조립한 뒤 실행한다
Tools inputSchema Schema가 문. 호스트가 호출한다
State session / turn / JSONL 재구성하지 못하면 상태가 아니다

네 층은 객체다. Prompt는 구조가 아니다.

Harness는 고리이지 모델이 아니다

2026 Coding Agent는 “한 모델이 저장소를 고친다”처럼 보인다. 나누면 객체 네 장이다. 모델이 내는 것은 다음 동작의 JSON뿐이다. 문맥을 싣고, Schema를 통과시키고, 권한을 평가하고, 도구를 돌리고, 결과를 다시 쓰고, 창이 가득 차면 이력을 압축하는 것은 Harness다. 이 고리가 없으면 모델은 채팅일 뿐이다. Claude Code 공식은 같은 사실을 고리로 쓴다: 프롬프트를 받고, 평가하고, 도구를 실행하고, 다시 평가하며, 도구 호출이 없을 때까지 반복한다. Codex는 연속성을 Thread와 세션 기록에 쓴다. Cursor도 호스트 고리다. 모델이 디스크를 만지지 않는다.

  • Model: lookup_order와 A-1042만 채운다. 소켓을 보지 않고 프로세스도 만들지 않는다.
  • Harness: 시스템 프롬프트, 도구 표, 권한, 압축, 예산, max_turns. 호스트 상태이지 모델 가중치가 아니다.
  • Tools와 State: 도구에는 Schema가 있다. 세션에는 점검 가능한 JSON이 있다. 채팅 텍스트는 상태가 아니다.

내장 도구가 JSON을 어떻게 채우는지는 AI Coding Agent와 JSON 도구 호출. MCP 한 홉은 Coding Agent는 MCP를 어떻게 호출하나. Schema가 문인 이유는 AI에 JSON Schema가 필요한 이유. 한 제품의 플러그인 호스트는 DeepSeek Harness 글. 이 글은 그 제품을 다시 쓰지 않는다. 네 층을 어떻게 쌓는지만 쓴다.

첫 층: Model은 JSON만 채운다

모델이 보는 것은 호스트가 준 도구 표와 지금까지의 메시지다. 출력은 텍스트이거나 도구 호출이거나 둘 다다. 가상 조회. 실제 비밀은 붙이지 않는다:

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

이 객체는 HTTP가 아니고 MCP tools/call도 아니다. 모델이 호스트에게 “A-1042를 조회하라”고 말하는 것이다. 호스트가 형태를 확인한 뒤 실행하거나 막는다. 모델을 실행기로 보면 그 홉을 놓친다.

둘째 층: Harness가 고리를 돌린다

세션은 상태 기계이지 한 번의 완성이 아니다. 공식 고리는 객체 한 장에 들어간다. 불변은 모델 이름과 도구 표. 가변은 메시지 수, 대기 도구, 예산. 2에서 8로 가는 턴은 호스트가 센다. 모델이 일기를 쓰는 것이 아니다:

{
  "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
  }
}

Claude Code의 한 턴: 모델이 도구가 있는 응답을 내고, 호스트가 실행하고, tool_result를 다시 쓴 뒤 다시 묻는다. 도구 호출이 없으면 고리가 끝나고 사용량과 session_id를 넘긴다. 훅은 실행 전에 가로챌 수 있다. Codex는 승인 후 샌드박스를 고르고 실행한다. 권한을 규칙으로 쓰는 법은 Coding Agent 권한 모델. Harness는 권한을 호출한다. 권한 자체가 아니다.

셋째 층: Tools는 Schema를 통과한다

도구 표는 모델의 메뉴다. 각 행에 이름, 설명, JSON Schema가 필요하다. 넓은 object는 키 발명을 부른다. 조회에 환불 필드를 넣지 않는다:

{
  "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
  }
}

문을 통과한 뒤 호스트가 실행하고 결과를 객체로 다시 먹인다. 모델이 읽는 것은 회신이지 터미널 스크롤이 아니다. 실패도 객체다. 산문 한 문장만 버리지 않는다:

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

넷째 층: JSON State는 재생된다

상태는 “방금 무슨 대화를 했는지”가 아니다. 상태는 메시지, 도구 결과, 세션 번호, 압축 경계다. Codex는 세션을 JSONL에 덧붙이고 resume에서 기록으로 재구성한다. 한 줄이 어느 조회인지 가리켜야 한다:

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

문맥은 가득 찬다. Claude Code는 한도에 가까워지면 자동 압축하고 스트림에 compact_boundary를 낸다. 초반 세부사항은 요약에 먹힐 수 있다. 프로젝트 루트 CLAUDE.md는 다시 주입된다. 남겨야 할 규칙은 파일에 쓴다. 첫 프롬프트에만 쓰지 않는다. 압축은 상태 변화이지 조용한 망각이 아니다:

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

Harness를 객체로 만드는 다섯 단계

“더 센 모델로 바꾸자”에서 시작하지 않는다. 객체를 쓴다:

  1. 먼저 도구 표를 적는다. 각 도구에 Schema. additionalProperties: false. Schema 없는 도구는 모델에 넘기지 않는다.
  2. 고리의 정지 조건을 쓴다. max_turns 또는 예산. 열린 작업을 맨몸으로 돌리지 않는다.
  3. 권한을 규칙으로 쓴다. 읽기 조회는 allow. 쓰기는 ask. 환불은 deny. “전부 자동 승인”을 기본값으로 두지 않는다.
  4. 상태를 JSON으로 남긴다. 최소한 tool_use, tool_result, session_id. id가 맞으면 고리가 연결된 것이다.
  5. 압축을 사건으로 다룬다. compact는 요약이 옛 메시지를 바꿨다는 뜻이다. 남겨야 할 규칙은 디스크 파일에 둔다.

Schema를 문으로 쓰는 법은 Agents API + JSON Schema. 이 글이 요구하는 것은 네 층이 객체이고, 호스트가 부작용을 실행하는 것이다.

Agent를 되짚으려면 JSON 네 장을 펼친다

실패의 대부분은 도구 표, 권한, 상태가 어긋난 것이다. 모델이 “충분히 똑똑하지 않아서”가 아니다.

  • inputSchema와 input을 포맷하고 빠진 필드와 여분 키를 본다.
  • JSONPath 추출로 $.name, $.input.order_id, $.session_id를 뽑는다.
  • 성공한 회신과 거절된 호출을 JSON Diff. Schema와 규칙을 먼저 고치고 프롬프트는 나중에.
  • call_3c91이 tool_use와 tool_result에서 같은 id인지 대조한다.
  • 세션 JSONL, 토큰, 저장소 경로를 공개 포맷터에 붙이지 않는다. JSONSort는 브라우저에서 돌아간다.

네 객체를 Schema로 재구성할 수 있어야 구조를 모으는 것이다. “파일을 고친 것 같았다”를 모으는 것이 아니다.

흔한 함정

  • Harness를 새 모델로 본다. 모델은 바뀐다. 고리, 문, 상태를 먼저 안정시킨다. 평가가 빗나가는 지점이 여기다.
  • 더 긴 system prompt를 구조로 본다. 프롬프트는 압축에 먹힌다. 도구 표, 권한, 세션 기록은 먹히지 않는다.
  • Schema를 건너뛰고 실행한다. Harness는 고리를 맡는다. 두 번째 검증을 면제하지 않는다. 넓은 object는 발명된 키를 부작용으로 보낸다.
  • 채팅 창을 상태로 본다. 창은 렌더다. 상태는 JSON이다. resume이 읽는 것은 기록이지 스크린샷이 아니다.
  • 압축을 아무 일도 아니라고 본다. 공식 클라이언트는 경계 사건을 낸다. 중첩 규칙은 파일을 다시 읽어야 돌아올 수 있다.
  • 한 제품의 플러그인 말을 범용어로 쓴다. dsh와 Capability Seam은 그 글의 대상이다. 범용 네 층은 Model / Harness / Tools / State다.

FAQ

Harness와 Agent Framework는 같은가?

같은 층이 아니다. Framework는 업무 저장소 안에서 노드를 엮는 경우가 많다. Harness는 호스트 고리다: 문맥을 싣고, 문을 통과시키고, 실행하고, 상태를 남긴다. Harness 위에 업무 Agent를 써도 된다. 두 층을 섞지 마라.

모델이 스스로 파일을 고치는가?

고치지 않는다. 모델이 채우는 것은 도구 JSON뿐이다. 읽기, 쓰기, Shell, MCP는 호스트가 실행한다. 호스트가 없으면 저장소는 바뀌지 않는다.

JSON State는 반드시 JSONL이어야 하는가?

아니다. Codex는 세션 JSONL을 권위 기록으로 쓴다. Claude Code는 세션 기록과 사건 스트림을 쓴다. 필요한 것은 id로 당시 모델이 본 객체를 재구성하는 것이다. 파일 접미사가 아니다.

압축이 Schema를 버리는가?

도구 정의는 보통 안정된 접두로 다시 주입된다. 버려지는 것은 초반 대화 세부다. 남겨야 할 규칙은 프로젝트 파일에 쓴다. 첫 사용자 메시지에만 쓰지 않는다.

모델을 바꾸면 Harness를 다시 써야 하는가?

다시 쓰면 안 된다. 모델 어댑터는 바꿀 수 있다. 도구 Schema, 권한 표, 상태 모양은 먼저 고정한다. 모델 교체는 JSON을 채우는 층을 바꾸는 것이지 네 층 구조를 바꾸는 것이 아니다.

결론

Coding Agent 구조는 “더 큰 모델”이 아니다. Model이 JSON을 채우고, Harness가 고리를 돌리고, Tools가 Schema를 통과하고, State가 재생되는 것이다.

A-1042가 어느 도구, 어느 턴, 어느 상태 기록으로 들어가는지 먼저 쓴다. 네 층 객체를 가리킬 수 있으면 Harness가 있는 것이다. 모델은 바뀐다. 네 층을 먼저 안정시켜라.

더 읽기

변경 기록: 최초 게시