DSHX는 DeepSeek Harness를 위한 플러그인 개발 툴체인입니다. 이 글에서는 Vite 플러그인에서 시작해 Host/Client, Typed API, 실제 Profile 디버깅, 호환성 진단, Framework Hub까지 포괄하는 완전한 워크플로로 어떻게 발전했는지 되돌아봅니다.
최근에는 DeepSeek Harness를 계속 뜯어보고 있었습니다.
처음에는 DSH용 플러그인 몇 개만 만들면 되는 줄 알았습니다. 실제로 손을 대고 나니 플러그인 로직 자체는 생각보다 어렵지 않았고, 주변의 엔지니어링 문제들이 훨씬 더 골치 아팠습니다.
- Host와 Client가 각각 어디에서 실행되는지
- Cordis의 의존성 주입과
inject는 어떻게 선언해야 하는지 - UI를 어떤 Slot에 붙여야 하는지
package.json에 어떤 DSH 메타데이터가 필요한지- Host와 Client를 각각 어떻게 빌드하고 디버그하는지
- 플러그인이 빠르게 변하는 DSH 버전에 어떻게 대응해야 하는지
플러그인 하나만 만들 때는 소스 코드를 찾아보면 이 문제들을 해결할 수 있었습니다. 하지만 앞으로도 계속 플러그인을 만들거나 더 많은 개발자를 참여시키고 싶다면, 모든 사람이 소스 코드에서 개발 흐름을 다시 짜는 것은 좀 아까운 일이 됩니다.
그래서 DSHX를 만들었습니다.
- 프로젝트 주소: github.com/liyown/dshx
- Framework Hub: dshx.io
2026년 8월 기준, DSHX는 여전히 0.1.x Preview 단계입니다. 현재 Authoring API는 API Candidate이며, 1.0 안정성을 약속하는 단계는 아닙니다.
플러그인을 작성하는 것은 어렵지 않다. 어려운 것은 실제로 구동하는 일이다
처음에는 DSH를 위한 Vite 플러그인 하나만 만들려 했습니다.
아이디어는 단순했습니다.
TypeScript / React
↓
Vite
↓
Host + Client
↓
DSH Plugin
Host와 Client의 빌드 진입점을 잘 감싸고 타입도 조금 보완하면 개발 경험이 훨씬 편해질 것 같았습니다.
그런데 조금 더 파고들다 보니 훨씬 현실적인 문제에 곧바로 부딪혔습니다. 예를 들어 Client 컴포넌트에서 Host API를 호출하려면, 개발자는 Host가 무엇을 노출하는지, Client가 Connection을 어떻게 얻는지, 플러그인이 어떤 Provider를 선언해야 하는지, 입출력 타입이 무엇인지, 현재 DSH Runtime이 실제로 이 경로를 지원하는지 알아야 합니다.
이 지식은 빌드, 런타임, Manifest, 구체적인 서비스들 사이에만 흩어져 있습니다. Vite는 그중 일부만 해결할 수 있습니다.
DSHX도 Vite 플러그인에서 Authoring API, 빌드, 검사, 스캐폴딩, 실제 Profile 디버깅, 호환성 진단까지 갖춘 툴체인으로 조금씩 변했습니다.
현재 내가 DSHX를 이해하는 방식은 이렇습니다. DSHX는 “DSH가 무엇을 할 수 있는지”를 “플러그인 개발자가 어떻게 작성해야 하는지”로 정리할 뿐이며, DeepSeek Harness를 다시 구현하지는 않습니다.
먼저 경계를 정하자: DSHX는 DSH Runtime을 다시 구현하지 않는다
Framework은 만들면 자꾸 범위가 커지기 쉽습니다.
API가 불편하면 그 위에 한 겹을 씌우고, 통신이 불편하면 RPC를 새로 만들고, 상태 처리가 불편하면 Store를 더합니다. 그러다 보면 Framework가 DI, Runtime, RPC, Cache, Event Bus를 전부 갖게 되고, 정작 원래의 DSH는 그저 저수준 드라이버로 남습니다.
이 길은 나중에는 의도적으로 피했습니다.
DSHX의 원칙은 이렇습니다. 개발 경험은 다시 설계해도 되지만, 런타임의 동작 방식은 최대한 DSH에 맡깁니다. 아래 표는 항목을 일대일로 대응시킨 표가 아니라, 양쪽의 책임 경계를 보여 줍니다.
| DSHX가 담당 | DSH / Cordis가 계속 담당 |
|---|---|
| 타입과 선언 | Fiber와 Scope |
| 코드 생성과 정적 검사 | Registry와 의존성 주입 |
| Host / Client 빌드 | Connection과 런타임 통신 |
| 스캐폴딩과 개발 워크플로 | Persistence와 Prompt Assembly |
| 호환성 진단 | HMR, 언로드와 disposer 수명 주기 |
DSHX는 defineHost, defineApi, defineSlot 같은 Authoring API를 제공할 수 있지만, 최종적으로는 다시 DSH 공식 기능에 매핑됩니다. 빌드 산출물도 실행하기 위해 DSHX 전용 Runtime을 함께 넣을 필요가 없습니다.
좀 보수적으로 들릴 수 있지만 DSH는 아직 빠르게 변화하고 있습니다. DSHX가 그 위에 별도의 Runtime을 또 만들면 단기적으로는 편할 수 있지만, 반년 뒤 두 개의 런타임 의미 체계를 동시에 유지하는 일이 오히려 프로젝트를 발목 잡을 가능성이 큽니다.
Typed API: 먼저 TypeScript가 오류를 내도록 만들기
Host와 Client 간 통신은 대표적인 사례입니다.
먼저 공유 Contract를 정의합니다.
Host가 이를 구현합니다.
Client는 같은 Contract를 그대로 사용합니다.
여기서 중요한 건 코드를 몇 줄 덜 쓰는 것이 아니라, 오류를 더 일찍 발견할 수 있느냐입니다.
- 메서드 이름을 잘못 쓰면 TypeScript가 오류를 냅니다
- Host가 Handler 하나를 빠뜨리면 정확한 Handler 타입이 이를 막아 줍니다
- 입출력이 Schema와 맞지 않으면 Host 경계에서 거부합니다
- Client가 어떤 기능을 사용했는데 플러그인이 해당 Provider를 선언하지 않았다면
dshx check가 알려 줍니다 - 로컬에 설치한 DSH가 현재 Adapter가 지원하는 범위 밖에 있다면 빌드 또는 개발 단계에서 바로 호환성 진단을 보여 줍니다
플러그인을 DSH에 설치하고 페이지까지 연 다음, 어떤 맥락도 없는 Runtime Error를 마주치는 일을 더 이상 기다리고 싶지 않습니다.
dshx dev는 왜 실제 DSH를 실행해야 하는가
개발 서버를 가장 간단하게 구현하는 방법은 직접 DSH 환경을 Mock하는 것입니다.
그러면 시작도 빠르고 제어하기도 쉽습니다. 하지만 Mock으로 만든 Slot, Connection, Provider, 수명 주기는 모두 가짜입니다. 개발 서버에서 모두 잘 돌아간다고 해서 플러그인을 실제 DSH에 설치한 뒤에도 정상적으로 작동하는 것은 아닙니다.
그래서 dshx dev는 별도의 모방 Runtime이 아니라 실제 DSH Profile을 실행합니다.
- Client를 수정하면 DSH 공식 HMR을 거칩니다
- Host 수정이 성공하면 다시 빌드하고 기본적으로 Host를 재시작합니다
- 초기 빌드가 통과한 뒤에만 DSH를 시작합니다
- 구성이나 의존성 재로드가 실패하면 마지막으로 사용할 수 있던 세션을 유지합니다
Runtime Inspect도 같은 원칙을 따릅니다.
dshx inspect slots
dshx inspect tools
dshx inspect services
dshx inspect events
inspect는 현재 Composition에서 Adapter가 지원하는 공식 Provider만 읽습니다. Runtime을 사용할 수 없으면, 완전한 것처럼 보이지만 실제 실행 환경과는 무관한 오프라인 목록으로 대신하지 않고 진단 결과를 반환합니다.
이 방식은 Coding Agent에도 유용합니다. Agent는 “여기쯤 sidebar.xxx Slot이 있겠지”라고 추측할 필요 없이, 먼저 Runtime을 Inspect한 뒤 코드를 어디에 붙일지 결정할 수 있습니다.
DSHX CLI의 명령이 최대한 atomic하게 동작하고, 결과를 확인할 수 있으며, 다른 명령과 조합할 수 있도록 설계하고 싶습니다. CLI는 사실과 진단을 제공하고, 다음 단계는 개발자나 Agent가 직접 세우도록 합니다.
호환성을 DSH 버전별로 일일이 나열할 수는 없다
DSH는 여전히 Developer Preview 단계입니다. 앞으로 다음과 같은 버전들이 연달아 나온다고 해 봅시다.
0.1.0
0.1.1
0.1.2
0.2.0
...
DSHX가 버전마다 Adapter를 만들고, “모든 플러그인 × 모든 DSH 버전” 조합을 전부 시험한다면, 이 유지 관리 방식은 곧바로 감당이 불가능해집니다.
그래서 DSHX는 호환성을 “프로토콜 세대”로 관리합니다. 공식 Contract, API seam, Loader 동작 또는 Runtime invariant가 서로 다른 적응을 필요로 할 만큼 바뀐 경우에만 새로운 Protocol Generation으로 넘어갑니다. 단순히 patch 또는 minor release가 나온다고 자동으로 새 Adapter가 생기지는 않습니다.
또한 의도적으로 자주 섞여서 다루어지는 세 가지 사실을 구분하기 시작했습니다.
검증되지 않은 prerelease에는 DSHX가 experimental을 추가로 붙이고, Adapter가 담당하지 않는 버전은 unsupported로 표시합니다.
어떤 버전이 SemVer 범위에 속한다는 것은 알려진 프로토콜 세대와 겹친다는 뜻일 뿐, 실제 Runtime에서 이미 검증됐다는 의미는 아닙니다. 이 차이는 특히 플러그인 마켓플레이스에서 중요합니다.
Framework Hub는 플러그인 제작자를 대신해 보증하지 않는다
저는 한때 플러그인 마켓을 아주 엄격하게 만들 생각도 했습니다. 패키지가 DSH 플러그인인지 자동으로 판별하고, 어떤 버전과 호환되는지, 설치할 수 있는지, 메타데이터가 완전한지 자동으로 판단하도록 만들려 했습니다.
하지만 곧 유지 관리 비용이 감당할 수 없는 수준까지 커진다는 걸 알게 됐습니다. 서드파티 플러그인 모두가 DSHX 규칙에 맞춰 완전한 메타데이터를 제공하지는 않을 테고, 제가 모든 제작자의 모든 버전 조합을 직접 테스트할 수도 없습니다.
이제 dshx.io의 포지셔닝은 훨씬 선명해졌습니다. Framework Hub는 주로 플러그인 정보 계층입니다. GitHub, npm 등 공개 소스에서 프로젝트, 버전, 소스 코드, 작성자, README, 설치 대상, 호환성 선언, 위험 신호를 정리하고, 출처에 기반한 사실, 커뮤니티가 정리한 내용, 실제 검증 증거를 명확히 구분합니다.
Hub는 DSHX 검증을 거치지 않은 플러그인을 거부하지 않습니다. 특정 사용자의 DSH 환경에서 반드시 설치될 수 있다고 약속하지도 않습니다.
커뮤니티 플러그인에 대해서는 사실, 증거, 위험 요소를 함께 제시하고, 최종 결정은 사용자에게 맡기는 편을 더 좋아합니다. DSHX는 자신이 확실히 아는 범위에만 책임집니다.
DSHX로 DSH 안에서 동작하는 플러그인 마켓플레이스 만들기
저장소에는 제가 특히 좋아하는 Dogfooding 프로젝트도 있습니다.
@becomeopc/dshx-plugin-marketplace
이 프로젝트 자체는 일반적인 DSH Bundle입니다. 설치한 뒤에는 다음 경로에서 Framework Hub에 설치할 수 있는 플러그인을 둘러볼 수 있습니다.
Settings → Plugins → Marketplace
이 Marketplace는 DSHX 개발 경로를 빠짐없이 활용합니다.
defineHostdefineSettingsdefineApidefineClientdefineLocaledefineSlot- Standard Schema
useApiQuery- CSS Modules
- Profile 개발 워크플로
- Client HMR
Preview 버전은 다음과 같이 설치할 수 있습니다.
dsh plugin --profile web add @becomeopc/dshx-plugin-marketplace@preview
dsh --profile web
제가 만든 Framework로 제 플러그인 마켓플레이스조차 만들기 힘들다면, API 설계가 아직 제대로 다듬어지지 않았다는 뜻일 가능성이 큽니다. 독립 Demo를 수십 개 쌓는 것보다, 실제 플러그인 하나로 계속 문제를 드러내는 편을 더 좋아합니다.
DSHX는 드디어 첫 번째 실제로 쓸 수 있는 Preview 단계에 들어섰습니다
API와 아키텍처를 몇 차례 엮고 바꾼 뒤, DSHX는 처음으로 실제로 사용할 수 있는 Preview 단계에 진입했습니다.
플러그인을 만듭니다.
pnpm create dshx@preview my-plugin
cd my-plugin
pnpm check
pnpm dev
더 완전한 API 예제가 필요하면 다음과 같이 실행합니다.
pnpm create dshx@preview my-plugin --template showcase --style tailwind
현재 다음 영역을 지원합니다.
- Host / Client Authoring
- Typed API, Settings와 Prompt
- Slot과 Locale
- Vite 빌드, CSS Modules와 Tailwind
- Profile 개발 워크플로와 Client HMR
- Runtime Inspect
- CLI 검사, 진단과 제한적인 결정론적 수정
- DSH 프로토콜 세대와 호환 Adapter
- 플러그인 스캐폴딩
- Framework Hub
- DSH 내부의 Marketplace 플러그인
Conversation Components는 여전히 @becomeopc/dshx/experimental/conversation에 있습니다. Streaming 역시 당장 공개 추상화로 만들지는 않았습니다.
이 기능들은 업스트림에서 더 안정적으로 정해질 이벤트 용어, 영속성, Connection Ownership, 취소, 재연결, 백프레셔 의미론에 의존합니다. 지금 제공하지 않는 편이, 반년 뒤 반드시 폐기해야 할 비공개 프로토콜을 만드는 것보다 더 합리적입니다.
이제 먼저 실제 플러그인을 더 만들어 다듬기
DSHX는 아직 0.1.x입니다. API를 더 추가하는 데 서두르지 않고, 실제 플러그인을 직접 몇 개 만들어 보면서 이 흐름이 어디서 막히는지 확인하고 싶습니다.
이 경로가 안정적으로 이어진다면, DSHX가 비로소 DeepSeek Harness 플러그인 개발 경험의 문제를 실제로 해결했다고 말할 수 있습니다.
처음에는 Vite 플러그인 하나를 만들려고 했습니다. 지금 돌아보면, 내가 정말로 만들려던 것은 DeepSeek Harness에 완전하고, 검사할 수 있으며, 계속 진화할 수 있는 플러그인 개발 워크플로였습니다.
프로젝트는 여전히 Preview이고 API도 앞으로 조정될 예정입니다. 바로 그만큼 지금이 실제 플러그인을 실컷뤄보기에 알맞은 때입니다.