Files
deepseek-harness/docs/architecture.md
2026-07-16 10:14:33 +08:00

15 KiB

DeepSeek Harness Architecture

The DeepSeek Harness SDK builds agent harnesses on Cordis. The principle is simple: everything is a plugin. The shipped loop is one plugin, not a privileged kernel.

Overview

A harness is one Cordis context. Packages add services (ctx.llm, ctx.tools, ctx.sessions), typed interception and notification events (agent/request, tools/pre-execute, session/event), and disposable registrations for prompts, tools, providers, adapters, and listeners.

packages/core/ groups the default agent flow; surrounding capabilities are equally first-class Cordis plugins.

Default Services

ctx key Package Role
dsh-scope scoped-context registration primitive (library)
ctx.sessions dsh-session in-memory event-sourced sessions
ctx.systemPrompt dsh-system-prompt ordered prompt sections, tool schemas, and prompt variables
ctx.tools dsh-tools tool registry and execution pipeline
ctx.agents dsh-agent live agent registry, public Agent handle, agent/* events
ctx.agentLoop dsh-agent-loop shipped ReactLoopAgent driver

Capability Services

ctx key Package family Role
ctx.llm llm/ adapter registry and streaming model calls
ctx.tokenMeter llm/token-meter replay-aware request/surface pressure per model
ctx.bash bash/ foreground/background command execution
ctx.sandbox sandbox/ same-world process confinement (argv wrapping, per-call policy)
ctx.codeRuntime code-runtime/ model-written program execution
ctx.fs fs/ filesystem provider primitives and policy events
ctx.skills skill/ skill provider registry and progressive disclosure
ctx.web web/ search/fetch provider registries
ctx.compact compact/ session-log compaction
ctx.subagents subagent/ named delegation providers
ctx.tasks tasks/ background task registry + generic task_* control tools
ctx.workflows workflow/ script-driven multi-agent orchestration
ctx.sessionPersistence session-persistence/ durable storage for session logs
ctx.sessionQuery session-query/ live-preferred logical-corpus and exact-event reads

Event

Events form the service extension API; see the exhaustive events catalog and producer/consumer map.

Event Domains

  • Session events are durable replay facts: boundaries, messages, tools, steering, compaction, and tool-owned state flow through session/event.
  • Agent events carry the live Agent for status, diagnostics, prompt admission, request shaping, result validation, and continuation.
  • Capability events belong to their action owner. tools/*, llm/*, system-prompt/*, fs/*, and subagent/* attach policy and adapters without importing the loop.

Interception Semantics

Waterfall events behave like around-middleware: a listener delegates by calling next(); returning without it vetoes or takes over. Full rule: Cordis waterfall semantics.

Default Loop Lifecycle

The shipped loop drains work, assembles requests, streams answers, executes tools, applies continuation policy, and checkpoints state through plugin-visible calls and events.

A session is an agent's append-only log; a turn drains one queued batch; a step is one model request and its tool executions. Quoted names below are durable events, and unquoted event names are extension points (sequence companion).

Turn Flow

prepare private session + agent.ctx -> await unpublished setup
  -> enter session + agent -> session/created -> agent/created
  -> enable driving -> agent/session-start(source) -> start driver
forever:
  wait for queued messages
  emit agent/status(running)
  TURN:
    'turn/start'
    each queued message -> agent/prompt-submit
      allowed prompt -> 'user/message' plus injected context
    every prompt blocked -> 'turn/end'(rejected)
    STEP loop:
      drain steering
      assemble system prompt and tool schemas
      agent/session-prefix (first step)
      agent/pre-step
      snapshot the derived messages (the reconstruction boundary)
      'step/start'
      agent/request (config only) -> log request/header -> llm/stream (frozen)
      on final adapter failure or terminal in-band error/aborted finish:
        'step/end'
        agent/request-error(original error, consecutive retry attempt, signal)
        retry in the next numbered step or preserve the original error
      otherwise:
        'assistant/chunk'
        agent/step-result
        'assistant/message' (transformed content or empty success anchor after step-result rejection)
        each tool call:
          'tool/call'
          tools/pre-execute -> monotonic guards -> tools/execute -> tools/post-execute -> tools/result
          'tool/result'
        append post-tool context and steering
        agent/post-step
        'step/end'
        agent/turn-continuation
        agent/turn-stop (terminal policy)
        stop unless tools or continuation policy ask for another step
    'turn/end'
    checkpoint persistence and notify idle/running status

Each step renders one prompt assembly. Plugins contribute ordered sections, tool schemas, and {{name}} variables; missing values fail the turn. dsh-system-prompt owns harness identity and the default persona, which an agent-scoped persona may shadow. The loop supplies model and cwd (prompt ownership).

Post-tool context follows all results, preserving call/result adjacency. Steering drains before agent/post-step, which observes durable output, results, context, and steering while the step signal remains open. Leftover steering becomes next-turn input. agent/turn-stop is terminal through close and flush: later steering is discarded, while ordinary queued prompts survive.

When loaded, dsh-compact-basic consumes that post-step checkpoint for ctx.tokenMeter pressure under the actual routed header. It also consumes canonical context overflow at agent/request-error, but authorizes retry only after a tool-balanced compaction advances surface.replaceGeneration. The same turn signal owns both summarization paths.

Failure Boundaries

The turn is the containment boundary. LlmService preserves and privately tags errors from final adapter selection, dispatch, and iteration. Those errors and terminal in-band error/aborted finishes close the failed step before agent/request-error; retry reconstructs the next numbered step from the log, while decline or failed recovery preserves the provider error. Attempts count consecutive failures and reset after success.

Prompt, middleware, result, tool, post-step, and continuation failures remain ordinary agent/error failures. Cancellation and disposal beat recovery. Durable undispatched tool calls receive synthetic ABORTED results, preventing dangling replay. cancel() clears queues and aborts active work; disposal awaits quiescence before unregistering.

Every session event is turn-enclosed. Reload preserves a crashed tail and closes it with synthetic interrupted; post-close failures report only through agent/error. A turn has one TurnEndReason (completed, aborted, error, disposed, max-tokens, rejected, or interrupted), detailed in session.md.

Agent Handles

ctx.agents owns live agents and returns AgentHandle { agent, dispose() }. Plugins drive Agent through send(), steer(), inject(), cancel(), and whenIdle(). The caller fiber and factory provider structurally co-own programmatic lifecycles; the consumer handle is the sole non-structural teardown capability, and all owners share one awaited disposer.

Agent Scope

Each agent owns agent.ctx; its registrations shadow globals, receive only that agent's dispatches, and unwind on disposal, including awaited background-task cleanup. CreateAgentOptions.setup(agentCtx) composes it before publication. Typed resolvers derive carrier checks from merged events and scopeTarget (semantic gates, agent scope, subagent controls).

State

Session Log

The session log is the source of truth. deriveMessages() projects session events into the Message[] sent to the model; raw assistant/chunk events stay in the log for replay and UI fidelity. Replay, fork, resume, transcript rendering, telemetry, and persistence all derive from the same event stream.

Model-visible ⟺ logged: the log reconstructs every request — messages at step/start fronted by the header's session prefix, headers by folding request/header — and dev invariants assert this (reconstructability RFC).

Durability is a plugin concern. Persistence backends buffer synchronous session/event notifications and the loop awaits a turn-end checkpoint before moving on. The SessionPersistence seam stores SessionEvent directly, with metadata in SessionHeader; JSONL and SQLite share one contract suite.

Model Content

Messages are arrays of typed content blocks (text, reasoning, tool-call, tool-result). The union derives from the merge-extensible ContentBlockMap; the same pattern types MessageSource, FinishReason, TurnTrigger, and TurnEndReason. New block types require coordinated adapter, UI, token-meter, and persistence changes; replay measurement types live in token-meter.md.

Streaming is a raw chunk protocol (block-start through finish) with BlockAssembler as the shared chunk-to-block assembler. The loop logs raw chunks while assembling them for dispatch. LlmAdapter is the provider seam: subclass, implement stream(), and register with ctx.llm.registerAdapter(models, adapter). StreamChunk conventions live in llm-streaming.md.

Extension And Composition

Capability Pattern

A swappable capability usually splits into interface / implementation / consumer: the interface owns its ctx key and events, an implementation registers a backend, and a consumer exposes model behavior through tools or prompts. Bash is the reference; the capability graph shows every family.

Some seams bend the template deliberately. LLM keeps interface and consumer vocabulary together because adapters are the implementations. Filesystem adds policy gates around provider primitives. Web is one service with search and fetch provider registries, so provider swaps do not rename model tools. Skills and subagents use named provider registries; local skills scan project/user roots, and other providers can add embedded or remote catalogs without registry/tool changes. Subagents spawn fresh, fork from the parent's completed-turn prefix, or use ACP children (subagent.md).

Bundles And Apps

dsh-agent-spine-demo is the default composition bundle: one plugin loading the shared spine (README). App packages compose it with a front door and boot bin: dsh-stdio-demo for terminal REPL, and dsh-acp-demo for ACP over JSON-RPC stdio with no stdout logger (ui/). dsh-jsonrpc-agent instead boots an external cordis.yml; the Python SDK injects the package default only when no explicit config channel is set and drives dsh-jsonrpc over line-delimited stdio JSON-RPC (Python SDK). A deployment is a thin cordis.yml leaf: swappable backends, one app entry, and optional product tools (examples/, runnable wirings, graph atlas).

Where New Behavior Goes

New behavior should attach to a documented extension point; changing the shipped loop requires updating this map.

Goal Mechanism
Add a model provider register an adapter on ctx.llm
Add a model-facing capability register a tool on ctx.tools; schemas flow into prompt assembly
Add command execution implement and register a ctx.bash backend
Add a long-running/background capability register the work on ctx.tasks; the generic task_* tools collect/stop it
Add filesystem access or policy implement a ctx.fs provider or listen on fs/* policy events
Confine spawned processes a ctx.sandbox backend; consumers wrap their argv before spawning
Intercept prompts, requests, model completion/failure, tool use, or continuation listen on the relevant agent/* or tools/* event; use serial agent/turn-stop for a monotonic terminal stop
Add a session-stable request prefix outside history compose it on agent/session-prefix, once per loop instance; logged on the request header
Add UI or editor integration drive ctx.agents and render from session/event
Add durable session state add a SessionEventMap member and render/replay from the log
Fork a live session use ctx.sessions.fork(source, boundary?, childSessionId?)
Scope a tool, prompt section, or listener to ONE agent register it through that agent's agent.ctx (see Agent Scope)

The extension cookbook carries plugin skeletons and the feature-to-seam map; step-by-step guides cover packages, tools, LLM adapters, and vendored packages.

Quick Reference