이 노트의 목적은 DeepSeek Harness(
dsh)를 단순히 “DeepSeek의 코딩 에이전트”로 소개하는 것이 아니라, 에이전트 런타임을 어떻게 조립·실행·기록·교체하는지 코드 수준에서 학습할 수 있게 만드는 것이다. 출발점은 2026-08-15-x-deepseek-harness-developer-preview와 deepseek-harness이며, 이번 노트는 고정 커밋의 저장소 코드와 문서를 추가로 읽어 실행 경로와 실습 순서를 확장했다.
질문
- DeepSeek Harness는 모델, 에이전트, Cordis, 프로필, 프리셋, 세션을 어떤 경계로 나누는가?
- “Everything is a plugin”이 구호가 아니라면, 실제 부팅과 요청 처리에서 어떤 의미를 갖는가?
SessionEvent로그가 왜 단순 대화 기록이 아니라 모델 컨텍스트·재생·fork·관측의 원장인가?- 도구·샌드박스·서브에이전트·워크플로우를 바꿀 때 실제로 바뀌는 것은 무엇이고, 아직 약한 경계는 어디인가?
- 저장소를 어떤 순서로 읽고, 어떤 작은 실습을 해야 DSH의 설계 감각을 내 것으로 만들 수 있는가?
한 문장 결론
DeepSeek Harness는 DeepSeek 모델을 호출하는 앱이라기보다, Cordis의 가역적인 플러그인·스코프·이벤트 조합 위에 세션 이벤트 원장과 에이전트 루프를 올린 “에이전트 런타임을 만드는 런타임”이다.
공식 저장소의 설계는 다음 세 축으로 압축된다.
- 구성(composition) — Cordis 서비스와 설정 행을 번들·프로필·프리셋으로 조립한다.
- 실행(execution) — 에이전트 루프가 프롬프트, LLM 스트림, 도구 호출, 승인, 다음 step을 제어한다.
- 기록(durability) — 모델에게 보이는 입력과 실행 결과를 append-only
SessionEvent로그로 남기고, history·replay·resume·fork·UI·telemetry를 그 로그에서 투영한다.
이 세 축을 놓치면 DSH를 “플러그인이 많은 TypeScript 앱”으로 오해한다. 반대로 세 축을 연결하면, 왜 세션 로그·프로필 패치·agent preset·capability seam이 한 설계 안에 함께 있는지 이해된다.
조사 범위와 검증 경계
고정한 대상
- 저장소:
deepseek-ai/deepseek-harness - 조사 커밋:
47f943859bef60e4160492346772ded9b24f765a - 릴리스 라인:
dsh@0.1.0-rc.5 - 조사 시점: 2026-08-17 KST
- 읽은 범위:
docs/architecture.md, Cordis primer/tutorial, profile boot, base/web/headless bundle, agent presets, session, agent loop, tools, sandbox, MCP, skills, subagent, workflow, LLM provider, code runtime
이번 조사에서는 소스를 고정 커밋으로 읽었지만 pnpm install, 빌드, Web UI 시작, API key 설정, 실제 모델 호출은 수행하지 않았다. 따라서 아래 내용은 코드·설정·문서의 구조 분석이지, 이 릴리스의 실제 실행 품질이나 보안 감사를 통과했다는 뜻이 아니다. 저장소는 developer preview이고 호환성을 깨는 변경이 계속될 수 있다.
기존 요약과 이번 노트의 역할
- 2026-08-15-x-deepseek-harness-developer-preview: 발표와 저장소를 빠르게 고정한 1차 요약
- deepseek-harness: 엔티티 관점의 패키지 지도·모드·핵심 불변식
- 이 노트: 학습 순서, 코드 경로, 설계 이유, 실습 과제, 한계와 보정을 한데 묶은 심층 답변
1. 먼저 mental model을 세운다
1.1 모델과 하네스를 분리하라
DeepSeek의 공식 설명은 에이전트를 “Model + Harness”로 구분한다. 모델은 다음 행동을 제안하는 확률적 추론기이고, 하네스는 그 제안을 실제 환경의 파일·셸·도구·세션·권한·검증에 연결하는 실행 시스템이다. 그러므로 DSH의 핵심 질문은 “DeepSeek 모델이 얼마나 똑똑한가?”가 아니라 다음에 가깝다.
모델이 낸 한 번의 출력이 어떤 컨텍스트로 만들어지고, 어떤 도구를 호출하며, 어떤 권한으로 실행되고, 그 결과가 다음 요청에 어떻게 다시 들어가며, 중간 상태를 어떻게 복구할 수 있는가?
1.2 DSH를 네 개의 평면으로 읽는다
| 평면 | 핵심 질문 | 대표 구성 |
|---|---|---|
| 구성 평면 | 무엇을 마운트할 것인가? | Cordis plugin, bundle, profile patch, agent preset |
| 실행 평면 | 한 turn을 어떻게 진행할 것인가? | ReactLoopAgent, agent/pre-step, LLM stream, tool scheduler |
| 내구성 평면 | 무엇을 사실로 남길 것인가? | SessionEvent, JSONL persistence, projection, fork |
| 정책·표면 평면 | 누가 무엇을 볼 수 있고 어디에서 실행하는가? | scope, tool registry, sandbox/approval, Web/headless/client |
한 기능을 조사할 때 이 네 질문을 순서대로 던지면 된다.
- 구성: 어떤 plugin/row가 기능을 제공하는가?
- 실행: 어느 event/loop 단계에서 호출되는가?
- 내구성: 무엇이 durable log에 기록되는가?
- 정책·표면: host 전체인가, 특정 agent preset인가, 브라우저 client인가? 권한은 어디서 정해지는가?
1.3 핵심 객체 관계
CLI / Web / Headless
│
▼
profile boot ── bundle rows ── profile/home/--patch overlays
│
▼
Cordis context
├─ host scope: LLM, session persistence, sandbox, policies, registries
├─ agent scope: preset, persona, prompt sections, model-facing tools
└─ client scope: browser UI, remote projection, interaction surface
│
▼
Agent loop
├─ inbox → pre-step → prompt/request
├─ LLM stream → assistant message
└─ tool scheduler → pre/execute/post → tool result
│
▼
append-only SessionEvent log
├─ deriveMessages() → next model request
├─ projection → UI / search / transcript / telemetry
└─ persistence → resume / fork / replay위 그림의 화살표가 곧 DSH의 설계 철학이다. 기능을 실행하는 경로와 기능을 기록하는 경로를 따로 만들지 않고, 실행 이벤트를 원장에 남긴 뒤 여러 소비자에게 투영한다.
2. 저장소를 읽는 올바른 순서
2.1 파일을 무작정 읽지 말고 경계를 먼저 읽는다
추천 독서 순서는 다음과 같다.
| 순서 | 읽을 파일/폴더 | 얻어야 하는 답 |
|---|---|---|
| 1 | README.md, docs/architecture.md | DSH가 앱인지 런타임인지, 핵심 불변식이 무엇인지 |
| 2 | docs/cordis-primer.md, docs/cordis-tutorial/ | plugin·service·event·effect·config의 문법 |
| 3 | apps/cli/src/profile-boot.ts | 실제 부팅 레이어와 overlay 순서 |
| 4 | packages/bundle/base/cordis.patch.yml | 공통 host capability의 실제 목록 |
| 5 | packages/bundle/web-app/, headless/ | 표면별로 어떤 capability가 추가·이동되는지 |
| 6 | packages/preset/agent-presets/, apps/cli/config/agent-presets/ | agent별 model-facing composition과 scope |
| 7 | packages/core/session/ | 무엇이 durable event이고 history로 어떻게 투영되는지 |
| 8 | packages/core/agent-loop/ | turn/step/request/tool 호출의 실제 순서 |
| 9 | packages/core/tools/ | 도구 registry·승인·동시성·Code Mode 경계 |
| 10 | packages/sandbox/, mcp/, skill/, subagent/, workflow/ | capability seam의 교체 가능성과 현재 한계 |
| 11 | packages/llm/llm-deepseek/, code-runtime/ | provider와 코드 실행의 현실적인 경계 |
packages/ 문서 수가 많다는 사실 자체에 압도될 필요는 없다. 먼저 architecture → profile-boot → session → agent-loop → tools의 세로 슬라이스를 완성한 뒤, sandbox·subagent·workflow 같은 가로 capability를 붙여 읽어야 한다.
2.2 재현용 시작 명령
공식 README 기준으로 Web UI를 가장 빨리 보는 경로는 다음이다.
# 배포 패키지 경로
npx @deepseek-ai/dsh web
# 기본 주소: http://127.0.0.1:3080
# 소스 경로
pnpm install
pnpm run build
pnpm dsh web
# 실제 profile 조합을 눈으로 확인
dsh --profile web --dump-config소스 빌드는 root package.json의 host/client TypeScript aggregate와 web build를 함께 타므로, 첫 학습에서는 UI보다 --dump-config가 더 많은 것을 알려준다. 위 명령은 이 조사 세션에서 실행하지 않았다. Node 요구사항은 ^22.19.0 || >=24.0.0, package manager는 pnpm 11.7.0으로 고정되어 있다.
3. Cordis를 이해해야 DSH가 보인다
3.1 Cordis plugin의 최소 단위
Cordis에서 plugin은 단순한 “기능 폴더”가 아니다. 공유 Context에 다음을 기여하는 등록 단위다.
- Service: 다른 plugin이 사용할 수 있는 named capability
- typed event: 실행 순서와 확장 지점을 표현하는 이벤트 어휘
- reversible effect: 등록한 handler·service·listener를 unload 시 되돌리는 효과
- dependency declaration:
inject로 필요한 service가 준비된 뒤 실행되도록 하는 의존성
개념적인 plugin은 다음처럼 생긴다. 실제 API 이름은 버전에 따라 바뀔 수 있으므로 “문법을 외우는 코드”가 아니라 “구성 단위의 모양”으로 읽는다.
export const name = 'study-plugin'
export const inject = ['systemPrompt', 'tools']
export function apply(ctx: Context) {
ctx.systemPrompt.section({
id: 'study-plugin-hint',
content: '현재 작업의 성공 조건을 먼저 확인하라.',
})
ctx.tools.register({
name: 'study_echo',
description: '학습용 입력을 그대로 반환한다.',
parameters: { /* JSON schema */ },
output: { /* output schema + renderer */ },
execute: async (input, runtime) => ({ value: input }),
})
ctx.effect(ctx.on('agent/pre-step', (event, next) => next(event)))
}여기서 중요한 것은 study-plugin이 중앙 Agent 클래스를 상속해 기능을 삽입하지 않는다는 점이다. systemPrompt, tools, agent/pre-step이라는 계약된 capability/event에 붙는다. 이 구조가 “privileged core 없음”의 실질적인 의미다.
3.2 의존성과 scope
inject는 “이 plugin이 어떤 서비스 없이는 의미가 없는가?”를 선언한다. 그 결과 plugin은 전역 싱글턴을 임의로 찾는 대신, 현재 Context와 scope에서 해석된 service를 받는다.
이것이 agent preset과 결합되면 같은 이름의 service라도 scope에 따라 다른 구현·설정이 보일 수 있다.
agent-local tools ── shadow ──> preset tools ── shadow ──> global tools
가까운 scope 먼 scope- 가까운 scope의 service/tool이 먼 scope를 가린다.
restrict는 상속받은 도구 집합을 줄이는 방향으로만 작동한다.- preset-local service는
isolate된 realm이 요구되며, process-global service를 몰래 publish하면 mount가 거부한다. - 따라서 “플러그인 교체 가능”은 파일을 import만 바꾸는 일이 아니라 어느 scope에 어떤 lifetime으로 마운트하는가까지 포함한다.
3.3 typed event와 waterfall
Cordis 이벤트는 “알림 방송”만이 아니다. DSH는 실행을 바꾸는 지점과 관찰만 하는 지점을 구분한다.
| 이벤트 방식 | 의미 | 학습 포인트 |
|---|---|---|
emit | 모든 listener에게 전달 | 상태를 바꾸지 않는 관찰·통지에 적합 |
parallel | listener를 병렬 실행 | 서로 독립적인 부수 효과에 적합 |
serial | 순서대로 실행 | 순서가 의미인 lifecycle에 적합 |
waterfall | 이전 handler가 다음 handler에 값을 넘김 | 승인·프롬프트·요청 수정처럼 결정권이 있는 파이프라인 |
Waterfall에서는 next()를 호출해야 뒤의 handler가 실행된다. 반환만 하면 short-circuit할 수 있다. 이 패턴은 agent/pre-step, agent/request, tools/pre-execute처럼 누가 실행을 허용할지 또는 어떤 요청을 모델에 보낼지 결정하는 지점에 쓰인다.
3.4 temporal/spatial composability를 실무 언어로 번역하기
Cordis의 “spatiotemporal composability”를 처음 읽을 때 논문 용어에 머물지 말고 두 질문으로 번역한다.
- 시간(temporal): 이 capability는 언제 활성화되고, 언제 효과가 되돌려지며, HMR·shutdown·재시작 때 어떤 순서로 사라지는가?
- 공간(spatial): 이 capability는 process 전체, 특정 session, 특정 agent, 특정 child scope 중 어디에서 보이는가?
DSH의 bundle/profile은 주로 시간과 부팅 순서를, agent preset과 scope는 공간과 상속을 표현한다. 이 둘을 합치면 “현재 이 agent가 이 순간에 볼 수 있는 도구·프롬프트·정책”을 구성할 수 있다.
4. 부팅: bundle, profile, patch, preset
4.1 설정은 정적 파일이 아니라 조합 프로그램이다
DSH 부팅은 단일 config.yml을 읽는 과정이 아니다. profile boot가 여러 레이어를 순서대로 쌓아 실행할 Cordis graph를 만든다.
bundle 1
→ bundle 2
→ profile patch
→ $DSH_HOME/cordis.patch.yml
→ --patch overlay
→ telemetry overlay
→ Cordis loader / plugin mount구체적으로 profile-boot.ts는 다음을 한다.
- 선택한 profile을 로드한다.
- bundle 선언 순서대로 row를 쌓는다.
- profile patch를 적용한다.
- 사용자 home patch와 CLI
--patch를 덧댄다. - 동일한
id가 있으면 행 전체를 교체한다. - loader/HMR/shutdown lifecycle을 준비하고, 실패는 조용히 삼키지 않고 드러낸다.
여기서 가장 위험한 오해는 patch를 YAML deep merge로 생각하는 것이다. agent-loop row의 한 필드만 바꾸고 싶어도 전체 row를 다시 써야 한다. 실습할 때는 먼저 dsh --profile web --dump-config로 원본 row를 복사한 뒤 최소 변경을 적용해야 한다.
4.2 base bundle의 의미
packages/bundle/base/cordis.patch.yml은 “모델이 곧바로 보는 모든 것”의 목록이 아니다. 공통 host capability의 조립판에 가깝다.
대표 row는 다음과 같다.
- timer, HMR, settings, credentials
llm,session, JSONL session persistence, session projection- agent registry, default model, retry, title, jobs
- local subprocess, sandbox, sandbox policy, approval, permission presets
- filesystem, search, bash/pwsh, persistent shell, terminal 계열
- skill registry/filesystem/tool, commands/feedback
- goal, plan mode, compaction, spill, token meter, todo
- subagent registry/provider/tool, workflow worker/tool
- web/search, tools registry, system prompt, agent loop
Web profile에서는 이 중 일부 model-facing row를 agent preset으로 이동시킨다. 즉 base가 모든 기능을 전역으로 공개하고 web이 UI만 붙이는 단순한 구조가 아니다. host가 소유할 capability와 agent가 볼 capability를 분리하기 위해 web overlay가 composition을 재배치한다.
4.3 host·agent·client 세 plane
| plane | lifetime | 주로 소유하는 것 | 모델에게 직접 보이는가? |
|---|---|---|---|
| host | process/profile | LLM provider, session persistence, sandbox policy, approval, registries, API gateway | 보통 간접적 |
| agent | agent/session scope | persona, system prompt sections, model-facing tools, skill/goal/plan/compaction composition | 예 |
| client | browser connection/UI | 대화창, trajectory, settings, tool/result rendering, remote method projection | 모델은 직접 보지 않음 |
이 plane 분리는 보안과 교체성 모두에 중요하다. 예를 들어 모델별 system prompt나 도구 집합은 agent preset으로 바꾸지만, session persistence나 credential provider를 브라우저 UI plugin이 임의로 소유하지 않는다.
Host와 client는 TypeScript declaration merging으로 같은 Context 키를 공유하지만 빌드 aggregate는 tsconfig.host.json과 tsconfig.client.json으로 분리된다. 한 프로그램에 합치면 host 전용 service와 client 전용 remote projection이 충돌할 수 있기 때문이다. Typert는 host 타입에서 client용 remote API projection을 만드는 역할을 한다.
4.4 agent preset은 “설정 파일”보다 scope 템플릿이다
agent preset은 agent.cordis.yml 하나를 읽어 옵션을 바꾸는 수준이 아니다.
- preset은 standing scope에 한 번 mount된다.
- session의 agent scope는 standing scope를 부모로 갖는다.
- service view는
agent → preset → global순서로 해석된다. - child agent는 부모 preset을 다시 mount하지 않고
composeFrom으로 합성된 parent standing composition에 join한다. - preset switch는 첫 output 이전에만 허용된다.
- 선택한 preset은 durable session header/event에 남는다.
- preset 파일은 persistence target이 아니라 authoring input이다.
이 구조가 필요한 이유는 “세션마다 tool set이 달라질 수 있지만 process-global registry를 매번 복제하고 싶지는 않다”는 요구를 만족하기 위해서다.
5. 실행 모드 네 가지를 비교한다
| 모드 | 목적 | 모델-facing 구성 |
|---|---|---|
| Standard | 일반적인 장기 코딩 작업 | 파일·셸·검색·스킬·plan·goal·subagent·workflow 등 풀 구성 |
| Code | 여러 도구 호출을 하나의 프로그램으로 오케스트레이션 | Standard + run_code와 SDK; 모델의 직접 도구 표면은 줄어듦 |
| Minimal | 최소 실행면에서 모델·하네스 비교 | persistent bash + str_replace_editor 중심; compaction 등 최소화 |
| Creator | 새 preset/플러그인/모드를 만드는 실험 | Standard + runtime inspection·in-memory Cordis plugin authoring |
모드는 “능력치가 다른 제품판”이 아니라 서로 다른 plugin composition이다. 같은 agent-loop와 session 원장을 유지하면서 tool/prompt/runtime surface를 바꾸는 것이 핵심이다. 따라서 Minimal은 기능이 부족한 Standard가 아니라, 하네스 변수를 줄여 모델 또는 실행 전략을 관찰하기 위한 실험 조건으로 읽는 편이 정확하다.
6. 한 번의 요청이 흐르는 전체 경로
6.1 turn과 step을 구분한다
- turn: 사용자의 한 작업이 종료될 때까지의 논리적 사이클. 0개 이상의 step을 포함할 수 있다.
- step: 모델 요청 1회와 그 요청이 낳은 assistant 출력·tool call 처리 묶음.
개념적 흐름은 다음과 같다.
turn/start
→ inbox 입력 claim
→ agent/pre-step
→ step/start
→ system prompt + runtime context + derived messages + tools
→ request/header + request/context 기록
→ LLM stream
→ assistant/chunk
→ assistant/message
→ tool/call
→ tools/pre-execute
→ approval / guard
→ tools/execute
→ tools/post-execute
→ tool/result
→ step/end
→ 다음 step이면 반복
→ agent/turn-stopping
turn/end6.2 inbox와 세 종류의 입력
ReactLoopAgent는 입력을 단일 inbox에서 받되, 언제 어느 경계에 들어갈지를 구분한다.
| 입력 API | 반영 시점 | 의미 |
|---|---|---|
followup | 다음 turn | 현재 turn이 끝난 뒤 새 사용자/시스템 follow-up으로 처리 |
steer | 다음 step | 현재 turn 안에서 다음 모델 요청을 조정 |
inject | 다음 step | 다음 step에 주입하지만, 새 입력만으로 loop를 깨우지는 않음 |
이 차이는 “메시지를 넣었다”와 “에이전트를 깨웠다”를 분리한다. 예컨대 백그라운드 관찰자가 context를 준비해도, 아무 이벤트 없이 모델을 계속 깨워 무한 루프를 만들지 않도록 할 수 있다.
6.3 agent/pre-step가 authoritative한 이유
pre-step에서 에이전트는 inbox를 claim하고, runtime context를 모델-visible user/message surface로 투영하고, prompt를 조립한다. 그 뒤 agent/pre-step waterfall이 최종 결정권을 갖는다.
따라서 pre-step handler는 다음을 할 수 있다.
- 이번 step을 거부하거나 연기한다.
- 주입할 context를 재작성한다.
- 권한·목표·계획·compaction 조건을 확인한다.
- 다음 단계에 들어갈 user message를 결정한다.
agent/* 이벤트를 단순 UI 알림으로 취급하면 이 경계를 놓친다. agent/pre-step는 loop의 제어면이다.
6.4 LLM request는 기록된 요청이다
step은 system prompt를 렌더링하고, session.deriveMessages()로 history를 얻고, scoped tool schema를 가져와 buildRequest를 만든다. 이후 다음 메타데이터를 기록한다.
request/header: system prompt, tools, adapter defaults, call configrequest/context: provider, model, context window 등
그 다음 llm.prepareCall을 통해 정확한 adapter 등록에 request를 고정하고 stream을 받는다. 이 구조는 “실제로 모델이 무엇을 봤는가?”를 현재 코드와 추측으로 재구성하지 않고 로그에서 확인하게 한다.
7. SessionEvent 로그: DSH의 가장 중요한 학습 포인트
7.1 세션은 transcript가 아니라 source of truth다
DSH에서 session은 대화 UI에 보이는 문자열 묶음이 아니다. append-only SessionEvent 스트림이 원장이고, 다른 표현은 그 원장의 projection이다.
SessionEvent log
├─ deriveMessages() → model history
├─ session projection → UI/read model
├─ JSONL/SQLite persistence→ durable storage
├─ replay/transcript → 사용자·디버거 표현
├─ fork/resume → 새 실행의 초기 상태
└─ telemetry/search → 관측·조회 표현공식 architecture의 핵심 불변식은 model-visible means logged다. 모델이 볼 수 있는 prompt 조각, runtime context, user/assistant/tool 결과는 로그에서 재구성되어야 한다. 그래야 “왜 이 모델 요청이 이 행동을 했는가?”를 나중에 설명할 수 있다.
7.2 durable event와 live event
| 종류 | 예 | 로그에 남는가? | 역할 |
|---|---|---|---|
| turn lifecycle | turn/start, turn/end | 예 | turn 경계와 종료 상태 |
| step lifecycle | step/start, step/end | 예 | 모델 요청 단위 경계 |
| message surface | user/message, assistant/message | 예 | 모델 history의 재구성 입력 |
| tool surface | tool/call, tool/result | 예 | 도구 실행과 결과의 원장 |
| request metadata | request/header, request/context | 예 | 모델 요청 구성의 증거 |
| stream/UI detail | assistant/chunk | 예, replay/UI용 | 스트리밍 표시; history에 chunk를 중복 삽입하지 않음 |
| live control | agent/pre-step, agent/request-error | 주로 live hook | loop 제어·재시도·관찰 확장점 |
| tool pipeline | tools/pre-execute, tools/execute, tools/post-execute | 주로 live hook | 승인·가드·실행 middleware |
| raw LLM stream | llm/stream | live 중심 | provider stream 처리 |
“live event는 전혀 기록하지 않는다”로 단정하면 안 된다. 일부 live 성격의 정보가 durable projection이나 오류 이벤트로 별도 기록될 수 있지만, 모델 history의 원장은 message surface와 그에 연결된 session event다. 분류의 목적은 모델 재구성에 필수인 이벤트와 실행 중 확장점을 구분하는 것이다.
7.3 surface operation이 필요한 이유
assistant/message, user/message, tool/result는 단순히 text를 append하지 않는다. 각 message-producing event는 surface operation과 source sequence reference를 갖는다.
append: 새 메시지를 추가한다.- replacement 계열: 기존 surface의 특정 source를 재작성·대체한다.
sourceEventSeqs: 이 surface가 어떤 durable event에서 유래했는지 연결한다.
이 설계 덕분에 compaction이나 tool-result pruning이 원본 로그를 지우지 않고 모델에 투영하는 표면만 바꿀 수 있다. 원본 실행 사실과 현재 모델에 보여주는 압축 history를 분리하는 것이다.
7.4 assistant/chunk와 assistant/message를 혼동하지 않는다
stream 중에는 여러 assistant/chunk가 들어온다. UI는 이를 즉시 렌더링해야 하지만, 다음 모델 요청이 chunk 하나하나를 독립적인 assistant message로 읽으면 history가 깨진다. 그래서 BlockAssembler가 stream을 완성된 assistant/message로 묶고 source sequence를 연결한다.
학습 시 확인할 것:
- chunk는 replay/UI에 왜 필요할까?
- 완성 message가 별도로 필요한 이유는 무엇일까?
- source sequence가 없으면 stream과 최종 message 사이의 provenance를 어떻게 잃을까?
7.5 fork와 seed
session fork는 임의의 열린 turn 중간에서 복제하는 작업이 아니다. 현재 구현은 open turn 밖에서 fork하도록 요구하고, seed된 session에는 session/end-seed 같은 경계 이벤트를 사용한다. 즉 fork는 “지금까지 기록된 실행 상태에서 새 branch를 시작한다”는 내구성 연산이다.
8. 도구 실행 파이프라인을 코드처럼 읽기
8.1 ToolDefinition은 함수 하나가 아니다
DSH 도구 정의는 execute(input)만 제공하는 얇은 함수가 아니다.
| 필드/개념 | 역할 |
|---|---|
name, description, parameters | 모델이 선택할 도구 계약 |
| output schema/render | 도구 결과를 모델·UI에 표현하는 계약 |
timeoutMs | 실행 시간 상한 |
isConcurrencySafe | 병렬 실행에 명시적으로 opt-in하는 플래그 |
presentCall, presentResult | 표면별 호출·결과 표시 |
finalizeContent | 실행 결과를 모델-facing content로 정리 |
execute | 실제 capability 소비 |
도구의 핵심은 “함수 구현”과 “모델에게 노출되는 계약”을 함께 관리하는 것이다. output schema나 renderer가 빠지면 결과는 실행되더라도 모델·UI·replay에서 일관되게 소비하기 어렵다.
8.2 scoped registry와 restrict
ctx.tools는 전역 Map 하나가 아니라 scope-aware layered registry다.
global tools
↓ inherited
preset tools
↓ shadow / restrict
agent-local tools- 더 가까운 scope의 같은 이름이 먼 scope를 shadow한다.
restrict는 상속 도구를 줄이는 방향으로 작동한다.- 새 local tool은 자기 scope에서 등록할 수 있다.
- guard는 monotonic해야 하므로 자식 scope가 부모의 제한을 몰래 풀 수 없다.
이 설계는 Standard와 Minimal의 차이를 단순 boolean flag가 아니라 도구 표면 자체의 다른 scope composition으로 만든다.
8.3 승인·가드·실행의 순서
도구 호출 하나는 대략 다음 pipeline을 탄다.
tool call 계획
→ tools/pre-execute waterfall
→ allow / deny / ask
→ approval provider
→ scope/policy guards
→ tools/execute waterfall around body
→ 실제 provider/capability
→ tools/post-execute
→ finalizeContent
→ tools/result observer
→ durable tool/resulttools/pre-execute는 실행 전에 거부·승인 요청을 만들 수 있고, approval은 정책과 사용자 선택에 따라 실행을 중단할 수 있다. cancellation 때도 이미 시작한 body를 drain하고, 시작되지 않은 호출에는 ABORTED_BEFORE_DISPATCH 같은 synthetic 결과를 남겨 원장을 끊지 않는다.
8.4 병렬 실행은 opt-in이고 fail-closed다
여러 tool call을 받았다고 자동 병렬화하지 않는다.
isConcurrencySafe === true인 도구만 병렬 후보가 된다.- 그 외는 exclusive로 취급한다.
- exclusive call은 barrier를 만들어 앞뒤 호출의 순서를 보장한다.
- rolling pool로 병렬 수를 제한한다.
- 실행은 병렬이어도 결과는 model order로 기록한다.
- 시작 전에 재분류해 앞선 도구가 남긴 상태 변화가 뒤 호출의 안전성을 바꿀 수 있게 한다.
파일 편집·git·셸처럼 서로의 상태를 바꿀 수 있는 도구를 섣불리 병렬화하지 않는 보수적인 설계다.
9. Code Mode: 도구를 줄여서 능력을 늘리는 이유
9.1 Native mode와 Code mode
Standard에서는 모델이 여러 도구를 직접 호출한다. Code Mode에서는 모델에게 run_code를 주고, 모델이 한 TypeScript 프로그램 안에서 SDK로 여러 도구를 조합한다.
Native mode
model → tool_a → result → model → tool_b → result → model
Code mode
model → run_code(program)
├─ SDK dispatch tool_a
├─ inspect result
├─ branch / loop / transform
└─ SDK dispatch tool_b
→ curated program result → modelCode Mode의 장점은 결정론적인 orchestration을 모델 출력 사이가 아니라 프로그램 안에 넣는 것이다.
- 반복·분기·결과 변환을 모델 재호출 없이 수행할 수 있다.
- 중간 결과를 코드가 필터링해 context 폭증을 줄인다.
- 모델은
run_code하나만 직접 선택하고, SDK가 nested tool을 호출한다. - nested dispatch도
tool/code-dispatch-start,tool/code-dispatch같은 log-only event로 추적된다. - 최종 curated result만 outer model history에 들어간다.
9.2 Code Mode가 만능 sandbox는 아니다
code-runtime은 worker thread backend로 프로그램을 실행하고 {value, logs, error} 형태로 결과를 돌려준다. 문서의 language 타입에는 TypeScript/Python이 언급되지만, 이 릴리스에서 공개 backend는 TypeScript다. 또한 “worker에서 돌린다”거나 “isolation”이라고 쓰였다는 사실만으로 보안 격리를 보장하지 않는다.
따라서 Code Mode를 다음처럼 해석하면 안 된다.
- “모델이 모든 도구를 자유롭게 호출하니 더 안전하다” — 아니다. SDK가 노출한 binding이 실제 권한 경계다.
- “worker thread니까 untrusted code sandbox다” — 아니다. 별도의 강한 OS·container·remote sandbox가 필요하다.
- “중간 출력이 전혀 기록되지 않는다” — 아니다. nested dispatch는 추적 이벤트를 남기고, outer result는 model history에 들어간다.
10. capability seam: 무엇을 어떻게 교체할 수 있는가
10.1 seam의 세 부분
DSH 문서의 seam은 추상 interface 하나를 뜻하지 않는다.
Service Definition ← Service Provider → Consumer
계약 구현 모델-facing tool / loop / UI세 부분 중 하나만 보면 교체 가능성을 과대평가한다. 소비자가 특정 provider의 내부 타입을 직접 import하면 definition은 있어도 실제 seam은 깨진다.
10.2 대표 seam 지도
| capability | definition/service | provider 예 | consumer 예 | 교체 효과 |
|---|---|---|---|---|
| LLM | ctx.llm | DeepSeek direct SSE, 다른 adapter | agent-loop request | provider를 바꿔도 loop·session 원장은 유지할 수 있음 |
| filesystem | ctx.fs | local, E2B/다른 remote 구현 | fs tools, editor, search | 파일 도구 전체의 실행 세계를 이동 |
| subprocess/shell | subprocess·bash service | local, sandbox, remote | bash, PTY, LSP 연계 | 명령 실행 환경을 이동 |
| sandbox | ctx.sandbox | local policy, Landlock/ACL, remote | shell/fs/subprocess | 효과 범위와 권한 모델을 이동 |
| persistence | session persistence service | JSONL, SQLite 등 | session/fork/resume/UI | 저장 매체를 교체하되 event 모델은 유지 |
| subagent | ctx.subagents | in-process, ACP, 외부 agent | subagent tools/workflow | child 실행 엔진·위임 대상을 교체 |
| workflow | ctx.workflowEngine | worker-thread workflow | workflow tool | 여러 child를 묶는 orchestration만 교체 |
| MCP | MCP client service | stdio, streamable HTTP | server-qualified tools | 외부 도구 생태계를 연결 |
| skill | ctx.skills | filesystem, embedded, HTTP provider | skill discovery/invocation tool | 지식·행동 패키지 공급원을 교체 |
10.3 seam을 검증하는 세 가지 질문
새 기능이나 provider를 읽을 때 다음 질문을 적어본다.
- Definition: 소비자가 의존하는 가장 작은 typed contract는 무엇인가?
- Provider: lifecycle·scope·취소·오류·정책을 누가 책임지는가?
- Consumer: 모델-facing tool이나 loop가 provider의 어떤 세부를 알아야 하는가?
세 번째 답이 “내부 구현의 특정 객체를 직접 알아야 한다”면 교체 seam은 아직 얕다. DSH의 아키텍처가 provider 교체를 계속 강조하는 이유는 모델·도구·샌드박스·스토리지 사이의 결합도를 낮추는 것이 장기 실행 에이전트의 핵심 비용이기 때문이다.
11. LLM provider는 기본값이지 정체성이 아니다
11.1 DeepSeek adapter의 위치
base bundle에는 deepseek-official provider와 deepseek-v4-flash 기본 모델 route가 장착되어 있다. llm-deepseek는 직접 fetch/SSE를 수행하고, credential/settings seam에서 API key와 모델 설정을 얻는다. provider 이름이 agent-loop에 박혀 있는 것이 아니라 ctx.llm contract 아래에 있다.
이 릴리스 문서의 provider 기본값에는 1,000,000 context window와 최대 256,000 output token 같은 모델 설정이 보이지만, 이것은 이 코드 버전의 adapter/config 기본값이지 모든 DeepSeek 모델이나 향후 릴리스의 보장값이 아니다. 모델 catalog는 advisory이고, 등록되지 않은 model id를 pass-through할 수 있는 구조다.
11.2 request retry를 loop와 분리한다
LLM retry는 dsh-llm-retry 같은 capability로 별도 장착된다. 이것은 agent loop가 “모델 호출 실패 → 어떻게 재시도할지”의 정책을 모두 갖지 않게 한다.
- loop는 request lifecycle과 session event를 책임진다.
- provider는 실제 모델 호출과 stream 오류 taxonomy를 책임진다.
- retry plugin은 backoff·재시도 가능 오류·횟수 제한을 책임진다.
이 분리가 있어야 provider를 바꿔도 session/agent loop의 durable contract가 흔들리지 않는다.
12. 서브에이전트, workflow, goal을 혼동하지 않는다
12.1 Subagent
ctx.subagents는 child agent provider registry다.
start: one-shot child를 실행한다.startContinuable: 지속 가능한 child session과 후속 activation을 만든다.followup: continuable child에 다음 작업을 보낸다.interrupt,report,listChildren,listDescendants: parent가 child를 관리한다.- provider capability로 output schema, depth limit, tool filter, persona 등을 조정한다.
child는 부모의 preset composition을 다시 중복 mount하지 않고 composeFrom으로 참여한다. child에는 durable descriptor와 delegation depth가 기록된다. continuable child의 durable session은 cold resume 경로를 가질 수 있지만, 한 process 안에서 살아 있는 activation은 process-local이다. 즉 session의 내구성과 실행 중 worker의 생존성은 같은 것이 아니다.
위임 정책도 별도 읽어야 한다. child가 부모의 명시적인 sandbox override를 물려받을 수 있어도 approval 정책과 dynamic 변경의 적용 시점은 provider가 정한 계약에 따른다. “subagent를 만들면 부모와 똑같은 권한으로 영원히 실행된다”라고 단정하지 말고, child session의 policy event와 provider 구현을 확인해야 한다.
12.2 Workflow
workflow는 모델이 작성한 JavaScript script로 여러 subagent/item을 fan-out하는 foreground orchestration이다.
- workflow holder가 실행과 dispose를 소유한다.
- total agents/items에 cap이 있다.
- workflow start/end, phase, log, child start/end event가 projection된다.
- script/model outcome을 이유로 run promise가 곧바로 reject되지 않고 result로 수렴하는 경로가 있다.
- 그러나 현재 package 문서의 명시적 경계는 journaling/resume 없음, nested/saved workflow 없음, token budget 없음이다.
그러므로 workflow는 “장기 실행 durable workflow engine”이라기보다 현재 릴리스에서는 세션 안에서 추적 가능한 foreground fan-out 도구로 읽는 것이 안전하다.
12.3 Goal과 plan
Goal은 session 안의 objective와 round를 durable event로 관리한다. 하지만 goal 자체가 scheduler나 evaluator는 아니다.
- 한 session에 현재 goal 하나를 둔다.
- round cap으로 반복을 제한한다.
- activation은 persistence되지 않는다.
- 실제 진행·검증·종료는 agent loop와 도구/사용자 이벤트가 담당한다.
Plan mode도 목표의 실행을 자동 보장하는 마법이 아니라, 모델-facing context와 loop가 계획을 다루도록 하는 composition이다. 상태를 기록하는 것과 상태를 실제로 진전시키는 것은 다른 capability라는 점이 핵심이다.
13. Skills와 MCP: 모두 “플러그인”이지만 같은 것은 아니다
13.1 Skill registry
Skill은 파일 하나를 system prompt에 무조건 붙이는 정적 문서가 아니다.
ctx.skills가 provider를 등록한다.- filesystem·embedded·HTTP provider가 source가 될 수 있다.
snapshot,list,get, runtimeregister, invalidation을 제공한다.modelInvocable과userInvocable로 호출 정책을 분리한다.- 모델 consumer는 skill tool을 통해 canonical
<skill_content>형태로 내용을 렌더한다. - discovery가 불완전할 수 있으므로 “목록에 없다 = 존재하지 않는다”로 단정하면 안 된다.
학습 포인트는 skill이 prompt 문자열과 capability 실행 사이의 중간 계층이라는 것이다. skill provider와 tool invocation policy를 분리하면, 지식 공급원과 모델이 실제로 호출할 수 있는 행동을 독립적으로 통제할 수 있다.
13.2 MCP client
현재 MCP package는 다음을 한다.
- stdio 또는 streamable HTTP transport로 server에 연결한다.
- tool을
mcp__<serverName>__<rawName>형태의 server-qualified name으로 등록한다. - 연결 generation을 fetch-then-swap하고, 충돌 시 generation 전체를 rollback한다.
- timeout·abort·reconnect를 관리한다.
- output schema의 지원 subset을 변환한다.
현재 문서상 resources와 prompts는 bridged되지 않고 deferred다. 따라서 “MCP 지원”을 전체 MCP primitive 지원으로 읽으면 안 된다. DSH의 현재 MCP seam은 외부 server의 도구를 안전한 이름공간과 tool pipeline 안으로 가져오는 경로에 가깝다.
14. Sandbox와 권한: 가장 위험한 오해를 먼저 제거한다
14.1 sandbox는 same-world capability다
ctx.sandbox의 policy mode는 대체로 다음 세 단계다.
| 모드 | 의미 | 주의 |
|---|---|---|
read-only | 읽기 중심 효과 제한 | 도구 자체의 정보 노출은 여전히 고려해야 함 |
workspace-write | 지정 workspace 안의 쓰기 허용 | workspace 경계·심볼릭 링크·하위 provider 확인 필요 |
danger-full-access | 제한을 거의 풀어 실행 | 사용자 승인·배포 경계가 핵심 |
이 provider는 기본적으로 같은 host world 안에서 효과를 제한한다. 자동으로 container, microVM, remote machine이 되는 것이 아니다. 강한 격리가 필요하면 filesystem/subprocess/sandbox provider를 실제 OS·컨테이너·원격 실행 구현으로 교체해야 한다.
14.2 정책 해석 순서
sandbox policy는 deployment default 하나만 읽지 않는다. 현재 구현은 대략 다음 우선순위의 resolved policy를 만든다.
session mode event override
> explicitly approved mode
> deployment defaultWeb base patch의 shipped 기본값은 DSH_PERMISSION_MODE가 없으면 workspace-write이고, danger-full-access가 아니면 approval은 ask 쪽으로 설정된다. 이는 “안전하다”는 보증이 아니라 배포 profile이 선택한 기본 정책이다.
또한 --host 0.0.0.0을 Web CLI가 거부하는 것은 네트워크 노출면을 줄이는 별도 안전장치다. 로컬 host bind를 제한하는 것과 파일/셸 실행을 강하게 격리하는 것은 서로 다른 문제다.
14.3 Landlock과 provider 선택
저장소에는 Linux Landlock native runner가 있지만, repository에 해당 구현이 있다는 사실이 모든 profile의 모든 실행이 Landlock으로 강제된다는 뜻은 아니다. 실제로 어떤 sandbox provider와 policy row가 조합되는지 dump-config와 bundle patch를 확인해야 한다.
15. persistence와 recovery를 설계 관점에서 읽기
15.1 로그가 먼저이고 저장소는 provider다
session persistence는 JSONL·SQLite 같은 저장 구현을 선택하는 seam이다. 핵심은 저장 포맷이 아니라 SessionEvent 계약이다.
agent / tools / UI
↓ append
Session API: JSON-serializable, contiguous seq, frozen event
↓ provider
JSONL / SQLite / another persistenceSession append는 JSON serializability와 surface contract를 검증하고, observer 오류가 원장 append 자체를 망치지 않도록 분리한다. 이 원칙은 로그를 “부수적인 debug output”이 아니라 실행의 진실로 만든다.
15.2 복구 가능한 것과 복구되지 않는 것을 구분한다
현재 설계에서 비교적 강한 쪽:
- durable session event에서 model history를 재구성할 수 있다.
- persistence provider가 session을 다시 읽을 수 있다.
- fork는 닫힌 turn 경계에서 새 branch를 만들 수 있다.
- continuable subagent는 activation이 사라져도 session persistence에서 cold resume할 경로를 가진다.
- UI·transcript·search는 event projection으로 다시 만들 수 있다.
현재 명시적으로 약한 쪽:
- workflow는 journaling/resume을 제공하지 않는다.
- live in-process activation은 process가 죽으면 그대로 보존된다고 볼 수 없다.
- Code runtime은 실행 간 상태를 유지하지 않는다.
- model-visible context가 아닌 live hook의 모든 내부 상태가 자동으로 durable해지는 것은 아니다.
즉 “session이 durable하다”는 문장을 “모든 실행이 exactly-once로 재개된다”로 확장하면 안 된다.
15.3 compaction, spill, pruning
컨텍스트가 커지면 compaction·spill·tool-result pruner가 model-facing projection을 줄이는 역할을 맡는다. 여기서 지켜야 할 원칙은 다음이다.
- 원본 tool call/result와 실행 이벤트를 함부로 삭제하지 않는다.
- 현재 모델에 보일 surface를 줄이더라도 provenance를 유지한다.
- compaction 결과도 모델-visible이면 SessionEvent로 기록한다.
- UI replay와 model history가 서로 다른 projection일 수 있음을 인정한다.
이것이 일반적인 “messages 배열을 잘라내기”와 DSH의 event-sourced context 관리가 다른 지점이다.
16. 기존 DeepSeek Harness 요약에서 보정할 점
2026-08-15-x-deepseek-harness-developer-preview에는 developer preview 시점의 핵심 한계가 잘 정리되어 있지만, 이번 고정 커밋의 세부 코드까지 읽으면 다음처럼 더 정밀하게 표현하는 편이 좋다.
| 거친 표현 | 더 정확한 표현 |
|---|---|
| subagent가 process-local이고 durable report mailbox가 없다 | 실행 중 activation은 process-local이지만, continuable child session·descriptor·settlement/cold-resume 경로가 있다. 다만 범용 durable mailbox로 확대하면 안 된다. |
| workflow에 resume이 없다 | 현재 workflow package가 journaling/resume을 제공하지 않는다는 의미다. session 자체의 event persistence와 workflow 내부 재개는 구분해야 한다. |
| MCP는 tools만 된다 | 현재 mcp-client가 model-facing tool bridge 중심이고 resources/prompts는 deferred다. |
| sandbox가 있다 | same-world policy provider가 있다. 강한 보안 격리나 remote execution은 별도 provider가 필요하다. |
| plugin이면 어디서나 교체된다 | definition/provider/consumer 세 seam이 분리되고 scope·lifetime·policy가 맞아야 실제 교체 가능하다. |
이 보정은 기존 노트를 반박하기 위한 것이 아니라, “preview의 부재”와 “현재 코드가 제공하는 최소 경로”를 구분하기 위한 것이다.
17. DSH의 강점과 구조적 비용
17.1 강점
- 교체의 파급 범위가 명확하다 — provider를 바꿀지, consumer까지 바꿀지 seam 단위로 생각할 수 있다.
- 모델 입력이 설명 가능하다 — request header/context와 durable message surface를 통해 무엇을 보았는지 재구성한다.
- 구성 변경을 코드 fork와 분리한다 — bundle/profile/patch/preset으로 제품 조합을 바꾼다.
- scope가 권한과 노출을 표현한다 — process-global capability를 모든 agent에 자동 노출하지 않는다.
- 실행·UI·persistence의 공통 원장이 있다 — 동일 event stream에서 projection을 만든다.
- Code Mode라는 두 번째 orchestration 표현이 있다 — 모델 turn 사이의 반복·분기·변환을 프로그램으로 옮길 수 있다.
17.2 구조적 비용과 위험
- 학습 곡선이 높다 — plugin, service, event, effect, scope, row, profile, preset을 동시에 이해해야 한다.
- whole-row patch는 강력하지만 취약하다 — 원본 row의 나머지 필드를 잃기 쉽다.
- developer preview다 — public API와 preset schema가 빠르게 바뀔 수 있다.
- same-world sandbox는 보안 경계가 아니다 — 권한이 큰 환경에서 실행하려면 실제 isolation provider와 별도 audit이 필요하다.
- Code Mode worker는 untrusted execution proof가 아니다 — OS 경계와 credential boundary를 별도로 설계해야 한다.
- workflow는 아직 durable workflow가 아니다 — foreground orchestration과 journaling/resume을 혼동하면 안 된다.
- MCP와 Python runtime의 범위가 문서의 인상보다 좁을 수 있다 — tools 중심 MCP, published TypeScript runtime이라는 현재 릴리스 경계를 확인해야 한다.
- 구성 가능성이 곧 품질은 아니다 — provider와 plugin을 많이 바꿀 수 있어도 prompt·tool contract·verification이 나쁘면 성능과 안정성은 낮다.
18. 학습 로드맵: 읽기에서 손으로 검증하기까지
단계 0 — 용어 카드를 만든다
다음 12개를 한 문장으로 설명할 수 있어야 한다.
- Cordis context
- service definition/provider/consumer
- effect와 dispose
- bundle
- profile patch
- whole-row replacement
- host scope
- agent preset standing scope
SessionEventderiveMessages()- turn vs step
- capability seam
문장을 만들 때 “어떤 파일에서 확인했는가?”를 같이 적는다. 설명은 출처 없는 비유보다 오래 간다.
단계 1 — Cordis만 따로 학습한다
읽을 것: docs/cordis-primer.md → docs/cordis-tutorial/의 first plugin, lifecycle/effects, services, events, config, composition/HMR.
연습 과제:
study-plugin을 만든다.- service 하나를 등록한다.
- typed event 하나를 declaration merge로 추가한다.
- listener를
ctx.effect로 묶는다. - context dispose 뒤 service와 listener가 사라지는지 확인한다.
waterfallhandler에서next()를 호출했을 때와 호출하지 않았을 때의 차이를 기록한다.
목표는 DSH 기능을 만드는 것이 아니라, “기능을 등록하고 되돌리는 런타임”의 감각을 얻는 것이다.
단계 2 — profile composition을 눈으로 비교한다
다음 세 구성의 dump를 나란히 저장한다.
dsh --profile web --dump-config
dsh --profile headless --dump-config
dsh --profile web --patch ./my.patch.yml --dump-config비교할 열:
- 어떤 row가 공통 base인가?
- web만 추가하는 host/client row는 무엇인가?
- base의 model-facing row 중 agent preset으로 이동한 것은 무엇인가?
agent-loop,system-prompt,tools,sandbox-policy가 어느 plane에 있는가?- 같은
id의 row를 patch하면 원본의 어떤 필드까지 복사해야 하는가?
결과를 “모드별 기능 목록”이 아니라 row diff와 scope diff로 기록한다.
단계 3 — session log를 작은 테스트로 관찰한다
실습 목표는 모델을 잘 부르는 것이 아니라 event 원장을 관찰하는 것이다.
- 새 session을 만든다.
turn/start,step/start, user message를 append한다.- fake LLM provider가
assistant/chunk를 여러 개 내도록 한다. - BlockAssembler 뒤의
assistant/message를 확인한다. - 도구 호출과 결과를 추가한다.
deriveMessages()의 결과가 chunk 중복 없이 구성되는지 비교한다.- request header/context에 실제 model-facing 설정이 남는지 확인한다.
- session을 fork하고 부모·자식 seq 범위를 비교한다.
가능하면 실제 API key 대신 fake provider를 사용한다. DSH를 배우는 첫 테스트에서 네트워크 모델 품질은 변수가 아니다.
단계 4 — 도구 pipeline을 하나씩 열어 본다
학습용 echo 도구에 다음 변형을 순서대로 적용한다.
- 정상 실행
tools/pre-execute에서 deny- approval ask 후 사용자 거부
- timeout
- abort 전파
isConcurrencySafefalse 두 개를 병렬 요청- true 하나와 exclusive 하나를 섞은 요청
- output schema/render 변경
- child scope에서
restrict적용
각 실습마다 “모델에 보이는 결과”, “로그에 남는 결과”, “UI에 보이는 표현”을 세 줄로 나눠 기록한다.
단계 5 — Minimal과 Standard를 같은 task로 비교한다
동일한 파일 편집 task를 Minimal과 Standard에 넣고 아래를 측정한다.
- direct tool schema 수
- prompt section 수
- step 수
- tool call 순서
- session event 수
- 오류 후 복구 경로
- 결과를 검증할 수 있는 artifact
여기서 점수만 보지 말고 하네스가 모델에게 허용한 행동 공간과 검증 계약을 비교한다. 이것이 harness와 2026-07-11-harness-bench-research-and-open-projects를 DSH에 연결해 읽는 방법이다.
단계 6 — Code Mode를 Native Mode와 대조한다
동일한 파일 목록·검색·수정 task를 두 방식으로 실행한다.
- Native: 모델이
search → read → edit → test를 각각 호출 - Code:
run_code가 검색 결과를 필터링하고 필요한 파일만 읽은 뒤 수정
관찰할 것:
- 모델 round-trip 수
- outer context에 들어가는 결과의 크기
- nested dispatch event의 provenance
- 코드 프로그램 오류와 tool 오류의 구분
- worker runtime이 유지하지 않는 상태
단계 7 — capability provider를 교체해 본다
처음부터 remote sandbox를 만들지 말고 fake provider로 시작한다.
- fake
ctx.fs를 만든다. - 기존 fs tool consumer가 provider의 내부 구현을 모르고 동작하는지 확인한다.
- fake
ctx.sandbox가 policy를 거부하는 경우를 만든다. - session persistence를 memory provider에서 JSONL provider로 바꾼다.
- provider dispose 순서와 event log의 연속성을 확인한다.
이 실습의 합격 기준은 “다른 구현을 연결했다”가 아니라, consumer 코드가 provider의 내부 타입을 몰라도 되는가다.
단계 8 — headless runner로 표면을 줄인다
Web UI는 많은 것을 가려준다. headless bundle은 한 task를 persisted agent에 넣고 turn/end까지 기다린 뒤 최종 assistant를 출력하는 경로를 드러낸다.
- UI가 없어도 session/persistence/agent-loop가 충분한가?
- 종료 code가
turn/end완료 여부와 어떻게 연결되는가? - 중간 event를 어디에서 관찰할 수 있는가?
- headless에서 approval이 필요하면 어떤 provider가 응답하는가?
19. 학습자를 위한 설계 질문과 모범 답안
질문 1. 왜 모든 것을 plugin으로 만들면 좋은가?
기능별로 lifecycle·scope·provider·consumer를 분리하고, 실행 표면을 profile/preset으로 재조합할 수 있기 때문이다. 단순히 파일을 잘게 나누는 이점이 아니라, 설치·마운트·dispose·관찰의 단위를 기능과 일치시키는 이점이다.
질문 2. 왜 중앙 Agent 클래스에 다 넣지 않는가?
중앙 클래스가 도구·메모리·sandbox·UI·persistence를 모두 알면 새로운 provider나 surface를 바꿀 때 결합도가 폭발한다. DSH는 loop가 필요로 하는 contract와 capability provider를 분리해 loop의 durable event semantics를 안정화하려 한다.
질문 3. 왜 session log를 source of truth로 두는가?
모델 history, UI transcript, telemetry, replay, fork가 각자 상태를 가지면 서로 어긋난다. 한 append-only 원장에서 projection을 만들면 “실행 사실”과 “현재 표현”을 분리하면서 provenance를 유지할 수 있다.
질문 4. 왜 모델-visible 입력은 반드시 로그에 있어야 하는가?
프롬프트 조각이나 runtime context가 로그 밖에서 주입되면, 나중에 같은 요청을 재구성할 수 없고 실패 원인을 설명할 수 없다. traceability는 단순 디버깅이 아니라 재현·평가·안전 감사의 전제다.
질문 5. 왜 tool parallelism을 opt-in으로 만드는가?
도구는 외부 상태를 바꾼다. 함수가 pure인지, 같은 workspace를 건드리지 않는지, approval/policy가 독립적인지 자동 추론하기 어렵기 때문에 기본을 exclusive로 두고 명시적으로 안전성을 선언하게 한다.
질문 6. 왜 Code Mode에서 직접 도구를 숨기는가?
도구 호출 사이의 반복·조건·데이터 변환을 프로그램으로 옮겨 모델 round-trip과 context noise를 줄이기 위해서다. 대신 SDK binding 자체가 새로운 capability surface이므로 안전성이 자동으로 증가하는 것은 아니다.
질문 7. preset과 bundle은 어떻게 다른가?
bundle은 profile을 배포·부팅하는 비교적 큰 구성 단위이고, preset은 agent scope에 model-facing composition을 제공하는 런타임 템플릿이다. bundle이 host/client surface를 만들고 preset이 각 agent의 보이는 세계를 조정한다고 이해하면 된다.
질문 8. session이 durable한데 왜 workflow resume이 없는가?
session event persistence는 이미 발생한 agent/tool/message를 저장하는 계약이다. workflow resume은 script의 program counter, fan-out child 상태, 중간 결과, 재실행 idempotency까지 저장·재개해야 하는 별도의 문제다.
질문 9. workspace-write면 안전한가?
아니다. 이는 deployment default policy 이름일 뿐이다. host의 credential·네트워크·심볼릭 링크·subprocess·provider 구현과 approval을 함께 봐야 하며, 강한 격리가 필요하면 container/microVM/remote provider가 필요하다.
질문 10. DSH의 핵심 경쟁력은 DeepSeek 모델인가?
이 저장소 구조에서 더 본질적인 경쟁력은 모델 교체가 가능한 실행·기록·구성 substrate다. DeepSeek provider는 훌륭한 기본값일 수 있지만, 하네스 설계의 학습 대상은 provider와 독립인 loop·session·scope·seam이다.
20. 시험용 체크리스트
다음 질문에 파일 경로와 event/서비스 이름을 붙여 답하지 못하면 해당 부분을 다시 읽는다.
-
dsh --profile web --dump-config의 row overlay 순서를 설명할 수 있다. - whole-row replacement와 deep merge의 차이를 예로 들 수 있다.
- host, agent, client plane의 lifetime을 비교할 수 있다.
- agent preset standing scope와 child
composeFrom을 설명할 수 있다. -
turn과step의 경계를 그릴 수 있다. -
followup,steer,inject의 반영 시점을 구별할 수 있다. -
agent/pre-step가 왜 authoritative한지 설명할 수 있다. -
assistant/chunk와assistant/message의 차이를 설명할 수 있다. -
deriveMessages()가 session event에서 어떤 projection을 만드는지 설명할 수 있다. - request header/context가 traceability에 왜 필요한지 설명할 수 있다.
- tool approval pipeline과 cancellation 결과를 그릴 수 있다.
- concurrency safe opt-in과 exclusive barrier를 설명할 수 있다.
- Native Mode와 Code Mode의 model-facing surface를 비교할 수 있다.
- service definition/provider/consumer 세 역할을 한 capability에 적용할 수 있다.
- same-world sandbox와 container/microVM sandbox를 구분할 수 있다.
- MCP tools 지원과 resources/prompts deferred의 차이를 말할 수 있다.
- continuable subagent의 durable session과 process-local activation을 구분할 수 있다.
- workflow의 현재 journaling/resume 한계를 말할 수 있다.
- goal state가 scheduler/evaluator가 아닌 이유를 설명할 수 있다.
- 실제 런타임 품질을 검증하지 않은 조사라는 경계를 말할 수 있다.
21. 핵심 용어 사전
| 용어 | 이 노트에서의 의미 |
|---|---|
| Cordis | service·typed event·reversible effect·scope를 조합하는 기반 런타임/프레임워크 |
| plugin | capability를 context에 등록하고 lifecycle에 참여하는 조립 단위 |
| bundle | 여러 plugin과 설정 row를 함께 배포하는 profile 구성 단위 |
| profile | Web/headless 같은 실행 표면을 만드는 bundle 조합 |
| patch row | id를 기준으로 config row 전체를 교체하는 overlay 항목 |
| scope | service/tool의 lifetime·visibility·상속 경계를 표현하는 공간적 컨텍스트 |
| agent preset | agent scope에 model-facing plugin composition을 제공하는 템플릿 |
| turn | 하나의 논리적 작업 사이클 |
| step | LLM request 하나와 그에 따른 결과·도구 처리 묶음 |
| SessionEvent | 실행 사실을 append-only로 기록하는 durable event |
| surface | event log에서 모델/UI에 투영되는 message 표현 |
| deriveMessages | event/surface에서 다음 LLM request의 history를 만드는 투영 함수 |
| capability seam | service definition·provider·consumer가 만나는 교체 경계 |
| Code Mode | run_code와 SDK로 여러 도구 호출을 프로그램 안에서 조합하는 표면 |
| same-world sandbox | host world 안에서 policy로 효과를 제한하는 sandbox; 강한 격리와 다름 |
| activation | session을 실제 process/worker 실행으로 깨운 생명주기 단위 |
22. 관련 노트와 비교해서 읽기
- deepseek-harness — 이 노트의 엔티티 요약과 패키지 지도를 빠르게 복습한다.
- 2026-08-15-x-deepseek-harness-developer-preview — 발표·공식 페이지·초기 한계 기록을 원문 provenance와 함께 확인한다.
- harness — 하네스의 일반 개념과 모델/실행 시스템 분리를 연결한다.
- moc-ai-agents-harness — DSH를 다른 하네스·자가개선·평가 노트 사이에 배치한다.
- 2026-07-11-harness-bench-research-and-open-projects — 같은 모델에서 하네스 구성이 성능·실패 양상을 바꿀 수 있다는 평가 관점으로 확장한다.
- loop-engineering — durable state·검증·위임·핸드오프를 DSH의 loop와 비교한다.
- 2026-08-15-managed-deep-agents-overview — 관리형 deep agent의 session·harness·sandbox 분리를 비교한다.
- hermes-agent — 다른 오픈소스 agent harness의 tool·memory·session 설계와 대조한다.
공식 자료와 고정 커밋 링크
- DeepSeek Harness repository
- Architecture at
47f9438 - Cordis primer at
47f9438 - Agent lifecycle at
47f9438 - Session implementation at
47f9438 - Agent loop at
47f9438 - Cordis project
- Cordis spatiotemporal composability paper
이 노트의 한계
이 노트는 47f9438 한 커밋을 기준으로 한 구조 학습 자료다. 공개 preview가 바뀌면 profile row, preset schema, package API, provider boundary가 달라질 수 있다. 실제 도입 전에는 반드시 현재 commit에서 dump-config, 테스트, 권한 정책, persistence/recovery, provider source를 다시 검증해야 한다.