Skip to content

线路协议是让一个进程能够驱动另一个进程中的 DeepSeek Harness 运行时的契约。本页专注协议本身——帧格式、具名消息类型,以及它们与会话和 Agent 主循环的关系。对话两端分别在 SDK 客户端SDK 服务端 中讲解;本页引用 packages/sdk/protocol/src

@deepseek-ai/dsh-sdk-protocolpackages/sdk/protocol/package.json)是一个纯库:它不注册插件、没有 Config、也没有任何注册动作,只提供传输层类与具名线路类型,外加导出的 JsonRpcResponseError

字段
name@deepseek-ai/dsh-sdk-protocol
version
角色SDK 运行时的共享线路协议
产物lib/index.jslib/types/**/*.d.ts
peer 依赖dsh-invariantsdsh-llmdsh-sessiondsh-subagentcordis(仅类型)
模块src/index.tstransport.tstypes.ts 再导出

设计目标

协议回答一个特定的产品问题:当运行时没有终端 UI、也没有审批界面时,外部调用者(Python 或 TypeScript 进程)如何操作一个完整的 harness——而不只是调用单个模型?三个决定随之而来。

  1. 基于 stdio 的新行分隔 JSON-RPC 2.0。子运行时的 stdout 被保留给帧;诊断信息属于 stderr。通道就是进程自身的管道,因此没有端口、没有端口探测、也没有 HTTP。
  2. 持久事件流,而非提示词结果。服务端不会用一个 assistant 消息来回答提示词。相反,它把每一条会话日志事件与整个 agent 的状态转换作为 通知(notification) 推送出去,客户端从这条流中自行拼装一轮对话。
  3. 除了 initialize 之外没有中场状态机initialize 是一次握手,在服务端整个生命周期内固定 cwd/provider/model;此后都是无状态的 session/prompt 请求。

传输层是 JsonRpcLineTransportpackages/sdk/protocol/src/transport.ts)。JsonRpcTransportPeer 是出站接口(只有 request/notify),服务端类与客户端都以它作为类型基准。

帧格式规则

每条 JSON-RPC 2.0 消息是一个紧凑 JSON 对象,用 JSON.stringify 序列化并以 \n 结尾。行通过 UTF-8 StringDecoder 读取、trim、逐行排空。非法 JSON 行会被静默忽略,不产生错误反压。

text
出站(服务端 -> 客户端):{"jsonrpc":"2.0","method":"session.event","params":{...}}\n
                             {"jsonrpc":"2.0","method":"session.status","params":{...}}\n
入站(客户端 -> 服务端):{"jsonrpc":"2.0","id":"req_…","method":"session/prompt","params":{...}}\n
                             {"jsonrpc":"2.0","id":"req_…","result":{"messageId":"…"}}\n

帧分类(来自 handleLine):

帧形态含义
同时带有 id method请求——分发给请求处理器
只有 id响应——解析对应的 pending 请求
只有 method通知——分发给通知处理器

请求 id 形如 req_<uuid-hex>randomUUID().replaceAll('-', ''))。request(method, params, signal) 支持可选的 AbortSignal:中止会移除 pending 条目(为永远不回应的响应保留陈旧状态),并以 signal 的 reason 拒绝。flush() 通过写入一个空屏障来等待先前的写回调。错误响应变成 JsonRpcResponseError,它保留线路上的 code 与可选的 data

错误场景线路码说明
未安装请求处理器-32601method not found
处理器被拒绝-32603internal error携带 error.message
有 pending 请求时 close()pending 请求以 JSON-RPC transport closed 拒绝
非法 JSON 行丢弃该行
ts
// packages/sdk/protocol/src/transport.ts(节选)
export class JsonRpcLineTransport implements JsonRpcTransportPeer {
  // ...
  request(method: string, params: object, signal?: AbortSignal): Promise<unknown> {
    const id = `req_${randomUUID().replaceAll('-', '')}`
    const message = { jsonrpc: '2.0', id, method, params }
    // 注册 pending 条目,写入帧,返回 promise
  }
  notify(method: string, params?: object): void {
    this.write(params === undefined
      ? { jsonrpc: '2.0', method }
      : { jsonrpc: '2.0', method, params })
  }
}

线路类型

packages/sdk/protocol/src/types.ts 命名了每种载荷。HarnessSdkRequestMap 索引客户端→服务端请求;HarnessSdkNotificationMap 索引服务端→客户端通知。

方向方法请求类型 → 结果类型
客户端→服务端initializeInitializeParamsInitializeResult
客户端→服务端session/promptSessionPromptParamsSessionPromptResult
客户端→服务端shutdown无参数 → {}
服务端→客户端session.eventSessionEventNotification
服务端→客户端session.statusSessionStatusNotification
服务端→客户端subagent.startedSubagentStartedNotification
服务端→客户端subagent.finishedSubagentFinishedNotification

initialize

进程级握手。cwd 会被记录到每个 SDK 创建的会话的 header 上;providermodel 成为每个 SDK 创建的 agent 所运行的路线。maxTokens 是可选的、必须为正的安全整数输出 token 上限,由 SDK 创建的 agent 及其进程内后代继承;非法值会拒绝握手,省略时则让适配器按精确模型或 provider 使用默认值。

ts
// packages/sdk/protocol/src/types.ts(节选)
export interface InitializeParams {
  cwd: string
  provider: string
  model: string
  maxTokens?: number
}
export interface InitializeResult {
  serverInfo: { name: string; version: string }
}

serverInfo.name 是线上稳定的 deepseek-harness-sdk-runtimeversion 是普通字符串。

session/prompt

某个 SDK 会话上的一轮用户对话。sessionId 是 SDK 侧 id;未知 id 会在服务端懒创建“agent+会话”对。contentBlocks 会被原样作为用户消息发送。结果是持久的入队回执,而非对话结果:

ts
export interface SessionPromptParams {
  sessionId: string
  contentBlocks: ContentBlock[]
}
export interface SessionPromptResult {
  messageId: string   // 入队的 UserMessage 的身份
}

messageId 只标识入队的 UserMessage,并不标识后面的 assistant 消息、一轮对话的结束或最终答案。因此客户端需要把开放的 session.event 流与整个 agent 的 session.status 结合起来,由自己拥有活动时间区间。

通知

四种服务端→客户端通知。载荷类型刻意依赖 SessionEventdsh-session)、ContentBlockdsh-llm)与 SubagentStopReasondsh-subagent)——协议流式传输的是完整会话日志外壳,因此会话词汇本身也是线上契约的一部分

方法载荷字段语义
session.eventsessionIdevent: SessionEvent运行时中每一个会话(不过滤)的一条持久会话日志事件
session.statussessionIdstatus: 'idle' | 'running'整个 agent 的生命周期状态转换
subagent.startedparentSessionIdchildSessionId运行时内创建了一个子会话
subagent.finishedprovideragentIdparentSessionIdchildSessionIdstatusstopReasonlastAssistantMessage?一次进程内子 agent 运行结束
ts
// packages/sdk/protocol/src/types.ts(节选)
export interface SubagentFinishedNotification {
  provider: string
  agentId: string            // 本地运行时等于 childSessionId
  parentSessionId: string
  childSessionId: string
  status: SdkRunStatus       // 'ok' | 'error'
  stopReason: SubagentStopReason
  lastAssistantMessage?: ContentBlock[]  // 子 agent 未产生任何输出时缺省
}

SdkRunStatus'ok' | 'error'lastAssistantMessage 是子 agent 最后一条非空的 assistant 消息,若不存在这样的消息则是其累积的 assistant 文本;只有子 agent 两者都没产生时该字段才缺省。subagent.finished 只上报进程内的子运行——远程运行不会出现在这条线上。

它如何映射到 Agent 主循环

协议是对 agent 主循环自身事件的一个前置包装。在服务端,HarnessSdkJsonRpcServer 订阅三个 Cordis 生命周期事件,并把每个重新投射为一条通知:

Cordis 事件(ctx.on发出的通知
session/eventsession.event
agent/statussession.status
session/created(带 header.parentSessionsubagent.started
subagent/end(仅当 info.localsubagent.finished

没有“本轮对话以此文本结束”的推送通道。最接近的持久事实是 turn/end 会话事件,由客户端从 session.event 流中读出。这正是 SDK README 反复强调“提示词级别结果被刻意省略”的原因:活动归属属于观察者,而非线路本身。

版本管理

不存在协议版本协商。握手只携带 serverInfo.version,客户端并不校验它。这是明确的预发布立场,跨版本不提供兼容性承诺。后果是:SDK 客户端与运行时必须来自同一发布通道;把来自某个 revision 的客户端与不同的服务端 revision 混用,超出契约范围。

已知协议缺口

缺口影响变通
没有取消方法客户端无法单独放弃一轮对话关闭运行时进程
没有会话关闭方法SDK 创建的 agent 会一直存活到进程关闭
没有针对提示词的结果messageId 只是收件箱准入凭证客户端自己负责“回执→idle”的收集
服务端→客户端请求传输层支持,但服务端从不发送Python 的响应面为将来审批流程预留
没有协议版本协商握手版本不被校验让客户端与运行时保持同步

可运行示例

这套协议由 examples/jsonrpc-agent/cordis.yml 端到端实践:它在一个无人值守的编码 agent 组合(bash、文件工具、子 agent、todo、JSONL 持久化、compaction)内部挂载 sdk-jsonrpc-server 插件;dsh-jsonrpc-agent bin 的生命周期则在 packages/examples/jsonrpc-demo/src/runner.tsexamples/jsonrpc-agent/tests/snapshots/*/notifications.expected.jsonl 下的快照展示了线路实际承载的 session.event/session.status/subagent.* 形状。

延伸阅读

  • SDK 客户端——讲这套协议的 DeepSeekHarness/HarnessSession/HarnessClient 各层。
  • SDK 服务端——@deepseek-ai/dsh-sdk-jsonrpc-server 如何无头地托管 agent。
  • Typert:类型生成器——同一工程章节中、与本协议无关的类型/schema 生成器。
  • 发行组合:examples/jsonrpc-agent/cordis.yml(服务端行后面到底有哪些插件)。
  • 会话事件与 agent 事件的区分:会话管理
  • 仓库内相对路径:packages/sdk/protocol/src/transport.tspackages/sdk/protocol/src/types.ts