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 生命周期更复杂。
复杂度预算花在哪里
这些复杂度不是平均分布。聊天气泡本身不难,真正成本集中在失败后的事实一致性、外部进程生命周期与跨端恢复。
值得警惕的风险
巨型协议与巨型组件
约 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 状态迁移更便宜。
架构评审问题清单
- 哪个进程拥有工作区副作用?
- 用户对象是否与供应商 session 解耦?
- “命令接受”和“外部执行完成”是否被区分?
- 网络重试是否复用同一 idempotency key?
- 客户端丢事件后从哪里重建,gap 上限是多少?
- 是否只有一个连接 retry owner?
- Provider stream 异常结束时谁结算 turn?
- pending approval 能否跨断线/重启/设备恢复?
- 每个高权限 RPC 是否有 method scope?
- Git snapshot 是否触碰用户 index/HEAD?
- 高频流是否有 buffer/backpressure/容量边界?
- 数据库迁移失败和 binary downgrade 如何处理?
如果一个设计能清楚回答这些问题,它已经掌握 T3 Code 最值得借鉴的部分。
总结
T3 Code 的核心不是某个框架,而是一组互相加强的所有权选择:Server 拥有副作用,事件日志拥有事实,Provider adapter 拥有协议差异, supervisor 拥有重试,registry 拥有环境生命周期,Git driver 拥有工作区快照。复杂系统变可靠,通常不是添加更多 fallback, 而是让每件事只有一个明确所有者,并为失去增量状态设计重建路径。