Unreal Labs는 Sequoia와 First Round가 투자한 스타트업이고, CERN·Meta·Snap·Bloomberg·DeepMind 출신 엔지니어가 만들었다고 소개한다. 9월 22일 이 회사가 Unreal Agent라는 에이전트 하네스를 공개했다. 주장은 하나다. 같은 모델로 같은 통과율을 내면서 Codex보다 최대 40%, Pi보다 최대 20% 싸다. 저장소는 Go로 짰고 MIT 라이선스이며, 공개 사흘 만에 스타 1,800개를 받았다. Hacker News 스레드는 240점에 댓글 121개가 달렸다.
이 글에서 확인하는 것은 이 하네스가 줄였다는 비용이 실제로 어디서 나오는지, 코드에서 그 절감을 어떻게 구현했고 무엇을 포기했는지, 벤치마크 표를 어디까지 믿을 수 있는지다. 마지막에는 자기 하네스에서 같은 낭비를 찾는 방법과 직접 구현할 때 확인할 항목을 정리한다. 코드는 9월 23일 커밋 기준으로 읽었고, 벤치마크는 돌리지 않았다.
이 블로그에서 하네스를 다룬 글은 #35가 이름의 유래, #54가 스스로 고치는 하네스, #64가 루프를 플러그인으로 나눈 구조였다. 이번 글은 비용 하나만 본다.
기다리기만 하는 턴에도 돈이 나간다
Unreal Labs가 줄이려는 낭비는 새로 발견한 것이 아니다. Codex 저장소에 이미 두 건의 이슈로 올라와 있다.
3월 6일 이슈 13733의 작성자는 소스를 읽고 루프를 추적했다. 모델이 cargo build 같은 명령을 백그라운드로 띄우면 Codex는 “후속 필요” 플래그를 세우고 모델을 다시 부른다. 모델은 프로세스가 아직 돌고 있으니 빈 입력으로 상태를 묻는다. 하네스는 5초를 기다렸다가 “새 출력 없음”을 돌려주고, 다시 모델을 부른다. 빌드가 끝날 때까지 이 루프가 돈다. 한 번 돌 때마다 대화 전체를 모델에 다시 보내고, 모델은 기다릴지 말지 추론까지 한다. 댓글이 42개 달렸고, 150K 토큰짜리 컨텍스트를 폴링마다 다시 내는 게 가장 괴롭다는 경험담과, 최소한의 정보만 든 감시 스레드가 본 스레드를 깨우는 구조로 가자는 제안이 이어졌다.
7월 24일 이슈 35259에는 숫자가 붙었다. Codex Desktop에서 멀티에이전트 작업을 돌린 한 사용량 주기를 로그로 재구성했더니, 도구 동작이 대기나 상태 확인뿐인 턴이 원시 토큰량의 19.8%였다. 30초나 60초 간격으로 모델이 다시 들어와 “아직인가”를 확인한 비용이다.
여기서 짚을 반론이 하나 있다. 5월에 OpenAI 쪽 기여자가 같은 스레드에 남긴 설명이다. 웹소켓 전송으로 바뀐 뒤로는 새 메시지만 서버로 보내고, 그 전에도 히스토리 대부분은 서버 캐시에 있었으며, 캐시된 토큰은 사용량에 잡히지 않는다는 것이다. 그러니 폴링의 토큰 영향이 댓글에서 말하는 것만큼 크지 않다고 했다. 이 설명은 ChatGPT 구독으로 Codex를 쓰는 경우에 맞는 말이다. API로 과금하는 경우는 다르다. #62에서 정리한 대로 GPT-6 Astra는 캐시된 입력도 100만 토큰당 1달러를 받는다. 폴링 한 번이 캐시 히트여도 컨텍스트 길이만큼 돈이 나간다. Unreal Labs가 인용한 (KV) Cache Rules Everything Around Me의 요지도 같다. 에이전트는 도구 호출마다 컨텍스트 전체를 다시 읽으므로 청구서의 대부분은 캐시 읽기이고, 재생한 SWE 트레이스에서 출력 토큰은 9~18%에 그쳤다. 그러면 비용을 줄이는 변수는 두 개로 좁혀진다. 턴 수와 턴마다 읽는 컨텍스트 길이다. Unreal Agent는 첫 번째를 건드린다.
Unreal Agent가 기다림을 처리하는 방식
저장소의 README는 구성요소 표로 시작한다. 코디네이터가 입력을 저장하고 LLM 턴을 돌리고 도구 호출을 오퍼레이션으로 바꿔 넘긴다. 오퍼레이션 매니저는 그 오퍼레이션을 백그라운드에서 실행하는 액터 런타임이다. 컨텍스트 빌더는 세션 기록에서 모델 입력을 조립한다. 세션 저장소는 append-only 로그다. 도구는 Bash, ViewImage, 스킬 로드 세 개가 전부고 서브에이전트나 워크플로는 없다.
모델에게 주는 설명은 짧다. 프리앰블이 11줄인데, 턴마다 대화 전체를 다시 보내니 도구 호출을 여러 턴에 걸쳐 잇지 말고 한 턴에 넓게 펼쳐라. 도구 호출은 비동기라서 부르는 순간 시작되고 여러 개가 동시에 돈다. 결과가 오면 그 결과가 새 턴을 깨우고, 같이 도착한 결과는 같은 턴에 들어온다. 아직 도는 호출은 자리표시자로 보인다. 도구를 지켜볼 필요는 없고, 10분 동안 아무 일도 없으면 하트비트가 깨울 테니 그때 점검하라. 도구 호출 없이 턴을 끝내면 뭔가 끝날 때까지 잔다. Bash 도구의 설명도 한 문장이다. “셸 명령을 백그라운드에서 실행한다. 독립적인 명령은 한 턴에 병렬 호출로 내라.”
코드에서 실제 흐름은 이렇다.
- 모델이 Bash 호출을 여러 개 낸다. 코디네이터가 각각을 오퍼레이션으로 바꿔 매니저에 넘기고, 세션 로그에는 호출이 “실행 중” 상태로 기록된다.
- 코디네이터는 1초를 기다린다. 코드의 상수 이름이
toolCallRunGracePeriod다. 1초 안에 끝나는 호출은 결과가 바로 같은 턴에 들어가고, 오래 걸리는 호출만 자리표시자로 남는다. 자리표시자 문구는 “도구 호출이 아직 실행 중이다. 결과는 나중 턴에 온다. 독립적인 일을 계속하거나, 턴을 끝내고 기다려라”이다. - 오퍼레이션이 끝나면 매니저가 업데이트 채널로 알린다. 코디네이터가 결과를 세션 로그에 붙이고 모델을 부른다. 모델은 이때 처음으로 결과를 본다.
- 도는 호출만 남고 아무 일도 없으면 10분 뒤 하트비트가 제어 메시지로 들어와 모델을 한 번 깨운다. 기본값은 실행기 플래그로 바꿀 수 있고 0이면 끈다.
- 사용자 메시지는 인박스를 거쳐 언제든 들어온다. 모델이 응답 중이면 그 호출을 취소하고 새 입력을 포함해 다시 부른다.
달라진 점은 하나다. 기다릴지 판단하는 쪽이 모델에서 하네스로 옮겨 갔다. Codex에서는 모델이 “아직인가”를 묻고 하네스가 5초 뒤 “아니”라고 답했다. Unreal Agent에서는 하네스가 완료를 알고 있으니 모델에게 물어볼 일이 없다. 모델은 도구가 끝났을 때만 불린다.
캐시를 지키려고 결과를 두 번 남긴 대가
원문 각주 2번이 이 부분을 “캐시를 깨지 않고 구현하는 게 그 자체로 흥미로운 엔지니어링 과제였다”고 적었다. 코드를 보면 무엇을 했는지 드러난다.
컨텍스트 빌더는 모델 입력을 두 덩어리로 나눠 들고 있다. 커밋된 접두부와 대기 중인 접미부다. 새 턴이 시작되면 접미부를 접두부 끝에 붙여 커밋한다. 자리표시자는 접미부에 들어가고, 실제 결과가 도착하면 접미부에서 자리표시자를 지우고 결과를 넣는다. 그런데 자리표시자가 이미 커밋된 뒤에 결과가 오면 접두부는 건드리지 않는다. 커밋된 접두부를 고치면 그 지점부터 캐시가 무효가 되기 때문이다. 결과는 그냥 뒤에 붙는다. 이 경우 같은 호출 ID에 결과 아이템이 두 개 생긴다. 하나는 “실행 중”, 하나는 최종 결과다.
Responses API 문서는 이런 형식을 정의하지 않는다. Unreal Labs는 OpenAI에서는 통과했고 OpenAI 외 일부 공급자의 일부 모델에서 거부됐으며, 모델 자체는 실행 중에서 최종 결과로 이어지는 흐름을 이해했다고 적었다. 공개 이틀 뒤 이슈 11이 올라왔다. 병렬 Bash 호출이 턴을 넘어 겹치자 같은 호출 ID에 결과가 두 개 실려 공급자가 400을 돌려줬고, 세션을 다시 열어도 같은 요청이 재생돼 세션을 영영 못 쓰게 됐다는 보고다. 개발자는 각주 2번을 가리키며 알려진 제약이라고 답했다. 보고자의 임시 대응은 무거운 Bash 호출을 두 개 이하로 줄이고 400이 나면 새 세션을 여는 것이었다.
이 문제는 설계 때문에 생겼다. 자리표시자를 접두부에서 지우는 대신 뒤에 결과를 덧붙이기로 했으니, API가 그 형식을 받아주는지에 호환성이 달려 있다. OpenAI 직접 호출이 아니라면 쓰기 전에 확인할 첫 항목이다.
벤치마크 표에서 읽을 것과 걸러낼 것
네 벤치마크 모두 GPT-6 Astra xhigh로 돌렸다. Harbor에서 재현할 수 있는 셋과 그렇지 않은 하나다.
| 벤치마크 | 하네스 | 통과율 | 총비용 | 시행당 입력 | 턴 |
|---|---|---|---|---|---|
| Terminal-Bench 4.0 | Unreal Agent | 57.9% | $1,428 | 1.73M | 28 |
| Codex (리더보드) | 57.9% | $2,350 | – | – | |
| Pi | 55.0% | $1,827 | 2.83M | 44 | |
| SWE-Atlas Codebase QnA | Unreal Agent | 65.8% | $936 | 898k | 16 |
| Codex | 63.3% | $1,303 | 1.69M | 22 | |
| Pi | 64.0% | $1,033 | 1.29M | 24 | |
| DeepSWE 1.1 | Unreal Agent | 72.4% | $1,367 | 1.60M | 26 |
| Codex | 69.0% | $1,633 | 2.19M | 30 | |
| Pi | 69.6% | $1,584 | 2.21M | 40 | |
| ALE-CLI | Unreal Agent | 30.0% | $217 | 0.76M | 18 |
| Codex | 29.0% | $292 | 1.59M | – | |
| Pi | 29.0% | $262 | 1.19M | 27 |
읽을 것은 턴과 시행당 입력 토큰이다. Pi와 비교하면 Terminal-Bench에서 44턴이 28턴으로, 입력이 2.83M에서 1.73M으로 줄었다. DeepSWE에서는 40턴이 26턴이다. 통과율은 비슷하고 턴이 줄었으니 비용이 줄었다. 앞 절의 두 변수 중 턴 수를 줄인 결과가 그대로 표에 나온다. 통과율 차이는 Unreal Labs 스스로 벤치마크 변동으로 본다고 적었으므로 성능 우위로 읽지 않는 게 맞다.
걸러낼 것도 있다. 전부 자체 실행이다. HN에서 대표 차트가 Unreal Agent는 xhigh, Codex는 max로 비교돼 있다는 지적이 나왔고, Unreal Labs 쪽은 “우리가 게을렀다, 고치겠다”고 답했다. 개별 벤치마크는 셋 다 xhigh로 비교했고 대표 수치도 xhigh 기준으로 계산했지만, 종합 지표(Agentic Coding Index)의 파레토 차트에 기본으로 Codex max가 들어가 혼동이 생겼다는 설명이다. Terminal-Bench의 Codex 값은 직접 돌린 것이 아니라 리더보드 기준선이라 턴과 입력 토큰이 비어 있다. Harbor 실행 ID가 표에 붙어 있으니 재현은 가능하다.
이미 있던 것들
비동기 도구 호출 자체는 새롭지 않다. HN 댓글에서 여러 사람이 짚었고, 확인해 보면 맞다.
- Claude Code는 Bash 도구에 백그라운드 실행 옵션이 있고, 작업이 끝나면 모델을 자동으로 다시 부른다. 도구 설명에 폴링하지 말라는 문장이 들어 있다.
- tekacs는 1~2월에 Codex 포크에 백그라운드 명령이 끝나면 모델을 깨우는 패치를 넣고 계속 리베이스하고 있다. 도구 설명도 “빈 폴링 대신 완료 알림을 기다려라”로 바꿨다. HN에서 절감 폭이 Unreal이 보인 것과 비슷했다고 썼다.
- Codex 이슈 13733 스레드에서는 5월에 “런타임이 대기를 소유하고 실제 상태 변화가 있을 때만 모델을 다시 부르자”는 방향으로 논의가 모였고, 9월에는 한 사용자가 콜백 스킬로 우회한 사례를 올렸다.
- Pi 사용자는 백그라운드 작업이 끝나면 에이전트를 깨우는 pi-notify 확장을 Claude에게 짜게 해서 해결했다고 HN에 적었다.
Unreal Agent는 여기에 세 가지를 더했다. 이 구조를 옵션이 아니라 하네스의 유일한 동작으로 만들었고, 그 효과를 같은 모델로 네 벤치마크에서 비용으로 보여줬고, Go 라이브러리로 떼어 냈다. 같은 시기에 나온 HarnessTax 논문이 같은 모델이라도 하네스에 따라 비용이 최대 5배 벌어진다는 걸 21개 모델·하네스 쌍으로 보였는데, Unreal Agent는 그 차이를 만드는 요인 하나를 골라 격리한 사례로 읽을 수 있다.
내 구성에서 확인할 것
이 글의 숫자를 내 상황에 옮기려면 다음을 순서대로 보면 된다.
먼저 기다리는 턴이 얼마나 있는지 센다. 세션 로그에서 도구 동작이 대기나 상태 확인뿐인 턴을 골라 그 턴의 입력 토큰을 합친다. 이슈 35259 보고자가 쓴 방법이 그대로 쓸 만하다. 이 비율이 한 자릿수면 하네스를 바꿔도 얻을 게 적다. 20% 근처면 앞 절의 절감 폭이 내 워크로드에서도 나올 수 있다. 과금 방식도 같이 본다. API 과금이면 캐시 히트도 돈이고, 구독이면 OpenAI 설명대로 캐시된 토큰은 사용량에 안 잡힌다.
Codex를 쓰고 있다면 하네스 설정으로 줄일 수 있는 폭에 한계가 있다. 이슈 13733의 9월 재현에 따르면 0.153.4에서 exec_command의 대기 요청은 30초로 잘리고, 더 긴 값을 요청해도 폴링 빈도가 줄지 않았다. 완료 알림 방식이 들어오기 전까지는 tekacs 포크나 콜백 스킬 같은 우회가 남은 선택지다. HN에서 v0.148의 async가 도구 호출도 비동기로 바꿨다는 말이 나왔는데, 릴리스 노트에서 확인한 범위는 훅 비동기 실행이고 도구 호출 완료 알림은 확인하지 못했다.
직접 하네스를 만들거나 고친다면 확인할 항목은 다섯 개다.
- 도구 결과 자리표시자를 넣고, 실제 결과가 오면 커밋 전에는 교체하고 커밋 후에는 덧붙인다. 덧붙이는 경우 쓰는 공급자가 같은 호출 ID의 결과 두 개를 받는지 미리 테스트한다. 안 받으면 결과를 덧붙이는 대신 새 사용자 메시지로 감싸는 식의 대안이 필요하고, 그 대안이 캐시에 미치는 영향을 잰다.
- 결과 묶기 시간을 둔다. Unreal Agent는 1초다. 빠른 도구까지 전부 새 턴을 깨우면 턴 수가 오히려 는다.
- 하트비트 간격을 정한다. 완료 알림이 유실됐을 때 모델이 영원히 자지 않게 하는 보험이다. 10분이 기본값이고 짧을수록 폴링으로 되돌아간다.
- 프롬프트에서 “한 턴에 넓게” 지시와 “지켜보지 마라” 지시를 같이 넣는다. HN의 Crush·Claude Code 경험담처럼 모델이 비동기 호출 직후 긴 타임아웃으로 wait를 부르면 같은 문제로 돌아간다. 이 지시가 먹히는지는 로그의 대기 턴 비율로 확인한다.
- 도구 출력에 상한을 건다. Unreal Agent의 Bash는 기본 4만 자에서 머리와 꼬리를 남기고 자르며 전체 스트림은 파일 경로로 준다. 열린 이슈 중에는 백그라운드 Bash가 자기 출력 파일을 읽어 61GB까지 커진 사례가 있다. 턴을 줄여도 턴 하나의 컨텍스트가 커지면 앞에서 본 두 변수 중 두 번째, 턴마다 읽는 컨텍스트 길이가 다시 는다.
채택 기준은 같은 과제, 같은 완료 기준에서 통과율·턴 수·입력 토큰·달러를 나란히 놓는 것이다. 턴만 줄고 통과율이 떨어졌다면 모델이 결과를 기다리지 않고 성급히 끝낸 것이니 되돌린다.
정리
Unreal Agent는 모델이 도구 완료를 확인하려고 쓰던 턴을 줄였다. 이 낭비는 Codex 저장소에 3월부터 이슈로 있었고, 7월 측정에서는 토큰의 19.8%였다. Unreal Agent는 완료 판단을 하네스로 옮기고, 자리표시자와 1초 묶기와 10분 하트비트로 모델이 물어볼 일을 없앴다. 그 대가로 같은 호출의 결과를 두 개 남기는 형식을 쓰고, 이 형식을 거부하는 공급자에서는 세션이 깨진다. 벤치마크는 자체 실행이고 대표 차트는 비교 조건이 어긋나 수정 예정이지만, 턴 수와 입력 토큰이 줄어 표의 개별 값을 보면 턴 수와 입력 토큰이 줄어 비용이 줄었다. 같은 구조는 Claude Code에 이미 있고 Codex 포크와 Pi 확장으로도 나와 있다. 공개 사흘 된 코드이니 도입보다는 자기 로그에서 기다리는 턴을 세어 보는 데 먼저 쓰는 게 맞다.
참고
- Unreal Agent 공개 글 (Unreal Labs, 2026-09-22)
- unreallabsai/unreal-agent (GitHub) · 이슈 11: 같은 call_id 결과 중복으로 400 · 이슈 3: 출력 파일 61GB
- Hacker News 스레드
- openai/codex 이슈 13733: 백그라운드 폴링이 매번 전체 히스토리를 보낸다 · 이슈 35259: 대기·상태 확인 턴이 토큰의 19.8%
- tekacs/codex: 백그라운드 명령 완료 시 모델 깨우기
- HarnessTax: How Much Does the Harness Matter for Coding Agents? (Pan, Yang, Arabzadeh, Chiang, Stoica, Zaharia, 2026)
- (KV) Cache Rules Everything Around Me (Complete Skeptic, 2026-09-09)
- Terminal-Bench 4.0 리더보드 (Harbor Hub)
벤치마크 수치는 Unreal Labs 자체 실행값이며 이 글에서 재현하지 않았다. 코드 설명은 2026-09-23 커밋 기준이다. Codex 폴링 비용에 관한 수치는 사용자 보고이고, OpenAI 기여자의 반론은 같은 이슈 스레드에 있다.