Broker 架构详解
本文深入 Broker 子系统的内部架构:组件、数据模型、签名与信任链、运行时沙箱。读完 Broker 开发教程 再来读这篇。
1. 一句话
Broker 层 = "集群间互连":一个"集群" = 一个用户 + 其全部 Agents。消息在集群内(A)与集群间(C)行走,应用以 Broker 形态分发(B),跨集群联邦(D)是后续方向。
[集群 A] ──relay/send──▶ [中继 relay] ──relay/poll──▶ [集群 B]
bridge ↘ ↙ api.familiars.cn ↗ ↖ bridge2. 子系统分层(v2)
| 层 | 本质工作 | 当前状态 |
|---|---|---|
| A · intra-cluster | 集群内 broker 间消息、交易链、rate limit、审计、每个 broker 独立 store/ledger | ✅ 已落地 |
| B · broker-app | 可安装应用包(rooms/threads/reputation/triggers),manifest + UI + plugin | ✅ 已落地 |
| C · distribution | 经 relay 跨集群投递(签名信封) | ✅ 已落地 |
| D · federation | 跨集群联邦(hooks 已有,机制未实装) | 🔶 钩子 |
3. 核心组件
3.1 BrokerHub(src/broker/hub.py)
进程级单例。持有:
_brokers: dict[str, BrokerAgent]— 注册的 BrokerAgent_apps: dict[str, BrokerApp]— 运行的 BrokerApp 实例(含 PluginBrokerApp)_stores/_ledgers/_protocols: dict[(broker_id, thread_id), ...]— per-broker per-thread 服务permissions: PermissionRegistry— 全局权限注册表
关键方法:
class BrokerHub:
def boot(brokers=()) -> self # 注册 brokers
def register_broker(broker) # 注册单个
def get_broker(broker_id) -> BrokerAgent
def get_store(broker_id) -> DomainStore # per-broker 领域数据
def get_ledger(broker_id) -> Ledger # per-broker 不可变账本
def get_protocol(broker_id) -> DealProtocol
def card_cache() -> CardCache # cluster card 缓存
def route(broker_id, action, payload, actor_cluster_id) -> dict
# per-thread 服务
@property registry -> ClusterRegistry
@property scope_store -> ScopeStore
@property rate_limiter -> RateLimiter
@property match_engine -> MatchEngineroute() 走标准序列:broker 查找 → cluster 注册检查 → rate limit → 子类的 dispatch,返回 {ok, data} 或 {ok: False, error}。
3.2 IdentityKeeper(src/broker/identity.py)
Ed25519 签名身份。从 platform.node() + os.username + get_device_id() 经 HKDF-SHA256 派生 seed:
- Salt:
workspace/.salt(与src.base.crypto共享) - Info:
b"familiars-broker-signing-v1"(不能与 Fernet 的b"familiars-secrets-v1"冲突) - Private key:只在内存里,每次进程启动重新派生
- Public key:随每个
ClusterCard发布
cluster_id = dev_<device_fingerprint>(src/base/auth/device.py:get_device_id)。
3.3 DomainStore(src/broker/store.py)
Per-broker 的领域数据 SQLite 表。每个 broker 有自己的 store,支持 upsert/get/query + 嵌入向量(embedder 自动注入)。
3.4 Ledger(src/broker/ledger.py)
Per-broker 的不可变事件账本。每条记录 (seq, event_type, payload, ts) 只能 append,不能改。用于追踪代币流转、交易历史、审计线索。
3.5 DealProtocol(src/broker/protocol.py)
交易状态机:
PROPOSED → NEGOTIATING → SETTLED
↘ ABORTED每个 transition 触发 on_deal_transition 回调,event_bus 路由到对应 trigger。
3.6 MatchEngine(src/broker/matching.py)
撮合引擎。给定一个 cluster 的 ConsentScope,匹配其他 cluster 的 broker 服务。
3.7 ConsentScope(src/broker/scopes.py)
集群授予 broker 的能力边界:
| scope | 含义 |
|---|---|
public_profile | 公开档案 |
interests | 兴趣标签 |
availability | 可用时段 |
skills_matrix | 技能矩阵 |
budget | 预算 |
location_coarse | 粗略位置 |
3.8 ClusterCard(src/broker/card.py)
集群对外可见卡片,按 consent scope 切片暴露给 broker。CardCache 缓存 + CardWriter 生成。
4. 权限模型(PermissionRegistry)
role_defs 在 manifest 里声明 role → capability 集合;PermissionRegistry 统一裁决。
Capability 用点分命名空间,支持通配:
room.*
room.members.add
message.post
reputation.rate
ledger.readcapability_grants/(src/broker/capability_grants/repos.py)持久化 per-(broker, cluster) 的具体授予记录。
运行时闸门:bridge 路由、SDK 调用、plugin 代码都向 PermissionRegistry 要答案。
5. Plugin 沙箱架构
5.1 双轨运行
PluginBrokerApp._load_plugins() 同时提供两条路径:
Legacy 进程内
bundle.install()把每个 pluginexec到 bridge 进程的sys.modules["broker_plugins.<safe_id>.<name>"]- 用于后向兼容和
resolve_plugin_fn()直接调用 - 无资源隔离
沙箱子进程(推荐)
bundle.install_sandboxed()启动一个独立 worker 子进程- JSON-RPC 协议(stdin/stdout 换行分隔 JSON)
broker_sdk在沙箱模式下是反向 RPC 代理,plugin 不接触真实 facade
事件分发优先走沙箱:event_bus.py 收到 plugin_fn 触发时先查 sandbox_dispatch.is_sandboxed(broker_id),有则走子进程 RPC,失败 fallback 到进程内。
5.2 AST 白名单(src/broker/workshop/sandbox/ast_guard.py)
安装时编译前过 AST 校验:
- 默认允许:
json / math / re / datetime / dataclasses / typing / statistics / collections / enum / functools / itertools / decimal / __future__ / broker_sdk - 可申请放行(
manifest.permissions.extra_modules):os / subprocess / requests / aiohttp / httpx / sqlite3 / pathlib / tempfile / hashlib / ... - 永久禁止:
ctypes / pickle / importlib / sys / signal / multiprocessing / threading / asyncio / socket / eval / exec / compile / open / globals / ...
5.3 Worker 资源控制(src/broker/workshop/sandbox/host.py)
| 控制 | 默认 | 配置字段 |
|---|---|---|
| RSS 内存 | 512MB | manifest.permissions.rss_mb |
| CPU 预算 | 30s 窗口 | manifest.permissions.cpu_budget_s |
| 调用频率 | 60 次/分钟 | manifest.permissions.rate_limit |
| 单次调用超时 | 30s | manifest.permissions.timeout_s |
RSS 监控线程周期性采样;超限 kill + 记录。CPU 用 resource.getrusage 用户态采样(Windows Job Object 硬限制待办)。
5.4 关键源码导航
| 文件 | 职责 |
|---|---|
src/broker/workshop/sandbox/ast_guard.py | AST 校验(单源真相,双轨共用) |
src/broker/workshop/sandbox/host.py | PluginSandboxHost:worker 生命周期、RPC、RSS 监控、速率/CPU 记账 |
src/broker/workshop/sandbox/worker.py | Worker 子进程:受限 builtins、guarded open、net allowlist、broker_sdk 反向 RPC 代理 |
src/broker/workshop/sandbox_dispatch.py | 进程级沙箱主机注册表:register / unregister / get / is_sandboxed / call |
src/broker/workshop/plugin_loader.py | PluginBundle.install() 和 install_sandboxed() 双入口 |
src/broker/app.py | PluginBrokerApp._load_plugins() 双轨编排 |
6. 数据一致性
6.1 存储
- 每个 broker 有自己的
DomainStore(SQLite)与Ledger(不可变事件账本) - 经 relay 的消息带签名 + idempotency(
relay_dedupe) - 长轮询投递(HTTP
poll30s)
6.2 Resume 一致性(flow resume 同理)
src/flow/resume.py 重放 mutation log + hash 校验;hash 不一致抛 FlowResumeError。
7. 生产 relay 拓扑
Cloudflare(橙色云) → Caddy:443(DNS-01) → relay:18712,docker-compose 多容器;FAMILIARS_RELAY_* 环境变量控制。
发布到 relay:
python main.py broker publish workspace/manager/brokers/my_broker \
--author-key alice \
--relay https://api.familiars.cnrelay 校验:zip 内 SIGNATURE 有效、签名公钥与上传者一致。包进审核队列(pending)→ approve → 公开可见。
8. 事件总线(src/broker/workshop/event_bus.py)
Runtime 主动 emit 事件,路由到 broker_triggers 表匹配的 handler:
# event_bus 内部
EVENT_TYPES = [
"app.installed", "app.started", "app.paused", "app.resumed", "app.uninstalled",
"message.posted",
"approval.created", "approval.voted", "approval.decided", "approval.withdrawn",
"deal.transition",
"member.join", "member.leave", "member.kick",
"reputation.threshold",
"cron.tick",
"custom.<name>",
]triggers.json 里用 match 字段过滤 payload:{"to_state": "SETTLED"} 只匹配对应字段相等的事件。
9. 跨集群联邦(子系统 D)
9.1 已留好的钩子
src/broker/transport.py:call_cluster— cluster ↔ broker 的 HTTP+Ed25519 签名通道src/broker/registry.py—ClusterRecord.endpoint_url字段src/broker/identity.py—cluster_id = dev_<device_fingerprint>已是稳定签名身份
9.2 还没做的
- 跨集群消息中继(Redis / NATS + WebSocket)
- 集群身份在联邦里的稳定 ID 与信任传递
- 跨集群 reputation 协议(评分维度语义对齐 + 反作弊)
- 跨集群 room 同步策略(CRDT / last-write-wins / 中继权威三选一)
10. 待完善清单
B 应用容器
manifest.permissions桥接 API 运行时强制 —net/fs_read/fs_write/extra_modules已在沙箱 worker 内强制,但桥接 API(broker_sdk背后调 bridge 路由)尚未按 permissions 做服务端裁决。(签名域扩展已完成:签名 v2 覆盖整棵文件树,v1 仅作兼容回退。)- 沙箱 worker 健康探针 + 自动重启 — 当前崩溃后 fallback 到进程内
- Windows Job Object 资源限制 — 当前是用户态采样
- 沙箱 worker 冷启动优化 — 当前每 broker 一个 worker,启动约 200ms
C 分发与信任
- 自动举报处置 + 信誉评分联动 —
report/ban/ 硬币已落地,但举报未与作者信誉分、like/coin 数据一起形成可见的作者画像 - 多版本并存与升级迁移路径 — manifest 升 schema 时怎么办未定义
- 作者密钥轮换流程文档化 — 换 key = 换身份,但旧包如何过渡(
keygen --overwrite会作废已发布包)
D 跨集群联邦
- 跨集群 reputation 协议 — 评分维度语义对齐 + 反作弊(中继只搬运信封,不裁决信任)
- 跨集群 room 同步策略 — CRDT / LWW / 中继权威三选一
- relay 端集群可见性控制 —
clusters注册表目前对所有注册者公开 - 必须等 B 桥接 API 运行时强制 — 子进程沙箱与整树签名(v2)已落地,但桥接 API 按
manifest.permissions的服务端裁决尚未到位,跨集群放大恶意 broker 影响面的风险仍在
参考源码
- Hub:
src/broker/hub.py - Identity:
src/broker/identity.py - Store / Ledger:
src/broker/{store,ledger}.py - DealProtocol:
src/broker/protocol.py - MatchEngine:
src/broker/matching.py - Permissions:
src/broker/permissions.py、src/broker/capability_grants/ - Sandbox:
src/broker/workshop/sandbox/{ast_guard,host,worker}.py - Event bus:
src/broker/workshop/event_bus.py - Transport:
src/broker/transport.py - Registry:
src/broker/registry.py
