DeepSeek Harness 架构深读系列
本系列面向想读懂 dsh 源码、写插件做二次开发、或在 Claude Code / Cursor / Codex 之外评估一个"全插件化"开源 harness 的工程师与架构师。它不重复讲"AI 会写代码",而是回答一个具体问题: 一个把…
专栏
面向希望理解 AI Coding Agent 底层架构的开发者。本专栏基于 mindcarver/91ai 的 DeepSeek Harness 系列,覆盖 Cordis 可组合性、插件树与 Profile、运行时核心、能力接缝、执行子系统、源码导读、扩展开发与横向评测。建议按“是什么与首次运行”→“Cordis 与插件组合”→“运行时和能力接缝”→“源码与工程化”顺序阅读。
本系列面向想读懂 dsh 源码、写插件做二次开发、或在 Claude Code / Cursor / Codex 之外评估一个"全插件化"开源 harness 的工程师与架构师。它不重复讲"AI 会写代码",而是回答一个具体问题: 一个把…
不比功能清单,不比模型能力,不比 benchmark 分数。这三样随版本剧烈变化,依赖太多外部因素,写下来三个月就过期。
Cordis 的源头是 Koishi,一个 TypeScript 写的跨平台聊天机器人框架,从 2020 年左右开始开发。聊天机器人是插件框架最残酷的练兵场之一:平台多(每个聊天平台一个适配器)、用户杂(从爱好者到企业部署)、插件生态大,…
dsh 的文档被两类读者消费:公司内外的人,和干活中的 agent。范围内的每一份文档都维护英文和简体中文两个版本,两种语言权威相等,先写哪边都合法,一份中文先写的 Agent Note 和一份英文先写的完全平权。绑定它们的只有一条纪律:…
软件项目最常见的文档问题不是没有文档,是漂移。文档写的时候是对的,代码改了之后没人跟着改,文档就慢慢变成一份精心排版的谎言。读者不知道它过期了,照着配置,照着调 API,浪费一个小时才发现字段早改名了。
写过一个 agent 应用的人多半见过这种场景:单测全绿,CI 全绿,真的把编辑器连上来,第一个 RPC 就挂。这不是测试写得敷衍,是 agent harness 这个形态天然给测试挖了三个坑。
docs/defensive patterns.md 的第一句话就把这批规则和常见"最佳实践"划清了界限。原文说,这些是硬赢来的 bug 类规则,每条对应一类确实发布过或差点发布过的缺陷,规则的表述就是防止它复发的表述。
传统的 Web 架构里,服务器渲染 HTML,浏览器只是显示。dsh 的 Web 客户端不走这条路:Host 和 Client 是两个独立的插件图,各自有完整的 Cordis 生命周期。
Web UI 是给人用的:人在对话框里发任务,看 agent 干活,必要时点个批准。把 agent 编进 CI、批处理或你自己的程序时,需求完全变了。没有人坐在屏幕前,没有可点的批准按钮,任务来自上一步的输出,结果要交给下一步消费。此时一…
想给对话界面加一张自己的卡片,比如一个代码审查的进度条,最直觉的写法是:开一个事件订阅,把属于这个审查的事件攒进一个自己的 store,store 变了就重渲染。单机演示这样能跑,放进 dsh 的 Web 客户端里它会在三个真实场景下坏掉。
单体软件出问题,怀疑对象是那一个进程;微服务出问题,怀疑对象是某台服务。全插件化的 harness 出问题,怀疑对象是"组合":同样是官方发布的一堆插件,你的 patch 层叠方式、你的 profile、你加载顺序里的某个第三方插件,任何…
agent 在跑的时候,内部发生了什么?模型请求了几次、每次多少 token、工具调了什么、命令输出了什么、哪个 step 出错了、上下文什么时候压缩了。这些信息都在会话日志里,但会话日志有自己的主人:它要从中投影出模型可见的历史、UI…
dsh 的会话事件日志有自己的独立子系统,不在这篇的范围。这篇讲会话日志之外的三类有状态数据:用户配置(settings)、凭证(credentials)、键值存储(storage)。
dsh 的一切能力都是 Cordis 插件挂上去的:模型适配器、工具注册表、会话日志、agent loop,全是插件。正常情况下你扩展这个系统的方式是编辑 cordis.yml、重启进程。这是开发时的扩展,动手的是人,时机是停机。
看到 schedule 和 reminder 这两个词,多数人脑子里浮现的是一个通知系统:定个时间,到点了弹浏览器通知、推系统消息、发封邮件。web schedule 不是这个东西,把它当这个东西用,第一天就会失望。
一个只记得住当前上下文的 agent 是残废的。用户换个窗口回来问"上次那个问题怎么解决的",agent 得能找到上次的会话;客户端界面要实时显示每个会话的 todo、目标、权限状态,这些值是从会话日志里算出来的;用户在对话里 @ 了另一…
bash、workflow 这些接缝,一个 context 一个实现:再注册第二个 executor 直接抛错。这对 bash 是合理的,一台机器跑命令就一种方式。子 agent 不是。同一个父会话里,可能这一刻要把一个便宜的小任务委派给…
让 agent 管理目标,最朴素的设想是给它一个"当前目标"字段。但"管目标"至少藏着两个层次不同的需求。
第一次想"上下文太长怎么办",本能方案是给模型一个 compact 工具,让它觉得历史太长时自己调用。直觉上很自然:模型最懂哪些内容重要,让它自己压缩最合理。
把 web 和技能放一篇讲,不是因为功能相近(一个联网、一个管指令),而是因为它们面对同一个问题:接多个来源时,怎么不让来源差异泄漏到模型那一层。
agent 调工具,默认是一次调一个:读个文件、跑条命令、改段代码。每一步都要模型想一下、发一个 tool call、等结果回来、再想下一步。对"把这个函数的用法总结一下"这类任务,这个节奏没问题。但对"把 30 个文件里的日期格式统一改…
agent 想理解代码,最朴素的办法是文本搜索。但文本搜索不知道 foo 在第 30 行是个变量定义、在第 200 行是一次调用、在第 50 行只是注释里被提到。三者混在一起返回,agent 就得自己猜哪个是真正的引用。
让 agent 跑命令,最朴素的想法就是调操作系统的 exec:一句话跑命令,拿回输出。但这种"一句话"在真实场景里会立刻分成三种截然不同的需求。
粗看,agent 读写文件就是几个函数:读、写、改、列目录。直接调 fs.readFile 不就行了?
拿到一个 OpenAI 兼容端点(自建 vLLM、第三方网关、任何实现了 chat completions 的服务),第一反应不该是写代码。dsh 的 llm pi ai 插件本身就是"接任意 OpenAI 兼容端点"的配置通道:pi a…
多数 agent 的图片处理停在"把图塞进下一个请求"。一次性对话这样够用,但 dsh 的会话是持久日志,要支撑 fork、resume、换 provider、重放。一张图一旦被接受,就不只服务当前这轮,它会作为历史的一部分,跟随这个会话…
多数 agent 框架接模型的方式是每个 provider 写一个 client,各自解析各自的响应。后果很具体:换一个 provider,错误处理、token 计费、工具调用解析、流式拼接全要重写一遍,agent loop 里散落着对…
多数 agent 框架的系统提示是一份手写长文本,改提示等于改文件、重启会话。开源 harness dsh 没法这么做:bash 工具要交代自己的用法,Code Mode 要注入一份生成的 SDK 文档,部署方要写人格,会话还有随时间变化…
多数 agent 的工具调用接近一次函数调用:模型说要调,harness 找到函数,跑,把结果塞回去。单机单人没问题,但 agent 的工具调用天生要挂一堆策略:钩子要观察、权限要裁决、沙箱要围栏、审批要问人、结果可能要脱敏或截断。这些策…
很多项目自称"插件化",但它们的插件只能在外围加点东西:换换主题、加加面板,核心能力换不掉。dsh 的"接缝(capability seam)"走得更远:一个接缝是一个可以被整体替换的能力。
先立一条总则: 一个事件的派发模式,是它公开契约的一部分。 这不是实现细节,是这个事件能被怎么用的规定。
先把模型立起来。dsh 里一个 Session,本质是一个 只追加的事件日志(append only log):一个 agent 从生到死的全部交互,都记成一条条有类型的事件(SessionEvent),按顺序追加。这条日志是唯一真相源(…
写过后端的人都干过一件事:往框架里注册一个东西。往 Express 注册一个中间件、往 Koa 注册一个中间件、往 Webpack 注册一个 loader、往 Vue 注册一个全局组件。注册很容易,一行代码。
满足这三条就能开始。不需要先 clone 仓库、不需要装 pnpm、不需要编译——npx 会拉取已发布的包。