拆解Deepseek Harness(二):Cordis 元框架与可组合性
引言从 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 → DISPOSEDContext统一上下文组件与环境的唯一交互面形成上下文树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 等ICALP13。论文 §2.2 定义: While effects model a programs impact on the world, coeffects model the worlds 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→ 执行applyLOADING→ 运行中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.on、ctx.set、ctx.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需要执行工具调用时直接输出结构化jsonassiant完成我已经成功创建了一个没有 tool 工具的 No-Tools AgentPS.免费模型能力边界——创造模式的工具链对 schema 遵循能力要求很高严格 oneOf、不可变版本、纯 JS 函数体。这类任务建议换更强的模型GLM-5.2、Kimi K3、DeepSeek 旗舰免费 Flash 模型做简单对话可以创造模式这种精细活会频繁卡壳。创建完成后点击新建会话选择该agent效果如下