Files
deepseek-harness/docs/architecture.md
T

17 KiB

DeepSeek Harness Architecture

English | 中文

DeepSeek Harness SDK uses Cordis: everything is a plugin, including the loop.

Overview

Harnesses are Cordis contexts; packages contribute services, typed events, and disposable registrations.

packages/core/ groups the default agent flow; capabilities remain plugins.

Default Services

ctx key Package Role
dsh-scope scoped-context registrations and shared layer storage (library)
ctx.sessions dsh-session in-memory event-sourced sessions
ctx.systemPrompt dsh-system-prompt ordered stable system sections, cache-safe dynamic contexts, tool schemas, and variables
ctx.tools dsh-tools tool registry and execution pipeline
ctx.agents dsh-agent live agents, delegated creation, agent/* events, process-local initiator scope
ctx.agentLoop dsh-agent-loop concrete Agent driver

Capability Services

ctx key Package family Role
ctx.llm llm/ adapter registry, streaming model calls
ctx.tokenMeter llm/token-meter replay-aware request and surface pressure
ctx.bash bash/ foreground/background command execution
ctx.subprocess subprocess/ managed child-process trees for bash, LSP, and ACP subagent backends
ctx.pty pty/ owner-scoped persistent terminal sessions
ctx.sandbox sandbox/ same-world process confinement through argv wrapping and per-call policy
ctx.sandboxPolicy sandbox/ shared sandbox policy home
ctx.codeRuntime code-runtime/ model-written program execution
ctx.fs fs/ filesystem provider primitives and policy events
ctx.lsp lsp/ semantic navigation registry
ctx.skills skill/ skill provider registry, progressive disclosure
ctx.web web/ search/fetch provider registries
ctx.compact, ctx.toolResultPrune compact//compact-tool-result-prune summary compaction, optional model-free result pruning
ctx.subagents subagent/ named delegation providers
ctx.planMode plan/ logged plan collaboration state
ctx.tasks tasks/ background task registry, generic task_* controls
ctx.workflows workflow/ script-driven multi-agent orchestration
ctx.goals goal/ persisted same-session goals
ctx.sessionPersistence session-persistence/ durable session-log storage
ctx.sessionQuery session-query/ live-preferred exact/filter/trace queries over SQLite FTS, workspace-authorized model tools
ctx.sessionTitle session-title/ log-backed fallbacks, one optional asynchronous provider
ctx.settings settings/ per-plugin user-settings namespaces layered over composition entries
ctx.credentials credentials/ named secret references resolved per operation, never inlined in configuration
ctx.directoryPicker host/directory-picker GUI-host directory picking (native/browse interactions)
ctx.typert typert/registry runtime registry for generated package reflection and live Zod schemas
ctx.invariants support/invariants package-name-selected registry of package-owned runtime checks

Event

Events are the service extension API (catalog, producer/consumer map).

Event Domains

  • Session events are durable log facts emitted through session/event.
  • Agent events carry live Agent for status, prompt admission, request shaping, validation, and continuation.
  • Capability events let owning seams attach policy and adapters without a loop import.

Interception Semantics

Waterfalls are around-middleware: listeners delegate with next(); returning without it vetoes or takes over (semantics).

Default Loop Lifecycle

A session is append-only. An ordinary turn claims one queued send() item; injection claims none. A successor awaits its predecessor's checkpoint but may share its running interval (decision). A turn ends when model or plugins stop it; a step is one model request plus tools. Quotes in the sequence below mark durable events.

Creation without an id mints <config-id>-session-<uuid>; sessionId resumes or creates, while resumeSessionId requires history. Resume restores lineage and delegation depth before publication. Setup failures emit agent-loop/config-start-failed; teardown is silent.

Turn Flow

choose declarative identity and fresh/resume path
  -> 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 occurrence
  claim (edit/remove end) -> emit agent/status(running) if starting an interval
  open the next-step acceptance window
  -> agent/prompt-submit
    blocked or failed prompt -> close the window without opening a turn
      append a context-only caller batch immediately
      keep steering and context staged beside it pending for a later admitted turn
    allowed prompt:
      'turn/start'
      append prompt + additional contexts as separate 'user/message' events
    STEP loop:
      agent/step
      drain injected context and steering (steering bypasses prompt-submit)
      assemble system prompt and tools
      materialize changed runtime context as sourced 'user/message'
      snapshot the derived messages (the reconstruction boundary)
      'step/start'
      agent/request (config only) -> prepare adapter defaults/provenance under turn signal -> log request/header -> llm/stream (frozen, registration-bound)
      'assistant/chunk'
      'assistant/message'
      schedule tool calls by ctx.tools.executionMode:
        exclusive -> barrier
        parallel -> rolling pool, <= maxParallelToolCalls; reclassify-at-start; scheduler failure -> stop starts, drain dispatches
        start -> 'tool/call' -> ordered tools/pre-execute -> concurrent tools/execute
        model-order result -> ordered tools/post-execute -> 'tool/result'
      drain accepted tool context and steering
      'step/end'
      continue for tools or steering unless a result concluded the turn
      otherwise agent/turn-stopping -> drain -> continue only for steering
    close the next-step acceptance window
    'turn/end' -> agent/settled
  start the next waking queued message, or emit agent/status(idle)

idle inject:
  append 'user/message'
  do not open a turn or run the model

Each step assembles ordered stable system sections, cache-safe dynamic contexts, tool schemas, and variables; unknown references fail the turn. dsh-system-prompt owns identity and persona; the loop supplies provider, model, and cwd (prompt ownership).

Admission-time and active-turn inject() stage for the next step; post-tool additionalContexts settles after results. Steering shares that staging boundary and requests another step. Idle inject() appends immediately without changing turn numbers; persistence drains eagerly.

Pruning precedes summaries; overflow retries require durable progress. agent/request-error may authorize one retry turn between failed-step and turn close; cancellation wins. Adapter-owned retryPolicy makes normal mode bounded; always mode delegates specialized recovery before retrying until success or cancellation (compaction, retry foundation, provider policy).

Failure Boundaries

Adapter failures close their step before agent/request-error receives the exact Error, normalized LlmFailure, and signal. A handled failure closes its turn and opens a retry turn from durable history without an idle notification; exhaustion leaves terminal turn/end. Failed chunks commit neither messages nor tool calls.

Other failures use agent/error. Cancellation and disposal beat recovery. Before request-header commit, the turn signal cancels asynchronous model-capability preparation; undispatched tools get synthetic tool/call/ABORTED_BEFORE_DISPATCH pairs. Effective cancel(cause) emits its cause before queue clearing and abort; observers cannot veto; idle calls emit nothing. Durability records user or parent cancellation as aborted, teardown as disposed; teardown awaits quiescence. The cause affects reporting, not late result-context handling (decision).

Turn and step events are turn-enclosed; idle injected user/message events may sit between turns. Reload closes an interrupted tail with a synthetic turn end. After close, only agent/error reports failures. Each turn has one TurnEndReason.

Agent Handles

ctx.agents owns live agents and returns AgentHandle { agent, dispose() }. Plugins use full send() options or followup(), steer(), and inject() presets; cancel() and whenIdle() control lifecycle. One awaited disposer coordinates teardown ownership.

Agent Scope

Each agent owns scoped agent.ctx; shared storage overlays its tool, prompt, and command entries on globals while preserving domain views (decision). Scoped listeners filter dispatch; contributions unwind with awaited cleanup. CreateAgentOptions.setup(agentCtx) composes before publication. Typed resolvers derive carrier checks from merged Events and scopeTarget (semantic gates). Details: agent scope, subagent composition. AgentLoop runs under ctx.agents.withInitiator(); private orchestration derives agent.session, but turn, step, signal, cwd, and authority stay explicit (decision).

State

Session Log

The session log is authoritative. deriveMessages() projects model history; raw assistant/chunk events preserve replay and UI fidelity. Fork, resume, transcript rendering, telemetry, and persistence derive from this stream.

Model-visible ⟺ logged: before step/start, the loop appends the full current runtime-context snapshot as a sourced user/message, then snapshots derived messages. Those messages and the folded request/header reconstruct each request. The header marks adapter defaults so later proposals discard them and re-resolve the route without losing explicit settings. dsh-agent-loop/invariant asserts this through ctx.invariants (reconstructability).

Durability is a plugin concern. Backends eagerly drain synchronous session/event notifications. session/flush barriers precede each request and top-level tool dispatch, then follow turn/end before another queued turn or idle observation. SessionPersistence stores SessionEvent directly and metadata in SessionHeader; JSONL defaults to checksummed Zstandard, while SQLite shares the contract (decision).

Log-only events may sit between turns. Owners append through Session, flushing only for durability. session/title relies on eager persistence and lifecycle drains. Latest title wins with provenance; fallback and provider work never delays responses. Such records are fork boundaries, so forks inherit titles (decision).

Model Content

Messages use typed blocks from merge-extensible ContentBlockMap; the pattern also types MessageSource, FinishReason, TurnTrigger, and TurnEndReason. New blocks coordinate adapters, UI, compaction, token metering, and persistence; replay measurements live in token-meter.md.

Streaming uses raw chunks and BlockAssembler. Each LlmAdapter.stream() is one provider attempt; adapters report normalized failure facts, and a handling agent/request-error plugin returns a retry action. The loop logs chunks, successful provenance, and replay state. Remote adapters use per-read idle watchdogs. Replay crosses routes only through a shared adapter instance (contract).

Extension And Composition

Capability Pattern

A swappable capability usually has interface / implementation / consumer layers: service/events, backend, and model-facing tools/prompts. Bash is the reference; the capability graph maps each family.

Exceptions combine LLM interface/consumer, filesystem policy, web registries, and named skill/subagent providers. Subagents spawn fresh, fork a completed-turn prefix, or use ACP children (subagent.md).

dsh-workspace-context injects baseline at the first agent/step and appends ctx.fs-discovered changes through tools/post-execute; its decision records isolation. dsh-paths owns shared paths.

Bundles And Apps

dsh-agent-spine-demo bundles a spine and optional goals. App packages own TUI, CLI, ACP automation, and JSON-RPC front doors (README, acp/, ui/). dsh-jsonrpc-agent boots external cordis.yml; the Python SDK defaults when config is absent (Python SDK). Thin deployments use swappable backends and optional tools (examples/, runnable wirings, graph atlas).

Where New Behavior Goes

New behavior attaches to a documented extension point; a loop change updates this map.

Goal Mechanism
Add a model provider register its adapter on ctx.llm
Add a model-facing capability register on ctx.tools; schemas join prompt assembly
Add shell execution implement and register a ctx.bash backend; the local backend spawns through ctx.subprocess
Add persistent terminal execution register a ctx.pty backend plus dsh-tool-pty
Add a human command register on ctx.commands; adapters discover and dispatch without a model turn
Add background work register on ctx.tasks; generic task_* tools collect or stop it
Add filesystem access or policy implement a ctx.fs provider or listen to fs/* policy events
Confine spawned processes use a ctx.sandbox backend; consumers wrap argv before spawning
Intercept a request, tool, or turn use its agent/* or tools/* event; agent/turn-stopping is the stop boundary
Add model-facing context call agent.inject() to append a sourced user/message without a turn
Add UI or editor integration drive ctx.agents, render from session/event; terminal-only overlays use ctx.tui
Add durable session state extend SessionEventMap; render and replay from the log
Add asynchronous session-title generation register the sole ctx.sessionTitle provider
Manage a same-session objective use ctx.goals; continue through Agent and agent/*
Fork a live session call ctx.sessions.fork(source, boundary?, childSessionId?)
Scope a registration to one agent use its agent.ctx (see Agent Scope)

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