이 노트의 목적은 DeepSeek Harness(dsh)를 단순히 “DeepSeek의 코딩 에이전트”로 소개하는 것이 아니라, 에이전트 런타임을 어떻게 조립·실행·기록·교체하는지 코드 수준에서 학습할 수 있게 만드는 것이다. 출발점은 2026-08-15-x-deepseek-harness-developer-previewdeepseek-harness이며, 이번 노트는 고정 커밋의 저장소 코드와 문서를 추가로 읽어 실행 경로와 실습 순서를 확장했다.

질문

  • DeepSeek Harness는 모델, 에이전트, Cordis, 프로필, 프리셋, 세션을 어떤 경계로 나누는가?
  • “Everything is a plugin”이 구호가 아니라면, 실제 부팅과 요청 처리에서 어떤 의미를 갖는가?
  • SessionEvent 로그가 왜 단순 대화 기록이 아니라 모델 컨텍스트·재생·fork·관측의 원장인가?
  • 도구·샌드박스·서브에이전트·워크플로우를 바꿀 때 실제로 바뀌는 것은 무엇이고, 아직 약한 경계는 어디인가?
  • 저장소를 어떤 순서로 읽고, 어떤 작은 실습을 해야 DSH의 설계 감각을 내 것으로 만들 수 있는가?

한 문장 결론

DeepSeek Harness는 DeepSeek 모델을 호출하는 앱이라기보다, Cordis의 가역적인 플러그인·스코프·이벤트 조합 위에 세션 이벤트 원장과 에이전트 루프를 올린 “에이전트 런타임을 만드는 런타임”이다.

공식 저장소의 설계는 다음 세 축으로 압축된다.

  1. 구성(composition) — Cordis 서비스와 설정 행을 번들·프로필·프리셋으로 조립한다.
  2. 실행(execution) — 에이전트 루프가 프롬프트, LLM 스트림, 도구 호출, 승인, 다음 step을 제어한다.
  3. 기록(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이고 호환성을 깨는 변경이 계속될 수 있다.

기존 요약과 이번 노트의 역할

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

한 기능을 조사할 때 이 네 질문을 순서대로 던지면 된다.

  1. 구성: 어떤 plugin/row가 기능을 제공하는가?
  2. 실행: 어느 event/loop 단계에서 호출되는가?
  3. 내구성: 무엇이 durable log에 기록되는가?
  4. 정책·표면: 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 파일을 무작정 읽지 말고 경계를 먼저 읽는다

추천 독서 순서는 다음과 같다.

순서읽을 파일/폴더얻어야 하는 답
1README.md, docs/architecture.mdDSH가 앱인지 런타임인지, 핵심 불변식이 무엇인지
2docs/cordis-primer.md, docs/cordis-tutorial/plugin·service·event·effect·config의 문법
3apps/cli/src/profile-boot.ts실제 부팅 레이어와 overlay 순서
4packages/bundle/base/cordis.patch.yml공통 host capability의 실제 목록
5packages/bundle/web-app/, headless/표면별로 어떤 capability가 추가·이동되는지
6packages/preset/agent-presets/, apps/cli/config/agent-presets/agent별 model-facing composition과 scope
7packages/core/session/무엇이 durable event이고 history로 어떻게 투영되는지
8packages/core/agent-loop/turn/step/request/tool 호출의 실제 순서
9packages/core/tools/도구 registry·승인·동시성·Code Mode 경계
10packages/sandbox/, mcp/, skill/, subagent/, workflow/capability seam의 교체 가능성과 현재 한계
11packages/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에게 전달상태를 바꾸지 않는 관찰·통지에 적합
parallellistener를 병렬 실행서로 독립적인 부수 효과에 적합
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는 다음을 한다.

  1. 선택한 profile을 로드한다.
  2. bundle 선언 순서대로 row를 쌓는다.
  3. profile patch를 적용한다.
  4. 사용자 home patch와 CLI --patch를 덧댄다.
  5. 동일한 id가 있으면 행 전체를 교체한다.
  6. 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

planelifetime주로 소유하는 것모델에게 직접 보이는가?
hostprocess/profileLLM provider, session persistence, sandbox policy, approval, registries, API gateway보통 간접적
agentagent/session scopepersona, system prompt sections, model-facing tools, skill/goal/plan/compaction composition
clientbrowser 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.jsontsconfig.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/end

6.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 config
  • request/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 lifecycleturn/start, turn/endturn 경계와 종료 상태
step lifecyclestep/start, step/end모델 요청 단위 경계
message surfaceuser/message, assistant/message모델 history의 재구성 입력
tool surfacetool/call, tool/result도구 실행과 결과의 원장
request metadatarequest/header, request/context모델 요청 구성의 증거
stream/UI detailassistant/chunk예, replay/UI용스트리밍 표시; history에 chunk를 중복 삽입하지 않음
live controlagent/pre-step, agent/request-error주로 live hookloop 제어·재시도·관찰 확장점
tool pipelinetools/pre-execute, tools/execute, tools/post-execute주로 live hook승인·가드·실행 middleware
raw LLM streamllm/streamlive 중심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/chunkassistant/message를 혼동하지 않는다

stream 중에는 여러 assistant/chunk가 들어온다. UI는 이를 즉시 렌더링해야 하지만, 다음 모델 요청이 chunk 하나하나를 독립적인 assistant message로 읽으면 history가 깨진다. 그래서 BlockAssembler가 stream을 완성된 assistant/message로 묶고 source sequence를 연결한다.

학습 시 확인할 것:

  1. chunk는 replay/UI에 왜 필요할까?
  2. 완성 message가 별도로 필요한 이유는 무엇일까?
  3. 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/result

tools/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 → model

Code 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 지도

capabilitydefinition/serviceprovider 예consumer 예교체 효과
LLMctx.llmDeepSeek direct SSE, 다른 adapteragent-loop requestprovider를 바꿔도 loop·session 원장은 유지할 수 있음
filesystemctx.fslocal, E2B/다른 remote 구현fs tools, editor, search파일 도구 전체의 실행 세계를 이동
subprocess/shellsubprocess·bash servicelocal, sandbox, remotebash, PTY, LSP 연계명령 실행 환경을 이동
sandboxctx.sandboxlocal policy, Landlock/ACL, remoteshell/fs/subprocess효과 범위와 권한 모델을 이동
persistencesession persistence serviceJSONL, SQLite 등session/fork/resume/UI저장 매체를 교체하되 event 모델은 유지
subagentctx.subagentsin-process, ACP, 외부 agentsubagent tools/workflowchild 실행 엔진·위임 대상을 교체
workflowctx.workflowEngineworker-thread workflowworkflow tool여러 child를 묶는 orchestration만 교체
MCPMCP client servicestdio, streamable HTTPserver-qualified tools외부 도구 생태계를 연결
skillctx.skillsfilesystem, embedded, HTTP providerskill discovery/invocation tool지식·행동 패키지 공급원을 교체

10.3 seam을 검증하는 세 가지 질문

새 기능이나 provider를 읽을 때 다음 질문을 적어본다.

  1. Definition: 소비자가 의존하는 가장 작은 typed contract는 무엇인가?
  2. Provider: lifecycle·scope·취소·오류·정책을 누가 책임지는가?
  3. 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, runtime register, invalidation을 제공한다.
  • modelInvocableuserInvocable로 호출 정책을 분리한다.
  • 모델 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 default

Web 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 persistence

Session 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 강점

  1. 교체의 파급 범위가 명확하다 — provider를 바꿀지, consumer까지 바꿀지 seam 단위로 생각할 수 있다.
  2. 모델 입력이 설명 가능하다 — request header/context와 durable message surface를 통해 무엇을 보았는지 재구성한다.
  3. 구성 변경을 코드 fork와 분리한다 — bundle/profile/patch/preset으로 제품 조합을 바꾼다.
  4. scope가 권한과 노출을 표현한다 — process-global capability를 모든 agent에 자동 노출하지 않는다.
  5. 실행·UI·persistence의 공통 원장이 있다 — 동일 event stream에서 projection을 만든다.
  6. Code Mode라는 두 번째 orchestration 표현이 있다 — 모델 turn 사이의 반복·분기·변환을 프로그램으로 옮길 수 있다.

17.2 구조적 비용과 위험

  1. 학습 곡선이 높다 — plugin, service, event, effect, scope, row, profile, preset을 동시에 이해해야 한다.
  2. whole-row patch는 강력하지만 취약하다 — 원본 row의 나머지 필드를 잃기 쉽다.
  3. developer preview다 — public API와 preset schema가 빠르게 바뀔 수 있다.
  4. same-world sandbox는 보안 경계가 아니다 — 권한이 큰 환경에서 실행하려면 실제 isolation provider와 별도 audit이 필요하다.
  5. Code Mode worker는 untrusted execution proof가 아니다 — OS 경계와 credential boundary를 별도로 설계해야 한다.
  6. workflow는 아직 durable workflow가 아니다 — foreground orchestration과 journaling/resume을 혼동하면 안 된다.
  7. MCP와 Python runtime의 범위가 문서의 인상보다 좁을 수 있다 — tools 중심 MCP, published TypeScript runtime이라는 현재 릴리스 경계를 확인해야 한다.
  8. 구성 가능성이 곧 품질은 아니다 — 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
  • SessionEvent
  • deriveMessages()
  • turn vs step
  • capability seam

문장을 만들 때 “어떤 파일에서 확인했는가?”를 같이 적는다. 설명은 출처 없는 비유보다 오래 간다.

단계 1 — Cordis만 따로 학습한다

읽을 것: docs/cordis-primer.mddocs/cordis-tutorial/의 first plugin, lifecycle/effects, services, events, config, composition/HMR.

연습 과제:

  1. study-plugin을 만든다.
  2. service 하나를 등록한다.
  3. typed event 하나를 declaration merge로 추가한다.
  4. listener를 ctx.effect로 묶는다.
  5. context dispose 뒤 service와 listener가 사라지는지 확인한다.
  6. waterfall handler에서 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 원장을 관찰하는 것이다.

  1. 새 session을 만든다.
  2. turn/start, step/start, user message를 append한다.
  3. fake LLM provider가 assistant/chunk를 여러 개 내도록 한다.
  4. BlockAssembler 뒤의 assistant/message를 확인한다.
  5. 도구 호출과 결과를 추가한다.
  6. deriveMessages()의 결과가 chunk 중복 없이 구성되는지 비교한다.
  7. request header/context에 실제 model-facing 설정이 남는지 확인한다.
  8. session을 fork하고 부모·자식 seq 범위를 비교한다.

가능하면 실제 API key 대신 fake provider를 사용한다. DSH를 배우는 첫 테스트에서 네트워크 모델 품질은 변수가 아니다.

단계 4 — 도구 pipeline을 하나씩 열어 본다

학습용 echo 도구에 다음 변형을 순서대로 적용한다.

  • 정상 실행
  • tools/pre-execute에서 deny
  • approval ask 후 사용자 거부
  • timeout
  • abort 전파
  • isConcurrencySafe false 두 개를 병렬 요청
  • 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

여기서 점수만 보지 말고 하네스가 모델에게 허용한 행동 공간과 검증 계약을 비교한다. 이것이 harness2026-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로 시작한다.

  1. fake ctx.fs를 만든다.
  2. 기존 fs tool consumer가 provider의 내부 구현을 모르고 동작하는지 확인한다.
  3. fake ctx.sandbox가 policy를 거부하는 경우를 만든다.
  4. session persistence를 memory provider에서 JSONL provider로 바꾼다.
  5. 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을 설명할 수 있다.
  • turnstep의 경계를 그릴 수 있다.
  • followup, steer, inject의 반영 시점을 구별할 수 있다.
  • agent/pre-step가 왜 authoritative한지 설명할 수 있다.
  • assistant/chunkassistant/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. 핵심 용어 사전

용어이 노트에서의 의미
Cordisservice·typed event·reversible effect·scope를 조합하는 기반 런타임/프레임워크
plugincapability를 context에 등록하고 lifecycle에 참여하는 조립 단위
bundle여러 plugin과 설정 row를 함께 배포하는 profile 구성 단위
profileWeb/headless 같은 실행 표면을 만드는 bundle 조합
patch rowid를 기준으로 config row 전체를 교체하는 overlay 항목
scopeservice/tool의 lifetime·visibility·상속 경계를 표현하는 공간적 컨텍스트
agent presetagent scope에 model-facing plugin composition을 제공하는 템플릿
turn하나의 논리적 작업 사이클
stepLLM request 하나와 그에 따른 결과·도구 처리 묶음
SessionEvent실행 사실을 append-only로 기록하는 durable event
surfaceevent log에서 모델/UI에 투영되는 message 표현
deriveMessagesevent/surface에서 다음 LLM request의 history를 만드는 투영 함수
capability seamservice definition·provider·consumer가 만나는 교체 경계
Code Moderun_code와 SDK로 여러 도구 호출을 프로그램 안에서 조합하는 표면
same-world sandboxhost world 안에서 policy로 효과를 제한하는 sandbox; 강한 격리와 다름
activationsession을 실제 process/worker 실행으로 깨운 생명주기 단위

22. 관련 노트와 비교해서 읽기

공식 자료와 고정 커밋 링크

이 노트의 한계

이 노트는 47f9438 한 커밋을 기준으로 한 구조 학습 자료다. 공개 preview가 바뀌면 profile row, preset schema, package API, provider boundary가 달라질 수 있다. 실제 도입 전에는 반드시 현재 commit에서 dump-config, 테스트, 권한 정책, persistence/recovery, provider source를 다시 검증해야 한다.