DeepSeek Harness: 에이전트 루프까지 설정으로 교체하는 플러그인 구조의 코딩 에이전트

DeepSeek Harness 소개

코딩 에이전트를 자기 환경에 맞게 고쳐 쓰려는 사람은 대체로 같은 자리에서 멈춥니다. 모델 공급자를 바꾸는 것 정도는 설정으로 되지만, 도구를 실행하기 전에 정책을 한 겹 끼우거나 파일시스템 접근을 원격 샌드박스로 돌리거나 에이전트가 도는 루프 자체를 다른 것으로 갈아 끼우려 하면, 결국 본체 코드를 포크해야 합니다. 확장점(extension point)은 만든 사람이 미리 뚫어 둔 구멍만큼만 있고, 그 밖의 변경은 업스트림을 따라가는 비용으로 되돌아옵니다.

DeepSeek Harness(dsh)는 이 지점을 구조로 풀려는 시도입니다. DeepSeek AI가 공개한 에이전트 하네스(agent harness)로, "everything is a plugin" 이라는 한 문장을 아키텍처 전체에 밀어붙입니다. 모델 어댑터, 도구 레지스트리, 세션 로그, 그리고 에이전트 루프 자체까지 전부 플러그인이며, 저장소의 docs/architecture.md 는 그 결과를 "고쳐야 할 특권적인 코어가 없다(There is no privileged core to patch)" 고 표현합니다. 기능을 더하는 방식은 본체를 수정하는 것이 아니라 다른 플러그인들 옆에 하나를 더 마운트하는 것입니다.

이 구조를 떠받치는 것은 Cordis 라는 플러그인 프레임워크입니다. 플러그인들이 하나의 공유 컨텍스트에 서비스와 타입이 붙은 이벤트, 되돌릴 수 있는 효과(effect)를 등록하는 방식이며, 그 설계는 A Programming Paradigm for Spatiotemporal Composability 문서에 정리되어 있습니다. 제품은 TypeScript로 작성됐고 Web UI와 헤드리스 실행기, Python SDK를 함께 제공합니다.

DeepSeek Harness가 확장을 다루는 방식의 차이

일반적인 코딩 에이전트는 코어가 있고 그 주변에 플러그인 API가 붙습니다. 코어가 부르는 지점에서만 개입할 수 있으니, 개입하고 싶은 자리가 API로 열려 있지 않으면 방법이 없습니다. DeepSeek Harness는 반대 방향입니다. 부팅 시점에 만들어지는 것은 코어가 아니라 순서가 있는 계층으로 조립된 플러그인 트리이고, 트리의 어떤 행이든 사용자가 자기 패치로 대체할 수 있습니다.

비교 항목 코어 + 확장 API 방식 DeepSeek Harness
확장 가능한 범위 코어가 열어 둔 확장점 트리에 올라간 모든 행
모델 어댑터 교체 보통 지원 ctx.llm 에 어댑터 등록
에이전트 루프 교체 일반적으로 포크 필요 루프도 플러그인(ctx.agentLoop)
등록 해제 구현에 따라 다름 플러그인 언로드 시 효과가 되감김

되감김이 성립하는 이유는 등록이 값이 아니라 효과이기 때문입니다. 프롬프트 섹션, 도구 스키마, 어댑터, 공급자, 리스너는 모두 ctx.effect()ctx.on() 을 통해 설치되므로, 플러그인이 내려갈 때 그 등록들도 함께 풀립니다.

지금 자기 머신이 실제로 어떤 트리를 부팅하는지는 한 줄로 확인할 수 있습니다.

dsh --profile web --dump-config

여기서 출력되는 모든 행은 사용자가 작성한 패치로 대체할 수 있습니다.

DeepSeek Harness는 누구에게 유용한가

에이전트의 동작을 코드 수준에서 바꿔 보려는 사람, 그중에서도 도구 실행 전후에 자기 정책을 끼우거나 파일시스템과 서브프로세스를 원격 샌드박스로 돌리려는 사람에게 맞습니다. 반대로 지금 당장 안정적으로 쓸 코딩 에이전트를 찾는 상황이라면 적합하지 않습니다. 프로젝트가 스스로 개발자 프리뷰(developer preview) 단계임을 밝히면서 "호환성을 깨는 변경이 있을 것(THERE WILL BE COMPATIBILITY-BREAKING CHANGES)" 이라고 대문자로 경고하고 있기 때문입니다. Python SDK 예제 구성이 danger-full-access 권한으로 동작한다는 점도 함께 고려할 부분이라, 처음 시도할 때는 버려도 되는 체크아웃이나 컨테이너 안에서 돌리는 편이 안전합니다.

DeepSeek Harness의 프로파일과 번들

실행 중인 dsh 는 부팅 시점에 여러 계층이 순서대로 쌓여 구성된 플러그인 트리입니다. 이 조립을 다루는 단위가 프로파일(profile)과 번들(bundle) 두 가지입니다.

프로파일 은 하네스 홈에 저장되는 이름 붙은 구성입니다. 자신이 쌓을 번들 목록을 가지고 있고, 트리 밖에서 설치한 플러그인과 사용자의 cordis.patch.yml 을 함께 보관합니다. webheadless 가 템플릿으로 제공됩니다. 번들 은 Cordis 설정 행과 그 행이 마운트하는 코드를 함께 배포하는 형식이며, 번들이 끼워 넣은 것도 위 계층에서 계속 패치할 수 있도록 유지됩니다. 둘 다 자신의 package.jsondsh 필드로 스스로를 선언합니다.

기본 번들은 다음과 같이 역할이 나뉩니다.

  • dsh-base: 모든 프로파일의 첫 계층입니다. 모델 어댑터, 도구, 영속화, 샌드박스와 승인 정책, 설정, 자격증명, 텔레메트리가 여기에 들어갑니다.
  • dsh-web-app: 브라우저 애플리케이션을 더합니다.
  • dsh-headless: 서버 없이 한 번 실행하고 끝나는 실행기를 더합니다.

계층이 적용되는 순서는 프로파일이 나열한 번들 순서, 프로파일의 cordis.patch.yml, 홈 수준의 패치 파일, 마지막으로 --patch 오버레이입니다. 패치는 행을 id로 지목해 그 설정 전체를 교체하거나 새 행을 끼워 넣습니다.

DeepSeek Harness의 턴과 스텝, 그리고 세션 로그

용어가 두 개로 나뉩니다. 스텝(step) 은 모델 요청 한 번과 그 요청이 호출한 도구들이고, 턴(turn) 은 0개 이상의 스텝입니다. 턴은 첫 입력을 가져가기 전에 열리고, 더 처리할 것이 남지 않으면 닫힙니다. 아키텍처 문서는 이 흐름을 다음과 같이 적어 두고 있습니다.

turn/start
  claim next-step input plus one queued message
  assemble prompt sections + tool schemas
  -> agent/pre-step                   reject | enter(messages)
     reject, or a first enter rewritten empty -> close the turn with no step
     step/start
     append entered messages as user/message
     derive model history from the log
     agent/request -> llm/stream -> assistant/chunk* -> assistant/message
     tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
     step/end
     tools owe another request, or next-step input arrived -> claim -> next step
  -> agent/turn-stopping
turn/end

여기서 turn/*, step/*, user/message, assistant/*, tool/* 는 세션 로그에 남는 지속적인 이벤트이고, 나머지는 살아 있는 확장 지점입니다. agent/pre-step, agent/request, llm/stream 과 세 개의 tools/* 이벤트는 워터폴(waterfall)이라 리스너가 next() 를 호출해야 다음으로 넘어갑니다. next() 없이 반환하면 그 자리에서 흐름을 끊고 자기가 결정을 가져갑니다. 정책 플러그인이 도구 실행을 막는 일이 이 한 줄로 표현됩니다.

세션 로그는 모델이 보는 문맥의 출처입니다. deriveMessages() 가 로그에서 모델 히스토리를 만들어 내고, 원본 assistant/chunk 이벤트가 재생과 UI 표현을 보존합니다. 포크, 재개, 트랜스크립트, 텔레메트리, 영속화가 모두 이 스트림에서 파생됩니다. 문서가 "모델에 보이면 로그에 남는다(Model-visible means logged)" 로 정리한 규칙은 런타임 불변식으로 강제되어, 모델 요청에 도달하는 모든 것은 로그에서 재구성할 수 있어야 합니다.

DeepSeek Harness의 능력 이음새와 확장 지점

능력 이음새(capability seam)는 교체 가능한 능력 하나를 세 가지 역할로 나눈 것입니다. 인터페이스를 선언하는 서비스 정의, 그것을 구현하는 서비스 공급자, 그리고 그것을 사용하는 소비자이며 소비자는 보통 모델이 호출하는 도구입니다. 한 패키지가 여러 역할을 겸할 수는 있지만 역할 하나만으로는 이음새가 되지 않아서, 능력을 추가한다는 것은 셋을 모두 설계한다는 뜻이 됩니다.

이 구조 덕분에 공급자 하나를 바꾸면 제품 전체가 따라 바뀝니다. 파일시스템과 서브프로세스 공급자가 하나의 실행 세계를 공유하므로, 이 둘을 원격 샌드박스로 돌리면 Bash와 PTY, LSP가 함께 이동합니다. 공급자별로 갈라진 포크가 필요하지 않습니다.

새 동작을 어디에 붙일지는 저장소의 docs/architecture.md 에 표로 정리되어 있습니다. 자주 쓰일 만한 것을 옮기면 다음과 같습니다.

하려는 일 붙이는 자리
모델 공급자 추가 ctx.llm 에 어댑터 등록
모델이 호출할 능력 추가 ctx.tools 에 등록, 스키마가 프롬프트 조립에 합류
셸 실행 추가 ctx.shell 백엔드 등록
파일시스템 접근·정책 추가 ctx.fs 공급자 등록 또는 fs/* 이벤트 수신
프로세스 격리 ctx.sandbox 백엔드 사용
요청·도구·턴 가로채기 해당 agent/* 또는 tools/* 이벤트 사용
백그라운드 작업 추가 ctx.jobs 에 등록, job_* 도구로 수거·중지
살아 있는 세션 포크 ctx.sessions.fork(source, boundary?, childSessionId?)

모델에게 실제로 제공되는 도구는 저장소의 docs/tool-catalog.md 에 생성된 문서로 정리되어 있습니다. bashstr_replace_editor 같은 기본 도구 외에 glob · grep(패키징된 ripgrep 바이너리를 사용), terminal_* 여섯 개, subagent, skill, lsp, create_goal 계열, schedule_* 계열이 각각 별도 패키지로 들어 있습니다. 이 카탈로그는 손으로 쓰는 문서가 아니라 각 도구 플러그인을 실제 컨텍스트에 부팅해 ctx.tools.schemas() 를 읽어 생성하며, packages/*/tool-* 를 훑어 빠진 패키지가 있으면 생성이 실패하도록 되어 있습니다.

DeepSeek Harness 설치 및 실행

Node.js가 설치되어 있으면 npm 배포판을 바로 실행할 수 있습니다.

npx @deepseek-ai/dsh web

이 명령은 Web UI를 띄우고 기본값으로 http://127.0.0.1:3080 에서 서비스합니다. 저장소를 직접 체크아웃해 실행하려면 다음과 같습니다.

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web

Web UI가 뜨면 Settings → Models 에서 DeepSeek API 키를 넣고 저장합니다. 서버를 재시작하지 않아도 모델 경로가 바로 사용 가능해집니다.

dsh 프로세스는 자신이 실행된 디렉토리를 기본 파일시스템 위치로 쓰지만, 새로 뜬 Web UI에는 선택된 워크스페이스가 없습니다. Choose workspace 로 프로젝트 디렉토리를 추가해 선택해야 세션 작성기가 열립니다. 이후 세션을 시작하면 에이전트가 워크스페이스 파일을 읽고 편집하고 명령을 실행하고 작업을 위임하며 계획을 유지합니다. 활성 권한 정책에서 승인이 필요한 작업은 Web UI가 먼저 물어봅니다.

DeepSeek Harness의 Python SDK

Web UI 대신 프로그램에서 호출하는 경로도 있습니다. Python 3.10 이상, Git, 그리고 Linux x64·Linux arm64 또는 arm64 macOS 14 이상이 필요합니다. 설치된 런타임은 시스템에 Node.js가 없어도 동작합니다.

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk

SDK 호출은 컨텍스트 매니저 형태입니다.

from pathlib import Path

from deepseek_harness import DeepSeekHarness

config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()
workspace = Path("/absolute/path/to/workspace").resolve()
sessions = Path("/absolute/path/to/sessions").resolve()

with DeepSeekHarness(
    provider="deepseek-official",
    model="deepseek-v4-flash",
    max_tokens=49_152,
    cwd=str(workspace),
    session_root=str(sessions),
    cordis=str(config),
) as harness:
    result = harness.run(
        "Inspect the repository and fix the failing tests.",
        session_id="example-001",
    )

print(result.final_response)

DeepSeekHarness 는 번들된 런타임을 필요한 시점에 띄우고 컨텍스트 매니저가 끝날 때까지 재사용합니다. 같은 하네스와 같은 세션 id를 다시 쓰면 그 세션이 소유한 Bash 프로세스가 작업 디렉토리와 내보낸 변수, 셸 함수까지 그대로 유지됩니다. 독립된 작업이라면 새 세션 id를 쓰고, 앞선 대화를 이어가야 할 때만 id를 재사용합니다.

예제 구성이 어디까지 켜져 있는지는 문서에 표로 명시되어 있습니다. 모델이 호출할 수 있는 도구는 지속 bashstr_replace_editor 둘뿐이고, Bash 타임아웃은 300초, 편집기 출력 상한은 16,000자, 컨텍스트 압축은 꺼져 있으며, 파일시스템은 런타임 프로세스에 보이는 경로라면 절대 경로로 접근할 수 있는 맨 로컬 백엔드입니다. 하네스 정체성, 워크스페이스 프롬프트 텍스트, 스킬, 작업 도구, 압축 등 나머지 모델 대면 플러그인은 이 구성에서 빠져 있습니다. 권한이 danger-full-access 라는 점과 지속 PTY 백엔드가 POSIX 터미널을 요구해 Windows 에이전트를 지원하지 않는다는 점도 문서에 함께 적혀 있습니다.

DeepSeek Harness의 라이선스

DeepSeek Harness는 MIT 라이선스로 공개되어 있어 개인 및 상업적 목적으로 자유롭게 사용할 수 있습니다. third-party 의존성과 각각의 라이선스는 저장소의 THIRD_PARTY_NOTICES.md 에 별도로 공개되어 있습니다.

:house: DeepSeek Harness 공식 홈페이지

:books: DeepSeek Harness 사용자 가이드 문서

:github: DeepSeek Harness 프로젝트 GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 초안을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 댓글로 알려주시기를 부탁드립니다. :hugs:

이 도구를 직접 설치해 사용해보셨다면, :pytorch:파이토치 한국 사용자 모임:south_korea: 회원들을 위해 경험이나 팁을 댓글로 남겨주세요! :folded_hands: