DEV Community

Pramoda Sahu
Pramoda Sahu

Posted on

DeepSeek Harness: What Happens When the Agent Runtime Becomes the Product

How an "everything is a plugin" architecture reframes what an AI agent actually is — and what it teaches builders of any agent stack.

1. The Agent Is More Than the Model

There's a persistent shorthand in agent engineering: take a capable LLM, give it a system prompt and a handful of tools, and call the result an "agent." That shorthand works for demos. It falls apart the moment an agent needs to run for more than a few minutes, survive a restart, call a sub-agent, recover from a failed tool call, or let a human inspect what it actually did three hours ago.

Once you cross that line, an agent needs a lot of infrastructure that has nothing to do with the model itself: a place to execute tools safely, a way to keep state across turns, a policy for what context the model sees on each call, a mechanism for delegating work to other agents, a sandbox to contain what the agent can touch, a way to recover from partial failures, and a record of what happened that a person — or an evaluation harness — can replay later.

Collectively, this surrounding machinery is often called the agent harness: the runtime that sits between the model and the world, and that actually determines how the agent behaves in practice. Two agents built on the same underlying model can behave completely differently depending on the harness wrapped around it — how it manages context, what tools it exposes, how it recovers from errors, and how it schedules work.

DeepSeek Harness (dsh), an open-source project released by DeepSeek AI in developer preview under the MIT license, is a useful concrete example of where this thinking leads when taken seriously. It didn't invent the idea of an agent harness — Anthropic's Claude Code, OpenAI's Codex CLI, and various open-source agent frameworks have been converging on similar territory. What makes DeepSeek Harness worth a close read is how far it pushes a single architectural commitment — "everything is a plugin" — and what that commitment forces the rest of the design to look like.

2. What Is DeepSeek Harness?

In plain terms: DeepSeek Harness is a runtime for building and running coding/automation agents. You install it, point it at a model provider, and get a working agent — with file editing, shell access, web search, sub-agents, and a web UI — out of the box. It's explicitly model-agnostic: alongside DeepSeek's own models, the provider catalog covers Anthropic, OpenAI, AWS Bedrock, Azure, and Google's Gemini Enterprise Agent Platform, plus custom OpenAI-compatible endpoints. Nothing in the design ties the harness to DeepSeek's own models — a telling signal about what the project is actually trying to be.

Technically, the separation is stricter than "the code happens to support multiple providers." DeepSeek Harness is built on a general-purpose plugin framework called Cordis, whose composition model is described in a paper titled "A Programming Paradigm for Spatiotemporal Composability" by researchers from Peking University and DeepSeek. Cordis provides plugins with a shared context (ctx), through which they contribute services, typed events, and — notably — reversible effects. According to the project's own architecture documentation, "every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself." There is, by design, no privileged core to patch: extending the harness means mounting a new plugin beside the existing ones, and every registration is an effect that cleanly unwinds when its plugin unloads.

This separation matters for a simple reason: it turns the model into an interchangeable component rather than the organizing principle of the system. The runtime doesn't just call an LLM — it owns the session, the tool pipeline, and the execution history independently of which model happens to be answering right now. That's the practical meaning of "model-vs-harness separation," and it's the assumption that makes the rest of the architecture legible.

3. The Architecture

At boot, a running dsh instance is a plugin tree assembled from ordered layers. A profile (the docs ship web and headless templates) lists which bundles it stacks; a bundle is a distribution unit of Cordis configuration plus the code it mounts. dsh-base is the foundational bundle every profile includes — model adapters, tools, persistence, sandbox and approval policy, credentials, telemetry — and dsh-web-app or dsh-headless add a browser UI or a one-shot runner on top. Layering is deterministic and inspectable: you can run dsh --profile web --dump-config and see the exact plugin tree your machine will boot, then override any row with your own patch file.

A handful of core packages anchor this tree (each owning a distinct piece of ctx, the shared plugin context):

Package Owns
core/session The append-only session-event log and in-memory store
core/system-prompt Prompt-section and tool-schema assembly
core/tools The scoped tool registry and guarded execution pipeline
core/agent The Agent interface, live registry, and lifecycle events
core/agent-loop The default driver implementing that interface
llm/llm Message/stream vocabulary and the model-adapter seam

Zoom out from the package table and the shape is straightforward: a Cordis kernel sits at the center, and the agent loop, the model adapter (ctx.llm), the tool registry (ctx.tools), skills, the subagent runtime (ctx.subagents), the sandbox and filesystem layer, and the session log (ctx.sessions) all hang off it as sibling plugins. The tool registry talks to the sandbox to actually execute anything; the session log receives events from everywhere else and is what fork, resume, and the trajectory UI all read from. Crucially, the agent loop itself is just one more plugin in that list, not a privileged core the others report to — which is precisely the point. The runtime has no single "agent class" you subclass; it has a composition of independently swappable services.

4. The Plugin Architecture: Why Bother?

It would be easy to read "everything is a plugin" as an engineering slogan. The more interesting question is what problem it actually solves.

A monolithic agent framework typically hard-wires its tool list, its context-management policy, and its loop logic into one execution path. That's fine until you need to change one dimension without touching the others — swap the sandbox for a remote one, add a new model provider, or give a subset of sessions a different toolset. In a monolith, those changes ripple through shared code paths and are hard to test in isolation.

DeepSeek Harness's answer is what its docs call a capability seam: a swappable capability defined by three roles — a Service Definition (the interface), a Service Provider (an implementation), and a Consumer (typically a model-facing tool). Filesystem and subprocess access are one seam; because Bash, PTY access, and code-navigation tools all consume that same seam, pointing it at a remote sandbox moves all three together, with no need to fork any of the individual tools. Subagents are a different seam — one where multiple provider implementations coexist by name in the same context (a locally spawned child, a forked child sharing conversation history, a delegated Claude Code or Codex session), because different delegation strategies are genuinely useful side by side, not mutually exclusive.

This is the architectural difference from a typical monolithic framework: capabilities aren't conditionally-compiled features of one big class, they're independently loaded plugins that contribute to a shared context and can be added, removed, or replaced without touching the runtime's source. The project's own extension guidance is concrete about this — adding a model provider means registering an adapter on ctx.llm; adding a model-facing capability means registering on ctx.tools; confining spawned processes means providing a ctx.sandbox backend that tool consumers wrap around before spawning. None of these require modifying the agent loop.

5. The Agent Loop

DeepSeek Harness's documentation describes execution in terms of turns and steps, not a single flat request-response cycle. A step is one model request plus whatever tools it calls; a turn is zero or more steps, opening when input is first claimed and closing once nothing is owed to the model.

The documented turn flow runs like this. A turn opens by claiming the next input plus any queued message, then assembles the prompt sections and tool schemas that plugins have registered. That claim passes through an agent/pre-step event, which can reject or rewrite it before the model ever sees it — and even a rejected or empty first claim still closes a durable turn, so the attempt is recorded rather than silently vanishing. Once a step starts, the request goes out (agent/requestllm/stream), and if the model calls a tool, that call runs through a three-stage pipeline — tools/pre-execute, tools/execute, tools/post-execute — before the result comes back and the step ends. If more work is owed, or new input has arrived, the loop claims again and opens another step; otherwise it fires agent/turn-stopping and closes the turn.

A few details are worth calling out because they explain why the loop is shaped this way rather than as a simpler while-loop. agent/pre-step is a waterfall event: listeners can rewrite or outright reject the messages a step is about to see, which is the extension point for things like injected context, guardrails, or compaction — all without touching the loop's own code. Even a rejected first claim still closes a durable turn, so the attempt is recorded rather than silently disappearing. And the three tool-pipeline events (pre-execute, execute, post-execute) are also waterfalls, meaning any plugin can intercept a tool call in flight — for approval gating, cost tracking, or rewriting arguments — by hooking a well-defined seam instead of forking the tool implementation.

This is the general argument for lifecycle hooks in a production agent: reliability work — retries, guardrails, cost caps, human approval — is almost never expressible as "add another if statement to the main loop." It needs defined interception points that don't require understanding or modifying the whole control flow. A framework that doesn't expose those points forces every operational concern into ad hoc wrapper code around the model call.

6. Sessions, Events, and Replay

Perhaps the least flashy and most consequential design decision in DeepSeek Harness is that the session is an append-only event log, not a stored conversation transcript. deriveMessages() reconstructs whatever the model actually sees by projecting model history from that log; separate raw streaming events are retained purely for replay and UI fidelity. The docs state a hard invariant: anything that reaches a model request must be reconstructable from the log — a new kind of model-visible input requires a new session-event type, not a side-channel.

Concretely, the log accumulates a strict sequence of typed events per turn — a user/message, one or more assistant/message events, any tool/call and tool/result pairs, and a closing turn/end — and everything downstream reads from that same sequence rather than from a separate cache: deriveMessages() projects the model-visible history from it, forking branches off any completed-turn boundary in it, cold resume restarts a session by replaying it, and the trajectory view inspects it event-by-event, filtered by source.

Why build it this way instead of just storing the final message history? A plain transcript answers "what did the conversation look like," but it can't answer "what did the model actually see at step 12," "what would have happened if we'd used a different model from this point," or "replay exactly this trajectory for an eval." An append-only, typed event log can answer all three, because every fact — including tool calls, subagent scheduling, and context injections — is a durable, individually addressable record rather than a flattened string.

This is what makes forking tractable: the subagent system, for example, can seed a new child session with a "balanced completed-turn prefix" of a parent's log — the events up through its last completed turn, deliberately excluding any in-flight, unbalanced turn — and the runtime's own invariants will accept that seed as valid replay input. The same mechanism underlies session resume after a process restart, and the "Trajectory view" the project's UI exposes for inspecting a run event-by-event, filtered by source. None of this is bolted on after the fact; it falls directly out of treating the session as a log rather than a cache.

7. Tools, Skills, and Subagents

DeepSeek Harness distinguishes these abstractions cleanly, and it's worth being precise about the difference, since the terms get blurred in casual agent talk elsewhere:

  • Tools are the model-facing, single-call capabilities registered on ctx.tools — file edits, shell execution, search. They're the atomic unit the model invokes and the pipeline guards (pre-execute, execute, post-execute).
  • Skills are reusable, composable instructions or procedures the agent can draw on — closer to a library of "how to do X well" than a callable function.
  • Subagents are a distinct capability seam for delegating entire pieces of work to a child agent, with its own session, its own turn loop, and (per the docs) either a one-shot lifecycle or a continuable one that can receive follow-up messages across multiple activations.

The subagent design is unusually deep for a developer-preview project. Multiple named providers can coexist behind the same interface — a locally spawned child, a forked child that inherits the parent's conversation history, or a delegated session running inside Claude Code or Codex via their own SDKs — and a caller can request start-time capabilities like an output schema, a depth limit, or a restricted tool set, which the runtime validates against the chosen provider before starting the child rather than silently ignoring what isn't supported. Continuable children maintain a durable session that can be resumed cold, interrupted, or reported back to their parent through a distinct "report" channel, deliberately separated from ordinary conversation so a transcript never confuses "what the runtime observed" with "what the child actually said."

The practical lesson: tools, skills, and subagents solve different problems — atomic actions, reusable know-how, and delegated autonomy — and conflating them (e.g., implementing delegation as "just another tool call with no state") tends to produce systems that can't resume, can't be interrupted cleanly, and can't distinguish a child's own words from the runtime's bookkeeping.

8. Code Mode

Standard tool-calling means every operation — read a file, then grep, then edit, then run tests — is a separate round trip through the model: it sees a result, decides the next call, and pays for another full context pass each time. Code mode is one of DeepSeek Harness's four shipped presets (alongside Standard, Minimal, and Creator), and it changes this by exposing the same tool set as a generated TypeScript SDK. Instead of calling tools one at a time, the model writes a short program against that SDK and the harness executes it, so a sequence that would otherwise take five round trips can run as a single call.

The claimed advantages are the obvious ones for anyone who has watched an agent burn tokens re-deciding the same next step five times: fewer model round trips, the ability to use real control flow (loops, conditionals, batching) instead of forcing every branch through the model, and more deterministic composition of operations that don't individually need a fresh judgment call.

The trade-offs are just as real and worth stating plainly rather than glossing over. Letting a model write and execute code — even against a curated SDK — expands the attack surface and the sandboxing burden relative to a fixed menu of individually-validated tool calls; it shifts some debugging burden from "which tool call went wrong" to "which line of generated code went wrong"; and it depends on the model reliably producing correct, well-scoped programs, which is a different and not strictly easier reliability problem than reliably picking the next tool call. It's telling that DeepSeek's own published benchmarking for its models reportedly used Minimal mode — a stripped two-tool (bash plus str_replace_editor) preset — rather than Code mode, which suggests the project itself treats Code mode as a genuinely different, not strictly superior, execution model.

9. DeepSeek Harness vs. LangGraph

It's tempting to put these side by side as competitors, but they operate at different layers, and the comparison is more useful read that way.

Dimension LangGraph DeepSeek Harness
Core abstraction A directed (often cyclic) state graph of nodes and edges A Cordis plugin tree; the agent loop itself is one plugin
Agent loop You define nodes/edges explicitly; the graph is the control flow A built-in turn/step loop with waterfall hook points (agent/pre-step, tools/*)
State A typed shared state object flowing through graph nodes An append-only session event log; state is derived, not stored directly
Tools Callables wired into graph nodes A registered, guarded tool pipeline with pre/execute/post events
Persistence Checkpointers (e.g., Postgres) snapshot graph state after each step The session log itself is the persistence layer
Replay/recovery Resume from a checkpoint by thread ID; re-execute from a historic checkpoint to branch Fork from any completed-turn boundary in the log; cold-resume continuable sessions
Subagents Multi-agent patterns (supervisor, swarm) built as graph structures A first-class capability seam with named providers and continuable children
Extensibility Add nodes/edges to the graph; swap components via Python/TS code Add or swap plugins via Cordis configuration without touching source
Runtime A Python/TS library you embed and orchestrate yourself A standalone runtime with its own CLI, web UI, and process model
Best use case Modeling explicit, inspectable control flow for workflows and multi-step pipelines Running a full, extensible coding/automation agent with tools, sandboxing, and a UI out of the box

The honest framing is that LangGraph is primarily an orchestration and state-machine framework: you bring the model calls and tools, and it gives you a graph structure, checkpointing, and human-in-the-loop interrupts for controlling execution flow explicitly. DeepSeek Harness aims at something broader — a complete agent runtime, with the tools, sandbox, session storage, subagent transport, and UI already assembled, extensible through plugins rather than through graph authorship. You could, in principle, build a LangGraph node that calls out to a DeepSeek Harness session, or vice versa; they're not mutually exclusive so much as answers to different questions ("how do I control this workflow's flow" vs. "what infrastructure does my agent run inside of").

10. Where It Sits in the Broader Harness Ecosystem

DeepSeek Harness arrives alongside — and explicitly interoperates with — a growing set of coding-agent harnesses: it reads AGENTS.md and CLAUDE.md files, ships an MCP client and Agent Client Protocol support, and its subagent system can delegate to Claude Code or Codex sessions directly through their own SDKs rather than treating them as black-box competitors. That interoperability is itself a signal: the emerging convention isn't "one harness to rule them all," but a shared vocabulary (MCP for tools, AGENTS.md for repo-level instructions, ACP for cross-agent communication) that different harnesses are converging on independently. Compared to something like Claude Code, the philosophical difference DeepSeek Harness leans into hardest is depth of plugin surface — its own docs note there is "no privileged core to patch," which is a stronger claim about extensibility than most comparable tools make about themselves.

11. What DeepSeek Gets Right

A few architectural choices stand out as genuinely well-reasoned, not just well-marketed:

  • Treating the harness as infrastructure, not prompt glue. The plugin boundary is enforced at the framework level (Cordis), not left as a convention developers are trusted to follow.
  • An event log as the single source of truth. Deriving model-visible context from the log, rather than storing it separately, closes a whole category of "the UI shows something different from what the model saw" bugs by construction.
  • Capability-checked delegation. The subagent system validates requested capabilities (schema support, depth limits, tool filters) against a provider before starting a child, rejecting loudly rather than silently ignoring an unsupported request — a small detail that prevents a common class of "it looked like it worked" failures.
  • Configuration-level extensibility. Swapping a sandbox backend, adding a model provider, or scoping a session's toolset are all documented as configuration changes, not source patches.

12. What Is Still Difficult

None of this makes autonomous agents reliable by default, and the project doesn't claim otherwise — it's explicitly a developer preview with, in its own words, compatibility-breaking changes still ahead.

A better harness doesn't solve model reliability: a model that hallucinates a file path or misreads a diff will do so regardless of how well-designed the surrounding plugin system is. Context growth over long sessions is still a real cost and latency problem that a good event log doesn't eliminate, only makes visible. Tool failures and partial states still need application-level handling — the pipeline gives you hooks, not automatic correctness. Sandboxing a model that can write and execute arbitrary code (as Code mode does) is a genuinely harder security problem than sandboxing a fixed menu of tools, and it's not clear the field has converged on a satisfying answer yet. Long-running, multi-hour agent tasks still accumulate cost and drift in ways that better session bookkeeping only helps you observe, not prevent. And evaluating or debugging a complex multi-turn, multi-subagent trajectory remains labor-intensive even with a trajectory viewer — you still have to read it.

13. What Developers Can Learn From This — Even Without Using It

Several of these principles generalize well beyond this specific project:

  1. Separate the model from the runtime. Treat the model as a swappable adapter, not the organizing abstraction of your system.
  2. Treat tool execution as a first-class subsystem, with its own guarded pipeline — not inline logic in your agent loop.
  3. Make agent state durable and reconstructable, not just cached in memory for the current process.
  4. Record execution trajectories, not just final outputs — you cannot debug or evaluate what you didn't log.
  5. Design for replay and recovery from the start; retrofitting it into a stateless design is much harder than building it in.
  6. Use plugins or defined interfaces instead of hard-coded integrations, so capabilities can be swapped without touching core logic.
  7. Treat skills as composable capabilities distinct from tools — reusable know-how is a different abstraction from a callable function.
  8. Make observability part of the runtime, not an afterthought layered on top via logging statements.
  9. Design failure handling explicitly, with defined lifecycle hooks — don't rely on wrapping the whole loop in a try/catch.
  10. Keep the harness model-agnostic where practical — it future-proofs the investment in everything else you build.

14. A Minimal Agent Harness Architecture

If you strip this down to the smallest version worth building yourself, it looks roughly like this. A swappable model adapter sits behind an agent loop that owns the turn/step lifecycle and exposes hook points at each stage. That loop draws on three sibling capabilities: context assembled fresh from the log on each step, a guarded tool-execution pipeline, and a library of composable skills. The tool pipeline in turn depends on an isolation seam — a sandbox — for anything that touches the filesystem or a shell, and a separate delegation seam for spawning or resuming subagents. Every one of those components writes to a single append-only event log, which is the system's actual source of truth, and that log is what makes replay, forking, resuming a crashed session, and general observability possible after the fact — rather than something you have to reconstruct from scattered application logs.

Not every project needs all of this on day one. But knowing which piece you're skipping — and what you're giving up by skipping it — is a much better position than discovering the gap in production.

Conclusion

DeepSeek Harness is not a new model, and it doesn't need to be evaluated as one. It's a bet about where the differentiation in AI agents is heading: less about which model answers a given call, and more about the runtime that decides what that model sees, what it's allowed to do, how its work is recorded, and how failures are recovered from. That bet isn't unique to DeepSeek — it's visible across the current generation of coding-agent harnesses converging on shared protocols like MCP and AGENTS.md — but the "everything is a plugin" commitment, enforced by a real framework rather than a convention, makes DeepSeek Harness a clear and fairly rigorous illustration of it.

The next generation of AI agents may be differentiated less by which model they call and more by the runtime that surrounds the model. If that's right, the interesting engineering work isn't in the model API call at all — it's in everything this article just walked through.

Sources & Further Reading

DeepSeek Harness is in active developer preview at the time of writing; APIs, plugin interfaces, and preset behavior are explicitly expected to change before a stable release.

Top comments (0)