본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 02. 03:49

Claude Code의 워크플로우 작성: 활용 가능한 기능과 주의해야 할 점

요약

Anthropic의 Claude Code를 위한 Workflow API의 작동 원리와 구조를 분석합니다. 워크플로우는 직접 작업을 수행하는 대신 에이전트와 도구를 조율하는 오케스트레이션 계층 역할을 수행합니다.

핵심 포인트

  • Workflow API는 에이전트와 도구를 관리하는 오케스트레이션 계층임
  • 워크플로우는 격리된 node:vm 샌드박스 환경에서 실행됨
  • 워크플로우는 직접 파일 접근 대신 에이전트를 통해 작업을 수행함
  • 샌드박스 내에서도 현대적인 JavaScript 문법 사용이 가능함

Anthropic은 Claude Code를 위한 Workflow API를 도입했으며, 저는 이것이 실제로 어떻게 작동하는지 파헤치는 데 시간을 보냈습니다.

저는 에이전트(agents)가 어떻게 실행되는지, 단계 간에 결과를 어떻게 전달하는지, 언제 parallel이 적절한지, 언제 pipeline이 더 적합한지, 그리고 워크플로우가 기술(skills) 및 서브 에이전트(sub-agents)와 어떤 관계를 갖는지 살펴보았습니다.

요약하자면, 워크플로우를 사용하면 Claude Code의 작업을 하나의 긴 프롬프트(prompt)가 아닌 하나의 시나리오로 기술할 수 있습니다.

다음과 같은 방식이 아닙:

먼저 이것을 하고, 그다음 저것을 잊지 말고, 마지막으로 하나 더 확인하세요.

대신 실제 실행 가능한 프로세스로 정의합니다: 단계(phases), 에이전트(agents), 입력 데이터(input data), 응답 스키마(response schemas), 그리고 최종 합성(synthesis) 단계와 같은 방식입니다.

좋은 예시들을 살펴보기 전에, 한계점부터 시작하는 것이 가치가 있습니다. 이 한계점들은 처음 생각하는 것보다 더 중요하기 때문입니다.

샌드박스(sandbox)부터 시작하기

워크플로우 스크립트 자체는 프로젝트에 대한 전체 권한을 갖지 못합니다.

이는 격리된 node:vm 컨텍스트 내에서 실행됩니다. 다시 말해, 모듈을 임포트(import)하고, 파일을 읽고, API를 호출하며, 셸 명령어를 실행할 수 있는 일반적인 Node.js 애플리케이션이 아닙니다.

워크플로우 스크립트는 다음 항목에 직접 접근할 수 없습니다:

fs
process
require
...

import() 또한 일반적인 ESM 임포트처럼 동작하지 않습니다.

처음에는 이것이 이상하게 보일 수 있습니다. 만약 워크플로우가 파일을 읽거나 도구(tools)를 직접 호출할 수 없다면, 어떻게 코드 작성을 도울 수 있을까요?

답은 간단합니다. 워크플로우는 직접적인 작업을 수행하는 주체가 아닙니다.

에이전트(agents)가 바로 그 역할을 합니다.

에이전트는 현재 Claude Code 세션에서 해당 도구들을 사용할 수 있다는 가정하에, 파일을 읽고, 프로젝트를 검색하고, LSP를 사용하고, bash를 실행하고, MCP 도구를 호출하거나 웹 검색을 수행할 수 있습니다.

따라서 사고 모델(mental model)은 다음과 같습니다:

workflow는 프로세스를 오케스트레이션(orchestrates)합니다
agents는 작업을 수행합니다
tools는 agents에게 코드, 검색 및 외부 컨텍스트에 대한 접근 권한을 부여합니다

이것이 핵심 아이디어입니다.

워크플로우는 에이전트나 기술(skills)을 대체하는 것이 아닙니다. 그것들은 그 상위의 오케스트레이션 계층(orchestration layer)입니다.

샌드박스 내부의 JavaScript는 여전히 실제 JavaScript입니다

"샌드박스화(sandboxed)"되었다는 것을 "언어로서의 기능이 거의 없다"는 의미와 혼동하지 마세요.

워크플로우 내부에서도 여러분은 여전히 현대적인 JavaScript를 사용할 수 있습니다: 배열 (arrays), 객체 (objects), 프로미스 (promises), 클래스 (classes), 클로저 (closures), Map, Set, 재귀 (recursion), eval, new Function, 그리고 현대적인 V8 기능들을 말이죠.

예를 들어, 다음과 같은 코드는 작동합니다:

const makeScore = new Function("x", "return x.priority * 2")
const score = makeScore({ priority: 3 }) // 6

이것 또한 작동합니다:

const rule = "item.severity === 'high'"
const filter = new Function("item", "return " + rule)

...

이는 워크플로우 내부에서 작은 라우터 (router), 필터 (filter), 상태 머신 (state machine), 또는 동적인 결과 처리 로직 (dynamic result-processing logic)을 구축하고자 할 때 유용할 수 있습니다.

하지만 이 모든 것은 여전히 샌드박스 (sandbox) 내부에서 일어납니다.

evalnew Function은 호스트 환경 (host environment)에 대한 접근 권한을 부여하지 않습니다. 이를 사용하여 파일, 네트워크, 또는 Node API에 접근할 수 없습니다.

다음은 작동하지 않습니다:

const fs = require("fs") // require is not defined

또한, 다음과 같은 코드 역시 작동할 것이라고 기대해서는 안 됩니다:

const files = await fetch("https://example.com") // fetch is not defined

만약 파일을 읽거나, 프로젝트 내에서 심볼 (symbol)을 찾거나, 외부 서비스를 호출하거나, 명령어를 실행해야 한다면, 워크플로우는 agent()를 통해 해당 작업을 에이전트 (agent)에게 위임해야 합니다.

워크플로우 도입 이전의 방식

워크플로우가 도입되기 전, Claude Code에서의 복잡한 프로세스는 대개 스킬 (skills)과 하위 에이전트 (sub-agents)를 조합하여 구성되었습니다.

예를 들어, 코드 리뷰를 위한 스킬을 다음과 같이 작성할 수 있었습니다:

먼저, 작업을 이해하세요.
그 다음 관련 파일을 찾으세요.
그 다음 비즈니스 로직을 확인하세요.
...

이 방식은 작동합니다. 여전히 유용하기도 합니다.

스킬은 에이전트에게 규율 (discipline)을 부여합니다. 즉, 어떻게 검색할지, 어떻게 검증할지, 어떤 도구를 사용할지, 그리고 결과를 어떻게 형식화할지를 알려줍니다. 이는 팀 워크플로우에서 특히 중요합니다. 이러한 규칙이 없다면, 에이전트는 빠르게 그럴싸해 보이지만 내용은 부실한 답변을 내놓기 시작합니다.

문제는 스킬 자체가 나쁘다는 것이 아닙니다.

문제는 스킬이 여전히 텍스트 지시문 (text instruction)이라는 점입니다. 스킬은 모델에게 어떻게 행동해야 하는지를 알려주지만, 작업 순서 (order of operations)가 프로그램이 되지는 않습니다.

모델은 시퀀스 (sequence)를 컨텍스트 (context) 내에 유지해야 합니다: 먼저 파일을 찾고, 그다음 그것들을 검토하고, 그다음 증거를 수집한 뒤, 마지막으로 보고서를 작성해야 합니다. 작은 작업의 경우 이는 문제가 되지 않습니다. 하지만 더 긴 작업의 경우, 익숙한 문제들이 나타납니다:

  • 단계가 건너뛰어짐;
  • 검토 내용이 너무 일반적인 수준에 머무름;
  • 여러 에이전트 (agents)가 거의 동일한 답변을 반환함;
  • 의심스러운 발견 사항을 재확인하지 않음;
  • 모든 입력값이 준비되기 전에 최종 보고서가 작성됨.

워크플로우 (workflow)는 바로 이 계층을 다룹니다.

워크플로우는 에이전트에게 "주의해 주세요"라고 말하지 않습니다.

대신 시스템에 다음과 같이 명령합니다:

이것을 먼저 실행하고, 그다음 이것을 실행하며, 이 체크 항목들을 병렬 (parallel)로 실행한 뒤, 그제서야 결과를 합성 (synthesize)하세요.

서류상으로는 그 차이가 작아 보일 수 있지만, 실제로는 매우 중요합니다.

skills define rules (스킬은 규칙을 정의합니다)
agents execute tasks (에이전트는 작업을 실행합니다)
workflow controls the order (워크플로우는 순서를 제어합니다)

워크플로우란 무엇인가

Claude Code 워크플로우는 서브 에이전트 (sub-agents)를 오케스트레이션 (orchestrate)하는 JavaScript 파일입니다.

몇 가지 핵심 빌딩 블록 (building blocks)이 있습니다:

  • agent(): 에이전트를 시작합니다;
  • phase(): 작업의 단계를 표시합니다;
  • parallel(): 여러 작업을 실행하고 모두 완료될 때까지 기다립니다;
  • pipeline(): 항목들을 여러 단계를 거쳐 통과시킵니다;
  • schema: 예상되는 응답 형태를 정의합니다;
  • args: 입력 데이터를 워크플로우로 전달합니다.

단순화하자면, 워크플로우는 작은 백엔드 (backend) 프로세스처럼 느껴집니다.

차이점은 일반적인 함수를 호출하는 대신 에이전트를 실행한다는 것입니다.

스킬 (skill)에서는 다음과 같이 작성할 수 있습니다:

먼저 후보 파일을 찾으세요.
그다음 각 발견 사항을 확인하세요.
그다음 보고서를 작성하세요.

워크플로우에서는 이것이 실행 가능한 코드가 됩니다:

phase("Explore")

const candidates = await agent(
...

스킬에서 순서는 말로 설명됩니다.

워크플로우에서 순서는 실행됩니다.

워크플로우가 실제로 가치가 있는 경우

모든 요청이 워크플로우가 될 필요는 없습니다.

하나의 함수를 설명하거나, 하나의 테스트를 수정하거나, 무언가가 정의된 위치를 빠르게 찾아야 하는 경우라면 일반적인 Claude Code로도 충분합니다.

워크플로우는 반복 가능한 시퀀스 (sequence)가 있을 때 유용해집니다.

예를 들어, Pull Request (PR)를 생성하기 전에 항상 Claude Code에게 다음과 같은 요청을 할 수 있습니다:

  1. 변경 사항 파악하기;
  2. 코드베이스의 관련 부분 찾기;
  3. 비즈니스 로직 (business logic) 검토하기;
  4. 테스트 검토하기;
  5. 보안 리스크 (security risks) 확인하기;
  6. 취약하거나 지원되지 않는 결과 제거하기;
  7. 짧은 보고서 생성하기.

채팅 모드에서는 이것이 빠르게 수동 오케스트레이션 (manual orchestration)으로 변질됩니다. 모델에게 다음에 무엇을 해야 할지 계속해서 상기시켜야 합니다. 모델은 어떤 것은 기억하지만 다른 것은 놓치며, 그러면 당신은 모델에게 결과를 다시 확인하거나 요약해 달라고 요청하게 됩니다.

워크플로우 (workflow)를 사용하면 이러한 순서를 단 한 번만 작성하면 됩니다.

워크플로우와 스킬 (skills)의 관계

여기에는 중요한 뉘앙스가 있습니다.

워크플로우 스크립트 자체가 스킬 (skills)로부터 규칙을 자동으로 상속받지는 않습니다. 스킬은 보통 agentType을 통해 특정 에이전트 (agent)의 컨텍스트 (context)로 로드됩니다.

만약 agentType 없이 일반적인 agent()를 호출한다면, 당신이 기대했던 프로젝트 전용 규칙을 받지 못할 수도 있습니다.

예를 들어, 당신의 팀이 코드베이스를 검색하는 방식(먼저 LSP를 사용하고, 그다음 grep을 사용하며, 증거가 있을 때만 주장하고, 항상 경로와 줄 번호를 반환함)을 알고 있는 에이전트가 있다고 가정해 봅시다.

이 경우, 해당 에이전트 타입을 명시적으로 선택하는 것이 좋습니다:

await agent(
  "이 심볼이 어디에서 사용되는지 찾으세요. grep 전에 LSP를 사용하세요. 경로와 줄 번호를 반환하세요.",
  {...

그렇지 않으면, 작업은 이해하지만 당신이 의존하는 정확한 워크플로우 규율 (workflow discipline)을 따르지 않는 일반적인 에이전트를 받게 될 수 있습니다.

좋은 경험칙 (rule of thumb)은 다음과 같습니다:

단계의 규율이 중요하다면, 마법에 의존하지 마세요. 올바른 agentType을 사용하거나 프롬프트 (prompt)에 규칙을 직접 작성하세요.

workflow는 순서를 제어합니다
skill은 에이전트의 동작을 제어합니다
agent는 구체적인 단계를 수행합니다

코드가 아닌 프로세스로 시작하세요

가장 흔한 실수는 즉시 JavaScript 작성을 시작하는 것입니다.

보통은 일반 텍스트로 시작하는 것이 더 좋습니다.

예를 들어:

작업: 기능이 Pull Request를 위한 준비가 되었는지 확인합니다.

1. 어떤 영역이 영향을 받았는지 파악합니다.
...

그 이후의 코딩은 거의 기계적인 작업이 됩니다.

당신은 이미 단계 (phases)를 가지고 있습니다:

Scope → Review → Synthesize

이제 당신은 단일 에이전트(agent)만으로 충분한 곳이 어디인지, 여러 에이전트를 병렬로 실행해야 하는 곳이 어디인지, 그리고 최종 합성(synthesis) 단계가 필요한 곳이 어디인지만 결정하면 됩니다.

최소한의 워크플로우 (A minimal workflow)

워크플로우 파일은 meta로 시작합니다.

이는 Claude Code에게 워크플로우를 설명합니다:

export const meta = {
  name: "feature-review",
  description: "Review a feature before opening a pull request.",
...

meta는 단순한 객체(object)여야 합니다. 변수, 함수, 스프레드 연산자(spread operators) 또는 계산된 값(computed values)을 포함해서는 안 됩니다. 런타임(runtime)이 이를 정적으로 읽기 때문입니다.

그 이후에는 일반적인 JavaScript를 작성할 수 있습니다:

const A = typeof args === "string" ? JSON.parse(args) : (args || {})
const task = A.task || ""

...

여기에는 짜증스럽지만 중요한 세부 사항이 하나 있습니다: args가 문자열(string)로 전달될 수 있다는 점입니다. 설령 객체(object)를 전달했더라도, JSON.parse를 사용하여 파싱하는 것이 더 안전합니다.

이 과정을 생략하면, 왜 A.task가 비어 있는지 파악하느라 너무 많은 시간을 허비하게 될 수 있습니다.

첫 번째 에이전트 (The first agent)

에이전트(agent)는 하나의 구체적인 단계를 수행합니다.

phase("Scope")

const scope = await agent(
...

여기서 일어나는 일은 다음과 같습니다:

  1. phase("Scope")가 해당 단계를 엽니다.
  2. agent()가 서브 에이전트(sub-agent)를 시작합니다.
  3. 결과가 scope에 저장됩니다.

지금까지는 일반적인 프롬프트(prompt)처럼 보입니다. 차이점은 이제 그 결과를 코드의 나머지 부분에서 사용할 수 있다는 것입니다.

스키마(schemas)가 없으면 삶이 고달파지는 이유

schema가 없으면 에이전트는 텍스트(text)를 반환합니다.

텍스트는 사람이 보기에는 괜찮지만, 워크플로우의 다음 단계에서는 불편합니다. 필터링, 병합, 검증 및 변환(transform)이 더 어렵기 때문입니다.

이것이 대부분의 워크플로우에서 응답 스키마(response schemas)를 초기에 정의해야 하는 이유입니다.

예를 들어, 리뷰 영역(review areas)의 목록을 원한다고 가정해 봅시다:

const SCOPE_SCHEMA = {
  type: "object",
  required: ["areas"],
...

이제 에이전트 호출 시 해당 스키마를 사용합니다:

const scope = await agent(
  "Identify review areas for this task.\n\nTask:\n" + task,
  {...

이제 scope.areas는 데이터(data)입니다. 정규 표현식(regexes)을 사용하여 추측하며 헤매야 하는 텍스트 블록이 아닙니다.

병렬 체크 (Parallel checks)

첫 번째 에이전트가 다음 세 영역을 반환한다고 가정해 봅시다:

logic
tests
security

여러분은 이를 병렬로 검토할 수 있습니다:

phase("Review")

const reviews = await parallel(
...

한 가지 중요한 세부 사항은, 이미 시작된 프로미스(promises)가 아니라 함수를 parallel()에 전달해야 한다는 점입니다.

올바른 예:

parallel([
  () => agent("Check tests"),
  () => agent("Check security")
...

잘못된 예:

parallel([
  agent("Check tests"),
  agent("Check security")
...

parallel()은 배리어(barrier) 역할을 합니다. 여러 작업을 시작하고 그 작업들이 모두 완료될 때까지 기다립니다.

이는 다음 단계에서 전체 결과 세트를 확인해야 할 때 유용합니다.

파이프라인(pipeline)이 필요한 경우

pipeline()은 다른 문제를 해결합니다.

파일 목록이 있다고 상상해 보세요. 각 파일은 먼저 분석되어야 하고 그 후에 검증되어야 합니다. 하지만 첫 번째 파일에 대한 검증을 시작하기 전에 모든 파일의 분석이 끝날 때까지 기다릴 필요는 없습니다.

그것이 바로 pipeline()의 용도입니다:

const results = await pipeline(
  files,

...

요약하자면:

parallel = 여러 작업을 시작하고 모두 완료될 때까지 기다림
pipeline = 각 항목을 여러 단계에 걸쳐 이동시킴

모든 체크가 완료된 후 하나의 통합된 보고서가 필요한 경우에는 parallel을 사용하세요.

각 파일, 모듈, 티켓 또는 항목이 동일한 단계의 체인을 거쳐야 하는 경우에는 pipeline을 사용하세요.

워크플로우는 파일을 읽지 않으며, 이는 정상입니다

이 부분은 잊기 쉽기 때문에 다시 한번 강조할 가치가 있습니다.

워크플로우 스크립트는 '손' 역할을 해서는 안 됩니다.

워크플로우는 파일을 읽지 않습니다. 네트워크를 호출하지 않습니다. 셸 명령(shell commands)을 실행하지 않습니다.

잘못된 사고 모델:

// 이제 워크플로우가 직접 파일을 읽고 변경 사항을 찾을 것입니다

대신 다음과 같이 생각하세요:

await agent(
  "변경된 파일을 읽고 위험한 로직을 찾으세요. 파일 경로와 줄 번호를 인용하세요."
)

에이전트가 도구(tools)를 사용할 수 있다면, 에이전트는 파일을 읽고, 프로젝트를 검색하고, LSP를 사용하거나 명령을 실행할 수 있습니다.

워크플로우는 직접 작업을 수행하지 않습니다.

워크플로우는 에이전트에게 작업을 할당합니다.

최종 종합

모든 에이전트의 가공되지 않은 출력(raw outputs)을 사용자에게 직접 반환하지 마십시오.

그러한 출력에는 거의 항상 반복, 일관되지 않은 스타일, 그리고 불필요한 세부 정보가 포함됩니다. 별도의 합성(synthesis) 단계를 거치면 보통 훨씬 더 나은 결과를 얻을 수 있습니다:

phase("Synthesize")

const report = await agent(
...

훌륭한 최종 보고서는 다음 네 가지 질문에 답해야 합니다:

무엇이 발견되었는가 (what was found)
왜 그것이 중요한가 (why it matters)
증거가 어디에 있는가 (where the evidence is)
...

완전한 스타터 템플릿 (starter template)

여기서부터 시작할 수 있는 템플릿이 있습니다. 프롬프트(prompts)는 귀하의 프로젝트와 워크플로우에 맞게 조정되어야 합니다.

export const meta = {
  name: "custom-review",
  description: "구조화된 검토를 실행하고 실행 가능한 결과(actionable findings)를 반환합니다.",
...

agentType을 추가하는 방법

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0