Boot: From `dsh web` to a Mounted Plugin Tree
Seven stops tracing a single `dsh web` invocation from the CLI binary to a settled, mounted Cordis tree
What you will learn
- How the `dsh` bin classifies an invocation and dynamically imports only the mode it needs
- What a profile is and how it stacks bundle layers plus one or more user patch layers
- How `composeProfile` orders the patch stack and builds a row index of the composed tree
- How `runProfile` delegates real work to the app-boot `boot()` entry point
- What `boot()` actually does: build a shared `Context`, install a Loader, and mount the root include
- How `mountRootInclude` resolves `cordis:` and bundled specifiers into the tree
- How a failing entry is caught, labelled by stage, and torn down without masking the original error
Prerequisites
- You can read TypeScript and basic JavaScript
- The Mental Model tour is recommended first (it defines profiles, bundles, and the `ctx` key)
The Binary Is a Dispatcher
apps/cli/src/bin.ts:27Dynamic imports per mode so dsh --version does not pay for the whole tree, and only a valid mode reaches the switch
The CLI parses argv, then dynamically imports the mode handler. The three modes are profile (boot an app), plugin (manage plugins), and dump-config (print the composed tree). For dsh web, the mode is profile and the profile is web. The bin does not contain an agent; it hands off to the module that boots one.
The CLI is a thin dispatcher. The "work" of dsh lives in profile-boot.ts, not here.
---
const invocation = parseDshArgs(process.argv.slice(2), readVersion())
switch (invocation.mode) {
case 'profile': {
const { runProfile } = await import('./profile-boot.ts')
await runProfile({
environment: loadLayeredEnv('dsh'),
profile: invocation.profile,
patchFiles: invocation.patches,
args: invocation.args,
})
break
}
case 'plugin': {
const { runPlugin } = await import('./plugin.ts')
process.exit(runPlugin(invocation.profile, invocation.args))
break
}
case 'dump-config': {
const { runDumpConfig } = await import('./dump-config.ts')
runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches)
break
}
default:
invocation satisfies never
throw new Error(`dsh: unhandled invocation mode ${JSON.stringify(invocation)}`)What a Profile Loads
apps/cli/src/profile-boot.ts:49A profile directory has a cordis.yml root, a user cordis.patch.yml, and a dsh.profile.bundles list in package.json
prepareProfile asks the Cordis loader for a profile directory. The root file cordis.yml is always rewritten to an empty list. This is deliberate: the profile's "real" content is the composed patch stack (bundle layers in order, then user layers, then overlays), which is applied over that empty root. The root exists on disk only so the Loader has a real include anchor for baseUrl.
A profile is a directory. Its shape is: a root cordis.yml (always empty), a dsh.profile.bundles list, a cordis.patch.yml user layer, and a base directory.
---
export function homePatchPath(): string {
return join(resolveDshHome(), PROFILE_PATCH_FILENAME)
}
/** Absolute path of this dsh installation's package.json (both anchors: src/ and lib/ sit one level under apps/cli). */
export const INSTALL_ANCHOR = fileURLToPath(new URL('../package.json', import.meta.url))
/** The session-telemetry row id the DSH_TELEMETRY_DISABLED switch targets. */
const TELEMETRY_ROW_ID = 'session-telemetry-otel'
/** The empty root entry list every profile tree patches over. */
const PROFILE_ROOT_CONFIG = `# dsh profile root — an empty entry list. The tree is composed as patches:
# each bundle in package.json's dsh.profile.bundles, then cordis.patch.yml, then any
# --patch overlays. Edit cordis.patch.yml, not this file.
[]
`
/** Root config filename inside a profile directory. */
export const PROFILE_ROOT_FILENAME = 'cordis.yml'
/**
* Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
* value (including `'0'`/`'false'`) disables: a privacy switch prefers
* off-by-mistake over on-by-mistake. A composition without the telemetry row
* exports nothing, so the switch is then trivially satisfied and no patch is
* generated — custom profiles need not mount telemetry to run with the
* switch set.
* @param disabledEnv - the raw `DSH_TELEMETRY_DISABLED` value (`undefined` when unset).
* @param hasRow - whether the composition carries the telemetry row.
* @returns the disable patch, or `undefined` when no hard-disable patch is required.
*/
export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: boolean): PatchOptions | undefined {
if ((disabledEnv ?? '') === '' || !hasRow) return undefined
return { id: TELEMETRY_ROW_ID, disabled: true }
}
/**
* Load a resolved profile for `name`: heal the shared module fallback, then
* (re)write the empty root config. The root is always rewritten: the whole
* composition is patch layers, and the vendored Loader's tree write-back (a
* plugin self-disposing persists the current tree) can bake composed rows
* into this file — which would duplicate every bundle insert on the next
* boot. The file exists on disk only because the Loader needs a real include
* root to anchor `baseUrl` at the profile directory (the config dump anchors
* on the same file, so both compose over the identical base).
* @param name - the profile name.
* @param userLayer - `false` skips parsing `cordis.patch.yml` (the default dump).
* @returns the loaded profile.
*/
export function prepareProfile(name: string, userLayer = true): Profile {
healProfilesModuleFallback(INSTALL_ANCHOR)
const profile = loadProfile(NAME, name, INSTALL_ANCHOR, undefined, { userLayer })
writeFileSync(join(profile.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG)
return profile
}Stacking the Patch Layers
apps/cli/src/profile-boot.ts:110The application order of every layer in a boot, and the row index built by composing them
The order is: bundle layers in profile order → profile user layer → home-level user layer → --patch overlays. composeEntries produces the final list of rows. Each row that carries an id is indexed in rows, so later code can ask "what is the configured value for agent-presets?" and get a real answer from the composed tree.
The composed tree is data, not code. It is a list of rows that the Loader will mount, in a fixed order. ---
/** The home-level user layer (`$DSH_HOME/cordis.patch.yml`), applied after the profile's own. */
homePatches: PatchOptions[]
/** Layers above the user layers on a live reload: `--patch` overlays and the telemetry switch. */
overlays: PatchOptions[]
/**
* id → row of the composed tree (bundles + user layers + overlays), for the
* launcher's own row checks.
*/
rows: ReadonlyMap<string, EntryOptions>
}
/** The full patch stack of one composed profile, in application order. */
function allPatches(composed: ComposedProfile): PatchOptions[] {
return [
...composed.bundlePatches,
...composed.profile.patches,
...composed.homePatches,
...composed.overlays,
]
}
/**
* Load `name` and compose its effective patch stack: bundle layers in
* `dsh.profile.bundles` order (the base bundle gates the shell stacks by
* platform on its own rows), the profile's user layer, the home-level user
* layer (`$DSH_HOME/cordis.patch.yml` — machine-local preferences that apply
* to every profile, so it outranks the per-profile layer), `--patch` overlays,
* then the telemetry switch.
* @param name - the profile name.
* @param patchFiles - `--patch` overlay paths, in argv order.
* @returns the profile, its patch layers, and the composed row index.
*/
function composeProfile(
name: string,
patchFiles: readonly string[],
): ComposedProfile {
const profile = prepareProfile(name)
const homePatches = loadOptionalPatches(NAME, homePatchPath()) ?? []
const overlays = patchFiles.flatMap(file => loadOverlayPatches(NAME, resolve(file)))`runProfile` Wires Signals and Calls `boot()`
apps/cli/src/profile-boot.ts:207The profile-mode bootstrap: compose, install signal handlers, freeze env, then call app-boot's boot()
The interesting part is the split between _static_ (the composed tree) and _live_ (the user layers that can be hot-reloaded). runProfile reads the user layers fresh per composition pass so a watcher edit never bakes into the bundle row underneath. Then it hands everything to boot() with a prepare callback that installs the launch environment and the parsed command line.
runProfile is the boundary between "launcher concerns" (signals, env, cwd) and "boot concerns" (context, tree, activation). Everything after the boot() call is in app-boot.
---
export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Context; shutdown: ProcessShutdown }> {
const composed = composeProfile(options.profile, options.patchFiles)
const app: { current?: Context } = {}
const shutdown = createProcessShutdown(async () => { await app.current?.fiber.dispose() })
const signalShutdown = new AbortController()
const interrupt = (code: number): void => {
signalShutdown.abort()
shutdown.interrupt(code)
}
// Signals own teardown throughout the startup window, not only after boot()
// settles: an inserted provider can publish before sibling rows finish mounting.
// SIGTERM is a supervisor's ordinary stop request and exits 0 on every
// surface — the launcher does not know whether the app considered its work
// complete; SIGINT is a user interrupt and reports 130.
process.on('SIGTERM', () => { interrupt(0) })
process.on('SIGINT', () => { interrupt(130) })
installFailLoud(NAME, process, async () => {
await app.current?.fiber.dispose()
})
const rootConfig = join(composed.profile.dir, PROFILE_ROOT_FILENAME)
// Recomposition for the live user layers: bundle layers below, overlays
// above, so a user edit can never displace them. Parsed app arguments are
// not in here at all — they live in app-provided services that survive a
// recomposition. BOTH
// user files are re-read per generation (the HMR watcher hands us only the
// changed file's patches, which one of the reads duplicates — fresh reads
// keep the two watchers from stitching in each other's stale copy).
// Fresh clones per generation: the include pushes `insert` rows into the
// mounted tree BY REFERENCE and later id-targeted patches mutate those
// objects in place. Reusing one parsed patch object across applications
// would bake a user override into the bundle's in-memory insert row, so
// removing the override could never revert the row to the bundle default.
const composeLive = (): PatchOptions[] => structuredClone([
...composed.bundlePatches,
...loadOptionalPatches(NAME, composed.profile.patchPath) ?? [],
...loadOptionalPatches(NAME, homePatchPath()) ?? [],
...composed.overlays,
])
// Cloned for the same insert-aliasing reason as composeLive: the boot
// application must not mutate the objects later reloads recompose from.
const ctx = await boot(NAME, rootConfig, structuredClone(allPatches(composed)), (hostCtx) => {
app.current = hostCtx
// Before any config-tree entry mounts, so plugins resolve all launch-time
// environment values from the same immutable provenance snapshot.
hostCtx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, options.environment)
// The command line and bounded exit request are launcher facts available
// to every app plugin that injects the argument snapshot.
provideCmdline(hostCtx, {`boot()` Builds a Context, Mounts a Tree
packages/boot/app-boot/src/index.ts:757The generic "boot a profile" primitive: build a shared Context, install the Loader, mount the root include, wait for activation
This is the "boot" the Mental Model tour described. It (1) creates a shared Context, (2) installs the Loader plugin, (3) runs the optional prepare hook (where runProfile provides the launch env and command line), (4) mounts the root include (the composed tree), (5) waits for every entry to finish, and (6) asserts each entry activated. On failure, the entire tree is torn down and the error is labelled by stage.
Two stages — host preparation failed and plugin tree failed to load — are the only two failure classes the boot can report. Everything else is a plugin's own concern.
---
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 })
}
}How the Root Include Mounts the Tree
packages/boot/app-boot/src/index.ts:486The root include is the tree's entry point; bundled specifiers and cordis: specifiers are resolved inside it
The root include is a Cordis plugin that the Loader installs. Bundled specifiers (@deepseek-ai/dsh-base) are resolved by the Loader's own mechanism; relative (./) and cordis: names are delegated to the parent include. This is why bundles can reference one another by package name without a registry.
The root include is the tree's entry point. It is where "mount a plugin tree" becomes "resolve and import a bundle name". ---
export async function mountRootInclude(
ctx: Context,
absoluteConfigPath: string,
patches: readonly PatchOptions[] = [],
bareModuleBaseUrl?: string,
): Promise<Entry | undefined> {
ctx.loader.builtins.include = bareModuleBaseUrl === undefined
? Include
: class HostResolvedRootInclude extends Include {
override import(name: string, getOuterStack?: () => string[]): unknown {
const specifier = isAbsolute(name) ? pathToFileURL(name).href : name
if (name.startsWith('.') || name.startsWith('cordis:')) return super.import(specifier, getOuterStack)
const internal = this.ctx.loader.internal
/* v8 ignore next -- Node supplies the internal loader; this preserves the
original diagnostic for hypothetical embedders without it. */
if (internal === undefined) return super.import(specifier, getOuterStack)
return internal.import(specifier, bareModuleBaseUrl, {})
}
}
// `cordis:group` alongside it: a group row is how a composition gives one
// `isolate` realm to a provider and its consumers together, and an agent
// preset living outside this workspace cannot resolve `@deepseek-ai/cordis-plugin-group`
// by name. Both builtins load through the ambient module pipeline, so neitherFailure Has Shape
packages/boot/app-boot/src/index.ts:658assertEntriesActivated is the activation audit; installFailLoud is the launcher's last-resort error surface
Between "the include reported done" and "the app is ready to run" sits an audit. assertEntriesActivated waits for every mounted entry to reach an activated state; any entry that failed is reported with the package name and the original error. The launcher's installFailLoud then formats the message and exits with a non-zero code so a supervisor can see the boot failed.
A failed entry does not leave a half-booted app running. The boot either fully activates or it does not. ---
export function assertEntriesLoaded(ctx: Context, binName: string): void {
const failed = [...ctx.loader.entries()].filter(entry => entry.fiber === undefined && !entry.disabled)
if (failed.length > 0) {
const names = failed.map(entry => entry.options.name).join(', ')
throw new Error(`${binName}: plugin(s) failed to load: ${names}; Cordis startup failed because these plugin(s) could not be resolved (see the error(s) logged above)`)
}
}
/**
* Value mirrors used because Cordis's const enum has no runtime object to import.
* Keep aligned with `packages/extensions/tool-cordis/src/fiber-state.ts` and
* `packages/client/web/src/loader-status.ts`.
*/
const FIBER_PENDING = 0 as FiberState.PENDING
const FIBER_ACTIVE = 2 as FiberState.ACTIVE
const FIBER_FAILED = 3 as FiberState.FAILED
/** Render a thrown plugin value without discarding an Error's original stack. */
function formatActivationError(error: unknown): string {
return error instanceof Error ? error.stack ?? error.message : String(error)
}
/**
* Reject a settled Loader tree when an enabled entry failed or remains inactive.
* Plugin failures include the original thrown stack; pending entries name their
* unresolved services because no plugin error exists for that state. Active
* entries require no further wait; only failed fibers are awaited to recover
* their private rejection reason.
* @param ctx - the settled context whose Loader entries to audit.
* @param binName - the diagnostic prefix on the thrown error.
* @returns nothing when every enabled entry is active.
* @throws after one process rejection checkpoint when an entry failed to
* import, rejected during activation, or did not become active.
*/
export async function assertEntriesActivated(ctx: Context, binName: string): Promise<void> {
assertEntriesLoaded(ctx, binName)
const failures: string[] = []
const rejectionReasons: unknown[] = []
for (const entry of ctx.loader.entries()) {
const fiber = entry.fiber
if (fiber === undefined || entry.disabled) continue
const state = fiber.state
if (state === FIBER_ACTIVE) continueYou've walked through 7 key areas of the DeepSeek Harness codebase.
Continue: The Agent Loop: One Message, One Step, One Log → 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