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.

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, and Chat nodes.