---
title: "What Cordis Does Inside DeepSeek Harness"
url: "https://vanducng.dev/2026/08/15/what-cordis-does-inside-deepseek-harness/"
date: "2026-08-15"
series: "deepseek-harness"
categories: ["AI"]
tags: ["ai-agents","deepseek"]
---

# What Cordis Does Inside DeepSeek Harness

> **Source**: [Cordis 在做什么：从 DeepSeek Harness 看](https://blog.antinomie.org/) by Antinomie on antinomie.org. The piece is the site's current homepage post.

DeepSeek Harness (DSH) is an agent harness built on Cordis. The first thing that clicks is not a privileged core loop. Model adapters, tool registries, session logs, and the agent loop itself are all plugins.

This is the first post in my **DeepSeek Harness** series. I am starting with a close reading of how DSH actually uses Cordis: services, `inject`, reversible `ctx.effect` registration, Loader, and the config layers that turn composition into data.

> **Version note**: Code citations pin [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) `master` @ [`47f943859b`](https://github.com/deepseek-ai/deepseek-harness/tree/47f943859b). Cordis is the vendored source snapshot `@deepseek-ai/cordis@4.0.1` in [`vendor/cordis/package.json`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/package.json).
>
> Cordis has a theoretical background in [*A Programming Paradigm for Spatiotemporal Composability*](https://github.com/cordiverse/paper) ([PDF](https://github.com/cordiverse/paper/blob/main/paper.pdf); cited as "paper section x.y"). This post does not unpack the theory. It uses the mechanisms to answer one question: **how does DeepSeek Harness use the features Cordis provides?**
>
> Every code citation keeps `file:line` and links to the GitHub source at that commit.

## 1. Premise: Everything Is a Plugin

DSH is an agent harness.

Its first principle is: **model adapters, the tool registry, session logs, and the agent loop itself are all Cordis plugins. There is no privileged core.** So "how DSH uses Cordis" and "how DSH is built" are the same question.

The second principle is about how packages are split: **one capability becomes three packages**. The Definition package defines the service. The Provider package supplies an implementation. The Consumer package uses the capability. Providers attach an implementation to a service name in two ways:

- **Exclusive**: the provider occupies the service name. Only one implementation is live at a time. Shell works this way: `bash-sandbox` occupies `ctx.shell` (section 3). A second implementation throws at `provide` time (the duplicate check in section 5). Which implementation is running is not a runtime race. Configuration decides it statically. `dsh --profile web --dump-config` shows the stacked tree that actually takes effect.
- **Registry-style**: several implementations coexist under one capability. The Definition package holds a registry. That registry is not a framework feature. It is just a `Map` on the service, for example `WebRuntime`'s `private searchProviders = new Map<string, WebSearchProvider>()` ([`packages/web/web/src/index.ts:85`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/web/web/src/index.ts#L85)). "Register into the registry" means the provider calls a register method and lands in that `Map` (`store.set` in section 5). The registry absorbs high-frequency implementation changes: swap one implementation and you only undo one entry and add another. Plugins that depend on the service stay still.

Take the shell capability that will keep showing up:

- **Definition**: `@deepseek-ai/dsh-shell` (`ShellExecutor` plus a type patch, unpacked in section 3)
- **Provider**: `@deepseek-ai/dsh-bash-sandbox` (`SandboxBashExecutor` in section 6, plus bash-local and the pwsh family)
- **Consumer**: `@deepseek-ai/dsh-tool-bash`, which declares `inject = ['tools', 'shell', 'systemPrompt', 'shellEnv']` ([`packages/shell/tool-bash/src/index.ts:31`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/shell/tool-bash/src/index.ts#L31)), wraps shell as a bash tool the model can see, and calls `ctx.shell.run(...)` at [line 380](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/shell/tool-bash/src/index.ts#L380)

Provider and consumer never import each other. Both only know the Definition package and the service name. Swap the implementation and the consumer does not change a line.

```mermaid
flowchart TB
    Def["Definition<br/>@deepseek-ai/dsh-shell"]
    Prov["Provider<br/>dsh-bash-sandbox / bash-local / pwsh"]
    Cons["Consumer<br/>dsh-tool-bash"]

    Def --> Prov
    Def --> Cons

    classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
    classDef orangeClass fill:#F39C12,stroke:#333,stroke-width:2px,color:#fff
    classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff

    class Def blueClass
    class Prov orangeClass
    class Cons greenClass
```

I have to admit: because DeepSeek Harness needs a plugin tree with spatiotemporal independence, the framework code is expensive to understand. As a developer, we only need the mechanisms. And after this article, the surprise is that most of that complexity is eaten by DeepSeek Harness itself. Adding a plugin does not add extra complexity.

## 2. One Boot: Five Lines, Every Concept

Run `dsh --profile headless "fix this bug"` and you land in `boot()` ([`packages/boot/app-boot/src/index.ts:757-784`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/boot/app-boot/src/index.ts#L757-L784), error handling omitted):

```ts
export async function boot(
  binName: string,
  absoluteConfigPath: string,
  // ...
): Promise<Context> {
  const ctx = new Context()
  // ...
  ctx.provide('dshHomePath', dshHomePath)
  await ctx.plugin(Loader)
  // ...
  await mountRootInclude(ctx, absoluteConfigPath, patches, bareModuleBaseUrl)
  await ctx.get('loader')?.await()
  if (ctx.get('loader') === undefined) return ctx
  await assertEntriesActivated(ctx, binName)
  return ctx
}
```

Those five lines map to five Cordis mechanisms:

- `new Context()`: create the root of the plugin tree. Context is both the service store (every later `ctx.xxx` is registered on it) and a tree node. Each mounted plugin gets its own child context. Unload the parent and the subtree unloads with it.
- `ctx.provide('dshHomePath', ...)`: the first service registration. Hang a value on ctx so the whole system can read it.
- `ctx.plugin(Loader)`: programmatically mount the first plugin. Loader reads YAML config.
- `mountRootInclude(...)`: hand `cordis.yml` (plus patch layers, section 7) to Loader, which expands it declaratively into the full plugin tree.
- `await ctx.get('loader')?.await()` plus `assertEntriesActivated(...)`: wait until the tree is in place, then audit. If an entry never activates (for example a declared dependency will never exist), fail here and name what is missing. Do not run with a half-loaded tree.

```mermaid
flowchart LR
    A["new Context()"] --> B["provide dshHomePath"]
    B --> C["plugin Loader"]
    C --> D["mount cordis.yml"]
    D --> E["await + audit"]

    classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
    classDef orangeClass fill:#F39C12,stroke:#333,stroke-width:2px,color:#fff
    classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff
    classDef purpleClass fill:#9B59B6,stroke:#333,stroke-width:2px,color:#fff
    classDef tealClass fill:#16A085,stroke:#333,stroke-width:2px,color:#fff

    class A blueClass
    class B orangeClass
    class C greenClass
    class D purpleClass
    class E tealClass
```

The next three sections unpack the three most important mechanisms on this tree: services, dependency declarations, and reversible registration. Section 6 looks at a plugin as a whole. Section 7 covers the config layers.

### Plugin: the unit of loaded code

**A plugin is the unit of loaded code**: an `inject` declaration plus a block that runs after its dependencies are ready. That block has two shapes:

- A function plugin exports `apply(ctx, config)`.
- A class plugin (`extends Service`) has no `apply`. **Its constructor is that block.**

Both receive the same `(ctx, config)`. A plugin has a lifecycle: instantiate on mount, reclaim everything it registered on unload.

### Service: an object shared by name

**A service is an object one plugin shares with the rest of the system by name.** The provider uses `provide` to bind an object to a service name (`'web'`, `'shell'`, `'llm'`). The consumer reads the same name as `ctx.web`.

The object can be anything. `dshHomePath` is just a string. `loader` is an instance with methods. After Loader is mounted with `ctx.plugin`, its constructor binds itself to the `loader` service name ([`vendor/loader/src/index.ts:90`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/loader/src/index.ts#L90); a class plugin's constructor is the startup code, see section 3):

```ts
ctx.reflect.provide('loader', this, this[Service.check])
```

The only shared contract is the service name. `web-search-exa` declares that it needs `web`. It does not know or care which plugin provides the object.

If the provider is not loaded yet, reading the name returns nothing. If the provider is unloaded, the binding disappears. "I need this service" is not the same as "I can read it now." That is why we need `inject`: declare which services a plugin needs to start, then **wait until those services are in place**. That is section 4.

And "wait until ready" is not a one-shot. If a running service is swapped (for example the `shell` implementation plugin changes from bash-local to pwsh), Cordis reloads every plugin that declared it needs `shell` and re-runs `apply` on the new tree. If a service loses its provider, those plugins are stopped first. Dependencies always point at the provider that is currently in effect, not the one that existed at first boot.

Why reload instead of quietly handing the new object to a running plugin? Because `apply` already ran. The old implementation may already be captured in event listeners or timer closures. Swap only the object and the plugin still holds the old reference. Undo all of its effects and re-run `apply` on the new implementation. That is the only consistent way to land the whole plugin on the new implementation.

Why can reload happen at any time? Because every effect is reversible. There is no half-success, half-failure middle state. That is section 5.

Keep two layers of change distinct. The example above swaps **the service provider**, which triggers reload. When a registry-style provider such as web-search-exa is swapped (sections 4 and 5), **no plugin reloads**. The registry undoes one entry and adds another. The next `ctx.web.search()` follows the selection rules onto the new provider. That is the point of the three-package split in section 1: high-frequency provider changes stop at the registry layer and do not disturb plugins that depend on the service.

### The plugin tree: who mounts whom

The plugin tree is the set of "who mounted whom" edges. There are two ways to declare those edges. Start with the programmatic one.

> **Programmatic**: `ctx.plugin(X)` mounts X as a child of the ctx at the call site. X's `apply` receives that new child ctx. If X then calls `ctx.plugin(Y)` inside its own `apply`, Y is X's child. agent-spine in section 4 fires about twenty `ctx.plugin` calls in a row and produces a subtree with twenty-odd children.

Mount and start are separate actions.

Here is everything `ctx.plugin(X)` does ([`vendor/cordis/src/registry.ts:316-330`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/registry.ts#L316-L330), `RegistryService.plugin`, return wrapping omitted):

```ts
plugin(plugin: Plugin, config?: any, getOuterStack = buildOuterStack()) {
  const callback = this.resolve(plugin)   // function / class / { apply } object, unified into one callback
  if (!callback) throw new Error('invalid plugin, ...')
  this.ctx.fiber.assertActive()

  let runtime = this._internal.get(callback)   // registry for this plugin; fibers is a list
  if (!runtime) {
    // ...
    runtime = { name, callback, fibers: new DisposableList(), Config: plugin.Config }
    this._internal.set(callback, runtime)
  }

  const fiber = new Fiber(this.ctx, config, Inject.resolve(plugin.inject), runtime, getOuterStack)
```

Look at the last line: config and the inject declaration enter the fiber at this moment.

A **fiber** is the runtime object for one mount. It holds that mount's config, inject declaration, child ctx, and lifecycle state machine. This post does not unpack the state machine. Remember two faces: it holds a disposal list used to undo effects (section 5), and it waits until inject dependencies are complete before starting (section 4).

Fiber's constructor ([`vendor/cordis/src/fiber.ts:222-253,265`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/fiber.ts#L222-L265), intercept bookkeeping omitted):

```ts
constructor(
  public parent: Context,
  config: any,
  public inject: Dict<any>,
  public runtime: Plugin.Runtime | null,
  // ...
) {
  this._config = config
  // ...
  this.ctx = this.context = parent.extend({ fiber: this })   // derive a child ctx

  this._runner = {
    epoch: INACTIVE,
    execute: function () {
      if (isConstructor(runtime.callback)) {
        const instance = new runtime.callback(this.ctx, this.config)   // ← the impl class is new'ed only at start
        // ...
      } else {
        return runtime.callback(this.ctx, this.config)
      }
    },
    collect,
  }

  this.dispose = parent.fiber.effect(() => {   // register self as a parent-fiber effect → cascading unload
    const remove = runtime.fibers.push(this)
    // ...
  })
```

The `execute` closure is only prepared. The constructor does not call it. The implementation class object is `new`'ed only when the fiber starts (inject dependencies are complete, then `execute` runs).

That is the split:

- **Mount** = build a fiber and hang it on the tree. Synchronous, immediate, and it does not run plugin code.
- **Start** = run `execute`. Timing is decided by dependencies (section 4). agent-spine can write `ctx.plugin(...)` in any order because every mount finishes immediately. Who starts first is decided by dependencies, not by source order.

After the programmatic path, there is a second way to declare mount edges: declarative.

> **Declarative**: an entry in `cordis.yml` with `group: true` has a `config` that is a list of child entries (in section 7, `pty` is nested under `persistent-shell` in the minimal preset). Loader instantiates the entry hierarchy into a tree of the same shape. "Unload the parent and the subtree unloads" is one sentence: a child instance registers itself as an effect of the parent instance ([`vendor/cordis/src/fiber.ts:265`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/fiber.ts#L265)), and effects are reclaimed automatically when their instance unloads (section 5). Unload the parent and the whole subtree is reclaimed in cascade.

Why two ways? They face different people.

**Declarative is for the orchestrator.** That person manages the whole system and wants a data file they can edit line by line: disable a row, replace one row's config, then hot-reload. To the orchestrator, agent-spine is one line in `cordis.yml`. How many child plugins it contains is not their problem.

**Programmatic is for the component author.** Inside a component you need "decide which child plugins to mount from my own config, and with what settings." Literal YAML fields cannot express that. You write code.

Both paths produce the same tree. Children of a YAML group and children mounted by a bundle go through the same inject wait and the same effect reclaim.

## 3. Services: Where `ctx.shell` Comes From

In DSH, `ctx.shell`, `ctx.llm`, and `ctx.web` are not built into Cordis. Packages register them. Registration happens in the `Service` base-class constructor ([`vendor/cordis/src/service.ts:42,57`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/service.ts#L42-L57)):

```ts
constructor(protected ctx: Context, name: string) {
  // ...
  self.ctx.reflect.provide(name, self, this[symbols.check])
  return self
}
```

A subclass calls `super(ctx, 'shell')` and registers itself as `ctx.shell`. Shell as an example ([`packages/shell/shell/src/index.ts:40,65`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/shell/shell/src/index.ts#L40-L65)):

```ts
declare module '@deepseek-ai/cordis' {
  interface Context {
    shell: ShellExecutor
  }
}

export abstract class ShellExecutor extends Service {
  constructor(ctx: Context) {
    super(ctx, 'shell')
  }

  abstract resolve(request: ShellExecRequest): ShellExecSpec
  abstract run(spec: ShellExecSpec): Promise<ShellRunResult>
  abstract start(spec: ShellExecSpec): ShellProcess
}
```

Two mechanisms sit in that snippet:

- **`declare module '@deepseek-ai/cordis'`** (TypeScript module augmentation): add members to Cordis's `Context` interface so every `ctx.shell` in the repo is typed. This is not a Cordis feature, but the Cordis service model depends on it for type checking. Every DSH service and event type is declared this way.
- **The abstract class is the contract**: `ShellExecutor` is abstract. Implementation packages such as `bash-local` and `pwsh` subclass it, load as plugins, and occupy the name `ctx.shell`. Occupying the name is exclusive. Mount a second implementation and you get a duplicate-service error. Whichever row config selects is the implementation at runtime.

`ShellExecutor` is only the contract. The code that actually loads as a plugin lives in an implementation package, for example `bash-local` ([`packages/shell/bash-local/src/index.ts:95-112,122-123`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/shell/bash-local/src/index.ts#L95-L123)):

```ts
/**
 * Local bash executor over `ctx.subprocess`. Bounded output, spill files, and
 * process-group SIGTERM→SIGKILL escalation are the subprocess service's
 * mechanics; this executor supplies their configured budgets per spawn, so a
 * still-running background process stays managed (killed and joined at
 * composition teardown) even across an executor reload.
 */
export class LocalBashExecutor extends ShellExecutor {
  static inject = ['subprocess']

  static Config: z<Config> = z.object({
    cwd: z.string(),
    timeoutMs: z.number().default(120_000),
    maxTimeoutMs: z.number().default(600_000),
    maxOutputBytes: z.number().default(64_000),
    // ...
  })
  // ...
  constructor(ctx: Context, config: Config) {
    super(ctx)
```

The plugin's whole obligation is those few lines: inherit the contract (the constructor chain ends at `super(ctx, 'shell')` and occupies the name), declare its own dependencies (it still needs the lower-level `subprocess` service), and accept config.

There is no `apply`. When a class plugin starts, Cordis runs `new LocalBashExecutor(ctx, config)`. The constructor is the startup logic. `config` comes from the mount: the YAML entry's `config:` field, or the `config` argument to `ctx.plugin(X, config)`. The `static Config` above validates and fills defaults (`timeoutMs` defaults to 120 seconds in that schema).

How `resolve` / `run` / `start` are implemented is none of Cordis's business. Cordis only tracks which plugin instance currently owns the name `shell` (empty, occupied by `bash-sandbox`, or swapped to `pwsh-sandbox`) and when each plugin's declared dependencies are complete. The doc comment at the top of the quote is worth a pause: a still-running background process is killed and joined at composition teardown, "even across an executor reload." The implementation author wrote the code under the assumption "I may be reloaded at any time."

## 4. inject: Declare Dependencies, Infer Load Order

The second plugin mechanism is `inject`: declare "I need these services," and Cordis waits until they exist before starting the plugin. A complete function plugin ([`packages/web/web-search-exa/src/index.ts:32,35,60`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/web/web-search-exa/src/index.ts#L32-L60)):

```ts
export const name = 'web-search-exa'

/** The web seam this provider registers into. */
export const inject = ['web']

/** Register the Exa search provider with `ctx.web`. */
export function apply(ctx: Context, config: Config): void {
  ctx.web.registerSearchProvider(new ExaSearchProvider({
    apiKey: config.apiKey ?? launchEnvironmentOf(ctx).get('EXA_API_KEY')?.value ?? '',
    // ...
  }))
}
```

`inject = ['web']` means: keep this plugin waiting until some plugin provides the `web` service, then run `apply`. The class-plugin equivalent is a static property ([`packages/core/agent-loop/src/index.ts:296-297`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/core/agent-loop/src/index.ts#L296-L297)):

```ts
/** Concrete agent factory and driver service. */
export class AgentLoop extends Service implements AgentFactory {
  static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt']
```

The direct payoff of "declare dependencies, start only when they exist": **you do not care about source order when composing plugins**. DSH's fullest example is agent-spine, a bundle plugin that mounts about twenty child plugins in one go ([`packages/examples/agent-spine-demo/src/index.ts:212-261`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/examples/agent-spine-demo/src/index.ts#L212-L261)):

```ts
export function apply(ctx: Context, config: Config): void {
  // ...
  ctx.plugin(Timer)
  ctx.plugin(LlmRuntime)
  ctx.plugin(SessionStore)
  ctx.plugin(SystemPrompt, { /* ... */ })
  ctx.plugin(ToolRuntime, config.tools ?? {})
  // ...
  ctx.plugin(AgentLoop, { agents: config.agents ?? [], /* ... */ })
}
```

The file-header comment states the semantics ([lines 206-207](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/examples/agent-spine-demo/src/index.ts#L206-L207)): "Load order is irrelevant (cordis pends each fiber on its `inject` until the services it needs exist)". The last line, `AgentLoop`, is the class with `static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt']`. The `llm`, `tools`, and `systemPrompt` it declares are provided by earlier plugins in the list (`LlmRuntime`, `ToolRuntime`, `SystemPrompt`, ...). Move it to the first line and it still waits until those dependencies exist. It sits last only for readability.

## 5. Registration Is an Effect: Where Unload Comes From

### Inside one registration: where `ctx.effect` hides

The `apply` of that provider plugin in section 4 does one real thing:

```ts
ctx.web.registerSearchProvider(new ExaSearchProvider({ /* ... */ }))
```

That call enters `WebRuntime.registerSearchProvider`, which forwards to `registerProvider` in the same file. `ctx.effect` is hidden there ([`packages/web/web/src/index.ts:98-105,118-125`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/web/web/src/index.ts#L98-L125)):

```ts
/**
 * ...
 * if its id is already registered for search. Returns a disposer; disposed
 * with the calling fiber.
 */
registerSearchProvider(provider: WebSearchProvider): () => void {
  return this.registerProvider(this.searchProviders, provider)
}

private registerProvider<P extends { readonly id: string }>(store: Map<string, P>, provider: P): () => void {
  if (store.has(provider.id)) {
    throw new WebError(`a web provider with id "${provider.id}" is already registered`, 'WEB_DUPLICATE_PROVIDER')
  }
  const dispose = this.ctx.effect(function* () {
    store.set(provider.id, provider)
    yield () => store.delete(provider.id)
  }, 'web.registerProvider()')
  // ...
}
```

`ctx.effect` takes a "do" callback. The callback returns (or yields) an "undo" function. The generator body `store.set(...)` adds the provider to the registry. The yielded `store.delete(...)` is the undo. Two more details in the quote are worth naming. The second argument `'web.registerProvider()'` is a diagnostic label for this effect. It shows up in `getEffects()` output (for example when you ask which effects an instance holds, or which undo failed). The default is `'anonymous'` and it does not change behavior. The `dispose` returned by `ctx.effect` is wrapped and returned to the caller. Automatic execution on plugin unload is the backstop. The caller can also use that handle to undo this one registration early. web-search-exa does not take the handle. It relies on the automatic backstop.

### Generators: `ctx.effect` drives them immediately

After `ctx.effect` receives the callback, it drives it immediately ([`vendor/cordis/src/fiber.ts:366,375-382`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/fiber.ts#L366-L382), inside `_execute`, other return-shape branches omitted):

```ts
const effect: Effect = runner.execute.call(this)   // call the generator function, get an iterator
// ... (omitted: branches where the callback returns a function / Promise / async iterator)
} else if (Symbol.iterator in effect) {
  const iter = effect[Symbol.iterator]()
  while (true) {
    const result = iter.next()       // drive it to the end on the spot
    safeCollect(result.value)        // collect each yielded undo immediately
    if (result.done) return
  }
}
```

So `store.set` runs at the moment `ctx.effect` is called, and the undo function is collected at the same time. The returned `dispose` is a Cordis wrapper. Calling it runs every collected undo, and only once.

### Undo: whoever registers, undoes

The key is who `this.ctx` points at. Look at that `apply` from section 4 again. This time watch the parameter and which object the property access happens on ([`packages/web/web-search-exa/src/index.ts:60-61`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/web/web-search-exa/src/index.ts#L60-L61), provider constructor args omitted):

```ts
export function apply(ctx: Context, config: Config): void {
  ctx.web.registerSearchProvider(new ExaSearchProvider({ /* ... */ }))
}
```

The `ctx` that `apply` receives is web-search-exa's own plugin ctx (the child ctx from section 2). The `ctx.web` access happens on that ctx. The `WebRuntime` you get back is not a bare object. Cordis wraps it and, at wrap time, remembers "which ctx is reading." Methods called through that wrapper trace `this.ctx` back to it. So the undo function in `registerProvider` is registered on web-search-exa's own plugin instance, not on the `WebRuntime` instance.

Contrast that with `WebRuntime` calling `this.registerProvider(...)` internally (for example to register a built-in provider during its own init). Then `this.ctx` is its own ctx, and undo belongs to its instance. Removing that entry would mean touching `WebRuntime` itself.

On the real path, undo belongs to the registrar: the entry lives in `WebRuntime`'s table, but the function that removes it is kept by the web-search-exa instance. Each side owns its own concern. `WebRuntime` only holds the table. It does not need to know who put an entry in, and it does not clean up for anyone else. When web-search-exa unloads, the undo it registered (`store.delete`) runs automatically and the provider entry disappears from the registry. The registry holder's lifecycle and the registrar's lifecycle are not coupled.

The registrar never writes cleanup code. Unload itself is triggered by an orchestration action: delete that entry from `cordis.yml`, set `disabled: true`, or replace it with a patch. Loader disposes the matching plugin instance and runs every undo it registered.

"Registered on the instance" also has a concrete meaning. Each plugin instance has a disposal list. `ctx.effect` pushes the undo function onto it ([`vendor/cordis/src/fiber.ts:203`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/fiber.ts#L203), [`520`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/fiber.ts#L520), `Fiber`):

```ts
public readonly _disposables = new DisposableList<Disposable>()   // each instance's disposal list

// inside Fiber.effect:
removeWrapper = this._disposables.push(wrapper)                    // "register" = push onto the list
```

On unload, clear the list and run each item ([lines 675-676](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/fiber.ts#L675-L676), inside `_unload`):

```ts
await Promise.all(this._disposables.clear().map(async (dispose) => {
```

Loader and this list split "trigger" from "execute." Each entry holds a reference to the matching instance (`entry.fiber`). When a config change hits disable / delete / replace, Loader calls `await fiber.dispose()` once ([`vendor/loader/src/config/entry.ts:130-135`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/loader/src/config/entry.ts#L130-L135), `Entry._dispose`). It knows nothing about what is on the list. `fiber.dispose()` starts the instance's own unload path, and `_unload` above clears and runs the list. Loader decides when to dispose which instance. The instance decides what disposal does. The only seam is `fiber.dispose()`. That is how "config-driven unload" and "plugin authors write no cleanup" fit together.

### Why a generator: hand over as you go

An ordinary function can only `return` one undo at the end. A generator can **hand over as you go**: finish one step, `yield` that step's undo. The difference shows up only when something fails halfway. If a multi-step setup fails in the middle (or a dependency changes, or startup is aborted), the undos already yielded have been collected step by step, so completed steps can roll back precisely. A "return at the end" form has nothing to hand over on a mid-setup failure, so the work already done leaks. The generator can also be an async generator: steps may `await` (async setup) and undos are still collected incrementally. This example has only one setup step, so both forms are equivalent. The generator is DSH's uniform habit.

A real multi-step example: `SessionStore` creates a session in two steps, persist then broadcast `session/created` ([`packages/core/session/src/index.ts:833-839`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/core/session/src/index.ts#L833-L839); the comments are in the source):

```ts
// announcing so a throwing `session/created` listener rolls the attach back
// (the generator effect disposes already-yielded disposers on a throw)
// instead of leaking the store entry and its publication hooks.
this.ctx.effect(function* (this: SessionStore) {
  yield this.enter(session)   // step 1: persist, immediately hand over "remove it"
  this.announce(session)      // step 2: broadcast: a listener may throw
}.bind(this), 'sessions.create()')
```

Watch the order: the undo is handed over **before** the broadcast. The comment states the reason. If a listener throws, the already-yielded undo rolls the persist back, instead of leaving a dirty store entry with hooks attached. That is "hand over as you go" in production form. It is not a fix after failure. The code is written in the order "the next step might explode."

Async and composition have a real example too. `settings-file`'s `[Service.init]` is an async generator ([`packages/settings/settings-file/src/index.ts:232-269`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/settings/settings-file/src/index.ts#L232-L269)). Steps `await` (wait for path resolution, then start a file watcher) and `yield* super[Service.init]()` collects the base class's undos into the same chain: the base class's "load and publish settings" undo and the subclass's "close the watcher" undo share one reclaim path.

### The rule: every registration goes through `ctx.effect`

Actions like `store.set` that "put an object into a table another component holds, so the rest of the system can find it" (register a provider, an event listener, a tool, a service) are what DSH calls registration. It has a hard rule: **every registration goes through `ctx.effect()`**. At the framework layer that rule is literal. Look at the full `provide` implementation ([`vendor/cordis/src/reflect.ts:277-305`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/reflect.ts#L277-L305), property-declaration bookkeeping omitted):

```ts
provide(name: string, value?: any, check?: () => boolean) {
  return this.ctx.fiber.effect(() => {
    // ... (omitted: bookkeeping that declares name as a service)
    this.ctx.root[symbols.isolate][name] ??= Symbol(name)
    const key = this.ctx[symbols.isolate][name]
    const impl: Impl = { name, value, fiber: this.ctx.fiber, check }
    if (this.store[key]) {
      throw new Error(`service "${name}" has been registered at <${this.store[key].fiber.name}>`)
    }
    this.store[key] = impl
    this.ctx.fiber.store![name] = impl
    if (this.ctx.fiber.state === FiberState.ACTIVE) {
      this.notify([name])
    }
    return async () => {
      delete this.store[key]
      const fibers = this.notify([name])
      await Promise.allSettled(fibers.map(fiber => fiber.await()))
      // ensure self access before dependencies cleanup
      delete this.ctx.fiber.store![name]
    }
  }, `ctx.provide(${JSON.stringify(name)})`)
}
```

The "do" side: write `{ name, value, fiber }` into the store. If the name is taken, throw immediately. That is the exclusive name occupancy from section 3. If this fiber is already active, `notify` wakes plugins waiting on the service. The "undo" side is worth reading line by line: drop the name first, `notify` dependents, then **`await` until they have all stopped**, and only then clear the record on this instance. "Consumers stop first, provider withdraws second" is written here. So `ctx.provide('dshHomePath', ...)` in the section 2 boot, and `provide` in the section 3 `Service` constructor, all go through the same register-and-reclaim channel. Unload the provider plugin and the service name disappears. Plugins that declared a dependency on it stop. This rule is the foundation of two advanced DSH capabilities:

- **Config hot-reload**: change a patch file, Loader unloads the old subtree and mounts a new one. The effect mechanism guarantees that services, tools, and listeners registered by the old subtree are all reclaimed. No middle state.
- **Plugin scope isolation**: when a subtree mounted by a preset (section 7) unloads, every tool, prompt segment, and event listener it registered disappears automatically. No manual reconciliation.

Event listeners are not an exception. `ctx.on` ([`vendor/cordis/src/events.ts:288-301`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/events.ts#L288-L301)) calls `register`, and `register` is `ctx.effect` ([`254-259`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/cordis/src/events.ts#L254-L259)). A listener's lifetime follows the plugin that registered it. Unload the plugin and the listener is removed. The `waterfall` extension points DSH builds on top of events (`llm/stream`, `agent/pre-step`, and so on) are DSH usage, not a new Cordis mechanism. This post does not unpack them.

## 6. How to Write a Plugin: Cordis Hides the Complexity

The first five sections took the mechanisms apart. This section asks what writing a plugin actually requires, and what a plugin's lifecycle looks like.

Below is all the Cordis-related code a service plugin has to write. `SandboxBashExecutor` is the main class of the bash-sandbox package, the implementation that occupies `ctx.shell` in the base bundle ([`packages/shell/bash-sandbox/src/index.ts:44-45,67-72,182`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/shell/bash-sandbox/src/index.ts#L44-L182), sandbox business logic omitted):

```ts
import { Context } from '@deepseek-ai/cordis'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'

export class SandboxBashExecutor extends LocalBashExecutor {
  static override inject = ['subprocess', 'sandbox', 'sandboxPolicy']

  constructor(ctx: Context, config: Config) {
    super(ctx, config)
    this.mode = ctx.sandboxPolicy.defaultMode   // a service declared in inject, used directly
  }

  override resolve(request: ShellExecRequest): ShellExecSpec { /* ... */ }
  override async run(spec: ShellExecSpec): Promise<ShellRunResult> { /* ... */ }
  override start(spec: ShellExecSpec): ShellProcess { /* ... */ }
  // ... (sandbox business logic; Cordis does not care)
}

export default SandboxBashExecutor
```

Count what Cordis requires: **inherit the contract** (`extends LocalBashExecutor`, the chain reaches `ShellExecutor`), **declare dependencies** (`static inject`, two more services than the parent: `sandbox` and `sandboxPolicy`), and **take `(ctx, config)` in the constructor**, where `ctx.sandboxPolicy` is used directly because inject guarantees it is there. That is all. No register call, no unload code, no lifecycle hooks. `resolve` / `run` / `start` are pure domain logic. The trailing `export default` lets Loader find the class.

One question remains: after this class is mounted, what does the runtime do with it? That is the timeline below.

**Step 0: the contract comes first.** `@deepseek-ai/dsh-shell` defines the abstract class `ShellExecutor` (section 3): it declares the type of `ctx.shell` and three abstract methods. It provides no implementation.

**Step 1: mount.** The orchestration config has one entry ([`packages/bundle/base/cordis.patch.yml:178-182`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/bundle/base/cordis.patch.yml#L178-L182)):

```yaml
- id: bash-sandbox
  name: '@deepseek-ai/dsh-bash-sandbox'
  disabled: !!js process.platform === 'win32'
  config:
    timeoutMs: 60000
```

Loader reads it: `import` the package, create the plugin instance ([`vendor/loader/src/config/entry.ts:277-285`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/vendor/loader/src/config/entry.ts#L277-L285), `Entry._init`: first `this.parent.tree.import(this.options.name, ...)`, then `_start(plugin)` to build the instance). Mount is config-driven. At boot, Loader mounts when it expands to this row. Later config changes (hot-reload, patch, preset) also trigger it. The plugin never mounts itself. Notice the `disabled` expression: on Windows this row is not mounted at all. Its twin `pwsh-sandbox` ([lines 184-186](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/bundle/base/cordis.patch.yml#L184-L186), opposite condition) takes its place.

**Step 2: wait.** The implementation class declares `static inject = ['subprocess', 'sandbox', 'sandboxPolicy']` (the second line of the skeleton above). Until all three services are in place, the instance stays waiting. The constructor does not run.

**Step 3: start.** The three dependencies are ready. Cordis runs `new` (the class is the startup callback, sections 2 and 3). `static Config` validates config first: `timeoutMs: 60000` goes through the schema here. The constructor chain reaches `super(ctx, 'shell')`, `provide` occupies the name (the implementation at the end of section 5), the instance activates, and `notify` wakes every plugin that declared it needs `shell`.

**Step 4: service.** A consumer (for example the tool-bash plugin) calls it through `ctx.shell.run(...)`. Each call goes through the wrapper layer. `this.ctx` inside the method belongs to the caller. Registrations the consumer makes through it hang on the consumer's own instance (section 5).

**Step 5: unload.** Config disables or deletes the row, or hot-reload rebuilds it. Loader calls `fiber.dispose()`. The disposal list runs LIFO. Still-running background processes are reclaimed (the "killed and joined" promise in the section 3 doc comment). The `provide` undo drops the name `shell`, notifies dependents, and waits for them to stop first.

**Step 6: replace (if it happens).** Change the entry's `name` to another implementation package. The old instance takes step 5 and the name is vacated. The new instance takes steps 2-3 and occupies the name. Plugins that declared they need `shell` are reloaded onto the new implementation (the "continuous re-evaluation" in section 2).

```mermaid
flowchart TB
    S0["0. Contract first<br/>ShellExecutor defines ctx.shell"] --> S1["1. Mount<br/>Loader reads the YAML entry"]
    S1 --> S2["2. Wait<br/>inject services are not all ready"]
    S2 --> S3["3. Start<br/>new + provide occupies the name"]
    S3 --> S4["4. Serve<br/>consumers call ctx.shell.run"]
    S4 --> S5["5. Unload<br/>fiber.dispose, LIFO undo"]
    S5 --> S6["6. Replace<br/>new provider, dependents reload"]

    classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
    classDef orangeClass fill:#F39C12,stroke:#333,stroke-width:2px,color:#fff
    classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff
    classDef purpleClass fill:#9B59B6,stroke:#333,stroke-width:2px,color:#fff
    classDef tealClass fill:#16A085,stroke:#333,stroke-width:2px,color:#fff
    classDef redClass fill:#E74C3C,stroke:#333,stroke-width:2px,color:#fff

    class S0 blueClass
    class S1 orangeClass
    class S2 purpleClass
    class S3 greenClass
    class S4 tealClass
    class S5 redClass
    class S6 orangeClass
```

Composition complexity is collected into the framework in one place. Cordis owns registration, dependency decisions, and reclaim. DSH owns the organizational discipline of "one capability, three packages (Definition / Provider / Consumer)" and static checks on config. What is left for a module author is domain code. So "add a module" has two tiers. Adding another implementation of an existing capability (another search backend, for example) is a few dozen lines of code plus one YAML row.

The cost that remains is opening a new capability. You must prepare the Definition package, the Provider package, and the Consumer package that faces the model or the user at the same time. That is a design decision, not just writing domain code. Those three packages already appeared in section 1: `dsh-shell` (contract), `dsh-bash-sandbox` (implementation), `dsh-tool-bash` (consumer).

## 7. Composition Is Configuration: cordis.yml, Patch Layers, and Presets

Loader describes the whole system as data. A `cordis.yml` is a list of entries. Each entry commonly has four fields: `id` (row identity), `name` (package name), `config` (passed to the plugin), and `disabled` (on/off). A real example ([`examples/headless-agent/cordis.yml:9,23,44`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/examples/headless-agent/cordis.yml#L9-L44)):

```yaml
- id: settings
  name: '@deepseek-ai/dsh-settings-file'

- id: llm-deepseek
  name: '@deepseek-ai/dsh-llm-deepseek'
  config:
    thinking: enabled
    reasoningEffort: max
    models:
      - id: deepseek-v4-pro
        contextWindow: 128000

- id: agent-spine
  name: '@deepseek-ai/dsh-agent-spine-demo'
```

The file's only "programming" feature is `!!js` expressions, and they are allowed in only two fields: `config` (evaluated after that plugin's dependencies activate, so it can read environment state) and `disabled` (evaluated on every mount decision, so a whole row can switch by platform). Every other field is a literal. Static checks ([`scripts/verify-cordis-config.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/scripts/verify-cordis-config.ts)) enforce that at commit time.

The tree that actually runs is not a single file. A patch is not a complete tree. It is a **modification list addressed by `id`**: each item locates an existing row and replaces that row's `config` wholesale (no deep merge). The tree that takes effect is stacked from five patch layers, in order: each bundle's `cordis.patch.yml` (in declaration order) → the profile's own patch → machine-level `$DSH_HOME/cordis.patch.yml` (applies to every profile, so it overrides the per-profile layer) → CLI `--patch` → the telemetry switch. When the same row is written by more than one layer, the later write wins.

A real example. The base bundle mounts the HMR plugin ([`packages/bundle/base/cordis.patch.yml:19-22`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/bundle/base/cordis.patch.yml#L19-L22)):

```yaml
- id: hmr
  name: '@deepseek-ai/cordis-plugin-hmr'
  config:
    root: ['.']
```

The headless bundle writes the same `id: hmr` and only `disabled: true` ([`packages/bundle/headless/cordis.patch.yml:12-15`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/packages/bundle/headless/cordis.patch.yml#L12-L15); the comment is in the file):

```yaml
# The shared module-reload HMR row stays off; the launcher's watch-only
# fallback still keeps the user patch layers live until the run exits.
- id: hmr
  disabled: true
```

In the profile's bundle order, headless comes after base. Patches apply in that order, so HMR is off at headless runtime and the base row is not edited. To see which tree actually runs: `dsh --profile web --dump-config`.

Up to here, composition is process-level. `cordis.yml` is the base (layer one). Patch layers modify it (layer two). The whole process has one tree. **The third layer is the preset, and it is session-level** ([`apps/cli/config/agent-presets/*/agent.cordis.yml`](https://github.com/deepseek-ai/deepseek-harness/tree/47f943859b/apps/cli/config/agent-presets)). A preset is an entry list like `cordis.yml`. The difference is scope. The `cordis.yml` tree is shared by the whole process, and its services are process-wide singletons. A preset hangs on one session. Different sessions in the same process can hang different presets: one runs "minimal," one runs "create," and they do not interfere.

That produces a hard constraint: service rows in a preset must go into a `cordis:group` with `isolate`. Otherwise two sessions collide on the same process-level singleton (the `persistent-shell` row in minimal mode below is this pattern). The CLI's four modes (standard / PTC / minimal / create) **are not a `mode` field and not an if/else branch. They are four shipped preset directories.** Each directory has one composition YAML and one display-metadata file. The entire difference among the four modes is the difference among YAML rows:

| Mode | Preset directory | What the YAML changes |
| --- | --- | --- |
| Minimal | `minimal` | Persona occupies the whole system prompt (`complete: true`); few tools; per-session services sit in an `isolate` group |
| Standard | `standard` | Full coding agent; shell tool is bash or pwsh by platform |
| PTC | `code` | Standard plus one `tool-presentation` row with `mode: code` |
| Create | `cordis` | Standard plus self-modification tools and a composition skill |

- **Minimal mode** ([`minimal/agent.cordis.yml:8,18`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/apps/cli/config/agent-presets/minimal/agent.cordis.yml#L8-L18)): persona uses `complete: true` to occupy the entire system prompt and mounts only a few tools. Per-session services go into a `cordis:group` with `isolate`, so two sessions do not collide on one process-level singleton:

```yaml
- id: persona
  name: '@deepseek-ai/dsh-persona'
  config:
    text: You are a helpful software engineer assistant.
    complete: true

- id: persistent-shell
  name: cordis:group
  group: true
  isolate:
    terminals: true
  config:
    - id: pty
      name: '@deepseek-ai/dsh-terminal'
```

`isolate: { terminals: true }` means: open a resolution space private to this group for the service name `terminals`. The pty plugin inside it will provide the `terminals` service (the PTY registry).

When the same preset is mounted once by each of two sessions, without this isolation the second mount hits the exclusive-name duplicate error at `provide` (section 5). With it, provide and read inside the group land in the private space. The outside world and the same kind of group in another session cannot see it. Each session gets its own PTY registry. (The `true` here means this entry is private. Loader also supports a string so several entries share one isolation space. DSH's shipped config does not use that form.)

- **Standard mode** (`standard`): a full coding agent. The shell tool is chosen by platform ([`standard/agent.cordis.yml:44-50`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/apps/cli/config/agent-presets/standard/agent.cordis.yml#L44-L50); the same rows exist in the `code` preset):

```yaml
- id: tool-bash
  name: '@deepseek-ai/dsh-tool-bash'
  disabled: !!js process.platform === 'win32'

- id: tool-pwsh
  name: '@deepseek-ai/dsh-tool-pwsh'
  disabled: !!js process.platform !== 'win32'
```

- **PTC mode** (`code`): a complete copy of standard plus one row ([`code/agent.cordis.yml:259-262`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/apps/cli/config/agent-presets/code/agent.cordis.yml#L259-L262)). Tool presentation becomes "one `run_code` tool plus a generated TypeScript SDK." The model writes programs that compose multi-step operations:

```yaml
- id: tool-presentation
  name: '@deepseek-ai/dsh-agent-tool-presentation'
  config:
    mode: code
```

- **Create mode** (`cordis`): a copy of standard, plus self-modification ([`cordis/agent.cordis.yml:245,255`](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/apps/cli/config/agent-presets/cordis/agent.cordis.yml#L245-L255)): a toolset that reads, writes, mounts, and unmounts runtime plugins, plus a skill that teaches composition:

```yaml
- id: tool-cordis
  name: '@deepseek-ai/dsh-tool-cordis'

- id: skill-filesystem
  name: '@deepseek-ai/dsh-skill-filesystem'
```

This last preset's file header has a trust-boundary statement ([lines 9-12](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859b/apps/cli/config/agent-presets/cordis/agent.cordis.yml#L9-L12)): `cordis_mount` executes model-written JavaScript inside the runtime. A session running in create mode should be treated with the same caution as shell permissions.

```mermaid
flowchart TB
    subgraph Process["Process-level tree"]
        direction TB
        Y["cordis.yml base"] --> P1["bundle cordis.patch.yml"]
        P1 --> P2["profile patch"]
        P2 --> P3["DSH_HOME/cordis.patch.yml"]
        P3 --> P4["CLI --patch"]
        P4 --> P5["telemetry switch"]
    end

    subgraph Session["Session-level presets"]
        direction LR
        M["minimal"]
        S["standard"]
        C["PTC / code"]
        R["create / cordis"]
    end

    Process --> Session

    classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
    classDef orangeClass fill:#F39C12,stroke:#333,stroke-width:2px,color:#fff
    classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff
    classDef purpleClass fill:#9B59B6,stroke:#333,stroke-width:2px,color:#fff
    classDef tealClass fill:#16A085,stroke:#333,stroke-width:2px,color:#fff
    classDef redClass fill:#E74C3C,stroke:#333,stroke-width:2px,color:#fff

    class Y blueClass
    class P1 orangeClass
    class P2 greenClass
    class P3 purpleClass
    class P4 tealClass
    class P5 redClass
    class M blueClass
    class S orangeClass
    class C greenClass
    class R purpleClass
```

The mechanical result: **adding a fifth mode does not require changing any TypeScript**. Copy a preset directory and edit YAML. That is "composition is configuration" in the literal sense.

## 8. Mapping: Which Cordis Feature Each Usage Eats

Fold the first seven sections back onto Cordis features:

| Cordis feature | Usage in DSH |
| --- | --- |
| Context tree + service registration (`provide`) | Everything is a plugin. `ctx.shell` / `ctx.llm` / `ctx.web` are all registered by packages (section 3) |
| `inject` dependency declaration + start when available | Composing plugins does not care about order. agent-spine mounts about twenty children at once (section 4) |
| Reversible `ctx.effect` registration | Provider registration and event listeners are reclaimed when the plugin unloads. Config hot-reload leaves no middle state (section 5) |
| Loader declarative composition | `cordis.yml` + stacked patches (process-level), presets (session-level). Four modes are four YAML files (section 7) |
| `isolate` | Per-session services in a preset go into an isolate group. Multiple sessions in one process do not interfere (section 7) |

[Paper section 1.2.2](https://github.com/cordiverse/paper/blob/main/paper.pdf), "Self-Evolving Agent Harnesses," lists this as a motivating example: the harness generates and replaces its own components while it keeps serving. DSH's create mode is a productized slice of that scenario. The model edits its own runtime plugin tree through tools such as `cordis_mount`. It can do that safely because of every row above: dependency declarations decide when to start, effects decide how to unload cleanly, and Loader turns "the system shape I want" into readable, writable data.

## 9. Developer View: Why DSH Is a Meta Harness

The end of section 8 said create mode lets the model edit the runtime plugin tree directly. Next: why that capability holds, and what it means once it does.

A harness's job is **assembling the model's context**: system prompt, tool list, environment information. In the earlier sections, each of those fragments has a clear owner. persona owns prompt text (minimal mode in section 7 uses it to occupy the entire system prompt). systemPrompt owns prompt segments. tools owns the tool registry. Those fragments are all "effect registrations" from section 5: reversible, replaceable, hot-reloadable.

Changing harness behavior then becomes a local act. The article already has a ready example: swap the shell implementation from bash-local to bash-sandbox (the lead of section 6) and execution policy goes from unrestricted to sandboxed. The consumer tool-bash does not change a line (the mutual non-import in section 1). Hot-reload applies the change. No process restart (section 5). Want different file-access permission logic, a different search backend, or a different prompt organization? Same motion: write an implementation package, change one YAML row. You do not hunt for scattered handling points in glue code.

That workflow is isomorphic for humans and for agents. Each plugin's inputs and outputs are verifiable: config goes in, registration comes out, behavior is measurable. So "debug how this piece of context works" can be handed to the agent itself: change a plugin, hot-swap, verify, close the loop. Create mode productizes that loop and attaches an explicit trust-boundary statement.

That is enough to say what a meta harness means. DSH does not prescribe what an agent looks like, because the agent loop itself is just one of the twenty-odd lines in the agent-spine list (section 4). What it provides is a composition mechanism. The shape of the harness is decided by configuration. What is fixed first is **context structure**: which system-prompt segments, which tools, how environment information enters. Code (plugins) fills that structure. You do not write the code first and let context structure emerge from it. Extension points are no longer hook callbacks scattered through the code. Everything is a plugin.

That leaves a lot of room to explore. I will keep watching what gets built on DeepSeek Harness next. (Yes, that last line is a small ad.)

---

*This is Part 1 of the DeepSeek Harness series.*
