# 可组合性与插件系统

## 组合

编程的本质就是组合。将小的构建块拼装为更大的系统，再将大系统作为块继续拼装——这是从函数到模块到微服务一脉相承的思想。

组合可以分为两种：

- **静态组合**：编译期确定的组合，例如函数调用、模块导入。
- **动态组合**：运行时确定的组合，例如热更新、插件加载/卸载。

静态组合是逻辑的组合；动态组合为可组合性引入了时间和空间两个新维度。

## 三种可组合性

| 维度 | 定义 | 对应问题 |
|------|------|----------|
| **逻辑可组合性** (Logical) | 功能能否被任意拆分和组装 | 接口设计是否正交 |
| **时间可组合性** (Temporal) | 能否灵活、安全地控制组合的运行时序 | 能否热加载/卸载而不泄漏 |
| **空间可组合性** (Spatial) | 能否灵活、安全地管理组合的依赖关系 | 依赖缺失时行为是否确定 |

一门编程语言或应用框架越多地使用组合范式，就称它的可组合性越好。

## 传统插件系统的问题

插件系统是动态组合的典型形式。浏览器扩展、IDE 插件、操作系统驱动，都是其实例。然而大多数插件系统并不可靠。

### 不可逆的插件化

以 VSCode 为例：

- 卸载或更新插件时需要重启整个系统。
- 无法在运行时追踪和回收副作用，导致内存泄漏和非预期的资源占用。
- 即便提供了 `deactivate` 钩子，也无法强制开发者正确实现清理逻辑。

**根本原因**：未做到时间可组合——系统不知道某个插件产生了哪些副作用、占用了哪些资源。

### 不完全的插件化

- 无法表达插件间的依赖关系，扩展能力受限。
- 只有外围功能被下放给插件，核心功能依然通过修改主体代码来实现。

**根本原因**：未做到空间可组合——系统缺乏对依赖关系的建模和管理。

## Cordis 的解法

Cordis 同时解决了上述两个问题：

1. **可逆作用** (Revertible Effects) 实现时间可组合性——所有注册自动追踪、自动回收。
2. **响应式余作用** (Reactive Coeffects) 实现空间可组合性——依赖声明驱动加载顺序。

两者通过**上下文模型** (Context Model) 统一为单一的编程范式：开发者只需通过 `ctx` 调用框架 API，可逆性和依赖管理由框架保证。

## 在 Harness 中的体现

DeepSeek Harness 将 Cordis 的可组合性应用到 Agent 开发领域：

```ts
import type { Context } from 'cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-llm'

// 一个 Harness 插件天然是可逆的
export const inject = ['tools', 'llm'] // 空间可组合：声明依赖

export function apply(ctx: Context) {
  // 时间可组合：注册会被自动追踪和回收
  ctx.tools.register(defineTool({
    name: 'my-tool',
    description: '...',
    parameters: { /* ... */ },
    async execute(args) { return [] },
  }))
}
```

插件卸载时，tool 自动注销、事件监听自动移除——无需手动清理。依赖的服务（如 `llm`）消失时，插件自动挂起；恢复时自动重新加载。
