DeepSeek Harness Cordis理解

DeepSeek Harness Cordis理解

最近开源的 DeepSeek Harness 挺火的,说实话自Claude code源码泄露被迫开源之后,github上开源的成熟Agent/Harness框架/产品已经不少了,包括Codex内核也是开源的。所以新开源的Agent/Harness框架/产品一般很难扬起太大水花。

所以这次 DeepSeek Harness 开源的侧重点不具体放在Harness某块实现的方案深度上,而是基于其新打造的 "Cordis"底座,高喊出「一切皆插件」的口号,从另一个视角火爆技术圈。

这个一切皆插件的思想和我正在做的Agent产品核心思想不谋而合:可插拔/可组装。所以我还是挺好奇的,好奇它的实现上具体有啥亮点,甚至专门在arxiv上给Cordis框架发了篇论文。

响应式可插拔

看了下官网对Cordis的介绍包括quick-start文档,直观上感觉就是:把插件看成服务,cordis.yml 列出要加载的插件、给每个插件写配置、声明依赖谁;代码里再用 provide 注册服务、inject 声明需要什么,这不就是 docker-compose.yml 把服务先登记好,然后配置顺序、依赖关系吗,然后用一个全局上下文 ctx 包着,实现全局共享使用,说实话,第一时间没感觉到有啥亮点。

接着继续深入往下看,和 SpringBoot Ioc、K8s docker-compose.yml "启动式"可插拔相比, Cordis实现的是响应式可插拔

  • 干活的地方不同。 compose / K8s 里每个服务同样是在提供功能的实例,只是通常各跑在自己的进程或容器里,彼此靠网络访问。Cordis 的服务也干活,但活在同一个 JS 进程里:inject 之后拿到的就是对象,直接函数调用,不走端口、也不走 Service IP。
  • 依赖一变,插件跟着装卸。 compose 的 depends_on 只管谁先启动,依赖挂了得自己处理。Cordis 里某个服务一被 provide,依赖它的插件自动起来,一被撤掉自动卸掉。可插拔不是启动时配一次就完事,而是跟着依赖实时变。Spring 其实更近一层:也是进程内注入,也是服务对象直接调用。但它默认启动时一次把 Bean 织好,之后依赖变了,容器不会自动把相关模块卸掉再装上。Cordis 是依赖即生命周期------到位就启动,消失就卸载。

另一个比较深刻的感受就是:Cordis 的可插拔做得更彻底。

一切皆插件

Harness里没有特权主程序。 "主链路"=ReactLoopAgent,是一个插件:用 inject 声明对提示词、模型、工具、会话、agent 注册表这5 个服务接口的依赖,凑齐才启动;对外只认插座(ctx 上按名字暴露的服务接口,插上谁就用谁,不绑具体插件)------拼提示词走 ctx.systemPrompt,模型流走 ctx.llm,工具走 ctx.tools,日志走 ctx.sessions,拦截挂瀑布式事件,换哪个模型适配器,调用的还是 ctx.llm.stream()。这段循环作为工厂还可以整体替换;headless / web / sdk 这些入口驱动同样是插件。主链路不指挥插件,主链路也是插件;依赖就是生命周期。一旦缺件,对应插件停在未激活,启动时点名谁在等谁并退出------缺失等于不启动、大声失败,不会跑到一半才静默断掉。

  • 根上下文只带装插座的工具。 新建一个 Context 时,上面只有服务解析、插件表、事件总线和日志------这四样是让「插拔」能运转的最小机器,不是业务。每个插件加载时会分出自己的子上下文,还可以按隔离域切开服务实现。所以不是「一个空白的全局单例」,而是根上下文 + 一棵分层的上下文树。同一个进程里两个 session 可以挂不同的工具和策略,不必全局共用一套。
  • 登记都是可逆副作用(effect)。 监听事件、提供服务、设定时器,都会留下清理函数。插件卸载时按登记的逆序 把资源释放掉。提示词片段、工具 schema、适配器、监听器,挂上去时登记,卸下来时一并收回,不会留下幽灵监听。
    没有特殊对象,插上去能干活,拔下来能干净。

新增自定义插件

本地部署dsh以后,如果想新增一个自定义插件也很方便,就是往那棵插件树上再叠一层。这里主要是两种情况:装别人的已有插件、自己从0-1写一个插件。

装别人的已有插件的话,现在社区里有很多开源的dsh插件,拿比较出名的 dsh-web-ui]举例,全家桶一条命令:

sh 复制代码
dsh plugin --profile web add @linxin666/dsh-web-ui-all
dsh web

装完需要重启进程。

dsh --profile web --dump-config 里能看到多出来的层;不要了就 dsh plugin --profile web remove @linxin666/dsh-web-ui-all。也可以只装其中一个包,比如任务板:dsh plugin --profile web add @linxin666/dsh-client-ui-task-board

自己从0-1写一个插件,需要新增三个文件:

  • index.js:插件本体,Cordis 加载后调用 apply,你的逻辑写在这里。
  • cordis.patch.yml:告诉运行中的插件树「请插入这一行」,否则有代码也不会被挂上去。
  • package.json:普通 npm 包那几个字段之外,还要标明「安装时把哪份 yml 叠到树上」。没有这一标明,dsh plugin add 只当普通依赖装进去,启动时不会多出一层。
js 复制代码
// hello-plugin/index.js ------ 电器本身:被加载时干什么
export const name = 'hello-plugin' // 插件自己的名字,给 Cordis 认

export function apply(ctx) {
  // ctx 就是那根插排;加载成功就会进这里
  console.log('[hello-plugin] loaded')
}
yaml 复制代码
# hello-plugin/cordis.patch.yml ------ 往树上插一行(插座清单)
- insert: # 在现有配置上插入,不是整份替换
    - id: hello # 这一行的稳定编号,以后要覆盖或删掉都靠它
      name: dsh-hello-plugin # 加载哪个包,必须等于 package.json 里的 name
json 复制代码
{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "dsh": {
    "bundle": { "patch": "./cordis.patch.yml" }
  }
}

package.json 里 JSON 不能写注释,name 是包名,必须和 patch 里的 name 一致;main 指向上面的 index.js。多出来的 "dsh" 这一段,就是上面说的「标明」------告诉 dsh plugin add:装这个包的时候,把 ./cordis.patch.yml 当作一层叠上去。普通 npm 包没有这段,装完不会改插件树。

sh 复制代码
dsh plugin --profile demo add ./hello-plugin
dsh --profile demo --dump-config
dsh --profile demo

--dump-config 里应出现 # == dsh-hello-plugin。同样的不需要了就 dsh plugin --profile demo remove dsh-hello-plugin

相关推荐
魔术师Grace2 小时前
调用一次大模型,就算 Agent 吗?
aigc·agent·ai编程
桃西西呀2 小时前
GPT-5.6 的 ultra 模式凭什么开 4 个 Agent 并行跑?——多智能体协作,是把"一个聪明人"换成"一个团队"
人工智能·llm·agent
怕浪猫2 小时前
用好这 5 把钥匙,你就能拦截 AI Agent 的一切行为
数据分析·agent·产品
澄怀3 小时前
Agent 说「我已经改好文件了」,这句话到底能不能信?
llm·agent
luckystar513~3 小时前
Hermes 工程化实战专栏:安装部署实战——从装好到跑通第一个任务
人工智能·agent·智能体·hermes·实战专栏·hermes安装配置
新知图书4 小时前
14.5 AI试驾预约系统的完整实现
人工智能·agent·ai agent·智能体
水管在开花.6 小时前
Agent范式与LangGraph-②零基础保姆级教程
人工智能·面试·langchain·agent
ShallWeL6 小时前
RAG 文档变更后的检索回归清单
人工智能·agent·知识库·rag·检索
Patrick在香港6 小时前
Claude Agent 进阶编排:循环控制 + 写权限审批闸门 + 幂等重试
python·agent·claude·编排·anthropic api