---
layout: default
title: 声明式与命令式：Cordis 的五个核心概念
description: 从插件模型理解 Cordis 如何管理依赖、状态与生命周期
eyebrow: DeepSeek Harness / 02
---

# 声明式与命令式：Cordis 的五个核心概念

把搜索服务从 Provider A 换成 Provider B，改配置再重启进程通常已经够用。DSH 仍然选择了一条更重的路：插件直接运行在 Harness 进程内，可以注册有状态服务、监听事件，甚至替换 Agent Loop。

问题不在于哪种插件“更先进”，而在于运行时是否需要替换**进程内、带状态、被其他组件依赖的能力**。一旦答案是需要，依赖、清理和失败回滚就不能再交给插件作者临场发挥，Cordis 正是用来托住这部分复杂度的。

## 两种插件世界观

| 插件模型 | 插件交付什么 | 默认优点 | 默认代价 |
| --- | --- | --- | --- |
| 声明式 | 文件、Prompt、Skill、MCP 配置、Hook | 门槛低，进程边界清楚，重启即可替换 | 只能在宿主预留的扩展点内工作 |
| 命令式 | 进程内代码、服务、事件和状态 | 能组合有状态能力，也能改变运行时行为 | 必须处理依赖、清理、并发和更新失败 |

二者的能力上限未必不同。你也可以在独立 MCP Server 里实现自己的热更新，但生命周期责任由服务作者承担。差别主要在默认下限：Cordis 强制插件进入统一的依赖和清理协议，让复杂替换不必从零处理边缘情况。

对于搜索、Skill 和普通 Hook，声明式模型通常更划算。Cordis 的价值集中在会话存储、上下文管理器、后台任务和 Agent Loop 这类跨 Turn 持有状态的组件上。

## 五个概念不是五个零件

官方 Cordis Primer 用五个概念描述插件运行时。重点不是背 API，而是看它们怎样形成一个闭环。

| 概念 | 解决的问题 | 形成的约束 |
| --- | --- | --- |
| Plugin | 谁向系统贡献能力 | 插件只负责在自己的作用域内注册能力和行为 |
| Context | 能力在哪里被发现 | 消费方按稳定服务键取能力，不绑定具体实现 |
| inject | 什么时候可以启动 | 依赖未满足时等待，提供方变化时重新评估 |
| Typed Event | 横切逻辑怎样协作 | 通知、决策和环绕拦截使用不同分发语义 |
| Effect / Fiber | 插件离开后怎样收场 | 每个副作用有归属，并在卸载时逆序撤销 |

### Plugin：贡献能力，而不是占有主流程

Cordis 插件可以是函数、对象或类。形态不是关键，关键是插件通过 Context 提供服务、注册监听器或产生受托管副作用。它不需要知道完整系统长什么样，也不应该靠直接导入另一个插件的实例协作。

这使插件成为运行时组合单元，而不只是一个工具目录。模型适配器、工具注册表、会话日志和 Agent Loop 在组合层面没有特权差异。

### Context：服务容器也是作用域边界

`ctx.llm`、`ctx.tools` 这类稳定键表达“我要某种能力”，而不是“我要某个实现”。提供方可以替换，消费者不需要改 import。

Context 还可以派生局部作用域。`extend` 继承父级能力，`isolate` 则让某项服务在子树中拥有独立世界。工程上，这意味着某个 Agent 可以看到自己的工具、文件系统或策略，而不污染根上下文中的其他 Agent。

### inject：把启动顺序变成依赖关系

传统启动脚本通常靠人工排列模块。Cordis 让插件声明所需服务：依赖未就绪时，插件处于等待状态；服务出现后才激活；提供方离开时，依赖分支也会停下并等待新的实现。

这就是“空间可组合性”的工程含义：系统不是按一条固定加载顺序拼起来，而是根据当前作用域中有哪些服务，持续解析谁能够工作。

### Typed Event：区分通知与决策

服务适合直接调用，事件适合一对多通知和横切拦截。当前官方 Primer 重点区分四种分发方式：

| 模式 | 语义 | 典型场景 |
| --- | --- | --- |
| `emit` | 同步广播，不汇总结果 | 会话事件、状态通知 |
| `parallel` | 并发执行并等待全部完成 | 相互独立的异步观察者 |
| `serial` | 顺序执行，允许首个有效结果拍板 | 多个候选处理器 |
| `waterfall` | 监听器包裹下游，可委托也可短路 | 模型请求、工具审批、重试决策 |

`waterfall` 是理解 DSH 策略面的关键。观察者必须调用下游，否则一个本想记日志的插件也会意外截断模型请求；只有真正拥有否决权的策略插件才应短路。它把“在哪里可以干预”从核心代码里的条件分支变成可组合的决策链。

### Effect 与 Fiber：注册必须可逆

事件监听器、定时器、服务注册和后台任务都会改变进程。Cordis 把这些变化归到插件实例对应的 Fiber 上，并要求非托管资源登记 disposer。卸载时，Fiber 逆序撤销副作用，尽量把运行时恢复到插件安装前的状态。

这就是“时间可组合性”的工程含义：安装插件不只是会做什么，还必须定义它离开后如何撤销。HMR 的本质因此不是替换文件，而是卸载旧 Fiber、清理副作用、激活新 Fiber；若新实现失败，则需要保留或恢复最后一个可用状态。

## 为什么 Agent Loop 值得这份复杂度

如果 Loop 是核心中的硬编码控制流，插件最多只能在预设节点挂 Hook。若 Loop 本身是服务，就可以整体替换调度策略，例如从单 Agent 循环切换到多 Agent 协作，或重新定义工具并发、上下文装配和错误恢复。

这解释了 DSH 的复杂度为何一路延伸到 Fiber、disposer、依赖通知和事务性 HMR：它们不是互不相关的功能，而是在为“控制流本身也能被替换”提供基础设施。

## 这套设计的失败方式

- `inject` 写错或依赖永远不出现，插件会停在等待状态；诊断工具必须能解释它在等什么。
- 插件绕过 Effect 创建资源，卸载后仍会留下监听器、连接或任务，热更新就不再可靠。
- `waterfall` 观察者忘记委托，会把整条执行链静默截断。
- 进程内插件共享故障域，恶意或低质量插件比独立进程插件拥有更大的破坏面。
- 生命周期正确不等于业务状态正确；数据库事务、外部副作用和迁移仍需插件自己设计。

因此，Cordis 提供的是较高的工程下限，不是自动正确。只有当运行中替换有状态能力确实重要时，这份复杂度才值得承担。

事实基线见官方 [Cordis Primer](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.0-rc.8/docs/cordis-primer.zh.md)、[Architecture](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.0-rc.8/docs/architecture.zh.md) 与 [Cordis 论文](https://github.com/cordiverse/paper)。想动手验证的读者可参考 [NanoCordis](https://github.com/SheltonLiu-N/nano-cordis)——一个约 1600 行的教育复现，覆盖了 Plugin、Context、inject、Event 和 Effect 的核心机制，但有意省略了 streaming、per-session scopes、subagents、sandboxing 和审计事件；这些省略恰好标记了"Demo 能跑"与"生产可用"之间的差距。

## 小结

- Context 让能力按服务键和作用域发现，`inject` 让加载顺序由依赖推导。
- Typed Event 把通知、协作和策略否决分开，`waterfall` 是主要拦截机制。
- Effect 与 Fiber 让副作用有归属、可撤销，构成 HMR 的前提。
- Cordis 真正服务的是运行中替换有状态组件，尤其是 Agent Loop，而不是让普通插件显得更复杂。

下一篇建议继续看：

- [运行中的 DSH：插件树、组合层与能力接缝](../03-profile-bundle-patch/index.html)
