🔬 Forensic LLM Orchestration Analysis

Inside DeepSeek Harness
the everything-is-a-plugin agent runtime

An open-source agent harness by DeepSeek AI. 54 packages. ~450K lines of TypeScript. Zero privileged core — every component, from the model adapter to the agent loop itself, is a replaceable plugin powered by the Cordis framework.

GitHub Repo ↗ Explore the Anatomy ↓
$ npx @deepseek-ai/dsh web  # web UI at 127.0.0.1:3080
0
Packages
0
Lines of TS
0
Test Files
0
TS Files
31
Capability Seams
MIT
License
01 · Architecture Overview

One context to rule them all

A running dsh is a plugin tree composed at boot from ordered layers. Every capability lives behind a service key on a shared ctx — plugins find each other by key, never by import. There is no privileged core to patch: you extend dsh by mounting a plugin beside the others.

FIG 1 · DeepSeek Harness plugin architecture — services on the shared Cordis context
flowchart TB
  subgraph BOOT["Boot Composition (Profiles × Bundles)"]
    P1["profile: web"] --> B1["bundle: dsh-base"]
    P1 --> B2["bundle: dsh-web-app"]
    P2["profile: headless"] --> B1
    P2 --> B3["bundle: dsh-headless"]
    B1 --> PATCH["cordis.patch.yml layers
(profile → home → --patch)"] end subgraph CTX["Shared Cordis Context (ctx.*)"] C1["ctx.sessions
append-only event log"] C2["ctx.systemPrompt
prompt section registry"] C3["ctx.tools
tool registry + guarded pipeline"] C4["ctx.agents
live agent registry"] C5["ctx.agentLoop
default driver (ReactLoop)"] C6["ctx.llm
adapter registry + stream waterfall"] end subgraph SEAMS["Swappable Capability Seams"] S1["ctx.fs → fs-local / fs-sandbox / fs-e2b"] S2["ctx.subprocess → local / e2b"] S3["ctx.shell → bash-local / bash-sandbox / pwsh"] S4["ctx.sessionPersistence → jsonl / sqlite"] S5["ctx.subagents → in-process / acp / codex / claude-code"] S6["ctx.web → exa / perplexity / deepseek search"] end BOOT --> CTX CTX --> SEAMS C5 --> C1 & C2 & C3 & C4 & C6
🧩

Everything is a plugin

Model adapters, tool registries, session logs, even the agent loop itself are plugins contributing services, typed events and reversible effects. Registrations unwind when a plugin unloads.

packages/core/agent-loop · packages/llm/llm
📜

The log is the truth

“Model-visible means logged.” Everything that reaches a model request must be reconstructable from the append-only session log. A runtime invariant asserts it.

packages/core/session · deriveMessages()
🔌

Seams, not forks

One provider swap changes the whole product. Point filesystem & subprocess at a remote sandbox and Bash, PTY and LSP all move with them — no provider forks.

docs/capability-seams.md · 31 seams
02 · The Foundation

Cordis in five ideas

Cordis is the vendored plugin framework underneath dsh — a “programming paradigm for spatiotemporal composability.” These five ideas define how every plugin in the harness works.

1 · Plugin = Service object

A plugin is a function with optional inject and apply(ctx), or a Service subclass Cordis mounts into the current context. Lifecycle is framework-managed.

// packages/core/agent-loop/src/index.ts export class AgentLoop extends Service implements AgentFactory { static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt']

2 · Context = service repository

A service claims a stable ctx.<key> like ctx.tools or ctx.llm. Plugins find each other by key instead of importing concrete implementations — dependency inversion baked into the framework.

// TypeScript declaration merging extends the context declare module '@deepseek-ai/cordis' { interface Context { systemPrompt: SystemPrompt } }

3 · inject = dependency order

A plugin that names required services waits until those services exist. Load order is expressed through service requirements rather than manual boot sequencing — the tree self-assembles.

// Declare the services this plugin needs to run static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt'] // Cordis waits, then mounts — no manual ordering

4 · Typed events, 4 dispatch modes

Events are declared via TypeScript declaration merging and dispatched as emit, waterfall, parallel or serial — observe, wrap, fan out, or run in order. The mode is part of the public contract.

emit · observe waterfall · wrap (next()) parallel · fan out serial · ordered

5 · Registrations are reversible effects

Prompt sections, tool schemas, adapters, providers and listeners are installed through ctx.effect() or ctx.on() — so reload and teardown unwind them predictably. This is what makes hot plugin replacement safe and is the foundation of dsh's live-config story.

// Every registration has a disposer — teardown unwinds in reverse order ctx.effect(() => () => this.ownership.dispose(), 'agentLoop.transactions()') ctx.effect(() => ctx.agents.setFactory(this), 'agentLoop.setFactory()')
03 · Event Sourcing

The session log: append-only, replayable, authoritative

Every fact in a conversation — turn boundaries, user messages, streaming chunks, tool calls, compactions — is a durable SessionEvent appended to a per-session log. The model's context window is a projection of that log, not a separately managed buffer.

Durable events (replay = truth)

These event types survive reload and drive everything downstream:

turn/startturn/end step/startstep/end user/messageassistant/chunk assistant/messagetool/call tool/resultrequest/headercompaction/startcompaction/end

Raw assistant/chunk events preserve replay and UI fidelity — the UI can render the exact token stream again, and sourceEventSeqs links each assistant message to the chunks that produced it.

The surface: model-visible projection

Only 3 event types produce LLM messages: user/message, assistant/message, tool/result. The surface is an ordered view of those nodes. Compaction replaces a surface range with a summary — the old nodes are shadowed in the log, never deleted.

// packages/core/session/src/surface.ts const SURFACE_EVENT_TYPES = new Set([ 'user/message', 'assistant/message', 'tool/result', ])

⚡ “Model-visible means logged” — a runtime invariant asserts any new model-visible input requires a new session event.

FIG 2 · Session log → surface projection → model context
flowchart LR
  subgraph LOG["Append-Only SessionEvent Log (seq 1 → n)"]
    E1["turn/start"]
    E2["user/message"]
    E3["assistant/chunk × k"]
    E4["assistant/message"]
    E5["tool/call"]
    E6["tool/result"]
    E7["compaction/start"]
    E8["summary user/message (replace)"]
    E9["compaction/end"]
  end
  LOG --> FOLD["Surface Fold
replay + replace ops"] FOLD --> SURF["Surface Nodes
(3 eligible types)"] SURF --> DERIVE["deriveMessages()
projection rule"] DERIVE --> REQ["Model request messages"] SURF -. shadowed .-> OLD["shadowed range stays in log
for replay & audit"]
04 · Turn & Step Lifecycle

A turn is zero or more steps; a step is one model request + its tools

The ReactLoopAgent drives the loop. A step is one model request plus the tools it calls. A turn opens before its first input is claimed and closes once nothing is owed. Every boundary is a durable event; every interception is a typed event.

FIG 3 · The full turn/step sequence (from docs/agent-lifecycle.md)
sequenceDiagram
  participant U as User
  participant A as Agent (ReactLoop)
  participant H as Hook listeners
  participant P as ctx.systemPrompt
  participant L as ctx.llm
  participant T as ctx.tools
  participant S as Session
  U->>A: followup(content)
  A->>S: turn/start
  Note over A: claim next-step input + one queued message
  A->>H: agent/pre-step (waterfall)
  H-->>A: enter(messages) | reject
  A->>S: step/start + user/message*
  A->>P: system-prompt/assemble (waterfall)
  A->>L: agent/request → llm/stream (waterfall)
  L-->>A: StreamChunk*
  A->>S: assistant/chunk* (durable)
  A->>S: assistant/message
  A->>T: classify calls → executeToolCalls
  T->>S: tool/call + tool/result (model order)
  A->>S: step/end
  Note over A: tools owe another request? → next step
  A->>H: agent/turn-stopping (serial checkpoint)
  A->>S: turn/end
    

🎯 agent/pre-step

Waterfall that decides what the model sees. Listeners may rewrite claimed messages or reject them outright. A rejected first claim still closes a durable turn that spent no step.

🔄 agent/request

Waterfall over the request config (provider, model, reasoning effort, max tokens). Plugins can propose changes; the adapter resolves exact-model defaults at prepareCall().

🧯 agent/request-error

Recovery waterfall for failed streams. Listeners return a retry action or preserve the original error. Compaction hooks here for context overflow recovery.

The driver state machine

Three phases — idle → maintenance → running. Input reaches the driver through one inbox with splice/claim semantics. Some messages wake it immediately (followup, steer); injected context (agent.inject()) waits in the inbox until another message does. max-tokens is sticky: once any step hits the ceiling, later completed steps cannot downgrade the turn outcome.

// packages/core/agent-loop/src/agent.ts — sticky max-tokens if (turnEnds === null || turnEnds.kind !== 'max-tokens') turnEnds = stepEnd
05 · The Prompt Seam

System prompt as a merge-extensible registry

Instead of a single hard-coded system string, dsh assembles the system prompt from named, ordered sections contributed by any plugin — plus dynamic contexts, tool schemas and typed variables. Assembly runs a system-prompt/assemble waterfall so any plugin can transform the final result.

FIG 4 · Prompt assembly — sections, contexts, tools, variables → renderPrompt
flowchart TB
  subgraph SECTIONS["PromptSections (ordered, named)"]
    S1["order -100 · harness identity"]
    S2["order 0 · deployment persona
(PERSONA_ORDER)"] S3["order 100–199 · tool guidance"] S4["order n · plugin sections"] end subgraph OTHER["Other providers"] C1["PromptContexts
dynamic runtime snapshots"] T1["Tool schemas
from ctx.tools"] V1["Variables {{provider}} {{model}} {{cwd}}"] end SECTIONS --> ASSEMBLE["system-prompt/assemble
waterfall (scope-filtered)"] OTHER --> ASSEMBLE ASSEMBLE --> RENDER["renderPrompt()
interpolate → filter empty → join \\n\\n"] RENDER --> MODEL["model system prompt"] subgraph SCOPE["Scoped assembly"] SC1["global providers"] SC2["agent.ctx providers
(per-agent overrides)"] end SCOPE --> ASSEMBLE

Section contract

interface PromptSection { readonly name: string // unique — duplicates throw readonly order: number // ascending concat order readonly text: string | Fn // static or per-assembly readonly complete?: boolean // replace ALL sections }

A complete section becomes the sole prompt section (restored after the waterfall so listeners can't mutate it). More than one effective complete section fails assembly.

Strict variable interpolation

// Malformed/unknown/undefined {{vars}} throw — fail loud const VARIABLE_NAME = /^[a-z][a-z0-9_]*$/ // registered: provider, model, cwd (agent-loop) ctx.systemPrompt.variable('provider', c => c.agent?.options.provider)

Variables are resolved once — substituted values are not scanned again. A lone {{ without a closing }} is treated as literal prose.

Runtime context snapshot

Dynamic context (time, agent instructions, session references, tmux state) is materialized as a durable user-role snapshot — not a system-prompt append. It lands as its own message with a supersede marker: “Current runtime context. This snapshot supersedes earlier runtime-context snapshots.” Because it's a logged event, replay and fork stay faithful.

06 · Tool Execution

The guarded tool pipeline

Every tool call flows through policy waterfalls, monotonic guards, an approval seam, an around-dispatch waterfall, filesystem intent gates and a frozen final-result notification — all without changing the agent loop.

FIG 5 · Tool execution pipeline (from docs/tool-execution-pipeline.md)
flowchart TD
  M["assistant message has tool-call"]
  TC["session event: tool/call"]
  PRE["tools/pre-execute waterfall
hooks · permission · sandbox"] G["monotonic guards
deny or abstain"] APP["ctx.approval one-shot prompt
absent/unanswerable → deny"] EX["tools/execute waterfall
timeout · retry · metrics"] BODY["tool execute() body"] FSG["fs/write-intent · fs/edit-intent
tool-fs mutations only"] POST["tools/post-execute waterfall
accept · block · replace · add context"] NORM["registry normalization
snapshot failures → isError"] FIN["ToolDefinition.finalizeContent"] RES["tools/result
frozen authoritative outcome"] TR["session event: tool/result"] M --> TC --> PRE -->|allow| G PRE -->|ask| APP -->|allowed-once| G PRE -->|deny| DEN["denied — body skipped"] G -->|deny| DEN G -->|allow| EX --> BODY --> FSG FSG --> EX BODY --> POST --> NORM --> FIN --> RES --> TR PRE -. throw .-> NORM G -. throw .-> NORM EX -. wrapper throws .-> NORM POST -. throw .-> NORM DEN --> POST

🚦 Monotonic guards

Owner policy that must not be reordered stays as a guard — deny or abstain, never reordered by later registrations. Identity-protected.

⏱️ Around-dispatch

Timeout, retry and metrics wrap tools/execute — around-dispatch concerns live in the waterfall, so hooks span tool families without coupling tools to one policy service.

🧊 Frozen outcomes

tools/result observes the immutable, lossless-JSON outcome. additionalContexts flow into the next step's inbox as FIFO-injected user messages.

Parallel tool scheduler

Exclusive calls form barriers; parallel calls run in a bounded rolling pool (maxParallelToolCalls, default cap, live-adjustable). Dispatch may overlap, but policy, results and result context commit in model order — the model always sees its calls answered in the order it issued them. Abort records synthetic error results for skipped calls so replay stays valid.

// packages/core/agent-loop/src/tool-calls.ts while (next < planned.length) { const mode = ctx.tools.executionMode(first.exec).kind const group = mode === 'parallel' ? planned.slice(next) : [first] const outcome = await runGroup(ctx, turn, step, group, mode, signal, acceptContext) next += outcome.consumed }
07 · Context Management

Compaction & spill: keeping the window honest

dsh treats context as a priced, measured resource. A replay-based token meter measures pressure; region-based compaction summarizes the head while keeping a priced tail; a spill policy keeps oversized tool output out of the window entirely.

FIG 6 · Compaction transaction — select, summarize, verify stability, commit
flowchart TB
  P["post-step pressure event
or request-error (context overflow)"] M["token-meter replay fold
priced surface nodes"] SEL["selectCompactableRange()
retain priced recent tail"] PAIR["never split tool-call/result pair
toolPairingBalancedBefore"] START["session event: compaction/start
(the durable lock)"] SUM["summarize span → summary user/message"] STAB["stability check
whole-surface or selected-span"] CHG["SurfaceChangedError —
summary invalidated"] COMMIT["commit: replace surface span
shadow old nodes"] END["session event: compaction/end"] P --> M --> SEL --> PAIR --> START --> SUM --> STAB STAB -->|changed| CHG STAB -->|stable| COMMIT --> END

📏 Region selection

Walks the surface from the tail, accumulating token cost until the retainTokens budget is met; everything before stays compactable. The cutoff backs up to the nearest balanced tool-call/result boundary so a summary never splits a pair.

while (keepFromIdx > 0) { if (toolPairingBalancedBefore(session, surfaceNodes[keepFromIdx])) break keepFromIdx -= 1 }

🛡️ Stability check

Summarization is async, so the surface may move mid-flight. Before commit, dsh verifies the replacement boundaries are still the ones the summary was built from — otherwise it throws SurfaceChangedError, and the original request error remains authoritative.

A failed close deliberately leaves an unmatched compaction/start detectable — audit never lies.

💧 Spill policy: bounded tool output

When a plain-text tool result exceeds maxInlineBytes, the full text is saved to a session-scoped spill artifact and the model-facing result becomes a bounded head/tail preview plus a locator and retrieval hint. It's deliberately best-effort — a spill failure never turns a successful tool call into an error. The read tool is skipped to avoid a read → spill → read-again loop; the durable log copy is bounded by a second arm on tools/code-dispatch-log.

// packages/spill/spill-policy — the replacement notice `(${describeOmitted(omitted, 'bytes')} Full formatted result stored at: ${ref.locator}. ${ref.retrievalHint})`
08 · Capability Seams

31 seams — one provider swap changes the whole product

A seam is a swappable capability with three roles: a Service Definition declaring the interface, a Service Provider implementing it, and a Consumer using it (commonly a model-facing tool). One role alone is not a seam.

ctx keyRoleImplementationsConsumers
ctx.llmseamllm-deepseek · llm-pi-ai · llm-replayagent-loop · compaction-basic
ctx.fsseamfs-local · fs-sandbox · fs-e2btool-fs (+ fs-observation-policy gate)
ctx.subprocessseamsubprocess-local · subprocess-e2bbash-local · terminal-bash · lsp-stdio · subagent-acp/codex/claude-code
ctx.shellseambash-local · bash-sandbox · pwsh-localtool-bash · tool-pwsh · hooks-claude-code · hooks-codex
ctx.sessionPersistenceseamjsonl · sqliteagent-loop · session-query · feedback
ctx.subagentsseamspawn/fork-in-process · acp · codex · claude-code · dsh-sdktool-subagent · tool-subagent-control · tool-ralph
ctx.sandboxseamsandbox-local · (remote)bash-sandbox · terminal-bash · fs-sandbox
ctx.webseamweb-search-exa · perplexity · deepseek · web-fetch-httptool-web
ctx.compactionseamcompaction-basic (+tool-result-pruner)pressure events · request-error recovery
ctx.approvalseamacp bridge · (human)tools · tool-bash
ctx.credentialsseamcredentials-localllm-deepseek · llm-pi-ai · apiproxy
ctx.sessionTitleseamfirst-prompt-llm · all-prompts-llmUI / session query
ctx.spillStoreseamspill-localspill-policy (post-execute consumer)

Why seams matter

Filesystem and subprocess providers share one execution world — pointing them at a remote E2B sandbox moves Bash, PTY and LSP with them, with no provider forks. Subagent providers vary just as widely behind one interface: from a fresh child agent to a delegated turn in another product (Claude Code, Codex, ACP). This is how a harness stays future-proof: adding a capability means designing all three roles, and swapping one provider changes the whole product.

09 · Key Innovations

The nine ideas that make dsh different

1

No privileged core — everything is a plugin

The agent loop itself is a plugin. There is no fork point, no “core vs extension” boundary: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when the plugin unloads.

WHY IT MATTERS → Replace any subsystem from configuration; hot reload without a fork.
2

The append-only session log as single source of truth

Model-visible means logged. Fork, resume, transcripts, telemetry and persistence all derive from one event stream. Raw chunks preserved for byte-perfect replay.

WHY IT MATTERS → Every request is reconstructable; audit and replay are free.
3

System prompt as a merge-extensible registry

Named, ordered sections + dynamic contexts + tool schemas + strict variables, assembled through a scope-filtered waterfall. Persona is a replaceable slot (deployment:persona at order 0).

WHY IT MATTERS → Plugins compose the prompt; per-agent overrides shadow global sections.
4

31 capability seams with three-role contracts

Service Definition + Provider + Consumer. One provider swap (e.g. local fs → remote sandbox) cascades across the whole product.

WHY IT MATTERS → Sandboxing, remote execution and provider diversity are config, not code.
5

Waterfall events as around-middleware

llm/stream, tools/pre-execute, agent/pre-step are all waterfalls: listeners receive (...args, next) and delegate, short-circuit, or wrap. Retry, replay, routing and policy all live here.

WHY IT MATTERS → Middleware-style interception without decorator soup.
6

Model-order tool scheduling with bounded parallelism

Exclusive barriers + bounded rolling pool. Dispatch overlaps, but policy, results and context commit in model order. Abort records synthetic results to keep replay valid.

WHY IT MATTERS → Parallel speed with serial determinism — the best of both.
7

Region-based compaction with stability verification

Token-metered selection, tool-pair safety, async summarization guarded by a surface-stability check, and durable compaction/start–end brackets.

WHY IT MATTERS → Summaries can never corrupt or split conversation structure.
8

Spill policy — model-free context budgeting

Oversized tool results become head/tail previews + locators, spilled to session-scoped storage. Best-effort: failure never breaks the tool call.

WHY IT MATTERS → Context window stays honest; huge outputs stay retrievable.
9

Scoped per-agent registration

Each agent gets its own agent.ctx scope. Capability sets, prompt sections and tool sets can differ per agent — presets compose an agent by mounting scoped rows.

WHY IT MATTERS → One harness, many agent personas, zero global-state pollution.
10 · Code Walkthrough

Forensic code gems

The highest-signal excerpts from the analysis — the seams where dsh's design decisions live.

The turn loop — one model call per step, durable boundaries everywhere
// packages/core/agent-loop/src/agent.ts — turn() while (true) { signal.throwIfAborted() const step = phase.step + 1 const decision = await this.preStep(target, { turn, step }) if (decision.kind === 'reject') { turnEnds = { kind: 'blocked' }; return false } ... this.session.append('step/start', { turn, step }) try { for (const message of decision.messages) this.session.append('user/message', message, { surfaceOp: 'append' }) const stepEnd = await this.step(decision.assembly) if (turnEnds === null || turnEnds.kind !== 'max-tokens') turnEnds = stepEnd } finally { this.session.append('step/end', { turn, step }) } ... }
Request construction — the config waterfall and prepared calls
// packages/core/agent-loop/src/agent.ts — buildRequest() const proposedConfig = await this.dispatch.waterfall( 'agent/request', { turn, step, signal }, () => Promise.resolve(seedConfig), // default proposal ) ... preparedCall = await this.loopCtx.llm.prepareCall(proposedConfig, signal) // adapter resolves exact-model defaults (reasoningEffort, maxTokens) // header changes are themselves durable events: this.session.append('request/header', { header, reason: 'change' })
Compaction transaction — durable lock, stability guard, one close attempt
// packages/compaction/compaction-basic/src/region.ts const startEvent = session.append('compaction/start', lifecycle) try { const prepared = prepareCompaction(dependencies, session, selection) const summarized = await summarizeCompaction(...) assertStable(dependencies, session, summarized) // surface moved? const pending = commitCompactionBody(session, startEvent, summarized) const endEvent = session.append('compaction/end', lifecycle) result = completeCompaction(pending, endEvent) } catch (error) { // every failure makes exactly ONE compaction/end attempt session.append('compaction/end', { ...lifecycle, error: errorChain(error) }) }
The DeepSeek adapter — transport-only, credentials resolved per request
// packages/llm/llm-deepseek/src/adapter.ts // fetch + SSE against OpenAI-compatible chat/completions endpoint. // Transport-only: connection facts arrive through a thunk resolved once // per operation; the bearer token through a per-request resolver, so a // rotated credential reaches the very next request. export class DeepSeekAdapter extends LlmAdapter { // serializeRequest() → parseSse() → translate() → StreamChunk* }
Boot — snapshot-aware config, fail-loud loader guards
// packages/boot/app-boot/src/index.ts // $DSH_SNAPSHOT=replay swaps cordis.yml → cordis.snapshot.yml const replayName = basename(absolute).replace(/cordis\.ya?ml$/, 'cordis.snapshot.yml') // BOOTSTRAP_NAMES protects process-critical env vars from .env files: // PATH, HOME, NODE_OPTIONS, LD_PRELOAD, PYTHONPATH ...
11 · Sources

Analysis trail

All findings verified against a fresh git clone --depth=1 of deepseek-ai/deepseek-harness (MIT).

Primary sources:

  • docs/architecture.md — profiles, bundles, events, turn flow
  • docs/cordis-primer.md — the five ideas + dispatch modes
  • docs/agent-lifecycle.md — curated sequence diagram
  • docs/tool-execution-pipeline.md — curated pipeline flowchart
  • docs/capability-seams.md — 31-seam service table
  • packages/core/agent-loop/src/{index,agent,tool-calls}.ts — the driver
  • packages/core/system-prompt/src/index.ts — prompt seam
  • packages/core/session/src/{index,surface}.ts — event log + surface
  • packages/compaction/compaction-basic/src/region.ts — compaction
  • packages/spill/spill-policy/src/index.ts — spill policy
  • packages/llm/llm-deepseek/src/adapter.ts — provider adapter
  • packages/boot/app-boot/src/index.ts — boot glue