DeepSeek Harness Internals: Plugin Architecture, Cordis Events & Agent Loops

This article explores DeepSeek Harness's plugin-centric architecture built on Cordis, detailing its event dispatch modes, seam capability model, agent step/turn lifecycle, built-in presets, custom LLM adapter integration, package conventions, headless and Python SDK usage, session logging guarantees, and safety considerations for experimental use.

Data STUDIO
Data STUDIO
Data STUDIO
DeepSeek Harness Internals: Plugin Architecture, Cordis Events & Agent Loops

01 The Mechanism Behind "Everything Is a Plugin"

The previous article verified that "Everything is a plugin" is not just a slogan. DeepSeek Harness (dsh) achieves this through two structural pillars:

No privileged core : Model adapters, tool registries, session logs, and the agent loop itself are all plugins. Extending dsh means adding a plugin alongside the core, not forking and modifying it.

Cordis as the foundation : Cordis is a plugin framework providing services, typed events, and reversible effects, all attached to a shared ctx (context). The ctx.tools, ctx.effect, and inject=['tools'] seen earlier are Cordis primitives, not ad-hoc dsh interfaces.

02 Cordis Five Principles and Event Dispatch

The official Cordis primer distills the framework into five principles:

A plugin is a service : A plugin can be a function module with optional inject and apply(ctx), or a Service subclass mounted into the context.

Context is the service registry : Services claim stable keys like ctx.tools, ctx.llm, ctx.sessions; other plugins discover them by key, not by importing concrete implementations.

Declare dependencies with inject : Plugins declare required services; the framework resolves load order from the dependency graph, eliminating manual boot sequencing.

Typed events for communication : Services declare event names via TypeScript declaration merging and emit them as needed.

Registration as reversible effects : Prompt sections, tool schemas, adapters, providers, and listeners are installed via ctx.effect() / ctx.on() and unwind predictably on unload.

Five Dispatch Modes

The framework provides five event dispatch modes, each with distinct semantics:

emit : Fire-and-forget, no waiting, observers called in registration order, no return value.

waterfall : Sequential, does not wait for async completion, observers receive (...args, next) and must call next() to pass control; skipping next() short-circuits the chain. Returns the final value.

parallel : All observers run concurrently, waits for all, no return value.

serial : Sequential, waits for each observer, returns the last observer's return value.

bail : Sequential until an observer returns a truthy value (bails), returns that value.

The waterfall mode is especially critical because many agent-loop hooks rely on it. It acts like around-middleware: a listener can either delegate via next() or terminate the chain, making the boundary between decision-making plugins (which may short-circuit) and observational plugins (which must call next()) explicit.

03 Seam: The "Seam" of Capabilities

Tools are just one kind of plugin. The real unit of replacement in dsh is the seam , which splits a capability into three roles:

Service Definition : Declares the interface (the capability contract).

Service Provider : Implements the interface and mounts it on a ctx key.

Consumer : Consumes the capability by key, never importing the concrete implementation.

The official stance is strict: "one role alone is not a seam; adding a capability means designing all three." The repository's docs/capability-seams.md generates a panorama of ctx. services: ctx.llm (LLM adapter registry), ctx.sessions (session storage), ctx.sessionPersistence (persistence), ctx.shell, ctx.sandbox, ctx.subagent, ctx.commands (human commands), ctx.skills (skill provider), ctx.tools (tool registry), etc. Each is a replaceable seam.

Shared Execution World

A practical advantage of seams over independent tools is that providers like filesystem and subprocess can share a single execution world. For example, switching the sandbox provider to a remote environment automatically migrates Bash, PTY, and LSP together, avoiding separate Remote Bash, Remote PTY, Remote LSP forks. This difference is negligible in local demos but becomes valuable when integrating remote sandboxes, containers, or clusters.

Consumer-side code illustrates the hard vs. optional dependency distinction:

export const inject = ['tools', 'shell', 'systemPrompt', 'shellEnv']

// Hard dependencies: declared in inject, accessed directly as properties; missing ones prevent startup
const defaultMode = ctx.shell.sandboxMode

// Optional dependencies: not in inject, fetched via ctx.get(), may return undefined
const sandboxPolicy = defaultMode === undefined ? undefined : ctx.get('sandboxPolicy')
inject

+ direct property access = hard dependency; ctx.get(...) = optional dependency. Interception and policy use events ( ctx.on / waterfall); direct capability calls use service methods. Do not mix them.

04 Agent Loop: Step, Turn, and Event Waterfall

The agent loop itself is a plugin ( @deepseek-ai/dsh-agent-loop), meaning "how the agent loops" is not hard-coded in the framework. It defines two fundamental units:

step : One model request plus the tools it invokes.

turn : Zero or more steps. Opens before the first input is claimed, closes when "nothing more is owed."

A complete turn's event waterfall (official sequence, simplified):

turn/start → claim input
 → agent/pre-step (waterfall, can reject/rewrite/pass)
 → step/start → user/message → system-prompt/assemble
 → agent/request (waterfall) → llm/stream (waterfall)
 → assistant/chunk* → assistant/message
 → tool/call* → tools/pre-execute → tools/execute → tools/post-execute → tool/result*
 → step/end → ... → agent/turn-stopping (serial, no next) → turn/end

Key judgments: agent/pre-step, agent/request, llm/stream, and the three tools/* events are waterfall — listeners must call next() to delegate. agent/turn-stopping is serial with no next(); it is the final checkpoint for "should this turn stop?". agent/* events are the live coordination API (queue/state/prompt interception/request construction/steering/continuation/error); session/event is replayable persistent data. SDK/UI replay consumes session/event; real-time control, prompt interception, request construction consume agent/*.

A counter-intuitive detail: even if the first claim is rejected or the input is empty, a "persistent turn with no consumed steps" is still created. Turn boundaries are defined by complete open/close cycles, not by whether work was done. This shows dsh's logs record full runtime facts, not just successful actions.

05 Agent Preset and Isolate Realm

To give a session a distinct capability set, you compose an agent preset . The architecture docs state: "give a session a different capability set — compose an agent preset; the service row there needs an isolate realm" — meaning that capability set does not pollute other sessions' scopes.

The official repo includes four built-in presets under packages/preset/agent-presets/presets/: standard (standard mode, full coding agent) ptc (PTC mode, script-first) minimal (minimal mode, only bash/pwsh + str_replace_editor) cordis (creative mode, for authoring custom presets; directory name is cordis, display name is "creative mode")

An agent preset is essentially a directory containing agent.cordis.yml (service row composition) and preset.yml (name/description/order). PTC and Minimal are worth noting for advanced users:

PTC mode : Model emits a script first, then executes it; tools can be called directly from the script, with parameters/return types inferred from a single schema, running through the normal execution pipeline (including permission policy). The process-level switch is env var DSH_TOOLS_MODE with values native, ptc, both; other values cause startup failure.

Minimal preset : Combines only persistent bash (or pwsh on Windows) and str_replace_editor, with a fixed system prompt: You are a helpful software engineer assistant. This provides a clean baseline for benchmarks or minimal sandbox experiments.

06 Plugging in a Custom LLM Adapter

"Model is a plugin" materializes as implementing a LlmAdapter. To integrate a custom model service, private gateway, or new provider, the recommended shape is:

class MyAdapter extends LlmAdapter {
  async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> { /* ... */ }
}

export const name = 'llm-myprovider'
export const inject = ['llm']
export const Config: z<Config> = z.object({
  apiKey: z.string(),
  // ...
})

export function apply(ctx: Context, config: Config) {
  // effect-based registration: HMR-safe; duplicate registration for same provider throws
  ctx.llm.registerAdapter(['my-provider'], new MyAdapter(/* ... */))
}

The code is straightforward; the protocol obligations are where implementers trip. The official docs specify: usage must be emitted before finish; nothing may be emitted after finish.

Tool call arguments are end-to-end RAW JSON strings ; streaming fragments use argumentsDelta.

Two legitimate error exits: throw from stream() (transport/protocol failure), or end with finish {kind: 'error' | 'aborted'} (provider-internal failure).

Must respect options.signal (pass through to fetch / your SDK).

Unsupported options must explicitly throw UNSUPPORTED_OPTION, not silently ignore.

Secrets use Cordis-native schemastery Config with environment variable fallback (e.g., !!js process.env.MY_KEY in cordis.yml); do not read temporary key files in code.

07 Adding a Package by Specification

Building on dsh often means creating a new @deepseek-ai/dsh-* package. The official adding-a-package cookbook provides a creation-to-validation checklist; three principles are most impactful:

Split replaceable capabilities by Definition/Provider/Consumer . When the three roles will evolve independently , split into three packages; the shell trio ( tool-bash → shell / shell-env / sandbox-policy) is the official template. Single-responsibility plugins can stay in one package.

package.json has many invariants enforced by pnpm run constraints: private: true, version aligned with root, @deepseek-ai/cordis in both peerDependencies and devDependencies, etc. You cannot write it arbitrarily.

Naming singular/plural follows rules : singular ctx key for "one engine/runtime/policy/controller/resolver/store/config"; plural key for "a registry or a service with multiple named members".

Validation command:

pnpm install && pnpm run doc-sync && pnpm run constraints && pnpm run typecheck && pnpm run lint && pnpm run build && pnpm run hygiene

08 Advanced Composition: Profiles, Bundles, and Patches

We previously used --patch as a "temporarily attach a plugin" entry point. Advancing further requires understanding dsh's full configuration layering. A running dsh is essentially a plugin tree composed by layered stacking :

Each bundle in profile (per dsh.profile.bundles order)
 → profile's own cordis.patch.yml
 → home-level $DSH_HOME/cordis.patch.yml
 → command-line --patch overlay layer

Bundle names resolve first from dsh install directory (built-in packages like @deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app), then from profile's node_modules (pnpm-installed out-of-tree plugins).

Built-in profiles web, headless, sdk, sdk-minimal, acp auto-initialize from templates on first use; custom profiles require dsh plugin --profile <name> add <package>. dsh --profile web --dump-config / --dump-default-config show the composed plugin tree without starting.

The web profile defaults to patchReload: live (patch changes hot-reload); headless / sdk (one-shot/stdio apps) use startup (apply once at launch).

09 Advanced Headless and Python SDK Usage

headless: Embedding dsh in Scripts/CI

dsh --profile headless "run the tests"

Output contract (explicit in official docs):

Each non-empty reasoning delta writes to stderr (prefixed dsh: reasoning:).

Final answer writes to stdout .

Normal completion exit 0 ; abort or error exit 1 (error printed to stderr as dsh: <code>: <message>).

One run = one task , no interactive follow-up.

Ideal for CI steps and one-way batch processing: no port listening, runs to completion, exit code is the result. However, because it runs unattended and has not yet been security-audited, do not use it for irreversible bulk write operations.

Python SDK: Deterministic Entry for Batch Processing

from deepseek_harness import DeepSeekHarness

with DeepSeekHarness(
    dsh_home="/absolute/path/to/isolated-dsh-home",
    cwd="/absolute/path/to/workspace",
    provider="deepseek-official",
    model="deepseek-v4-flash",
) as harness:
    result = harness.run(
        "Migrate fetch() calls across src/api.",
        session_id="batch-001",
    )

print(result.final_response)

Advanced points: dsh_home must be passed explicitly (or set DSH_HOME); the SDK deliberately does not read ~/.dsh, enabling each batch job to use an isolated home for credentials/sessions/plugins. DeepSeekHarness lazy-starts and reuses the runtime until close() / exit from with block. profile="sdk-minimal" gives a clean agent with only bash + str_replace_editor (suitable for reproducible batch jobs).

Persistent plugins via

dsh plugin --profile <name> add file:/.../my-plugin-bundle

; per-run temporary changes via patches=("/path/to.patch.yml",). harness.run() returns

RunResult(session_id, final_response, finish_reason, events, notifications)

.

10 Session Logs: Model-Visible Means Logged

Amid all the plugin, seam, and preset discussion, the single most important architectural constraint is:

Everything the model sees must be reconstructible from the session log.

Officially termed Model-visible means logged . Logs are append-only. Prompt fragments, every tool call's input/output, raw responses all go in; fork, resume, replay, transcription, telemetry, persistence all derive from this log stream, and the runtime enforces invariants on it.

The real value: observability ceases to be a "dashboard added after something breaks." It becomes a structural part of the system. When debugging why an agent made a strange decision, you never first hit the basic problem of "what did the model actually see, and why isn't it in the logs?".

11 When to Stop

Three concerns outweigh "what else can we extend" at this stage:

Officially marked as not security-audited . The SAFETY doc states: not yet security-audited, must not be considered safe or production-ready ; sandbox, approval, and permission controls do not guarantee isolation . The further you go (custom providers, remote sandboxes, headless batch), the more you must run in disposable/rollback environments.

Developer preview + breaking changes . All fields (seam, profile, preset, adapter) are as of 0.1.2-alpha.1 and current config-catalog; iteration is fast — verify against latest official source before any production use.

Credentials and telemetry defaults . Keys stored in $DSH_HOME/.credentials.yaml, write-only, no echo; telemetry off by default, gated by feedback, DSH_TELEMETRY_MODE changes behavior. Do not run advanced experiments without understanding credential exposure scope.

The verdict remains consistent with the previous article, but with clearer reasoning. What makes dsh interesting is not "many plugins" but that it turns parts of an agent that are usually hard-coded into replaceable capability boundaries. Model, tools, sessions, execution environment, even the agent loop — all swappable. This is genuinely valuable for anyone building Agent Frameworks, Coding Agents, or Long-Task Harnesses.

Yet the other side cannot be ignored: it is still 0.1.2-alpha.1, developer preview, security audit incomplete, interfaces in rapid flux. The appropriate posture remains: use it to build experiments, study the architecture, do not rush production workloads onto it.

For deeper exploration, the official repo's docs/architecture.md, docs/cordis-primer.md, docs/agent-lifecycle.md, and docs/cookbook/ are the next best references.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

plugin architecturesafetyheadlessPython SDKAgent LoopCordisDeepSeek HarnessLLM adapter
Data STUDIO
Written by

Data STUDIO

Click to receive the "Python Study Handbook"; reply "benefit" in the chat to get it. Data STUDIO focuses on original data science articles, centered on Python, covering machine learning, data analysis, visualization, MySQL and other practical knowledge and project case studies.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.