ByteNoteByteNote
DeepSeek Harness 持久终端子系统解析
字

字节笔记本

2026年10月6日 · 约 6 分钟读完

DeepSeek Harness 持久终端子系统解析

API中转
¥120

DeepSeek Harness 是 DeepSeek 开源的智能体框架,简称 dsh,主打一切皆插件,底层由 Cordis 框架驱动,采用 MIT 协议,目前处于开发者预览阶段。对 agent 来说,跑一条一次性命令并不难,难的是让终端状态留得住:开发服务器要挂着随时看日志,REPL 会话要能接着追问,交互式程序的输出要能分段读回。dsh 的终端子系统为此提供了持久 PTY 会话,本文基于它的子系统文档,拆解这套设计的核心词汇与取舍。

DeepSeek Harness 持久 PTY 会话架构

会话标识与属主授权

持久终端的第一件事,是给会话一个可靠的身份。TerminalSessionId 由服务统一铸造,是带有品牌标记的 id,调用方无法自己伪造一个来冒充。会话可以带一个可选名称,但它只是属主本地的显示元数据,授权时不会被当作凭证。真正决定访问权的是属主本身:每次操作比较的都是拥有该会话的那个确切 Agent 对象,而不是名称,也不是猜测出来的 id。换句话说,知道名字不等于拿到权限,只有会话的真正属主才能向它发送输入、读取滚动缓冲或关闭它。

等待原因与会话状态是两回事

这套设计里最容易混淆的是两组信号。TerminalWaitReason 解释一次交互式发送为什么把控制权还给调用方,它有四个取值:

ts
type TerminalWaitReason = 'stdin_read' | 'inferred_idle' | 'timeout' | 'session_exit'

前台进程在等输入、静默到可以推断空闲、等待超时、顶层 shell 退出,都会让一次发送提前返回。而 TerminalSessionStatus 描述的是顶层 PTY 进程本身的状态,只有运行中和已退出两种,退出时附带退出码与信号:

ts
type TerminalSessionStatus =
  | { kind: 'running' }
  | { kind: 'exited'; exitCode: number | null; signal: NodeJS.Signals | null }

两组信号刻意不挂钩:一次发送可能因为静默或超时返回,但顶层 shell 仍然活着;反过来,session_exit 说明的是这个 shell 已退出,而不是某个任意的前台子进程退出了。把等待原因和会话状态分开,模型就不会把一次普通的超时误判成终端已经挂掉。

可替换后端与就绪发布

会话由后端启动。TerminalBackend 是某一类 PTY 会话的可替换提供方,用稳定的 type 字符串标识,通过 ctx.terminals.registerBackend 注册进服务。TerminalSessionService 只在后端初始化成功之后才把会话发布出来,发布之前外部看不到这个会话;发布之后,服务负责 id 授权与清理。启动失败有一条讲究的路径:如果无法清理已经启动的部分资源,后端会以 TerminalBackendCleanupError 拒绝启动。这样一来,资源释放流程既能保留清理失败这个事实,也不会用它覆盖调用方原本的取消原因。后端会话拥有终端状态,并负责让捕获的进程树完全停稳。

一次只允许一个活动发送

一次交互式发送的生命周期

活跃会话在同一时刻只接受一个活动发送。发送操作向前台调用方提供最终结果,同时向通用后台任务提供读取后即推进的输出游标:每次 readOutput 都消费自上次调用以来新产生的输出,不会重复。操作以 done 这个 Promise 收场,就绪、超时、取消或顶层进程退出都会让它落定;落定前调用 cancel 可以请求 SIGINT,落定之后调用只会得到 false。返回的 TerminalSendResult 带四个字段:截至落定时的有界渲染增量 viewport、等待原因 waitReason、当时观察到的会话状态 sessionStatus,以及标记输出是否被丢弃的 truncated。想翻历史则走另一条路:read 按页读取会话保留的滚动缓冲,支持按最新相对偏移与行数的有界分页,与发送操作互不干扰。

归属、清理与持久性

清理逻辑挂在确切的属主作用域上:一项等待完成的清理会附加到拥有者的生命周期里,其他属主对该会话的操作会被直接拒绝,后端或工具插件重载期间会话继续保持存活。PTY 的原始字节与状态始终局限在进程内,框架并不重复记录 PTY 会话事件;真正持久化的是模型输入与有界返回输出,它们沿着既有的 tool/call、tool/result 与任务结果路径留档。对外 API 的面不大,一共九个方法:注册后端 registerBackend、列出后端 listBackends、创建会话 spawn、查询属主活动的 hasOwnerActivity、发送 startSend、读缓冲 read、发信号 signal、关闭会话 kill,以及按属主列出会话快照的 list。其中 signal 只对验证过的前台进程组生效,kill 默认以模型请求作为清理原因。

上手体验

终端子系统很能体现 dsh 的工程取向:身份由服务铸造,权限认属主而不认名字,连失败路径也要保留完整的因果。想实际感受,装好 Node.js 后执行 npx @deepseek-ai/dsh web 即可启动本地 Web UI;仓库在 GitHub 上以 MIT 协议开源。需要注意的是它仍在快速迭代,官方明确提示后续会有破坏性兼容变更,生产环境使用前请留意版本公告。

相关文章

分享: