06 · 附录:资源、HOWTO 与结语¶
Python SDK 驱动 harness、添加插件与贡献、常见 HOWTO 与参考表。
9. 用 Python SDK 驱动 harness¶
9.1 SDK 与 dsh 的关系和区别¶
| 关系 | Python SDK 是 dsh 的进程级客户端:启动内置运行时(单文件 dsh-jsonrpc-agent)子进程,经 stdio 换行分隔 JSON-RPC 驱动;能力边界由组合里挂了哪些插件决定,不重新实现 harness 内核。 |
|---|---|
| 方面 | dsh(产品本体) |
| --- | --- |
| 形态 | TypeScript monorepo:CLI + Web + 插件生态 + 组合系统,从源码构建运行 |
| 扮演角色 | 运行时产品本身(消费者直接面向用户) |
| 与运行时的连接 | 进程内直接调用插件 |
| 配置方式 | profile / bundle / patch 组合树(cordis.yml) |
| 环境变量 | 继承 DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL / DSH_HOME 等 |
| 平台 | macOS / Linux 为主(Windows 部分支持) |
9.2 上手步骤¶
- 安装(Python 3.10+):
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk
会自动装上同版本的 deepseek-harness-runtime-bin 平台 wheel,无需手动指定可执行文件。
- 设置凭据:
DEEPSEEK_API_KEY必须;若走 OpenAI 兼容代理再设DEEPSEEK_BASE_URL。
export DEEPSEEK_API_KEY=sk-your-key-here
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
- 最小调用:
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness as harness:
result = harness.run("Say hi.")
print(result.final_response)
上下文管理器会延迟启动运行时子进程并在多次调用间复用。
- 带 workspace / 会话持久化 / 自定义组合:
from pathlib import Path
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-flash",
max_tokens=49_152,
cwd=str(Path("/absolute/path/to/workspace").resolve),
session_root=str(Path("/absolute/path/to/sessions").resolve),
cordis=str(Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve),
) as harness:
result = harness.run("Inspect the repo and fix failing tests.", session_id="example-001")
print(result.final_response)
-
理解返回值:
RunResult含session_id、final_response(该区间根会话最后提交的助手文本)、finish_reason(最后一个turn/end的kind,如completed/max-tokens/error)、events(根会话事件)、notifications(根会话 + 全部已知后代)、session_root。 -
session id 复用语义:复用同一 harness + 同一
session_id会保留该会话的 Bash 进程(工作目录、已导出的变量、shell 函数)。独立任务用新 id;只有需要延续同一段持久化会话才复用。
安全边界
内置组合(sdk-runtime/.../cordis.yml)不含 permission 层——bash 与编辑器可改任何路径,且上下文压缩关闭。行为等价于 danger-full-access(学习归纳:该名仅见于上游 app-boot.spec.ts 的 .env 拒绝名单 DSH_PERMISSION_MODE=danger-full-access,非正式组合名)。只应在可丢弃的 checkout 或容器内运行。仓库内置示例:python examples/jsonrpc-agent/minimal.py --workspace <dir> --session-root <dir> --session-id example-001 "task"。
10. 添加插件与贡献¶
所有扩展都走"在旁边挂插件",不改核心。两条路径:入门路径是往 preset 里挂一个最小插件(5 分钟验证);正式贡献是按仓库清单新增一个 workspace 包(含门禁、测试、文档约定)。
10.1 最小工具插件(入门路径)¶
最小工具就是一个有 name/inject/Config/apply 的 Cordis 插件 + ctx.tools.register(defineTool(...))。schema 自动进入 system-prompt 组装,卸载插件即注销工具:
import { readFile } from 'node:fs/promises'
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'my-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'read_file',
description: 'Read a file from disk.', // 模型看到的描述
parameters: {
path: { type: 'string', required: true, description: 'Absolute path' },
limit: { type: 'number' }, // 默认可选
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args, exec) {
// args 由 schema 推导出类型 { path: string; limit?: number }
return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
},
}))
}
execute 约定要点:参数在进入 execute 前已按 ParameterSchemaSpec 校验;exec 携带不可变身份与 token,exec.signal 是唯一可替换的运行期字段(用于超时);返回单一 canonical JSON 值;抛错或返回非法值即视为 isError。完整约定见 docs/cookbook/adding-a-tool.md。
UI 渲染是设计的一部分:用 output.presentCall/presentResult 返回 card-tagged render intent(generic / terminal / diff / search / web),且必须是 args(+ result)的纯函数——回放也要跑。见 11.3。
10.2 挂载到 preset 验证¶
在 $DSH_HOME/profiles/<name>/cordis.patch.yml(或 --patch <path> overlay)里写一个 patch 列表。文件是顶层 YAML 数组:按 id 定位的 patch 会整段替换目标行的 config(未改字段也要重述),insert 则插入新条目;!!js 表达式在挂载时插值。裸插件 name 从 profile 目录向上按 Node 模块规则解析,回退到 $DSH_HOME/profiles/node_modules。
# cordis.patch.yml —— 顶层数组,insert 一个插件行
- insert:
- id: my-tool
name: my-tool # 你的插件(安装进 profile 的 node_modules)
把插件安装进 profile 用 dsh plugin --profile <name> add <spec>(转发给 pnpm,profile 缺失时先初始化;相对路径 spec 锚定到调用目录,因此从插件 checkout 里执行 add . 装的就是该 checkout)。若是带 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } 声明的组合包,安装后会自动加入 dsh.profile.bundles 层栈。
验收
pnpm dsh --profile <name> --dump-config 能看到该行(注释标注来源文件与 overlay);跑一次任务观察工具被调用;对行为变化按 docs/testing.md 补测试。
10.3 正式贡献:新增 workspace 包¶
完整 checklist 见 docs/cookbook/adding-a-package.md(以 bash 与 adapter 包为模板校验过)。流程:
- 建包:
packages/<group>/<pkg>/下四个文件——package.json(拷贝packages/core/tools,改 name/description/deps)、tsconfig.json(extends../../../tsconfig.base.json,rootDir src,references 含vendor/cosmokit+vendor/cordis(用 Config 时加schemastery)+ 每个 dsh 依赖)、src/index.ts(service 默认导出或name/inject/apply/Config插件)、README.md(含 gated Model Experience 段 + Known Limitations 段)。 - 注册进根配置:新包组加
tsconfig.base.json的@deepseek-ai/dsh-*wildcard;Host/Client 包分别加tsconfig.host.json/tsconfig.client.json的references(一个包只属于一个 aggregate);有额外 entrypoint 才动knip.json。workspaces、publint、tsdown、oxlint、constraints 全部由 glob 自动覆盖,不用改。 - 决定拓扑:可替换能力按 Service Definition / Provider / Consumer 拆包(shell 三人组是模板);单一用途插件一个包。命名用稳定职责,role 后缀有词典(Controller/Store/Registry/Policy/Provider/Backend/…),
local仅在"约定包含同宿主执行"时用。 - 写 README:包自身 API/事件/扩展点在前,最后以固定四段收尾:
## Model Experience→### Request context and condition(What the model sees / Token effect / KV Cache effect)→## Known Limitations and Deferred Work。 - 验证:
pnpm install
pnpm run doc-sync
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run build && pnpm run hygiene
再按 docs/testing.md 补行为测试与快照;非平凡变更必须同 PR 附 Agent Note。
其它插件形态参考
docs/cookbook/extension-cookbook.md 有完整 feature → mechanism 映射:hook 插件(ctx.on('tools/pre-execute', ...) 返回 typed decision 的瀑布)、UI 插件(监听 session/event + agent.followup)、外部协议驱动(把 wire peer 映射到 ctx.agents)、LLM 适配器(LlmAdapter 子类 + ctx.llm.registerAdapter(['my-provider'], ...))。
11. 其他常用 HOWTO¶
11.1 读懂组合树与排查启动问题¶
dsh --profile <name> --dump-default-config:只打印 bundle 层;--dump-config额外含 profilecordis.patch.yml、home 级cordis.patch.yml、--patchoverlays。两者都注释每行来源文件与修改过它的 overlay,!!js表达式保持未求值。- 裸插件名解析失败、patch 目标找不到、schema 校验失败、启动失败都会报错并以非零退出——不会静默跳过。解析顺序:dsh 安装目录 → profile 目录 →
$DSH_HOME/profiles/node_modules维护的后备符号链接。 - 环境变量:
DSH_HOME(默认~/.dsh)、DEEPSEEK_API_KEY、可选DEEPSEEK_BASE_URL。
11.2 无 key 联调与本地回放¶
pnpm mock:llm:本地 mock LLM 服务器,无需 API key 跑通工具调用回合。pnpm run test:snapshot:keyless ACP/headless 回放,比对期望输出;-t <name>过滤;test:snapshot:record重录(需 key)。- 会话即 JSONL 日志:模型可见历史由
deriveMessages投影,回放 =sessions.create(id, { seed })。任何模型可见输入都能从日志重建。
11.3 给工具配 UI 卡片(render intent)¶
presentCall(args) 返回待处理卡片,presentResult(args, { content, isError, meta }) 返回完成卡片;二者必须是纯函数(直播与回放都跑,禁止 I/O/读会话状态/时钟)。卡片类型:
| card | 用途 | 参考工具 |
|---|---|---|
generic |
默认;可设 locations: [{path, line?}] 让编辑器跟随 |
大多数工具 |
terminal |
调用本身就是一条 shell 命令 | tool-bash |
diff |
创建/修改文件,内联 diff | tool-fs write/edit |
search |
按文件分组的发现结果(仅 result 视图) | tool-fs-search grep/glob |
web |
完成的网页检索,按 kind: search\|fetch 判别 |
tool-web |
UI 专用格式(fenced console、diff、相对路径)留在卡片投影里,绝不进 canonical 值或 Native 内容;持久化的 replayable 卡片数据走 output.presentationMeta(args, value),经 tool/result 的 meta 回放复原。
11.4 常用参考¶
- 事件:
docs/event-producer-consumer.md+ 每包组 subsystems 页;事件 JSDoc 需@mode与 payload@param。 - 测试:
docs/testing.md(门禁test:coverage每文件 100%;模型/用户可见行为要 keyless 快照)。 - Cordis 入门:
docs/cordis-primer.md+docs/cordis-tutorial/动手教程。 - 文档规范:
docs/AGENTS.md;贡献任何非平凡变更需 Agent Note(.agents/notes/),已归档的冻结。 - 协议族:SDK wire 协议见
packages/sdk/protocol;ACP 自动化协议见packages/acp/acp/README.md;Claude Code/Codex hooks 桥接见packages/hooks。
12. 结语¶
上游 dsh 把模型、工具、沙箱与子 agent 都做成可替换插件,会话事实只追加在事件日志里;外层用 profile 与 bundle 组合出 Web、headless、SDK 等形态。用 Python 复现时,优先亲手实现三件事:日志作唯一数据源、可逆副作用与 waterfall 短路、扩展口三角色(Definition / Provider / Consumer)。做完 手册 01–05 与 05 路线图 §7.3,再读上游源码会快很多。