Slash Command는 단축키가 아니라 Runtime Dispatch다
메뉴
Moonshot Notes orbit notebook mark
Moonshot NotesAI 도구와 개발 워크플로우 기록하는 공간

AI Agent

Slash Command는 단축키가 아니라 Runtime Dispatch다

Claude Code CLI 분석을 바탕으로 slash command를 prompt 생성, local 실행, interactive UI, forked execution으로 나누는 runtime dispatch 패턴을 설명합니다.

Slash Command는 단축키가 아니라 Runtime Dispatch다 hero image
Markdown약 1808 tokens

핵심 요약

  • Agent의 command는 문자열 shortcut이 아니라 side effect를 가진 runtime dispatch다.
  • command는 모델 prompt를 만들 수도 있고, local action을 실행할 수도 있고, UI overlay를 열 수도 있다.
  • built-in, user-defined, plugin command를 합칠 때 availability와 conflict resolution이 필요하다.
  • command 결과는 input normalization 계층의 공통 반환 타입으로 맞춰져야 한다.

이번 글에서는 slash command를 보겠습니다. Claude Code 같은 CLI agent에서 command는 단순한 단축키가 아닙니다.

/help, /review, /status 같은 입력은 모두 같은 입력창에서 시작하지만 실행 방식은 다릅니다. 어떤 command는 모델 prompt를 생성하고, 어떤 command는 로컬 상태만 보여주며, 어떤 command는 설정 UI를 열고, 어떤 command는 별도 agent 작업으로 분기할 수 있습니다.

그래서 command system은 parser가 아니라 runtime dispatch 계층입니다.

1. Command를 shortcut으로 보면 안 되는 이유

일반 CLI에서 command는 기능 호출입니다. 하지만 AI agent에서 command는 모델 루프와도 연결됩니다. 예를 들어 “코드 리뷰해줘”라는 slash command는 로컬에서 끝나는 기능이 아니라, 현재 diff를 읽고 리뷰용 prompt를 만들어 모델에게 보내는 흐름일 수 있습니다.

반대로 “상태 보여줘” 같은 command는 모델을 부를 필요가 없습니다. runtime 상태를 화면에 보여주면 됩니다.

이 둘을 같은 callback으로 처리하면 문제가 생깁니다.

command모델 호출side effect주의점
help아니오UI 출력model context 오염 방지
reviewcontext 수집prompt 생성 범위 명확화
settings아니오overlay openqueue 처리와 충돌 방지
spawn job경우에 따라 예별도 실행권한과 transcript 분리

2. 네 가지 command 실행 타입

command는 최소 네 종류로 나누는 편이 좋습니다.

타입설명예시
prompt commandcommand를 model-visible message로 변환리뷰, 요약, 테스트 계획
local command모델 없이 로컬 상태 변경 또는 출력help, clear, status
interactive commandoverlay나 선택 UI를 열고 사용자 응답을 기다림settings, model select
forked command현재 세션 일부를 복사해 별도 agent 작업 실행background review, isolated analysis

이 타입은 단순 label이 아니라 runtime policy와 연결되어야 합니다. 예를 들어 prompt command는 allowed tools를 제한할 수 있고, forked command는 별도 transcript를 만들어야 할 수 있습니다.

3. Command registry와 availability

제품형 agent에는 built-in command만 있지 않습니다. 사용자 정의 command, 프로젝트 command, plugin command, workflow command가 들어올 수 있습니다.

registry는 단순 배열이 아니라 다음 책임을 가져야 합니다.

  • 같은 이름의 command 충돌 해결
  • 현재 mode에서 사용 가능한 command만 노출
  • 권한 정책에 따라 command 숨김 또는 제한
  • plugin 실패 시 기본 command 유지
  • command 설명과 인자 schema 제공

주의:
프로젝트 내부 command를 무조건 신뢰하면 위험합니다. command가 prompt template만 만드는지, local side effect를 실행하는지, 외부 도구를 호출하는지 구분해야 합니다.

4. Prompt command와 local command의 차이

prompt command는 모델에게 보낼 message를 만듭니다. local command는 화면이나 local state만 바꿉니다. 이 둘을 구분하지 않으면 사용자의 /status 결과가 모델 context에 들어가거나, 반대로 /review가 모델 호출 없이 끝나는 문제가 생깁니다.

# 읽는 법: 아래 항목은 동작 흐름을 빠르게 확인하기 위한 요약 예시입니다.slash text→ command registry lookup→ availability check→ command kind 확인→ prompt/local/interactive/forked 경로 선택→ PreparedTurn 또는 RuntimeEvent로 정규화

5. Forked command의 context 절단

forked command는 특히 조심해야 합니다. 현재 세션 전체를 그대로 복사해 별도 agent 작업으로 넘기면 context 오염과 권한 혼선이 생길 수 있습니다.

별도 실행에는 필요한 것만 넘겨야 합니다.

넘길 수 있는 것조심해야 하는 것
필요한 message subset전체 transcript 무조건 복사
workspace metadata민감한 local state
허용 도구 목록현재 세션의 임시 승인 상태
cancellation policyinteractive overlay state

6. 개념 코드로 보는 dispatch

아래 코드는 원본 구현이 아닌 설명용 코드입니다.

# 읽는 법: 실제 구현 복제가 아니라 runtime 경계를 설명하는 개념 코드입니다.class CommandSpec:    # 객체가 이후 단계에서 참조할 runtime 의존성과 상태 저장소를 초기화합니다.    def __init__(self, name, kind, available, handler):        self.name = name        self.kind = kind        self.available = available        self.handler = handler # slash text를 command registry에서 찾아 local 실행이나 model message 생성으로 보냅니다.async def dispatch_command(text, runtime):    parsed = parse_slash_text(text)    if parsed is None:        return None     spec = runtime.command_registry.find(parsed.name)    if spec is None:        return make_local_notice(f"알 수 없는 command: {parsed.name}")     if not spec.available(runtime.context):        return make_local_notice("현재 모드에서 사용할 수 없는 command입니다.")     if spec.kind == "prompt":        message_bundle = await spec.handler.build_messages(parsed.args, runtime)        return PreparedTurn.from_messages(message_bundle)     if spec.kind == "local":        event = await spec.handler.run_local(parsed.args, runtime)        return PreparedTurn.local_only(event)     if spec.kind == "interactive":        overlay = await spec.handler.open_panel(parsed.args, runtime)        return PreparedTurn.local_only(overlay)     if spec.kind == "forked":        job = await start_isolated_agent_job(spec, parsed.args, runtime)        return PreparedTurn.local_only(make_job_event(job))

핵심은 command가 실행된 뒤에도 downstream이 이해할 수 있는 형태로 정규화된다는 점입니다.

7. AI 활용 개발자 관점

AI 도구를 사용할 때 command가 어떤 타입인지 알 수 있으면 좋습니다.

  • 이 command가 모델을 호출하는가?
  • 파일이나 shell에 side effect가 있는가?
  • 현재 대화의 context를 얼마나 사용하나?
  • command 실행 결과가 transcript에 남는가?
  • plugin command인지 built-in command인지 표시되는가?

특히 팀 환경에서는 사용자 정의 command가 프롬프트만 생성하는지, 실제 local action을 실행하는지 구분하는 것이 중요합니다.

8. Agent 개발자 체크리스트

# 읽는 법: 아래 항목은 동작 흐름을 빠르게 확인하기 위한 요약 예시입니다.Command System 체크리스트 [ ] command kind가 prompt/local/interactive/forked 등으로 명시되어 있다.[ ] command registry가 built-in, user, plugin command를 병합한다.[ ] command 충돌과 availability filter가 있다.[ ] command 결과는 PreparedTurn 또는 RuntimeEvent로 정규화된다.[ ] local command 결과가 불필요하게 model context로 들어가지 않는다.[ ] forked command는 필요한 context만 복사한다.[ ] plugin command 실패가 기본 command를 죽이지 않는다.

실패 사례: slash command를 문자열 alias로만 처리한 경우

/review를 "현재 diff를 읽고 리뷰해줘"라는 prompt alias로만 만들면 처음에는 편합니다. 하지만 실제 agent에서는 command가 prompt alias보다 넓습니다. /status는 로컬 상태만 보여줘야 하고, /login은 인증 UI를 열 수 있고, /init은 파일을 만들 수 있으며, /review는 별도 agent나 read-only workflow로 분기할 수 있습니다. 이 모든 것을 문자열 치환으로 처리하면 command마다 side effect가 숨습니다.

특히 plugin command가 들어오면 문제가 커집니다. 같은 이름의 command가 built-in과 user config 양쪽에 있을 수 있고, workspace 권한에 따라 어떤 command는 숨겨야 하며, 일부 command는 모델 호출 없이 끝나야 합니다. 충돌 규칙과 availability filter가 없으면 사용자는 같은 /deploy를 입력했는데 환경마다 다른 일이 일어나는 상황을 겪습니다.

비교표: command kind별 실행 방식

KindModel 호출Side effect기록 방식
prompt/review있음보통 없음prepared turn으로 기록
local/status없음없음 또는 읽기runtime event로 기록
interactive/login없음인증 상태 변경 가능UI action과 audit log
forked/fix-ci있음제한된 작업 가능child run summary

이 표처럼 command kind를 먼저 정하면 dispatch가 단순해집니다. parser는 문자열을 command id로 바꾸고, registry는 command definition을 찾고, dispatcher는 kind에 맞는 executor를 고릅니다. prompt command만 model loop로 들어가고, local command는 screen state만 갱신하며, forked command는 필요한 context만 복사합니다.

구현 예시: command result를 정규화하기

type CommandResult =  | { type: "prepared_turn"; prompt: string; contextRefs: string[] }  | { type: "local_event"; title: string; details: string }  | { type: "interactive"; view: "login" | "settings" | "picker" }  | { type: "forked_run"; task: string; allowedPaths: string[] };

이런 result union을 두면 command executor가 무엇을 반환하든 submit boundary는 다음 단계를 일관되게 결정할 수 있습니다. 중요한 것은 command 실행 결과 전체를 무조건 model context에 넣지 않는 것입니다. /status의 긴 로그를 모델에게 모두 보내면 비용만 늘고, 민감한 로컬 상태가 transcript에 섞일 수 있습니다.

마무리

Slash command는 agent runtime의 숨은 복잡도를 잘 보여줍니다. 같은 입력창에서 시작하지만 어떤 command는 모델을 부르고, 어떤 command는 local UI만 바꾸고, 어떤 command는 별도 agent 작업을 만듭니다.

다음 글에서는 agent의 중심인 query loop를 보겠습니다. 모델 호출은 한 번으로 끝나지 않습니다. streaming response, tool request, result injection이 반복되는 상태 기계입니다.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP