架构总览

Tether 是 deepseek-harness(dsh)的 C# 重实现, 建立在 Cordis 插件框架之上——一切皆插件。本页说明包为什么这么划分,以及划分背后的三条规则。

两层

分层是硬约束,不是风格偏好。

  • Cordis.* —— 与 agent 无关的通用插件框架层。它不知道什么是 LLM、什么是工具调用。
  • Tether.* —— agent harness 产品层。能力按 Service Definition、Provider、Consumer 三种角色组合,进程入口只负责装配插件树。

Tether 以 dsh 的调用方可观察语义为复刻目标,但允许采用符合 .NET 生态的包划分、类型和内部实现。 复刻基线与对齐制度见 dsh 基线与对齐。

依赖只向下 apps/* (Headless · Cli · WebHost · SdkHost) Composition Root:唯一引用实现包并装配插件树 Tether.* 产品层 Service Definition(seam 接口) Contracts · Llm · Fs · Shell · Subprocess Provider / Consumer 实现包 Session · Agent · Tools · Persistence · *.Local Cordis.* 框架层(与 agent 无关) Cordis 核心 Context · Registry · EffectScope · Events 框架周边 Loader · Hmr · Scripting
框架层不知道什么是 LLM 或工具调用;产品层建在它之上;只有进程入口把实现包聚齐。

三条结构性规则

一、Cordis 与 Tether 分层。 Cordis 是通用插件框架,保持独立;Tether 建立在它之上。框架核心禁止引用任何 Tether.* 包。

二、能力 seam 决定是否拆程序集。 Service Definition 单独一个程序集,里面只有接口与数据类型;Provider 与 Consumer 都引用它。这样组合预设时只换实现、不换引用。

三、Composition Root 收敛到 Boot。 进程入口只管日志与宿主,插件树由加载器从 YAML 挂载,保持一切可 patch。

实际的依赖图

下表来自各 csproj 的 ProjectReference,不是设计意图的描述。

零引用的定义程序集

这些程序集一个 ProjectReference 都没有——连 Cordis 都不引用。

程序集定义什么
Cordis插件框架核心
Tether.Core.Contracts会话、Agent、工具、提示词、审批的契约
Tether.Llm模型访问 seam
Tether.Fs存储原语 seam
Tether.Shellshell 执行 seam(前台 + 后台 job)
Tether.Subprocess进程原语 seam

第一条规则因此是被机械保证的,不依赖人的自觉;第二条规则也在这里落地:换实现永远不动引用。

能力包的三角色模式

每块能力都按同一个形状展开,引用关系是可预测的公式:

角色命名引用公式
seamTether.X无
ProviderTether.X.LocalCordis + Tether.Invariants + Tether.X
工具Tether.X.ToolsCordis + Tether.Core.Contracts + Tether.Invariants + Tether.X
Tether.X seam · 零引用 · 只有接口 引用 Tether.X.Local Provider:换实现只换这个 Tether.X.Tools 把能力暴露给模型 其它消费者 只依赖 seam,看不见实现
三方都指向同一个零引用的 seam。预设里把 .Local 换成别的 Provider 时,工具与消费者的引用一行都不用改。

按这个模式展开的能力:

能力seamProvider工具
子进程Tether.Subprocess.Local—
ShellTether.Shell.Local(bash / pwsh).Tools
文件系统Tether.Fs.Local.Tools、.Search
模型访问Tether.Llm.DeepSeek、.Providers—
后台作业与持久终端Tether.Jobs、Tether.Terminal.Local.Tools
沙箱Tether.Sandbox.Local—
LSPTether.Lsp.Stdio.Tools
Web 访问Tether.Web.Http.Tools
设置Tether.Settings.Local—
凭据Tether.Credentials.Local—
附件与溢写Tether.Attachment、Tether.Spill.Local—
会话持久化Tether.Session.Persistence.Jsonl、.Sqlite—

同一个形状重复了十余次,这不是巧合——它是第二条规则被机械执行的结果。看到一个新的 Tether.X 目录,不用读代码就能推断出它是零引用的接口包,Tether.X.Local 是它的本地实现,Tether.X.Tools 把它暴露给模型。

会话持久化在三角色之外再显式拆出 .Coordinator:它消费唯一的 ISessionEventStore Provider,并向其它 consumers 提供 ISessionPersistence;composition 替换 .Jsonl / .Sqlite 时不替换 Coordinator。

能力清单与文档覆盖

仓库当前有约六十个程序集,本站不为每一个都写专页。下表按能力族给出全图,并如实标注哪些还没有专页。

族程序集专页
框架核心Cordis插件与 Context、服务与注入、事件系统
框架周边Cordis.Loader、Cordis.Scripting、Cordis.Hmr组合、脚本、HMR
会话内核Tether.Core.Contracts、Tether.Core.Session会话与事件溯源
会话持久化Tether.Session.Persistence、.Coordinator、.Jsonl、.Sqlite会话持久化
会话投影Tether.Session.Projection投影注册表与缓存
会话领域投影Tether.Session.Title、.Stats、.Telemetry会话可观测性
AgentTether.Core.Agent、.AgentLoopAgent 生命周期
提示词Tether.Core.SystemPrompt系统提示词组装
工具管线Tether.Core.Tools工具管线
审批与交互Tether.Interaction审批与用户交互
横切校验Tether.Invariants运行期不变量
形式化建模formal/lean(TetherFormal,Lean 4)形式化建模
装配与入口Tether.Boot、Tether.Presets、Tether.Bundles、apps/Tether.Cli、apps/Tether.Desktop预设与运行时 Bundle、Composition Root、原生桌面壳
身份与配置Tether.Identity、Tether.Settings、Tether.Credentials设置、凭据与身份
自我演化回路Tether.Evolution*、apps/Tether.ShadowHost自我演化系统
模型可观测性Tether.Llm.ObservabilityLLM 流式与模型访问
Web 与桌面平台Tether.Web.Client、Tether.Desktop.*Web 客户端平台、原生桌面壳

这张表是快照,不是契约

包清单以 Tether.slnx 与源码树为准。项目正处在快速展开期, 新增能力包的速度快于文档补齐的速度——看到表里没有的目录属正常, 不代表它是实验性的。

其余程序集

程序集引用角色
Cordis.ScriptingCordis源码编译为可卸载插件
Cordis.LoaderCordisYAML 组合与挂载
Cordis.HmrCordis、Cordis.Scripting文件监听与热替换
Tether.InvariantsCordis横切:运行期不变量注册表
Tether.Core.SessionCordis、Contracts、InvariantsProvider
Tether.Core.SystemPrompt同上Provider
Tether.Core.Tools同上Provider
Tether.Core.Agent同上Provider
Tether.Interaction同上Provider(审批与提问)
Tether.Core.AgentLoop加 Tether.Core.Agent、Tether.LlmConsumer
apps/Tether.Headless全部实现包Composition Root

三点值得单独指出:

  • 产品层实现包彼此不可见,仅通过服务接口相遇。唯一的例外是 Tether.Core.AgentLoop 依赖 Tether.Core.Agent。
  • 只有拥有两个可独立观察、可能分叉的运行期事实的实现包才引用 Tether.Invariants 并注册 package-owned 检查;没有这种关系的包不创建空 companion。详见运行期不变量。
  • Tether.Shell.Local 与 Tether.Fs.Search 都额外引用 Tether.Subprocess——前者用它起 bash / pwsh,后者用它跑打包的 ripgrep。进程执行只有一份实现。

只有进程入口引用全部实现包

apps/Tether.Headless 是唯一把所有实现包聚在一起的地方——这正是第三条规则的形状: 装配集中在一处,其余包互不感知。见 Composition Root 与运行。

目录约定

src/Cordis/          框架核心,与 agent 无关,禁止引用任何 Tether.* 包
src/Cordis.*/        框架周边(Loader / Hmr / Scripting)
src/Tether.*/        产品层能力包
apps/                进程入口(CLI / Web 宿主)
tests/               与 src 镜像:src/Foo/Foo.csproj ↔ tests/Foo.Tests/Foo.Tests.csproj
formal/lean/         Lean 4 形式化对照模型(独立 Lake 工程,不进解决方案)

当前已落地的进程入口

apps/ 下目前有: - Tether.Headless:无界面宿主,从 YAML 挂载完整插件树并把结果以 JSON 快照输出,兼具端到端验收载体; - Tether.Cli:交互式终端命令行入口,支持会话交互、工具执行与插件管理; - Tether.WebHost:ASP.NET Core / Web 宿主,提供实时推流、浏览器控制台与 REST 接口; - Tether.Desktop:基于 PhotinoX 的轻量单进程原生桌面壳,内嵌 vendored 客户端并与 CLI 共享存储与 Profile; - Tether.ShadowHost:演化评测专用的影子验证宿主,在隔离环境中验证冻结场景与回放回归。

既定技术选型

这些是已经拍定的决策,新包按此选型,不需要重新讨论。

子系统技术
Context 点访问体验第一版用 ctx.Get<T>() 朴素写法;partial interface IContext + Roslyn 源生成器聚合门面后补
配置校验DataAnnotations + Microsoft.Extensions.Options;复杂场景 FluentValidation
YAML 组合加载YamlDotNet + stable-id 有序补丁;Profile 支持 bundles: 有序栈驱动装配(#308);不做 !!js 表达式,用环境变量替换
热重载可收集 AssemblyLoadContext(isCollectible: true)
动态插件Microsoft.CodeAnalysis.CSharp 内存编译 + 自管可收集 ALC
LLM 流式HttpClient + SSE 解析成 IAsyncEnumerable<T>;抽象对齐 Microsoft.Extensions.AI.IChatClient;标准 OpenAI 走官方 Microsoft.Extensions.AI.OpenAI adapter(#470,钉死 MEAI.OpenAI 10.9.0 / OpenAI 2.12.0 / System.ClientModel 1.14.0,SDK 自带重试关闭),其余 wire 手解析
RPC / SDKStreamJsonRpc;Web 实时推流用 ASP.NET Core + SignalR
LSP stdio严格手写 Content-Length JSON-RPC framing;保持有界缓冲与零额外 transport 依赖
会话持久化provider-neutral coordinator + append-only ISessionEventStore;本地唯一 first-party provider 为 JSONL/Zstd(SQLite session 已按 #150 退役,SQLite 仅作为领域 KV 与观察索引),序列化用 System.Text.Json 源生成
PTYWindows ConPTY(P/Invoke),Unix openpty native broker
沙箱平台原生(macOS sandbox-exec seatbelt 已交付,Job Objects / landlock 后补;E2B 容器已按 #271 退役)

工程规范要点

  • C# 最新语言版本,net10.0;Nullable 与 ImplicitUsings 保持开启。
  • 包版本统一由根目录 Directory.Packages.props(CPM)管理,csproj 的 PackageReference 不写 Version。
  • 异步 API 一律 ValueTask 优先;禁止 sync-over-async(.Result / .Wait())。
  • 公开 API 必须有 XML doc 注释,说明语义而不复述签名。
  • 每个核心语义必须有单元测试,提交前 dotnet test 全绿。
  • 新能力包按 Service Definition / Provider / Consumer 三角色评估是否拆程序集。

已知风险

热重载卸载成功率。 ALC 卸载要求没有任何活引用指向旧程序集——事件处理器、静态缓存、timer 回调都算。注册即回收纪律是唯一保障。

沙箱跨平台。 全项目唯一无法纯托管解决的部分,首版容器优先压成本。

点访问 ergonomics。 ctx.tools 式体验需要源生成器聚合 partial interface,是计划内投入,但不要提前做。

首个 tag 之前:基础优先于兼容包袱

没有外部消费者,因此类型、命名空间、包划分可以自由重命名,但必须一次性更新所有引用。 各持久化 provider 的物理 schema / 日志格式不做向后兼容承诺,版本号单调递增即可。

范围与进度的事实来源

README 与本站都不维护实现进度或完成清单。以 GitHub 为唯一事实来源:

下一步

在 GitHub 上编辑此页