HarnessRouter: Codex와 Claude Code를 내 컨테이너 하나에서 실행하는 자체 호스팅 게이트웨이

HarnessRouter 소개

코딩 에이전트를 여러 개 쓰는 팀은 실행 환경이 도구마다 갈리는 문제를 만납니다. Codex 와 Claude Code, Hermes 는 각자 자기 방식으로 설치되고 자기 방식으로 세션을 관리하며, 대화 기록도 각자의 자리에 남습니다. 여기에 자격 증명 관리가 겹칩니다. 도구별로 키를 넣어 두면 어느 키가 어디에 있는지 흐려지고, 관리형 서비스로 묶으면 그 대신 키와 코드가 외부로 나갑니다. 에이전트가 실제로 무엇을 실행했는지 한 곳에서 보고 싶다는 요구와, 아무것도 밖으로 내보내고 싶지 않다는 요구가 여기서 부딪힙니다.

이번에 소개할 HarnessRouter Community Edition 은 그 두 요구를 컨테이너 한 개로 맞추려는 프로젝트입니다. 프로젝트가 말하는 하네스(Harness)는 모델을 감싼 실행 계층이고, Codex 와 Claude Code, Hermes 가 각각 그 하네스에 해당합니다. HarnessRouter 는 이들을 저장된 설정 객체로 다루어, 기반 런타임(Runtime)과 모델, 지시문, 한도를 묶어 하나의 하네스로 만들고, 그 설정을 한 번 실행하는 단위를 작업(Task)이라고 부릅니다. 작업은 bash 와 git 이 있는 실제 POSIX 작업 공간에서 돌아가는 진짜 대화이고, 진행 중에 스트리밍으로 되돌아옵니다.

이 저장소는 관리형 클라우드 서비스와 같은 코드베이스입니다. 저자들은 콘솔이 축소판 재구현이 아니라 "the same pages, the same components, the same API client" 라고 밝히고 있으며, 계정과 결제, 마켓플레이스처럼 한 대의 서버로는 제공할 수 없는 화면만 감춰 둔 것이라고 설명합니다. 자체 호스팅 쪽은 Apache-2.0 으로 공개된 무료 판이고, 관리형 쪽은 요금이 있는 서비스입니다. 로컬에서 다듬은 하네스는 콘솔의 Harnesses → Push to cloud 로 관리형 계정에 복사할 수 있는데, 이 이동은 의도적으로 한 방향입니다. 클라우드에서 다시 끌어오는 기능이 없어서, 같은 하네스가 두 곳에서 서로 최신이라고 주장하는 상태가 생기지 않습니다.

HarnessRouter의 컨테이너 구조

프로젝트가 정리한 내부 구조는 다음과 같습니다:

┌─ container ─────────────────────────────────────────────┐
│  UI (Next.js)  :3000  ← the only published port         │
│      │ same-origin proxy                                │
│  Gateway       :8080  Responses API, harness CRUD       │
│      │ loopback                                         │
│  Runner        :8081  one agent CLI per session         │
└─────────────────────────┬───────────────────────────────┘
       /data (volume): SQLite, files, secrets, workspaces

게이트웨이와 실행기는 컨테이너 안의 루프백에서만 듣고 외부로 공개할 수 없습니다. 들어오는 길은 콘솔 포트 하나뿐이며, 그래서 로그인 게이트가 앞단 프록시가 아니라 이미지 안에 들어 있습니다. 세션은 동시에 실행되고 서로 격리되어 각자 자기 작업 공간 디렉토리와 대화 상태, 체크포인트를 가집니다. 동시 실행 수의 기본값은 머신의 코어 수입니다. 자체 호스팅 환경은 관리형 배포처럼 샌드박스를 필요할 때 늘릴 수 없으므로, 한도가 곧 그 기계가 실제로 실행할 수 있는 양이라고 프로젝트는 적어 두었습니다.

권한 처리도 눈여겨볼 부분입니다. 컨테이너는 root 로 시작해야 하고 0.8.2 이후 버전은 다른 방식으로는 시작을 거부합니다. 다만 이것은 흔히 보는 root 실행 편법이 아니라 그 반대 방향인데, root 가 필요한 이유가 에이전트 CLI 를 세션마다 별도의 사용자로 실행하기 위해서이기 때문입니다. 그 사용자는 자기 세션의 작업 공간만 소유하고, 제품 본체는 첫 순간부터 권한 없는 사용자로 동작합니다. 그래서 한 에이전트가 다른 세션의 파일이나 데이터베이스, 비밀 저장소를 읽거나 쓰는 것이 금지가 아니라 불가능해집니다. --privileged 도, --cap-add 도, 별도 seccomp 프로필도 쓰지 않습니다.

HarnessRouter 설치와 첫 실행

Docker 와 약 4GB 의 디스크, 그리고 모델 공급자의 API 키가 필요합니다. 이미지는 약 700MB 를 내려받습니다:

docker pull harnessrouter/harnessrouter

docker run -d --name harnessrouter \
  -p 127.0.0.1:3000:3000 \
  -v harnessrouter:/data \
  harnessrouter/harnessrouter

-p 127.0.0.1:3000:3000 이 콘솔을 이 기계에서만 접근하게 묶어 두고, -v harnessrouter:/data 가 데이터베이스와 파일, 첫 실행에서 설치되는 에이전트 CLI 를 보존합니다. 포트 3000 이 이미 쓰이는 중이면 왼쪽 숫자만 바꿉니다. 컨테이너는 항상 내부의 3000 을 듣습니다.

여기서 주의할 점은 첫 실행이 느리다는 것입니다. docker run 은 1초쯤 뒤에 프롬프트를 돌려주지만 콘솔은 30초 정도를 더 필요로 하고, 그 사이 http://localhost:3000 은 연결을 거부합니다. 로그에 ready on :3000 이 찍힌 다음 브라우저를 여는 것이 맞습니다:

docker logs -f harnessrouter

기본 계정은 사용자명과 암호가 모두 harnessrouter 입니다. 프로젝트는 이 값이 문서에 그대로 적혀 있으니 비밀이 아니라 자리표시자이며, 기본값이 남아 있는 동안 컨테이너가 매 시작마다 경고한다고 밝히고 있습니다. 접속 직후 Profile 에서 암호를 바꾸는 것이 첫 작업입니다.

그다음 Integrations 에서 공급자와 API 키를 등록합니다. 이미지에는 번들 모델도, 시험용 키도, 숨은 무료 구간도 없어서 이 단계를 지나기 전에는 아무것도 실행되지 않습니다. 어떤 모델을 그 공급자가 제공하는지는 직접 설정하지 않아도 되고, 공급자를 고르고 키를 등록하면 해당 모델이 행에 나타납니다.

HarnessRouter가 실행하는 하네스는 몇 개인가

여기에 원문을 대조할 때 갈리는 지점이 하나 있습니다. README 산문의 지원 하네스 항목과 API 설명의 harness_id 목록은 Codex, Claude Code, Hermes 세 개를 적고 있습니다. 반면 같은 문서에 실린 콘솔 화면과 첫 실행 로그에는 다섯 개가 나옵니다. 로그는 각 백엔드를 설치하면서 라이선스 조건까지 함께 출력합니다:

[harnessrouter] installing Claude Code (Anthropic's terms apply)…
[harnessrouter] installing Codex (Apache-2.0)…
[harnessrouter] installing Pi (MIT) and its MCP adapter (MIT)…
[harnessrouter] installing DeepSeek Harness (MIT, developer preview — version-pinned)…
[harnessrouter] installing Hermes (check its upstream license before use)…
[harnessrouter] data=/data  backends available: claude codex hermes pi dsh
[harnessrouter] ready on :3000

Codex, Claude Code, Hermes, Pi, DeepSeek Harness (:pytorch::kr: DeepSeek Harness: 에이전트 루프까지 설정으로 교체하는 플러그인 구조의 코딩 에이전트) 다섯 개가 설치되며, 콘솔 화면도 다섯 개를 나열합니다. 산문 쪽 목록이 갱신되지 않은 것으로 보이므로, 실제로 무엇을 쓸 수 있는지는 자기 인스턴스의 Harnesses 화면과 첫 실행 로그로 확인하는 것이 정확합니다.

에이전트 CLI 를 이미지에 넣지 않고 첫 실행에 설치하는 이유도 위 로그에 드러납니다. 각 도구의 라이선스가 서로 다르고 그중 일부는 재배포를 전제하기 어렵기 때문입니다. 백엔드를 켜기 전에 해당 도구의 약관을 읽어야 한다는 안내도 저장소에 함께 적혀 있습니다.

HarnessRouter API로 직접 호출하기

콘솔은 선택 사항입니다. 콘솔이 하는 일은 모두 같은 API 로 할 수 있고, 기본 설치에서는 그 API 가 콘솔 포트를 통해 열리며 로그인 게이트가 API 에도 적용됩니다. 그래서 먼저 세션 쿠키를 받아 둡니다:

curl -s -c hr.cookies http://localhost:3000/api/selfhost/login \
  -H 'content-type: application/json' \
  -d '{"username":"harnessrouter","password":"harnessrouter"}'
# {"ok":true}

게이트웨이는 Responses API 를 쓰므로, 한 턴을 실행하는 요청은 다음과 같습니다:

curl -s -b hr.cookies http://localhost:3000/api/harness/v1/responses \
  -H 'content-type: application/json' \
  -d '{"input":"Reply with exactly this and nothing else: it works.",
       "metadata":{"harness_id":"codex"},
       "model":"gpt-5.4-mini",
       "stream":false}'

이렇게 실행한 턴은 별도 경로로 사라지지 않고 콘솔의 Tasks 에 같은 하네스 아래 전체 기록으로 나타납니다. 하네스 CRUD 는 /v1/harnesses, 모델 목록은 /v1/models, 세션은 /v1/sessions/{id}/turns/cancel, 작업 목록은 /v1/traces 로 같은 접두어 아래에 있고, "stream":true 를 주면 한 번에 오는 응답 대신 서버 전송 이벤트를 받습니다.

Unified Harness Protocol: 구현이면서 표준

이 저장소가 다른 자체 호스팅 도구와 다른 점은 프로토콜을 따로 문서화해 두었다는 점입니다. 게이트웨이가 말하는 규약은 Unified Harness Protocol(UHP)이라는 이름으로 protocol/ 아래에 명세되어 있고, 버전이 매겨져 있으며 시험할 수 있습니다. 구성은 열 개의 규범 장으로 이루어진 명세, 하나의 원본에서 생성한 OpenAPI 3.1 과 JSON Schema 2020-12, 그리고 적합성 시험 모음입니다. 프로젝트는 그 시험을 통과하는 것이 곧 적합하다는 뜻이며 UHP 라는 이름을 쓸 자격이라고 정의합니다.

중요한 것은 이 표준이 특정 서비스에 묶여 있지 않다는 점입니다. 저자들은 "The standard can be implemented without HarnessRouter Cloud" 라고 적어 두었고, HTTP 계약일 뿐 호스팅 서비스를 요구하지 않는다고 설명합니다. 자기 서버를 상대로 적합성 시험을 직접 실행해 볼 수도 있습니다:

pip install -e protocol/conformance
uhp-conformance --base-url https://your-server --api-key "$KEY" --class full

HarnessRouter의 스타터 킷

콘솔에는 스타터 킷이 함께 들어 있습니다. 코드 조각이 아니라 앱과 그 앱을 조작하도록 설정된 에이전트, 그리고 그 에이전트에게 출력 형식을 가르치는 스킬까지 묶은 완제품 예시이고, 전부 오픈소스입니다. 현재 네 종류가 소개되어 있습니다.

슬라이드 킷은 발표 자료 한 벌을 대화 하나로 만듭니다. 구조를 먼저 잡고 스타일 체계를 정한 다음 장별로 만들어 나가며, 작업 중에 슬라이드가 하나씩 나타나서 방향이 틀렸을 때 두 장만 고치면 되는 시점에 말할 수 있습니다. 시트 킷은 행이 데이터이고, 에이전트 열이 모든 행에 대해 하네스를 한 번씩 실행하며 왼쪽 열들을 입력으로 받습니다:

대시보드 킷은 데이터베이스를 가리키면 스키마를 읽고 질문마다 질의를 작성해 적절한 차트를 고른 뒤 패널을 배치합니다. 대시보드를 열 때마다 모든 질의를 다시 실행하므로 화면이 만들 때의 스냅샷이 아니라 지금의 데이터베이스를 보여줍니다.

HarnessRouter는 누구에게 유용한가

여러 코딩 에이전트를 한 곳에서 실행하고 그 기록을 남기고 싶지만 키와 코드를 외부로 보낼 수 없는 팀에게 HarnessRouter Community Edition 은 지금 검토할 값어치가 있습니다. 관리형 제품과 같은 /v1 표면을 쓰기 때문에 여기에 맞춰 만든 것이 나중에 관리형으로 옮겨도 그대로 동작하고, 저장 계층이 어댑터 인터페이스 뒤에 있어 자체 호스팅과 관리형이 같은 코드베이스로 유지됩니다. 프로토콜이 따로 명세되어 있어 나중에 다른 구현으로 갈아탈 여지가 남는다는 점도 게이트웨이를 도입할 때 계산에 넣을 만합니다.

다만, 공개 주소에 올릴 계획이라면 버전을 반드시 확인해야 합니다. 저자들은 0.1.x0.2.0 에는 로그인 게이트가 아예 없어 포트에 닿을 수 있는 누구에게나 열려 있다고 밝히고 있으며, 게이트가 들어간 첫 릴리스는 0.3.0 입니다. 콘솔은 하네스를 만들고 모든 작업 기록을 읽고 내 공급자 키로 에이전트를 실행할 수 있으므로, 기본 암호를 바꾸기 전에 다른 사람이 인스턴스에 닿는 상황을 만들면 안 됩니다. TLS 가 필요하면 콘솔을 루프백에 두고 앞에 종료 프록시를 세우되, 스트리밍이 버퍼링되지 않도록 설정해야 콘솔이 멈춘 것처럼 보이지 않습니다.

HarnessRouter의 라이선스

HarnessRouter Community Edition 은 Apache-2.0으로 공개되어 있어 개인 및 상업적 목적으로 자유롭게 사용할 수 있습니다. 서드파티 고지는 NOTICE 파일에 정리되어 있습니다.

단, 함께 실행되는 에이전트 CLI 는 이 저장소가 재배포하지 않고 첫 실행에서 각자의 라이선스로 설치되므로, 백엔드를 켜기 전에 해당 도구의 조건을 확인해야 합니다.

:house: HarnessRouter 홈페이지 (문서와 관리형 클라우드 서비스)

:books: Unified Harness Protocol 표준 사이트

:github: HarnessRouter Community Edition 프로젝트 GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 초안을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 댓글로 알려주시기를 부탁드립니다. :hugs:

이 도구를 직접 설치해 사용해보셨다면, :pytorch:파이토치 한국 사용자 모임:south_korea: 회원들을 위해 경험이나 팁을 댓글로 남겨주세요! :folded_hands: