TL;DR — DSH 하네스의 도구, 설정, 지침을 통해 Terminal-Bench의 10개 과제를 수행하며, 비용을 최대 30% 절감한 경험을 공유합니다.
Table of contents
Open Table of contents
들어가며
hel 하네스를 만드는 과정을 통해 하네스의 내부 작동 원리와 구조를 확인할 수 있었습니다. 그래서 이번엔 상용 하네스인 DSH(DeepSeek Harness)를 새로운 샌드박스로 삼아 하네스의 최적화 작업을 진행해보기로 했습니다.
실험 환경
- 기준 과제: Terminal-Bench의 10개 과제를 각 1회씩 실행합니다.
- 채점: 공식 verifier로 채점
- 모델:
gpt-6.1-sol(reasoning high)
Terminal-Bench의 모든 과제는 비용과 시간의 문제로 측정하지 못했습니다. 비용은 실행한 결과를 OpenAI의 모델 API 단가로 환산한 값입니다.
1. 하네스의 역할이 바뀌었다
올해 초까지만 하더라도 Claude Code, Codex 같이 모델 프로바이더가 제공하는 하네스를 이용한 작업을 진행할 때, task를 여러 개로 다 나누어 놓고 이를 순차적으로 실행하라는 지시를 해도, 모델 자체의 작업 문맥 파악 능력 혹은 하네스의 미숙함 때문에 중간에 멈춘 경험을 해 본 적이 있었을 것입니다.
그러나 불과 몇 개월이 지난 지금은 모델의 성능과 하네스 모두 비약적으로 발전했습니다. 모델은 사용자 지시를 수행하기 위해 최선을 다해 추론을 하고 사용할 수 있는 도구를 사용합니다. 하네스는 이러한 작업이 끊기지 않도록 agent loop를 안정적으로 제공해주는 기반이 됩니다.
그래서 이 부분이 오히려 독이 되는 부분이 있습니다.
2. 순정 DSH: 시킨 일을 너무 성실히 한다
다음 표는 순정 DSH와 Mixdog을 같은 10개 과제를 바탕으로 측정한 결과입니다.
| 지표 | 순정 DSH | Mixdog | 차이 |
|---|---|---|---|
| 공식 통과 | 10/10 | 10/10 | 동일 |
| 모델 응답 단계 | 185 | 105 | +76.2% |
| 누적 입력(캐시 포함) | 3,813,636 | 1,359,142 | +180.6% |
| 출력 | 31,062 | 31,522 | −1.5% |
Mixdog은 하네스 최적화가 적용된 레퍼런스 프로젝트입니다. 순정 DSH와 비교를 위해 같이 측정을 진행했습니다.
두 하네스 모두 10개 과제를 모두 통과했습니다. 모델의 응답 단계와 입출력 토큰을 보면 큰 차이가 있음을 확인할 수 있습니다.
모델 응답 단계는 모델이 응답 하나를 완료한 횟수입니다. 이 글에서는 이를 모델과 하네스 사이의 왕복이라고 부르겠습니다. 왕복이 늘어난 대표적인 원인은 두 가지였습니다.
- planning 왕복: DSH의 Todo 도구 설명에는 다단계 작업이면 목록을 먼저 만들고, 진행 상태를 갱신하라는 지시가 있습니다. 한 단일 과제 실행에서는 33단계 중 7단계가
create_goal,todo_write,get_goal,update_goal같은 planning tool과 관련된 호출이었습니다. - 결과 재조회 왕복: 긴 명령을 짧은 timeout으로 실행한 뒤
job_output으로 결과를 다시 조회하는 패턴이 반복됐습니다.
이와 같이 모델은 과제 수행을 위해 직접적으로 필요하지 않은 todo, goal과 같은 도구를 tool 설명을 보고 자의적인 판단에 의해 성실하게 사용했습니다.
또한 결과 재조회 왕복에서는 하네스가 너무 성실하게 모델의 단순 작업 완료 확인을 위한 요청도 매번 즉답으로 모델에게 응답을 주면서 하네스와 모델 사이의 왕복이 추가되는 현상을 확인했습니다.
이렇게 tool을 호출하고, 하네스와 모델 사이의 왕복이 반복되면 컨텍스트가 점점 누적되고, 이에 따라 압축 정책과 비용에 영향을 주게 됩니다. 어떻게 보면 하네스와 모델이 똑똑해지면서 발생하는 일이라 볼 수 있는 것이죠.
3. 세 번의 조절
DSH는 내부 코드를 직접 수정하지 않고, 플러그인을 바탕으로 훅이나 tool 등을 추가하여 기본 작동 방식을 고칠 수 있습니다.
다이어그램의 system-prompt/assemble, tools/post-execute는 DSH의 기본 agent loop에 추가할 수 있는 훅입니다. 실험에서 진행한 지침 조절과 offload 등의 작업은 이 훅을 바탕으로 진행됐고, tool 역시 플러그인을 이용해 추가를 했습니다.
각 단계 별로 진행된 수정을 한 번에 살펴보고, 최종 결과는 다음 절에서 살펴보도록 하겠습니다.
flowchart LR Assemble["system-prompt/assemble 지침·도구 설명 수정"] --> Model["모델"] Model --> Tool["도구 실행"] Tool --> Post["tools/post-execute 긴 출력 offload"] Post --> Model
const decision = await ctx.waterfall(
scope,
"tools/post-execute",
exec,
result,
() => Promise.resolve({ kind: "accept" })
);
3.1 1차: 도구의 추가
첫 수정은 주로 모델이 사용할 수 있는 도구를 추가했습니다.
- 긴 도구 출력은 파일에 저장하고 미리보기만 모델에 전달하는 offload를 추가했습니다.
- 여러 파일을 한 번에 읽는
test_batch_read도구를 추가했습니다.
그 결과는 단일 과제 기준으로 누적 입력은 28.4% 줄었지만, 왕복은 29단계에서 33단계로 오히려 늘었습니다. test_batch_read는 한 번도 호출되지 않았습니다.
3.2 2차: 지침의 수정
두 번째 수정은 주로 DSH의 시스템 프롬프트에 작성된 지침을 수정한 것입니다.
- 독립적인 도구 호출은 한 응답에 묶기
- 결과가 필요한 명령은 포그라운드에서 충분히 기다리기
- 요구사항에 맞는 만큼 검증하고, 증거와 한계를 요약한 뒤 종료하기
- headless 실행에서는 Goal/Todo 노출을 제외하기
지침에서 요구한 내용들은 DSH의 기본 제공 도구에서도 지원하는 기능입니다. 만약, 이렇게 지침을 내렸어도, 모델이 사용하는 하네스의 도구가 이런 기능을 제공하지 않으면 오히려 부작용이 생길 수도 있습니다.
이 지침을 적용해 1개의 과제를 수행한 결과 모델과 하네스 사이의 왕복이 33 -> 6으로 줄었고, 누적 입력은 무려 93.7% 감소했습니다. (물론 이는 테스트에 사용한 과제의 특징 때문이기도 합니다. 일반적으로 이렇게 감소하지는 않습니다.)
그러나 이번에도 test_batch_read는 역시 호출하지 않았습니다. 대신 모델은 한 번의 응답에 여러 도구를 요청하는 방식을 택했습니다.
3.3 3차: 도구의 설정 변경
마지막 수정에서는 모델의 대기 작업에 소요되는 기본 대기 시간을 30초 -> 120초로 늘렸고, 기존 테스트 로그에서 의존성이 있는 작업의 순서를 무시한 상태로 호출을 하여 에러가 발생한 경우가 있어 task_run이라는 도구를 추가해 의존성 있는 작업의 순서를 지정할 수 있게 했습니다.
다만 결과적으로 task_run은 한 번도 사용하지 않았습니다. 전반적으로 2차 수정보다 누적 입력은 20% 정도 줄었지만, agent 시간 합계는 10.9% 증가했습니다.
4. 결과
순정 상태와 3차 수정까지 완료된 DSH, 그리고 Mixdog의 10개 과제 결과를 요약하면 다음과 같습니다.
| 지표 | 순정 DSH | 3차 DSH | Mixdog |
|---|---|---|---|
| 공식 통과 | 10/10 | 10/10 | 10/10 |
| 실행 오류·미채점 | 0건 | 0건 | 0건 |
| 모델 응답 단계 | 185 | 107 | 105 |
| 누적 입력(캐시 포함) | 3,813,636 | 1,481,769 | 1,359,142 |
| 비캐시 입력 | 229,764 | 188,713 | 205,990 |
| 캐시 읽기 입력 | 3,583,872 | 1,293,056 | 1,153,152 |
| 출력 | 31,062 | 27,998 | 31,522 |
| 캐시 히트율 | 93.98% | 87.26% | 84.84% |
| agent 시간 합계(초) | 1,685.4 | 1,613.0 | 1,792.6 |
| API 환산 비용(USD) | 1.1285 | 0.7867 | 0.8425 |
캐시 히트율은
캐시 읽기 / (비캐시 입력 + 캐시 읽기)로 계산한 토큰 기준 비율입니다. agent 시간은 에이전트가 과제를 수행한 시간의 합이며, 실행 환경 준비와 채점 시간은 제외했습니다.
비용은 100만 토큰당 비캐시 입력 2.00달러, 캐시 읽기 0.10달러, 출력 10.00달러 단가로 계산했습니다.(OpenAI 공식 문서 기준)
3차 수정까지 반영한 DSH는 응답 단계와 토큰 소모량이 줄어들어 전반적인 비용의 감소로 이어지게 됐습니다. 특히 순정 버전에 비해서는 약 30%의 비용 절감 효과를 누릴 수 있었습니다.
5. 줄여야 할 것은 왕복, 비용을 정하는 것은 토큰
결과 표를 보면 왕복이 줄면서 비용도 줄었습니다. 하지만 두 값이 같은 비율로 움직이지는 않았습니다. 이 절에서는 왕복, 토큰, 캐시, 비용이 어떻게 연결되는지 정리합니다.
5.1 왕복은 문맥을 다시 읽게 만든다
모델은 상태를 기억하지 않습니다. 하네스는 매 요청마다 시스템 프롬프트, 도구 설명, 지금까지의 대화와 도구 결과를 모두 다시 보냅니다. 그래서 왕복이 한 번 늘면 그 시점까지 쌓인 문맥 전체가 한 번 더 입력됩니다.
문맥이 왕복마다 비슷한 크기로 늘어난다고 단순하게 가정하면, 번 왕복할 때의 누적 입력은 대략 다음과 같습니다.
여기서 는 왕복 한 번에 늘어나는 문맥의 크기입니다. 이 계산에서 누적 입력은 왕복 수에 비례하지 않고 그 제곱에 가깝게 늘어납니다. 실제 실행은 이렇게 균일하지 않지만, 왕복 수 차이보다 누적 입력 차이가 훨씬 크게 벌어진 이유는 이것으로 설명할 수 있습니다. 순정 DSH의 왕복은 Mixdog의 1.76배였지만, 누적 입력은 2.81배였습니다.
요청 한 번에 들어간 평균 입력도 차이가 났습니다.
| 버전 | 누적 입력 | 모델 응답 단계 | 단계당 평균 입력 |
|---|---|---|---|
| 순정 DSH | 3,813,636 | 185 | 약 20,600 |
| 3차 DSH | 1,481,769 | 107 | 약 13,800 |
| Mixdog | 1,359,142 | 105 | 약 12,900 |
왕복이 많아지면 문맥도 길어지고, 길어진 문맥을 더 많이 다시 읽게 됩니다. 불필요한 왕복을 가장 먼저 줄여야 하는 이유입니다. 다음 행동에 필요 없는 planning 호출이나 결과 재조회 호출은 그 호출 자체의 토큰보다, 이후의 모든 요청을 무겁게 만든다는 점에서 비용이 더 큽니다.
따라서 불필요한 왕복을 먼저 줄이고, 그 다음 캐시 히트율과 압축 정책을 신경 쓰는 게 하네스의 병목을 해결하는 순서라고 볼 수 있겠습니다.
5.2 비용을 정하는 것은 토큰의 종류와 단가
그렇다고 왕복 수가 비용 단위는 아닙니다. API 과금은 비캐시 입력, 캐시 읽기, 출력 토큰에 각각 다른 단가를 매깁니다. 세 버전의 비용을 토큰 종류별로 나누면 다음과 같습니다.
| 항목(USD) | 순정 DSH | 3차 DSH | Mixdog |
|---|---|---|---|
| 비캐시 입력 | 0.4595 | 0.3774 | 0.4120 |
| 캐시 읽기 | 0.3584 | 0.1293 | 0.1153 |
| 출력 | 0.3106 | 0.2800 | 0.3152 |
| 합계 | 1.1285 | 0.7867 | 0.8425 |
순정 상태와 3차 수정에서 발생한 비용 차이 중 가장 큰 비중은 캐시 읽기에서 나왔으며, 전체 비용의 차이인 0.3418달러 중 약 67%인 0.2291달러가 캐시 읽기의 차이에서 나왔습니다.(실제로 순정이 캐시 히트율이 가장 높았습니다.) 그러나 왕복을 줄이고, 문맥을 아끼는 것에서 오는 비용 절감이 더 컸기 때문에 전체적인 비용은 감소하게 됐습니다.
또한 이 결과를 보면 알 수 있듯이, 3차 수정 DSH가 Mixdog에 비해 누적 입력이 약 9% 많지만, 비용은 6.6%가 낮게 기록되었는데, 이는 DSH가 Mixdog에 비해 단가가 높은 비캐시 입력과 출력 토큰에서 비용을 줄였기 때문임을 알 수 있습니다.
앞서 말한 응답을 줄인 상태에서는 캐시 히트율과 출력 토큰과 같이 보다 세부적인 항목으로 단가 계산이 이동되는 것입니다.
그러나 왕복을 줄여야만 누적 입력이 줄어드는 것은 아닙니다. 1차 수정에서 추가한 출력 offload는 긴 도구 결과를 파일로 저장하고 미리보기만 문맥에 남깁니다. 단일 과제 실험에서 왕복은 29단계에서 33단계로 오히려 늘었지만, 누적 입력은 28.4% 줄었습니다. 문맥에 한 번 들어간 긴 출력은 이후 모든 요청에 실려 가기 때문에, 그 크기를 줄이는 것만으로도 후속 요청이 가벼워집니다.
5.3 캐시 히트율이 높다고 효율적인 것은 아니다
세 버전 중 캐시 히트율이 가장 높았던 것은 순정 DSH(93.98%)였고, 비용이 가장 높았던 것도 순정 DSH였습니다.
캐시 히트율은 입력 중 캐시에서 읽은 비율일 뿐, 입력의 총량을 말해주지 않습니다. 같은 긴 문맥을 여러 번 다시 읽으면 대부분이 캐시에 걸리므로 히트율은 올라갑니다. 그러나 캐시 읽기도 무료가 아니며, 순정 DSH의 캐시 읽기 비용(0.3584달러)은 출력 비용(0.3106달러)보다 컸습니다. 3차 DSH의 히트율이 87.26%로 떨어진 것은 효율이 나빠졌다는 뜻이 아니라, 다시 읽을 문맥 자체가 줄었다고 보는 게 더 정확한 표현일 것 같습니다.
따라서 캐시 히트율은 비용 지표가 아니라 보조 지표로 봐야 합니다. 비용을 판단할 때는 히트율보다 캐시 읽기의 절대량과 비캐시 입력, 출력의 양을 함께 확인해야 합니다.
5.4 문맥의 크기는 비용만의 문제가 아니다
문맥이 커지는 문제는 비용에서 끝나지 않습니다. 컨텍스트 윈도우는 한정되어 있어서, 문맥이 빠르게 커지면 하네스는 더 일찍 compaction으로 오래된 대화를 요약해야 합니다. 요약 과정에서 세부 정보가 빠지면 모델이 이미 확인한 내용을 다시 조회하는 왕복이 생길 수 있고, 이는 다시 문맥을 키웁니다.
이번 측정의 누적 입력은 여러 요청의 합이라 한 번의 컨텍스트 점유량과는 다르고, compaction 횟수도 따로 집계하지 않았습니다. 그래서 이 연결을 수치로 보여주지는 못합니다. 다만 불필요한 왕복과 긴 도구 출력을 줄이는 일이 비용뿐 아니라 compaction 정책과도 맞닿아 있다는 점은 하네스 설계에서 중요하게 생각해야 할 부분임은 변하지 않습니다.
정리하며
현재 실험을 진행한 수정 버전은 최대한 왕복을 줄이기 위한 방향으로 최적화가 진행됐습니다. 그러나 이러한 방식이 모든 모델과 에이전트에 최적화된 방식은 아니라 생각합니다. Goal/Todo와 같은 planning tool이 필요한 오케스트레이터나 다른 역할의 에이전트에는 또 다른 최적화 설정이 필요할 것으로 보입니다. 지금 만든 설정은 작업 명세서가 있을 때, 이를 바탕으로 작업을 수행하는 Worker 스타일에 적합하다고 보는 게 맞을 것 같습니다.
핵심 요약:
- 모델은 주어진 요청과 지침을 최대한 수행하려 합니다. 과제와 무관한 planning 지침도 왕복이 됩니다.
- 새로운 도구(
test_batch_read,task_run)는 쓰이지 않았고, 효과는 지침과 설정을 조절한 데서 나왔습니다. - 적절한 도구, 설정, 지침으로 같은 결과를 30% 낮은 비용에 얻었습니다. 프롬프트 엔지니어링과 컨텍스트 엔지니어링이 하네스 엔지니어링으로 이어지는 지점입니다.
- 하네스의 핵심은 모델의 작업 범위를 조절해 불필요한 왕복을 줄이는 것입니다.
참고 자료
- Terminal-Bench — 터미널 작업 기반 agent 벤치마크
- Harbor — 벤치마크 실행기
- OpenAI API Pricing — 비용 환산 단가 기준
- Harness Engineering Lab — 하네스를 밑바닥부터 만든 학습 프로젝트
이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.