본문으로 건너뛰기
effidevFlutter · Cloudflare 엣지 · 클라우드 비용 최적화

딥시크 하네스: 전부 플러그인인 에이전트

effidev

플러그인 레이어가 겹겹이 쌓여 하나의 에이전트 실행 트리를 이루는 구조

리포가 생긴 지 하루도 안 됐는데 스타가 3만 7천이다

deepseek-ai/deepseek-harness 리포는 2026년 8월 13일 11시 56분(UTC), 한국 시각으로 어제 저녁 8시 56분에 만들어졌다. 이 글을 쓰는 8월 14일 오전 5시 35분(KST) 기준으로 스타 37,891개, 포크 2,946개다. 아홉 시간이 채 안 되는 사이에 붙은 숫자다.

이름은 DeepSeek Harness, 우리말로 옮기면 딥시크 하네스이고 명령어는 dsh다. README 첫 줄은 이렇게 시작한다. “DeepSeek AI가 개발한 오픈소스 에이전트 하네스다.” 라이선스는 MIT, 언어는 TypeScript다.

여기서 “하네스(harness)“라는 단어를 그냥 넘기면 안 된다. 모델이 아니라 모델을 부리는 껍데기를 공개했다는 뜻이다. 지금까지 DeepSeek가 내놓은 건 가중치와 API였는데, 이번엔 클로드 코드나 코덱스가 서 있는 자리에 물건을 하나 놓았다.

무엇이 공개됐나

설치와 실행은 한 줄이다.

npx @deepseek-ai/dsh web

이 명령이 웹 UI를 띄우고 기본값으로 http://127.0.0.1:3080에 붙인다. 소스에서 돌리려면 리포를 클론해 pnpm install && pnpm run buildpnpm dsh web이다.

npm 레지스트리를 보면 @deepseek-ai/dsh 패키지는 **8월 10일 19시 41분(UTC)**에 처음 올라왔다. 리포를 공개하기 사흘 전부터 조용히 배포를 돌리고 있었다는 뜻이다. 최신 버전은 0.1.0-rc.6이고 8월 13일 12시 35분(UTC)에 올라왔다. rc.2, rc.3, rc.6이 하루 안에 연달아 찍혔다.

그리고 README에는 대문자로 박아 둔 경고가 있다.

DeepSeek Harness is currently in developer preview and is iterating rapidly. THERE WILL BE COMPATIBILITY-BREAKING CHANGES.

개발자 프리뷰이고, 호환성을 깨는 변경이 있을 것이라고 미래형으로 못 박았다. 버전 번호도 그 말과 일치한다. 이 글의 결론은 마지막 절에 있지만 미리 말하면, 지금 이걸 팀 워크플로에 꽂는 건 이르다.

“전부 플러그인”이 진짜로 뜻하는 것

리포 설명은 한 문장이다. “Everything is a Plugin.” 마케팅 문구처럼 들리는데, 아키텍처 문서를 열어 보면 문자 그대로다.

밑바닥에는 Cordis라는 프레임워크가 있다. DeepSeek가 이번에 만든 게 아니라 2022년 5월부터 있던 별개 오픈소스 프로젝트(cordiverse/cordis, 스타 1,096개)이고, 스스로를 “시공간적 조합 가능성의 메타 프레임워크”라고 부른다. 플러그인이 공유 컨텍스트에 서비스·타입 있는 이벤트·되돌릴 수 있는 효과를 등록하는 구조다.

아키텍처 문서의 핵심 문장은 이거다.

제품의 모든 부분이 플러그인이다. 모델 어댑터도, 툴 레지스트리도, 세션 로그도, 에이전트 루프 그 자체도 포함해서. 그래서 모든 부분이 설정으로 교체 가능하다.

이어서 “패치할 특권적 코어가 없다”고 덧붙인다. 기능을 확장한다는 건 코어를 고치는 게 아니라 다른 플러그인 옆에 하나를 더 마운트하는 것이고, 플러그인이 언로드되면 그 등록은 효과째 되감긴다.

에이전트 루프까지 플러그인이라는 대목이 이 설계의 전부다. 보통 에이전트 도구에서 “확장 가능”이라고 하면 툴을 추가하거나 프롬프트를 바꾸는 수준인데, 여기선 루프를 구현한 core/agent-loop가 그냥 기본 드라이버 하나일 뿐이고 Agent 인터페이스를 만족하는 다른 구현으로 갈아끼울 수 있다.

부팅은 레이어를 쌓아 올리는 일이다

실행 중인 dsh는 부팅 시점에 순서 있는 레이어들로 조립된 플러그인 트리다.

적용 순서는 명확하다. ① 프로파일이 나열한 순서대로 각 번들의 패치 → ② 프로파일의 cordis.patch.yml → ③ 홈 레벨 $DSH_HOME/cordis.patch.yml → ④ --patch 오버레이. 패치는 행을 id로 찾아 설정 전체를 갈아치우거나 새 행을 끼워 넣는다.

모든 프로파일의 첫 레이어는 dsh-base다. 모델 어댑터, 툴, 영속화, 샌드박스와 승인 정책, 설정, 자격증명, 텔레메트리가 여기 들어 있다. 그 위에 dsh-web-app이 브라우저 앱을 얹고, dsh-headless는 서버 없이 한 번 돌고 끝나는 러너를 얹는다.

내 기계가 실제로 무엇을 부팅하는지는 이렇게 본다.

dsh --profile web --dump-config

여기서 찍히는 아무 행이나 내 패치로 대체할 수 있다는 게 이 명령의 의미다. --dump-default-config도 있어서 부팅 없이 조합된 트리를 비교할 수 있다.

진입 모드는 네 가지다.

명령 하는 일
dsh --profile <name> $DSH_HOME/profiles/<name> 프로파일로 부팅
dsh --profile headless "job" 새 세션 하나를 돌려 최종 답만 출력하고 종료
dsh web --profile web의 별칭
dsh plugin --profile <name> <pnpm args> 프로파일 디렉터리에서 pnpm으로 플러그인 관리

플러그인 관리를 자체 패키지 매니저로 만들지 않고 pnpm에 그대로 넘긴다는 점이 눈에 띈다. 프로파일 디렉터리가 그냥 package.json을 가진 pnpm 프로젝트라서 가능한 일이다. 헤드리스 모드가 처음부터 1급으로 들어 있는 것도, 이 물건이 사람 앞의 채팅창만 상정하고 만들어지지 않았다는 신호다.

턴 하나가 어떻게 흘러가는가

문서는 용어부터 정리한다. **스텝(step)**은 모델 요청 한 번과 그게 호출한 툴들이고, **턴(turn)**은 0개 이상의 스텝이다. 턴은 첫 입력을 집어 들기 전에 열리고, 갚을 게 없어지면 닫힌다.

turn/start
  다음 스텝 입력 + 큐에 있던 메시지 하나를 claim
  프롬프트 섹션 + 툴 스키마 조립
  -> agent/pre-step         reject | enter(messages)
     step/start
     agent/request -> llm/stream -> assistant/chunk* -> assistant/message
     tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
     step/end
  -> agent/turn-stopping
turn/end

agent/pre-step, agent/request, llm/stream, 그리고 tools/* 세 개는 워터폴 이벤트다. 리스너가 next()를 불러야 다음으로 넘어간다. 즉 플러그인이 모델이 볼 메시지를 다시 쓰거나 아예 거절할 수 있고, 툴 실행 앞뒤로 끼어들 수 있다. agent/turn-stopping은 직렬이고 next()가 없다.

이벤트는 세 도메인으로 갈린다. 세션 이벤트는 로그에 append되는 지속적 사실이고, 에이전트 이벤트(agent/*)는 살아 있는 에이전트를 들고 다니며 진행 중인 작업을 관찰·가로채고, 케이퍼빌리티 이벤트(fs/*, tools/*, telemetry/*)는 루프를 임포트하지 않고 정책과 어댑터를 붙인다.

세션 로그 쪽에 걸린 불변식 하나가 특히 단단하다.

모델이 본다는 것은 로그에 있다는 것이다. 모델 요청에 도달하는 모든 것은 로그로부터 재구성 가능해야 하며, 런타임 불변식이 이를 단언한다.

그래서 모델이 보는 입력을 새로 추가하려면 반드시 새 세션 이벤트를 만들어야 한다. 포크, 재개, 트랜스크립트, 텔레메트리, 영속화가 전부 이 한 스트림에서 파생된다. 에이전트 실행을 나중에 감사(audit)해야 하는 팀이라면 이 설계가 무슨 뜻인지 바로 알 것이다.

툴 51개, 그리고 코드 모드

packages/ 아래 디렉터리는 49개다. acp, api, attachment, boot, bundle, client, code-runtime, compaction, context, core, credentials, e2b, extensions, feedback, fs, goal, guard, hooks, host, identity, interaction, jobs, llm, lsp, mcp, plan, preset, runtime-diagnostics, sandbox, schedule, sdk, session-query, session, settings, shell, skill, spill, storage, subagent, subprocess, terminal, test-support, todo, typert, util, web, workflow, workspace 같은 이름들이다.

이 중 툴 패키지가 24개이고, 모델에게 실제로 노출되는 툴 이름은 51개다. 자동 생성되는 툴 카탈로그 문서에서 그대로 뽑으면 이렇다.

마지막 줄이 흥미롭다. 에이전트가 자기가 돌고 있는 플러그인 트리를 런타임에 정의하고 실행하고 멈출 수 있다. “전부 플러그인”이라는 설계를 모델 쪽으로도 열어 둔 셈이다.

run_code도 그냥 코드 실행 툴이 아니다. 툴 레지스트리가 mode: code로 뜨면 와이어에 나가는 툴은 run_code 하나뿐이고, 나머지 능력은 로드된 런타임 언어로 생성된 SDK 섹션으로 선언된다. 모델은 툴을 하나씩 호출하는 대신 프로그램을 써서 바인딩으로 부르고, 그 중첩 호출들은 다시 완전한 가드 파이프라인을 통과한 뒤 바깥 결과에 연결된다. 동시 실행은 maxParallelSubCalls로 묶인다. 툴 호출을 왕복시키는 대신 한 번에 프로그램을 짜게 하는 접근인데, 툴이 51개나 되는 상황에서 컨텍스트를 아끼는 실질적인 수단이다.

딥시크 모델만 돌아가나? 아니다

가장 궁금할 부분이다. 답은 “아니다”이고, 문서가 꽤 구체적이다.

기본 경로는 물론 DeepSeek다. Settings → Models에서 DeepSeek 카드에 API 키를 넣고 저장하면 서버 재시작 없이 다음 요청부터 먹는다. 키는 $DSH_HOME/.credentials.yaml에 저장되고, 설정 파일에는 자격증명 참조만 남는다. 저장 후 페이지가 돌려받는 건 가려진 서술자뿐이라 화면으로 원문 키가 다시 나오지 않는다.

그 위에 세 가지 길이 더 있다.

  1. 카탈로그 제공자 추가 — Anthropic이나 OpenAI 같은 제공자를 고르고 키를 넣으면 엔드포인트·프로토콜·모델 목록은 설치된 카탈로그가 채운다.
  2. 네이티브 인증 제공자 — Bedrock은 AWS 자격증명과 리전, Vertex는 ADC 프로젝트, Azure는 api-version, Codex는 OAuth를 쓴다. API 키 칸만 채우면 설정되지 않는다고 문서가 명시한다.
  3. 커스텀 제공자 — 사내 게이트웨이나 자체 호스팅 서버용. 소문자 Provider ID, base URL, API 프로토콜, 자격증명, 모델 최소 1개를 넣는다. Provider ID는 영구다. 요청·저장된 세션·모델 기본값·자격증명 참조가 전부 이걸 참조하기 때문에, 이름을 바꾸려면 새로 만들고 옛것을 지우는 수밖에 없다.

여기 함정이 하나 있다. 손으로 입력한 모델은 명시하기 전까지 텍스트 전용으로 취급된다. 엔드포인트에 “너 이미지 받니?“라고 물어볼 방법이 없기 때문이다. 그래서 비전 모델은 settings.yaml에 한 줄을 더 써야 한다.

llm-pi-ai:
  providers:
    my-gateway:
      apiKeyEnv: GATEWAY_API_KEY
      api: openai-completions
      baseURL: https://gateway.example/v1
      models:
        - id: vision-preview
          input: [text, image]

라우트 전체에 걸려면 defaultInput: [text, image]를 쓰는데, 이건 오버라이드가 아니라 폴백이라 카탈로그가 이미 이미지를 인정한 모델에서 이미지를 뺏지는 않는다. 그리고 문서가 트러블슈팅 항목에 조용히 적어 둔 사실 하나 — DeepSeek 자신의 chat-completions 라우트는 텍스트 전용이고, 설정으로 바꿀 수 없다. 스크린샷을 던져 가며 일하는 습관이 있다면 이 줄이 실무에서 제일 먼저 부딪히는 벽이다.

MCP는 되고, 이름 규칙까지 같다

dsh-mcp-client 플러그인이 외부 MCP 서버에 붙어서 그 툴들을 ctx.tools에 등록한다. 서버 하나당 플러그인 인스턴스 하나를 cordis.yml에 적는 방식이다.

- id: mcp-github
  name: '@deepseek-ai/dsh-mcp-client'
  config:
    serverName: github
    transport: stdio
    command: npx
    args: ['-y', '@modelcontextprotocol/server-github']

전송은 stdiostreamable-http 둘 다 되고, 툴 호출 타임아웃 기본값은 60초다. 모델에게 보이는 이름은 mcp__github__create_issue 꼴인데, 문서가 대놓고 “클로드 코드와 코덱스가 쓰는 것과 같은 서버 한정 형태“라고 적어 놨다. 기존 MCP 서버 자산을 그대로 옮겨 붙일 수 있다는 뜻이고, 프롬프트에 툴 이름을 박아 둔 경우까지 호환된다.

HMR로 핫스왑도 된다. 설정 항목을 편집하면 프로세스 재시작 없이 연결이 끊겼다 다시 붙고, serverName이 그대로면 툴 이름도 동일하게 재생된다.

별도로 dsh-acp 패키지가 Agent Client Protocol 서버를 JSON-RPC stdio로 연다. 다만 이건 자동화 전용이라고 선을 그었다. 에디터 내비게이션, 트랜스크립트 리플레이, 커맨드, 모드, 설정 선택기, 추론, 계획, 툴 표현 같은 건 제공하지 않는다. session/new는 절대 경로 cwd로 새 에이전트를 만들고, additionalDirectoriesmcpServers는 비어 있을 때만 받는다. 대화형 렌더링과 사람에게 묻는 일은 웹 호스트 쪽 몫이라는 역할 분담이다.

샌드박스와 심(seam)

sandbox 패밀리는 세션별 격리 정책을 프로세스 실행에 적용한다. 서비스 정의(sandbox), 로컬 플랫폼 백엔드(sandbox-local), 지속적인 세션별 정책 해석(sandbox-policy), 그리고 윈도우 ACL 전용 패키지가 따로 있다.

이 설계에서 제일 실용적인 대목은 심(seam) 개념이다. 심은 세 역할로 이뤄진 교체 가능한 능력이다. 인터페이스를 선언하는 서비스 정의, 그걸 구현하는 프로바이더, 그리고 이를 쓰는 소비자(주로 모델이 보는 툴). 문서의 설명은 이렇다.

심이 있기 때문에 프로바이더 하나를 바꾸면 제품 전체가 바뀐다. 파일시스템과 서브프로세스 프로바이더는 하나의 실행 세계를 공유하므로, 그 둘을 원격 샌드박스로 향하게 하면 Bash·PTY·LSP가 함께 따라 옮겨간다. 프로바이더 포크는 필요 없다.

로컬에서 개발하다가 원격 컨테이너로 실행 환경을 통째로 옮기는 일이, 툴을 하나하나 고치는 게 아니라 설정 행 두 개를 바꾸는 일이 된다는 얘기다. packages/e2b가 별도로 있는 걸 보면 원격 실행 환경 어댑터도 이미 하나 들어 있다.

딥시크 하네스, 지금 깔아도 되나

딥시크 하네스에 대한 솔직한 답은 “구경은 지금, 도입은 아직”이다.

지금 확인할 가치가 있는 이유. MIT 라이선스로 풀린 완성도 있는 에이전트 하네스가 하나 더 생겼고, 그게 특정 모델에 묶여 있지 않다. 사내 게이트웨이를 커스텀 제공자로 물리면 우리 인프라 위에서 돌고, 기존 MCP 서버는 이름 규칙까지 같아서 그대로 붙는다. 세션 로그가 append-only이고 “모델이 본 것은 전부 로그에 있다”는 불변식이 런타임에 걸려 있어서, 에이전트가 무엇을 근거로 무슨 일을 했는지 나중에 되짚을 수 있다. 이건 감사 요구가 있는 조직에서 생각보다 큰 차이다.

아직 도입하면 안 되는 이유. 버전이 0.1.0-rc.6이고 README가 호환성 깨는 변경을 예고했다. 하루에 rc가 세 번 올라오는 속도이기도 하다. 문서는 웹 UI를 안정 경로로 안내하는데, 웹 UI는 워크스페이스를 고르기 전까지 세션 작성기 자체가 잠겨 있고, 활성 권한 정책에 따라 승인이 필요한 작업마다 물어본다. 프로덕션 배포는 빌드된 패키지와 프론트엔드 산출물을 요구한다.

그리고 이건 별점 3만 7천이 말해 주지 않는 부분인데, 리포가 공개된 지 만 하루가 안 됐다. 이슈 트래커가 비어 있는 건 버그가 없어서가 아니라 아무도 아직 오래 써 보지 않아서다.

오픈웨이트 모델이 하드웨어 벽에 부딪히는 이야기는 Qwen3.8 2.4T 오픈웨이트, 450GB의 벽에서 따로 다뤘다. 모델이 아니라 하네스가 열렸다는 게 이번 건의 차이다. 가중치는 장비가 없으면 그림의 떡이지만, 하네스는 npx 한 줄이면 오늘 밤에 돌려 볼 수 있다.

정리