拆解Deepseek Harness(二):Cordis 元框架与可组合性
2026/8/26 2:18:22 网站建设 项目流程

引言:从 Harness 到 Cordis

在 LLM Agent 系统中,模型只提供生成能力,真正决定系统能否稳定运行的,是围绕模型构建的执行框架:它需要管理 Prompt、工具、上下文、策略、评测与运行流程。Deepseek Harness 正是这样一层用于组织、执行和实验的“执行壳”。但当模型、工具与任务不断增多时,简单的拼接方式很快就会遇到复用难、扩展难、回放难的问题。因此,Harness 需要一种更高层的抽象,把各类能力统一定义为可注册、可组合、可运行的对象。这正是 Cordis 元框架要解决的问题。

传统 Agent 框架有什么问题?

问题

核心矛盾

主要痛点

问题一

逻辑混在一起

模型调用、工具调用、Prompt 管理耦合在同一流程中

逻辑耦合、难以复用、难以测试、难以替换模型、难以插入策略、难以记录 trace

问题二

组件没有统一抽象

Tool / Model / Prompt / Evaluator 各有各的接口,缺乏统一协议

无法统一编排、无法统一配置、无法统一日志、无法统一评测

问题三

流程硬编码

执行步骤写死在代码里,流程与逻辑绑定

不可配置、不可可视化、不可序列化、不可回放、不可动态组装

下面逐一展开。

问题一:模型调用、工具调用、Prompt 管理混在一起

例如常见代码:

def run_agent(query): prompt = build_prompt(query) response = model.chat(prompt) tool_call = parse_tool(response) result = call_tool(tool_call) return format_result(result)

问题

具体表现

逻辑耦合

Prompt 构建、模型调用、工具解析、结果格式化揉在一个函数里,职责边界不清

难以复用

各环节紧绑定,无法单独抽出 Prompt 模板或工具调用逻辑供其他场景复用

难以测试

无法对单个环节做单元测试,只能端到端跑,调试和定位问题成本高

难以替换模型

模型调用硬编码在主流程中,切换底层模型需要改动业务代码

难以插入策略

重试、降级、路由、限流等横切策略没有合适的插入点

难以记录 trace

各环节缺少统一的拦截/钩子机制,无法完整记录调用链路和中间状态


问题二:组件没有统一抽象

可能每个模块长得不一样:

class Tool: def execute(...) class Model: def chat(...) class Prompt: def render(...) class Evaluator: def score(...)

问题

具体表现

无法统一编排

不同组件接口各异,无法用统一的调度器串联执行

无法统一配置

每个组件的初始化参数、加载方式各不相同,配置分散

无法统一日志

缺少统一的日志格式和采集入口,跨组件排查困难

无法统一评测

各组件输出格式不同,难以用同一套评估框架衡量效果


问题三:流程硬编码

step1() if condition: step2() else: step3() step4()

问题

具体表现

流程不可配置

步骤顺序和分支条件写死在代码中,修改流程需要改代码并重新部署

流程不可可视化

无法直观看到执行路径和节点关系,理解和排查成本高

流程不可序列化

流程定义无法导出为数据格式,难以迁移、分享和版本管理

流程不可回放

执行过程没有完整记录,无法复现某次运行的具体路径和中间状态

流程不可动态组装

无法根据输入或上下文在运行时动态拼接执行步骤

因此,Deepseek Harness 引入 Cordis。Cordis 的作用不是实现某个具体业务,而是定义一套元规则:什么对象可以被注册,什么对象可以被组合,组合后的对象如何运行,运行过程如何被观测和评测。

从“写 Agent”到“组装 Agent”

Cordis 是什么?

Cordis是《A Programming Paradigm for Spatiotemporal Composability》 的实现,论文指出现代软件越来越需要动态组合(运行时加载/卸载/重配置组件,如 VSCode 插件系统、自我修改的 agent harness),但现状只能靠"重启进程/重启容器"这种粗粒度手段:

维度

问题

具体表现

时间维度缺失

无法运行时动态卸载组件

VSCode 无法在不重启整个扩展宿主的情况下卸载单个扩展(Top 100 扩展中 87 个含可执行代码,卸载都得重启)

空间维度缺失

组件间缺乏安全、带类型的依赖机制

Top 100 中只有 7 个声明了扩展间依赖

粗粒度替代的代价

只能靠重启进程/容器来重配置

进程重启丢失全部运行时状态(缓存、连接),容器编排无法表达同地址空间内的依赖

Cordis架构的核心思想是把 effect / coeffect 从编译期概念提升为运行时机制,通过以下机制实现:

机制

作用

对应维度

Revertible Effects可逆副作用

组件对共享上下文的每次变换都携带显式的"逆操作",由运行时跟踪;组件被移除时,其全部副作用(资源分配、事件注册、状态变更)能被完全、按序地回收

时间可组合性

Reactive Coeffects响应式余效应

组件以"规格说明"声明对环境的依赖;上下文每次变化都会按该规格通知组件,分类为 activating / deactivating / neutral,并支持隔离(isolation)与拦截(interception)

空间可组合性

五大核心概念

概念

一句话说明

关键机制

Registry依赖注入

组件声明依赖,运行时保证就绪才执行

inject 数组 + 自动等待 / 失活

Coeffect依赖解析

把依赖检查从编译期提升为运行时响应式机制

activating / deactivating / neutral 三类通知

Fiber生命周期

每次实例化携带独立的生命周期状态

PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED

Context统一上下文

组件与环境的唯一交互面,形成上下文树

Proxy 属性访问 + ctx.extend() 派生子上下文

Effect副作用跟踪

环境变更自动登记逆操作,卸载即完全复原

ctx.on / ctx.set / ctx.plugin / ctx.effect

下面逐一展开。

Registry 依赖注入

组件用inject数组声明依赖,Registry 保证:apply被执行时,声明的依赖一定已经可用。依赖未就绪时插件停在等待状态,就绪后才运行;依赖消失时插件自动失活。插件作者不需要写任何判空或等待逻辑。

const myPlugin = { inject: ['mcp', 'tools'], // 声明依赖 apply(ctx) { ctx.mcp.excute() } } ctx.plugin(myPlugin) // 交给 Registry 装配

Coeffect 依赖解析

Coeffect是 effect的范畴论对偶概念,来自编程语言理论(Petricek 等,ICALP'13)。

论文 §2.2 定义: While effects model a program's impact on the world, coeffects model the world's constraints on the program. (effects 是"程序对世界的影响",coeffects 是"世界对程序的约束"。)

经典 coeffect 系统只做编译期静态分析(检查需求是否满足)。Coefffect把它提升为运行时机制:上下文每次变化,都会拿各组件的 coeffect 规格逐一对照,把变化分类为三种通知——

  • activating:你要的依赖现在齐了 → 激活你

  • deactivating:你依赖的东西没了 → 失活你

  • neutral:与你无关

这就是Coeffect,也是空间可组合性的来源:组件只需声明inject,依赖的出现/消失/替换由运行时统一协调,组件之间不需要互相知道对方的存在。

const myPlugin = { inject: ['mcp', 'tools'], // ← 这就是一份 coeffect 规格: // "我需要环境提供 mcp 和 tools" apply(ctx) { ctx.mcp.excute(...) // ← 读取 coeffect 并执行操作 } }

Fiber 生命周期

论文定义:一个组件可以被实例化多次,每次实例化携带自己的生命周期状态。我们把这样一次实例化称为 fiber。

源码里Fiber类注释:Runtime instance of one plugin application

每次ctx.plugin()都把组件实例化为一个 fiber,并伴有如下生命周期:

等待依赖(PENDING)→ 执行apply(LOADING)→ 运行中(ACTIVE)→ 卸载清理(UNLOADING)→ 已回收(DISPOSED)异常落入 FAILED。

状态迁移由 coeffect 通知驱动,apply就是论文里"带逆操作的 effect 函数"。

const fiber = ctx.plugin(myPlugin) // 依赖就绪 → 自动执行 apply // 依赖消失 → 自动卸载、回收全部副作用 await fiber.dispose() // 也可手动卸载

Context 统一上下文

apply(ctx)收到的ctx是组件与环境的唯一交互面:读依赖(ctx.llm)、提供服务、注册事件都发生在它上面。每个 fiber 运行在派生的子上下文里,形成上下文树;属性访问经 Proxy 落入 coeffect 解析,用起来和原生字段无异。

apply(ctx) { ctx.mcp.excute(...) // 属性式读取依赖mcp ctx.set('quota', quota) // 反射式提供服务 const sub = ctx.extend() // 派生子上下文 }

Effect 副作用跟踪

apply里的一切环境变更——ctx.onctx.setctx.plugin——都被自动跟踪:每个操作登记一个逆操作到所属 fiber 上,卸载时按相反顺序逐个执行,环境完全复原。也可以直接用ctx.effect登记自定义清理。

apply(ctx) { ctx.on('ready', handler) // 卸载时自动取消监听 ctx.effect(() => { const timer = setInterval(tick, 1000) // 正向:注册事件 return () => clearInterval(timer) // 逆操作:登记fiber._disposables }) }

定制个人专属DeepSeek Harness

拆解Deepseek Harness(一):一切皆可插件 按照上一篇文章指引配置好免费模型,就可以使用创造模式创建个人Agent。

user:帮我创建一个没有tool 工具的agent,需要执行工具调用时,直接输出结构化json

assiant:完成!我已经成功创建了一个没有 tool 工具的 No-Tools Agent

PS.免费模型能力边界——创造模式的工具链对 schema 遵循能力要求很高(严格 oneOf、不可变版本、纯 JS 函数体)。这类任务建议换更强的模型(GLM-5.2、Kimi K3、DeepSeek 旗舰),免费 Flash 模型做简单对话可以,创造模式这种精细活会频繁卡壳。

创建完成后点击新建会话,选择该agent,效果如下:

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询