DeepSeek Harness:PTC 模式是如何用 TypeScript 编排工具调用的
PTC 到底是什么
DeepSeek Harness 的 PTC 模式,官方英文页面称为 Code Mode。PTC 通常被解释为 Programmatic Tool Calling,意思是“通过程序调用工具”。
它不是让模型帮你写一个需要保存的 TypeScript 文件,而是让模型临时生成一段 TypeScript 程序,用这段程序编排多个工具调用,然后由 Harness 执行它。
可以把它概括成一句话:
普通模式是“模型调用工具”;PTC 模式是“模型写一个小程序,而小程序调用工具”。
模型仍然负责理解需求、规划步骤和生成代码;TypeScript 则负责循环、分支、并发、错误捕获以及中间数据处理。
四种 mode 分别做什么
截图中的四个 mode,本质上是四套不同的 Agent preset。它们不是四个不同的模型,而是决定当前 Agent 能看到哪些工具、怎样调用工具,以及是否可以检查和改造运行时。
| Mode | 核心能力 | 适合场景 |
|---|---|---|
| Standard mode | 完整工具集,直接调用工具 | 日常编码和一般 Agent 任务 |
| PTC mode | 通过 TypeScript 程序编排工具 | 批量、重复和多步骤任务 |
| Minimal mode | 持久 Shell 加文件编辑 | 简单编码、基准测试和最小环境 |
| Creator mode | 检查运行时并创建自定义 preset | 开发和实验 Agent 配置 |
Standard mode:功能完整的默认工作模式
Standard mode 可以看作普通的全功能编码 Agent。它通常直接向模型提供文件编辑、Shell、文件搜索、网页搜索、Skills、计划、目标、子 Agent 和工作流等能力。
例如用户说:
查看项目结构,修改登录页面,然后运行测试。
Standard mode 会让模型按步骤直接调用搜索、编辑和 Shell 工具:
模型 → 搜索文件 |
它的优点是直观、可观察,每一次工具调用都清楚地展示出来。读取少量文件、修改一个功能或执行几个明确步骤时,Standard mode 往往是最容易理解和排错的选择。
PTC mode:让程序编排工具
PTC mode 就是本文重点讨论的模式。它把工具调用入口收敛为
run_code,然后向模型提供一个 TypeScript
SDK。模型可以在一段程序里循环调用工具、并行执行独立任务、捕获错误并整理结果。
例如批量检查 50 个文件时,模型可以生成一个程序,在程序内部完成读取和过滤,最后只返回问题列表,而不必把每个文件的完整内容都逐次送回模型。
因此,PTC mode 更像是:
模型负责设计程序 |
它尤其适合批处理和长工作流,但如果只是调用一个工具,使用 PTC 的编排优势就不明显了。PTC 的详细机制见下文。
Minimal mode:最小化的编码 Agent
Minimal mode 只保留两类核心能力:持久 Bash
和文件编辑器。官方页面将它描述为一个使用 persistent bash 与
str_replace_editor 的双工具编码 Agent。
它不是功能更强的模式,而是刻意减少能力后的最小运行环境。这样做有两个价值:
- 工具越少,模型面对的选择越少,行为更容易分析。
- 不同模型可以在相近的工具条件下进行编码能力测试。
可以把它理解成一个非常精简的工作台:模型通过 Shell 查看和操作环境,通过编辑器修改文件,没有完整模式中的搜索、计划、子 Agent 和工作流等额外能力。
适合的场景包括:
- 测试模型最基本的读文件、改文件和运行命令能力
- 调试一个 Agent 是否依赖某个高级工具
- 在最小权限和最小工具集下运行简单编码任务
Creator mode:创建和实验自定义 Agent
Creator mode 面向的是 Harness 开发者,而不是普通使用者。它包含 Standard mode 的完整能力,并额外提供运行时检查、Cordis 插件实验和 preset 创作指导。
这里的 preset,可以理解为一套 Agent 配置:它规定使用哪些插件、提供哪些工具、采用哪一种工具呈现方式,以及如何组织会话、工作流和运行策略。
Creator mode 的典型流程是:
- 检查当前 Harness 已加载的运行时和插件。
- 在内存中试验某个 Cordis 插件。
- 组合需要的模型、工具、Skills 和执行能力。
- 创建一个新的 Agent preset。
- 用新的 preset 运行和验证任务。
所以,Creator mode 不是“回答质量更高的模式”,而是“用来制造其他模式的开发模式”。如果只是想让 Agent 帮你写代码,通常不需要选它;如果想研究插件组合或定制自己的 Agent,它才是合适的入口。
如何选择
可以用下面的简单规则:
日常写代码、改项目 → Standard mode |
四种 mode 也可以按“抽象程度”来理解:
Minimal 只给模型最基本的工具 |
这四者不是简单的“低级到高级”关系。PTC 并不一定比 Standard 更强,Minimal 也不只是 Standard 的残缺版本;它们针对的是不同的控制方式、调试需求和使用场景。
Cordis 是什么
Cordis 是 DeepSeek Harness 底层使用的 TypeScript 插件框架。可以把它理解为 Harness 的“插件运行时”或“操作系统内核”。
DeepSeek Harness 负责提供 Agent 能力,而 Cordis 负责把这些能力加载、连接、管理和卸载起来。模型、工具、文件访问、Agent loop 等能力,都可以作为插件挂载到同一个共享上下文中。
可以用下面的关系来理解:
Cordis = 插件运行时 / 内核 |
这也是 DeepSeek Harness 所说的“一切皆插件”:模型、工具、Skills、会话、沙箱、存储、循环、调度器和 UI,都可以被替换或重新组合,而不必把逻辑硬编码在 Harness 核心里。
Context:插件共享的上下文
Cordis 用 Context
作为插件之间共享服务的容器。概念上,运行中的 Harness
可能通过上下文暴露这些服务:
ctx.llm // 模型服务 |
插件不需要直接导入另一个插件的具体实现,而是通过 ctx
获取它需要的服务。这样可以替换服务的实现,同时保持插件接口不变。
Plugin:独立的功能模块
一个最简单的 Cordis 插件可以这样写:
export const name = "hello" |
然后在配置文件中加载它:
- name: "./hello.ts" |
Cordis 加载配置后,会调用插件的 apply(ctx)
函数。插件可以在这里注册服务、监听事件或增加工具。
Service:插件提供的服务
服务是插件对外提供的长期能力,例如
ctx.tools、ctx.llm 和
ctx.sessions。插件可以通过 inject
声明依赖:
const plugin = Object.assign( |
这表示该插件需要 logger 服务,Cordis
会等依赖准备完成后再启动它。因此,插件的启动顺序由依赖关系决定,而不是简单依赖配置文件中的排列顺序。
Event:插件之间的通信
插件还可以通过事件进行通信,而不需要互相直接调用:
export function apply(ctx) { |
当其他插件触发 app/ready 时,这个监听器就会执行:
ctx.emit("app/ready") |
这种方式可以降低插件之间的耦合,让多个独立能力通过事件协作。
Lifecycle:插件的生命周期
Cordis 不只是负责加载插件,也负责管理插件的生命周期:
加载 → 启动 → 运行 → 停止 → 清理副作用 |
例如,一个插件可能注册事件监听器、定时器或上下文修改。当插件被卸载时,相关资源也应该被清理,避免监听器和定时任务继续残留。
“空间组合”和“时间组合”
Cordis 背后的设计论文使用了 spatiotemporal composability(时空可组合性)这个概念。这里的“空间”和“时间”指的是软件组件的两个维度。
空间可组合性,指插件可以声明彼此的依赖关系:
Agent Loop |
Cordis 根据这些依赖决定哪些插件应该激活,以及何时激活。
时间可组合性,指插件被卸载后,它产生的副作用也应当能够被撤销:
加载插件 |
这让插件可以动态加入和退出,而不是只能在程序启动时一次性加载。
Cordis 与 DeepSeek Harness 的关系
DeepSeek 模型 |
更简单地说:
Harness 定义 Agent 能做什么;Cordis 定义这些能力如何被装配和运行。
PTC
模式也建立在这个插件化思路上:run_code、工具注册表、代码运行时和工具调度器,都是可以被
Cordis 管理和组合的能力模块。
普通工具调用的问题
假设用户提出这样的请求:
找出项目里所有 TODO,读取相关文件,然后给我一个汇总。
在普通模式中,过程大致是:
模型 → glob("**/*.ts") |
每次调用工具,都需要经过一次“模型决定 → 工具执行 → 结果回到模型”的循环。文件数量一多,模型往返次数和上下文中的中间结果都会增加。
PTC 如何改变调用流程
在 PTC 模式下,模型主要看到一个特殊工具:
run_code |
Harness 同时会在系统提示词中提供一份 TypeScript SDK 声明,告诉模型当前有哪些工具、每个工具需要什么参数以及会返回什么类型的数据。
于是模型可能生成这样的程序:
const files = await tools.glob({ |
模型实际发给 Harness 的,是一次外层调用:
{ |
Harness 收到这段代码后,会启动代码运行时,执行
TypeScript,并把程序里的
tools.glob()、tools.read_file()
映射为真正的工具调用。最后,只有程序的 return 值和
console.log() 输出会作为结果回到模型。
tools.xxx() 是什么
tools.xxx() 不是普通的 JavaScript 函数,而是 Harness
注入给程序的异步工具绑定。例如:
await tools.bash({ |
await tools.read_file({ |
概念上,Harness 会为模型提供类似下面的类型声明:
declare const tools: { |
这让模型能够按照工具的参数和返回类型来写程序。需要注意的是:这些声明只在
run_code 程序内部有效。在 PTC 模式下,模型不能直接调用
read_file,只能在 run_code 中间接调用它。
一次 PTC 调用的执行过程
一次完整的 PTC 调用可以拆成下面几步:
- 模型理解用户目标,决定需要哪些工具。
- 模型生成一段 TypeScript 程序。
- Harness 把程序交给代码运行时执行。
- 程序通过
tools.xxx(args)调用实际工具。 - 每个内部工具调用仍会经过权限、策略、执行和审计流程。
- 程序在本地过滤、合并和压缩中间结果。
return或console.log()的内容回到模型。
因此,PTC 不是绕过工具系统,而是增加了一层“程序化编排”。
为什么 PTC 有用
减少模型往返
普通模式可能是:
调用 A → 回模型 |
PTC 可以变成:
模型生成程序 → 一次执行 A、B、C、D → 回模型 |
这对批量读取文件、批量查询 API、检查多个目录或处理多条数据尤其有用。
在工具内部处理数据
普通模式下,读取 100 个文件后,100 份内容可能都要进入模型上下文;PTC 可以先在程序中筛选:
const files = await tools.glob({ |
文件全文只在这段程序内部流转,模型最终只看到
matches。这能减少上下文噪声和不必要的 token 消耗。
支持循环、分支和错误处理
模型不必每遇到一个失败就重新规划一次。程序可以捕获错误并继续处理:
const urls = [ |
可以并行调用
彼此独立的只读任务可以使用 Promise.all():
const [a, b, c] = await Promise.all([ |
但写文件、提交代码和删除文件等有副作用的操作通常应该串行执行。Harness
会依据工具的并发安全属性进行调度;当前配置中的
maxParallelSubCalls 默认值为 10,设为 1
可以恢复严格串行执行。
PTC 与普通工具调用的区别
| 方面 | 普通模式 | PTC 模式 |
|---|---|---|
| 模型直接看到 | 每一个工具 | 主要是 run_code |
| 调用方式 | 一次调用一个工具 | 一段程序调用多个工具 |
| 控制逻辑 | 每轮由模型决定 | 由生成的程序控制 |
| 中间结果 | 经常回到模型上下文 | 默认留在程序内部 |
| 循环和分支 | 需要多轮模型决策 | 直接写进程序 |
| 并行处理 | 逐步处理或由运行时决定 | 可以用 Promise.all() 表达 |
| 适合场景 | 少量、简单调用 | 批量、重复、多步骤工作流 |
PTC 不是让 TypeScript 取代模型
两者分工不同:
模型负责:
- 理解用户目标
- 设计执行策略
- 生成 TypeScript 程序
- 解释最终结果
- 根据结果决定是否继续
TypeScript 负责:
- 循环和条件判断
- 批量调用工具
- 并行处理独立任务
- 捕获和处理错误
- 过滤、聚合和压缩数据
换句话说,模型负责制定一个短程序,Harness 负责让这个短程序可靠地调用工具。
需要注意的限制
每次运行都是新的状态
一次 run_code 结束后,程序里的变量就会消失。PTC
不是一个默认持久化的
REPL,不能假设上一次运行定义的变量在下一次运行中仍然存在。
只有输出会回到模型
程序内部读取到的数据,如果没有通过 return 或
console.log()
输出,模型就看不到。因此程序应该主动提取摘要,而不是无差别地返回全部内容。
TypeScript 不是完整项目编译
PTC 执行的是面向工具编排的短程序,类型主要用于帮助模型生成正确代码。不能默认它可以像项目源码一样导入任意 npm 包、访问完整的构建环境或维护长期进程。
代码执行能力不等于绝对安全
PTC 可以间接调用 Bash 等高权限工具,因此安全边界仍然取决于 Harness 的沙箱、权限策略和工具配置。它不应该被理解为一个天然安全的代码沙箱。
什么时候该用 PTC
适合使用 PTC 的任务包括:
- 扫描大量文件并汇总结果
- 批量调用多个只读 API
- 对多条数据执行相同处理
- 需要循环、分支或错误恢复的工作流
- 中间数据很多,但最终只需要一个摘要
如果只是读取一个文件、执行一个命令或修改一个位置,普通工具调用往往更直观。PTC 的价值主要在于:把复杂的多轮工具交互,压缩成一次模型生成和一次程序执行。
总结
DeepSeek Harness 的 PTC 模式,本质上是一个“程序化工具调用”层:
用户目标 |
它的核心优势不是“会写 TypeScript”,而是让模型拥有了一种更紧凑、更可组合的工具使用方式。对于复杂 Agent 来说,模型负责决策,程序负责执行细节,这种分工可以显著改善批量任务和长工作流的效率。
参考资料:
- DeepSeek Harness 官方介绍
- DeepSeek Harness 工具系统与 PTC 文档
- DeepSeek Harness Code Runtime 文档
- Cordis 官方 README
- Cordis 入门教程
- Cordis:A Programming Paradigm for Spatiotemporal Composability
- DeepSeek Harness 官方源码仓库