Cordis 教程(C#)

Cordis 是 Tether 底层的插件框架:一个小型运行时,其中的每项能力——工具、LLM 适配器、文件访问乃至 agent loop 本身——都是挂载到共享 Context 上的插件。本教程对标 dsh 的 Cordis 教程, 通过动手实践讲解 C# 版 Cordis(src/Cordis/):每一章给出可以运行的代码、确切命令与预期输出, 最后把一个插件接入真实的 harness 服务。

本教程面向 agent 开发者。你不需要深入掌握 C#;下文的 C# 说明 会解释可能陌生的语法, 并且每一章都会给出确切命令和预期输出。

如果想阅读精简的概念参考,而不是逐步实践,请参阅 Cordis 入门; 完整的 API 语义见 插件与 Context、服务与依赖、 事件系统。

完整性说明:C# 版 Cordis 的核心语义已完整——五种派发模式、waterfall、inject 激活图、 scope 绑定的服务所有权、effect LIFO 回收、DataAnnotations 配置校验。差异在形态而非语义: Type 键替代字符串键,attribute / 泛型替代 TS 声明合并。每章会标注这些差异。

准备工作

克隆本仓库并完成一次构建(前置条件见 快速开始,需要 .NET 10 SDK):

git clone https://github.com/eanzhao-os/tether.git
cd tether
dotnet build

创建各章使用的临时目录。tmp/ 已被 git 忽略,写入的任何内容都不会进入版本控制:

mkdir -p tmp/cordis-tutorial
cd tmp/cordis-tutorial
dotnet new console -n Tutorial
cd Tutorial
dotnet add reference ../../../src/Cordis/Cordis.csproj

第 6 章会追加两个引用:Cordis.Loader(YAML 组合)与 Cordis.Hmr(热重载):

dotnet add reference ../../../src/Cordis.Loader/Cordis.Loader.csproj
dotnet add reference ../../../src/Cordis.Hmr/Cordis.Hmr.csproj

dsh 教程用 cordis.yml + 单文件启动器组合应用;C# 版前 5 章直接把挂载代码写进 Program.cs (等价于 loader 为每个配置项做的事),第 6 章起切换到 YAML 组合。每章的 Program.cs 都以这段宿主代码开头:

using Cordis;

await using var root = new Context();   // 根 Context:服务仓库 + 事件总线
await root.StartAsync();                // 应用就绪:触发 LifecycleEvents.Ready

var ctx = root;                         // 本章插件挂载在 root 上

Context 实现了 IAsyncDisposable:await using 保证进程退出前整棵插件树按 LIFO 完成回收。

章节

  1. 编写第一个插件:插件是 IPlugin,由 ctx.Plugin(...) 挂载。
  2. 生命周期与 effect:由 Cordis 管理的注册会在所属插件卸载时撤销。
  3. 服务:在 ctx 上公开一项能力,并通过 inject 依赖它。
  4. 事件:类型化事件、五种派发模式与 waterfall 的短路行为。
  5. 配置:读取组合文档中经过校验的配置,输入错误时明确报错。
  6. 组合与 HMR:把 YAML 文档作为插件树,使用热重载,诊断始终无法加载的插件。
  7. 进入 harness:向真实的 IToolRegistry 注册一个模型可调用的工具。

C# 说明

这些示例使用了几项可能陌生的 C# 功能:

  • ValueTask 是异步返回值的轻量表示,Cordis 的全部异步 API 都用它;消费方照常 await 即可。
  • await using var x = ... 在作用域结束时调用 IAsyncDisposable.DisposeAsync()——本教程用它保证插件树回收。
  • Plugin.From(c => ...) 把委托包成插件,等价于 dsh 的函数插件;不需要为它写一个类。
  • [Inject(typeof(...))] 是 attribute 形式的依赖声明,替代 dsh 的 export const inject = [...]。
  • dsh 用 declare module 声明合并给 ctx 增加类型;C# 不需要——类型安全由泛型(Get<T>() / On<T1, TResult>)在调用点落地。
在 GitHub 上编辑此页