CH 08 · 플러그인 트리 멘탈 모델: 만물은 모두 플러그인
본 장 목표
CH 02에서 "만물은 플러그인"이라고 했습니다. 이번 장에서는 그 말을 가져다 쓸 수 있는 멘탈 모델로 바꿔봅니다. 실제로 실행 중인 dsh가 무엇인지, 왜 모든 부분을 교체할 수 있는지, 그리고 "Provider만 바꾸면 제품이 바뀐다"는 말이 실제로 어떻게 성립하는지 다룹니다.
도입 한 줄: 실행 중인 dsh = 플러그인 트리
공식 아키텍처 문서는 이렇게 말합니다. 실행 중인 dsh는 플러그인 트리이며, 시작 시 정해진 순서의 레이어로 구성됩니다. 기억하기 쉽도록 세 개의 레이어로 나누면, 아키텍처 전체가 보여집니다.
다이어그램을 위에서 아래로 읽으면, 논리는 이렇습니다. ① 먼저 Profile을 고르고 → ② 플러그인 레이어를 순서대로 쌓고 → ③ 모든 레이어는 동일한 Cordis 커널 위에서 동작합니다. 세 가지 핵심 포인트를 짚어보겠습니다.
- 가장 아래는 Cordis 커널입니다. 플러그인이 서비스를 주입하고, 이벤트를 브로드캐스트하며, 이펙트를 마운트할 수 있도록 해주는 공유 컨텍스트입니다.
- 가운데는 플러그인 트리 자체입니다. 각자 한 가지 일만 담당하는 다양한 플러그인들이 트리 형태로 쌓여 있습니다.
- 가장 위는 Profile입니다. 새로운 능력을 더하는 게 아니라 "이 트리가 어떤 모양인지"만 결정합니다. 웹, 헤드리스, 또는 사용자가 직접 만든 조합 모두에 해당하며, 자세한 내용은 다음 절에서 다룹니다.
Cordis: 모든 것을 움직이는 프레임워크
dsh는 Cordis라는 플러그인 프레임워크 위에서 동작합니다(공식 README에서 명시적으로 "powered by Cordis"라고 표기). Cordis는 단 세 가지 핵심 규약만 갖고 있습니다.
- 플러그인은 공유 컨텍스트에 서비스를 주입합니다. 새로운 능력을 더하고 싶다면 플러그인을 작성해 등록하면 됩니다.
- 플러그인은 타입이 지정된 이벤트를 브로드캐스트합니다. 다른 플러그인은 그 이벤트를 듣고 조율하므로, 서로 직접 import할 필요가 없습니다.
- 플러그인 등록은 가역적인 이펙트입니다. 플러그인이 언로드되면 등록한 내용도 자동으로 원복되어 흔적이 남지 않습니다.
가장 중요한 것은 마지막 항목입니다. 이것이 바로 "만물은 플러그인"이라는 말이 성립할 수 있는 이유입니다. dsh에는 패치할 수 있는 특권 코어가 없습니다. 모델 어댑터도 플러그인이고, 도구 레지스트리도 플러그인이고, 세션 로그도 플러그인이며, 에이전트 메인 루프조차도 플러그인입니다. dsh를 확장하는 방법은 "옆에 플러그인을 하나 더 매다는 것"이지, "어떤 코어 파일을 수정하는 것"이 아닙니다. 그래서 변경 비용이 극도로 낮습니다. 바꿀 수 없다면 교체하고, 교체도 안 된다면 하나 더 추가하면 됩니다.
다른 도구들과는 어떻게 다를까요? 가장 많이 쓰는 CLI Agent 둘을 나란히 놓으면 차이가 확 드러납니다.
| 비교 | 확장 방식 | "뼈대"를 만질 수 있는가 |
|---|---|---|
| Claude Code | 도구는 확장 가능(MCP, skills), 그러나 모델과 메인 루프는 교체 불가 | 반쯤 열림: 도구는 추가 가능하지만 모델 어댑터, 세션, 에이전트 루프는 용접되어 있고 모델은 자사 생태계에 종속 |
| Codex | 도구는 확장 가능, 그러나 모델과 메인 루프는 교체 불가 | 반쯤 열림: 도구는 추가 가능하지만 모델 어댑터, 세션, 에이전트 루프는 용접되어 있고 모델은 자사 생태계에 종속 |
| dsh | 옆에 플러그인을 매달고, 설정 변경으로 교체 | 완전히 열림: 세션 로그, 에이전트 루프, 모델 어댑터까지 모두 플러그인이며, 특권 코어가 없음 |
한 줄 요약: Claude Code와 Codex의 "뼈대"는 용접되어 있지만, dsh의 "뼈대" 역시 플러그인입니다. 그래서 어떤 모델이든 끼울 수 있고(CH 06의 Codex 비교가 그 예입니다), 세션을 자신만의 구현으로 교체할 수도 있습니다. 뼈대 자체가 교체 가능한 것이고, 유일한 한계는 바꾸고 싶으냐 마냐뿐입니다.
--dump-config로 실제 트리 확인하기
이론을 백 번 말하는 것보다 한 번 직접 보는 게 낫습니다. CH 05에서 dsh --profile headless --dump-config를 한 번 실행해 보셨을 텐데, 출력되는 것은 @deepseek-ai/dsh-* 플러그인들의 긴 트리입니다. llm(모델), session(세션), credentials(키), session-persistence-jsonl(세션 영속화) 등이 각자 자기 일을 담당하며, 쌓이면 실행 가능한 dsh가 됩니다.
공식 팀이 정리한 코어 플러그인 목록은 다음과 같습니다. 트리에서 각각 하나의 "큰 가지"에 해당합니다.
| 코어 플러그인 | 담당 영역 |
|---|---|
core/session | 추가 전용 세션 로그(모델이 본 모든 것이 기록됨) |
core/system-prompt | 시스템 프롬프트 조립 방식, 도구 스키마 패키징 방식 |
core/tools | 도구 레지스트리와 통제된 실행 파이프라인 |
core/agent | Agent 인터페이스와 실시간 레지스트리 |
core/agent-loop | 기본 "요청 → 도구 호출" 루프 드라이버 |
llm/llm | 메시지와 스트리밍 어휘, 모델 어댑터 시임 |
마지막 줄의 "시임(seam)"에 주목하십시오. 이것이 다음에 다룰 핵심 개념입니다.
Profile과 Bundle: 플러그인이 어떻게 "모드"로 조합되는가
플러그인 = 한 가지 요리(볶음밥, 가정식 국, 반찬). bundle = 세트 메뉴(밥, 메인, 젓가락이 미리 패키지). profile = 메뉴판(어떤 요리를 어떤 순서로 내는지 적혀 있음). 뒤에서 다룰 Cordis 커널은 주방의 그 "웍"입니다.
트리의 구성을 결정하는 것은 Profile입니다. 손에 들린 메뉴판이라고 생각하시면 됩니다.
- Profile은 명명된 조합이며, Harness 디렉터리에 저장됩니다. 어떤 Bundle을 쌓을지, 추가로 어떤 플러그인을 설치할지를 적어 두며, 자신만의
cordis.patch.yml도 보관합니다. 메뉴판은 요리를 만들지 않습니다. 단지 "이 식사에 무엇이 들어가는지"만 적혀 있을 뿐입니다. 공식 출시본에는web과headless라는 메뉴판이 준비되어 있고, CH 02에서 살펴본 "네 가지 실행 모드"는 모두 그 아래의 서로 다른 플러그인 조합 프리셋이며, 메뉴판이 몇 장 더 있는 것에 불과합니다. - Bundle은 "한 무리의 플러그인 + 그것이 마운트하는 설정"의 배포 형식이며, 사전 포장된 세트 메뉴라고 할 수 있습니다.
dsh-base는 모든 Profile의 첫 번째 레이어(기반: 모델, 도구, 영속화, 샌드박스, 승인, 설정, 키, 텔레메트리)이고,web-app은 베이스 세트에 "매장 환경"(브라우저 인터페이스)을 더하며,headless은 "테이크아웃"(원샷 명령줄, 서버 없음)을 더합니다.
쉽게 헷갈리는 점 하나 짚고 가겠습니다. Profile은 플러그인이 아닙니다. 플러그인은 "일을 하는 것"(요리)이고, Profile은 단지 "메뉴판"(주문만 적혀 있고 요리하지 않음)입니다. "최소 모드"를 고른다고 해서 새 플러그인이 설치되는 게 아니라, 플러그인 트리만 더 최소한의 조합으로 바뀝니다. 메뉴판이 다를 뿐, 주방은 같습니다.
플러그인 적재는 엄격한 순서를 따릅니다(주방이 메뉴판대로 준비하듯). 나중에 적힌 내용이 앞선 항목을 덮어쓸 수 있습니다.
프로파일에 적힌 순서대로 각 bundle을 먼저 적재 ← 세트 메뉴 먼저
→ 프로파일 자체의 cordis.patch.yml ← 메뉴판의 추가 메모
→ Harness 디렉터리 레벨의 patch ← 사장님의 통일된 요구
→ 명령줄 --patch 오버레이 ← 임시 추가 메모, 가장 마지막이 이김각 레이어는 위의 레이어를 덮어쓸 수 있습니다. 패치는 id로 행을 찾아 설정을 통째로 교체하거나 새 행을 끼워 넣습니다. 그래서 CH 05에서 본 --patch는 "추가 구멍"이 아니라 이 트리를 공식적으로 수정하는 행위입니다. --dump-config로 출력되는 모든 줄은 자신의 패치로 교체할 수 있습니다.
자신만의 Profile을 만든다고 해서 코드를 처음부터 작성하는 건 아닙니다. 본질은 "메뉴판을 복사해 수정하는 것"입니다.
- web보다 작게 만들고 싶다면 → web 설정을 복사해 필요 없는 부분을 지웁니다("매장, 그런데 국은 제외");
- headless보다 크게(예: 자신이 만든 도구 플러그인을 기본으로 마운트) 만들고 싶다면 → headless를 복사해 bundle 목록에 한 줄을 더합니다("테이크아웃, 직접 가져온 반찬 추가");
- 가장 가벼운 방법은 메뉴판을 바꾸지 않고 메모만 더하는 것입니다. 기존 Profile에 플러그인을 설치하려면
dsh plugin --profile <name> add <package>를 쓰고("기존 메뉴판에 반찬 하나 추가"), 일시적으로 덮어쓰려면--patch를 씁니다("이번 식사 메모: 국을 사이다로 교체") — CH 05에서 둘 다 이미 살펴봤습니다.
로딩 과정을 하나의 타임라인으로 잇자, 앞서 나온 내용이 모두 자리를 잡습니다.
- 시작 시 dsh는 먼저 고른 Profile을 읽습니다. 이건 로딩 매니페스트이며, "어떤 레이어를 어떤 순서로 쌓을지"를 적은 목록입니다.
- 매니페스트 순서대로 레이어를 하나씩 적재합니다. 먼저 1번 베이스(
dsh-base), 다음 2번 추가(web-app/headless/ 본인 플러그인), 마지막으로 3번 덮어쓰기(cordis.patch.yml+--patch) — 나중에 적재할수록 앞서 로드한 내용을 더 많이 덮어쓸 수 있습니다. - 모든 레이어는 동일한 Cordis 커널 위에 설치되며, 거기서 서비스를 주입하고 서로 통신합니다.
마무리 한 줄: Profile = 시작 시 주문하는 순서; 플러그인 트리 = 그 순서로 설치된 요리; Cordis = 그 요리를 담는 웍.
이벤트와 능력 시임: 플러그인이 서로 대화하는 방식
플러그인은 서로를 import하지 않습니다. 다음 두 가지를 통해 조율합니다.
이벤트는 확장 지점이며, 세 가지 영역으로 나뉩니다.
- Session 이벤트: 영속적인 사실이며, 로그에 추가되고 브로드캐스트되어 재시작 후에도 살아남습니다(예: "이 메시지가 전송되었다").
- Agent 이벤트(
agent/*): 실행 중인 Agent를 실어 나르며, 진행 중인 작업을 관찰하거나 가로채는 데 쓰입니다(예: "이 step이 시작되기 전에 가로챈다"). - Capability 이벤트(
fs/*,tools/*,telemetry/*): 메인 루프를 바꾸지 않고 시임에 정책과 어댑터를 붙입니다.
능력 시임(capability seam)은 교체 가능한 능력이며, 고정된 세 역할이 있습니다.
- Service Definition: 인터페이스가 어떤 모양인지 선언합니다.
- Service Provider: 실제로 구현합니다.
- Consumer: 사용합니다(대개 모델이 호출하는 도구).
왜 "Provider를 바꾸면 제품이 바뀐다"고 할까요? 파일 시스템과 서브프로세스 Provider가 동일한 "실행 세계"를 공유하기 때문입니다. 그들을 원격 샌드박스로 향하게 하면 Bash, PTY, LSP가 모두 따라 움직이므로, 능력마다 별도로 구현할 필요가 없습니다. 모델 어댑터도 마찬가지입니다. ctx.llm에 등록된 어댑터가 곧 제품 전체가 사용하는 모델이며, 다른 것은 바꿀 필요가 없습니다.
멘탈 모델을 한 줄로
단 세 문장만 가져가도 된다면, 이 세 문장을 기억하시기 바랍니다(앞서의 작은 식당 비유와 함께).
- dsh에는 코어가 없다 — 모든 부분이 플러그인이며, 자기 자신조차 플러그인입니다. 옆에 요리를 더하고, 하나를 빼면 자동으로 원복됩니다.
- Profile이 트리의 모양을 결정한다 — web / headless / 사용자 정의 모두 같은 트리의 서로 다른 "메뉴판"입니다. 일하지 않고, 주문만 합니다.
- 이벤트와 시임이 플러그인 협업의 인터페이스다 — 붙이면 동작하고 떼면 원복되니, 자유롭게 교체하거나 추가하십시오. Provider를 바꾸면 제품이 바뀝니다.
이번 장에서 배운 것
아래 항목들을 스스로 점검할 수 있으면 합격입니다.
- [ ] "실행 중인 dsh = 플러그인 트리"이며, 정해진 순서의 레이어로 구성된다고 말할 수 있다
- [ ] Cordis의 세 가지 핵심 규약(서비스 주입 / 이벤트 / 가역 이펙트)을 말하고, 왜 특권 코어가 없는지 설명할 수 있다
- [ ]
--dump-config출력에서 코어 플러그인(llm/session/credentials/tools등)을 알아볼 수 있다 - [ ] Profile과 Bundle의 관계, 플러그인 적재의 레이어 순서를 설명할 수 있다
- [ ] "주문" 비유로 플러그인 / bundle / profile의 관계를 설명할 수 있고, 자신만의 profile을 만드는 것이 "메뉴판 복사 후 수정"이며,
plugin add/--patch가 가장 가벼운 수정임을 안다 - [ ] 능력 시임의 세 역할(Definition / Provider / Consumer)을 말할 수 있고, "Provider를 바꾸면 제품이 바뀐다"는 말이 성립하는 이유를 설명할 수 있다
