1. 为什么 headless 模式值得单独折腾一次DeepSeek Harnessdsh是 DeepSeek 官方开源的 agent harness一切皆插件的架构由 Cordis 驱动模型适配器、工具注册表、会话日志、agent 主循环本身都是可替换的插件。它适合谁适合想把 agent 能力嵌进自己流水线、又不想被 Web UI 绑住的开发者。dsh能做什么除了浏览器里的 Web UI它还提供 headless 一次性运行模式没有服务器、没有监听端口跑完就退出退出码直接反映成败。但很多人第一次跑 headless 会卡在两件事上一是 Key 到底放哪、怎么让 headless 和插件共用同一份凭证二是插件加载顺序和 profile 的关系没理清导致--profile headless跑起来报MISSING_CREDENTIAL或者模型找不到。这篇就聚焦这两个场景给你一份可复制的config.toml骨架思路dsh实际用 YAML 与 profile 清单下面会说明对应关系再演示一次 headless 调用和插件启停的验证动作目标是一次性完成本地环境自检。我试过把 Key 散落在环境变量、.env、.credentials.yaml三处结果 headless 和 Web UI 读到的不是同一份排查了半天。统一 Key 的核心就是让凭证解析顺序可预期下面按步骤来。2. 前置准备TaoToken 统一 Key 与 dsh 环境2.1 拿到统一 Keydsh需要一个兼容 OpenAI 协议的模型端点。你可以用 TaoToken 作为统一入口一个 Key 同时给 headless、Web UI 和插件里的模型适配器用省得每个 profile 配一遍。打开控制台创建 Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite创建完在 API Keys 页面复制https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite接入文档在这里字段含义和端点说明都在这https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteAPI 基地址是https://taotoken.net/api注意这个地址不加 UTM 参数直接填进配置即可。2.2 环境要求Node.js 要求^22.19或24从源码跑还需要 pnpm。先确认版本node -v # 期望输出 v22.19.x 或 v24.x pnpm -v如果只是想快速体验不克隆仓库也行npx deepseek-ai/dsh web但要做 headless 和插件调试建议从源码跑方便看--dump-config的实际插件树。git clone https://github.com/deepseek-ai/deepseek-harness.git cd deepseek-harness pnpm install pnpm run buildpnpm run build是生产运行的前提pnpm dsh走 tsx 以 ESM 模式直接从源码启动两者别混。3. 可复制配置统一 Key 与 config.toml 骨架3.1 关于 config.toml 的说明dsh的配置主体是 profile 清单加cordis.patch.yml不是 TOML。但很多团队习惯用一份 TOML 做“环境变量与端点”的单一事实来源再由启动脚本导出成环境变量。下面这份config.toml就是干这个的你可以放在项目根目录配合一个source脚本使用。# config.toml —— 统一 Key 与端点供 headless / web / 插件共用 [llm] provider_id taotoken display_name TaoToken Gateway base_url https://taotoken.net/api api openai-completions api_key_env TAOTOKEN_API_KEY default_model deepseek-chat [llm.models] # 手动声明模型避免 Fetch models 失败时无模型可选 ids [deepseek-chat, deepseek-reasoner] [harness] home .dsh permission_mode workspace-write tools_mode native [headless] profile headless timeout_seconds 1203.2 把 TOML 转成环境变量dsh的凭证解析顺序是继承的环境变量 →$DSH_HOME/.credentials.yaml→ 启动目录的.env→$DSH_HOME/.env先到先得。所以最省事的做法是把统一 Key 放进环境变量headless 和 Web UI 都能读到同一份。export TAOTOKEN_API_KEYsk-你的统一Key export DEEPSEEK_API_KEY$TAOTOKEN_API_KEY export DEEPSEEK_BASE_URLhttps://taotoken.net/api export DSH_HOME$PWD/.dshDEEPSEEK_API_KEY是dsh内置 DeepSeek 适配器认的变量名DEEPSEEK_BASE_URL指向 TaoToken 网关这样搜索和模型请求都走同一个 Key。DSH_HOME决定 profiles、credentials、settings 的落盘位置固定下来后 headless 和 web 共享同一套插件树。3.3 自定义 Provider 的 settings.yaml如果你不想用内置 DeepSeek 卡片而是走自定义 Provider在$DSH_HOME/settings.yaml里声明llm-pi-ai: providers: taotoken: apiKeyEnv: TAOTOKEN_API_KEY api: openai-completions baseURL: https://taotoken.net/api models: - id: deepseek-chat input: [text] - id: deepseek-reasoner input: [text]apiKeyEnv只存变量名明文 Key 留在环境变量里设置页只保留凭证引用不回显明文。这样 headless 和 Web UI 读的是同一个 Provider 定义。4. 验证请求headless 调用与插件启停4.1 先看插件树别急着跑在跑任务前先确认 profile 组合出来的插件树符合预期pnpm dsh --profile headless --dump-config输出会列出 profile 清单、各 bundle 的配置行、以及 patch 覆盖后的结果。重点看deepseek-ai/dsh-base是否在第一层deepseek-ai/dsh-headless是否挂上。如果这里就缺 headless bundle后面跑任务必然报用法错误。4.2 一次 headless 调用pnpm dsh --profile headless 用一句话说明这个仓库的用途行为预期创建一个全新的持久化会话提交任务等待静默刷新 Sessionstdout 输出最后一条非空 assistant 文本completed 时退出码 0否则退出码 1。没有监听端口stderr 在成功时无输出。验证退出码pnpm dsh --profile headless 运行测试 ; echo exit$?如果输出exit0且 stdout 有答案说明统一 Key 和 headless 链路通了。4.3 插件启停验证先装一个插件到独立 profile避免污染 headlesspnpm dsh plugin --profile tui add github:deepseek-harness/turtle-ui pnpm dsh --profile tui --dump-config--dump-config里应该能看到新插件对应的配置行。再移除pnpm dsh plugin --profile tui remove turtle-ui pnpm dsh --profile tui --dump-config对比两次输出确认插件行出现又消失说明插件加载与卸载生效。Git 托管的插件在安装时通过 prepare 脚本构建pnpm ≥10 需要先在 profile 的pnpm-workspace.yaml里允许构建按报错提示复制allowBuildskey 即可。4.4 Python SDK 跑通同一份 Keydsh提供 Python SDK适合把 headless 能力嵌进脚本。先起一个 Web 服务作为 SDK 的后端pnpm dsh web --port 3080另开终端cd python/sdk pip install -e .from dsh import DSHClient client DSHClient(http://127.0.0.1:3080) session client.create_session() for event in session.send(总结这个仓库): print(event)SDK 连的是本地 Web 服务而 Web 服务读的是同一份TAOTOKEN_API_KEY所以 Key 只维护一处。具体 API 以python/sdk/README.md为准。5. 本篇常见错排查MISSING_CREDENTIAL最常见。检查TAOTOKEN_API_KEY是否 export 到当前 shell以及$DSH_HOME/.credentials.yaml里是否有旧 Key 抢先命中。凭证解析是先到先得环境变量优先级最高但如果你在 Web UI 里保存过 Key它会写进 credentials 文件headless 在环境变量缺失时会读到它。UNKNOWN_MODEL模型没在 Provider 里声明。要么在settings.yaml的models列表里补上要么在 Web UI 的模型选择器里选一个已配置的。已发送过请求的会话会保留日志里记录的模型不会自动跟随新默认值。Fetch models 返回 401Key 不对或者端点没有GET /models。TaoToken 网关支持模型列表拉取如果失败就手动输入模型 id。headless 报“没有任务文本”headless 需要位置参数任务文本空参数是用法错误不是配置问题。dsh web --host 0.0.0.0报错CLI 暂不支持0.0.0.0用--trusted-host添加受信任域名。Git 插件 add 失败在 profile 的pnpm-workspace.yaml中允许allowBuilds按报错提示复制 key。图片在发送前被拒绝模型未声明图片模态给自定义模型加input: [text, image]。6. 把统一 Key 固化进日常流程到这一步headless 调用、插件启停、Python SDK 三条链路都验证过了。我的做法是把config.toml和一段export脚本放进项目根目录每次开新终端先 source 一次DSH_HOME固定指向项目内的.dsh这样 headless 和 Web UI 永远共享同一份凭证与插件树不会再出现“Web 能跑 headless 报错”的割裂。如果你要长期跑编码任务或 Agent 工作流可以看下 Coding Plan把额度用在持续会话上更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite想先在浏览器里手动验证模型对话是否正常用模型对话页快速试一条https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite接入细节和字段说明以文档为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite最后提醒一句dsh目前是开发者预览会有破坏兼容性的变更升级前先--dump-config对比插件树确认 patch 行 id 没变再跑 headless。
