ADR-0002: 业务 = REST/SSE; gRPC = 观测面¶
- Status: Accepted (方案 A 修正版落地)
- Date: 2026-04-26
- Deciders: PM (产品经理) + Flyto Agent core team
- Related code:
platform/common/internal/server/,platform/common/cmd/common/main.go,platform/common/internal/api/grpc/proto/{health,safetychain}.proto,deploy/docker-compose.yml,deploy/Caddyfile - Related TODO:
core/TODO.mdL692 (登记 + 完成同日, 2026-04-26); 衍生 tracked debt L693 / L694 / L695 - Commit chain:
01f08e7(C1) →8189d05(C2) →694bd07(C3) →e1db327(C4) →c35e761(C5) → C6 (本 ADR) - Cross-reference: ADR-0001 (反向思维 gate); memory
feedback_architecture_principle_over_rule_of_two.md(gRPC 优 HTTP 是 SafetyChain 通路铁律)
1. 背景 / Context¶
platform/common/internal/server/server.go 早期写好了 1263 行业务 REST/SSE
实现 (SSE 流式 / CORS / Bearer auth / IP 限速 / 15s 心跳 / 会话 CRUD / panic
recovery / request-id 中间件), 但 cmd/common/main.go 没启动它. 注释明说
"REST/SSE 仍由 internal/server 管理, 目前和本二进制正交; 未来 REST 侧需要同
一套 auth/tenant 接线时, 再并入这里." -- 即计划好了等连, 还没并入.
PM 在 L569 反事实工作流缩水后提新方向: 消费者语言无关, 引擎自身无需为特殊 语言频繁优化. 论据是: logistics platform 是 C#, 但未来可能加 Java/Python 等其他行业 platform. 如果业务通道走 gRPC, 每加一种语言就要 protoc gen + 维护 stub, 每个新行业要复制业务 RPC. 走 REST 则任何 HTTP 客户端通用.
但 9 天前 (2026-04-17) PM 拍板的 SafetyChain 方向是 "C# 走 gRPC 优于 HTTP"
(memory feedback_architecture_principle_over_rule_of_two.md 记载). 这个
决定基于: SafetyChain 是 platform/common 内部观测面, 给 C# logistics 拉
verdict / breaker 状态, 是后台到后台的强类型 RPC, gRPC 适配.
两个方向看似矛盾. 本 ADR 的核心工作就是阐明: 它们不冲突, 是 bifurcation (分层) 不是 U-turn (反悔).
2. 决策 / Decision¶
采纳方案 A 修正版 (激活 server.go + 改造接 OIDC/tenant/safetychain wire + 不加 grpc-gateway + 立 ADR), 拒绝原方案 A 完整版 (含 grpc-gateway 给观测 RPC 加 REST 镜像).
2.1 业务通道 = REST/SSE (/api/v1/* → common:8080)¶
- 引擎消费层 (
engine.Run/engine.Session) 唯一业务通道走 HTTP/1.1 + SSE - 端点形态 (server.go:286+ 已实现):
GET /api/v1/health/POST /api/v1/agent/run(SSE 流式) /POST /api/v1/sessions/GET|DELETE /api/v1/sessions/{id}/POST /api/v1/sessions/{id}/messages/POST /api/v1/sessions/{id}/permissions/{request_id} - 鉴权: OIDC bearer token via
auth.HTTPMiddleware, 与 admin HTTP 共用同一 套 Verifier (commit 2 把 raw shared-secret BearerToken 删除, 不再两套) - streaming: SSE
text/event-stream(text_delta/tool_use/done等 named events), W3C 标准, 浏览器 EventSource 原生支持 - 任何语言任何 HTTP 客户端都能消费, 不需要预先 codegen stub
- C# logistics platform 业务调用走 HttpClient (Refit / RestSharp 强类型 绑定), 不需要写业务 gRPC stub
2.2 观测通道 = gRPC (:9090)¶
- SafetyChainService (ListVerdicts / ListBreakerStates) + HealthService 仅 gRPC. 这两个是 9 天前 (2026-04-17) PM 拍板的内部强类型观测 RPC, 沿用 不变
- 不再加任何业务 gRPC RPC. C# logistics 后续业务调用走 REST/SSE
- 观测的 HTTP 镜像由 admin/server.go 单独实现 (
/admin/safetychain/verdicts /admin/safetychain/breakers), 不引 grpc-gateway 重复一份
2.3 Tool 级安全链装饰是行业 platform 责任¶
- cmd/common 起 REST listener 后不自动调
safetychain.Assemble装饰 Tool. 一刀切 AlwaysApprove + DefaultExtractor 会把所有 Tool 锁同一组合 或 fan out 到不匹配 Tool 语义的 wrap. - common 保持纯 transport. verdictStore + breakerRegistry 接线就绪, 由行业
驱动代码 (logistics 等) 在 Tool 注册时调
safetychain.Assemble装饰并 写数据. v0.3 阶段 verdictStore 仍然空 (C# logistics 的业务 Tool wire 还没起步).
2.4 端口拓扑¶
cmd/common 进程
├ :9090 gRPC — Health + SafetyChain (compose 内部, 不映射 host)
├ :8081 admin HTTP — /admin/health, /admin/version, /admin/tenant,
│ /admin/safetychain/{verdicts,breakers} (opt-in)
└ :8080 business REST — /api/v1/agent/run (SSE), /api/v1/sessions/*, /api/v1/health
(新增, 由 --rest-addr 启用)
Caddy 边界: hub.flytoex.net/api/v1/* → common:8080 (flush_interval -1 +
response_header_timeout 0); /admin/* → common:8081; 其他 → logistics:8080.
3. 评估流程 / Evaluation Process¶
3-agent 并行 review (memory feedback_agent_teams_review_mode.md 模式), 三方
独立产出报告后主线程 reconcile, PM 二次拍板.
3.1 调研 agent (业界证据)¶
调研 11 个主流 LLM Agent / SaaS 框架的 transport 选型:
| 框架 | 业务 transport | streaming | 双通道? |
|---|---|---|---|
| Anthropic Messages | REST | SSE (named events) | 无 gRPC |
| OpenAI Chat / Assistants | REST | SSE | 无 gRPC |
| AWS Bedrock | REST | binary application/vnd.amazon.eventstream |
无 gRPC |
| Google Vertex AI | gRPC + REST mirror | gRPC streaming | 同源 proto transcoding |
| Cohere / Mistral / Replicate / Together / Fireworks | REST | SSE | 无 gRPC |
| Ollama | REST | NDJSON | N/A 单进程 |
| LM Studio | REST (OpenAI 兼容) | SSE | N/A |
| LangServe | REST (FastAPI auto) | SSE / chunked HTTP | 无 gRPC |
业界共识: 10 / 11 公开 REST/SSE, 仅 Vertex 用 gRPC. Vertex 是 Google 内部全栈 gRPC 的延伸, 不仿照. streaming 共识: SSE 是 LLM 子领域共识 (Ollama / Bedrock 是少数派但仍是 HTTP/1.1 chunked, 不是 gRPC).
双通道 (业务 REST + 观测 gRPC) 在 LLM 圈零先例, 但与 LLM 主流 (REST 业务) 对齐. 唯一合法 prior art 是 GCP transcoding (一份 proto 同时长出 gRPC + REST), 不是 etcd/K8s 的反向 (gRPC 主, REST 副).
grpc-gateway 调研: v2.29.0 (2026-04-16) 健康活跃, 19,846 stars, etcd /
Istio 生产案例. 给我们场景 (SafetyChainService 加 REST 镜像) 价值窄: admin
/ server.go 已实现 handleSafetychainVerdicts + handleSafetychainBreakers
REST handler, 重复.
gRPC-Web 现状: Google 官方 grpc-web 仍要 Envoy. ConnectRPC 是 2024-2025 转向的现代替代 (Buf 自家替换 grpc-web 为 connect-web), 不需要 Envoy. 但 Flyto 没有浏览器前端需求, 这一项不影响本决策.
3.2 质疑 agent (6 道硬质疑, 4 个 blocker)¶
| Q | 严重程度 | 关键发现 |
|---|---|---|
| Q1: SSE streaming/性能坑 | 严重 | Caddy proxy buffering / Chrome 6-conn / 1000 单/s 带宽 / mobile NAT — Q1.1 上线 100% 踩坑须 Caddyfile flush_interval -1 |
| Q2: auth 双份维护 | 致命 | server.go 用 raw BearerToken ConstantTimeCompare, 不走 OIDC. 接进 cmd/common 后跑两套不兼容 auth. 必须 commit 2 单独修 |
| Q3: 端口/mux 整合 | 严重 | 同端口分路径 vs 双端口, 没拍板必返工. 推荐双端口 |
| Q4: grpc-gateway 价值 | 致命 | admin/server.go 已有 /admin/safetychain/{verdicts,breakers} REST handler, 加 grpc-gateway 是把同一表面再做一次. 砍 |
| Q5: ADR-0002 立硬规则 | 严重 | 与 9 天前 "gRPC 优 HTTP" 看似对冲. 实为分层. ADR 须显式 cross-reference |
| Q6: rollback / migration | 严重 | server.go in-memory sessions 单实例假设, k8s 多副本立刻挂. ADR 须显式标注限制 |
3.3 设计 agent (4 备选 + 推荐方案 A 修正版)¶
| 方案 | 范围 | commit 数 | 优点 | 代价 |
|---|---|---|---|---|
| A 完整版 | 激活 + grpc-gateway 镜像 + ADR | 8-10 | 观测 RPC 双通道 | 与 Q4 冲突, admin 已有 REST handler 重复 |
| A 修正版 | 激活 + 不加 gateway + ADR | 6 | 3-agent 共识 | server.go 改造工作量 5 处替换非 trivial |
| B 只激活不加 gateway 不立 ADR | 同 A 修正版减 ADR | 5 | 最快 | 失去架构原则记录, 下次再有人想加业务 RPC 又要重新讨论 |
| C 重写 server.go | 不复用 1263 行 | 10+ | 干净起步 | 浪费已踩坑过的 SSE/permission 桥接/限速实现 |
| D 全 gRPC 化业务 | 与 PM 方向冲突 | 12+ | C# 强类型 stub | LLM 圈零先例, sanity check 通过即砍 |
设计 agent 自行砍掉 grpc-gateway (与 Q4 共识), 推荐 A 修正版.
3.4 PM 拍板与 reconcile¶
主线程 reconcile 3 份报告, 关键结论: 1. grpc-gateway 砍 (3 方共识, admin 已有 REST handler) 2. ADR-0002 立, 但定性 bifurcation 不是 U-turn — 9 天前 SafetyChain 走 gRPC 是观测面方向, 现在仍成立; PM 新拍 "业务用 REST" 是补另一面 3. server.go 改造工作量 = 5 处替换 (auth OIDC / 删 provider 写死 / 加 tenant / 接 safetychain wire / 拆 signal), 6 commit 节奏对 4. 双端口拓扑 (业务 :8080 + admin :8081 + gRPC :9090) 5. Caddyfile flush_interval -1 必须显式 (Q1.1 缓解) 6. 4 tracked debt 登记: in-memory sessions / cross-transport request-id / SSE 带宽 / Swagger 与 L407 关联
PM 拍板 A 修正版后开干. 6 commit 节奏完整落地.
4. 后果 / Consequences¶
正面¶
- 与 LLM 业界 10/11 主流 (REST/SSE 业务通道) 对齐, 任何 HTTP 客户端通用
- 浏览器 EventSource 原生支持, 未来加 Web dashboard 零代理依赖
- C# logistics 不需要为业务调用写 gRPC stub, HttpClient (Refit / RestSharp) 即用; 后续加新行业 (Java / Python) 同样不需要 codegen
- OIDC 鉴权跨 gRPC + admin HTTP + 业务 REST 同一套 Verifier, dev 模式 Verifier=nil 跳过与 admin 一致
- SafetyChain / Health gRPC 观测面保留, 沿 9 天前方向不破坏
- common 保持纯 transport, Tool 级 SafetyChain 装饰由行业 platform 自决 + 自填 Validator + extractor, 避免 cmd/common 一刀切锁死单一组合
负面¶
- 失去 gRPC 强类型契约, REST 协议靠 OpenAPI/Swagger 文档维护 (L407 直接 子项)
- SSE framing 比 gRPC binary 多 5-10x 带宽 overhead (Q1.3); 1000 单/s 上量后跨 region 流量成本要量化 (L695 monitoring)
- in-memory sessions 单实例假设, k8s 多副本要换 SessionStore (L693)
- gRPC + REST cross-transport request-id / trace 串通要 OpenTelemetry 投入 (L694)
中性¶
- ADR-0002 是 "当前阶段默认 + 决策上下文", 不是硬规则. 触发 § 6 重评估 条件可改方向
5. 替代方案保留 / Alternatives Preserved (CLAUDE.md 原则 5)¶
方案 A 完整版 (含 grpc-gateway)¶
如果未来 SafetyChain 观测 RPC 加到 5+ 个且 admin/server.go 手写镜像维护 负担显现, 可以从 A 修正版升级到 A 完整版. 升级路径:
- SafetyChainService.proto 加
option (google.api.http) = { ... }注解 - Makefile 加 protoc-gen-grpc-gateway / protoc-gen-openapiv2 step
- cmd/common 加 grpc-gateway runtime.ServeMux, 路径
/api/observe/* - admin/server.go 现有 REST handler 视情况 deprecate 或保留作为 fallback
预期升级成本: ~300 行 Go (含 generated code) + 1 个新依赖 (grpc-ecosystem/grpc-gateway/v2). 触发条件: 见 § 6.
方案 D 全 gRPC 化业务 + grpc-gateway 反向产 REST¶
与 PM 拍板的方向冲突 (REST 业务). 列出来作 sanity check, 已 PM 二次确认 不走. 触发重评估: 见 § 6.
不立 ADR (方案 B)¶
放弃记录架构原则的代价是: 下次有人想加业务 RPC 时又要重新讨论. 已选立 ADR.
重写 server.go (方案 C)¶
放弃 1263 行已踩坑过的 SSE / permission 桥接 / 限速实现, 浪费. 已 PM 拍板复用.
6. 触发重新评估的条件¶
未来出现以下任一情况, 应重新评估 ADR-0002 的决定:
- SSE 性能瓶颈实测: logistics platform 上量后, SSE 跨 region 带宽 成本超过 gRPC streaming 5x (Q1.3 数学预测), 且 L695 monitoring 数据 支持
- 第二个非 C# 行业 platform 出现 gRPC 强需求: 如银行业 ERP 要求"高频 RPC 必须 gRPC + mTLS 过合规审计", rule of two 满足后 重评估业务 transport
- 业界共识改变: Anthropic / OpenAI / Bedrock 任一在自家公开 API 引入 gRPC 业务通道并稳定运行 6 个月以上
- 观测 RPC 数量爆炸: SafetyChain 加到 5+ RPC 且 admin/server.go 手写镜像维护负担显现, 触发 § 5 方案 A 完整版升级
- grpc-gateway 生态激进变化: ConnectRPC 全面取代 grpc-gateway 后, 升级 路径要重写 (但当前 ConnectRPC 与 Flyto 无浏览器前端需求无关, 不影响)
满足任一即可重新走 3-agent review 评估升级 / 切方向.
7. 参考链接¶
业界 LLM API 文档¶
- Anthropic Messages API streaming
- OpenAI Chat Completions streaming
- AWS Bedrock InvokeModelWithResponseStream
- Google Vertex AI gRPC predict
- Google AIP-127 HTTP/gRPC transcoding
- Cohere streaming
- Mistral La Plateforme
- Ollama API docs
- LM Studio OpenAI Compatibility
- LangServe
- Stainless customer page (OpenAI / Anthropic / Meta SDK auto-gen)
gRPC / 替代方案¶
- grpc-gateway repo
- etcd grpc-gateway docs
- APISIX K8s skip grpc-gateway analysis
- grpc-web repo
- State of gRPC in browser
- ConnectRPC connect-es
- Buf Connect-Web announcement
项目内部参考¶
core/docs/adr/0001-reverse-thinking-gate.md— ADR 体例参考~/.claude/projects/-home-admin-ccm/memory/feedback_architecture_principle_over_rule_of_two.md— gRPC 优 HTTP 是 SafetyChain 通路铁律 (本 ADR 的 cross-reference 论据)platform/common/internal/server/server.go— 业务 REST/SSE 实现 (1263 行)platform/common/cmd/common/main.go— 三 listener wire (gRPC + admin HTTP- 业务 REST)
platform/common/internal/admin/server.go— admin 观测面 (含 /admin/safetychain/* REST handler, 复用论据见 § 2.2)platform/common/internal/auth/middleware.go— auth.HTTPMiddleware, 跨 admin + 业务 REST 共用deploy/docker-compose.yml+deploy/Caddyfile— 端口拓扑 + Caddy 路由
8. 修订记录¶
| 日期 | 版本 | 变更 |
|---|---|---|
| 2026-04-26 | v1.0 | 初稿. 方案 A 修正版落地 6 commit, ADR 起草 |