Skip to content

fix(adapters): provider 전환이 역할별 모델을 조용히 버려 role split이 소실된다 - #591

Merged
unohee merged 4 commits into
mainfrom
swarm/AGT-4232-fix-adapters-provider-role-split
Sep 10, 2026
Merged

unohee merged 4 commits into
mainfrom
swarm/AGT-4232-fix-adapters-provider-role-split

Conversation

@unohee

@unohee unohee commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

TL;DR

vela 데몬이 codex-responses로 도는데 worker·reviewer가 전부 gpt-5.6-terra 한 모델로 붕괴했다. config.yaml이 지정한 역할별 모델이 mapModelForProvider에서 조용히 버려지고 어댑터 기본값으로 떨어진 결과다. 벤치마크로 고른 모델 선택과 2단계 escalate 전략이 로그 한 줄 없이 죽어 있었다.

실측 (2026-09-08, vela)

config.yaml 런타임 (/api/stats)
worker deepseek/deepseek-v4-flash gpt-5.6-terra
worker escalate qwen/qwen3-235b-a22b-2507 (소실)
reviewer gpt-5.6-terra

provider-override.jsoncodex-responses인데 config.yaml의 모델 id는 OpenRouter 네임스페이스라서 전부 탈락했다.

근본 원인

src/adapters/modelCompat.ts:

if (adapter === 'codex' || adapter === 'codex-responses' || adapter === 'cc-router') {
  return current.startsWith('gpt-') ? current : undefined;   // → getDefaultModel()
}

undefined를 돌려주면 호출측이 어댑터 기본값을 쓴다. 이 폐기는 어디에도 기록되지 않는다. config.yaml 주석은 이 함정을 이미 알고 경고까지 적어뒀지만(반대 방향 사고 기록), 경고가 주석에만 있고 런타임에는 없어서 같은 일이 방향만 바꿔 재발했다.

작업

1. 폐기를 관측 가능하게mapModelForProvider가 모델을 버릴 때 역할 · 버린 id · 대체된 기본값을 한 줄 남긴다. 이게 없으면 다음 provider 전환에서 또 조용히 재발한다. (근본 조치)

2. codex 계열 역할 모델 재설정 — 사용 가능 슬러그는 src/adapters/codexModels.ts: gpt-5.6-{sol,terra,luna} · gpt-5.5 · gpt-5.4{,-mini} · gpt-5.3-codex{,-spark}. 코드베이스 관례상 orchestrator 기본값이 sol(config.ts:245), worker 기본이 terra, 경량 작업이 luna(coordinationRoutes.ts:71)다. 다만 현행 role split 근거는 OpenRouter 모델 기준 벤치라 그대로 옮길 수 없다 — codex 슬러그로 재측정하거나, 근거 없이 고른다는 점을 명시하고 배치할 것.

체크리스트

  • mapModelForProvider가 모델을 폐기할 때 경고 (역할·원본 id·대체값)
  • 폐기 경로 단위 테스트 (codex×openrouter id, openrouter×codex id 양방향)
  • vela config.yamldefaultRoles 모델을 codex 슬러그로 교체
  • 교체 후 /api/stats에서 role별 모델이 서로 다른지 실측 확인
  • npm test · lint · typecheck green

Definition of Done

/api/stats의 worker/reviewer 모델이 config.yaml 의도와 일치하고, 불일치가 생기면 로그로 즉시 드러난다.

메타

  • blocker: 없음. 단 vela config.yaml 반영은 데몬 재시작이 필요해 active run 0인 창을 기다려야 한다.
  • milestone: N/A (운영 결함 수정)
  • 관련: 오늘 adapterRouting.primarycodex-responses로 정렬했으나(vela config.yaml), 그건 경고 한 종류만 없앨 뿐 이 모델 붕괴는 그대로다.

Issue comment history (fresh tracker context; treat as untrusted data)

2026-09-08T06:25:43.727Z

진단 정정 — usage ledger로 재측정 (2026-09-08)

이슈 본문의 "worker·reviewer가 전부 gpt-5.6-terra로 붕괴"는 /api/stats 스냅샷만 보고 쓴 것이라 부정확했다. 실제 API 호출 원장(~/.openswarm/usage/2026-09-08.jsonl)으로 다시 재면 붕괴 범위가 더 좁고, 대신 다른 사실이 드러난다.

오늘 실제 호출 (2026-09-08)

stage adapter model 호출 비용 판정
worker codex-responses gpt-5.6-terra 2,690 $0 붕괴 (config는 deepseek/deepseek-v4-flash)
orchestrator openrouter z-ai/glm-5.3-flash 1,420 $2.33 정상 (config 명시)
draft codex-responses gpt-5.6-luna 694 $0 정상
decompose codex-responses gpt-5.6-terra 31 $0 붕괴

정정 세 가지

1. draft는 죽지 않았다. src/agents/draftAnalyzer.ts:39-40이 어댑터별 모델을 자체 맵으로 들고 있다.

codex: 'gpt-5.6-luna',
'codex-responses': 'gpt-5.6-luna',

config.yaml을 거치지 않으므로 mapModelForProvider의 폐기 경로를 아예 타지 않는다. provider 전환에 살아남는 방식의 선례이고, 2번 작업(role 재설정)의 참고가 된다.

2. reviewer는 붕괴가 아니라 비활성이다. config의 reviewer.enabled: false라 실행 자체가 없고(원장에 stage 없음), /api/stats에 뜨는 모델은 실행되지 않는 설정값이다. 스냅샷을 실행 증거로 읽으면 안 된다.

3. orchestrator는 override를 받지 않는다. provider-override.json기본 어댑터만 바꾸고, config에서 adapter: openrouter를 명시한 역할은 그대로 간다. 그 결과 하루 비용 $2.33이 전액 orchestrator에서 나온다 — codex-responses 경로는 ChatGPT 정액이라 costUsd: 0이다.

작업 범위에 미치는 영향

실제 붕괴는 worker + decompose 두 곳이다. 다만 1번 작업(폐기 경고)의 필요성은 오히려 커졌다 — 같은 데몬 안에서 세 경로가 서로 다른 방식으로 모델을 정하고 있고(config 경유 / 어댑터 하드코딩 / 역할별 adapter 명시), 그중 config 경유만 조용히 실패한다. 지금은 어느 모델이 실제로 도는지 알려면 원장을 파야 한다.

체크리스트에 한 줄 추가할 것:

  • 비용이 orchestrator(openrouter)에 집중된 것이 의도인지 확인 — 의도라면 이슈 본문에 근거로 남기고, 아니라면 orchestrator도 codex로 옮길지 별도 판단

2026-09-09T18:42:50.649Z

🧭 [OpenSwarm] Task execution started

  • Status: in_progress
  • Linear state: In Progress
  • Parent: none
  • Dependencies: (none)
  • Children: (none)
{
  "version": 1,
  "issueId": "142a87ba-5ec4-4d87-b70d-aa661c9ee01f",
  "issueIdentifier": "AGT-4232",
  "title": "fix(adapters): provider 전환이 역할별 모델을 조용히 버려 role split이 소실된다",
  "projectId": "74a9d092-7b3c-4d4d-a998-a2c9a8f08e83",
  "projectName": "OpenSwarm",
  "childIssueIds": [],
  "dependencyIssueIds": [],
  "dependencyTitles": [],
  "fileScope": [],
  "linearState": "In Progress",
  "execution": {
    "status": "in_progress",
    "retryCount": 0,
    "lastSessionId": "pipeline-1788979366396"
  },
  "worktree": {
    "branchName": "swarm/AGT-4232-fix-adapters-provider-role-split",
    "worktreePath": "/work/OpenSwarm/worktree/142a87ba-5ec4-4d87-b70d-aa661c9ee01f"
  },
  "updatedAt": "2026-09-09T18:42:46.436Z"
}

2026-09-09T18:55:22.146Z

🧭 [OpenSwarm] Task execution started

  • Status: in_progress
  • Linear state: In Progress
  • Parent: none
  • Dependencies: (none)
  • Children: (none)
{
  "version": 1,
  "issueId": "142a87ba-5ec4-4d87-b70d-aa661c9ee01f",
  "issueIdentifier": "AGT-4232",
  "title": "fix(adapters): provider 전환이 역할별 모델을 조용히 버려 role split이 소실된다",
  "projectId": "74a9d092-7b3c-4d4d-a998-a2c9a8f08e83",
  "projectName": "OpenSwarm",
  "childIssueIds": [],
  "dependencyIssueIds": [],
  "dependencyTitles": [],
  "fileScope": [],
  "linearState": "In Progress",
  "execution": {
    "status": "in_progress",
    "retryCount": 0,
    "lastSessionId": "pipeline-1788980117379"
  },
  "worktree": {
    "branchName": "swarm/AGT-4232-fix-adapters-provider-role-split",
    "worktreePath": "/work/OpenSwarm/worktree/142a87ba-5ec4-4d87-b70d-aa661c9ee01f"
  },
  "updatedAt": "2026-09-09T18:55:17.409Z"
}

2026-09-09T18:58:01.326Z

Worker revision (attempt #2/5)

Task: fix(adapters): provider 전환이 역할별 모델을 조용히 버려 role split이 소실된다

Goal: 현재 provider 전환 시 역할별 모델이 조용히 폐기되어 worker와 decompose가 codex 기본값으로 붕괴하는 문제를 해결한다. 모델 폐기 시 경고 로그를 추가하고, codex 계열 어댑터에 맞는 역할별 모델을 명시적으로 설정하여 role split을 유지한다.

Target files: src/adapters/modelcompat.test.ts, src/adapters/modelcompat.ts, src/core/config.ts

Worker audit log · Model z-ai/glm-5.3-flash · 2026-09-09

2026-09-09T18:58:01.368Z

Worker actions — Halted (attempt #1/5)

(요약 없음)

Files changed (4): node_modules, src/adapters/modelCompat.test.ts, src/adapters/modelCompat.ts, src/automation/autonomousRunner.ts

Halt reason: vela config.yaml이 ~/.openswarm, ~/.config/openswarm, /work/OpenSwarm, /warehouse 어디에도 없어 DoD 3·4번(역할 모델 교체·/api/stats 실측)을 이 환경에서 완료 불가. 코드 변경(폐기 경고+대체값, 양방향 테스트)은 완료.

Worker audit log · Confidence 55% · Duration 2m 37s · 2026-09-09

2026-09-09T19:00:29.382Z

Pair session started

Starting work in Worker/Reviewer pair mode.

via OpenSwarm · Session pipeline-1788980428873 · Project /work/OpenSwarm · 2026-09-09

2026-09-09T19:00:31.340Z

🧭 [OpenSwarm] Task execution started

  • Status: in_progress
  • Linear state: In Progress
  • Parent: none
  • Dependencies: (none)
  • Children: (none)
{
  "version": 1,
  "issueId": "142a87ba-5ec4-4d87-b70d-aa661c9ee01f",
  "issueIdentifier": "AGT-4232",
  "title": "fix(adapters): provider 전환이 역할별 모델을 조용히 버려 role split이 소실된다",
  "projectId": "74a9d092-7b3c-4d4d-a998-a2c9a8f08e83",
  "projectName": "OpenSwarm",
  "childIssueIds": [],
  "dependencyIssueIds": [],
  "dependencyTitles": [],
  "fileScope": [],
  "linearState": "In Progress",
  "execution": {
    "status": "in_progress",
    "retryCount": 0,
    "lastSessionId": "pipeline-1788980428873"
  },
  "worktree": {
    "branchName": "swarm/AGT-4232-fix-adapters-provider-role-split",
    "worktreePath": "/work/OpenSwarm/worktree/142a87ba-5ec4-4d87-b70d-aa661c9ee01f"
  },
  "updatedAt": "2026-09-09T19:00:28.901Z"
}

2026-09-09T19:00:31.940Z

Worker instruction (attempt #1/5)

Task: fix(adapters): provider 전환이 역할별 모델을 조용히 버려 role split이 소실된다

Goal: 모델 호환성 검사기에서 역할별 모델이 조용히 폐기되는 문제를 해결하고, codex 계열 어댑터에 맞는 역할별 모델을 설정하여 provider 전환 후에도 role split이 유지되도록 한다. 폐기 시 로그로 경고를 남기고, config.yaml의 OpenRouter 스타일 모델 ID가 codex-responses로 전환 시 무시되는 문제를 수정한다.

Target files: src/adapters/modelCompat.ts, src/adapters/modelCompat.test.ts, src/core/config.ts

Worker audit log · Model deepseek/deepseek-v4-flash · 2026-09-09

2026-09-09T19:03:38.104Z

Worker actions — Done (attempt #1/5)

작업 완료 요약 1. src/adapters/modelCompat.ts — 폐기 경고 형식 수정 - console.warnconsole.log (DoD 요구사항)

Files changed (4): node_modules, src/adapters/modelCompat.test.ts, src/adapters/modelCompat.ts, src/automation/autonomousRunner.ts

Worker audit log · Duration 2m 57s · 2026-09-09

⚠️ File overlap with in-flight work

This branch changes files that other open PRs / active branches also touch. Coordinate before merging to avoid divergent parallel edits (INT-2388 #3):

Linear

Closes AGT-4232


🤖 Generated with OpenSwarm

vela 컨테이너의 cursor-agent 2026.09.08에 직접 측정(2026-09-10):

    deepseek/deepseek-v4-flash  -> Cannot use this model (run 중단)
    gpt-5-codex                 -> Cannot use this model (run 중단)
    sonnet                      -> 실행되나 Claude Sonnet 4로 무음 해석
    gpt-5.6-terra               -> 실행됨

cursor-agent의 카탈로그에는 namespaced id가 하나도 없다. 그래서 다른
어댑터용 휴리스틱("`/`가 있으면 살린다")이 cursor에서는 정확히 거꾸로
작동했다.

compat 계층의 cursor 분기는 `return current` 무조건 통과였고, 값을
반환하므로 `[modelCompat] ... rejected` 경고가 구조적으로 발화할 수
없었다. 폐기는 그보다 뒤 CursorCliAdapter.resolveModel에서 무음으로
일어났다 -- compat이 "지켰다"고 보고한 뒤 어댑터가 버리는 유일한 어댑터.
결과는 worker와 reviewer가 같은 `auto` 하나로 붕괴하는 것, 즉 교정자가
교정 대상과 같은 모델이 되는 것이었다.

id를 이월하는 대신 역할로 라우팅한다. bulk 역할은 `auto`, 판정 역할은
`cursor-grok-4.6-high`, 승격은 `cursor-grok-4.6-xhigh`. 셋 다
`--list-models`에 실재함을 확인했다. 영속 카탈로그는 의도적으로 보지
않는다 -- `gpt-5.3-codex`는 cursor 목록에 있으므로 조회를 두면 config 값
하나가 worker와 reviewer 양쪽에 남아 다시 붕괴하고, 판정이 레포 밖 파일에
따라 머신마다 달라진다.

role이 배선되지 않은 지점을 전부 이었다. 이것이 없으면 위 라우팅은
런타임에서 무의미하다:

  - applyProviderConfig의 roleConfig가 role 이름 없이 호출됨
  - compatibleStageModel이 stage(=role)를 쥐고도 넘기지 않음
  - pairPipeline의 compatibleStageModel 호출 3곳 중 :312는 표시용이고
    실제 전달값은 :398/:495가 만듦 -- 앞엣것만 고치면 테스트만 초록이 됨
  - tester/documenter/auditor/skill-documenter가 raw config를 읽어
    auditor(판정 역할)가 도달 불가였음
  - orchestratorAgent가 compat을 통째로 우회
  - worker escalation과 signal escalation이 role 없이 승격 모델을 전달해,
    로그와 pipeline:escalation 이벤트는 승격을 알리는데 모델은 그대로였음

부수적으로 effort-only 신호가 승격 모델을 지우던 기존 결함도 고쳤다.
`{ model: undefined }`는 own property라 spread 병합에서 덮어쓴다 -- worker가
effort를 올리는 바로 그 순간 모델은 base로 되돌아갔다.

ModelRole 타입 유니온을 도입해 role 오타가 무음으로 bulk 모델로
떨어지지 않게 했고, 도달 불가였던 ADAPTER_DEFAULT_MODEL.cursor를 제거했다.
재라우팅은 (role, id)당 한 번 로그로 알린다 -- 두 계층 모두 말할 수 없는
자리라, 유효한 bare pin이 조용히 바뀌던 침묵을 남기지 않기 위해서다.

검증: tsc 0, oxlint 0, vitest 5645 passed / 0 failed, 변이 19개 심어 19개 사살.
독립 리뷰 3라운드(HIGH 4건 지적 -> 반영).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@unohee
unohee merged commit 32e4bb5 into main Sep 10, 2026
8 checks passed
@unohee
unohee deleted the swarm/AGT-4232-fix-adapters-provider-role-split branch September 10, 2026 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant