핵심 요약
- 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 오염 방지 |
| review | 예 | context 수집 | prompt 생성 범위 명확화 |
| settings | 아니오 | overlay open | queue 처리와 충돌 방지 |
| spawn job | 경우에 따라 예 | 별도 실행 | 권한과 transcript 분리 |
2. 네 가지 command 실행 타입
command는 최소 네 종류로 나누는 편이 좋습니다.
| 타입 | 설명 | 예시 |
|---|---|---|
| prompt command | command를 model-visible message로 변환 | 리뷰, 요약, 테스트 계획 |
| local command | 모델 없이 로컬 상태 변경 또는 출력 | help, clear, status |
| interactive command | overlay나 선택 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 policy | interactive 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별 실행 방식
| Kind | 예 | Model 호출 | 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에 섞일 수 있습니다.
실무 적용 예시: /review와 /status를 다른 경로로 보내기
두 command는 같은 입력창에서 시작하지만 runtime 처리는 달라야 합니다. /review는 현재 diff, 관련 테스트, 리뷰 기준을 모아 model turn을 준비합니다. /status는 현재 branch, dirty file count, 실행 중인 job 같은 local state를 화면에 보여주고 끝납니다.
| command | dispatch 결과 | model context 포함 여부 |
|---|---|---|
/review | prepared turn | 필요한 diff와 기준만 포함 |
/status | local event | 기본적으로 포함하지 않음 |
/settings | interactive view | 포함하지 않음 |
/fix-ci | forked run | 제한된 context만 child run에 전달 |
실패 사례는 모든 command 결과를 transcript에 넣는 구조입니다. /status가 긴 로그를 출력하고 그 로그가 다음 모델 호출에 들어가면 비용이 늘고, 사용자가 의도하지 않은 로컬 정보가 모델 context에 섞입니다. 반대로 /review를 local event처럼 처리하면 모델은 diff를 받지 못해 일반론만 말합니다.
따라서 command registry에는 이름뿐 아니라 kind, required context, allowed side effect가 있어야 합니다. plugin command가 들어올 때도 같은 기준을 적용합니다. 예를 들어 workspace command가 /deploy를 등록했다면, 이 command가 prompt만 만드는지, shell을 실행하는지, 외부 API를 호출하는지 표시해야 합니다. command system의 핵심은 빠른 입력이 아니라 실행 경로를 명확히 나누는 것입니다.
마무리
Slash command는 agent runtime의 숨은 복잡도를 잘 보여줍니다. 같은 입력창에서 시작하지만 어떤 command는 모델을 부르고, 어떤 command는 local UI만 바꾸고, 어떤 command는 별도 agent 작업을 만듭니다.
다음 글에서는 agent의 중심인 query loop를 보겠습니다. 모델 호출은 한 번으로 끝나지 않습니다. streaming response, tool request, result injection이 반복되는 상태 기계입니다.
Key takeaways
- The Agent's command is not a string shortcut but a runtime dispatch with side effects.
- A command can create a model prompt, execute a local action, or open a UI overlay.
- Availability and conflict resolution are required when combining built-in, user-defined, and plugin commands.
- The command result must be aligned with the common return type of the input normalization layer.
In this article, we will look at the slash command. In CLI agents like Claude Code, commands are not just shortcuts.
Inputs such as/help,/review, and/statusall start from the same input window, but their execution methods are different. Some commands create a model prompt, some only show local status, some open the settings UI, and some can branch to a separate agent task.
So the command system is not a parser but a runtime dispatch layer.
1. Why Commands should not be viewed as shortcuts
In regular CLI, a command is a function call. However, in AI agents, commands are also connected to the model loop. For example, the slash command “Review the code” may not be a local function, but rather a flow that reads the current diff, creates a prompt for review, and sends it to the model.
Conversely, commands like “Show status” do not need to call a model. Just show the runtime status on the screen.
If you handle these two with the same callback, a problem arises.
| command | model call | side effect | Things to note |
|---|---|---|---|
| help | no | UI output | model context pollution prevention |
| review | yes | context collection | Clarify the scope of prompt creation |
| settings | no | overlay open | Queue processing and conflict prevention |
| spawn job | Yes in some cases | run separately | Separation of permissions and transcripts |
2. Four command execution types
It is better to divide commands into at least four types.
| type | explanation | example |
|---|---|---|
| prompt command | Convert command to model-visible message | Review, summary, test plan |
| local command | Change or output local state without a model | help, clear, status |
| interactive command | Open overlay or selection UI and wait for user response | settings, model select |
| forked command | Copies part of the current session and executes a separate agent task | background review, isolated analysis |
This type must be associated with a runtime policy, not just a label. For example, a prompt command may restrict allowed tools, and a forked command may require a separate transcript.
3. Command registry and availability
Product-type agents do not only have built-in commands. User-defined commands, project commands, plugin commands, and workflow commands can be entered.
The registry should have the following responsibilities, not just a simple array:
- Resolving conflict of commands with the same name
- Only commands available in the current mode are exposed.
- Hiding or restricting commands according to permission policy
- Maintain default command when plugin fails
- Provide command description and argument schema
caution: It is dangerous to unconditionally trust internal project commands. You must distinguish whether the command only creates a prompt template, executes a local side effect, or calls an external tool.
4. Difference between prompt command and local command
The prompt command creates a message to send to the model. Local commands only change the screen or local state. If you do not distinguish between the two, there will be a problem where the user's/statusresult enters the model context, or conversely,/reviewends without a model call.
# How to read: The items below are summarized examples to quickly check the action flow.slash text→ command registry lookup→ availability checkcheck to command kindSelect the path to prompt/local/interactive/forkedNormalize to PreparedTurn or RuntimeEvent
5. Cutting context of forked command
Be especially careful with forked commands. If you copy the entire current session and pass it on to a separate agent task, context contamination and permission confusion may occur.
For separate execution, only what is needed should be passed.
| something that can be passed over | What to watch out for |
|---|---|
| Required message subset | Copy the entire transcript unconditionally |
| workspace metadata | sensitive local state |
| Allowed tools list | Temporary approval status for current session |
| cancellation policy | interactive overlay state |
6. Dispatch as a concept code
The code below is for illustration purposes only, not the original implementation.
# How to read: Conceptual code that describes runtime boundaries, not a clone of the actual implementation.class CommandSpec: # Initializes runtime dependencies and state storage that the object will reference in later steps. def __init__(self, name, kind, available, handler): self.name = name self.kind = kind self.available = available self.handler = handler # Find slash text in the command registry and send it to local execution or model message creation.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"Unknown command: {parsed.name}") if not spec.available(runtime.context): return make_local_notice("This command cannot be used in the current mode.") 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))
The key is that even after the command is executed, it is normalized into a form that downstream can understand.
7. AI utilization developer perspective
When using AI tools, it is good to know what type the command is.
- Does this command call a model?
- Are there any side effects in the file or shell?
- How much context do you use in the current conversation?
- Are the command execution results left in the transcript?
- Does it indicate whether it is a plugin command or a built-in command?
Especially in a team environment, it is important to distinguish whether a custom command only generates a prompt or actually executes a local action.
8. Agent developer checklist
# How to read: The items below are summarized examples to quickly check the action flow.Command System Checklist [ ] The command kind is specified as prompt/local/interactive/forked, etc.[ ] The command registry merges built-in, user, and plugin commands.[ ] There are command conflicts and availability filters.[ ] The command result is normalized to PreparedTurn or RuntimeEvent.[ ] Local command results do not enter the model context unnecessarily.[ ] The forked command copies only the necessary context.[ ] A plugin command failure does not kill the base command.
Failure case: When the slash command is processed only as a string alias
It is convenient at first if you only create/reviewas a prompt alias that says "Read and review the current diff." However, in actual agents, the command is wider than the prompt alias./statusshould only show the local status,/logincan open the authentication UI,/initcan create a file, and/reviewcan branch to a separate agent or read-only workflow. If you handle all of this with string substitution, side effects are hidden for each command.
In particular, the problem becomes bigger when plugin commands come in. Commands with the same name can exist in both built-in and user config, and depending on workspace permissions, some commands must be hidden, and some commands must end without a model call. Without conflict rules and availability filters, users would end up entering the same/deploy, but different things would happen in different environments.
Comparison table: Execution method by command type
| Kind | yes | Model call | Side effect | recording method |
|---|---|---|---|---|
| prompt | /review | Yes | Usually none | Record as prepared turn |
| local | /status | doesn't exist | None or Read | Recorded as runtime event |
| interactive | /login | doesn't exist | Authentication status can be changed | UI actions and audit logs |
| forked | /fix-ci | Yes | Limited work available | child run summary |
Dispatching becomes simpler if you decide on the command kind first, as shown in this table. The parser converts the string into a command id, the registry finds the command definition, and the dispatcher selects an executor that matches the kind. Only the prompt command goes into the model loop, the local command only updates the screen state, and the forked command copies only the necessary context.
Implementation example: Normalizing command results
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[] };
By having this result union, the submit boundary can consistently determine the next step no matter what the command executor returns. The important thing is not to unconditionally put the entire command execution result into the model context. Sending all long logs of/statusto the model only increases costs and may cause sensitive local states to be mixed in the transcript.
Practical application example: Sending/reviewand/statusthrough different routes
The two commands start from the same input window, but their runtime processing must be different./reviewcompiles current diffs, related tests, and review criteria to prepare for model turn./statusends by showing local status such as current branch, dirty file count, and running job on the screen.
| command | dispatch result | Whether to include model context |
|---|---|---|
/review | prepared turn | Contains only the diffs and criteria you need |
/status | local event | Not included by default |
/settings | interactive view | Not included |
/fix-ci | forked run | Only limited context is passed to the child run. |
The failure case is structured to include all command results in the transcript. If/statusoutputs a long log and the log is included in the next model call, the cost increases and local information that the user did not intend is mixed into the model context. Conversely, if/reviewis treated like a local event, the model does not receive a diff and only speaks in general terms.
Therefore, the command registry must contain not only a name but also kind, required context, and allowed side effects. The same standards apply when a plugin command comes in. For example, if the workspace command registers/deploy, you must indicate whether this command only creates a prompt, runs a shell, or calls an external API. The key to a command system is not quick input, but clearly dividing the execution path.
finish
The slash command clearly shows the hidden complexity of the agent runtime. It starts from the same input window, but some commands call the model, some commands only change the local UI, and some commands create separate agent tasks.
In the next article, we will look at the query loop, the center of the agent. A model call doesn't just happen once. It is a state machine that repeats streaming response, tool request, and result injection.

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