MMax's Develop Story

Claude Code Workflows: 오케스트레이션을 대화에서 코드로 옮기다

Max··35분 읽기
Claude CodeWorkflows오케스트레이션Sub-Agents멀티에이전트회고

세미나 시리즈 5주차에서 Sub-Agent를 "외주 전문가에게 위임하는 일"이라고 정리했다. 그런데 외주 전문가가 다섯이 아니라 오백 명이라면, 그 발주서를 누가 관리해야 하는가. Workflows의 답은 명확하다. Claude가 아니라 스크립트가 관리한다. 그리고 이 글 자체가 그 증거다: 이 글의 조사는 deep-research 워크플로우 하나가 에이전트 98개를 20분 동안 돌려서 해냈고, 그동안 나는 같은 세션에서 초안을 쓰고 있었다.

사내 Claude Code 세미나를 5주로 마치고 나서, "다음 시리즈는 무엇으로 잡을까"라는 질문이 계속 남아 있었다. 후보를 고르려고 문서를 뒤지다가 한 페이지에서 멈췄다. 동적 워크플로우(Dynamic Workflows). 5주차에서 다룬 sub-agent의 그림이 "한 번에 몇 명의 전문가에게 위임"이었다면, 이 기능은 그 그림을 "스크립트 하나가 수백 명의 전문가를 프로그램적으로 조율" 로 확장한다. 코드베이스 전체 감사, 500개 파일 마이그레이션, 다중 소스 교차 검증 리서치. 대화 한 턴으로는 절대 감당이 안 되는 규모의 작업이 대상이다.

이 글은 공식 문서공식 블로그를 1차 소스로 삼고, 실제로 워크플로우를 돌려보면서 뜯어본 기록이다. 늘 그렇듯 백엔드 시니어의 시선으로: 이 도구가 어떤 그림 위에서 동작하는지, 그리고 우리가 이미 아는 어떤 원리가 여기서 다시 등장하는지.

1. 워크플로우란 무엇인가: 런타임이 실행하는 오케스트레이션 스크립트

공식 문서의 정의는 한 문장이다.

동적 워크플로우는 대규모로 서브에이전트를 조율하는 JavaScript 스크립트 다. Claude가 작업에 맞는 스크립트를 작성하고, 런타임이 백그라운드에서 실행하는 동안 세션은 응답성을 유지한다.

핵심은 "누가 계획을 들고 있는가"다. 문서가 제공하는 비교표를 그대로 옮긴다. 이 표 하나가 sub-agent, skill, agent team, workflow의 위상을 전부 정리한다.

서브에이전트스킬에이전트 팀워크플로우
정의Claude가 생성하는 워커Claude가 따르는 지침피어 세션을 감독하는 리드런타임이 실행하는 스크립트
다음 실행을 결정Claude, 차례대로Claude, 프롬프트 따라리드 에이전트스크립트
중간 결과 위치Claude의 컨텍스트Claude의 컨텍스트공유 작업 목록스크립트 변수
반복 가능한 것워커 정의지침팀 정의조율 자체
규모턴당 몇 개서브에이전트와 동일소수의 장기 피어실행당 수십~수백 에이전트
중단 시턴 다시 시작턴 다시 시작팀원 계속 실행같은 세션에서 재개

계획이 컨텍스트에서 코드로 이동

서브에이전트 체인에서는 Claude가 조율자다. "다음에 뭘 시키지?"를 매 턴 Claude가 결정하고, 모든 중간 결과가 컨텍스트 윈도우에 쌓인다. 파일 20개면 괜찮다. 200개면 중간 결과가 계획 자체를 밀어낸다. 워크플로우는 이 조율을 코드로 옮긴다. 루프, 분기, 중간 결과를 스크립트가 변수로 들고 있고, Claude의 컨텍스트에는 최종 답변만 들어온다.

여기서 한 가지 강조하고 싶은 문장이 문서에 있다. "계획을 코드로 이동하면 워크플로우가 반복 가능한 품질 패턴 을 적용할 수 있으며, 단순히 더 많은 에이전트를 실행하는 것이 아니다." 독립 에이전트끼리 서로의 발견을 적대적으로 검증한 뒤 보고하게 하거나, 여러 각도에서 계획을 작성해 비교하게 하는 것. 규모가 아니라 구조가 이 기능의 본질이다. 이 얘기는 6절에서 다시 한다.

참고로 이 기능은 Claude Code v2.1.154 이상, 모든 유료 요금제에서 사용 가능하다(Pro는 /config에서 활성화). Anthropic API 외에 Bedrock, Vertex AI, Microsoft Foundry에서도 동작한다.

2. 동작 방식: 격리된 런타임과 네 가지 제약

워크플로우 런타임은 대화와 분리된 격리 환경 에서 스크립트를 실행한다. 실행이 시작되면 스크립트가 ~/.claude/projects/ 아래 세션 디렉토리에 파일로 저장되고, 런타임이 각 에이전트의 결과를 추적한다. 이 추적이 뒤에서 다룰 재개(resume)의 기반이다.

런타임이 강제하는 제약은 네 가지다. 각각에 이유가 있다.

제약이유
실행 중 사용자 입력 없음에이전트 권한 프롬프트만 실행을 멈출 수 있다. 단계 사이 승인이 필요하면 각 단계를 별도 워크플로우로
스크립트 자체의 파일시스템·셸 접근 없음읽고 쓰고 실행하는 건 에이전트다. 스크립트는 에이전트를 조율만 한다
동시 에이전트 최대 16개로컬 리소스 보호. CPU 코어가 적은 머신에서는 더 적다
실행당 총 1,000 에이전트폭주 루프 방지

두 번째 제약이 설계의 성격을 가장 잘 보여준다. 스크립트는 순수한 컨트롤 플레인(Control Plane)이고, 데이터 플레인은 전부 에이전트다. 스크립트가 직접 파일을 읽을 수 없으니, "파일 목록을 얻는 것"조차 에이전트에게 시켜야 한다. 처음에는 번거로워 보였는데, 곱씹을수록 옳은 결정이다. 오케스트레이터가 워커의 일을 겸하기 시작하는 순간 경계가 무너진다는 걸, 우리는 배치 시스템에서 이미 배웠다.

동시성 상한도 눈여겨볼 부분이다. 100개 항목을 넘겨도 전부 수행은 되지만, 어느 시점에든 실제로 도는 것은 최대 16개이고 나머지는 큐에서 대기한다. 스레드 풀과 작업 큐, 그 이상도 이하도 아니다. 백엔드 개발자에게는 오히려 이 익숙함이 신뢰의 근거가 된다. 참고로 이 16이라는 숫자는 설정으로 올릴 수 없는 하드 리밋이다. 상한을 조정 가능하게 해달라는 GitHub 이슈가 이미 올라와 있지만, 로컬 리소스 보호라는 목적을 생각하면 보수적인 기본값이 맞는 방향이라고 본다.

3. 스크립트 해부: meta 블록과 다섯 개의 프리미티브

저장된 워크플로우 스크립트가 실제로 어떻게 생겼는지 보자. 공식 문서의 최소 예제다.

export const meta = {
  name: 'audit-routes',
  description: 'Audit every route handler for missing auth checks',
}
 
const found = await agent('List every .ts file under src/routes/.', {
  schema: { type: 'object', required: ['files'], properties: { files: { type: 'array', items: { type: 'string' } } } },
})
 
const audits = await pipeline(found.files, file =>
  agent(`Audit ${file} for missing authentication checks.`, { label: file }),
)
 
return audits.filter(Boolean)

본문은 최상위 await를 쓸 수 있는 순수 JavaScript다. TypeScript가 아니고, Node.js API도 없다. 한 가지 짚어둘 점: 공식 워크플로우 페이지가 직접 문서화하는 것은 meta 블록, agent(), pipeline(), 전역 args까지이고, 나머지 전체 옵션 집합은 Agent SDK 레퍼런스의 Workflow 도구 항목에 위임되어 있다. 실제 런타임이 제공하는 프리미티브를 정리하면 이렇다.

  • agent(prompt, opts): 서브에이전트 하나를 생성한다. 기본 반환은 에이전트의 최종 텍스트. schema 옵션에 JSON Schema를 넘기면 검증된 구조화 객체 가 돌아온다: 파싱 코드가 필요 없고, 스키마 불일치 시 모델이 재시도한다. label(표시 이름), model·effort(단계별 모델·추론 강도 라우팅), isolation: 'worktree'(격리 git worktree) 같은 옵션이 있다.
  • pipeline(items, stage1, stage2, ...): 각 항목을 모든 스테이지에 독립적으로 통과시킨다. 스테이지 사이에 배리어가 없다. 항목 A가 3단계에 있는 동안 항목 B는 아직 1단계일 수 있다.
  • parallel(thunks): 작업들을 동시 실행하고 전부 끝날 때까지 기다린다. 배리어다. 실패한 작업은 결과 배열에서 null이 된다.
  • phase(title) / log(message): 진행 상황 표시용. phase는 이후의 agent() 호출을 진행 뷰에서 하나의 그룹으로 묶고, log는 사용자에게 내레이션 한 줄을 남긴다.
  • args / budget: args는 저장된 워크플로우에 실행 시점 입력을 넘기는 전역 변수다. budget은 턴의 토큰 목표를 읽어 budget.remaining()으로 "예산이 남은 동안 계속 찾기" 같은 동적 루프를 만들 수 있게 한다.

schema 옵션은 따로 한 단락을 쓸 가치가 있다. LLM 파이프라인을 만들어 본 사람이라면 "앞 단계의 출력을 뒷 단계 입력으로 넘길 때 포맷이 흔들려서 깨지는" 경험이 반드시 있을 것이다. 워크플로우는 이걸 단계 간 계약(Contract) 으로 풀었다. 스키마가 곧 인터페이스 정의고, 검증 실패는 재시도로 흡수된다. 마이크로서비스에서 API 스펙 없이 서비스를 연결하지 않듯, 에이전트 파이프라인도 스키마 없이 연결하면 안 된다.

한 가지 함정도 있다. 스크립트 안에서 Date.now(), Math.random(), 인자 없는 new Date()의도적으로 막혀 있다. 재개(resume) 시 같은 호출이 같은 결과를 돌려줘야 캐시가 성립하기 때문이다. 타임스탬프가 필요하면 args로 주입한다. 결정론이 깨지면 재실행 가능성도 깨진다는 것: 이벤트 소싱에서 리플레이를 지켜본 사람에게는 아주 익숙한 규율이다.

4. pipeline vs parallel: 배리어는 언제만 정당한가

효율 관점에서 이 기능의 가장 중요한 선택이 여기다. 기본값은 pipeline()이다.

pipeline과 parallel의 타이밍 비교

parallel()을 두 번 연달아 쓰면 스테이지 사이에 배리어가 생긴다. 5개의 리뷰 에이전트를 돌렸는데 가장 느린 하나가 다른 넷의 3배 시간을 쓴다면, 빠른 넷의 검증 단계는 그 하나를 기다리며 논다. pipeline()은 항목별로 체인이 독립이라 이 대기가 없다. 전체 소요 시간이 "스테이지별 최악의 합"에서 "가장 느린 단일 항목 체인"으로 줄어든다.

그럼 배리어는 언제 정당한가. 다음 단계가 이전 단계 전체의 결과를 한꺼번에 필요로 할 때뿐이다.

  • 비싼 검증에 들어가기 전에 전체 발견 사항을 중복 제거 할 때
  • "발견 0건이면 검증 자체를 건너뛴다" 같은 조기 종료 판단이 필요할 때
  • 다음 단계의 프롬프트가 "다른 발견들과 비교해서" 처럼 교차 항목 컨텍스트 를 참조할 때

반대로 이런 이유는 배리어를 정당화하지 못한다.

// ❌ 결과를 flatten하려고 배리어를 만드는 패턴
const a = await parallel(files.map(f => () => agent(`review ${f}`)))
const b = a.filter(Boolean).flat()          // 교차 항목 의존이 전혀 없는 변환
const c = await parallel(b.map(r => () => agent(`verify ${r.title}`)))
// ✅ 변환은 pipeline의 스테이지 안에서: 배리어 없이 항목별로 흘려보낸다
const results = await pipeline(
  files,
  f => agent(`review ${f}`, { phase: 'Review' }),
  r => parallel(r.findings.map(fd => () =>
    agent(`verify: ${fd.title}`, { phase: 'Verify' }))),
)

"스테이지가 개념적으로 분리되니까", "코드가 깔끔하니까"는 이유가 아니다. 개념의 분리와 실행의 동기화는 다른 문제다. 이 문장은 분산 시스템 설계 리뷰에서 내가 수없이 반복한 말이기도 하다. fork-join을 남발한 배치가 왜 느린지 설명할 때와 논리가 한 글자도 다르지 않다.

5. 사용 방법: ultracode부터 /workflows까지

동작 원리를 이해했으니 실제 사용 동선을 정리한다. 진입점은 세 가지다.

① 프롬프트에서 단발 요청: ultracode 키워드를 포함하거나, 자연어로 "워크플로우 사용해서"라고 직접 요청한다. 이 옵트인이 명시적이어야 하는 이유는 단순하다: 워크플로우는 수십 개의 에이전트를 띄우고, 그만큼의 토큰을 쓴다.

ultracode: src/routes/ 아래 모든 API 엔드포인트에서 누락된 인증 체크를 감사하고,
각 발견을 보고 전에 적대적으로 검증해줘

② 세션 전체 옵트인: /effort ultracode. xhigh 추론 강도와 자동 워크플로우 조율을 결합한 세션 설정으로, 이후의 모든 실질적 작업마다 Claude가 워크플로우를 계획한다. 요청 하나가 "이해 → 변경 → 검증"의 워크플로우 세 개로 이어지기도 한다. 세션 한정이며, 일상 작업으로 돌아갈 땐 /effort high로 내린다. /effort 메뉴에 ultracode가 안 보인다면 현재 모델이 xhigh 추론을 지원하지 않는 경우이니 모델부터 확인하라.

③ 저장된 워크플로우 실행: /deep-research처럼 슬래시 명령으로. 번들 워크플로우와 직접 저장한 워크플로우 모두 / 자동완성에 뜬다.

실행 전에는 계획 승인 프롬프트가 뜬다. 계획된 단계 목록과 함께 "예 / 이 워크플로우는 다시 묻지 않기 / 원본 스크립트 보기 / 아니오"를 고를 수 있고, Ctrl+G로 스크립트를 에디터에서 열어볼 수도 있다. 권한 모델에서 주의할 지점이 하나 있다.

워크플로우가 생성하는 서브에이전트는 세션의 권한 모드와 무관하게 항상 acceptEdits 모드로 실행 되고, 도구 허용 목록을 상속한다. 파일 편집은 자동 승인된다. 반면 허용 목록에 없는 셸 명령·웹 페치·MCP 도구는 실행 중에도 프롬프트를 띄울 수 있다. 긴 실행이 새벽 2시에 권한 프롬프트에 걸려 멈춰 있는 걸 원치 않는다면, 필요한 명령을 시작 전에 허용 목록에 넣어라.

실행이 시작되면 백그라운드로 돌고 세션은 계속 쓸 수 있다. /workflows로 진행 뷰를 열면 단계별 에이전트 수·토큰 총계·경과 시간이 보이고, 드릴다운해서 개별 에이전트의 프롬프트와 도구 호출까지 읽을 수 있다. p(일시정지/재개), x(중지), r(에이전트 재시작), f(상태 필터) 같은 키가 있고, 가장 중요한 키는 s다: 현재 실행의 스크립트를 명령으로 저장한다.

저장 위치는 두 곳이다.

  • .claude/workflows/ (프로젝트): 저장소를 클론하는 팀 전체와 공유. 이름이 겹치면 프로젝트 쪽이 이긴다.
  • ~/.claude/workflows/ (홈): 모든 프로젝트에서 나만 사용.

저장된 워크플로우는 args로 실행 시점 입력을 받는다. "Run /triage-issues on issues 1024, 1025, and 1030"처럼 말하면 Claude가 목록을 구조화된 데이터로 넘겨주므로, 스크립트는 파싱 없이 args.map(...)을 바로 호출할 수 있다. 매 실행마다 스크립트를 편집하는 워크플로우는 저장할 자격이 없다. 파라미터화가 저장의 전제조건이다.

6. 품질 패턴: 더 많은 에이전트가 아니라 더 좋은 구조

1절에서 예고한 이야기다. 워크플로우의 가치는 에이전트 수가 아니라, 단일 패스로는 불가능한 품질 구조를 코드로 강제할 수 있다 는 데 있다. 대표 패턴 네 가지.

적대적 검증(Adversarial Verify): 발견 하나마다 독립 검증자 N명을 붙이되, 프롬프트를 "확인해줘"가 아니라 "반박해봐" 로 준다. 과반이 반박하면 발견을 버린다.

const votes = await parallel(Array.from({ length: 3 }, () => () =>
  agent(`다음 주장을 반박하라: ${claim}. 불확실하면 반박된 것으로 간주하라.`,
    { schema: VERDICT })))
const survives = votes.filter(Boolean).filter(v => !v.refuted).length >= 2

그럴듯하지만 틀린 발견(plausible-but-wrong)이 보고서에 살아남는 걸 막는 유일한 구조적 방법이다. 검증자에게 서로 다른 렌즈(정확성·보안·재현 가능성)를 주는 변형은 동일 프롬프트 3벌보다 실패 모드를 넓게 잡는다.

바닥날 때까지 반복(Loop-until-dry): 버그·플레이키 테스트처럼 총량을 모르는 탐색 은 "10개 찾으면 종료" 같은 고정 카운트가 아니라 "연속 2라운드 동안 새 발견이 없으면 종료"로 짠다. 꼬리를 놓치지 않는다.

심사 패널(Judge Panel): 어려운 설계 결정은 서로 다른 각도(MVP 우선·리스크 우선·사용자 우선)에서 N개의 독립 초안을 만들고, 심사 에이전트들이 채점한 뒤, 우승안에 차점안들의 좋은 아이디어를 접붙여 종합한다. 해공간이 넓을 때 "하나 만들어 고치기"보다 일관되게 낫다.

다중 모드 스윕(Multi-modal Sweep): 리서치라면 검색 각도별로(주체별·내용별·시간별) 에이전트를 따로 띄운다. 한 각도의 검색이 못 찾는 것을 다른 각도가 찾는다.

이 패턴들을 보면서 어떤 기시감이 들었다면 정확하다. 적대적 검증은 코드 리뷰 문화의 자동화고, 심사 패널은 설계 리뷰 회의의 자동화다. 우리가 사람 조직에서 품질을 만들기 위해 발명한 프로세스가, 에이전트 조직에도 그대로 필요하다. 다른 점은 하나뿐이다: 사람 조직에서는 프로세스가 문화로 유지되지만, 워크플로우에서는 코드로 강제 된다.

흥미로운 건 커뮤니티가 같은 패턴에 독자적으로 도달하고 있다는 점이다. 이번 조사에서 확인한 사례 둘: agent-review-panel 플러그인은 4~6개의 서로 다른 페르소나 리뷰어가 병렬로 독립 평가한 뒤 상호 반박을 거치고 최종 심판(judge) 에이전트가 이견을 중재하는 구조를 구현했고, adversarial-review는 아예 Workflow 기능 밖에서 셸 스크립트로 Claude CLI와 Codex CLI를 엮어 이종 모델 간 4단계 debate 루프(독립 리뷰 → 상호 비판 → 반박 → 종합)를 돌린다. 패턴은 도구에 종속되지 않는다. 워크플로우가 제공하는 것은 이 패턴들을 팀의 저장소에 커밋 가능한 형태로 코드화하는 가장 짧은 경로다.

7. 운영과 비용: 작게 시작하고, 재개를 믿어라

워크플로우는 단일 실행이 대화 대비 의미 있게 많은 토큰을 쓴다. 요금제의 사용량·속도 제한에 그대로 포함된다. 감이 잘 안 온다면 실측치 하나: 이 글을 위해 돌린 deep-research 실행은 에이전트 98개, 도구 호출 621회, 토큰 약 359만 개를 20분 동안 썼다. 대화 몇 턴이면 끝나는 세션과는 자릿수가 다르다. 그래서 v2.1.203부터는 실행이 25개 이상의 에이전트를 스케줄하거나 예상 토큰 총량이 150만을 넘으면 작업 패널에 Large workflow 경고 가 뜬다(실행을 막지는 않는 advisory다). 실행당 달러 예산을 설정하는 기능은 없다. 그래서 공식 문서와 실사용 양쪽에서 검증된 운영 수칙이 중요해진다.

  • 작은 범위로 먼저 잰다: 전체 저장소 대신 디렉토리 하나, 광범위한 질문 대신 좁은 질문으로 먼저 실행해 지출을 측정한다. /workflows 뷰가 에이전트별 토큰을 실시간으로 보여주고, 언제든 완료분을 잃지 않고 중지할 수 있다.
  • 모델을 단계별로 라우팅한다: 모든 에이전트는 기본적으로 세션 모델을 상속한다. 대규모 실행 전에 /model을 확인하고, 기계적인 수집 단계에는 더 작은 모델을 쓰도록 요청한다. 5주차에 정리한 "haiku로 충분한 작업에 opus를 쓰지 마라"가 여기서는 단계 단위로 적용된다.
  • 재개를 신뢰하되, 경계를 안다: 실행을 중지했다 재개하면 이미 완료된 에이전트는 캐시된 결과를 즉시 반환 하고 나머지만 라이브로 돈다. 스크립트를 수정한 경우에도 변경되지 않은 접두 구간은 캐시가 적용된다. 단, 재개는 같은 Claude Code 세션 안에서만 동작한다. 세션을 종료하면 다음 세션은 처음부터다. 장시간 세션에서 자동 컴팩션으로 세션 ID가 바뀌면 결과 저널이 유실되는 엣지 케이스도 보고되어 있으니, 대형 실행은 컨텍스트가 여유 있는 세션에서 시작하는 편이 안전하다.
  • 끄는 법도 알아둔다: /config의 동적 워크플로우 토글, ~/.claude/settings.json"disableWorkflows": true, 환경 변수 CLAUDE_CODE_DISABLE_WORKFLOWS=1. 조직 단위로는 관리 설정에서 차단할 수 있다. 비활성화하면 번들 명령·ultracode 키워드·/effort 메뉴의 ultracode가 모두 사라진다.

재개 메커니즘은 조금 더 강조하고 싶다. 런타임이 각 agent() 호출의 결과를 저널로 추적하기 때문에, 워크플로우는 사실상 체크포인트가 내장된 배치 잡 이다. 500파일 마이그레이션이 300번째에서 멈췄을 때 처음부터 다시 도는 시스템과 301번째부터 이어가는 시스템의 차이를, 새벽의 배치 장애를 겪어본 사람은 안다. 이 차이가 "큰 작업을 맡겨도 되는가"라는 신뢰의 근거다.

8. 시니어의 시선으로 정리하는 인사이트

① 이것은 LLM 위에 다시 쓴 배치 프레임워크다

컨트롤 플레인과 데이터 플레인의 분리, 스레드 풀과 작업 큐, 체크포인트와 재시작, 폭주 방지 상한. Spring Batch의 Job·Step·Chunk가 그대로 겹쳐 보인다. 다른 것은 워커가 스레드가 아니라 에이전트라는 것뿐이다. 새 패러다임처럼 보이는 것의 뼈대가 우리가 30년 검증한 설계라는 사실은, 이 기능을 신뢰해도 되는 이유이기도 하다.

② 계획을 코드로 옮기는 순간 재현 가능성이 생긴다

대화로 한 오케스트레이션은 휘발된다. 같은 작업을 다음 주에 또 하려면 같은 대화를 다시 해야 하고, 결과도 매번 다르다. 스크립트로 옮긴 오케스트레이션은 저장되고, 리뷰되고, 버전 관리되고, args로 파라미터화된다. "조율 자체가 반복 가능해진다"는 표의 한 칸이 이 기능의 존재 이유 전부다. 인프라를 코드로 옮겼을 때(IaC) 우리가 얻은 것과 정확히 같은 종류의 이득이다.

③ 배리어는 분산 시스템에서와 똑같이 비싸다

parallel() 연쇄는 fork-join이고, pipeline()은 스트리밍이다. 교차 항목 의존이 없는데 배리어를 두는 것은 파티션 간 셔플이 필요 없는데 스테이지를 끊는 것과 같다. 동기화 지점은 정당화될 때만 존재해야 한다 는 원칙은 실행 주체가 스레드든 프로세스든 에이전트든 변하지 않는다.

④ 검증을 의지가 아니라 구조로 강제하라

"보고 전에 검증해야지"는 의지다. pipeline(찾기, 검증하기)은 구조다. 의지는 마감 앞에서 무너지지만 구조는 무너지지 않는다. 적대적 검증 패턴의 진짜 교훈은 LLM의 환각 대응이 아니라, 품질 게이트는 프로세스에 박아야 지켜진다 는 오래된 원칙의 재확인이다. CI에 테스트를 박은 이유와 같다.

⑤ 명시적 옵트인은 비용 통제의 첫 번째 방어선이다

워크플로우는 사용자가 ultracode나 자연어로 명시하지 않으면 시작되지 않고, 시작 전에 계획 승인을 거치며, 실행 중에는 토큰이 실시간으로 보이고, 1,000 에이전트 상한이 폭주를 끊는다. 강력한 도구일수록 기본값은 꺼짐이어야 하고, 지출은 관측 가능해야 하며, 최악의 경우는 상한으로 막혀 있어야 한다. 요금이 나가는 모든 시스템에 적용해 온 서킷 브레이커의 원칙이, 에이전트 시대에도 그대로 유효하다.

마무리: 위임의 다음 단계는 조율의 위임이다

한 문장으로 줄이면 이렇다. Sub-Agent가 "일을 위임"하는 단계였다면, Workflows는 "조율 자체를 위임"하는 단계다. 그리고 조율을 위임받는 주체가 또 다른 LLM이 아니라 결정론적 스크립트라는 점이, 이 설계에서 가장 마음에 드는 부분이다. 판단이 필요한 곳(에이전트)과 판단이 필요 없는 곳(제어 흐름)을 갈라서, 후자에서 LLM을 걷어냈다.

다음 액션은 정해뒀다. 먼저 우리 저장소의 PR 리뷰 절차를 "파일별 리뷰 → 발견별 적대적 검증 → 순위화된 요약" 워크플로우로 만들어 .claude/workflows/에 커밋한다. 팀원 누구나 /review-changes로 같은 품질 게이트를 돌리게 되는 것: 그게 이 기능이 약속하는 팀 단위의 가치이고, 세미나 다음 시리즈의 첫 주제로 이만한 것이 없다.