Files
deepseek-harness/docs/architecture.md
T
Hypatia May 8a8c1965d7 feat(web): show durable token usage and context occupancy in the stats line
The chat stats line took its token totals from the loaded conversation nodes,
so paging changed them and compaction erased the billing behind replaced
content. It also had no way to show context occupancy: the numerator and
capacity never reached the browser.

Both now come from token-meter session projections read through the standard
useProjection seat. Window nodes keep supplying turn and step counts plus LLM
and tool wall times, which are correctly window-scoped facts about what is on
screen; accounting no longer comes from there.

`tokenUsage` supplies billing and cache hit. `contextPressure` supplies
occupancy, pairing the newest provider-reported prompt size with the newest
capacity recorded by `request/context`. Deployments without token-meter drop
the token groups; a route whose adapter advertises no capacity drops the
occupancy group rather than rendering a placeholder.

Occupancy is deliberately approximate: the numerator and capacity are
independent last-wins fields, not one atomic request observation, so switching
models pairs a fresh capacity with the prior route's pressure until the next
request reports usage. It is a user-facing reference figure that nothing in the
harness makes decisions from, and it matches how the TUI status line has always
computed occupancy. The Agent Note and token-meter README state this as a
decision, including why the atomic alternative was implemented and rejected, so
it is not re-litigated as a defect.

Snapshot delta is one added `Context N% of 128K` segment across eight web
goldens; the preceding commit absorbed master's pre-existing golden drift.
2026-07-30 14:48:19 +08:00

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 prompt sections, 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.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 tool schemas
      snapshot the derived messages (the reconstruction boundary)
      'step/start'
      agent/request (config only) -> prepare reasoning/default + context capacity under turn signal -> log request/header (+ request/context on route change) -> 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 prompt sections, 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: messages at step/start plus the folded request/header reconstruct every request; package-owned dsh-agent-loop/invariant can assert 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.