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 完成回收。
章节
- 编写第一个插件:插件是
IPlugin,由ctx.Plugin(...)挂载。 - 生命周期与 effect:由 Cordis 管理的注册会在所属插件卸载时撤销。
- 服务:在
ctx上公开一项能力,并通过 inject 依赖它。 - 事件:类型化事件、五种派发模式与 waterfall 的短路行为。
- 配置:读取组合文档中经过校验的配置,输入错误时明确报错。
- 组合与 HMR:把 YAML 文档作为插件树,使用热重载,诊断始终无法加载的插件。
- 进入 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>)在调用点落地。