Skip to content

One sentence ​

DeepSeek Harness is a plugin tree: a running dsh process is a set of Cordis plugins composed at boot from ordered patch layers, where every part of the product — model adapter, tool registry, session log, even the agent loop itself — is a plugin mounted beside the others and replaceable from configuration.

There is no privileged core to patch. You extend dsh by mounting a plugin beside the rest, and every registration is an effect that unwinds when its plugin unloads.

Cordis context as the backbone ​

Cordis is the vendored plugin framework underneath dsh. Every capability runs through one Context (ctx). A service claims a stable ctx.<key> — ctx.tools, ctx.llm, ctx.sessions — and other plugins find it by key rather than importing a concrete implementation. Dependencies are declared via inject so load order is expressed as service requirements, not a manual boot sequence.

Plugins contribute three things to the shared context:

  • Services — a value installed at ctx.<key> (the session store, the LLM adapter registry).
  • Typed events — names declared through TypeScript declaration merging and dispatched as emit, waterfall, parallel, or serial.
  • Reversible effects — registrations (prompt sections, tool schemas, listeners, providers) installed through ctx.effect() or ctx.on() that unwind predictably on reload and teardown.

The result is that a capability "seam" has three roles — a Service Definition declaring the interface, a Service Provider implementing it, and a Consumer using it — and swapping one provider changes the whole product. For example, filesystem and subprocess providers share one execution world, so pointing them at a remote sandbox moves Bash, PTY, and LSP together with no provider forks.

Profiles and bundles ​

Two package.json-level concepts structure the tree:

ConceptDefinitionDeclared where
ProfileA named composition stored under $DSH_HOME/profiles/<name>, listing the bundles it stacks, any out-of-tree plugins it installs, and the user's own cordis.patch.ymldsh.profile.bundles in the profile's package.json
BundleA distribution format for Cordis config rows plus the code they mount — whatever it inserts stays patchable by layers abovedsh.bundle.patch in the bundle's package.json

web and headless ship as profile templates. Three bundles cover the layers:

  • @deepseek-ai/dsh-base — the first layer of every profile: model adapters, tools, persistence, sandbox and approval policy, settings, credentials, telemetry.
  • @deepseek-ai/dsh-web-app — adds the browser application.
  • @deepseek-ai/dsh-headless — adds a one-shot runner with no server at all.

Layers apply to an empty entry list in order: each bundle in the profile's list, then the profile's cordis.patch.yml, then the home-level patch file, then any --patch overlay. A patch targets a row by id and replaces its whole config, or inserts new rows.

The core-packages table ​

packages/core holds the "product API spine" — the stable surface plugins build against:

PackageOwnsctx key
core/sessionThe append-only SessionEvent log and in-memory storectx.sessions
core/system-promptPrompt-section and tool-schema assemblyctx.systemPrompt
core/toolsThe scoped tool registry and guarded execution pipelinectx.tools
core/agentThe Agent interface, live registry, and agent/* eventsctx.agents
core/agent-loopThe default driver implementing that interfacectx.agentLoop
core/scopeThe per-agent scoped-registration primitivelibrary — no key
llm/llmMessage and stream vocabulary plus the adapter seamctx.llm

Note the deliberate split: core/agent owns the public Agent contract, while core/agent-loop is its single default implementation. Extension plugins depend on the agent seam (including when they need the initiating agent) and never on agent-loop directly, so the driver stays swappable. core/scope is the one non-service package: a dependency-free library (createScope/scopeOf/scopeTarget) that sits below session/ and system-prompt/ so they can consume it without a cycle.

Host and client split ​

dsh builds the same source tree against two faces (the root tsconfig.host.json and tsconfig.client.json, switched by --env.DSH_BUILD_FACE):

  • Host — the Node process: runs the webserver, API gateway, sandbox, subprocess and filesystem providers, and the agent loop. This is what dsh web starts.
  • Client — the browser bundle served to the page. It mounts web-client modules and the browser halves of dual-half packages (for example the Cordis dynamic-package runner at packages/extensions/cordis-client-runner), communicating with the host through the API gateway (packages/api/gateway).

A host-only package (e.g. @deepseek-ai/dsh-cordis-host-runner) is this process's own; a package with a browser half has to be carried out by a page, so the host suspends and the browser answers. The two faces never mix in one aggregate: the host aggregate excludes browser-half packages so each face keeps its own TypeScript program.

How layers compose at boot ​

Boot is one call in packages/boot/app-boot:

text
dsh web
  └► bin.ts                 parseDshArgs → { mode: 'profile', profile: 'web', args, patches }
      └► profile-boot.ts    runProfile
          ├► composeProfile ─ healProfilesModuleFallback
          │                  loadProfile → bundle layers (base, web-app)
          │                  + profile cordis.patch.yml
          │                  + home cordis.patch.yml
          │                  + --patch overlays
          │                  + telemetry switch + shipped preset root
          ├► boot(name, rootConfig, patches, prepare)
          │     new Context (Cordis)
          │     ctx.plugin(Loader)                cordis-plugin-loader
          │     prepare: provide env + cmdlineArgs/appExit
          │     mountRootInclude → 'cordis:include' builtin
          │     Loader creates the root Include entry
          │     Include reads cordis.yml (the empty profile root)
          │     include applies the flattened patch list,
          │     mounting every plugin row → ctx.sessions, tool registry, …
          │     await loader, assertEntriesActivated
      └► watchUserPatches  keep cordis.patch.yml hot-reloaded via HMR

The prepare callback runs before any config-tree entry mounts, providing the launch environment snapshot and ctx.cmdlineArgs/ctx.appExit — launcher facts, not config. The mountRootInclude call mounts the statically imported Include as the cordis:include builtin plus cordis:group, so the config tree's rows resolve without depending on the included tree's own specifier resolution.

The plugin-tree layering diagram ​

text
                 ┌──────────────────────────────────────────────┐
   --patch       │ overlay patches (CLI, highest precedence)    │  ─┐
                 ├──────────────────────────────────────────────┤  │
   $DSH_HOME/    │ home cordis.patch.yml (every profile)        │  │
   cordis.patch. │                                               │  │
   yml           │                                               │  │ applied
                 ├──────────────────────────────────────────────┤  │ bottom-up
   profile/      │ profile cordis.patch.yml (user's own layer)  │  │ (later wins)
   cordis.patch. │                                               │  │
   yml           ├──────────────────────────────────────────────┤  ┘
                 │ @deepseek-ai/dsh-web-app  (browser surface)  │
                 │   └ or @deepseek-ai/dsh-headless (one-shot)  │ patch layers
                 ├──────────────────────────────────────────────┤ in dsh.profile
                 │ @deepseek-ai/dsh-base                          .bundles order
                 │   model adapters,tools,persistence,sandbox,  │
                 │   approval,settings,credentials,telemetry    │
                 └──────────────────────────────────────────────┘
        root     profile/cordis.yml = [ ]  (empty entry list)

Composition is the same single applyEntryPatches call the boot include makes — composeEntries reuses it so --dump-config prints exactly what the same invocation would mount.

Where to look for what (a reading map) ​

You want to…Look here
See the exact tree your machine bootsrun dsh --profile web --dump-config
Compose profiles & bundlespackages/boot/app-boot/src/profile.ts (Profile, loadProfile, resolveBundleDir)
Boot a tree from a configpackages/boot/app-boot/src/index.ts (boot, mountRootInclude)
Understand the CLI flags & dsh pluginapps/cli/src/args.ts, apps/cli/src/plugin.ts
Read the session log / turn flowpackages/core/session/src/, docs/subsystems/session.md
See the Agent interface & eventspackages/core/agent/src/runtime-types.ts, docs/subsystems/core.md
Trace a turn end-to-endpackages/core/agent-loop/src/agent.ts (ReactLoopAgent), docs/agent-lifecycle.md
Add a model providerregister its adapter on ctx.llm (packages/llm/llm/)
Add a model-facing toolregister on ctx.tools; its schema joins prompt assembly
Swap a provider worldthe seams in docs/capability-seams.md
Change the loop itselfthis map (the loop is core/agent-loop), and docs/architecture.md

Events as extension points ​

Choosing the right event domain is the first decision in most changes:

  • Session events are durable facts appended to the log and broadcast through session/event — use one when the fact must survive a reload.
  • Agent events (agent/*) carry a live Agent — inbox, step, status, request, validation, continuation — use one to observe or intercept work in flight.
  • Capability events attach policy and adapters to a seam (fs/*, tools/*, telemetry/*) without importing the loop.

The event map (docs/event-producer-consumer.md) lists every event's producers and consumers.

Further reading ​

  • Boot process & CLI — what happens from dsh web to a running server.
  • The extension (Cordis) system — services, typed events, reversible effects, dsh's Cordis wrappers.
  • Runtime & agent lifecycle — agent lifecycle states, the setup window, the durable session/event stream, event dispatch.
  • Repo docs: docs/architecture.md, docs/capability-seams.md, docs/module-graph.md in the repository root.
  • Source: packages/core/README.md, packages/boot/app-boot/src/profile.ts, packages/core/agent/src/runtime-types.ts.