Local Coding Agent가 일상적인 태스크를 얼마나 수행할 수 있는지 검증해 보았다
요약
최근 성능이 비약적으로 향상된 Qwen3.6-27B와 같은 Open Weight 모델을 활용하여, MacBook 환경에서 Local Coding Agent의 실질적인 업무 수행 능력을 검증했습니다. OpenCode 환경을 통해 로컬 리포지토리 내 UI 편집 및 코드 수정 등 다양한 태스크를 수행하며 클라우드 모델 대비 실용성을 확인했습니다.
핵심 포인트
- Qwen3.6-27B는 4bit 양자화 시 MacBook에서 구동 가능한 수준의 크기이면서도 높은 SWE-bench 성능을 보유함
- OpenCode와 같은 도구를 사용하면 OpenAI 호환 엔드포인트를 통해 Local LLM을 코딩 에이전트로 쉽게 활용 가능함
- 단순 벤치마크 점수를 넘어 실제 로컬 리포지토리 환경에서의 UI 편집 및 코드 수정 능력을 검증함
- MacBook Pro M3 Max 64GB 환경에서 MLX를 활용한 로컬 추론 및 에이전트 구동이 가능함
안녕하세요
AI 팀의 Toda입니다
최근 Local LLM의 진화는 상당히 눈길을 끕니다.
프론티어 모델 (Frontier Model)을 보면, 2026년 4월에 출시된 Claude Opus 4.7은 SWE-bench Verified에서 **87.6%**라는 수준에 도달했습니다.
GPT-5 계열이나 Gemini 3.1 Pro도 같은 대역에서 나란히 하고 있습니다. 반면, 이러한 모델들은 모두 클라우드를 통해서만 사용할 수 있으며, 로컬에서 구동하기에는 현실적이지 않은 크기입니다.
그리고 같은 4월, Qwen이 Qwen3.6-27B라는 Open Weight 모델을 공개했습니다. SWE-bench Verified에서 **77.2%**를 기록했으며, Qwen 공식 비교표에서는 Claude 4.5 Opus (80.9%)와 3.7pt 차이가 납니다.
주목할 점은 4bit 양자화 (Quantization)를 하면 약 24GB에 수렴하기 때문에, 64GB의 MacBook이라면 평범하게 구동할 수 있는 크기라는 것입니다. Hacker News에서는 M5 Pro 128GB에서 실제로 구동해 보고 "20GB밖에 사용하지 않았다, 32GB 기기에서도 문제없을 것 같다"라는 보고도 있습니다.
그리고 harness 측, 즉 LLM에게 "파일을 읽기 / 편집하기 / 명령어를 실행하기"와 같은 도구를 사용하게 하는 실행 환경 (OpenCode, Qwen Code, Claude Code, Aider 등)도 최근 반년 사이에 크게 진화했습니다.
여기서 궁금한 점은, "모델의 수치는 SWE-bench에서 충분히 보았지만, 실제로 MacBook 위에서 구동했을 때 어느 정도 실용적인 업무를 수행할 수 있는가?" 하는 것입니다. SWE-bench Verified는 좋은 벤치마크이지만, 자신의 로컬 리포지토리 (Repository)에서 어떻게 작동하는지에 대한 실감은 별개의 문제입니다.
그래서 검증을 위해 현재 개발 중인 프로덕트와 유사한 형태의 더미 리포지토리를 작성하여, 성격이 다른 6가지 태스크를 Qwen3.6-27B + OpenCode에 던져 보았습니다. OpenCode를 선택한 이유는 OpenAI 호환 엔드포인트 (Endpoint)를 지정하기만 하면 Local Model에서도 그대로 사용할 수 있다는 구현의 용이성 때문입니다.
본 기사는 그 검증 결과를 공유합니다.
검증 설정
실행 환경
검증은 다음 환경에서 실시했습니다.
| 항목 | 내용 |
|---|---|
| 머신 | MacBook Pro M3 Max 64GB |
| ... | OpenCode (provider를 OpenAI 호환 엔드포인트로 mlx-lm에 연결하는 구성) |
| 대상 리포지토리 | TypeScript + React + Hono를 중심으로 한, 자사 프로덕트 상당 수준의 더미 리포지토리 |
기동 커맨드는 대체로 다음과 같은 형태입니다.
# 추론 서버의 기동
mlx_lm.server \
--model unsloth/Qwen3.6-27B-UD-MLX-4bit \
...
태스크
태스크는 다음 6가지입니다. 각 태스크에는 "처음부터 실패하는 테스트"를 심어 두었으며, Agent가 이를 통과하면 Pass, 통과하지 못하면 Fail입니다.
| No | 태스크명 | 요구되는 능력 |
|---|---|---|
| 1 | 헤더 정렬 | 국소 UI 편집 |
| ... |
또한, 이하의 각 태스크에서 접기 안에 나타내는 코드는 제가 사전에 준비한 예상 구현 예시이며, LLM이 실제로 생성한 코드가 아닙니다. Agent의 출력은 이것과 일치할 필요는 없으며, 심어 놓은 테스트를 통과하면 Pass로 간주합니다. LLM의 실제 출력에서 신경 쓰였던 동작에 대해서는 「결과」 섹션에서 다루겠습니다.
각각 간단히 보충하겠습니다.
1. 헤더 정렬
retrospect 상세 화면의 Edit/Delete 버튼이 별도의 wrapper로 나뉘어 있으므로, 동일한 data-testid="header-actions" 그룹에 넣는다.
예상 구현 예시
return (
<header>
<h1>Retrospect detail</h1>
...
2. 쿼리 정규화
?status=TODO,DONE 과 ?status=TODO&status=DONE 을 동일한 배열로 정규화. empty segment, unknown status, dedupe까지 포함.
예상 구현 예시
export const statusSchema = z.enum(["TODO", "DOING", "DONE"]);
+const validStatuses = statusSchema.options;
+
...
3. URL 자동 판별 (URL 自動判定)
Zoom / Google Meet / Microsoft Teams의 URL을 보고 회의 플랫폼(meeting platform)을 자동으로 선택합니다.
예상 구현 예시
export type MeetingPlatform = "zoom" | "google_meet" | "microsoft_teams" | "unknown";
-export function detectMeetingPlatform(_url: string): MeetingPlatform {
+export function detectMeetingPlatform(url: string): MeetingPlatform {
...
return {
onlineUrl: input.onlineUrl,
- platform: input.platform ?? "unknown",
...
4. 상한 설정 (上限設定)
tenant + department 단위로 topic 수를 특정 건수로 제한하고, 초과할 경우 409 Conflict를 반환합니다.
예상 구현 예시
-export function canCreateTopic(_topics: Topic[], _scope: { tenantId: TenantId; departmentId: DepartmentId }): TopicLimitResult {
+export function canCreateTopic(topics: Topic[], scope: { tenantId: TenantId; departmentId: DepartmentId }): TopicLimitResult {
+ const count = topics.filter(
...
- canCreateTopic(input.existingTopics, {
+ const result = canCreateTopic(input.existingTopics, {
tenantId: input.tenantId,
...
5. 필터링 (フィルタリング)
includeOtherDepartments=false일 때, DB 쿼리 시점에서 타 부서를 제외합니다.
예상 구현 예시
export function listRetrospects(repo: RetrospectRepository, query: RetrospectQuery): Retrospect[] {
- const rows = repo.findMany({
+ const baseQuery = {
...
get:
{
- parameters: ["page", "pageSize"],
+ parameters: ["page", "pageSize", "includeOtherDepartments"],
...
export type RetrospectListParams = {
page: number;
pageSize: number;
...
export type RetrospectListSearch = {
page: number;
pageSize: number;
...
6. CRUD
CRUD와 settings UI를 새로 구현합니다.
예상 구현 예시
export type Template = {
id: string;
tenantId: string;
...
return (
<main>
- <h1>agent</h1>
...
결과
| No | 태스크명 | 결과 | 시간 |
|---|---|---|---|
| 1 | 헤더 정렬 | Pass | 6m59s |
| ... | |||
| 시간은 걸렸지만 모두 Pass했으며, 구현의 질은 대체로 적절했습니다. 아래에서 로그를 보고 신경 쓰인 부분을 몇 가지 소개합니다. |
2. 쿼리 정규화
처음에는 z.enum (Zod의 열거형 (enum) schema) 방식으로 유도하려다 실패했지만, 도중에 "이것은 전처리 (preprocessing) 단계에서 flatten / validate / dedupe를 직접 작성하는 것이 더 직관적이다"라고 방침을 전환하여 테스트를 통과시켰습니다. 스스로 수정(self-correction)에 성공한 사례라고 할 수 있지 않을까요.
5. 필터링
pnpm openapi와 pnpm gen:api-client를 실제로 실행하는 단계까지 완료했습니다. 중간에 한 번 departmentId: undefined를 항상 쿼리 객체 (query object)에 포함하도록 구현했으나, 테스트 기대값과의 차이를 Agent 스스로가 인지하고 객체 형태 (object shape)를 분기하는 방식으로 수정했습니다. Pass는 했지만 처리 시간은 약 35분이 소요되었으며, 여러 레이어 (layer)에 걸친 태스크는 정확도보다 시간이 더 큰 걸림돌이 될 것으로 보였습니다.
Claude Code + Sonnet 4.6으로 동일한 실험
여기서 대조 실험을 위해 Claude Code로도 동일한 환경에서 실험을 진행해 보았습니다. 대략적인 API 이용량도 함께 기재합니다.
| No | 태스크명 | 결과 | 시간 | 비용 |
|---|---|---|---|---|
| 1 | 헤더 정렬 | Pass | 22s | $0.13 |
| ... |
6개 태스크를 묶어 첫 번째 실험인 OpenCode + Qwen3.6-27B와 비교하면 다음과 같습니다.
| OpenCode + Qwen3.6-27B | Claude Code + Sonnet 4.6 |
|---|---|
| API 비용 | $0.00 |
| ... |
약 20배 더 빠르며, 비용은 $1.39라는 결과가 나왔습니다.
요약
Qwen3.6-27B + MLX + OpenCode는 수중에 있는 MacBook에서 "작동"할 뿐만 아니라, 성격이 다른 6개의 태스크를 일단 통과시킬 수 있는 수준이었습니다.
반면 처리 시간은 Claude Code보다 20배 느리다고 볼 수 있으므로, 다른 작업을 하면서 30~60분 단위로 기다릴 수 있는 소-중규모 태스크라면 맡길 수 있는 수준이라는 인상을 받았습니다. 데이터를 외부로 유출하지 않고 무료로 구동할 수 있다는 점도 환경에 따라서는 장점이 될 수 있을 것입니다.
개인적인 소감으로는, 실무에서 사용하기에는 아직 실행 속도 측면에서 어려움이 있을 것 같다고 느꼈습니다. 이 부분 또한 베이스 모델 (base model)의 경량화와 하드웨어의 진화로 해결될 수 있을까요? 계속해서 지켜보고 싶습니다.
끝까지 읽어주셔서 감사합니다!
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기