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:

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.

Package Owns ctx key
core/session The append-only SessionEvent log and in-memory store ctx.sessions
core/system-prompt Prompt-section and tool-schema assembly ctx.systemPrompt
core/tools The scoped tool registry and guarded execution pipeline ctx.tools
core/agent The Agent interface, live registry, and agent/* events ctx.agents
core/agent-loop The default driver implementing that interface ctx.agentLoop
core/scope The per-agent scoped-registration primitive library, no key
llm/llm Message and stream vocabulary plus the adapter seam ctx.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.

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.

Goal Mechanism
Add a model provider register its adapter on ctx.llm
Add a model-facing capability register on ctx.tools; its schema joins prompt assembly
Give one session a different capability set compose an agent preset; a service row there needs an isolate realm
Add shell execution register a ctx.shell backend; the local one spawns through ctx.subprocess
Add persistent terminal execution register a ctx.terminals backend plus dsh-tool-terminal
Add a human command register on ctx.commands; it dispatches without a model turn
Add background work register on ctx.jobs; job_* tools collect or stop it
Add filesystem access or policy register a ctx.fs provider or listen to fs/* 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 stops a turn
Add model-facing context call agent.inject(); it lands in the next admitted request
Add UI or editor integration drive ctx.agents and render from session/event
Add a Web Client Chat node register a ConversationNodeDefinition + keyed renderer
Add durable session state extend SessionEventMap; render and replay from the log
Generate session titles register the sole ctx.sessionTitle provider
Manage a same-session objective use ctx.goals; continue through agent/*
Fork a live session ctx.sessions.fork(source, boundary?, childSessionId?)
Scope a registration to one agent use 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.

DeepSeek Harness 架构

改动 packages/ 下的任何内容之前,请先阅读本文。本文假定你已了解 Cordis;如果尚未了解,请先阅读入门教程

建议使用 agent(智能体)探索代码库并理解其架构。

Cordis

Cordis 是 dsh 底层的框架:插件向共享上下文贡献服务、类型化事件和可逆的副作用。产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop(智能体循环)本身,因此每一部分都可以从配置替换。

不存在需要打补丁的特权内核:扩展 dsh 的方式是把插件挂载到其他插件旁边,而各项注册都是副作用,会在其插件卸载时撤销。

Profile 与组合包

运行中的 dsh 是一棵插件树,由启动时按序叠加的各层组合而成。

profile 是存放在 Harness home 中的具名组装。它列出自己叠放的组合包,存放自己安装的树外插件,并保存用户自己的 cordis.patch.ymlwebheadless 作为模板随发行版交付。

组合包是 Cordis 配置项及其挂载代码的分发格式,因此它插入的内容始终可被其上各层 patch。

两者都在各自的 package.json 中通过 dsh 字段声明自己:dsh.profile 列出一个 profile 的组合包,dsh.bundle 指向一个组合包的 patch 文件。

dsh-base 是每个 profile 的第一层:模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测。dsh-web-app 增加浏览器应用;dsh-headless 增加一次性运行器,且完全不带服务器。

各层按此顺序应用在空条目列表之上:先按 profile 列出的顺序应用每个组合包,然后是 profile 的 cordis.patch.yml,然后是 home 级的那份,最后是任意 --patch overlay。一条 patch 按 id 定位某个条目并替换其整个 config,或插入新条目。

要查看你的机器实际启动的配置树:

dsh --profile web --dump-config

它打印出的任何条目,都可以由你自己的 patch 替换。

组装机制见 app-boot;配置字段见生成的配置目录

核心包

以下是向 Cordis 树贡献内容的部分核心包。

职责 ctx
core/session 仅追加的 SessionEvent 日志和内存存储 ctx.sessions
core/system-prompt 提示词片段与工具 schema 的组装 ctx.systemPrompt
core/tools 作用域化的工具注册表和带把关的执行流水线 ctx.tools
core/agent Agent 接口、活跃 agent 注册表和 agent/* 事件 ctx.agents
core/agent-loop 实现该接口的默认驱动器 ctx.agentLoop
core/scope 按 agent 划分作用域的注册原语 库,无 ctx 键
llm/llm 消息与流式词汇表,以及适配器 seam ctx.llm

事件

事件就是扩展点,而选对事件域是大多数改动的第一个决定。

  • 会话事件是追加到日志并通过 session/event 广播的持久事实。当某个事实必须在重新加载后仍然存在时,使用它。
  • Agent 事件agent/*)携带活跃 Agent:inbox、步骤、状态、请求、验证、续跑。要观察或拦截进行中的工作时,使用它。
  • 能力事件无需导入循环即可向某个 seam(fs/*tools/*telemetry/*)附加策略和适配器。

事件映射列出每个事件的生产方与消费方。

轮次流程

一个步骤是一次模型请求加上它调用的工具。一个轮次包含零个或多个步骤:它在领取首条输入之前打开,并在不再欠下任何工作时关闭。

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/messageassistant/*tool/* 是持久会话事件;其余是分属三个事件域的实时扩展点。agent/pre-stepagent/requestllm/stream 和三个 tools/* 事件是 waterfall(瀑布式事件),其监听器必须调用 next() 才能委托下去;agent/turn-stopping 是 serial 事件,没有 next()

输入通过同一个 inbox 到达驱动器。有些消息会立即唤醒它;注入的上下文会留在 inbox 中,直到另一条消息将其唤醒。

agent/pre-step 决定模型看到什么。监听器可以改写已领取的消息,也可以直接拒绝它们;首次领取被拒绝或被改写为空时,仍会关闭一个不含步骤的持久轮次,因此日志会记录这次尝试。每个步骤读取插件注册的提示词片段和工具 schema。

详情见时序图工具流水线取消与错误恢复

会话日志

会话日志是模型所见上下文的来源。deriveMessages() 从中投影出模型历史,原始 assistant/chunk 事件则保证回放和 UI 保真。fork、恢复、transcript(文本记录)、遥测和持久化都派生自该事件流。

模型可见即已记录。 抵达模型请求的一切都必须能从日志重建,并由一项运行时不变量断言这一点。因此,新增一项模型可见输入就需要新增一个会话事件:扩展 SessionEventMap 并从日志渲染。

能力 seam

一个 seam 是一项可替换能力,包含三种角色:声明接口的 Service Definition、实现它的 Service Provider,以及使用它的 Consumer(通常是面向模型的工具)。一个包可以合并承担多个角色,但单一角色本身不是 seam;添加一项能力意味着把三者一并设计(能力图)。

seam 正是替换一个提供方就能改变整个产品的原因。文件系统与进程提供方共享同一个执行世界,因此把它们指向远程沙箱,也就把 Bash、PTY 和 LSP 一并搬了过去,无需提供方专用 fork。subagent 提供方在同一个接口之后同样千差万别,从新建一个子 agent,到把一个轮次委派给另一个产品。

新行为的归属位置

新行为附加到已有文档记录的扩展点。改动循环本身时,本映射随之更新。

目标 机制
添加模型提供方 ctx.llm 上注册其适配器
添加面向模型的能力 ctx.tools 上注册;其 schema 加入提示词组装
让某个会话拥有不同的能力集合 组装一个 agent preset;其中的服务行需要 isolate realm
添加 shell 执行 注册 ctx.shell 后端;本地后端通过 ctx.subprocess spawn 进程
添加持久化终端执行 注册 ctx.terminals 后端和 dsh-tool-terminal
添加用户命令 ctx.commands 上注册;它无需模型轮次即可分派
添加后台工作 ctx.jobs 上注册;job_* 工具负责收集或停止
添加文件系统访问或策略 注册 ctx.fs 提供方,或监听 fs/* 事件
限制所启动的进程 使用 ctx.sandbox 后端;消费方在启动进程前包装 argv
拦截请求、工具或轮次 使用相应的 agent/*tools/* 事件;agent/turn-stopping 会停止轮次
添加模型可见上下文 调用 agent.inject();它会落到下一次获准的请求中
添加 UI 或编辑器集成 驱动 ctx.agents 并从 session/event 渲染
添加 Web Client Chat 节点 注册 ConversationNodeDefinition + keyed renderer
添加持久会话状态 扩展 SessionEventMap;从日志渲染和回放
生成会话标题 注册唯一的 ctx.sessionTitle 提供方
管理同会话目标 使用 ctx.goals;通过 agent/* 续跑
fork 活跃会话 ctx.sessions.fork(source, boundary?, childSessionId?)
将注册项限定到单个 agent 使用该 agent 的 agent.ctx

扩展实操手册将功能映射到能力,并索引工具LLM(大语言模型)适配器Chat 节点的分步指南。