Skip to content

The central claim

The root README.md calls dsh's architecture "everything is a plugin." Concretely:

[Cordis] is the framework under dsh: plugins contribute services, typed events, and reversible effects to a shared context. Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so every part is replaceable from configuration. — docs/architecture.md

The word to hold on to is everywhere it says "a plugin": the model adapter (ctx.llm), the tool registry (ctx.tools), the session log (ctx.sessions), and the agent loop (ctx.agentLoop) are all just entries in a Cordis tree. There is no privileged core to patch — you extend dsh by mounting a plugin beside the others.

What plugins contribute to the context

A Cordis plugin is an object that either is a Service subclass or a plain function with an optional inject property and an apply(ctx) body. Once Cordis mounts it, it contributes three kinds of things to the shared ctx:

ContributionMechanismExample
Serviceclaims a stable ctx.<key>ctx.llm, ctx.tools, ctx.sessions, ctx.agents
Typed eventdeclared via TypeScript declaration merging, dispatched with emit / waterfall / parallel / serialagent/pre-step, tools/execute, turn/start
Reversible effectinstalled via ctx.effect() or ctx.on(), unwound on unloada prompt section, a tool schema, a listener

Dependency is declared through inject: a plugin that names a service it needs waits until that service exists, so load order is expressed as service requirements rather than manual sequencing. Registrations are effects, so when a plugin unloads (a profile switch, a cordis.patch.yml edit, a reload), everything it installed rolls back predictably — nothing is left half-registered.

The underpinning framework is the vendored Cordis, whose design is documented in the paper A Programming Paradigm for Spatiotemporal Composability, and whose in-repo explanation is docs/cordis-primer.md. The package providing the "plugin" abstraction itself is @deepseek-ai/cordis under vendor/cordis.

No privileged core

Because every capability is a service, one provider swap changes the whole product. docs/architecture.md makes this explicit with the capability seam model: a swappable capability has three roles —

  1. Service Definition — declares the interface (e.g. ctx.shell).
  2. Service Provider — implements it (e.g. dsh-bash-local, dsh-bash-sandbox).
  3. Consumer — uses it, commonly a model-facing tool (e.g. dsh-tool-bash).

One role alone is not a seam; adding a capability means designing all three. The packages/shell/shell/README.md shows the split in miniature:

PackageRole
@deepseek-ai/dsh-shellService Definition: ShellExecutor (ctx.shell) + vocabulary types
@deepseek-ai/dsh-bash-localService Provider: local subprocesses
@deepseek-ai/dsh-bash-sandboxService Provider: same mechanics, every spawn confined via ctx.sandbox
@deepseek-ai/dsh-tool-bashConsumer: the model-facing tool schemas

Because the consumer talks only to the Service Definition, swapping dsh-bash-local for a containerized or remote executor needs no provider forks. Filesystem, subprocess, and subagent providers work the same way (see the capability-seams section of docs/architecture.md).

Profiles and bundles: named compositions

A running dsh is not a fixed program; it is a plugin tree composed at boot out of ordered layers.

  • A profile is a named composition stored in the Harness home. It lists the bundles it stacks, holds any out-of-tree plugins it installs, and keeps the user's own cordis.patch.yml. web and headless ship as templates (PROFILE_TEMPLATES in app-boot).
  • A bundle is a distribution format for Cordis config rows and the code they mount. dsh-base, dsh-web-app, and dsh-headless are the shipped bundles.

Each declares itself under the dsh field of its own package.json:

jsonc
// a profile package.json
{ "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] } } }

// a bundle package.json
{ "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }

dsh.profile.bundles lists a profile's bundles in order; dsh.bundle.patch points at a bundle's patch file — the YAML that inserts the code rows. The composition machinery lives in packages/boot/app-boot/ (exports resolveProfileDir, initProfile, loadProfile, composeEntries, PROFILE_TEMPLATES, DEFAULT_PROFILE_BUNDLES, PROFILES_DIR, PROFILE_PATCH_FILENAME).

Layer order

Layers apply to an empty entry list in a strict order, from the CLI and profile machinery:

text
empty root
  → each bundle's patch  (in dsh.profile.bundles order)
  → the profile's    cordis.patch.yml
  → the home-level   $DSH_HOME/cordis.patch.yml
  → any --patch overlays

Use apps/cli/src/args.ts to see the launcher flags themselves; the ordering doctrine lives in docs/architecture.md (Layers apply to an empty entry list in this order: each bundle in the profile's listed order, then the profile's cordis.patch.yml, then the home-level one, then any --patch overlay).

Inspecting the composed tree

Because a running tree is just a stack of patches, it is fully inspectable. This command prints the composed tree for the web profile and exits without booting a tree:

sh
dsh --profile web --dump-config

--dump-default-config prints the bundle layers only (no user layer, no --patch). The dump is rendered by renderConfigDump in app-boot using the include's own applyEntryPatches, so what it prints is exactly what would mount — the tooling cannot drift from the boot. Any row it prints can be replaced by a patch of your own. A patch targets a row by id, and replaces that row's wholeconfig (there is no deep-merge layer — a profile override must restate the fields it keeps), or inserts a new row.

Why this makes every part replaceable

The payoff is the "no privileged core" claim made concrete:

  • A bundle's row set is decided by a patch file, not by code — override a row by id and the behavior changes.
  • User preferences live in layers (per-profile, then home-level), each of which beats the bundle below it.
  • Flags parsed by an app plugin (via @deepseek-ai/dsh-cmdline's parseCmdline) are read from lazy !!js config, e.g. port: !!js ctx.webStartup.port ?? 3080, so even a launch flag is just the value of a config expression — write a literal over the expression and the flag silently stops winning.
  • Because registrations are reversible effects, live cordis.patch.yml edits (via the HMR plugin) recompose the tree in place without a full restart.

Interaction and lifecycle detail — the webserver row example, the web-startup provider, and profile machinery — is covered in depth on Boot & CLI and The Extension (Cordis) System.

Further reading

  • What Is DeepSeek Harness? — the product this philosophy drives.
  • Boot & CLI — profiles, bundles, and --dump-config in practice.
  • The Extension (Cordis) Systeminject, events, and ctx.effect().
  • docs/cordis-primer.md — the Cordis in Five Ideas summary.
  • docs/architecture.md — layer order and capability seams.
  • packages/boot/app-boot/README.md — profiles and composeEntries detail.