개요
로컬 스펙 사본 docs/symphony-spec.md(bc694f3, 2026-03-09)와 upstream openai/symphony SPEC.md (8001b52, 2026-08-12) 사이에 5개월치 변경이 쌓였다. 리서치 리포트 docs/reports/2026-08-28-upstream-spec-drift-research.md (PR #650 )가 구현(5b25655)을 새 스펙에 대조해 Drift 19건(A) , 처음부터 미구현 24건(B) , 라벨 안 된 divergence 13건(C) , 문서 오류 8건(D) , **버그/dead code 11건(E)**을 path:line 증거와 함께 정리했다. 이 Epic은 그 항목들을 PR 단위 하위 이슈로 묶어 추적한다.
한 문장: 스펙은 tracker-agnostic 계약으로 바뀌었고, 우리 구현은 아직 Linear 시대 계약 위에 GitHub 확장을 얹은 모양이다.
upstream 변경 요약
영역
새 규칙
tracker 설정
tracker.kind + 불투명 tracker.provider 객체, required_labels(ALL-of, 대소문자 무시), active/terminal states는 어댑터 프로필이 기본값을 문서화하지 않으면 REQUIRED
Issue 모델
dispatchable(REQUIRED, 어댑터 파생), native_ref, assignee_id; 라벨 trim/lowercase/dedupe; 타임스탬프 RFC 3339 파싱
적격성
오케스트레이터는 dispatchable + required_labels만 적용; provider blocker/board 의미론으로 분기 금지
어댑터 연산
fetch_issues_by_states + fetch_issues_by_ids(전체 스냅샷); 빈 입력 → provider 호출 없이 빈 결과
Retry / Reconcile
단일 ID refresh; terminal → cleanup+release; refresh 실패 → requeue; 슬롯 부족 → requeue; routable 아님 → cleanup 없이 종료
Workspace key
sanitize로 바뀐 identifier는 64-bit 해시 suffix
검증
integer-only; hooks.timeout_ms/max_turns 무효값은 검증 실패
런타임
turn_timeout_ms는 silence interval; 툴은 host-side 실행, 트래커 시크릿은 child에 미상속, secret_environment_names()
문서
어댑터별 compact profile(§11.2), 툴 계약 문서, implementation-defined 동작은 MUST document
워크스트림과 하위 이슈
W0 스펙 동기화
W1 정확성 핫픽스 — 모델 변경 없음, 병렬 가능
fix(orchestrator): rewrite the retry-fire path per spec §16.6 (single-ID refresh, terminal cleanup, requeue on refresh failure and slot exhaustion) #653 fix(orchestrator): §16.6 retry-fire 경로 (A7, B10)
Per-tick terminal workspace cleanup is unreachable on the GitHub Project tracker: the poll query excludes the very states it filters for #627 GitHub terminal cleanup per tick 미도달 (A8)
fix(orchestrator): pass a 1-based attempt to prompt rendering on retries and continuations #654 fix(orchestrator): 프롬프트 attempt 전달 (B1, A19)
fix(orchestrator): workspace hook semantics — fatal after_create/before_run, hooks on every attempt, filesystem-derived created_now, output truncation #655 fix(orchestrator): workspace hook 의미론 (B2, B3, B4, B18)
fix(orchestrator): isolate per-run restart failures inside the reconcile loop so one poison-pill retry cannot abort every tick #656 fix(orchestrator): retry-restart tick 격리 (B11)
fix(worker): treat codex subprocess exit as a turn completion condition, reject pending requests, cap line size, stop injecting the prompt twice #657 fix(worker): codex 프로세스 종료 = turn 종료, line cap, 프롬프트 중복 주입 (B12, B23, E1)
fix(worker): make turn_timeout_ms a silence interval and guarantee approval requests never stall the session #658 fix(worker): turn_timeout_ms silence 의미론, approval stall 방지 (A12, B13)
fix(core,orchestrator): normalize max_concurrent_agents_by_state keys (trim + lowercase) and ignore invalid entries #659 fix(core,orchestrator): per-state 동시성 키 정규화 (B9)
fix(trackers,orchestrator): §11.3 normalization — labels, timestamps, Linear priority 0, pagination integrity, priority sort bucket #660 fix(trackers): §11.3 정규화 + priority 정렬 (B14, B15, B16, A15)
fix(orchestrator): classify a worker exit after a failed turn as a failure retry (backoff), not a continuation retry #704 fix(orchestrator): 실패 turn 후 워커 종료를 continuation이 아닌 failure retry(backoff)로 분류 (PR fix(worker): handle codex protocol termination #680 스모크 관찰, 2026-08-28 추가)
W2 Tracker-agnostic 모델 — 순차
feat(core,trackers,orchestrator): explicit dispatchable and assigneeId on TrackedIssue; adapters return unassigned issues and the scheduler applies the flag #661 feat: 명시적 dispatchable + assigneeId (A1, A18)
feat(core,orchestrator): add dispatchable and assigneeId to TrackedIssue and gate dispatch on the flag #686 core/orchestrator 계약 + 스케줄러 게이트 · [ ] feat(tracker-github): derive dispatchable from assignment, board membership, and PR head repository; return unassigned issues #687 GitHub 어댑터 ← feat(core,orchestrator): add dispatchable and assigneeId to TrackedIssue and gate dispatch on the flag #686 · [ ] feat(tracker-linear,tracker-file): derive dispatchable instead of server-side assignee filtering; return unassigned issues #688 Linear/file 어댑터 ← feat(core,orchestrator): add dispatchable and assigneeId to TrackedIssue and gate dispatch on the flag #686 · [ ] docs+e2e: document dispatchable semantics and add a Docker E2E scenario for dispatchable=false #689 문서+E2E ← feat(tracker-github): derive dispatchable from assignment, board membership, and PR head repository; return unassigned issues #687 , feat(tracker-linear,tracker-file): derive dispatchable instead of server-side assignee filtering; return unassigned issues #688 (2026-08-28 triage 분할)
feat(tracker-github): return full normalized snapshots from fetchIssueStatesByIds #662 feat(tracker-github): fetchIssueStatesByIds 전체 스냅샷 (A9)
refactor(core,trackers,orchestrator): move blocker eligibility into adapter-derived dispatchable and retire blocker_check_states from core #663 refactor: blocker 규칙을 어댑터 dispatchable로 이관 (A2) ← S9
feat(core,orchestrator,worker): tracker.required_labels with spec semantics and a per-turn routable check #664 feat: tracker.required_labels + per-turn routable 검사 (A3, A10) ← S9, S10
refactor(core,orchestrator,trackers): add nativeRef and remove provider-payload branching from service.ts #665 refactor: nativeRef, service.ts의 provider 분기 제거 (A14, C12) ← S9
feat(core): collision-resistant workspace keys — stable 64-bit hash suffix when sanitization changes the identifier, with legacy-key lookup #666 feat(core): 충돌 방지 workspace key (A11)
W3 설정 계층
feat(core): strict front-matter validation — integer-only fields, hooks.timeout_ms, agent.max_turns, non-empty codex.command, supported tracker.kind, typed workflow errors #667 feat(core): 엄격 front-matter 검증 + typed error (A6, B8 일부, B17)
fix(core): expand ~ in workspace.root, scope $VAR expansion to path/secret values, handle missing secrets per value, make front matter optional #668 fix(core): ~ 확장, $VAR 범위, optional front matter (B5, B6, B7, E4)
feat(core,trackers,cli): tracker.provider object with adapter-owned validation and state defaults; deprecate flat tracker keys via doctor diagnostics #669 feat: tracker.provider + 어댑터 소유 검증/기본값 (A4, A5) ← S15
feat(core): parse and preserve tracker.provider; adapter-owned validation contract; map deprecated flat keys onto provider #707 core
feat(tracker-github,cli): GitHub provider keys, validateProviderConfig, lifecycle defaults, and doctor deprecation diagnostics with a converted provider block #708 GitHub+doctor ← feat(core): parse and preserve tracker.provider; adapter-owned validation contract; map deprecated flat keys onto provider #707
feat(tracker-linear,tracker-file): Linear/file provider keys, validateProviderConfig, and lifecycle defaults #709 Linear/file ← feat(core): parse and preserve tracker.provider; adapter-owned validation contract; map deprecated flat keys onto provider #707
feat(cli,docs): generate provider-form WORKFLOW.md (workflow init, skill templates), reload semantics for provider changes, configuration docs #710 생성기/문서 ← feat(tracker-github,cli): GitHub provider keys, validateProviderConfig, lifecycle defaults, and doctor deprecation diagnostics with a converted provider block #708 , feat(tracker-linear,tracker-file): Linear/file provider keys, validateProviderConfig, and lifecycle defaults #709 (2026-08-28 triage 분할)
feat(cli,orchestrator): repo start workflow preflight, unsupported tracker.kind must not abort reconciliation, honor repo init --workflow-file #670 feat(cli): repo start preflight, --workflow-file 존중 (B8 나머지, B19) ← S15
chore(core): remove deprecated flat tracker keys (next major) — do not start until removal is signed off #679 chore(core): flat 트래커 키 제거 (다음 major) — 트래킹 전용, 사인 전 착수 금지 ← feat(core,trackers,cli): tracker.provider object with adapter-owned validation and state defaults; deprecate flat tracker keys via doctor diagnostics #669
W4 시크릿·툴 격리
design: agent-tool isolation model — host-side execution via the token broker vs a documented MCP-subprocess divergence (ADR) #671 design: 에이전트 툴 격리 모델 결정 ADR (A13)
feat(core,trackers,runtimes): secretEnvironmentNames() on the adapter contract; strip tracker secrets from the Codex and Claude child environments #672 feat: secretEnvironmentNames() + child env 시크릿 제거 (A13) ← S19
feat(tools,worker): pass the normalized issue (id, nativeRef) as tool execution context, remove the dead dynamic-tool path, document per-tool contracts #673 feat(tools): 정규화 issue 컨텍스트 전달, dead dynamic-tool 경로 제거 (E2, A16 툴) ← S13, S19
feat(worker,runtime-codex): host-side dynamic tool execution for Codex — advertise tool specs, handle item/tool/call in the worker with issue context, structured failure for unknown tools #730 Codex host-side
feat(worker,runtime-claude): host-side MCP over HTTP for Claude — worker-hosted MCP server, mcp.json with URL and session token only #731 Claude host-side
feat(core,tools,docs): tool execution context contract in tracker adapters, per-tool documentation, and Docker E2E for host-side tools #732 계약/문서/E2E ← feat(worker,runtime-codex): host-side dynamic tool execution for Codex — advertise tool specs, handle item/tool/call in the worker with issue context, structured failure for unknown tools #730 , feat(worker,runtime-claude): host-side MCP over HTTP for Claude — worker-hosted MCP server, mcp.json with URL and session token only #731 (2026-08-28 triage 분할)
feat(runtimes,worker): complete ADR Phase 1 — disable agent-owned MCP exposure, host-side Git transport, isolated child HOME (merge only with #673) #700 feat: ADR Phase 1b — MCP 노출 비활성화·host-side Git 전송·child HOME 격리 (#673과 동시 병합) ← feat(core,trackers,runtimes): secretEnvironmentNames() on the adapter contract; strip tracker secrets from the Codex and Claude child environments #672 , feat(tools,worker): pass the normalized issue (id, nativeRef) as tool execution context, remove the dead dynamic-tool path, document per-tool contracts #673
W5 문서·관측
docs(trackers): publish compact adapter profiles for github-project, linear, and file per spec §11.2 #674 docs(trackers): §11.2 어댑터 프로필 (A16) ← S17
docs: label existing divergences (C1–C13) as spec divergences and fix documentation errors (D1–D8) #675 docs: divergence C1–C13 라벨링 + 문서 오류 D1–D8 수정
docs+obs: WORKFLOW.md watch 미구현을 divergence로 문서화하고 적용 중인 리비전을 노출 #626 WORKFLOW.md watch divergence 문서화 + 리비전 노출
feat(observability,control-plane): issue_url on snapshot rows, emit run-retried, summed seconds_running, server.port/--port alias #676 feat(observability): issue_url, run-retried, seconds_running, server.port (A17, B22, E3, B24)
feat(runtime-claude): honor turn/read timeouts and max_turns on the Claude print-mode route #677 feat(runtime-claude): Claude 경로 timeout/max_turns (B21)
test: cover spec §17 conformance rows not owned by another sub-issue #678 test: 다른 이슈가 덮지 않는 §17 conformance 갭
B20(Claude 런타임 토큰 집계)은 #645 Epic의 첫 하위 항목으로 그쪽에서 발행한다.
순서
Critical path: S9 → S13 → S21, S15 → S17 → S22. 나머지 16개는 즉시 착수 가능. blocked-by 관계는 실제 기술 의존에만 걸었고, 권장 착수 순서는 W1 → W2 → W3 → W4 → W5.
결정 사항 (2026-08-28 확정)
#
결정
반영 이슈
1
툴 격리: (a) host-side 실행 — 트랜지션 전략(2026-08-28 확정): Phase 1a(#672 , raw 토큰 제거·기능 손실 없음) → Phase 2(#673 , 두 런타임 host-side 툴) → Phase 1b(#700 , MCP 비활성화·Git 대행·HOME 격리, #673과 같은 릴리스에서만 병합 ). 최종 도달점은 ADR 그대로
#671 (ADR) → #672 → #673 → #700
2
priority 정렬: (b) ADR 2026-05-18 supersede — 숫자 오름차순 유지를 documented different mapping으로 기록, Linear 0 → null
#660
3
flat 트래커 키: (a) deprecated alias 유지 (non-breaking), doctor가 변환된 provider 블록 출력, 다음 major에서 제거
#669 , 제거 트래킹 #679 (사인 전 착수 금지)
4
워크스페이스 키: (a) legacy 키 병행 조회 , 디스크 rename 없음, repo status에 legacy 표시
#666
5
approval 정책: never 외 값은 설정 검증에서 거부 (핸들러 미구현)
#658
6
blocker_check_states: tracker.provider로 이동 (폐기 아님)
#663
7
Docker E2E 동시 실행 충돌 임시 완화: max_concurrent_agents: 10 유지 (2026-08-28). 근본 수정은 #692 ; #693이 한 번 더 거절되면 하향(1–2) 재검토
#692
8
WIP 상한 8 (Ready + In progress + In review 합산, 2026-08-28): 병합 시 rebase·충돌 해소·재검증 공수를 줄이기 위함. 상한 초과 시 PR 없는 Ready부터 Backlog로 내리고, 신규 픽업은 총 WIP < 8일 때만
보드 운영
9
Land = 에이전트 병합 트리거 (2026-08-28 확인): Land 이동 시 에이전트가 approve된 PR을 squash-merge하고 Done 처리. 보드 쓰기는 반드시 직전에 현재 상태·이슈 open 여부를 재확인(가드)하고 닫힌/Done 항목은 건드리지 않음 — stale 스냅샷 쓰기로 #687 /#665가 Done→Land로 회귀했던 사고의 재발 방지
보드 운영
범위 외
Appendix A SSH worker extension (OPTIONAL) — docs/architecture.md에 범위 외로 한 줄 표기 (S23)
새 스펙이 MAY로 둔 fallback prompt (A20)
완료 기준
위 하위 이슈 전부 close
docs/symphony-spec.md가 upstream 8001b52와 일치하고 docs/README.md의 "Draft v1" 표기 갱신
리포트의 A/B 항목이 코드 또는 문서화된 divergence로 각각 해소되어 2026-08-28-upstream-spec-drift-research.md 헤더 status가 갱신됨
새 §17 conformance 행에 대응하는 테스트가 pnpm test에 포함
개요
로컬 스펙 사본
docs/symphony-spec.md(bc694f3, 2026-03-09)와 upstream openai/symphony SPEC.md(8001b52, 2026-08-12) 사이에 5개월치 변경이 쌓였다. 리서치 리포트docs/reports/2026-08-28-upstream-spec-drift-research.md(PR #650)가 구현(5b25655)을 새 스펙에 대조해 Drift 19건(A), 처음부터 미구현 24건(B), 라벨 안 된 divergence 13건(C), 문서 오류 8건(D), **버그/dead code 11건(E)**을path:line증거와 함께 정리했다. 이 Epic은 그 항목들을 PR 단위 하위 이슈로 묶어 추적한다.한 문장: 스펙은 tracker-agnostic 계약으로 바뀌었고, 우리 구현은 아직 Linear 시대 계약 위에 GitHub 확장을 얹은 모양이다.
upstream 변경 요약
tracker설정tracker.kind+ 불투명tracker.provider객체,required_labels(ALL-of, 대소문자 무시), active/terminal states는 어댑터 프로필이 기본값을 문서화하지 않으면 REQUIREDdispatchable(REQUIRED, 어댑터 파생),native_ref,assignee_id; 라벨 trim/lowercase/dedupe; 타임스탬프 RFC 3339 파싱dispatchable+required_labels만 적용; provider blocker/board 의미론으로 분기 금지fetch_issues_by_states+fetch_issues_by_ids(전체 스냅샷); 빈 입력 → provider 호출 없이 빈 결과hooks.timeout_ms/max_turns무효값은 검증 실패turn_timeout_ms는 silence interval; 툴은 host-side 실행, 트래커 시크릿은 child에 미상속,secret_environment_names()워크스트림과 하위 이슈
W0 스펙 동기화
docs/symphony-spec.md를 upstream8001b52로 동기화W1 정확성 핫픽스 — 모델 변경 없음, 병렬 가능
attempt전달 (B1, A19)turn_timeout_mssilence 의미론, approval stall 방지 (A12, B13)W2 Tracker-agnostic 모델 — 순차
dispatchable+assigneeId(A1, A18)fetchIssueStatesByIds전체 스냅샷 (A9)dispatchable로 이관 (A2) ← S9tracker.required_labels+ per-turn routable 검사 (A3, A10) ← S9, S10nativeRef,service.ts의 provider 분기 제거 (A14, C12) ← S9W3 설정 계층
~확장,$VAR범위, optional front matter (B5, B6, B7, E4)tracker.provider+ 어댑터 소유 검증/기본값 (A4, A5) ← S15repo startpreflight,--workflow-file존중 (B8 나머지, B19) ← S15W4 시크릿·툴 격리
secretEnvironmentNames()+ child env 시크릿 제거 (A13) ← S19W5 문서·관측
issue_url,run-retried,seconds_running,server.port(A17, B22, E3, B24)max_turns(B21)B20(Claude 런타임 토큰 집계)은 #645 Epic의 첫 하위 항목으로 그쪽에서 발행한다.
순서
Critical path: S9 → S13 → S21, S15 → S17 → S22. 나머지 16개는 즉시 착수 가능. blocked-by 관계는 실제 기술 의존에만 걸었고, 권장 착수 순서는 W1 → W2 → W3 → W4 → W5.
결정 사항 (2026-08-28 확정)
0→nulldoctor가 변환된provider블록 출력, 다음 major에서 제거repo status에 legacy 표시never외 값은 설정 검증에서 거부 (핸들러 미구현)blocker_check_states:tracker.provider로 이동 (폐기 아님)max_concurrent_agents: 10유지(2026-08-28). 근본 수정은 #692; #693이 한 번 더 거절되면 하향(1–2) 재검토범위 외
docs/architecture.md에 범위 외로 한 줄 표기 (S23)완료 기준
docs/symphony-spec.md가 upstream8001b52와 일치하고docs/README.md의 "Draft v1" 표기 갱신2026-08-28-upstream-spec-drift-research.md헤더 status가 갱신됨pnpm test에 포함