依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類 共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git), 讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等 per-branch 筆記。 - 02-prd/ 21 個檔(PRD、features、market-analysis 等) - 03-design/ 18 個檔(design-spec、wireframes、flows 等) - 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等) - 07-delivery/ 3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup) 合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv, 但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
8.1 KiB
8.1 KiB
ADR-008:Tunnel Client 採「程式碼複製」策略在多個專案間共享
狀態
Accepted — 2026-04-22(v1) Updated — 2026-04-22(v2,補充 §「visionA-backend 端 tunnel package 應刪除」)
背景 (Context)
Tunnel client(WebSocket + yamux)邏輯會在以下地方出現:
- POC edge-ai-platform:
server/internal/tunnel/client.go(原始來源) - visionA-backend:
internal/tunnel/(雛形 Q2 決策:從 POC 複製到此,獨立演進) - visionA Agent(本次規劃):也需要一份 tunnel client,連雲端 remote-proxy
問題:visionA Agent 的 tunnel client 要從哪來?有三個選項:
- A. 從 POC 再複製一份(獨立於 visionA-backend)
- B. 從 visionA-backend 複製(已經搬過一次,從新的來源搬)
- C. 共用同一份:git submodule、go module replace、或抽成獨立 library
決策 (Decision)
v2 修正:採方案 A(從 POC 直接複製到 local-agent),同時刪除 visionA-backend/internal/tunnel/。
v1 原決策:方案 B 從 visionA-backend 複製到 local-agent(已撤銷,原因見下方「v2 修正:visionA-backend 端 tunnel package 應刪除」)
具體做法:
cp -r edge-ai-platform/edge-ai-platform/server/internal/tunnel/ local-agent/internal/tunnel/- 調整 import path:
edge-ai-platform/pkg/wsconn→visiona-agent/server/pkg/wsconn(因為 agent 內已經從 local-tool 複製了server/pkg/wsconn) - 參數化
localAddr:讓NewClient()可接受httpserver.Controller.LocalAddr()回傳的 random port - 同步刪除
visionA-backend/internal/tunnel/整個 package(見下方說明)
與 ADR-007 一致:fork 後獨立演進,不主動 sync。
v2 修正:visionA-backend 端 tunnel package 應刪除(事實上已於 2026-04-21 刪除)
查證結論
經查證 visionA-backend 程式碼(2026-04-22):
- 目錄已不存在:
visionA-backend/internal/tunnel/在 2026-04-21 已被刪除(見visionA-backend/README.md§Known Issues) - 歷史上從未被 import:刪除前的版本,沒有任何 visionA-backend 程式碼 import 過這個 package
- README 已記錄此決策,但 v0.1 版的 visiona-agent-tdd.md 仍然提到「從 visionA-backend 複製 tunnel client」,需要更正
$ ls visionA-backend/internal/ | grep tunnel
(無任何結果 — 目錄不存在)
$ grep -r "internal/tunnel" visionA-backend/cmd/
(無任何結果 — 沒有任何 import)
$ grep "internal/tunnel" visionA-backend/README.md
324: ... 已於 2026-04-21 刪除 ...
本 ADR v2 的角色
本次 v2 更新不是觸發刪除動作(刪除已發生),而是:
- 把這個事實正式記錄進架構決策(之前只在 README)
- 修正 v1 ADR 的方向(原 v1 寫「從 visionA-backend 複製到 local-agent」,現改為「從 POC 直接複製到 local-agent」)
- 提供「為什麼當初會被複製進去 / 為什麼後來要刪」的完整脈絡,避免未來有人想加回去
為什麼當初會被複製進來
B3 階段(cmd/remote-proxy 實作時),為了「預留給未來 local-agent 用」而從 POC 複製到 visionA-backend。但實際上:
- visionA-backend 自己不需要 tunnel client — visionA-backend 是 tunnel 的雲端伺服器端,用
internal/relay/接受 agent 的入站連線,職責與 tunnel client(agent 出站連線)完全相反 - local-agent 沒必要繞道 — local-agent 直接從 POC 複製就好,多一層 visionA-backend 中轉沒有任何好處(POC 才是原始來源)
- 保留會誤導未來維護者 — 看到
visionA-backend/internal/tunnel/會以為 visionA-backend 有 tunnel client 角色,實際上沒有
決策
刪除 visionA-backend/internal/tunnel/ 整個 package(在 AB1 階段執行)。
職責對照(避免再混淆)
| Package | 位置 | 職責 | 連線方向 |
|---|---|---|---|
internal/relay/ |
visionA-backend | tunnel server:接受 agent 入站 WSS 連線,管理 yamux session pool | inbound(雲端被動接受) |
internal/tunnel/ |
local-agent(新位置,從 POC 複製) | tunnel client:主動撥 WSS 到雲端 relay,accept 反向 stream 並轉發到本機 HTTP server | outbound(agent 主動連出) |
internal/tunnel/ |
❌ 已於 2026-04-21 刪除(從未被使用,B3 預留誤導) | — |
原則明確化
程式碼不複製到不會用的地方。 「未來可能會用到」不是複製的理由 — 真正要用時再從原始來源複製即可。預先複製會:
- 增加維護負擔(要 sync POC 變更到一個沒人用的 package)
- 誤導維護者對架構的理解
- 違反 YAGNI
這個原則應該在所有 visionA 子專案間遵守(特別是 fork / 複製模式下)。
考慮過的替代方案
| 方案 | 優點 | 缺點 | 排除原因 |
|---|---|---|---|
| A. 從 POC 直接搬 | POC 是原始來源 | 代表 POC 要同時支援兩個下游,更新更分歧 | visionA-backend 已成為「雛形基準版」,從它搬更新 |
| C1. git submodule | 真共享、單一改動點 | submodule 在 monorepo 中難管理、CI 綁定複雜、協作者 clone 需額外步驟 | 過度複雜 |
C2. go module replace(replace directive) |
語法乾淨 | 要求有共同的 module,agent 和 backend module 不同;跨 go.mod 易造成 ghost dependency | 不自然 |
C3. 抽成獨立 visiona-tunnel repo |
最乾淨的 library 模式 | 需要設計 stable API、發版、文件;維護 3 個 repo 的版本矩陣;只有 2 個 consumer不值得 | 過度工程 |
C4. Monorepo root 放 pkg/tunnel/,agent + backend 皆 import |
真共享且管理集中 | 整個 visionA monorepo Go module 結構需要重組(目前是每個子專案一個 module);對 local-tool 本來獨立的狀態有風險 | 風險大 |
| B(本決策):code copy | 簡單、無新工具鏈、符合既有 fork 模式 | 未來 tunnel 協定要升版時要改 3 個地方(POC / backend / agent) | tunnel 協定已穩定,改動頻率低,維護成本可接受 |
後果 (Consequences)
正面影響
- 無新工具鏈:純
go.mod+ 普通目錄複製,沒有 submodule / replace 黑魔法 - 一致性風格:與 ADR-007 的 fork 策略一致
- 改動自由:visionA Agent 的 tunnel client 可以加專屬功能(Agent-Version header、重連事件 hook)而不影響 backend
負面影響(接受的取捨)
- 三份程式碼共存:POC / visionA-backend / visionA Agent 各一份 tunnel client
- Bug 修復需要 3 處同步:例如修了 reconnect 退避演算法,3 邊都要 review 是否跟進
- 未來的協定升級(如 HTTP/3)需要 3 次修改
風險
- 遺忘同步導致差異累積:明確的 mitigation — tunnel 協定變更必須寫新 ADR,同時 review 是否 cherry-pick
- POC 已經是死分支:預期 POC 不會再大改;主要 sync 路徑是 visionA-backend ↔ visionA Agent
Phase 1 重新評估條件
若出現以下任一情形,重新評估(可能抽成 library):
- tunnel 協定有 breaking change(HTTP/3 / gRPC 切換 / E2E 加密)
- Consumer 增加到 4+ 個(例如出現 headless tunnel-only agent)
- 實際出現 2+ 次的「bug 只修了 A 忘了修 B」事件
合規性
- 與 Q2 決策(POC → visionA-backend 複製)一致
- 與 ADR-007(fork 模式)一致
- 建立 tunnel 變更 checklist:TODO(在第一次實際跟進 local-tool 或 backend 變動時產出)
相關文件
.autoflow/04-architecture/tunnel.md- ADR-002(沿用 POC tunnel 協定)
- ADR-007(visionA Agent 架構)
- 相關程式碼:
/Users/jimchen/Innovedus/edge-ai-platform/edge-ai-platform/server/internal/tunnel/client.go(POC 原始 — 唯一存活來源)(v2 決策刪除,從未被使用)visionA-backend/internal/tunnel/visionA-backend/internal/relay/(保留 — 這是 tunnel server 端,與 client 不同職責)local-agent/internal/tunnel/(本次新增 — 從 POC 直接複製)