The Mental Model: Why There Is No Core
Seven stops that turn the "everything is a plugin" claim into something you can point at, file by file
What you will learn
- Why dsh has no privileged core and what that means for every change
- What "plugin" means concretely: services, typed events, and reversible effects in a shared context
- Where each core capability lives and the `ctx` key that exposes it
- Why the agent loop itself is a plugin and how the rest of the system stays decoupled from it
- How the CLI is just a dispatch mode that boots a tree of plugins
- Why any row of the boot config can be replaced by a patch of your own
Prerequisites
- You can read TypeScript and basic YAML
- You do not need to know Cordis — this tour introduces it
There Is No Privileged Core
docs/architecture.md:9The one sentence that changes how you read the whole codebase: there is no core to patch, you mount a plugin beside the others
Almost every agent framework has a center of gravity: an Agent class, a runner, a loop you configure around. dsh inverts that. The model adapter, the tool registry, the session log, and the agent loop are all at the same altitude. When you read a package asking "is this core or an extension?" the answer is always: it is a plugin. That is the mental load that drops first.
"Everything is a plugin" is a structural rule, not a slogan. If a change feels like it needs a special case in a privileged core, you are looking at the wrong layer. ---
## Cordis
[Cordis](cordis-primer.md) 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.
There is no privileged core to patch: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when their plugin unloads.
## Profiles and bundles
A running `dsh` is a plugin tree composed at boot from 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.
A **bundle** is a distribution format for Cordis config rows and the code they mount, so whatever it inserts stays patchable by the layers above it.
Each declares itself in its own `package.json` under a `dsh` field: `dsh.profile` lists a profile's bundles, and `dsh.bundle` points at a bundle's patch file.
What a Plugin Contributes
docs/architecture.md:78A plugin's surface is services it exposes and events it can observe; events are the extension points and choosing the right domain is the first decision in most changes
Three event domains shape the code. Durable session events are the log. Live agent events let you observe or intercept work in flight. Capability events attach policy to a seam without importing the loop at all. A new feature is usually a listener on one of these, not a modification of an existing class.
To find where new behavior goes, decide which domain a plugin lives in: log it, observe the agent, or attach to a seam. ---
step/end
tools owe another request, or next-step input arrived -> claim -> next step
-> agent/turn-stopping
turn/end
```
`turn/*`, `step/*`, `user/message`, `assistant/*`, and `tool/*` are durable session events; the rest are live extension points across three domains. `agent/pre-step`, `agent/request`, `llm/stream`, and the three `tools/*` events are waterfalls, whose listeners must call `next()` to delegate; `agent/turn-stopping` is serial and has no `next()`.
Input reaches the driver through one inbox. Some messages wake it immediately; injected context waits in the inbox until another message does.
`agent/pre-step` decides what the model sees. Listeners may rewrite the claimed messages or reject them outright; a rejected or empty first claim still closes a durable turn that spent no step, so the log records the attempt. Each step reads the prompt sections and tool schemas that plugins registered.
Details: the [sequence diagram](agent-lifecycle.md), the [tool pipeline](tool-execution-pipeline.md), and [cancellation and error recovery](subsystems/core.md#the-agent-handle).The Core Capability Map
docs/architecture.md:61The small set of core packages and the shared-context (ctx) key each one owns
This table is the map. Each package owns a capability and exposes it under a ctx key. The loop is ctx.agentLoop; the model is ctx.llm; the tools are ctx.tools. Nothing imports a concrete driver — they depend on the key and the events, which is what keeps every one of them replaceable.
Navigate by the ctx key, not by the package name. The key is the stable contract; the package behind it is swappable.
---
The [event map](event-producer-consumer.md) lists every event's producers and consumers.
## Turn flow
A **step** is one model request plus the tools it calls. A **turn** is zero or more steps: it opens before its first input is claimed and closes once nothing is owed.
```text
turn/start
claim next-step input plus one queued message
assemble prompt sections + tool schemas
-> agent/pre-step reject | enter(messages)
reject, or a first enter rewritten empty -> close the turn with no step
step/start
append entered messages as user/message
derive model history from the log
agent/request -> llm/stream -> assistant/chunk* -> assistant/message
tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
step/endThe Loop Is a Plugin Too
packages/core/agent-loop/src/agent.ts:63ReactLoopAgent is the concrete default driver, the only package with real loop logic, exposed as ctx.agentLoop
Note the class _implements_ Agent (defined in core/agent). This driver is the default, not the only one. Because other packages consume the Agent interface and ctx.agentLoop, and because its registrations are reversible effects, a different loop could be mounted in its place. That is the claim in Stop 1 made provable.
Find the loop by its interface and its ctx key. The concrete class you are looking at is replaceable, and the design is built to keep it that way.
---
/** Drives one session through turn and step boundaries. */
export class ReactLoopAgent implements Agent {
readonly inbox: Inbox
private phase: Phase
private activityDone: Promise<void> = Promise.resolve()
/** The agent-scoped registration boundary; the lifecycle owner unwinds it after the driver exits. */
readonly scope: Scope
readonly ctx: Context
/** Fused dispatcher, built once in the constructor so hot-path dispatches never allocate. */
private readonly dispatch: AgentEventDispatch
/** Whether this loop instance has appended its initial/resume request anchor. */
private requestHeaderLogged = false
private readonly runtimeContext: RuntimeContextProjection
constructor(
private loopCtx: Context,
public readonly id: SessionId,
public readonly options: AgentOptions,
public readonly session: Session,
) {
this.dispatch = agentEvents(loopCtx, this)
this.inbox = new Inbox(session, {
inserted: (message) => { this.dispatch.emit('agent/inbox/inserted', { message }) },
discarded: (message) => { this.dispatch.emit('agent/inbox/discarded', { message }) },
claimed: (message, turn) => { this.dispatch.emit('agent/inbox/claimed', { message, turn }) },
})
const lastTurn = session.events.findLast(event => event.type === 'turn/start')?.data.turn ?? 0
this.phase = { kind: 'idle', lastTurn }
this.scope = createScope(loopCtx, this)
this.ctx = this.scope.ctx.extend({ agent: this })
this.runtimeContext = new RuntimeContextProjection(this.ctx, session)
}The CLI Is a Mount, Not an Engine
apps/cli/package.json:8The dsh binary's "engine" is an almost-empty list of plugin packages it depends on
The CLI does not contain an agent. It depends on a long list of plugin packages — the base bundle, the app boot, the loop, the commands. The "product" is the sum of the tree it boots, not this file. Add a dependency, and you have added a feature; remove one, and it is gone.
Capability is a function of the plugin tree. The entry point is just the thing that composes and mounts it. ---
"repository": {
"type": "git",
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
"directory": "apps/cli"
},
"type": "module",
"bin": {
"dsh": "lib/bin.js"
},
"files": [
"lib/*.js",
"config"
],
"license": "MIT",
"dependencies": {
"@deepseek-ai/cordis-plugin-hmr": "workspace:^",
"@deepseek-ai/cordis-plugin-include": "workspace:^",
"@deepseek-ai/cordis-plugin-loader": "workspace:^",
"@deepseek-ai/cordis-plugin-timer": "workspace:^",
"@deepseek-ai/dsh-agent-tool-presentation": "workspace:^",
"@deepseek-ai/dsh-app-boot": "workspace:^",
"@deepseek-ai/dsh-base": "workspace:^",
"@deepseek-ai/dsh-cordis-client-runner": "workspace:^",
"@deepseek-ai/dsh-client-ui-agent-preset": "workspace:^",
"@deepseek-ai/dsh-client-ui-cordis": "workspace:^",
"@deepseek-ai/dsh-command-compact": "workspace:^",
"@deepseek-ai/dsh-command-goal": "workspace:^",
"@deepseek-ai/dsh-compaction-basic": "workspace:^",
"@deepseek-ai/dsh-compaction-tool-result-pruner": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-goal-round-driver": "workspace:^",
"@deepseek-ai/dsh-cmdline": "workspace:^",
"@deepseek-ai/dsh-launch-environment": "workspace:^",A Bundle Declares Itself
packages/bundle/base/package.json:36How a bundle registers into the composition — a single dsh.bundle field pointing at its patch file
This is how a bundle announces its existence to the compositor. It points at a cordis.patch.yml that describes the config rows and code it mounts. The base bundle is the first layer of every profile; the web and headless bundles stack on top of it. Each is just package.json plus a patch — nothing privileged.
A bundle is declarative. It contributes rows and code through a patch file; the compositor applies them in order. ---
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
},Boot Composes the Tree
packages/boot/app-boot/src/index.ts:757boot() builds a Context, mounts the root include, waits for the tree to load, and asserts every entry activated
Boot does not construct an agent. It makes a shared Context, installs a Loader, mounts the root config (the profile's composed tree), and waits until every entry in that tree is active. Failure is labelled by stage. This is the whole "boot": compose a plugin tree and wait for it to settle.
Boot = build a Context + mount a plugin tree + assert it activated. There is no hidden core being built underneath.
---
export async function boot(
binName: string,
absoluteConfigPath: string,
patches?: PatchOptions[],
prepare?: (ctx: Context) => Promise<void> | void,
bareModuleBaseUrl?: string,
): Promise<Context> {
const ctx = new Context()
// Two failure labels: `prepare` runs before any config-tree entry mounts,
// so its failure is host setup, not the plugin tree.
let stage = 'host preparation failed'
try {
ctx.baseUrl = pathToFileURL(dirname(absoluteConfigPath)).href + '/'
ctx.provide('dshHomePath', dshHomePath)
await ctx.plugin(Loader)
await prepare?.(ctx)
stage = 'plugin tree failed to load'
await mountRootInclude(ctx, absoluteConfigPath, patches, bareModuleBaseUrl)
// A surface can finish and dispose the whole tree while startup is still
// in flight, before the last entry settles. The Loader service goes with
// it, and the activation audit describes a live tree — reading `ctx.loader`
// past this point would throw a TypeError over an app that exited exactly
// as asked. Transactional group updates settle
// lifecycle inside the mount, so the teardown can land before it returns;
// re-check after every await.
await ctx.get('loader')?.await()
if (ctx.get('loader') === undefined) return ctx
await assertEntriesActivated(ctx, binName)
return ctx
} catch (cause) {
// Root-fiber disposal contains cleanup failures per observer (Cordis
// fiber.ts hardening) and a repeated call returns the settled single-shot
// result, so this await cannot reject and replace `cause`.
await ctx.fiber.dispose()
const detail = cause instanceof Error ? cause.message : String(cause)
// The transactional Loader wraps a failing entry apply in one message per
// tree layer; every layer's message is folded into `detail` above, and the
// deepest cause is the plugin's own thrown error, whose stack names the
// real failure site — append it so the startup diagnostic preserves the
// original activation error instead of only the wrap chain.
let deepest: unknown = cause
while (deepest instanceof Error && deepest.cause !== undefined) deepest = deepest.cause
const stack = deepest instanceof Error && deepest !== cause ? `\n${deepest.stack ?? deepest.message}` : ''
throw new Error(`${binName}: ${stage}: ${detail}${stack}`, { cause })
}
}Any Row Can Be Yours
docs/architecture.md:26How a profile stacks bundles and patches, and that the dump prints the exact tree your machine boots — and any printed row can be replaced by a patch
This is the payoff of the mental model. Because the product is a composed tree of declarative rows, you can inspect the exact tree you booted and override any row without touching the package that owns it. Extending dsh and reading dsh are the same act.
dsh --profile web --dump-config is the map of your system. Every row it shows is yours to replace.
---
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. A patch targets a row by id and replaces its whole config, or inserts new rows.
To see the tree your machine actually boots:
```sh
dsh --profile web --dump-config
```
Any row it prints can be replaced by a patch of your own.
Composition mechanics are in [app-boot](../packages/boot/app-boot/README.md#profiles); config fields are in the generated [config catalog](config-catalog.md).
## Core packagesYou've walked through 8 key areas of the DeepSeek Harness codebase.
Continue: Boot: From `dsh web` to a Mounted Plugin Tree → Browse all projectsCreate code tours for your project
Intraview lets AI create interactive walkthroughs of any codebase. Install the free VS Code extension and generate your first tour in minutes.
Install Intraview Free