fix(adapters): provider 전환이 역할별 모델을 조용히 버려 role split이 소실된다 - #591
Merged
Merged
Conversation
This was referenced Sep 9, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TL;DR
vela 데몬이
codex-responses로 도는데 worker·reviewer가 전부gpt-5.6-terra한 모델로 붕괴했다. config.yaml이 지정한 역할별 모델이mapModelForProvider에서 조용히 버려지고 어댑터 기본값으로 떨어진 결과다. 벤치마크로 고른 모델 선택과 2단계 escalate 전략이 로그 한 줄 없이 죽어 있었다.실측 (2026-09-08, vela)
/api/stats)deepseek/deepseek-v4-flashgpt-5.6-terraqwen/qwen3-235b-a22b-2507gpt-5.6-terraprovider-override.json이codex-responses인데 config.yaml의 모델 id는 OpenRouter 네임스페이스라서 전부 탈락했다.근본 원인
src/adapters/modelCompat.ts: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·대체값)config.yaml의defaultRoles모델을 codex 슬러그로 교체/api/stats에서 role별 모델이 서로 다른지 실측 확인npm test· lint · typecheck greenDefinition of Done
/api/stats의 worker/reviewer 모델이 config.yaml 의도와 일치하고, 불일치가 생기면 로그로 즉시 드러난다.메타
adapterRouting.primary를codex-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)
deepseek/deepseek-v4-flash)정정 세 가지
1. draft는 죽지 않았다.
src/agents/draftAnalyzer.ts:39-40이 어댑터별 모델을 자체 맵으로 들고 있다.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 경유만 조용히 실패한다. 지금은 어느 모델이 실제로 도는지 알려면 원장을 파야 한다.
체크리스트에 한 줄 추가할 것:
2026-09-09T18:42:50.649Z
🧭 [OpenSwarm] Task execution started
in_progressIn Progressnone(none)(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
in_progressIn Progressnone(none)(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.tsWorker 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.tsHalt 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
in_progressIn Progressnone(none)(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.tsWorker 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.warn→console.log(DoD 요구사항)Files changed (4):
node_modules,src/adapters/modelCompat.test.ts,src/adapters/modelCompat.ts,src/automation/autonomousRunner.tsWorker audit log · Duration 2m 57s · 2026-09-09
This branch changes files that other open PRs / active branches also touch. Coordinate before merging to avoid divergent parallel edits (INT-2388 #3):
package-lock.jsonpackage-lock.jsonpackage-lock.jsonpackage-lock.jsonpackage-lock.jsonpackage-lock.json,package.jsonpackage-lock.jsonpackage-lock.json,package.jsonpackage-lock.jsonpackage-lock.jsonLinear
Closes AGT-4232
🤖 Generated with OpenSwarm