dsh --profile headless "任务" 执行单次会话、输出结果、终止。它在 dsh-base 上叠 dsh-headless,所以没有网页服务端。无效命令、配置错误和启动失败一律非零退出——这正是它能被脚本安全包起来的原因。
被拿去演示的总是那个交互式浏览器 agent,但 harness 真正开始产生价值,是在 headless profile——agent 从「一个你手动开着用的东西」变成「一个你排期跑的东西」的那一刻。
调用方式
dsh --profile headless "总结一下测试套件为什么在挂"它执行一次会话、输出结果、终止。不起服务端、不绑端口、不等输入。
架构上它和交互式 agent 是同一个底座、换了顶层:headless 在 dsh-base 上叠 dsh-headless,而 web
叠的是 dsh-web-app。dsh-base 里的一切——模型适配器、工具、持久化、沙箱与审批策略、凭证、遥测——
两种情况下都在。你换的是套在 agent 外面的壳,不是 agent 本身。
退出码就是契约
这是让 headless 可脚本化的关键性质:无效命令、属于别的模式的选项、配置错误和启动失败,一律非零退出。
这条保证比听上去值钱。自动化 agent 里你怕的不是崩溃,而是「悄悄成功但什么都没干」。一个放错位置的 flag 会让 launcher 非零退出,意味着坏掉的流水线在第一次运行就暴露,而不是第十次。
if ! dsh --profile headless "$JOB"; then
echo "harness 执行失败" >&2
exit 1
fi参数顺序,再说一遍
launcher 先解析自己的 flag,遇到第一个不认识的 token,之后全部算 app 的参数。把 --profile
放在任何给 app 的东西前面:
dsh --profile headless --some-app-flag "任务"在 shell 脚本里这比交互式更要命,因为你会插值变量,而一次空展开就能让那条分界线位移。给变量加引号。
给 CI 单独一个 profile
别让 CI 指向你手动在用的那个 profile。profile 很便宜而且互相隔离,插件又是装进 profile 自己的
node_modules,所以单独一个 profile 白送你一套独立作用域的插件集。
好处是叠加的:
- 受限的插件集:CI 没道理持有这个任务用不到的能力。
- 更严的审批策略:审批和沙箱策略是
dsh-base的行,CI profile 可以把它们 patch 得更紧,完全不动 你交互用的那套。 - 可复现:你本地的 profile 会随着你折腾而漂移;一个
cordis.patch.yml进了版本控制的 CI profile 不会。 - 爆炸半径:搞坏本地 profile 的实验,不会搞坏流水线。
像建其它 profile 一样建好它,然后确认它实际组装出了什么:
dsh --profile ci --dump-configCI 里的凭证
自定义服务商通过 apiKeyEnv 指定的环境变量读 key,这和 CI 注入密钥的方式严丝合缝——在流水线里把密钥
设成环境变量,按名字引用即可。
存在 $DSH_HOME/.credentials.yaml 里的目录内服务商 key 反而更别扭,因为那要求这个文件存在于 runner
上。自动化场景优先走自定义服务商这条路,让凭证从 CI 的密钥库来,永远不落盘。
记住运行中的进程持有它启动时那份环境。在流水线里这通常不成问题——每一步都是全新启动——但也意味着, 在某些 CI 系统里,上一步 export 的密钥在后面的步骤里看不见。
headless 解决不了的事
它也不能替你决定「这个 agent 被允许干什么」。沙箱和审批策略来自 dsh-base 而且可 patch——这是把
双刃剑。一个持有仓库写权限、审批策略又很宽松的自动化 agent,是一项部署决策,不是便利设置。有意识地
决定它,并把编码这项决策的 patch 放进版本控制,让 reviewer 看得见。
常见问题
web profile 和 headless profile 有什么区别?
两者都叠 dsh-base。web 加 dsh-web-app 提供浏览器能力,headless 加 dsh-headless 提供无服务端的单次执行。
headless 模式会检查退出码吗?
会。无效命令、属于别的模式的选项、配置错误和启动失败一律非零退出,所以配错的流水线会大声失败,而不是悄悄成功。
CI 里能用另一套插件吗?
能,而且应该。插件按 profile 装进各自的 node_modules,所以 CI profile 可以持有一套刻意受限的插件集,完全不影响你交互用的 profile。