CH 05 · 命令行跑通:headless + CLI
本章目标
前面几章都在 Web UI 里点。这一章换个形态:不开任何界面,在终端里一条命令让 dsh 干完一件事就退出——这就是 headless。顺便把 dsh 这个"启动器"到底能开哪些门摸一遍,以后写脚本、挂 CI、批量干活都用得上。
先搞懂:dsh 是一个多入口启动器
dsh 不只是"那个网页"。它是个启动器(launcher)——同一个 Harness、同一套插件栈,可以按不同的形态启动:
官方给的入口就这几扇门:
| 入口 | 用途 | 大白话 |
|---|---|---|
web | 带界面的 Web 工作台 | 前面几章用的那个 |
headless | 跑一个任务、打印答案、退出 | 命令行一次性任务,本章主角 |
sdk | JSON-RPC stdio 服务 | 给程序当后端,让别的应用调用 |
acp | ACP stdio 服务 | 给自动化客户端提供服务 |
plugin | 管理 profile 的插件 | 装插件、给某个 profile 加依赖就靠它 |
不管从哪扇门进,底下都是同一套插件栈在干活。CH 02 说过"一切皆插件",到这里你应该能感受到:连"入口"本身也是插件的组合。
headless:一句话一个任务
headless 就是命令行模式的一次性任务。用法极简:
dsh --profile headless "你要它干的事"它的行为官方一句话说清:开一个全新的持久化会话 → 干完活 → 打印最终答案 → 退出。
几个要点:
- 工作区 = 你运行命令时所在的目录。在哪个目录敲命令,它就在哪个目录干活(不用像 Web UI 那样手动选工作区)。
- "开一个全新的持久化会话"是字面意思:每跑一次 headless,就等于以当前目录为工作区新开一条会话,并保存到
$DSH_HOME/sessions。这里容易搞混,帮你理一下——文件层面:会话按工作区文件夹存放,在C:\Users\<你的用户名>\.dsh\sessions\下,能直接看到以工作区路径命名的目录(--E-software-workspace-...--这种),里面是压缩的会话文件;Web UI 层面:headless 跑出来的会话却显示在未分组里,不会自动挂到某个工作区分组下(当前版本如此)。文件存储归工作区、界面显示归未分组,这是两回事。下面的实操会看到。 - 默认模型是
deepseek-v4-flash:CLI 场景没有界面、不用看图,官方默认就给了性价比最高的 flash。 - 没界面也有完整流程:上下文注入、规划、调工具、思考、收尾,一个不少,只是不画给你看。
动手:第一次 headless 任务
给 Agent 一个"重活":读仓库、总结架构、写一份中文文档。在工作区下跑:
dsh --profile headless "通读 deepseek-harness 子目录的代码和文档,总结 DeepSeek Harness 的整体架构(插件机制、分层结构、入口、主要包和目录),写一份中文 markdown 架构文档保存到当前目录,文件名用 deepseek-harness-arch.md"跑完打印的结果:
已完成。我通读了 deepseek-harness 子目录的关键源码与文档,整理成中文架构文档并保存到当前目录。
文件:E:\software-workspace\DeepSeek harness demo\deepseek-harness-arch.md(约 295 行)回到 Web UI 验证这条会话:在会话列表里能找到它,但注意它出现在未分组里,而不是某个工作区分组下(headless 会话不会自动归组,这是当前版本的实际表现):

右侧还能看到它完整的执行过程:上下文注入 → 思考 → Pwsh 列目录 → 读文档 → 写文件,底部统计条显示 1 轮 · 18 步、LLM 2m1s、缓存命中 92%、输入 1.1M tokens。
CLI 参数速查
| 命令 | 作用 |
|---|---|
dsh --profile <名字> "任务" | 启动指定 profile(headless 是其中之一) |
dsh web | --profile web 的别名,启动 Web UI |
dsh --dump-config | 打印组合后的完整配置树(排障神器,见下) |
dsh --dump-default-config | 打印不带用户改动的默认配置树 |
dsh --patch <路径> | 在 profile 之上再叠加一层配置 |
dsh plugin --profile <名字> add <包> | 给某个 profile 装插件 |
dsh --help | 看启动器自己的帮助 |
--dump-config 值得单独说:它把某个 profile 最终生效的插件组合打印出来。实际跑一下:

看到的就是一长串 @deepseek-ai/dsh-* 插件叠出来的树——llm(模型)、session(会话)、credentials(密钥)、session-persistence-jsonl(会话持久化)……还能直接看到 agent-default-model 配的是 deepseek-v4-flash。以后排障、想搞清"某个行为是哪来的",先 dump 一下配置树。
排障时还有个更省事的玩法:直接让 AI 自己跑这个命令。比如在会话里问它"用 dsh --profile headless --dump-config 检查一下当前配置树,看看默认模型为什么不是我想用的""查一下某个插件是不是没生效"——AI 会自己执行 --dump-config、读配置树、顺着配置逐条帮你定位问题。排查故障时这是个很实用的组合拳。
什么时候用 headless,什么时候用 web
| 场景 | 用哪个 |
|---|---|
| 想看着 Agent 干活、随时打断、逐步排障 | web |
| 脚本、CI、定时任务、批量处理,只要结果 | headless |
| 别的程序/工具要调 dsh 的能力 | sdk / acp |
| 想确认配置、排查启动问题 | --dump-config / --help |
再记住官方的一个边界:headless 每次调用只跑一个任务,没有交互式追问,想要多轮、要看着它干,就回到 web。
这一章你学到了什么
能自己完成下面几条,就算过关:
- [ ] 能说出 dsh 至少四个入口(web / headless / sdk / acp / plugin)分别干什么
- [ ] 能用
dsh --profile headless "任务"跑通一个命令行一次性任务,并解释它的行为(新会话 → 干活 → 打印答案 → 退出) - [ ] 知道 headless 的工作区是运行命令时的当前目录,且每次会开一条持久化会话($DSH_HOME/sessions 按工作区存;Web UI 会话列表里 headless 会话归在未分组)
- [ ] 能说出 headless 适合的场景(批量、CI、定时、仓库分析)和官方边界(一次调用只跑一个任务,无交互)
- [ ] 会用
dsh --dump-config查看配置树,知道它排障的用途 - [ ] 能判断一个场景该用 web 还是 headless
