Pi Agent Chord 架构深度剖析

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(线上格式压缩)

WireOpOp 基础上增加两层压缩:

  1. Path Interning :第二次使用某路径时发送 ["#", id, path] 定义,后续用 id 引用
  2. 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(最优场景,只生成一个 p op)
  • pop/shift/splice 标记为 diffreplace
  • sort/reverse 强制 diff

String 优化

  • 检测 after.startsWith(before) → 生成 a op
  • 检测字符串重叠(overlap 算法)→ 生成 t + a 组合

4.4 apply / applyImmutable

  • apply(target, ops)原地变更 target,返回修改后的引用
  • applyImmutable(target, ops)不可变方式,逐层复制容器后应用变更

两者都经过严格的 路径安全检查 (防止 __proto__ 注入)。


5. Facet 生命周期状态机

核心类FacetLifecyclefacets/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) 是一个精心设计的不中断替换流程:

  1. 重新 setup 同名 Facets(新实例)
  2. 验证 shape 不变(requires/provides 签名必须一致)
  3. 激活新 Facets(在旧 Facets 还活着时)
  4. Singleton 原子替换provider.replace() 让远端消费者无感知切换
  5. 旧 Facets 退休:逆序 dispose
  6. 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_VERSION
  • FACET_BUNDLE_ARTIFACT_FORMAT_VERSION
  • FACET_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 是一个设计极为紧凑的应用组合运行时

  1. Delta 协议独立可复用------一个 Proxy-based 的 JSON 对象变更追踪系统,6 种 op 覆盖所有场景,支持 path interning + arity omission 的高效序列化
  2. Facet 系统通过拓扑排序解决插件依赖,提供热 reload 能力而不中断消费者连接
  3. Service 抽象统一了进程内/远程调用,singleton(单例)和 keyed(多实例)两种模式覆盖不同服务形态
  4. Replicated State 用 Delta Op 序列实现跨边界增量同步,sequence 机制保证严格有序
  5. Context 贯穿所有异步操作,提供取消信号和键值对传播
  6. Wire 层使用保留 service ID 实现控制协议,严格的运行时校验抵御跨边界攻击
  7. 类型系统通过 conditional type 在编译期锁定 remote service contract 的 JSON 安全性

整个包 ~2500 行核心代码(不含 node 工具链),零运行时外部依赖,所有复杂度都在内部精确管控。

相关推荐
好好生活中1 小时前
把“信任”设计进系统:从胖东来的前置服务,看服务型组织的架构思维
架构
m0_587383002 小时前
外卖CPS系统开发实战:从架构设计到运营落地全指南
java·spring·小程序·架构·需求分析
Yanjun2i2 小时前
Agent学习记录三:完成 Agent Loop
python·学习·agent
头茬韭菜2 小时前
第 0 篇:「Mem0 深度分析与文档系列计划」—— 记忆层基础设施的源码级解读路线图
架构·mem0·agent长记忆
天空属于哈夫克33 小时前
企业微信开发怎么做:架构、队列与上线清单
架构·企业微信
梦因you而美3 小时前
LangChain-ReAct-Agent 智能客服系统 · 项目技术文档
langchain·agent·fastapi·扫地机器人·langgraph·rag 检索增强·react 智能客服
smartpi_ai3 小时前
原理图上的 1.2V 是输入还是输出?蜂鸟 M 裸芯供电架构、CORE 滤波设计与外围器件三规矩
单片机·嵌入式硬件·架构
萧瑟余晖3 小时前
持久层选型与框架对比详解
开发语言·架构
Csvn3 小时前
第 21 章 排错与调试实战
人工智能·aigc·agent