Retort Tasks: 무엇이 구축되며, 실행 결과가 얼마나 다르게 통과할 수 있는가
요약
Retort 프레임워크를 통해 AI 에이전트가 수행하는 태스크의 실행 효율성과 신뢰성을 분석합니다. 에이전트가 동일한 요구사항을 충족하더라도 과잉 엔지니어링이나 도구 사용 방식에 따라 실행 속도에서 큰 차이가 발생함을 보여줍니다.
핵심 포인트
- 에이전트의 성능은 단순 성공 여부를 넘어 실행 시간의 효율성으로 측정 가능함
- 동일한 태스크에서도 실행 속도가 최대 61배까지 차이 날 수 있음
- 과잉 엔지니어링, 언어 특성, 도구 사용 방식이 실행 속도 차이의 주요 원인임
- rest-api-crud 태스크는 에이전트의 능력이 아닌 신뢰성을 측정하는 척도로 활용됨
이것은 https://github.com/adrianco/retort/blob/main/tasks-blog.md에서 자동 생성된 분석 보고서의 스냅샷입니다.
발행일 2026-07-30 · 수정일 2026-07-30 — Adrian Cockcroft
Retort가 에이전트(agent)에게 실제로 무엇을 구축하도록 요청하는지, 그리고 각 태스크(task)별로 완전히 통과한 실행 중 가장 빠른 실행과 가장 느린 실행은 무엇인지 보여줍니다. 두 경우 모두 requirement_coverage == 1.0 점수를 기록한 실행들 중 가장 짧거나 긴 duration_seconds를 의미하며, 로그가 없는 기록은 표시할 수 없으므로 에이전트 로그(agent log)가 아카이브된 실행들로 제한됩니다.
느린 쪽의 결과도 빠른 쪽만큼이나 주목할 가치가 있습니다. 거기서는 실패하는 것이 없습니다. 아래의 모든 실행은 완벽한 1.00 점수를 기록합니다. 다만 그 결과에 도달하는 데 4배에서 61배 더 오래 걸릴 뿐이며, 그 이유는 태스크마다 다릅니다. 어떤 것은 과잉 엔지니어링 (over-engineering) 때문이고, 어떤 것은 언어 자체 때문이며, 세 번째는 시간을 소모하는 도구 때문입니다.
태스크 정의는 tasks/에 있으며 tasks/registry.yaml에 의해 인덱싱됩니다. 등록된 7개의 태스크 중 3개가 대규모로 실행되었습니다. 나머지 4개(react-dashboard, cli-data-pipeline, brazil-bench-neutral, funkygibbon-port)는 정의되어 있으나 점수가 매겨진 실행이 거의 없거나 없습니다.
기록에 대한 주의 사항.
master.db에 두 개의 더 빠른 실행이 존재하지만 여기에는 표시되지 않습니다: 2.49분의 brazil (exp-2)와 0.71분의 rest-api (exp-1)입니다. 두 실행 모두 에이전트 로그 아카이브와 기계적 게이트(mechanical gate)가 도입되기 이전의 것입니다. 형제 격인 exp-2 "통과"는test_coverage=0.0을 가지고 있는데, 이는 오늘날 자동으로 실패 처리됩니다. 이들은 아래의 실행들과 비교할 수 없습니다.
1. rest-api-crud — 일상적인 태스크
출처: tasks/rest-api-crud/ · 712회 실행 · "쉬운" 태스크
도서 컬렉션을 위한 CRUD REST API 구축: POST /books, GET /books (?author= 필터 포함), GET/PUT/DELETE /books/{id}, 그리고 GET /health. SQLite 또는 해당 언어의 내장 데이터베이스를 사용하며, 올바른 상태 코드(status codes)를 포함한 JSON 응답, 입력 유효성 검사 (input validation), README, 그리고 최소 3개의 테스트를 포함해야 함. 13개 언어 모두에서 점수가 매겨짐.
이것은 일꾼(workhorse) 역할을 합니다. 의도적으로 평범하게 설계되었습니다. 핵심은 숙련된 스택이라면 매번 이 태스크에서 1.00점을 받아야 한다는 것이며, 따라서 이는 능력이 아닌 _신뢰성 (reliability)_을 측정합니다.
가장 빠른 기록된 통과 — 44.5초
| Stack | Claude Fable 5, effort=low, python, prompt=neutral |
| ... | |
| 전체 실행 과정을 요약하면 다음과 같습니다: |
[Read] TASK.md
[TEXT] "Flask + SQLite 기반의 테스트를 포함한 도서 API를 구축하겠습니다."
[Write] app.py
...
한 번의 읽기, 네 번의 쓰기, 한 번의 검증 명령으로 완료되었습니다. 탐색도, 반복도, 실패한 시도도 없었습니다. 이것이 모델이 단순히 정답을 알고 있을 때의 일상적인 태스크(routine task) 모습이며, 이것이 바로 일상적인 태스크가 더 이상 최첨단 모델(frontier models) 간의 _신뢰성_을 차별화하지 못하고 오직 비용으로만 차별화하는 이유입니다.
가장 느린 기록된 통과 — 45.6분, 동일한 1.00 점수
| Stack | Claude Opus 5, effort=max, python, prompt=neutral |
| ... | |
Fable 5의 194줄에 비해 2,351줄을 작성했습니다. 7개의 모듈로 구성된 패키지를 구축했고, 요청하지 않았음에도 ruff를 사용하여 스스로 린팅(linting)을 수행했으며, 하위 에이전트(subagent)를 생성하고, 도서 CRUD API에 대해 **변이 테스트 (mutation testing)**를 실행했습니다. 심사관이 찾아낸 5가지 사항은 모두 명세(spec)를 넘어서는 범위를 기록한 info 사항이었습니다: PATCH 엔드포인트, 필터링 및 페이지네이션(pagination), 직접 작성한 304줄의 OpenAPI 문서, WAL 저널링, NUL-byte 유효성 검사 등입니다. 태스크는 5개의 엔드포인트, 헬스 체크(health check), 그리고 최소 3개의 테스트를 요구했습니다. |
이것은 품질 저하(slop)가 아닙니다. 유지보수성(maintainability) (0.85 대 0.27)과 관용성(idiomaticity) 측면에서 더 높은 점수를 받았습니다. 하지만 게이트(gate)는 두 실행을 구분할 수 없습니다. 둘 다 1.00이기 때문입니다. 사고 조절 다이얼(thinking dial)이 얼마나 많은 변동성을 추가하는지를 포함한 전체 분석 내용은 experiments-blog.md에 있습니다.
2. brazil-bench — 어려운 태스크
Source: github://brazil-bench/benchmark-template · 284회 실행
6개의 실제 Kaggle 브라질 축구 CSV 파일(세 가지의 서로 다른 스키마를 가진 5개 파일에 걸친 23,954개 경기, 그리고 18,207명의 FIFA 선수 데이터)을 사용하여 MCP 서버를 구축합니다. REQUIREMENTS.json에 명시된 12개의 고정 요구사항: 팀별 / 날짜 범위 / 대회 / 시즌별 경기 쿼리, 팀 승-무-패 기록, 선수 검색 및 필터링, 결과로부터 계산된 시즌 순위, 집계 통계, 상대 전적(head-to-head), 그리고 자동화된 테스트가 포함됩니다.
이 태스크는 알고리즘과는 무관한 이유들로 인해 어렵습니다: 팀 이름에 상태 접미사와 악센트가 포함되어 있고(São Paulo-SP vs Sao Paulo), 한 파일은 포르투갈어 컬럼명과 DD/MM/YYYY 날짜 형식을 사용하며, 데이터셋이 중복되어 있습니다 — 동일한 실제 경기가 두 개 또는 세 개의 파일에 나타납니다.
가장 빠른 기록된 통과 시간 — 3분 19초
| Stack | GPT-5.6 Terra (codex), effort=medium, python, prompt=neutral |
| ... | |
| 이전의 현대적 하네스(harness)에서의 최고 기록은 5.67분(Opus 5, clojure)이었으며, 이전의 가장 빠른 python 기반 brazil 통과 기록은 5.02분이었습니다. 이것은 단순한 반올림 차이가 아닌, 실질적인 단계적 변화(step change)입니다. |
실행 과정 요약:
[CMD] ls && sed -n '1,240p' TASK.md && rg --files # 명세서 읽기 + 리포지토리 확인
[CMD] sed -n '241,520p' TASK.md ... head data/kaggle/*.csv # 나머지 명세서 읽기 + 데이터 형태 확인
[CMD] sed -n '1,240p' prompts.txt; wc -l data/kaggle/*.csv # 행 수 계산
...
16단계의 과정, 2번의 실제 실패가 있었으나 모두 진단 및 수정되었습니다. 운 좋게 한 번에 성공한 것이 아닙니다.
솔루션의 모습. 3개 파일에 걸친 342줄의 코드이며, 의존성 없음 (zero dependencies) — 오직 Python 표준 라이브러리만 사용합니다:
brazilian_soccer_mcp.py(291줄) —SoccerData서비스 및 MCP 레이어.server.py(5줄) — 엔트리포인트(entrypoint),serve()호출.test_brazilian_soccer_mcp.py(46줄) — BDD 방식으로 명명된 7개의 테스트.
Kaggle 데이터가 실제로 병합되고 로드되었나요? 네. 실행 중 확인된 모든 6개 파일은 23954 경기와 18207 선수를 로드했으며, 이는 정확히 10296 + 4180 + 1337 + 1255 + 6886입니다. 세 가지 다른 행 구조를 가진 다섯 개의 경기 파일은 디스패칭(dispatching) 로우-매퍼(row-mapper)에 의해 하나의 고정된 Match dataclass로 정규화됩니다:
| Kind | Files | Columns it reads |
|---|---|---|
standard | Brasileirao, Copa do Brasil, Libertadores | datetime, home_team, away_team, home_goal, season, round |
| ... | ||
선수 이름은 악센트를 제거하고(strips accents), 소문자로 변환하며(lowercases), 모든 27개 브라질 주(UFs)와 남미 국가 코드를 나열한 정규표현식(regex)을 통해 주 접미사(state suffixes)를 제거하는 normalized() 함수를 거쳐 일치시킵니다 — 따라서 São Paulo-SP와 Sao Paulo는 동일하게 비교됩니다. 날짜는 다중 형식의 parse_date를 거칩니다. 이 두 가지 모두 사양서(spec)의 데이터 품질 노트에 대한 직접적인 답변입니다. |
데이터베이스 백엔드는요? 없습니다. 데이터베이스도 그래프 저장소도 없습니다 — SQLite, Neo4j, networkx와 같은 어떤 것도 없습니다. SoccerData는 self.matches: list[Match]와 self.players: list[dict]를 포함하며, 모든 쿼리는 23,954개 경기에 대한 **선형 스캔(linear scan)**이며 호출마다 재필터링됩니다. 에이전트 자체의 요약에서는 이를
게이트(gate)가 잡아내지 못한 결함입니다. 소스 파일들의 커버리지가 중복되고(BR-Football 2014–2023, Brasileirao_Matches 2012–2022, novo_campeonato 2003–2019), load() 함수가 중복 제거 없이 데이터를 연결(concatenate)하기 때문에, 동일한 실제 경기가 두 번 또는 세 번씩 계산됩니다. 에이전트는 이를 부분적으로 인지했습니다. 에이전트는 standings()가 전용 파일만을 사용하도록 가드(guard)를 추가했지만, 이는 오직 Brasileirão의 2003~2019년 시즌에만 적용되었습니다. 이것이 에이전트가 출력한 2019년 순위표가 역사적으로 정확했던(Flamengo, 38경기 출전, 90점) 이유입니다.
그 외의 모든 것은 여전히 중복 계산되고 있습니다. 실행 결과의 MCP 핸드셰이크(handshake)가 이를 증명합니다:
{"team": "Corinthians", "season": 2022, "venue": "home",
"matches": 44, "wins": 28, "draws": 11, "losses": 5, "goals_for": 62}
Brasileirão 클럽은 시즌당 19번의 홈 리그 경기를 치릅니다. 홈 컵 대회와 리베르타도레스(Libertadores) 경기를 합산하더라도 약 25경기가 최대치입니다. 44라는 숫자는 리그 경기 수가 두 번 계산된 데다 컵 대회까지 더해진 결과입니다. 명세서(spec) 자체의 작동 예시에서도 Corinthians의 2022년 홈 기록은 19경기, 11승, 5무, 3패라고 명시되어 있습니다.
테스트가 이를 잡아내지 못하는 이유는 테스트가 회계 항등식(accounting identities) — 즉, matches == wins + draws + losses, points == wins*3 + draws — 을 검증하기 때문입니다. 모든 경기가 중복 계산되더라도 이 식들은 여전히 참으로 유지됩니다. 심사관(judge)은 이 중복 문제를 **낮음/개선 사항(low/enhancement)**으로 분류하고 standings()에만 국한시켰으나, 문제는 그보다 더 광범위하며 team_statistics, head_to_head, search_matches, aggregate_statistics에 존재하는 정확성 결함입니다.
이것은 태스크 체크리스트에 관한 발견 사항이며, 실행 결과 자체를 철회하는 것은 아닙. 3분 19초라는 시간과 12/12라는 결과는 측정된 대로 유지됩니다. 하지만 "고정된 요구사항을 통과함(passes the pinned requirements)"과 "정확한 답변을 반환함(returns correct answers)"은 같은 의미가 아니며, 이 사례에서는 두 개념이 서로 갈라집니다.
가장 느리게 기록된 통과 — 64분간의 Objective-C
| Stack | Claude Opus 5, objc, prompt=neutral |
| ... | |
A 완전히 다른 실패 모드입니다. 이것은 단순히 결과물을 다듬는(gold-plating) 문제가 아니라, 바로 **언어(language)**의 문제입니다. 9,000줄에 달하는 .m 및 .h 파일 — 직접 구현한 CSV 파서, 매치/플레이어 모델, 쿼리 엔진(query engine), 그리고 MCP 서버 — 이 모든 것은 Objective-C가 이 작업에서 의존할 만한 생태계를 제공하지 않기 때문에 발생했습니다. 도구 사용 내역(tool mix)이 이를 명확히 보여줍니다: 59번의 편집(Edits), 42번의 쓰기(Writes), 54번의 Bash 실행, 그리고 단 **2번의 읽기(Reads)**뿐이었습니다. 모델은 탐색을 하고 있었던 것이 아니라, 단순히 타이핑을 하고 있었습니다. |
비용을 확인하는 가장 깔끔한 방법은 모델과 작업을 고정하고 언어만 변경해 보는 것입니다. Opus 5는 이 작업에 대해 13개 언어 모두를 실행했으며, 모든 언어가 1.00의 점수를 기록했습니다:
| clojure | python | rust | swift | java | go | cpp | erlang | c | csharp | elixir | objc | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| min | 5.7 | 16.1 | 38.2 | 39.4 | 41.4 | 42.2 | 42.3 | 52.8 | 53.2 | 56.4 | 58.3 | 64.0 |
| $ | 2.55 | 9.38 | 20.08 | 16.87 | 20.18 | 22.35 | 17.50 | 23.01 | 24.11 | 39.05 | 33.61 | 31.31 |
동일한 모델, 동일한 사양, 동일한 1.00의 점수임에도 불구하고, 전적으로 언어에 의해 실제 소요 시간(wall clock)은 11배, 비용은 15배의 차이가 발생했습니다. (TypeScript는 제외되었습니다: 59.9분 만에 통과했으나 $0.00으로 기록되었는데, 이는 무료 실행이 아니라 텔레메트리(telemetry) 상의 공백 때문입니다.) 이는 모델이 아니라 _스택(stack)_이 측정 단위라는 Retort의 전제를 보여주는 가장 강력한 사례입니다.
3. py-catalog-reservations — 기존 수정 작업 (modify-existing task)
출처: tasks/py-catalog-reservations/ · 18회 실행
나머지 두 작업은 빈 디렉토리에서 시작합니다. 반면 이 작업은 작동하는 111줄 규모의 catalog/ 패키지(모델, 저장소, 대여, 서비스 파사드(service facade)) 및 기존 테스트 스위트를 포함하여 제공한 뒤, 예약(reservations) 기능을 추가할 것을 요구합니다: 복사본이 0개일 때만 예약 가능, FIFO(선입선출) 순서, 복사본 반납 시 자동 이행, 취소 기능 등을 모두 파사드를 통해 노출해야 하며, 이 과정에서 이미 통과하고 있는 6개의 테스트를 깨뜨려서는 안 됩니다 (no_regression 게이트).
graphify tooling factor를 위해 구축되었습니다: 에이전트가 직접 작성하지 않은 코드를 이해해야 합니다.
가장 빠른 기록된 통과 — 72.8s
| Stack | Claude Opus 4.8, tooling=none, python |
| ... | ... |
[Read] TASK.md
[Read] catalog/models.py, catalog/store.py, catalog/loans.py,
catalog/service.py, tests/test_catalog.py, conftest.py # 먼저 모든 것을 읽습니다
...
그린필드 (greenfield) 작업과 비교했을 때 그 형태에 주목하십시오: 첫 번째 수정이 이루어지기 전에 6번의 읽기(reads)가 수행되었습니다. 기존 것을 수정하는 (modify-existing) 작업에서 에이전트는 이해 과정을 앞부분에 집중시킨 후, 정밀하게 수정합니다 — models.py와 service.py는 다시 작성되는 것이 아니라 수정됩니다. 그 차이가 바로 이 작업이 존재하는 이유 전체입니다.
가장 느린 기록된 통과 — 4.5분, 그리고 무료
| Stack | Qwen3-Coder-Next 80B local (Hermes + oMLX), tooling=beads, python |
| ... | ... |
세 번째 실패 모드는 사실 실패가 아닙니다. 이 실행은 클라우드 기록보다 3.7배 느리지만 비용이 전혀 들지 않습니다 — 노트북에서 실행되었습니다. 한 번만 실행하는 작업이라면 0.43달러와 73초가 승리하겠지만, 루프(loop) 내에서 반복 실행하는 작업이라면 무료 옵션이 산술적 계산을 바꿉니다. 이는 시계 옆의 비용 열을 확인해야 하는 가장 명확한 사례입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기