Skip to content

Latest commit

 

History

History
517 lines (398 loc) · 32.5 KB

File metadata and controls

517 lines (398 loc) · 32.5 KB

Why APME — 감(感)에서 데이터로

apme.md무엇을 만들었나, apme-pipeline.md어떻게 동작하나. 이 문서는 왜 만들었나 — 현재 내가 풀고 있는 문제, 지금까지의 한계, 그리고 APME가 어떤 구조로 그 문제를 풀어나가는지에 대한 생각을 정리한다.


1. 문제: 6개 모델을 감으로 라우팅하는 일상

현재 내 작업 루틴에 들어와 있는 모델은 최소 6개다.

┌───────────────────────────────────────────────────────────┐
│                     내가 쓰는 모델 라인업                  │
├──────────────────┬────────────────────────────────────────┤
│ Claude Opus 4.6  │ 메인 드라이버. 비싸고 느리지만 정확    │
│ Claude Sonnet 4.6│ Opus 리밋 소진 시 대체                  │
│ Codex (GPT-5.4)  │ 세컨드 오피니언, 대안 구현             │
│ Gemini Antigrav  │ 대용량 context, research               │
│ GLM-5.1          │ 중간 난이도 작업                        │
│ Qwen3-1.7B MLX   │ 로컬, 비용 0, 요약/분류 fallback        │
└──────────────────┴────────────────────────────────────────┘

이걸 내가 어떻게 배분하고 있나?

  Opus 리밋 다 쓸 것 같다 (감)
    │
    ▼
  "요건 Codex 한테" (감)  ─────┐
  "저건 Gemini 한테" (감)      │
  "이건 GLM 한테" (감)          ├─── 🎲 감에 의한 라우팅
  "요약은 Qwen MLX" (감)       │
                               ┘

이게 효율적으로 돌아가는지 알 방법이 없다. 두 가지 구체적인 불편함이 있다:

불편함 1 — 역할 경계의 불확실성

  • "OpenClaw에 붙인 로컬 모델로 요약 돌리는 게 정말 괜찮은가?"
  • "debugging은 Opus한테 가야 하나? Codex가 더 빠르지 않나?"
  • "planning 단계는 Gemini가 맞나? 아니면 Opus의 reasoning이 필요한가?"

답이 없다. 경험 스냅샷(지난주에 Codex가 한 번 잘했다)은 있지만 누적 데이터가 없다.

불편함 2 — 비용 최적화의 근거 부족

  • Opus는 비싸다. 어디까지 안 써도 괜찮은지 모른다.
  • 로컬 MLX는 공짜지만 어느 수준까지 품질이 나오는지 모른다.
  • "비용 대비 품질"이라는 값을 한 번도 계산해본 적이 없다.

결과적으로 두려움 기반 과잉 지출이 생긴다. 잘못되면 안 되는 작업은 무조건 Opus로 돌리고, 나중에 "정말 Opus가 필요했나?"를 돌이켜볼 수 없다.


2. 해결 방향: 개인화된 평가 시스템

범용 벤치마크(HumanEval, SWE-bench 같은)는 답이 아니다. 세 가지 이유로:

  범용 벤치마크                    내가 필요한 것
  ─────────────                    ─────────────
  표준 태스크                  ≠   내 실제 작업
  평균적 사용자                ≠   내 스타일/선호
  "overall 점수"               ≠   "내 코드베이스에서 debugging 잘함?"

내 작업에 대한 개인화된 평가 시스템이 필요하다. 즉:

  1. 내가 실제로 시키는 태스크에서 평가 데이터 수집
  2. 카테고리별로 모델의 강점/약점 프로파일
  3. 내 기준(품질+속도+비용)에 맞춰 추천

이게 APME가 풀려는 문제다.


3. APME의 4단계 진화

APME는 "한 번에 완성된 자동 평가 시스템"이 아니다. 단계적으로 성숙하는 구조로 설계했다:

Stage 1 — 데이터 수집 (모든 세션 기록)
            │
            ▼
Stage 2 — 자동 분류 (태스크 카테고리 판별)
            │
            ▼
Stage 3 — 사람 레이블링 (대시보드 vibe check)
            │
            ▼
Stage 4 — LLM Judge 튜닝 (사람과 일치하는 자동 평가)

각 단계가 이전 단계의 데이터를 연료로 삼는다.

Stage 1 — 데이터 수집 (모든 세션을 기록한다)

모든 에이전트 세션이 끝날 때까지 아무것도 평가하지 않는다. 일단 기록만 한다.

수집 대상 출처
프롬프트 UserPromptSubmit hook / chat_start timeline
응답 PTY 마커 tail / chat_response / Stop hook
툴 호출 히스토리 PreToolUse/PostToolUse hooks
파일 변경 git diff (before/after)
토큰 사용량 / 비용 /usage 명령, Codex bash 로그
세션 시간 시작/종료 타임스탬프

3가지 서로 다른 에이전트(Claude Code / OpenClaw·OpenCode / Codex)를 하나의 스키마로 수렴시키는 게 관건이다. 각 에이전트의 이벤트 형태가 완전히 달라서 3개의 인제스트 경로를 만들고 ApmeCollector 공통 API로 수렴시켰다.

┌───────────────┬────────────────────────┬─────────────────────────────┐
│   에이전트     │      수집 경로          │         특이사항             │
├───────────────┼────────────────────────┼─────────────────────────────┤
│ Claude Code   │ hook HTTP POST         │ Stop 훅 발화율 ~18% (불안정)  │
│               │ /hook/:event           │ → PTY ⏺ tail 파싱이 1차 경로  │
│               │                        │ → pendingPtyResponse 3경로    │
│               │                        │   race 해결                   │
├───────────────┼────────────────────────┼─────────────────────────────┤
│ OpenClaw      │ adapter timeline       │ chat_start/response/end      │
│ OpenCode      │ events                 │ → collector 이벤트 매핑       │
│               │ (source:'timeline')    │                              │
├───────────────┼────────────────────────┼─────────────────────────────┤
│ Codex CLI     │ PTY parser             │ hook/timeline 없음           │
│               │ (spinner_stop + ⏺)     │ → PTY tail 파싱 유일 경로     │
└───────────────┴────────────────────────┴─────────────────────────────┘
                                │
                                ▼
                      ┌──────────────────┐
                      │  ApmeCollector   │   ingestHook()
                      │  (공통 API)       │   setTurnResponse()
                      └────────┬─────────┘   closeTurn() / closeRun()
                               ▼
                      ~/.agentdeck/apme.sqlite

Claude Code Stop 훅이 왜 안정성 문제인가? v2.1.104 기준 발화율이 11회 중 2회 수준. 그래서 PTY 링버퍼에서 마커를 찾아 응답 텍스트를 직접 뽑는 자체 파서가 1차 경로가 되었다. 훅이 오면 더 깨끗한 텍스트로 덮어쓰는 2차 보강만 한다.

이 단계의 핵심은 평가에 앞서 기록이 있다는 것. 평가 로직이 나중에 바뀌더라도 원본 데이터가 있으면 재평가가 가능하다.

Stage 2 — 자동 분류 + 카테고리별 평가 전략

수집된 run을 10가지 카테고리 중 하나로 분류한다:

  coding       refactoring    debugging     review
  planning     research       conversation  ops
  multi_agent  unknown

왜 분류가 먼저인가? 카테고리마다 "좋은 응답"의 정의도, 평가 방법도 다르기 때문이다. APME의 가장 중요한 아키텍처 결정 하나가 이 지점에 있다.

분류 방식 (저비용 2단)

  Rules (priority-ordered)
    │
    ├─ plan mode 사용? → planning
    ├─ ≤2 tools, <120s? → conversation
    ├─ web search + grep? → research
    ├─ tests + edits + bash? → debugging
    ├─ >50% Edit, 3+ files? → refactoring
    └─ ...
    │
    ▼
  unknown?
    │
    ▼
  Local MLX classifier (fallback, 공짜)

데몬이 30초마다 task_category IS NULL인 run을 재분류한다. 세션 프로세스가 일찍 kill되어 async classify가 중단된 경우를 이 루프가 복구한다.

🔑 카테고리별로 평가 전략이 다르다 — 핵심 결정

overall score 하나 같은 단일 평가는 의미가 없다. 카테고리에 따라 ground truth의 형태 자체가 다르기 때문이다.

┌─────────────────┬──────────────────────┬──────────────────────────────┐
│   카테고리       │   Ground Truth       │   평가 타이밍 + 루브릭        │
├─────────────────┼──────────────────────┼──────────────────────────────┤
│ coding          │                      │                              │
│ refactoring     │   git diff 존재      │ ▶ run 종료 후 (run-level)    │
│ debugging       │   + 커밋/테스트       │   + 결정론 레이어 (lint/     │
│                 │                      │     build/tests)             │
│                 │                      │   + llm_judge (카테고리별)    │
├─────────────────┼──────────────────────┼──────────────────────────────┤
│ conversation    │                      │                              │
│ planning        │   응답 텍스트 자체    │ ▶ 턴마다 즉시 (turn-level)   │
│ research        │   (git 무관)         │   + llm_judge only           │
│ review          │                      │   (결정론 레이어 없음)        │
├─────────────────┼──────────────────────┼──────────────────────────────┤
│ ops             │   Bash 결과          │ ▶ run-level, general rubric  │
│ multi_agent     │   에이전트 위임 성공  │ ▶ run-level, general rubric  │
│ unknown         │   fallback           │ ▶ run-level, general rubric  │
└─────────────────┴──────────────────────┴──────────────────────────────┘

왜 이렇게 나눴나?

  1. 코딩 3종 (coding/refactoring/debugging)

    • git diff가 있어야 "무엇을 했는지"를 명확히 평가할 수 있다 → run이 끝나야 outcome 판정
    • 결정론 레이어(lint/build/tests)가 실제로 값을 준다 — 통과/실패는 직접 측정 가능
    • LLM judge는 결정론 레이어 위에 품질을 덧대는 역할
  2. 비코딩 4종 (conversation/planning/research/review)

    • git diff는 의미 없다 (파일이 안 바뀜)
    • 응답 텍스트 자체가 결과물이므로 턴마다 평가 가능
    • 결정론 레이어는 사용 불가 (테스트할 게 없음)
    • turn-level mid-session eval — 세션이 끝나기를 기다릴 필요 없음. 턴 완료 직후에 즉시 judge 호출, evals 테이블에 layer='turn_judge' + turn_id로 저장
    • 대시보드에 실시간으로 턴 카드 + score + reasoning이 찍힌다
  3. 나머지 (ops/multi_agent/unknown)

    • 명확한 ground truth 형태가 없거나 혼합형이므로 general rubric + run-level로 fallback

카테고리별 루브릭 7종

각 카테고리는 고유한 **평가 축(axis)**을 가진 별도 루브릭을 사용한다. store.tsCATEGORY_RUBRICS가 이걸 DB에 시드한다:

┌──────────────┬──────────────────────────────────────────────────┐
│ general      │ task_completion · code_quality · efficiency       │
│ (coding)     │   (weights: 0.50 · 0.30 · 0.20)                   │
├──────────────┼──────────────────────────────────────────────────┤
│ conversation │ accuracy · helpfulness · conciseness              │
│              │   (0.50 · 0.30 · 0.20)                            │
├──────────────┼──────────────────────────────────────────────────┤
│ planning     │ completeness · feasibility · clarity              │
│              │   (0.40 · 0.35 · 0.25)                            │
├──────────────┼──────────────────────────────────────────────────┤
│ research     │ thoroughness · relevance · synthesis              │
│              │   (0.30 · 0.40 · 0.30)                            │
├──────────────┼──────────────────────────────────────────────────┤
│ debugging    │ diagnosis · fix_quality · verification            │
│              │   (0.35 · 0.40 · 0.25)                            │
├──────────────┼──────────────────────────────────────────────────┤
│ refactoring  │ safety · improvement · scope                      │
├──────────────┼──────────────────────────────────────────────────┤
│ review       │ coverage · insight · accuracy                     │
└──────────────┴──────────────────────────────────────────────────┘

이 구조의 함의: 같은 모델이 "debugging 잘함 + conversation은 장황함"일 수 있고, 그게 정상이다. 카테고리별 스코어카드가 모델 선택의 근거가 된다.

Stage 3 — Composite Score + 사람 레이블링

🔑 Composite Score — 4차원 가중합

단일 점수(judge 점수 하나)는 신뢰할 수 없지만, 여러 차원의 독립 신호를 합치면 훨씬 안정적이다. APME의 composite_score는 이렇게 계산된다:

composite = 0.40 × outcomeScore       ← "실제로 끝났는가"
          + 0.40 × judgeScore          ← "품질이 좋은가" (LLM judge)
          + 0.15 × efficiencyScore     ← "자원을 낭비하지 않았는가"
          + 0.05 × vibeScore           ← "사람이 승인했는가"

각 차원이 독립적이라서 한 쪽이 오류를 내도 다른 차원이 잡아준다. judge가 너무 후하게 줘도 outcome이 "abandoned"면 전체 점수는 낮아진다.

Outcome Taxonomy (코딩 vs 비코딩)

outcome은 카테고리별로 다르게 판정된다:

┌── 코딩 카테고리 (git diff 기반) ──┬─ score ─┐
│ committed      실제 커밋까지 감   │  1.00   │
│ ab_winner      A/B 테스트 승자    │  1.00   │
│ iterated       여러 번 시도 후    │  0.60   │
│ exploratory    변경 있으나 미커밋  │  0.50   │
│ pending        아직 진행 중       │  0.50   │
│ interrupted    사용자가 중단       │  0.30   │
│ abandoned      변경 없음          │  0.20   │
│ ab_loser       A/B 테스트 패자    │  0.10   │
└────────────────────────────────────┴─────────┘

┌── 비코딩 카테고리 ────────────────┬─ score ─┐
│ committed      응답 텍스트 캡처됨  │  1.00   │
│ (git 무관)                         │         │
└────────────────────────────────────┴─────────┘

비코딩은 "git commit 없이도 응답만 있으면 성공"으로 본다. Research나 planning에서 "어떤 파일도 안 바뀌었다"가 abandoned가 되면 안 되니까.

사람 레이블링 (Vibe Check)

이 단계가 현재 APME의 병목. LLM judge를 먼저 만들지 않고 사람이 먼저 레이블링하는 이유는?

  "LLM judge가 정답이다"  ❌
  "사람이 레이블링한 데이터가 ground truth"  ✓

Judge 모델의 점수는 그 자체로는 믿을 수 없다. 같은 응답에 대해 모델마다, 프롬프트마다, 온도마다 다른 점수를 낸다. 내 취향이 기준이 되어야 한다.

대시보드에서 run 하나하나를 훑으면서 👍 / 👎 / 중립으로 vibe feedback을 입력한다. vibe_feedback 테이블에 쌓이는 이 데이터가:

  1. 모델별 실제 성능 프로파일 — "debugging에서 Opus가 내 기준으로 80%, Codex가 70%"
  2. Composite score의 5% 가중치 — 직접 반영
  3. Stage 4의 ground truth — judge 튜닝의 학습 신호

Judge 백엔드 — "로컬 MLX가 기본"이라는 결정

┌────────────┬──────────────────────────────┬─────────┐
│ backend    │ 엔드포인트                    │ 역할     │
├────────────┼──────────────────────────────┼─────────┤
│ mlx        │ 127.0.0.1:8800               │ 기본     │
│ openclaw   │ 127.0.0.1:18789 (gateway)    │ 보조     │
└────────────┴──────────────────────────────┴─────────┘

Judge는 모든 run에 돌아간다. 클라우드 API를 쓰면 비용이 폭발하거나 사용자가 judge를 끄게 되고, 끄면 데이터가 축적 안 된다. 그래서 App Store Swift daemon 은 Apple Intelligence Foundation Models 를 기본으로 쓰고, CLI-only 환경은 Swift daemon proxy 가 없을 때 MLX Qwen3-1.7B-4bit fallback 또는 OpenClaw Gateway를 쓴다. 모두 로컬 경로라 전수 평가(sampleRate: 1.0)가 가능하다.

Stage 4 — LLM Judge 자동 튜닝

Stage 3 데이터가 충분히 쌓이면, 사람-judge 불일치를 learning signal로 사용할 수 있다:

 tests_pass=1 but judge.overall < 0.5       ← 결정론적 테스트는 통과했는데
                                              LLM judge는 나쁘다고 함

 tests_pass=0 but judge.overall > 0.8       ← 테스트 실패인데 judge는 좋다고 함

 vibe=rejected but judge > 0.8              ← 내가 거부했는데 judge는 고득점 줌

 vibe=approved but judge < 0.4              ← 내가 승인했는데 judge는 저득점 줌

이런 disagreement 샘플을 모아서 judge에게 "왜 이런 차이가 났는지 설명하고, 더 나은 루브릭을 제안하라"고 요청한다. 제안된 루브릭을 shadow mode로 기존 데이터에 적용해서 vibe와의 상관관계가 실제로 개선되는 경우에만 accept한다.

이게 OPRO 스타일 루프다. 결과는 시간이 지날수록 내 판단과 일치하는 judge가 된다.


4. 전체 그림 (실제 아키텍처)

┌─────────────────────────────────────────────────────────────────────┐
│                         WHY (지금 문제)                              │
│                                                                     │
│  6개 모델 · 감 기반 라우팅 · 비용 최적화 근거 없음 · 범용 벤치마크로  │
│   ──────────────────────────────── 해결 불가 (개인화 필요)           │
└─────────────────────────────┬───────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│                        HOW (APME 구조 — 8 레이어)                    │
│                                                                     │
│  [L1] 3-path ingestion                                              │
│      Claude hook / OC timeline / Codex PTY ──┐                      │
│                                               ▼                     │
│  [L2] ApmeCollector → SQLite                                         │
│      runs · turns · steps · artifacts · evals · vibe · rubrics      │
│                         │                                           │
│          ┌──────────────┼──────────────┐                            │
│          ▼              ▼              ▼                            │
│  [L3] Classifier   [L4] Runner    [L5] Tuner                        │
│   rules+MLX         category-      disagreement                     │
│   10 categories     aware eval     → new rubric                     │
│   ┌─────────────────┴─────────────┐                                 │
│   │                               │                                 │
│   ▼                               ▼                                 │
│  run-level (coding)         turn-level (non-coding)                 │
│   det + llm_judge            llm_judge only                         │
│   git-diff outcome           response-captured outcome              │
│                         │                                           │
│                         ▼                                           │
│  [L6] Recommender — scorecard → 모델 추천                            │
│                         │                                           │
│                         ▼                                           │
│  [L7] Daemon 30s loop + HTTP /apme/* + WS apme_eval broadcast       │
│                         │                                           │
│                         ▼                                           │
│  [L8] Device rendering — ★ eval_result timeline                     │
│      Stream Deck / Apple / Android / ESP32 / TUI                    │
└─────────────────────────────┬───────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│                       WHAT (최종 산출물)                              │
│                                                                     │
│  1. 카테고리별 모델 스코어카드 (v_category_scorecard)                 │
│     "debugging 에서 Opus 0.82, Codex 0.75, GLM 0.68"                │
│                                                                     │
│  2. Composite score — 4차원 가중합                                    │
│     0.40 outcome + 0.40 judge + 0.15 efficiency + 0.05 vibe        │
│                                                                     │
│  3. 비용 효율 지표 (cost_per_quality)                                │
│     "같은 품질이라면 Codex가 Opus 대비 40% 저렴"                      │
│                                                                     │
│  4. 자동 라우팅 추천 (recommender.ts)                                 │
│     "이 태스크는 GLM → 실패 시 Opus로 escalate"                       │
│                                                                     │
│  5. 내 취향에 맞춰 진화하는 LLM judge                                 │
│     "수동 레이블링 없이도 신뢰할 수 있는 자동 평가"                     │
└─────────────────────────────────────────────────────────────────────┘

데몬 루프가 자기 치유를 보장한다

평가는 세션 프로세스의 수명과 분리되어 있어야 한다. 세션이 kill되거나 크래시되면 진행 중이던 async 작업(classify, judge)이 날아가기 때문이다. 그래서 데몬이 30초마다 복구 루프를 돈다:

┌─── 30초 주기 데몬 루프 (daemon-server.ts) ───┐
│                                              │
│  1. 미평가 run 큐 enqueue                     │
│  2. 10초 이상 닫힌 run의 outcome 계산          │
│  3. task_category IS NULL 재분류              │
│  4. orphan run 태깅                          │
│                                              │
└──────────────────────────────────────────────┘

이 루프 덕분에 세션이 비정상 종료돼도 평가 데이터가 결국 채워진다.

디바이스 렌더링이 vibe 레이블링을 촉진한다

평가 결과가 대시보드 안에서만 보이면 사용자는 그걸 열람하지 않는다. 그래서 eval_result 를 timeline entry type으로 만들어 모든 디바이스로 broadcast한다:

apme_eval WS  ──▶  BridgeTimeline  ──▶  ★ timeline entry
                                          ├─ Stream Deck (amber ★)
                                          ├─ Apple (ledAmber EVAL)
                                          ├─ Android (LEDAmber)
                                          ├─ ESP32 (@ TLToolReq)
                                          └─ TUI dashboard

run이 끝나면 Stream Deck + iPhone + ESP32 LED가 동시에 ★ debugging 82%를 띄운다. 시선이 가니까 "👍/👎 누를까?"라는 생각이 든다. 데이터 수집 행동을 트리거하는 UX가 아키텍처의 일부다.


5. 지금 어디쯤 와 있나

단계 상태 비고
L1 3-path ingestion ✅ 완료 Claude(hook+PTY)/OpenClaw/OpenCode/Codex 모두 가동
L2 Collector + Store ✅ 완료 7개 테이블 + 3개 집계 뷰. rubrics.version PK 충돌 버그 2026-04-13 수정
L3 Classifier ✅ 완료 10 카테고리 rule + MLX fallback + 30초 재분류 루프
L4 Runner (coding run-level) ✅ 완료 결정론 레이어(lint/build/tests) + llm_judge 카테고리별 루브릭
L4 Runner (non-coding turn-level) ✅ 완료 턴 완료 직후 즉시 평가, layer='turn_judge'
L4 Judge backends ✅ 완료 MLX(default) / OpenClaw gateway (둘 다 로컬)
L5 Tuner 🧪 구조 완료, 가동 대기 tuner.ts 구현. vibe 데이터 30개 이상 쌓이면 첫 tune
L6 Recommender ✅ 완료 scorecard 기반 top-3 제안
L7 HTTP + WS broadcast ✅ 완료 /apme/* 8개 라우트 + apme_eval WS
L8 Device rendering ✅ 완료 Stream Deck / Apple / Android / ESP32 / TUI 전부 eval_result 렌더링
Vibe labeling 데이터 축적 🔧 병목 인프라는 완성. 내가 실제로 👍/👎 누르는 습관이 남았음

병목은 Stage 3다. 기술적으로 어려운 게 아니라 내가 실제로 레이블링을 해야 한다는 행동 습관의 문제다. 디바이스 전반(Stream Deck, Apple, Android, ESP32)에 ★ eval_result 렌더링을 붙인 것도 이 때문이다 — run이 끝나면 시선에 바로 들어와야 "👍/👎 누를까?"라는 생각이 든다.


6. 의도적으로 안 한 것

몇 가지는 일부러 안 했다. 그 이유도 기록해둘 가치가 있다:

6-1. Judge를 먼저 만들지 않음

LLM judge를 잘 만들어두면 사람이 레이블링할 필요 없지 않나? — 이 생각은 틀렸다. judge가 내 기준에 맞는지 검증할 방법이 없기 때문이다. 사람 데이터가 없으면 "judge가 잘못되고 있다"는 것조차 발견할 수 없다. 그래서 vibe 데이터가 먼저.

6-2. 범용 리더보드 배지 안 만듦

대시보드에 "이번 주 Top Model"같은 UI를 넣고 싶은 유혹이 있었지만 안 했다. 데이터가 30개도 안 쌓인 상태에서 랭킹을 보여주면 사용자(나)가 잘못된 결론을 내린다. 최소 표본 크기가 확보되기 전까지는 raw run 리스트만 보여준다.

6-3. Judge를 로컬로만 돌림

Judge는 로컬 backend 만 쓴다. App Store 앱은 Apple Intelligence, CLI-only fallback 은 MLX, 보조로 OpenClaw Gateway가 있지만 그것도 로컬이다. 이유:

  1. 비용 — judge는 모든 run에 돌아가야 한다. 클라우드 API면 즉시 비용 문제
  2. 신뢰 loop — 비용이 두려우면 judge를 끄게 되고, 끄면 데이터가 축적 안 된다
  3. 죄책감 없는 전수 평가sampleRate: 1.0이 기본. 로컬이니까 가능

성능 trade-off(로컬 < 프론티어 모델)는 Stage 4의 auto-tune이 채운다는 게 가설이다. 싼 judge + 튜닝 루프 > 비싼 judge + 정적 루브릭.

6-4. "코딩만" 평가하지 않음

APME 초기 설계는 git diff 기반 coding 평가가 중심이었다. 그런데 내가 실제로 모델을 가장 자주 쓰는 건 코딩이 아니라 다른 것이었다:

  • "이 에러 로그 설명해줘" (conversation)
  • "이 라이브러리 뭐 쓰는지 조사해줘" (research)
  • "이걸 어떻게 리팩토링할까" (planning)

이런 non-coding 카테고리가 평가 안 되면 APME는 내 실제 사용 패턴의 절반도 못 담는다. 그래서 category rubrics와 turn-level mid-session eval을 만든 것이다 (코딩은 run 종료 후, 나머지는 턴마다).


7. 성공의 정의

APME가 성공했다고 말할 수 있는 조건은 무엇일까?

6개월 후 어느 날, 새 태스크를 시작할 때:

  "이건 debugging이니까 Codex"  ← 감이 아니라
  "지난 50개 debugging run에서 Codex가 Opus 대비 품질 95%,
   비용 40% — 이 태스크도 Codex로 가자"  ← 데이터 기반

혹은:

  "이 작업은 중요하니까 Opus"  ← 감이 아니라
  "이 카테고리는 내 vibe 기준으로 모델 간 갭이 크다
   → Opus, 반대로 카테고리는 갭이 작음 → MLX"  ← 데이터 기반

이렇게 판단의 근거가 SQLite 안에 있게 되는 것이 목적이다.


관련 문서