T3 Code 源码拆解
工程化

21 · 设计取舍与可迁移经验

哪些设计是真正的系统杠杆,哪些复杂度是代价,以及何时不应照搬。

十个最有杠杆的决定

1. 本地 Server 是唯一执行边界

所有文件、Git、进程、PTY 和 Provider 会话集中在一处,让 Web/Mobile 都是可重建控制面。代价是远程接入和本地 daemon 生命周期复杂。

适用: 工作负载强依赖用户机器与私有工作区。
不适用: 任务天然在云沙箱执行、无本地资源依赖。

2. Thread 与 Provider session 分离

产品历史不会因供应商进程重启而丢失,多账号/切换 Provider 也有落点。代价是必须维护 binding、恢复与结算状态。

3. 单写入者事件溯源

一条队列给所有命令全序,纯 decider 与 projector 易测试,receipt 处理网络重试。代价是所有写吞吐经过一个 worker,Schema 演化更严谨。

适用: 单环境写吞吐中等、状态因果与恢复很重要。
不适用: 多节点高写入且无法按 aggregate 分区的系统。

4. 写时同步关系投影

Event log 与产品查询兼得,客户端快照不必重放历史。代价是写事务同时维护两种 representation,迁移和 invariant 测试更多。

5. Commit 后 Reactor

用户意图先成事实,Provider/Git 副作用异步执行并回流。失败可解释、可补偿。代价是“accepted”与“completed”分离,UI/调用者必须理解最终一致。

6. Provider 窄腰 + 双层归一化

Adapter 统一控制/事件形状,Ingestion 统一时序/领域含义。新增 SDK/协议不会侵入所有客户端。代价是映射层多、调试要关联原生与领域两条时间线。

7. 一个连接 supervisor

Factory 只尝试一次,registry 管环境 scope,supervisor 独占 retry。避免重试风暴和 UI 分叉。代价是所有 wakeup/认证变化都必须正确进入状态机。

8. 快照 + 有界增量,而非无限 replay

小 gap 用 cursor 修补,大 gap 重新取 snapshot,给 Server 内存与客户端恢复设硬边界。代价是 snapshot loader/reducer 要能可靠替换状态。

9. Git 临时 index + 隐藏 refs

可回退 Agent 代码又不污染用户 staged/branch。代价是需要理解 Git plumbing、worktree/common-dir 和危险 restore 边界。

10. 环境身份与访问路径分离

Direct、Relay、Tailscale、SSH/WSL 可以切换而不复制 project/thread。代价是 catalog、resolver、credential 与 endpoint 生命周期更复杂。

复杂度预算花在哪里

复杂度换来的系统属性
Event log + projections 可恢复 / 可解释 Idempotency receipts 安全网络重试 Provider registries 多 Provider / 多账号 Connection supervisor 移动网络韧性 DPoP + tickets + scopes 安全远程控制 Temp index + refs 非侵入回退 Effect layers + scopes 资源生命周期 Layered tests + drain 可演化性

这些复杂度不是平均分布。聊天气泡本身不难,真正成本集中在失败后的事实一致性、外部进程生命周期与跨端恢复。

值得警惕的风险

巨型协议与巨型组件

约 95 个 RPC 集中在 rpc.ts,ChatView/Sidebar 也非常大。集中契约有发现性优势,但冲突和认知负载会上升。 可演化方向是按 domain 分文件、最终组合成同一个 group;UI 则按 state ownership/interaction boundary 拆分,而非只抽 JSX。

双投影漂移

内存 projector 与 persisted relational projection 若不是共享足够语义,可能产生不一致。应建立 replay parity/事务测试, 并考虑让持久化 projector 复用纯 transition 结果,而不是重复业务判断。

单 worker 吞吐与队列容量

单写入者简化正确性,但 unbounded queue 会把持续过载变成内存增长。需要监控 queue/outstanding、command latency、event size, 在 RPC 边界限制高频输入;文本 delta 已通过 ingestion 合并,是正确方向。

Provider 兼容持续成本

五种上游都独立更新。生成 Schema、capability probe、unknown event 观测和 fixture 更新应自动化;否则“支持”会随上游漂移。

远程安全面扩大

Terminal、Git、preview automation 都是高权限。每新增 RPC 必须同时更新 scope mapping/test;pairing/ticket 日志要审计;Relay 只保存必要元数据。

本地数据库长期演化

40 轮 migration 意味着用户数据会跨很多版本。备份、schema version、migration failure repair 和 downgrade 提示应作为产品能力, 不能只假设重装清库。

如果从零实现一个较小版本

不要一开始复制全部复杂度。可以按风险递增:

第 1 阶段:单 Provider、本机 Web

  • Server 单执行边界;
  • contracts + thin RPC;
  • Thread 与 provider session 分开;
  • SQLite 最终状态 + command ID 幂等;
  • 一个 connection owner。

第 2 阶段:流式恢复与第二个 Provider

  • canonical event + ingestion;
  • event sequence、snapshot + bounded gap;
  • adapter registry 与 capability snapshot;
  • pending approval 持久化。

第 3 阶段:移动/远程

  • environment identity/access 分离;
  • pairing、scopes、short WS tickets;
  • supervisor offline/wakeup 状态机;
  • Relay 只做 discovery/tunnel。

第 4 阶段:Git/平台增强

  • checkpoint refs + temp index;
  • terminal replay buffer;
  • desktop process supervisor/IPC;
  • telemetry/preview 旁路。

事件溯源是否从第 1 阶段就采用,取决于产品对多端恢复、审计和复杂生命周期的确定性。若迟早需要,早期建 command/event boundary 通常比后期从 mutable session 状态迁移更便宜。

架构评审问题清单

  1. 哪个进程拥有工作区副作用?
  2. 用户对象是否与供应商 session 解耦?
  3. “命令接受”和“外部执行完成”是否被区分?
  4. 网络重试是否复用同一 idempotency key?
  5. 客户端丢事件后从哪里重建,gap 上限是多少?
  6. 是否只有一个连接 retry owner?
  7. Provider stream 异常结束时谁结算 turn?
  8. pending approval 能否跨断线/重启/设备恢复?
  9. 每个高权限 RPC 是否有 method scope?
  10. Git snapshot 是否触碰用户 index/HEAD?
  11. 高频流是否有 buffer/backpressure/容量边界?
  12. 数据库迁移失败和 binary downgrade 如何处理?

如果一个设计能清楚回答这些问题,它已经掌握 T3 Code 最值得借鉴的部分。

总结

T3 Code 的核心不是某个框架,而是一组互相加强的所有权选择:Server 拥有副作用,事件日志拥有事实,Provider adapter 拥有协议差异, supervisor 拥有重试,registry 拥有环境生命周期,Git driver 拥有工作区快照。复杂系统变可靠,通常不是添加更多 fallback, 而是让每件事只有一个明确所有者,并为失去增量状态设计重建路径。

On this page