본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 25. 09:09

세 번째 실행 경로로 Cursor Composer 2.5 추가: Opus와 유사한 점수이면서 비용은 10배 저렴하지만, 스모크 테스트 결과는

요약

Codens Purple 오케스트레이션 서비스에 비용 효율적인 Cursor Composer 2.5를 세 번째 실행 경로로 추가한 사례를 다룹니다. Opus와 유사한 성능을 유지하면서도 비용을 10배 절감하여 에이전트 운영의 단위 경제성을 개선했습니다.

핵심 포인트

  • Composer 2.5는 Opus 대비 약 10배 저렴한 비용으로 유사한 성능 제공
  • 모델별 재시도 제한(retry cap) 전략을 통해 최악의 시도 비용을 획기적으로 감소
  • 특정 벤더 종속성을 탈피하기 위한 멀티 모델 오케스트레이션의 중요성 강조
  • 스모크 테스트 결과 통합은 성공했으나 프로덕션 수준의 벤치마크는 추가 검증 필요

유사한 정확도에서 작업당 비용이 약 10배 감소한다는 것은 그리 오래 무시할 수 없는 수치 중 하나입니다. Composer 2.5는 Opus와 비슷한 수준의 SWE-Bench Multilingual 수치를 발표했으며, 시도당 API 비용은 약 한 자릿수(order of magnitude) 더 낮습니다. 프로젝트당 매주 수백 번의 시도를 실행하는 에이전트 하네스 (agent harness)의 경우, 실행 가능한 경로에서 발생하는 10배의 비용 압축은 단순한 일시적 현상이 아니라 실제 통합을 정당화할 수 있을 만큼 단위 경제성 (unit economics)을 재편합니다.

그래서 저는 각 작업에 어떤 모델을 실행할지 결정하는 오케스트레이션 서비스 (orchestration service)인 Codens Purple에 Composer 2.5를 세 번째 실행 경로 (executor lane)로 배포했습니다. Codens는 이미 두 개의 경로를 나란히 실행하고 있었습니다: Anthropic API를 통한 Claude와 자체 호스팅된 Qwen 배포 모델입니다. 세 번째 경로는 5월 23일부터 24일까지 이틀에 걸쳐 Phase 1 스켈레톤 커밋 (skeleton commit), Phase 2 SDK 와이어링 (wire), ECS Fargate 태스크 정의 변경, IAM 자격 증명 격리 수정, 그리고 단일 프로젝트 카나리 토글 (canary toggle)을 통해 도입되었습니다.

그 후 스모크 테스트 (smoke pass)를 실행했습니다. v4부터 v17까지 25번의 시도 중 16번이 실패했습니다. 통합은 작동합니다. 벤치마크 수치는 프로덕션 (production) 수치가 아닙니다. 이 글은 배포된 내용과 스모크 단계에서 실제로 알게 된 것, 이 두 가지 측면을 모두 다룹니다.

왜 세 번째 경로가 필요한가

세 번째 경로를 도입해야 하는 근거는 제가 올해 초에 주장했던 모델별 재시도 제한 (per-model retry cap) 패턴의 근거와 동일합니다. 각 모델은 고유한 실패 형태와 고유한 비용 곡선을 가지고 있습니다. 전체 하네스 (harness)를 하나의 제공업체에 고정하는 것은 하나의 청구서, 하나의 속도 제한 (rate-limit) 정책, 그리고 하나의 "모델이 틀렸다"는 정의를 그대로 물려받는 것을 의미합니다.

Composer 2.5는 우리의 재시도 한도(retry caps)에서 유의미한 방식으로 비용 산식을 변화시킵니다. Codens는 각 모델별로 작업당 최대 한도까지 재시도를 수행합니다: 현재 기준으로 claude=3, qwen=6, composer-2.5=5입니다. Opus를 사용하며 한도가 3일 때, 최악의 경우 시도 비용(worst-case attempt cost)이 작업당 예산을 지배합니다. 반면, 시도당 비용이 약 1/10 수준이면서 정확도는 대등한 Composer 2.5를 사용하며 한도가 3일 경우, Opus보다 높은 첫 번째 시도 성공률(first-pass success)을 고려하기 전이라도 최악의 경우 시도 비용이 대략 한 자릿수(an order of magnitude)만큼 감소합니다. 이러한 계산 결과가 바로 통합에 시간을 들일 가치가 있게 만든 핵심입니다.

선택권(optionality)에 대한 논거 또한 최근 더욱 강력해졌습니다. Anthropic은 Agent SDK와 claude -p CLI 워크플로우가 에이전트 사용 사례(agent use cases)를 위한 구독 플랜에 포함되지 않는다는 점을 명확히 했으며, 이는 Codens가 이미 실행 중인 API 직접 경로(API-direct path)의 타당성을 입증합니다. 그 위에 Cursor 경로를 추가하는 것은 확장된 동일한 베팅입니다. 즉, 특정 벤더의 가격 책정이나 정책에 얽매이지 말고, 현재 작업 부하(workload)에 대해 비용과 신뢰성 측면에서 승리하는 경로로 작업을 라우팅할 수 있도록 하네스(harness)를 자유롭게 유지하는 것입니다.

Executor 경로 설계

설계 과정에서 즐거웠던 점은 PurpleTask.execute_model이 이미 작업별 모델 전환을 지원하고 있었고, PurpleProject.default_model을 통해 프로젝트 전체가 특정 모델을 고정할 수 있었다는 점입니다. 세 번째 경로를 추가하는 것은 아키텍처의 변경이 아니었습니다. 그것은 단지 열거형 값(enum value) 하나와 새로운 러너(runner) 모듈을 추가하는 것이었습니다.

class PurpleTask(Base):
    # 기존 필드 생략
    execute_model = Column(
...

러너 디스패처(runner dispatcher)에는 이미 두 개의 분기(branch)가 있었습니다: claude -p CLI를 래핑(wrap)하는 Anthropic API 경로를 위한 runner_claude.py와 자체 호스팅 엔드포인트를 위한 runner_qwen.py입니다. 세 번째 러너인 runner_cursor.py는 동일한 입력 규약(task spec, workspace dir, env)과 동일한 출력 규약(workspace diff, structured result, failure_reason on non-zero)을 가지고 이 두 경로 옆에 끼워 넣어집니다.

저는 의도적으로 변경 사항을 두 개의 커밋으로 나누었습니다. 1단계(Phase 1)는 모든 호출 시 0이 아닌 종료 코드(non-zero exit)로 종료되는 검증 전용 러너(runner)와 열거형(enum) 추가 작업이었습니다. 이는 단독으로 배포 가능하며, 아직 composer-2.5를 가리키는 곳이 없었기 때문에 기존 작업의 동작에는 아무런 변화를 주지 않았습니다. 2단계(Phase 2)는 실제 SDK 호출 작업이었습니다. 이렇게 나누는 것은 각 커밋을 개별적으로 되돌릴(revert) 수 있음을 의미하며, 열거형 마이그레이션(enum migration)이 SDK 동작 관련 문제와 결합되지 않도록 합니다.

저는 열거형 추가를 그것에 의존하는 런타임(runtime)과 함께 묶어서 처리할 경우, 런타임이 문제로 밝혀졌을 때 커밋을 깔끔하게 되돌릴 수 없다는 사실을 고통스러운 경험을 통해 배웠습니다. 1단계/2단계 분리는 저렴한 보험과 같습니다.

1단계: 골격 (the skeleton)

1단계 커밋 5a575031은 세 가지 작업만 수행했으며 그 외의 일은 하지 않았습니다. 모델 열거형(model enum)에 composer-2.5를 추가했고, 디스패치 테이블(dispatch table)에 runner_cursor.py를 등록했으며, 러너가 입력을 검증하고 명확한 "아직 구현되지 않음"이라는 failure_reason과 함께 0이 아닌 종료 코드로 종료되도록 만들었습니다. 마이그레이션은 스테이징(staging) 환경에서 실행되었습니다. 디스패치 테이블은 새로운 항목을 인식했습니다. 운영(production) 작업 중 새로운 경로를 가리키는 것이 없었으므로, 러너는 라이브 경로에서 호출되지 않았습니다.

이것은 아무것도 하지 않는 것처럼 보이지만 실제로는 가장 중요한 일을 하는 종류의 커밋입니다. 즉, 새로운 코드가 배관(plumbing) 시스템의 버그를 숨기기 전에 주변 배관이 올바른지 증명하는 것입니다. 만약 2단계가 한 번에 반영되었고 SDK 호출이 실패했다면, 저는 실패 원인이 디스패처(dispatcher)인지, 환경 배선(env wiring)인지, IAM 역할(IAM role)인지, 아니면 SDK인지 파악하기 위해 다음 한 시간을 허비했을 것입니다. 1단계가 이미 한 시간 동안 운영 환경에 배포되어 있었기에, 2단계가 망가뜨릴 수 있는 유일한 것은 SDK 호출 그 자체뿐이었습니다.

2단계: Cursor SDK 배선 (wiring the Cursor SDK)

2단계 커밋 b1e7ebcd는 실제 작업이 이루어진 단계입니다. Cursor Python SDK는 Bridge → Client → Agent → events 순으로 진행되는 세션(session)을 노출합니다. 러너에서의 형태는 다음과 같습니다:

bridge = await Bridge.launch(...)
client = Client(bridge=bridge)
agent = await client.agent.create(
...

local=LocalAgentOptions(cwd=workspace_dir) 부분이 중요합니다. Cursor 에이전트(agents)는 원격(remotely) 또는 로컬(locally)에서 실행될 수 있으며, Codens의 경우 워크스페이스(workspace)가 이미 알려진 경로로 Fargate 태스크(task) 내에 마운트되어 있습니다. 따라서 로컬 모드(local-mode)를 사용하면 파일 I/O(file IO)가 태스크 내부에서 유지되어, 차이점(diff)을 네트워크를 통해 주고받는 왕복 과정(round-tripping)을 피할 수 있습니다. agent.send는 실행 핸들(run handle)을 반환하며, 이 핸들의 events() 비동기 이터레이터(async iterator)는 우리가 Claude 경로에서 이미 소비하는 방법을 알고 있는 구조화된 이벤트 스트림(structured event stream)을 생성합니다. runner_cursor.py의 변환 계층(translation layer)은 Cursor의 이벤트 형태(event shapes)를 Purple의 나머지 부분이 이미 이해하고 있는 내부 이벤트 스키마(internal event schema)로 정규화(normalizes)합니다.

CURSOR_API_KEY는 명백한 차단 요소입니다. 우리는 이를 AWS Secrets Manager의 purple-codens-prod/cursor-api-key에 저장하고, 각 태스크의 환경 변수(environment)에 주입하여 SDK가 자동으로 이를 인식하도록 합니다. PR #1156(커밋 d1ef5db4656f42e4)의 ECS Fargate 태스크 정의(task definition) 변경 사항은 시크릿 ARN(secret ARN)을 환경 변수로 노출합니다:

{
  "name": "CURSOR_API_KEY_SECRET_ARN",
  "value": "arn:aws:secretsmanager:ap-northeast-1:...:secret:purple-codens-prod/cursor-api-key"
...

엔트리포인트(entrypoint) 스크립트는 러너(runner)를 실행하기 전에 이를 해결(resolves)합니다:

CURSOR_API_KEY=$(aws secretsmanager get-secret-value \
    --secret-id "$CURSOR_API_KEY_SECRET_ARN" \
    --query SecretString --output text)
...

이 부분은 제가 특별히 주의를 환기하고 싶은 버그를 도입한 지점입니다. 왜냐하면 이는 멀티 테넌트(multi-tenant) SaaS가 절대 출시해서는 안 되는 종류의 버그이기 때문입니다. 초기 커밋은 태스크 환경에서 활성화된 AWS_PROFILE이 무엇이든 그것을 사용하여 시크릿을 가져왔는데, 일부 코드 경로에서는 이것이 고객의 연결된 AWS 자격 증명(credentials)으로부터 상속되었습니다. 이는 멀티 테넌트 하네스(multi-tenant harness)에서 잘못된 방식입니다. 커밋 6210a052에서의 수정 사항은 엔트리포인트가 Secrets Manager 호출 시 고객의 프로필이 아닌 ECS 태스크 IAM 역할(IAM role)을 사용하도록 만듭니다. 고객 자격 증명은 고객의 리소스에만 범위가 제한됩니다. 우리의 Cursor API 키를 포함한 플랫폼 자격 증명은 반드시 태스크 역할(task role)을 통해 해결되어야 합니다. 쉬운 실수였지만, 중요한 수정입니다.

카나리 절차 (The canary procedure)

실제 프로젝트가 최소 하루 동안 해당 경로에서 실행되기 전까지는 프로덕션(production) 환경의 새로운 경로를 신뢰하지 않습니다. 카나리 절차 (The canary procedure, 커밋 d6fe3cb3)는 의도적으로 작게 설계되었습니다. 정확히 하나의 내부 Corevice-org 프로젝트에 대해서만 purple_projects.default_model = 'composer-2.5'로 설정을 변경하고, 이를 직접 사용(dogfood)하며 지표를 관찰하는 방식입니다. 다른 모든 프로젝트는 기존에 사용하던 모델을 그대로 유지하므로, 카나리는 완전히 격리됩니다.

SQL 문은 단 한 줄입니다:

UPDATE purple_projects
SET default_model = 'composer-2.5'
WHERE id = '<internal-project-id>';

롤백(Rollback)은 이전 값으로 설정하는 동일한 문장을 실행하는 것입니다. 별도의 코드 배포(code deploy)가 필요하지 않습니다. 모델 선택을 배포 아티팩트(deploy artifacts)에 고정하지 않고 런타임 데이터(runtime data)로 유지함으로써 얻는 장점 중 하나는, 롤백이 릴리스(release)가 아닌 트랜잭션(transaction)이라는 점입니다.

카나리 경로와 동일한 프로젝트의 지난 30일간 Opus 사용 데이터를 비교하는 축은 다음과 같습니다:

  • 완료율 (Completion rate, 재시도 횟수를 모두 소진하지 않고 작업이 완료됨)
  • 검증 통과율 (Verify pass rate, Codens 검증 단계가 최종 diff에 대해 성공함)
  • 작업당 실제 소요 시간 (Wall time per task)
  • 완료된 작업당 비용 (Cost per completed task)

카나리의 목적은 해당 경로가 좋다고 인증하는 것이 아닙니다. 목적은 실제 고객이 새로운 경로를 접하기 전에, 벤치마크(benchmarks)에서는 드러나지 않는 실패 모드(failure modes)를 표면화하는 것입니다.

스모크 테스트(smoke runs)가 실제로 보여준 것

v4부터 v17까지, 스모크 테스트(smoke pass)는 카나리 프로젝트에서 25회의 시도를 수행했습니다. 9회는 완료되었고, 16회는 실패했습니다. 이는 Opus를 사용했을 때 완료율이 80% 이상이었던 작업 부하(workload)와 비교했을 때 36%의 완료율에 불과합니다. 벤치마크 수치와 프로덕션 수치는 서로 다른 수치였습니다.

두 가지 실패 모드가 거의 모든 실패 사례를 차지했습니다.

몇몇 장시간 실행되는 작업(long-running tasks) 중에 Cursor SDK 브릿지(bridge)가 세션 중간에 끊기는 현상이 발생했습니다. 브릿지가 끊기면 진행 중이던 워크스페이스 차이(workspace diff)가 손실되고, 실행 핸들(run handle)에 오류가 발생하며, 러너(runner)는 일반적인 SDK 예외(exception)를 보고했습니다. 브릿지가 끊기는 시점에 부분적인 차이(partial diff)를 구조(salvage)하는 것이 명백한 해결책이었습니다. 커밋 0f95f020은 브릿지 끊김 예외를 포착하고, 워크스페이스 내 디스크에 현재 있는 내용을 스냅샷(snapshot)하여, 다음 시도가 처음부터 다시 시작하지 않도록 해당 차이를 재시도(retry) 컨텍스트에 전달합니다.

다른 실패 모드는 더 까다로웠습니다. 작업이 재시도 한도(retry cap)를 초과하면 러너는 failure_reason = "exceeded max executions (5)"라고 보고하고 끝났습니다. 반대편의 운영자(operator)는 그 다섯 번의 시도가 각각 왜 실패했는지 알 수 있는 가시성(visibility)이 전혀 없었습니다. 동일한 커밋(0f95f020)에서의 수정 사항은 failure_reason에 마지막 시도의 실제 오류 문자열을 추가하여 내용을 풍부하게 만듭니다. 이제 한도에 도달하면 운영자는 "exceeded max executions (5): last attempt failed with: <real error>"를 확인하고, 해당 작업을 다른 경로(lane)로 라우팅하거나 에스컬레이션(escalate)할 수 있습니다.

두 가지 작은 수정 사항도 함께 배포되었습니다. 커밋 1be0614f는 Secrets Manager 호출이 실패할 때 AWS CLI 오류를 드러냅니다. 이전에는 엔트리포인트(entrypoint)가 이를 조용히 삼켜버려 러너가 빈 CURSOR_API_KEY로 시작했고, 3초 후 SDK로부터 불투명한 401 오류를 생성했습니다. 이제 엔트리포인트는 러너가 시작되기도 전에 AWS CLI 오류와 함께 0이 아닌 종료 코드(non-zero exit)로 종료됩니다. 커밋 64af2b50은 작업별 환경 변수 주입(per-task env injection)을 정리하고, Cursor 이벤트 스키마와 내부 스키마 간의 message 필드 충돌을 해결했습니다. 이 충돌은 번역 과정에서 일부 이벤트의 페이로드(payload)를 유실시키는 원인이었습니다.

이러한 수정 사항 중 어느 것도 Composer 2.5를 우리 워크로드에 적합한 프로덕션급(production-grade) 경로로 만들어주지는 않습니다. 대신, 우리가 계속해서 반복 개선(iterate)하는 동안 내가 운영하고, 관찰하고, 추론할 수 있는 경로로 만들어줍니다. 카나리(canary)는 여전히 카나리로 남습니다. 고객 대상 프로젝트는 기존에 사용하던 경로를 유지합니다.

결론

멀티 레인 실행기 아키텍처 (Multi-lane executor architecture)는 헤지 (hedge)이며, 모든 헤지와 마찬가지로 그 가치는 실제로 그것이 필요할 때만 나타납니다. Composer 2.5는 향후 몇 주 안에 Codens의 기본 라우팅 레인 (default-routing lane)이 될 수도 있고 그렇지 않을 수도 있습니다. 10배의 비용 압축은 실제이며, 벤치마크 수치도 실제이고, 스모크 테스트 (smoke phase) 결과 또한 실제입니다. 카나리 (canary) 절차의 핵심은 고객이 체감하기 전에 이 세 가지 수치 중 어떤 것이 우리의 워크로드 (workload)에 중요한지를 알아낼 수 있다는 점입니다.

통합 비용은 Phase 1의 스켈레톤 (skeleton), Phase 2의 SDK 와이어 (wire), ECS 태스크 정의 (task definition) 변경, IAM 수정, 그리고 단 한 줄의 SQL 토글 (toggle) 수준이었습니다. Composer 2.5의 안착 여부와 상관없이, 통합의 가치는 가격 발표나 모델 출시가 비용 곡선을 재편할 때마다 하네스 (harness)가 통과할 수 있는 레인이 하나 더 늘어났다는 데 있습니다. 이러한 선택권 (optionality)이야말로 AI 개발 하네스 (AI dev harness)가 제공해야 하는 본질입니다.

자율 코드 수정 및 QA를 위한 멀티 레인 하네스 (multi-lane harness)가 실제 운영 환경에서 어떻게 작동하는지 보고 싶다면 https://www.codens.ai/en/에서 Codens를 확인해 보십시오.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0