Skip to content

DeepSeek Harness Architecture ​

Read this before changing anything under packages/. It assumes you know Cordis; if you do not, start with the primer or the tutorial.

We recommend using an agent to explore the codebase and understand its architecture.

Cordis ​

Cordis is the framework under dsh: plugins contribute services, typed events, and reversible effects to a shared context. Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so every part is replaceable from configuration.

There is no privileged core to patch: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when their plugin unloads.

Profiles and bundles ​

A running dsh is a plugin tree composed at boot from ordered layers.

A profile is a named composition stored in the Harness home. It lists the bundles it stacks, holds any out-of-tree plugins it installs, and keeps the user's own cordis.patch.yml. web and headless ship as templates.

A bundle is a distribution format for Cordis config rows and the code they mount, so whatever it inserts stays patchable by the layers above it.

Each declares itself in its own package.json under a dsh field: dsh.profile lists a profile's bundles, and dsh.bundle points at a bundle's patch file.

dsh-base is the first layer of every profile: model adapters, tools, persistence, sandbox and approval policy, settings, credentials, telemetry. dsh-web-app adds the browser application; dsh-headless adds a one-shot runner with no server at all.

Layers apply to an empty entry list in this order: each bundle in the profile's listed order, then the profile's cordis.patch.yml, then the home-level one, then any --patch overlay. A patch targets a row by id and replaces its whole config, or inserts new rows.

To see the tree your machine actually boots:

sh
dsh --profile web --dump-config

Any row it prints can be replaced by a patch of your own.

Composition mechanics are in app-boot; config fields are in the generated config catalog.

Core packages ​

Here are some core packages that contribute to the Cordis tree.

PackageOwnsctx key
core/sessionThe append-only SessionEvent log and in-memory storectx.sessions
core/system-promptPrompt-section and tool-schema assemblyctx.systemPrompt
core/toolsThe scoped tool registry and guarded execution pipelinectx.tools
core/agentThe Agent interface, live registry, and agent/* eventsctx.agents
core/agent-loopThe default driver implementing that interfacectx.agentLoop
core/scopeThe per-agent scoped-registration primitivelibrary, no key
llm/llmMessage and stream vocabulary plus the adapter seamctx.llm

Events ​

Events are the extension points, and picking the right domain is the first decision in most changes.

  • Session events are durable facts appended to the log and broadcast through session/event. Use one when the fact must survive a reload.
  • Agent events (agent/*) carry a live Agent: inbox, step, status, request, validation, continuation. Use one to observe or intercept work in flight.
  • Capability events attach policy and adapters to a seam (fs/*, tools/*, telemetry/*) without importing the loop.

The event map lists every event's producers and consumers.

Turn flow ​

A step is one model request plus the tools it calls. A turn is zero or more steps: it opens before its first input is claimed and closes once nothing is owed.

text
turn/start
  claim next-step input plus one queued message
  assemble prompt sections + tool schemas
  -> agent/pre-step                   reject | enter(messages)
     reject, or a first enter rewritten empty -> close the turn with no step
     step/start
     append entered messages as user/message
     derive model history from the log
     agent/request -> llm/stream -> assistant/chunk* -> assistant/message
     tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
     step/end
     tools owe another request, or next-step input arrived -> claim -> next step
  -> agent/turn-stopping
turn/end

turn/*, step/*, user/message, assistant/*, and tool/* are durable session events; the rest are live extension points across three domains. agent/pre-step, agent/request, llm/stream, and the three tools/* events are waterfalls, whose listeners must call next() to delegate; agent/turn-stopping is serial and has no next().

Input reaches the driver through one inbox. Some messages wake it immediately; injected context waits in the inbox until another message does.

agent/pre-step decides what the model sees. Listeners may rewrite the claimed messages or reject them outright; a rejected or empty first claim still closes a durable turn that spent no step, so the log records the attempt. Each step reads the prompt sections and tool schemas that plugins registered.

Details: the sequence diagram, the tool pipeline, and cancellation and error recovery.

Session log ​

The session log is the source of the context the model sees. deriveMessages() projects model history from it, and raw assistant/chunk events preserve replay and UI fidelity. Fork, resume, transcripts, telemetry, and persistence all derive from this stream.

Model-visible means logged. Anything that reaches a model request must be reconstructable from the log, and a runtime invariant asserts it. This is why a new model-visible input requires a new session event: extend SessionEventMap and render from the log.

Capability seams ​

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. A package may combine roles, but one role alone is not a seam; adding a capability means designing all three (capability graph).

Seams are why one provider swap changes the whole product. Filesystem and subprocess providers share one execution world, so pointing them at a remote 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.

Experimental Agent Teams is a private opt-in coordination seam on ctx.agentTeams, with a durable roster, task board, and mailbox layered over continuable subagents.

Where new behavior goes ​

New behavior attaches to a documented extension point. Changing the loop itself updates this map.

GoalMechanism
Add a model providerregister its adapter on ctx.llm
Add a model-facing capabilityregister on ctx.tools; its schema joins prompt assembly
Give one session a different capability setcompose an agent preset; a service row there needs an isolate realm
Add shell executionregister a ctx.shell backend; the local one spawns through ctx.subprocess
Add persistent terminal executionregister a ctx.terminals backend plus dsh-tool-terminal
Add a human commandregister on ctx.commands; it dispatches without a model turn
Add background workregister on ctx.jobs; job_* tools collect or stop it
Add filesystem access or policyregister a ctx.fs provider or listen to fs/* events
Confine spawned processesuse a ctx.sandbox backend; consumers wrap argv before spawning
Intercept a request, tool, or turnuse its agent/* or tools/* event; agent/turn-stopping stops a turn
Add model-facing contextcall agent.inject(); it lands in the next admitted request
Add UI or editor integrationdrive ctx.agents and render from session/event
Add a Web Client Chat noderegister a ConversationNodeDefinition + keyed renderer
Add durable session stateextend SessionEventMap; render and replay from the log
Generate session titlesregister the sole ctx.sessionTitle provider
Manage a same-session objectiveuse ctx.goals; continue through agent/*
Fork a live sessionctx.sessions.fork(source, boundary?, childSessionId?)
Scope a registration to one agentuse that agent's agent.ctx

The extension cookbook maps features to capabilities and indexes the step-by-step guides for packages, tools, LLM adapters, Chat nodes, and settings cards.