토큰은 부족한데, 슬롯(枠)은 남는다
평소 개발 작업은 대부분 Claude Code의 구독 서비스로 진행하고 있다. 현재의 Claude Fable 5.1과 Opus 5.5는 매우 잘 작동하여 SOTA라고 해도 무방할 정도이다. 유일한 문제는 토큰이 금방 부족해진다는 점이다.
반면, 손에는 다른 모델들의 슬롯(枠)이 남아 있다. 예전에 Xiaomi의 MiMo 무료 슬롯을 얻기도 했고, 다른 중국 모델들도 가끔 저렴하게 사용할 기회가 있다. 친구가 사용하지 않게 된 GLM의 구독 서비스도 있다. 그래서 단순하고 할 일이 거의 정해져 있는 작업은 이 모델들에게 맡기고, Claude에게는 정말 Claude만이 필요한 부분만 처리하도록 할 수 없을까 고민하기 시작했다.
첫 번째 시도: TUI를 통해 다른 harness 조작하기
가장 먼저 시도한 것은 herdr였다. Claude Code에서 herdr의 API를 호출하여 다른 페인(pane)에서 opencode 같은 harness를 실행하고 지시를 보내고, 화면을 읽어 결과나 진행 상황을 확인할 수 있다.
작동은 했지만 어딘가 부자연스러웠다. 게다가 더 나쁜 점은 Claude Code가 다른 harness의 권한 확인을 승인해 버릴 수 있다는 것, 그리고 두 개의 harness가 서로를 승인하며 권한 체크 자체를 회피할 수 있다는 것을 깨달았다. 애초에 herdr 자체가 Claude Code의 권한 메커니즘이나 Codex의 샌드박스(sandbox) 완전히 외부에 존재한다. 이는 위험하다고 느꼈다.
두 번째 시도: skill 뒤에 숨긴 harness
이전에 paimon이라는 직접 만든 harness도 작성했었다. 여기에 내장된 skill을 추가하여 Claude Code나 Codex에서 직접 호출할 수 있도록 했다. 사용법은 claude -p와 비슷하지만, 몇 가지 기능을 더했다. 호출하는 쪽에서는 paimon의 세션 기록을 범위 지정으로 문의할 수 있다. 또한, paimon이 작업을 마치고 프로세스가 종료된 후에도 다시 시작하여 같은 세션의 계속부터 작업시킬 수 있다.
원리적으로는 claude -p보다 우수해야 하지만, 실제 사용감은 역시 좋지 않았다. Claude가 항상 paimon에게 일을 맡겨주는 것은 아니었다. 최종 결과뿐만 아니라 더 많은 문맥을 알고 싶을 때, paimon의 세션 기록을 추적하는 데 여러 단계가 필요했고, 그 과정 자체가 토큰을 소모했다. 단순한 작업에서는 아낀 만큼보다 더 많은 토큰을 사용했을 것이라고 생각한다.
직관적으로 볼 때, 진짜 문제는 더 깊은 곳에 있었다. skill에 힌트를 적어 놓아도 Claude Code는 paimon을 자신의 서브 에이전트 중 하나로 취급해주지 않았다.
착상
그래서 떠올린 것이, 이런 저가 모델들 대부분이 Anthropic의 API와 호환된다는 점이었다. 예전에 환경 변수를 몇 개 설정하는 것만으로도 Claude Code에서 사용해 본 적이 있었다.
그렇다면 로컬에 프록시(proxy)를 세우면 되지 않을까? 프록시를 실행하고, Claude Code를 그쪽을 향하도록 띄운 다음, 고유한 모델 이름을 가진 커스텀 서브 에이전트들을 몇 개 등록한다. LLM으로의 모든 요청은 프록시를 거치게 된다. Fable이나 Opus로 가는 요청은 그대로 Anthropic에 전달한다. 커스텀 서브 에이전트, 예를 들어 저렴한 explore 에이전트로부터의 요청은 MiMo나 Kimi, GLM의 Anthropic 호환 엔드포인트로 보낸다. Claude Code 입장에서 보면, 저가 모델들이 내장된 서브 에이전트와 동등한 존재가 된다.
조금 조사해 보니 상황은 생각했던 것보다 좋았다. Claude Code는 기본 URL 환경 변수에 더하여 커맨드라인 인수로 서브 에이전트를 정의할 수 있으며, 각각에 용도 설명, 권한, 사용 가능한 도구를 지정할 수 있다. 즉, Claude Code의 설정에는 전혀 손댈 필요가 없다. 래퍼(wrapper)가 프록시를 실행하고 환경 변수와 추가 인수를 붙여 Claude Code를 실행하기만 하면 된다. 순수한 claude를 실행하면 이전과 완전히 똑같이 작동한다.
기존에도 해결책이 있다. 예를 들어 Claude Code Router 같은 것이 있다. 하지만 내가 필요로 하는 것보다 기능이 너무 많고, 상당히 복잡하기도 하다. 내 경우에는 훨씬 단순해야 하고, 이 모델들은 애초에 Anthropic 형식의 프로토콜을 지원하기 때문에 프로토콜 변환이 필요 없다. 그래서 직접 만들기로 했다.
cameo
기대했던 것보다 쉬웠다. 트윗 4~5개 분량의 프롬프트 하나로 Claude Code와 Opus 5.5가 한 번에 결과물을 완성해 주었다.
완성된 것이 cameo라는, 약 400줄짜리 작은 Go 프로그램이다. 이름은 영화 용어인 'cameo'에서 따왔는데, 짧고 특별한 출연을 의미한다. 이들 외부 모델이 여기서 하는 역할이 바로 그것이다. Claude Code 내부에 서브 에이전트로 한두 장면만 등장하고, 주역은 어디까지나 Claude가 맡는다.
cameo는 TOML 파일을 읽어와 localhost에서 HTTP 프록시를 시작하며, 설정된 서브 에이전트를 커맨드라인 인수로 변환하여 Claude Code를 실행한다. 프록시는 각 요청의 model 필드만 본다. 그 모델이 cameo의 서브 에이전트인 경우, 제공자 측의 모델명과 API 키로 교체하여 해당 곳으로 전달한다. 그 외는 모두 손대지 않고 Anthropic으로 보낸다. 메시지의 다른 부분을 이해할 필요가 없기 때문에 코드가 이렇게 작게 끝날 수 있었다.
아래는 프로젝트에 포함된 설정 예시이다.
[agents.cheap-general]
description = """
General purpose agent for multi-step tasks: researching questions, searching \
...
각 [agents.<name>] 섹션이 Claude Code의 서브 에이전트가 된다. description에는 해당 에이전트를 언제 사용해야 하는지, 다른 에이전트와 비교하여 어느 정도 우선순위를 두어야 하는지를 작성한다. 위 두 개는 내장된 general-purpose와 Explore에 일대일로 대응하지만, 더 저렴한 선택지임을 명시해 놓았다. 둘 다 남기고 싶지 않다면, 에이전트에 Explore나 general-purpose라는 이름을 붙여서 내장된 것을 덮어쓸 수 있다.
그 외에는 claude를 실행하던 곳에서 cameo를 실행하기만 하면 된다. 인수는 모두 그대로 claude에 전달된다.
실제로 사용해 보니, Claude Code는 paimon을 호출할 때보다 훨씬 자주 저렴한 쪽의 에이전트를 선택해 준다. 특히 명시적으로 지정했을 때는 더욱 그렇다.
Codex는 어떻게 할까
다음으로는 Codex에 대응하고 싶었다. 이 저가 모델들은 OpenAI 호환 엔드포인트도 제공하기 때문이다. 하지만 조금 조사해 보니, Codex는 이미 Responses API로 전면 전환했으며, 이들 모델의 대부분은 오래된 Chat Completions API에만 대응하는 것을 알게 되었다. Codex에 대응하려면 프로토콜 변환을 직접 작성해야 하는데, 그러면 Claude Code Router를 재구현하는 것과 다름없다. 그래서 포기하기로 했다. 적어도, 이 모델들이 Responses API를 지원할 때까지는 말이다.
논의