Chord (@earendil-works/chord) 是 pi-main 的应用组合运行时 ,提供了服务发现、RPC、插件系统(Facets)、可复制状态(Replicated State)和增量同步(Delta)。它是整个项目的 L2 协议/服务层基础。
1. 包元数据概览
| 属性 | 值 |
|---|---|
| 版本 | 0.85.1 |
| Node 要求 | >=22.19.0 |
| 外部依赖 | esbuild 0.28.1(仅用于 bundler) |
| 设计原则 | sideEffects: false,纯模块,可树摇 |
公开子路径:
chord → 核心 API(Facet, Service, ReplicatedState)
chord/context → Context 上下文系统
chord/delta → 增量变更追踪(可独立使用)
chord/bundler → Facet 打包工具(esbuild 封装)
chord/node → Facet Bundle 加载/解析器
2. 目录结构与文件职责
packages/chord/src/
├── api.ts ← 顶层便捷 API(createFacetHost, defineService...)
├── types.ts ← 所有公开类型定义(核心契约层)
├── index.ts ← 主入口 re-exports
├── json.ts ← JSON 类型守卫
├── context/
│ └── index.ts ← Context 上下文传播系统(AbortSignal + 键值对)
├── delta/
│ └── index.ts ← 增量变更协议(Tracker, Op, Encoder, Decoder, apply)
├── facets/
│ ├── host.ts ← FacetKernel(核心:生命周期、依赖排序、装配)
│ └── loader.ts ← Facet 加载器 + 组合器(createStaticFacetLoader, combineFacetLoaders)
├── services/
│ ├── provider.ts ← RemoteServiceProvider(发布/订阅/调用服务)
│ ├── consumer.ts ← RemoteServiceBindingImpl(消费侧:单例/键值)
│ ├── handle.ts ← ServiceSlot(本地服务句柄代理)
│ ├── instances.ts ← InstanceDirectory(键值服务实例目录 + 观察者管理)
│ ├── loopback.ts ← 本地回环传输层(in-process transport)
│ ├── wire.ts ← Wire 协议(解析/校验/控制调用)
│ ├── state.ts ← MutableReplicatedState + ReplicatedStateReplica
│ ├── state-internals.ts ← WeakMap 注册表(将 state 对象与其内部实现关联)
│ ├── state-codec.ts ← ServiceStateEncoder/Decoder(每个 state 的 Op 编解码器)
│ └── errors.ts ← RemoteServiceError + 错误码枚举
├── bundler.ts ← re-export node/bundle + node/package
└── node/
├── bundle.ts ← bundleFacets(esbuild 打包 Facets)
├── package.ts ← bundleFacetPackage(打包整个 Facet 包)
├── bundle-loader.ts ← 运行时加载 Facet Bundle artifact
└── manifest.ts ← Facet Bundle 格式/版本常量
3. 核心子系统关系图
┌──────────────────────────────────────┐
│ FacetHost (api.ts) │
│ createFacetHost(options) → FacetHost │
└──────────────┬───────────────────────┘
│
┌────────────────────▼─────────────────────────┐
│ FacetKernel (facets/host.ts) │
│ │
│ ┌─── 阶段状态机 ───┐ │
│ │ setup → assembling → connecting → │
│ │ activating → active → reloading → │
│ │ disposing → dead │
│ └────────────────────┘ │
│ │
│ 职责: │
│ • 遍历 Facets 收集 requires/provides │
│ • 拓扑排序确定激活顺序 │
│ • 组装 Provider + Binding + KeyedRegistry │
│ • 生命周期回调/资源清理 │
└──────┬───────────────────┬───────────────────┘
│ │
┌────────────▼───────┐ ┌───────▼──────────────────────┐
│ FacetEnvironment │ │ RemoteServiceProvider │
│ ┌──────────────┐ │ │ (services/provider.ts) │
│ │ .provide() │ │ │ ┌──────────────────────┐ │
│ │ .provideMany │ │ │ │ Service Registration │ │
│ │ .use() │ │ │ │ ├─ singleton instance│ │
│ │ .observe() │ │ │ │ └─ keyed instances │ │
│ │ .replicated │ │ │ │ + subscribers │ │
│ │ .own() │ │ │ └──────────────────────┘ │
│ │ .onActivate │ │ └──────────┬──────────────────┘
│ └──────────────┘ │ │
└────────────────────┘ │
│
┌───────────────────▼─────────────────────┐
│ RemoteServiceTransport(传输抽象) │
│ • invoke(call, ctx) → Promise<T> │
│ • subscribe(serviceId, mode, listener) │
│ │
│ 实现: │
│ ├─ LoopbackTransport(本地进程内) │
│ └─ 外部实现(CBOR/WS/IPC...) │
└───────────────────┬─────────────────────┘
│
┌───────────────────▼─────────────────────┐
│ RemoteServiceBindingImpl(消费侧) │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ Singleton │ │ KeyedBinding │ │
│ │ Binding │ │ + InstanceDir │ │
│ └──────┬───────┘ └────┬───────────┘ │
│ │ │ │
│ ┌──────▼───────────────▼──────────┐ │
│ │ ServiceFacade + Proxy │ │
│ │ proxy.method(...) → invoke() │ │
│ │ proxy.state → MemberSlot.value │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
4. Delta 增量协议(独立可复用)
文件 :chord/delta/index.ts(1267 行)------ 这是整个 chord 中最复杂也最精巧的模块。
4.1 六种 Op 操作码
| Op | 签名 | 语义 |
|---|---|---|
["r", value] |
替换根 | 全量快照(base batch 起点) |
["s", path, value] |
set | 在 path 设置值 |
["d", path] |
delete | 删除 path 处的属性/数组元素 |
["a", path, str] |
append | 向字符串追加(s += chunk 的高效形式) |
["t", path, n] |
truncate | 从头部截断 n 字符 |
["p", path, idx, remove, items] |
splice | 数组 splice(splice(idx, remove, ...items)) |
4.2 WireOp(线上格式压缩)
WireOp 在 Op 基础上增加两层压缩:
- Path Interning :第二次使用某路径时发送
["#", id, path]定义,后续用id引用 - Arity Omission :同一 batch 中连续 op 路径相同,省略 path(短形式如
["s", value]表示 path 继承自上一个 op)
关键不变式 :"r" 重置路径字典和 previous,保证 r 是 recovery point。
4.3 Tracker(变更追踪器)
TypeScript
const tracker = track<T>(initialState);
tracker.state.field.nested = newValue; // 通过 Proxy 变更
tracker.state.items.push(item); // 自动识别数组 append
const ops = tracker.flush(); // 生成 Op[],内部 baseline 同步
Tracker 通过深度 Proxy 拦截所有操作,在内部维护一棵 DirtyNode 树标记变更路径。flush 时对比 baseline 和 target,生成最小化 Op 集。
Array 优化:
push标记为append(最优场景,只生成一个pop)pop/shift/splice标记为diff或replacesort/reverse强制diff
String 优化:
- 检测
after.startsWith(before)→ 生成aop - 检测字符串重叠(overlap 算法)→ 生成
t+a组合
4.4 apply / applyImmutable
apply(target, ops):原地变更 target,返回修改后的引用applyImmutable(target, ops):不可变方式,逐层复制容器后应用变更
两者都经过严格的 路径安全检查 (防止 __proto__ 注入)。
5. Facet 生命周期状态机
核心类 :FacetLifecycle(facets/host.ts:59-143)
setup(env)
│
▼
┌─────────┐
│setting_up│ ← assertSettingUp(): 只能 provide/use/observe
└─────┬───┘
│ lifecycle.prepared()
▼
┌─────────┐
│ prepared │ ← 等待所有 Facet setup 完成
└─────┬───┘
│ lifecycle.activate()
▼
┌─────────┐
┌──────▶│ active │ ◀── FacetKernel 阶段: "active"
│ └─────┬───┘
│ │ dispose/reload
│ ▼
│ ┌──────────┐
│ │ disposing │ ← 逆序执行 effects
│ └─────┬────┘
│ ▼
│ ┌─────────┐
└────────│ dead │
reload 替换 └─────────┘
FacetKernel 装配阶段:
activate():
1. setup → 遍历 facets, 调用 facet.setup(env), 收集 requires/provides
2. assembling → validateFacets() 拓扑排序, 组装 Provider + Binding + LocalKeyedRegistry
3. connecting → bindings.ready() 等待所有外部/内部服务安装初始快照
4. activating → 按拓扑序调用 lifecycle.activate()(触发 observations + onActivate 回调)
5. active → 运行态
5.1 拓扑排序与依赖验证
validateFacets(records, externalServices):
1. 构建 providers Map: serviceId → { facetId, mode }
2. 检查: 无重复提供, mode 不冲突, 所有 requires 都能被满足
3. 构建依赖图: requires → providers[facetId]
4. 拓扑排序 (Kahn 算法)
5. 检测循环依赖 → throw Error("Facet dependency cycle: ...")
5.2 Reload(热更新)
FacetKernel.reload(facets) 是一个精心设计的不中断替换流程:
- 重新 setup 同名 Facets(新实例)
- 验证 shape 不变(requires/provides 签名必须一致)
- 激活新 Facets(在旧 Facets 还活着时)
- Singleton 原子替换 :
provider.replace()让远端消费者无感知切换 - 旧 Facets 退休:逆序 dispose
- Keyed 服务重新 connect(旧实例关闭,新实例上线)
6. Service 系统:两种模式
6.1 Singleton(单例服务)
Provider 侧: Consumer 侧:
provider.provide(svc, impl) binding.use(svc) → proxy
provider.replace(svc, impl') binding.use(svc) → 同一个 proxy(透明切换)
provider.withdraw(svc) → 收到 "unavailable" update, facade.clear()
Singleton 生命周期:provider.provide() → replaced/unavailable。消费者的 proxy 对象永不变,内部 ServiceFacade 切换到新实现。
6.2 Keyed(键值服务)
Provider 侧: Consumer 侧:
const close = provider.spawn( binding.observe(svc, (svc, ctx) => {
svc, // 对每个 live instance 回调一次
"instance-key", handler(facade.proxy, context)
impl })
)
close() → emit("closed", address)
Keyed 每个实例有 address = { key, generation },generation 在 spawn 时自增,防止过期实例被处理。
6.3 ServiceMember 两种类型
每个 Service 的实现对象可以暴露两种 member:
| 类型 | 检测方式 | 远程语义 |
|---|---|---|
| method | typeof value === "function" |
`(args, ctx) → Promise<JsonValue |
| state | getReplicatedStateInternals(value) 非空 |
自动订阅增量更新序列 |
Provider 通过 classifyRemoteServiceImplementation() 扫描 implementation 的自有数据属性(非 getter/setter),分类为 method 或 state。
7. Replicated State 数据流
┌────────────────────────────────────────────┐
│ Provider 端 │
│ MutableReplicatedStateImpl (state.ts) │
│ │
│ tracker.state.field = newVal ← 变更 │
│ tracker.state.items.push(x) ← 变更 │
│ │ │
│ ▼ │
│ publish(context) │
│ ├─ ops = tracker.flush() │
│ ├─ sequence += 1 │
│ ├─ applyImmutable → publishedValue │
│ ├─ #sourceListeners (ops, seq, ctx) │
│ └─ #listeners (value, ctx, delivery) │
└───────┬────────────────────────────────────┘
│
Delta Op[] 序列化
│
▼ Wire 层
┌─────────────────────────────┐
│ state-codec.ts │
│ ServiceStateEncoder / │
│ ServiceStateDecoder │
│ (内部 Op ↔ WireOp 编解码) │
└────────────┬────────────────┘
│
Transport.invoke/subscribe
│
▼
┌──────────────────────────────┐
│ Consumer 端 │
│ ReplicatedStateReplica │
│ │
│ hydrate(seq, ops, ctx) │ ← 初始快照 (base batch)
│ applyImmutable(undefined, ops)
│ #value = result │
│ │
│ update(seq, ops, ctx) │ ← 增量更新
│ sequence 必须连续递增! │
│ applyImmutable(#value, ops) │
│ │
│ sequence gap → clear() │ ← 断链重置
└──────────────────────────────┘
关键设计:
- Immutable value:每次 publish/hydrate/update 后的 value 是不可变的,listen 回调拿到新引用
- Sequence 严格递增 :consumer 检查
sequence === expected + 1,gap 则 clear 并等待下一次 hydrate - WeakMap 注册表 :
registerReplicatedStateInternals(stateObj, internals)让 provider 能从 implementation 对象上"发现" state member
8. Context 传播机制
文件 :context/index.ts
Context 是 chord 中所有异步操作的"执行环境"。
功能:
• AbortSignal 传播 (可取消)
• 键值对注入 (通过 ContextKey<T>)
实现:
BaseContext (abstract)
└── EmptyContext → BACKGROUND_CONTEXT / TODO_CONTEXT
└── ContextValue(parent, key, value) → 链式叠加
工具函数:
withContextValue(key, value, parent) → 派生新上下文
withAbortSignal(signal, context) → 结合父 signal
withCancel(context) → 派生独立可取消子上下文
withoutAbortSignal(context) → 移除取消 (用于强制清理)
awaitWithContext(promise, context) → 等待或被取消
所有 Service method 签名要求最后一个参数是 Context:
TypeScript
method: (args: JsonValue[], context: Context) => Promise<JsonValue | void>
Consumer 侧自动从最后一个参数提取 Context:
TypeScript
#call(args):
context = args.at(-1) // 最后一个参数必须是 Context
businessArgs = args.slice(0, -1)
invoke(businessArgs, context)
9. Wire 协议(传输边界)
文件 :services/wire.ts
Wire 协议是 chord 在进程/沙箱边界 上使用的控制协议,以 "$chord.service" 为保留 Service ID:
TypeScript
// 控制调用
{ serviceId: "$chord.service", member: "catalogue", args: [] }
{ serviceId: "$chord.service", member: "subscribe", args: [subId, svcId, mode] }
{ serviceId: "$chord.service", member: "unsubscribe", args: [subId] }
// 返回值 (catalogue)
[{ serviceId: "...", mode: "singleton" }, ...]
// Subscription 初始快照
{
serviceId: "...", mode: "singleton"|"keyed",
instances: [
{ instance?: { key, generation },
members: [
{ name, kind: "method" },
{ name, kind: "state", sequence, ops: WireOp[] }
]
}
]
}
// 后续增量更新 (ServiceProviderUpdate)
{ type: "state", member, sequence, ops, instance? } // state 增量
{ type: "spawned", instance: ServiceInstanceSnapshot } // keyed 新实例
{ type: "closed", instance: {key, generation} } // keyed 关闭
{ type: "replaced", snapshot: ServiceInstanceSnapshot } // singleton 替换
{ type: "unavailable" } // singleton 下线
所有 Wire 类型都有严格的运行时校验函数 (parse* / assert*),防止跨边界注入畸形数据。
10. Facet Bundle(Node 工具链)
10.1 打包
TypeScript
import { bundleFacets } from "@earendil-works/chord/bundler";
const result = await bundleFacets({
facets: [...],
platform: "node",
// ...
});
// 产出 manifest.json + 代码 bundle
底层使用 esbuild 将 Facet 代码及其依赖打包为单一 artifact。
TypeScript
import { createFacetBundleLoader } from "@earendil-works/chord/node";
const loader = createFacetBundleLoader({ manifestPath, externalResolver });
const loaded = await loader.load(); // 加载并执行 bundle
const facets = loaded.facets; // 拿到 Facet[]
const host = await createFacetHost({ facets });
格式版本化:
FACET_BUNDLE_FORMAT_VERSIONFACET_BUNDLE_ARTIFACT_FORMAT_VERSIONFACET_BUNDLE_MANIFEST_FILE = "manifest.json"
11. 类型系统亮点
11.1 RemoteServiceContract 编译期约束
TypeScript
type InvalidRemoteMember<T> =
T extends ReplicatedState<infer TV>
? InvalidJsonPart<TV> extends never ? never : "state value is not JSON"
: T extends (...args: [...infer TArgs, Context]) => Promise<infer TResult>
? /* 验证 TArgs 和 TResult 都是 JSON */
: "member is not a remote method or ReplicatedState";
type RemoteServiceContract<T> = InvalidRemoteMemberNames<T> extends never ? T : never;
这段类型体操实现了:如果 Service implementation 暴露了非 JSON-compatible 的 member,编译期就会报错。
11.2 Service 身份类型
TypeScript
declare const SERVICE_TYPE: unique symbol;
interface Service<T> {
readonly id: string;
readonly local: boolean;
readonly [SERVICE_TYPE]?: (value: T) => T; // 类型品牌
}
SERVICE_TYPE 是 phantom type marker:Service<Foo> 不会意外匹配到 Service<Bar>。
12. 错误码体系
TypeScript
enum RemoteServiceErrorCode {
service_not_allowed // service 不在 allowlist
service_not_found // service ID 不存在
service_mode_mismatch // singleton vs keyed 冲突
service_member_not_found // member 名不存在
service_member_mismatch // method vs state 类型不匹配 / 描述变更
service_instance_not_found
service_stale_instance // generation 过期
service_invalid_value
}
所有错误码都有 isRemoteServiceErrorCode() 运行时类型守卫,方便跨边界传输后恢复结构化信息。
13. 安全性设计
| 维度 | 设计 |
|---|---|
| 路径安全 | RESERVED_SEGMENTS = {"__proto__", "constructor", "prototype"} 在所有 path 处理入口检查 |
| Op 校验 | assertValidOp / assertValidWireOp 严格验证每个操作码、arity、类型 |
| 数组安全 | 禁止 sparse array(delete arr[i] 抛错),禁止超出边界的 index |
| Service 命名 | "$chord." 前缀保留给内部控制调用 |
| Facet setup | 必须同步完成,不允许 Promise;生命周期断言 (assertSettingUp) |
| State 连续性 | Consumer 检查 sequence 必须连续,gap 则 clear 等待重同步 |
14. Chord 在 pi-main 中的定位
pi-main 分层架构:
┌─────────────────────────────┐
│ L5 产品层 (pi/tui) │
├─────────────────────────────┤
│ L4 编排层 (agent) │ ← agent harness 使用 chord 组装 Facets
├─────────────────────────────┤
│ L3 传输/集成层 (protocol) │ ← CBOR 传输适配 RemoteServiceTransport
├─────────────────────────────┤
│ L2 协议/服务层 (chord) │ ← 就是这里!
├─────────────────────────────┤
│ L1 基础层 (ai/utils/...) │
└─────────────────────────────┘
agent harness 的 plugins 系统基于 chord 的 Facet 机制,tool 调用通过 chord service 暴露,runtime 状态可以通过 replicatedState 跨进程同步。
总结
Chord 是一个设计极为紧凑的应用组合运行时:
- Delta 协议独立可复用------一个 Proxy-based 的 JSON 对象变更追踪系统,6 种 op 覆盖所有场景,支持 path interning + arity omission 的高效序列化
- Facet 系统通过拓扑排序解决插件依赖,提供热 reload 能力而不中断消费者连接
- Service 抽象统一了进程内/远程调用,singleton(单例)和 keyed(多实例)两种模式覆盖不同服务形态
- Replicated State 用 Delta Op 序列实现跨边界增量同步,sequence 机制保证严格有序
- Context 贯穿所有异步操作,提供取消信号和键值对传播
- Wire 层使用保留 service ID 实现控制协议,严格的运行时校验抵御跨边界攻击
- 类型系统通过 conditional type 在编译期锁定 remote service contract 的 JSON 安全性
整个包 ~2500 行核心代码(不含 node 工具链),零运行时外部依赖,所有复杂度都在内部精确管控。