DeepSeek Harness Runtime Architecture: Separating Capabilities from Facts via Cordis, Agent Loop & Session
This article analyzes DeepSeek Harness v0.1.2-alpha.2's runtime architecture, explaining how it separates current capabilities (Cordis) from committed facts (Session) with the Agent Loop mediating between them, detailing plugin assembly, turn/step lifecycle, tool execution pipeline, session event logs, and dynamic capability replacement.
Overall Runtime Layering
DeepSeek Harness (DSH) splits the agent runtime into two formal tracks: Cordis maintains "what can be done now" (the current capability graph), while Session preserves "what has already happened" as an append-only event log. The Agent Loop sits between them, driving task progression by pulling capabilities from Cordis and writing results back to Session. This design addresses distributed-systems-style boundary problems — message placement, state commitment, failure recovery — that determine whether an agent runs reliably in production.
The analysis covers dsh-v0.1.2-alpha.2 (Developer Preview; interfaces may change).
Startup Assembly: Profile, Bundle, Patch
DSH startup uses a layered configuration model:
Profile
↓ selects Bundle
Bundle
↓ provides full plugin configs
Patch
↓ overrides partial config or adds extensions
Final plugin config treeProfile (e.g., web or headless) is a runtime recipe choosing which Bundles to include and which Patches to apply. Bundle (e.g., dsh-base) groups commonly co-occurring plugins and defaults for reuse. Patch targets entries by ID, replacing whole configs or inserting lines — not a simple deep merge. The resulting config tree is then fed to the Cordis Loader to instantiate the running plugin graph.
Key insight: Agent Loop is not the owner of all capabilities; it is a scheduling point. Model providers, tool sets, prompt assembly, and session persistence are supplied by surrounding plugins. Plugin boundaries make replacement points explicit.
Cordis: Wiring Plugins into a Dependency Graph
Cordis provides runtime primitives: Context, Service, Event, Scope, Loader. Plugins provide services; others inject to declare dependencies. For example, Agent Loop injects agents, sessions, llm, tools, systemPrompt — it won't start until those services are ready.
This replaces fixed-slot architectures with a dependency graph: callers depend on service contracts, not concrete implementations. Model providers, filesystems, or sandboxes can be swapped while the Loop still depends on the same service names.
Cordis also handles two critical runtime concerns:
Events : approval, permissions, telemetry plugins can listen to tool-execution events without being hard-wired into every tool or the Loop.
Scope & cleanup : services, listeners, and resources are bound to a scope; when a plugin exits, the runtime can reclaim them together. This makes "replace an implementation" include its registered resources, not just an import change.
Agent Loop: Same Loop, New Position
The Loop still performs the familiar cycle: assemble request → call model → parse tool calls → wait results → decide next step. The difference: it no longer owns the execution world. It draws capabilities from the plugin graph:
Agent Loop
├── fetch Agent from agents
├── read/append SessionEvent from sessions
├── get current prompt from systemPrompt
├── request streaming output from llm
└── enter unified tool execution pipeline via toolsTwo assembly phases exist: Host startup wires shared runtime (model, Session, tools, Loop). Session creation applies a Preset (Standard, PTC, Minimal, Cordis) that equips the Agent with a specific tool/prompt/service combination. Switching Preset changes the capability set for that session; it does not migrate an already-running Loop's code, inbox, cancellation signals, or in-flight tool calls.
Turn, Step, and the Frozen Request
The Loop decomposes work into Turn (a continuous work round) and Step (one model request plus its triggered tool executions). The sequence:
turn/start
↓
claim inbox + agent/pre-step
↓
step/start
↓
agent/request + prepareCall()
↓
system/message + user/message + request/header
↓
derive and freeze request
↓
llm/stream
↓
assistant/message or assistant/attempt
↓
tool/call → tool pipeline → tool/result
↓
step/end → next step or turn/endInbox queues : DSH splits pending input into next-turn and next-step queues, recorded as agent/inbox/spliced events in Session. Three insertion methods: followup() → next-turn, wakes Loop. steer() → next-step, wakes Loop. inject() → next-step, does not wake Loop.
This distinguishes user interruptions (should affect current task's next step) from background context (queued for next turn). On process restart, queues are reconstructed by folding splice events from the log — not from a memory guess.
Request freezing : agent/pre-step lets plugins inspect/reject/rewrite input; then agent/request and prepareCall() resolve provider, model, adapter defaults, tool schemas, and call capabilities. After prepareCall(), the request messages are frozen. Retries re-use the same rendered assembly, avoiding re-reading potentially changed plugin state.
Cancellation has a defined place: if cancelled during async prep, system messages aren't committed; if the active Loop is aborted, new wake-up input goes to next turn, not into the dead request. Failed model attempts are logged as assistant/attempt (kept for debugging) but never masquerade as successful assistant/message in history.
Tool Execution Pipeline: Not a Black Box
Every tool call passes through a unified pipeline:
tool/call
↓
tools/pre-execute permissions, approval, policy
↓
monotonic guard only further deny, never re-allow
↓
tools/execute timeout, retry, metrics, actual dispatch
↓
tools/post-execute rewrite content, block result, add context
↓
finalizeContent + tools/result
↓
tool/result written to model-visible historyBoundaries are strict: pre-execute handles extensible allow/deny/ask; the monotonic guard ensures final denial cannot be overturned; execution wrapper owns timeouts/retries; post-execute rewrites results; tools/result only observes the frozen authoritative result.
Parallel calls are allowed, but results are committed in the model's original call order. On cancellation mid-batch, already-dispatched calls are awaited; undispatched ones are recorded as aborted before dispatch — preventing dangling tool/call without a result on replay.
PTC (Programming Tool Call) does not bypass this boundary. Its run_code sub-calls return to the host tool registry, undergo the same permission/scheduling/result handling, and are logged as tool/ptc-dispatch events. The model sees only the outer run_code aggregated result; sub-call details serve audit and replay.
Worker threads and node:vm provide isolation and resource management, but the trust boundary remains the host's file, network, and process capabilities.
Session: Append-Only Event Log, Model Sees Projections
Session stores SessionEvent entries: user messages, model outputs, tool calls, tool results, request headers, turn/step boundaries. The model's next messages are derived by projecting and compressing this event stream:
SessionEvent stream
↓
Projection & compression
↓
Model-visible messagesThis means context can be compressed, replaced, or regenerated while raw facts stay in the log. The model sees a working set, not the sole fact copy.
Failure recovery boundary : If tool/call is logged but process crashes before tool/result, recovery can confirm the call was emitted but cannot know whether the external world changed. This is not "tool failed" nor "retry safely" — it's an unknown state requiring human or dedicated recovery logic. Read-only or idempotent ops may be retried; writes, payments, emails need external verification first.
Invariant : Model-visible implies recorded. Every system/user/assistant message and tool result that entered a model request must be derivable from Session log. Failed assistant/attempt streams are kept for debugging but excluded from subsequent model history because the model never saw them as successful.
Dynamic Cordis: Changing Subsequent Requests, Not Auto-Migrating State
Dynamic Cordis allows adding, replacing, or revoking plugin packages at runtime. Lifecycle: define → run → stop → undefine. A dynamic package can register services, listen events, add tools, extend prompts, even provide UI. It mutates the running graph for future requests — broader impact than a single PTC call.
Important distinctions:
Runtime dynamic loading ≠ persistent evolution . Dynamic definitions live in process memory; they don't auto-persist as permanent repo plugins, nor do they auto-run evaluation, release, state migration, or rollback. The replacement mechanism exists, but a full self-evolution loop is not yet built.
Swapping plugins ≠ seamless hot migration of running tasks . Stopping an old package must handle active Loop, in-flight tool calls, cancellation signals, Session cursor — each needs its own protocol and verification.
Capability Seams: Replaceable Interfaces, Not Just Classes
A capability seam comprises three parts: a Service Definition (interface), a Provider (implementation), and a Consumer (actual user). Example: filesystem capability isn't just an fs service; reading tools, pre-edit checks, subprocesses, sandboxes all share the same file/process boundary. Swapping local FS for a remote sandbox migrates a whole capability group if the Provider is cleanly designed; wrapping a local impl with a remote API leaves permissions and lifecycles scattered across tools.
Formal seams mark replacement points, but do not guarantee lossless hot-swap of an executing task. The running Loop still holds cancellation signals, inbox, emitted tool calls, Session cursor. Hot swap requires defining which state transfers, which calls must wait, which external side-effects become unknown.
Is This Complexity Worth It?
For fixed tools, short tasks, single model, single entry — a simple hand-written Loop is easier to maintain. Cordis, event projection, scopes, dynamic packages add real cost: more complex dependency graphs, harder lifecycle reasoning, stricter permission boundaries, state migration/rollback that can't be hand-waved.
But when requirements converge — multiple capability combos in one process; unified approval/observability for tools; long tasks needing recovery/audit; runtime capability replacement; shared Agent behavior across CLI, web, automation — DSH provides explicit runtime slots for each. Its focus isn't a fancy Loop algorithm, but formally separating "current capabilities" (Cordis) from "committed facts" (Session) with the Loop advancing between them.
This gives capability change a place, yet doesn't solve the hardest parts of self-evolution: independent evaluation, permission control, versioning, observability, state migration, rollback-safe releases. DSH is best viewed as an Agent Runtime that reserves positions for complex operational scenarios; reaching a system that can self-experiment, evaluate, release, and roll back stably still requires substantial engineering.
References
DeepSeek Harness source repo ( dsh-v0.1.2-alpha.2) —
https://github.com/deepseek-ai/deepseek-harness/tree/dsh-v0.1.2-alpha.2Architecture doc —
https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.2-alpha.2/docs/architecture.zh.mdAgent lifecycle doc —
https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.2-alpha.2/docs/agent-lifecycle.zh.mdSession subsystem doc —
https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.2-alpha.2/docs/subsystems/session.zh.mdTool execution pipeline doc —
https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.2-alpha.2/docs/tool-execution-pipeline.zh.mdDynamic Cordis dev doc —
https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.2-alpha.2/docs/user/develop/practice/dynamic-cordis.zh.mdSigned-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
