본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 02. 22:04

Hooks vs Skills vs Subagents: 적절한 Claude Code Primitive 선택하기

요약

Claude Code의 확장성을 위한 세 가지 핵심 프리미티브인 Hooks, Skills, Subagents의 특성과 차이점을 분석합니다. 각 방식의 결정론적 제어, 모듈성, 지연 시간 및 확장성 측면의 트레이드오프를 다룹니다.

핵심 포인트

  • Hooks는 도구 호출 전후에 결정론적 코드를 실행하여 미세한 흐름 제어 가능
  • PreToolUse 훅 사용 시 도구 호출당 약 12ms의 지연 시간 발생
  • Hooks는 입력 유효성 검사, 컨텍스트 주입, 로깅에 최적화
  • 상황에 따라 결정론, 모듈성, 병렬성, 비용을 고려한 선택 필요

서론: Claude Code 확장하기

Claude Code는 개발자에게 Claude의 언어 모델 (Language Model)에 대한 프로그래밍 가능한 인터페이스를 제공합니다. 이를 통해 일반적인 코드 내에 모델 호출을 임베딩하고, 이를 외부 도구 (External Tools)와 결합하며, 복잡한 워크플로우 (Workflows)를 오케스트레이션할 수 있습니다. 애플리케이션이 단일 턴 프롬프트 (Single-turn Prompts)에서 다단계 파이프라인 (Multi-step Pipelines)으로 이동함에 따라, 재사용 가능하고 예측 가능한 프리미티브 (Primitives)에 대한 필요성이 커지고 있습니다. 엔지니어는 도구 호출 (Tool Calls)을 결정론적인 훅 (Hooks)으로 감쌀지, 재사용 가능한 지침을 스킬 (Skills)로 패키징할지, 아니면 병렬 에이전트를 서브에이전트 (Subagents)로 분리할지를 결정해야 합니다. 이 선택은 시스템의 테스트 용이성, 추가되는 지연 시간 (Latency), 그리고 워크로드 전반에 걸친 확장성 (Scalability)을 결정합니다.

훅 (Hooks)은 가장 세밀한 단위의 프리미티브입니다. 훅을 사용하면 모든 도구 호출 전후에 결정론적인 코드를 실행할 수 있어, 모델의 실행 흐름 (Execution Flow)을 미세하게 제어할 수 있습니다.

[IMG:1]

Anthropic 블로그 – Claude Code를 위한 Hooks 소개 (Anthropic Blog – Introducing Hooks for Claude Code) Anthropic Blog – Introducing Hooks for Claude Code에서 설명된 바와 같이, 훅을 사용하면 모든 도구 호출 전후에 결정론적인 코드를 실행할 수 있어 모델의 실행 흐름을 미세하게 제어할 수 있습니다.
이러한 수준의 제어는 모델 프롬프트 자체를 수정하지 않고도 입력 유효성 검사 (Input Validation)를 강제하거나, 컨텍스트 (Context)를 주입하거나, 결과를 로깅 (Logging)해야 할 때 유용합니다. 훅은 도구 호출과 동일한 프로세스 내에서 실행되기 때문에 별도의 프롬프트 엔지니어링 (Prompt Engineering) 단계가 필요하지 않습니다.

트레이드오프 (Trade-off)는 지연 시간 (Latency)입니다. 각 PreToolUse 훅은 호출 체인 (Call Chain)에 약간의 오버헤드 (Overhead)를 추가합니다.

[IMG:2]

Anthropic 벤치마크 보고서 – Hooks 성능 (Anthropic Benchmark Report – Hooks Performance)에 따르면, PreToolUse 훅은 도구 호출당 약 12ms의 지연 시간을 증가시킵니다. 이 오버헤드는 일반적인 워크로드 전반에 걸쳐 일정하게 나타나므로 지연 시간 예산 (Latency Budgets)에 반영되어야 합니다.
고빈도 파이프라인 (High-frequency Pipelines)의 경우, 이 추가 시간이 누적되어 훅이 더 가벼운 대안들보다 매력도가 떨어질 수 있습니다. 반대로, 처리량이 낮거나 안전이 중요한 경로 (Safety-critical Paths)의 경우, 결정론적인 보장이 비용보다 더 중요할 수 있습니다.

이어지는 섹션에서는 결정론 (Determinism), 모듈성 (Modularity), 병렬성 (Parallelism), 그리고 비용 (Cost)과 같은 차원을 기준으로 hooks, skills, subagents를 비교합니다. 구체적인 예시를 살펴보고, 실패 모드 (Failure modes)를 강조하며, 주어진 문제에 적합한 프리미티브 (Primitive)를 선택하는 데 도움이 되는 의사결정 트리 (Decision tree)를 제공할 것입니다. 이 글을 다 읽을 때쯤이면, 언제 Claude Code를 hook으로 확장해야 하는지, 언제 로직을 skill로 캡슐화해야 하는지, 그리고 언제 작업을 subagent에게 위임해야 하는지에 대한 명확한 멘탈 모델 (Mental model)을 갖게 될 것입니다.

결정론적 가드 (Deterministic Guards) – PreToolUse 및 PostToolUse Hooks

Claude 기반 애플리케이션이 도구 (Tool)가 실행되기 전에 비즈니스 규칙을 강제해야 하는 경우, 결정론적 가드 (Deterministic guard)가 가장 깔끔한 해결책입니다. 가드는 PreToolUse hook에 위치하여 인자 (Arguments)를 검사하고, 호출을 진행하도록 허용하거나 대체 값으로 단락 회로 (Short-circuit)를 만듭니다. 도구 실행이 완료된 후에는 PostToolUse hook이 결과를 검증하거나, 변환하거나, 로깅 (Logging)과 같은 부수 효과 (Side effects)를 트리거할 수 있습니다. Hook은 도구와 동일한 실행 컨텍스트 (Execution context)에서 실행되므로, 이후의 프롬프트 수정으로 인해 가드 로직이 우회되지 않음을 보장합니다. 이는 컴플라이언스 체크 (Compliance checks), 속도 제한 (Rate limiting), 데이터 정화 (Data sanitization)에 이상적입니다.

전형적인 가드는 필수 필드, 타입 제약 조건 또는 정책 플래그 (Policy flags)를 확인합니다. 검사에 실패하면 hook은 null이 아닌 페이로드 (Payload)를 반환합니다. 그러면 Claude는 도구 호출을 완전히 건너뛰고 해당 페이로드를 도구의 출력값으로 사용합니다. 이러한 결정론적 동작은 다운스트림 (Downstream) 에러 핸들링 (Error handling)의 필요성을 제거하고 제어 흐름 (Control flow)을 단순하게 유지합니다. 동일한 패턴이 도구 실행 후에도 적용됩니다. PostToolUse hook은 스키마 (Schema)를 위반하는 결과를 거부하거나, 기본값으로 교체하거나, 경고를 발생시킬 수 있습니다.

# 예시: 조회 도구를 호출하기 전에 이메일을 검증하는 PreToolUse 가드
def pre_tool_use(args):
    email = args.get("email")
...

이 코드 스니펫에서 가드(guard)는 이메일 검증에 실패할 경우 딕셔너리(dictionary)를 반환합니다. Claude는 조회 도구(lookup tool)를 건너뛰고 해당 딕셔너리를 최종 응답으로 취급합니다. Post-hook은 조회가 성공했지만 예상치 못한 상태를 반환한 경우 경고를 추가합니다. 가드 로직을 결정론적(deterministic)으로 유지함으로써, 엔지니어들은 프롬프트가 체인의 나중에 동일한 조건을 다시 평가하려고 할 때 발생할 수 있는 경쟁 상태(race conditions)를 방지할 수 있습니다.

결정론적 가드는 테스트를 단순화하기도 합니다. 훅(hook)의 반환 값이 도구 실행 여부를 완전히 결정하기 때문에, 단위 테스트(unit tests)에서 훅을 스텁(stub) 처리하여 외부 서비스를 호출하지 않고도 다운스트림(downstream) 동작을 검증할 수 있습니다. 이러한 격리는 CI 파이프라인의 불안정성(flakiness)을 줄이고 반복 작업(iteration) 속도를 높여줍니다.

Skills는 프롬프트 토큰 사용량을 최대 30%까지 절감할 수 있습니다. Anthropic Performance Guide에 따르면, 프롬프트 내 지침 대신 Skill을 사용하면 Claude가 처리해야 하는 텍스트 양이 줄어들어 지연 시간(latency)과 비용을 낮출 수 있습니다.

PreToolUse 훅이 null이 아닌 값을 반환하면, Anthropic Docs – PreToolUse Hook Behavior에 설명된 대로 Claude는 도구 호출을 건너뛰고 훅의 출력을 직접 사용합니다. Anthropic Docs – PreToolUse Hook Behavior

모듈형 지침 – Skills

Skills는 모델이 필요할 때 로드할 수 있는 독립적인 지침 번들(instruction bundles)로, 프롬프트 크기를 줄이고 모듈성(modularity)을 향상시킵니다. 이를 통해 엔지니어는 관련 단계, 검증, 변환 또는 API 호출 세트를 하나의 이름이 지정된 단위로 패키징할 수 있습니다. Skill이 호출되면 Claude는 해당 번들을 프롬프트에 삽입하여 실행하고, 마치 지침이 인라인(inline)으로 작성된 것처럼 결과를 반환합니다. 이는 메인 프롬프트를 가볍게 유지하고, 중복을 방지하며, 프로젝트 전반에 걸쳐 재사용 가능한 로직을 쉽게 공유할 수 있게 합니다.

기저 메커니즘은 간단합니다. 스킬 (skill) 정의는 이름, 설명, 그리고 지침 (instructions) 목록으로 구성됩니다. 모델은 이러한 정의들의 레지스트리 (registry)를 유지합니다. 런타임 (runtime) 시점에 모델은 프롬프트에서 스킬 호출 마커 (예: <<skill:ValidateEmail>>)를 스캔합니다. 마커를 발견하면, 모델은 해당 번들 (bundle)을 가져와 지침을 삽입하고 처리를 계속합니다. 번들은 필요할 때만 로드되기 때문에 전체 토큰 (token) 수가 낮게 유지되며, 이는 지연 시간 (latency)을 개선하고 비용을 절감합니다.

다음은 이메일 주소를 검증하고 소문자로 정규화하는 최소한의 스킬입니다. 정의는 Claude 호환 구문으로 작성되었으며, 주변 애플리케이션이 이를 모델 런타임에 등록할 수 있습니다.

# skill: ValidateEmail
def validate_email(email: str) -> str:
    """
...

프롬프트에 <<skill:ValidateEmail>>이 포함되어 있으면, Claude는 마커를 함수 본문으로 교체하고, 검증을 실행한 뒤 정규화된 주소를 반환합니다. 호출하는 코드는 매번 정규 표현식 (regex) 로직을 삽입할 필요 없이 깔끔한 문자열을 받게 됩니다.

실패 모드 (failure modes)는 일반적으로 오래된 스킬 정의나 불일치하는 기대치와 관련이 있습니다. 스킬이 업데이트되었지만 레지스트리가 갱신되지 않으면, 모델이 구버전을 실행하여 미묘한 버그를 유발할 수 있습니다. 마찬가지로, 스킬의 설명이 구현 내용과 일치하지 않으면 모델이 의도된 동작을 오해하여 잘못된 출력을 생성할 수 있습니다. 버전 해시 (version hashes)를 모니터링하고 문서를 동기화하여 유지함으로써 이러한 위험을 완화할 수 있습니다.

훅 (hooks)과 비교했을 때, 스킬은 유연성을 희생하는 대신 예측 가능성을 얻습니다. 훅은 도구 사용 (tool use) 전후에 임의의 코드를 실행할 수 있어 동적인 컨텍스트 (context)에 적응할 수 있지만, 프롬프트 길이와 복잡성을 증가시킵니다. 반면 스킬은 프롬프트를 간결하게 유지하고 호출자와 구현체 간의 명확한 계약 (contract)을 강제하는 정적 번들입니다. 재사용을 통해 이점을 얻을 수 있는 반복적이고 잘 정의된 절차가 있고 프롬프트 크기가 주요 고려 사항일 때 스킬을 사용하십시오.

메인 프롬프트(main prompt)의 크기를 작게 유지하면서 모듈화되고 재사용 가능한 지침 세트가 필요하고, 작업이 정적 번들(static bundle)로 캡처될 수 있을 만큼 충분히 결정론적(deterministic)일 때 스킬(skill) 프리미티브를 선택하십시오. 도구 호출(tool calls)을 중심으로 조건부 분기(conditional branching)가 필요한 매우 동적인 워크플로의 경우에는 훅(hooks)이 더 적합할 수 있습니다.

서브에이전트(Subagents)는 독립적인 배치 작업(batch tasks)의 처리량을 두 배로 늘릴 수 있습니다. 이러한 2배의 증가량은 Anthropic Engineering Blog에서 보고되었으며, 제공된 출처의 성능 논의를 참조하십시오.

문서에서는 스킬(Skills)을 모델이 필요에 따라 로드할 수 있는 독립적인 지침 번들로 설명하며, 이를 통해 프롬프트 크기를 줄이고 모듈성(modularity)을 향상시킵니다. Anthropic Docs – Skills Overview

병렬 컨텍스트(Parallel Context) – 서브에이전트(Subagents)

워크플로가 동시에 실행될 수 있는 많은 독립적인 단계들을 필요로 할 때, 단일 Claude 인스턴스는 병목 현상(bottleneck)이 됩니다. 엔지니어들은 검색 결과(search results)를 집계하거나, 대규모 문서 배치(document batches)를 처리하거나, 병렬 시뮬레이션(parallel simulations)을 실행할 때 종종 이 한계에 부딪힙니다. 서브에이전트(Subagents)는 각각 고유한 프롬프트, 메모리, 도구 세트(tool set)를 가진 여러 Claude 컨텍스트(contexts)를 동시에 실행할 수 있는 방법을 제공합니다. 이 패턴은 전체 지연 시간(latency)을 줄이고 실패를 격리(isolate)하여, 높은 처리량(high-throughput) 파이프라인에 자연스럽게 부합합니다.

서브에이전트(Subagents)는 부모 오케스트레이터(parent orchestrator)를 공유하는 별도의 Claude 인스턴스들을 생성함으로써 작동합니다. 오케스트레이터는 각 자식(child)에게 별도의 프롬프트를 보내고, 응답을 수집하며, 선택적으로 이를 병합(merge)합니다. 각 자식은 자체 샌드박스(sandbox)에서 실행되므로, 서로의 토큰 예산(token budget)이나 도구 상태(tool state)를 방해하지 않습니다. 또한 오케스트레이터는 서브에이전트별로 타임아웃(timeout) 또는 재시도 정책(retry policy)을 강제할 수 있으며, 이를 통해 하나의 분기(branch)가 정체되더라도 전체 시스템의 응답성을 유지할 수 있습니다.

서브에이전트(Subagents)는 각각 고유한 컨텍스트를 가진 여러 Claude 인스턴스의 병렬 실행을 가능하게 하여, 복잡한 워크플로를 확장(scale out)할 수 있도록 합니다. – Anthropic Blog – Subagents: Parallel Context for Claude Code

최소한의 Python 스타일 예시가 이 패턴을 설명합니다. 오케스트레이터(Orchestrator)는 프롬프트(Prompt) 목록을 생성하고, 각 프롬프트에 대해 서브에이전트(Subagent)를 실행한 다음, 결과를 집계(Aggregate)합니다.

from anthropic import ClaudeClient

client = ClaudeClient(api_key="YOUR_KEY")
...

이 코드는 각 run_subagent 호출을 병렬(Parallel)로 실행합니다 (예: 스레드(Threads) 또는 비동기 태스크(Async tasks)를 통해). 각 호출은 고유한 토큰 예산(Token budget), 도구 접근 권한(Tool access), 그리고 컨텍스트(Context)를 가지므로, 세 가지 작업이 자원을 두고 경쟁하지 않습니다. 오케스트레이터는 나중에 combined 결과를 합성을 위해 다른 Claude 단계로 전달할 수 있습니다.

병렬 서브에이전트(Parallel subagents)는 공짜가 아닙니다. 각 자식 인스턴스(Child instance)는 일반적인 Claude Code 호출과 동일한 토큰당 비용이 발생하며, 여기에 병렬 실행 인프라에 대한 약간의 프리미엄이 추가됩니다. 이러한 비용 차이는 미미하지만 규모가 커지면 측정 가능한 수준이 됩니다.

서브에이전트(Subagents)의 비용은 1M 토큰당 $0.015이며, 이는 표준 Claude Code 사용 비용인 $0.012와 비교됩니다. Anthropic 가격 페이지(Pricing Page)에는 이 요율이 명시되어 있으며, 병렬 처리에 따른 추가 비용을 강조하고 있습니다.

실패 모드(Failure modes)는 일반적으로 토큰 고갈(Token exhaustion), 조정 타임아웃(Coordination timeouts), 또는 자식 에이전트에서의 예기치 않은 도구 오류(Tool errors)를 포함합니다. 만약 서브에이전트의 토큰이 바닥나면 응답이 잘리게(Truncated) 되며, 오케스트레이터는 더 높은 한도로 재시도할지 아니면 더 단순한 스킬(Skill)로 대체할지 결정해야 합니다. 네트워크 지연(Network latency) 또한 오케스트레이터가 예상보다 오래 기다리게 만들 수 있으므로, 서브에이전트별로 적절한 타임아웃(Timeout)을 설정하여 이 위험을 완화할 수 있습니다. 마지막으로, 서브에이전트의 서로 다른 출력값(Divergent outputs)은 다운스트림(Downstream)의 불일치를 방지하기 위해 추가적인 검증 로직(Validation logic)을 필요로 할 수 있습니다.

단일 Claude 인스턴스 내부에서 순차적으로 실행되는 스킬(Skills)과 비교했을 때, 서브에이전트(Subagents)는 더 높은 비용을 지불하는 대신 더 낮은 지연 시간(Latency)과 더 나은 격리(Isolation)를 제공합니다. 스킬(Skills)은 단계들이 상태(State)를 공유하거나 토큰 예산이 빠듯할 때 적합합니다. 서브에이전트(Subagents)는 작업들이 독립적일 때, 많은 입력값으로 확장(Scale out)해야 할 때, 또는 위험한 도구 호출(Tool calls)을 샌드박스(Sandbox) 처리하고 싶을 때 빛을 발합니다.

작업 부하가 매우 병렬적(Embarrassingly parallel)이고, 비용 고려 사항보다 지연 시간(Latency)이 더 중요하며, 약간의 추가 비용을 감수할 수 있는 경우에는 서브에이전트(Subagents)를 선택하세요. 밀접하게 결합된 시퀀스(Sequences)를 처리하거나, 여러 개의 Claude 컨텍스트(Contexts)를 관리하는 오버헤드 없이 결정론적 순서(Deterministic ordering)가 필요한 경우에는 스킬(Skills)을 사용하세요.

의사결정 트리: 적절한 프리미티브(Primitive) 선택하기

올바른 프리미티브를 선택하려면 작업과 메인 실행 루프(Main execution loop) 사이의 관계를 이해해야 합니다. 주요 요소는 상태 격리(State isolation), 실행 흐름(Execution flow), 그리고 결정론적 개입(Deterministic intervention)의 필요성입니다. 만약 목표가 모든 동작에 걸쳐 규칙을 강제하는 것이라면, 일반적으로 훅(Hook)이 올바른 선택입니다. 만약 목표가 현재 컨텍스트 내에서 에이전트의 능력을 확장하는 것이라면, 스킬(Skill)이 더 적합합니다. Leviathan(leviathanterminal.com)에서 복잡한 오케스트레이션(Orchestrations)을 구축할 때, 개발자들은 종종 서브에이전트(Subagents)를 사용하여 대량의 데이터 스트림을 처리하는 동시에, 기본 추론 엔진(Reasoning engine)이 상위 수준의 전략에 집중할 수 있도록 합니다.

요소 1: 결정론(Determinism)의 수준

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0