DeepSeek Harness(dsh)架构解析:从 Cordis 插件框架到智能体运行框架的配置骨架
2026/9/23 2:19:01 网站建设 项目流程

1. 为什么要在本地搭一套 dsh 运行环境

DeepSeek Harness(dsh)是 DeepSeek 开源的智能体运行框架,底层基于 Cordis 插件框架构建,核心范式是 Everything is a plugin——模型适配器、工具注册表、会话日志、agent 循环本身都是插件,全部通过 Cordis 挂载到共享的 ctx 上,从配置层面就能整体替换。它适合需要在本地搭建可扩展智能体运行环境的开发者,尤其是想自己控制插件加载顺序、替换模型 provider、或者把 agent 循环换成自定义实现的人。

dsh 的运行入口是npx @deepseek-ai/dsh web,默认 Web UI 监听http://127.0.0.1:3080。语言栈是 TypeScript(ESM)加 Python SDK(子进程驱动),包作用域为@deepseek-ai/dsh-<pkg>,其中@deepseek-ai/cordis是每个包的 peerDependency,许可证 MIT。

我试过从零跑通 dsh 的插件加载链路,最容易卡住的不是代码本身,而是配置骨架没搭对:profile 里 bundles 的顺序、cordis.patch.yml 的层级覆盖、以及模型适配器指向哪个 API 通道。这篇就围绕这三件事,交付一份可复制的config.tomlsettings.json骨架,并给出通过 TaoToken 统一 Key/API 通道接入的验证动作,目标是一次性跑通插件加载与运行链路。

需要先明确一点:dsh 当前处于 developer preview 阶段,SESSION_FORMAT_VERSION为 0,后端拒绝旧的磁盘格式,架构细节可能随版本变化。所以下面的配置骨架以仓库最新docs/AGENTS.md为准,遇到字段对不上时优先查文档。

2. 前置准备:TaoToken 统一 Key 与 API 通道

dsh 的 LLM 能力族在packages/llm,它拥有对话与流式类型,并通过ctx.llm暴露LlmRuntime。适配器契约里registerAdapter(providers, adapter)负责注册适配器实例,stream()是唯二必须方法。也就是说,你只要让适配器指向一个兼容的 API 通道,就能把模型请求接进来。

TaoToken 在这里扮演的是统一 Key/API 通道的角色:一个 Key 覆盖多家模型,API 地址固定,省去在 dsh 里为每个 provider 单独配凭证的麻烦。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (不加 UTM)。

操作顺序建议这样:

先到控制台创建 Key。打开 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,登录后在 API Keys 页面新建一个 Key,复制出来。这个 Key 后面会写进 dsh 的凭证引用里,不要直接硬编码在config.toml中。

然后确认模型对话通道可用。在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 选一个你想用的模型,记下模型名。dsh 的适配器需要 provider 名和模型名两个字段,provider 填taotoken,模型名按你选的填。

如果你打算长期跑编码类 agent 或做 Agent 编排,可以看 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,API Keys 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。

注意:dsh 的凭证走ctx.credentials引用机制,配置里只写引用名,真实 Key 放在环境变量或凭证存储里。这样 reload / teardown 时不会把密钥写进会话日志。

3. 可复制的 config.toml 与 settings.json 骨架

dsh 的运行时组合由 Profile 和 Bundle 两层构成。Profile 存储在 Harness home 的命名组合,列出堆叠的 bundles、树外插件以及用户自己的cordis.patch.yml;Bundle 是 Cordis 配置行及其挂载代码的发行格式,每个包在自己的package.jsondsh字段声明dsh.profiledsh.bundle

分层应用顺序(对空入口列表)是:profile 中dsh.profile.bundles顺序的每个 bundle → profile 的cordis.patch.yml→ home 级cordis.patch.yml--patch覆盖层。你可以用dsh --profile web --dump-config查看机器实际启动的树。

3.1 config.toml:profile 与 bundle 骨架

下面这份config.toml放在$DSH_HOME/profiles/local-dev/下,作为自定义 profile 的起点。它显式列出三层 bundle,并在最后挂一个模型适配器插件。

# $DSH_HOME/profiles/local-dev/config.toml # dsh 自定义 profile:本地可扩展智能体运行环境 [profile] name = "local-dev" # 第一层必须是 dsh-base:模型适配器、工具、持久化、沙箱与审批策略、设置、凭证、遥测 bundles = [ "dsh-base", "dsh-web-app", "dsh-headless", ] # 树外插件:本地开发的适配器与工具扩展 [[plugins]] name = "dsh-llm-taotoken" path = "./plugins/llm-taotoken" enabled = true [[plugins]] name = "dsh-tool-local-shell" path = "./plugins/tool-local-shell" enabled = true # 凭证引用:只写引用名,真实 Key 从环境变量读取 [credentials] llm_provider_ref = "TAOTOKEN_API_KEY" # 模型适配器配置:provider 名 + 模型名 [llm] provider = "taotoken" model = "deepseek-chat" base_url = "https://taotoken.net/api" stream = true # 沙箱与审批策略:本地开发建议先放宽,生产再收紧 [sandbox] backend = "none" approval_policy = "ask" # 会话持久化:JSONL 后端,便于 replay 与调试 [session] backend = "jsonl" format_version = 0

几个字段的取舍说明。bundles的顺序不能乱:dsh-base必须第一,它提供模型适配器、工具、持久化、沙箱与审批策略、设置、凭证、遥测;dsh-web-app增加浏览器应用;dsh-headless增加一个一次性运行器(无 server)。如果你只跑 headless 任务,可以去掉dsh-web-app

[llm]段里的base_url指向 TaoToken 的 API 基址,providertaotokenmodel按你在模型对话页选的填。stream = true对应适配器契约里的流式要求——StreamChunk是封闭判别联合,以assertNever收尾,包含block-starttext-deltareasoning-deltatool-call-deltablock-endusagefinish等变体。

[sandbox]段本地开发先设backend = "none",等链路跑通再换成bwrapLandlockSeatbeltapproval_policy = "ask"对应工具执行管线里的tools/pre-execute瀑布,允许 / 拒绝 / 询问三态。

3.2 settings.json:用户设置与凭证引用

settings.json放在$DSH_HOME/下,管的是用户级设置和凭证引用,不涉及插件树结构。

{ "version": 0, "credentials": { "TAOTOKEN_API_KEY": { "source": "env", "envVar": "TAOTOKEN_API_KEY" } }, "llm": { "defaultProvider": "taotoken", "defaultModel": "deepseek-chat", "requestTimeoutMs": 120000, "maxRetries": 2 }, "session": { "persistChunks": true, "deriveMessagesOnLoad": true }, "tools": { "preExecutePolicy": "ask", "guards": [] }, "telemetry": { "enabled": false } }

credentials段用source: "env"envVar的方式引用环境变量,这样config.toml里只出现引用名TAOTOKEN_API_KEY,真实值在 shell 里 export。session.persistChunks = true对应持久化契约——每个事件包括assistant/chunk都无损持久化,seq连续,所有event.data必须 JSON 可序列化,Session.append在源头校验。

tools.guards是空数组,对应ToolGuard的 scope 感知最终预分发策略。它的返回类型没有allow结果:undefined保留 waterfall 决策,返回reason只能收窄权限,所以后续 listener 无法把拒绝翻转为允许。这个设计保证了策略的单调性。

3.3 环境变量与启动

在 shell 里设置 Key,然后启动 profile:

export TAOTOKEN_API_KEY="你的_TaoToken_Key" export DSH_HOME="$HOME/.dsh" # 首次使用自定义 profile 需经 dsh plugin 创建 dsh plugin --profile local-dev init # 启动 web 形态 dsh --profile local-dev web # 或者跑一个 headless 任务 dsh --profile local-dev --profile headless "列出当前目录的 TypeScript 文件"

dsh plugin --profile <name> <pnpm args>会转发到 profile 目录的 pnpm,用来管理插件。web 与 headless profile 首次使用从模板自动初始化,其他 profile 须经dsh plugin创建。

4. 验证请求:跑通插件加载与运行链路

配置写完后,先别急着发模型请求,按下面三步验证链路。

4.1 检查配置树

dsh --profile local-dev --dump-config

这条命令打印机器实际启动的树。重点看三处:bundles是否按dsh-basedsh-web-appdsh-headless顺序展开;plugins里两个树外插件是否 enabled;llm.base_url是否指向https://taotoken.net/api。如果dsh-base不在第一层,启动会直接失败——Misconfiguration fails loud是 dsh 的不变量,自包含时加载即失败,否则在最早可解析点失败,绝不静默跳过缺失引用。

4.2 验证模型适配器

启动 web 形态后,打开http://127.0.0.1:3080,在对话里发一句简单请求。观察终端日志,应该能看到StreamChunk的流式输出:先是block-start,然后一串text-delta,最后block-endusagefinish

适配器契约里有几条硬性要求,验证时对照检查:usage必须在finish之前;tool-call参数保持 raw JSON 字符串;两种错误路径同一LlmFailure类型;一次适配器调用等于一次 provider 尝试;上下文溢出统一CONTEXT_WINDOW_EXCEEDED码;空完成是 retryable 错误;携带 app-attribution header。

如果请求返回但内容为空,先看是不是finish先于usage到达,这通常意味着适配器没按契约顺序发 chunk。

4.3 验证工具执行管线

发一个需要调用工具的请求,比如让它读一个文件。工具执行管线是:tools/pre-execute(允许 / 拒绝 / 询问)→ 已注册单调 guards →tools/execute(around-dispatch 包装)→tools/post-execute(检查 / 替换结果)→ 可选的finalizeContenttools/result(不可变权威结果)。只有tools/execute视图可替换 signal。

approval_policy = "ask"下,你应该在 UI 里看到审批提示。批准后工具执行,结果经tools/result落成不可变权威结果,同时写入会话日志的tool/calltool/result事件。

4.4 验证会话日志与 replay

会话是类型化SessionEvent的 append-only 日志,是 agent 整个交互历史的唯一事实源。LLM 消息历史是从日志派生(deriveMessages())的,从不单独存储;replay 即同一事件的再派生。

# 查看会话日志文件 ls $DSH_HOME/sessions/ # 用 headless 跑一个任务,观察日志写入 dsh --profile local-dev --profile headless "读一下 README.md 的前 20 行"

跑完后检查 JSONL 文件,应该能看到turn/startstep/startuser/messageassistant/chunkassistant/messagetool/calltool/resultstep/endturn/end等事件按seq单调递增排列。SurfaceEventType只有user/messageassistant/messagetool/result三类产生消息,携带surfaceOpappend{op:'replace',start,end}),deriveMessages()据此派生模型可见历史。

5. 本篇常见错排查

5.1 启动报 bundle 顺序错误

现象:dsh --profile local-dev web启动即失败,提示 bundle 依赖缺失。

原因:dsh-base不在bundles第一层。dsh-base提供模型适配器、工具、持久化、沙箱与审批策略、设置、凭证、遥测,是每个 profile 的第一层。

处理:把dsh-base移到bundles数组首位,重新--dump-config确认。

5.2 模型请求 401 或凭证找不到

现象:对话返回认证失败,或日志提示credential ref not resolved

原因:config.tomlllm_provider_ref = "TAOTOKEN_API_KEY"引用的环境变量没 export,或settings.jsonenvVar名字对不上。

处理:确认 shell 里echo $TAOTOKEN_API_KEY有值;确认settings.jsoncredentials.TAOTOKEN_API_KEY.envVar与 export 的变量名完全一致。Key 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 管理。

5.3 流式输出中断或空完成

现象:请求发出后没有text-delta,直接finish,或中途断开。

原因:适配器没按StreamChunk契约发 chunk,或base_url写错导致请求打到不兼容端点。

处理:确认base_url = "https://taotoken.net/api";检查适配器是否在finish之前发usage;空完成在契约里是 retryable 错误,看maxRetries是否生效。

5.4 工具调用被静默跳过

现象:模型请求了工具,但执行管线没触发,也没有审批提示。

原因:tools/pre-execute瀑布里某个 listener 没调用next(),短路了整条链。Waterfall 语义是next()委托给下游,不调用即短路。

处理:检查settings.jsontools.guards和已注册的tools/pre-executelistener,确认每个 listener 在非决策路径上都调用了next()

5.5 会话日志 replay 报格式错误

现象:加载旧会话时提示SESSION_FORMAT_VERSION不匹配。

原因:dsh 当前SESSION_FORMAT_VERSION为 0,无兼容承诺,后端拒绝旧磁盘格式。

处理:删掉旧会话文件重新跑,或把settings.jsonsession.format_version对齐当前版本。这是 pre-release 格式的预期行为,不是 bug。

5.6 插件 reload 后注册残留

现象:reload 插件后,旧的工具 schema 或提示段还在。

原因:注册没走ctx.effect()/ctx.on(),disposer 没在卸载时解除。Registrations are effects是贯穿代码库的工程纪律。

处理:检查插件里所有贡献是否都通过ctx.effect()/ctx.on()安装,确保返回的 disposer 在插件卸载时自动解除。

6. 下一步:把链路接进你的工作流

链路跑通后,接下来是把它接进实际工作流。如果你主要做编码类 agent 或长期运行的 Agent 编排,建议看 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合持续调用场景。接入细节和字段说明在接入文档: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。Key 的创建和轮换在 API Keys 页: https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。想先验证模型对话是否正常,可以直接在模型对话页试: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。

dsh 的扩展点机制里,新增行为挂在已文档化的扩展点上,改循环本身要更新docs/architecture.md。加模型 provider 在ctx.llm注册适配器;加模型面能力在ctx.tools注册;给某会话不同能力集就组合一个 agent preset;加 shell 执行注册ctx.shell后端;加持久终端注册ctx.terminals后端加dsh-tool-terminal;加人类命令在ctx.commands注册;加后台工作在ctx.jobs注册。事件域分三类:Session 事件是持久事实,追加到日志并广播session/event;Agent 事件(agent/*)携带实时 Agent;Capability 事件把策略与适配器挂到 seam(fs/*tools/*telemetry/*),无需 import 循环。

最后提醒一句:Model-visible means logged是运行时不变量,任何进入模型请求的内容都必须能从会话日志重建,新增模型可见输入等于必须新增一条会话事件。这条约束在扩展插件时最容易踩,写新工具或新提示段时记得同步加事件。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询