Skip to content

Latest commit

 

History

History
49 lines (44 loc) · 22 KB

File metadata and controls

49 lines (44 loc) · 22 KB

AgentHub Decisions

最后更新:2026-09-04

本文件是当前架构决策摘要。旧 ADR 全文已外迁到 docs/history.md 指向的 TokenDance docs archive;旧正文只作追溯,不覆盖 AGENTS.mddocs/architecture.mddocs/architecture/api/ 或当前源码事实。

ID 状态 当前结论 Owner 仍有效
ADR-001 Accepted AgentHub 保持 Hub/Edge 双层架构:Hub 负责账号、IM、同步、路由、审计;Edge 负责本地执行、adapter、workspace、事件和本地持久化。 Architecture / Hub / Edge
ADR-002 Accepted 运行事件走 typed WebSocket event stream;CLI NDJSON/JSONL 只在 Edge adapter 内解析,REST 负责查询和普通 RPC。 API / Edge / Frontend
ADR-003 Accepted 服务端状态使用 TanStack Query,客户端 UI 临时状态使用 Zustand;具体实现以 app/shared、Desktop/Web 当前代码为准。 Frontend
ADR-004 Accepted Edge 使用 Go os/exec + AgentAdapter 管理 Agent CLI/SDK runtime,进程生命周期由 Edge lifecycle 承担。 Edge
ADR-005 Accepted, updated 2026-06-27 worktree 隔离、短分支、PR 合并和清理仍是并行 Agent 工作的基本规则;模型/供应商路由改由 AGENTS.md 和 active skills 约束。 Repository governance 是,已更新
ADR-006 Accepted Agent 间协作以 Hub typed TeamAssignment / TeamRun 为事实源;IM message 只是人可读投影,不作为 Agent 决策输入。 Hub / Product model
ADR-007 Accepted Edge runtime 统一到 AgentAdapter + Registry + EventEmitter;新增 runtime 通过 adapter 接口接入,不让前端感知 CLI 差异。 Edge adapters
ADR-008 Accepted, partially superseded 设计 token 化原则仍有效;旧 --glass-* 细节是历史实现,当前共享设计合同以 TokenDance design docs、OKLCH/CSS Modules 和 --td-* intent 为准。 Frontend design 部分有效
ADR-009 Accepted, historical SettingsPage 拆分为 section/primitives/lazy lanes 的方向有效;是否已完成以当前 Desktop/Web settings 代码为准,不作为新 backlog。 Frontend settings 历史有效
ADR-010 Accepted Edge 暴露 MCP-compatible capability 时以 Edge auth、tool schema、transport 和当前 endpoint 代码为准;不要为每个外部工具做专用协议。 Edge MCP
ADR-011 Accepted 前端保持 pnpm workspace / shared package 架构;Desktop/Web/Mobile 共享类型和 UI contract,平台能力留在各平台 adapter。 Frontend platform
ADR-012 — 未单独成档 历史审计未找到 ADR-012 记录(git log 与归档索引均无)。编号跳过,不补记;后续新决策从 ADR-024 续号。
ADR-013 Accepted, historical Hub app 启动入口应保持职责拆分:wiring、events、background、admin、router 等边界清楚;当前文件布局以源码为准。 Hub server
ADR-014 Accepted, partially realized Agent team service 应按 CRUD、member、run、compete、routing、events 等领域拆分;当前落地情况以 hub-server/internal/service/ 为准。 Hub service 部分有效
ADR-015 Accepted 避免服务间循环依赖;优先窄接口、构造函数显式依赖和事件中介,不用 setter 注入掩盖循环。 Hub architecture
ADR-016 Accepted Hub->Edge dispatch 需要 delivery outbox / ACK / retry / dead-letter 语义,避免 fire-and-forget 造成状态永久分歧。 Hub reliability
ADR-017 Accepted Hub/Edge 授权区分身份令牌和 per-run capability token;真实安全边界以当前 JWT/capability 实现和风险登记表为准。 Edge auth / Security
ADR-018 Accepted 仓库根布局:README/AGENTS/LICENSE/go.work/.github 与编辑器/CI 根配置必须留在根目录;不 bulk move。根 docker-compose.yml + .env.example 暂留;仅当 scripts/dev 与 verify 同改时可迁 deployments/dev/。正文已外迁(见 history.md)。 Repository layout
A-V1 Accepted 2026-08-03;landed 2026-08-03 (#1526/#1566) adapters/lifecycle 拆分裁决:lifecycle 不拆(D-V1 已解 god-function 主痛点,纯谓词以文件名前缀表达足矣);adapters 只做定向抽取——adapters/orchestrator 13 源文件迁入叶子包(插件式单向依赖,叶子仅依赖 internal/orchestration 合同 + 窄 ports,composition root 装配),驳回全量叶子包化。Step 0 合同 SSOT 抽离 #1526;Step 2 orchestrator 叶子包迁移 + verify-orchestrator-deps.py 依赖方向门禁 #1566。正文已外迁(见 history.md)。 Edge adapters
A-V3 Accepted 2026-08-03;landed 2026-08-03 (#1525) @agenthub/shared 拆分裁决:驳回全量 hub/edge 三分(churn 高一个数量级、收益被既有 14 层边界门禁吃掉、mobile-rn 作为 hub-only 客户端仍需 hubClient);采纳两个 quick-win——剔除零消费的 apiClient.ts 死表面、workspace:* 显式依赖声明;edge 表面隔离门禁 verify-shared-edge-surface-isolation.py 硬化(web/mobile 禁 import edge 表面)。正文已外迁(见 history.md)。 Frontend boundary
ADR-019 Accepted 2026-08-09 bus/ws 单源:WebSocket 事件流走单一 bus 抽象,禁止前端/Edge 各自分叉 event 解析与重放;reconnect 补数据、dispatch 语义、停机广播、jitter、CAS 由 bus 统一承担。 Frontend / Edge / API
ADR-020 Accepted 2026-08-09 execution target target_type 对齐:DB CHECK 枚举与 service/route 层 target_type 取值集合保持同名同序,service 校验与 DB 兜底不得漂移;组合约束(local_edge/hub_relay 需 route/device,remote_* 需 host)留 service 层。 Hub / Edge
ADR-021 Accepted 2026-08-09 安全门禁四修(AH-SR-051):迁移触发器双炸弹修复链、Go toolchain 1.26.5(stdlib vuln GO-2026-5037/5038/5039)、i18n callsites ratchet 接线、文档版本对齐。 Security / CI
ADR-022 Accepted 2026-08-09 迁移触发器修复原则:已发布迁移不可变(不改 0040/0058.up/0060.up/0016.up),新建后续迁移(0061/0064)以 DISABLE/ENABLE TRIGGER USERNOT VALID 方式旁路 0040 append-only 触发器;down 链同样补外包。legacy 行 backfill 留给计数器列通道,不在此轮。 Hub migrations
ADR-023 Accepted 2026-08-09 Tauri 安全加固:sidecar 重启策略、command 门控(最小权限 capability)、SSRF 防护(出站 URL allowlist + 私网拦截)。正文见 Tauri 通道交付。 Desktop / Security
ADR-024 Accepted 2026-08-20;landed #1760/#1761(2026-08-19~20 全量落地) service/adapters 领域子包化收官:#1761 hub internal/service/ 平铺大包按领域拆 28 子包(Identity/Agent/IM/执行/资源/审计六族,handler -> service -> repository 单向依赖不变,纯包 dispatch/deliveryoutbox/im/agenteventverify-hub-pure-packages.py 门禁禁止 import gorm/cache/ws/service 树,持久化经 Store 接口注入);#1760 edge internal/adapters/ 按 Agent 家族拆 claude/codex/opencode/orchestrator/sdk/testdata 叶子子包(orchestrator 为唯一纯叶子,verify-orchestrator-deps.py + TestLeafDoesNotImportRootAdapters 机器门禁,composition root 装配,根包保留共享 ACP 运行时)。新增领域逻辑放入对应子包,不再回平铺包。 Hub / Edge architecture
ADR-025 Accepted 2026-08-29(设计基线,未实施) 双平面正式化:Hub 是控制面(不执行模型 Turn),Edge 是数据面;任务拆分/路由由确定性 supervisor 承担;证据由 Edge 产生并签名,Hub 只校验/审计。完整目标/差距矩阵见 docs/architecture/10-macro-engineering-design.md Architecture / Hub / Edge
ADR-026 Accepted 2026-08-29(设计基线,未实施) 协议分层与四条 P0 设计合同:自有 REST/WS 保持产品契约 SSOT,MCP/A2A/AG-UI 只做 capability mapping;事件一致性(outbox 同事务/idempotent/version/snapshot)、最小代理权(task-scoped/per-action/secret 隔离)、OTel GenAI 可观测进入实施 backlog。升级但不替代 ADR-016/ADR-017。 Architecture / Hub / Edge / Security
ADR-027 Accepted 2026-09-01 OTel GenAI tracing 裁决(#2111):span 族为 agenthub.run/agenthub.run.lifecycle/gen_ai.chat(CLIENT)/gen_ai.tool_call(sibling, draft)/agenthub.approval/agenthub.artifact.surface/agenthub.dispatch.callback;属性按 semconv v1.36 stable + v1.37 draft;不为 WS 帧、DB/Redis 建 GenAI span;导出管道与 Hub REST 侧迁移另开切片。正文见 #2111。 Observability
ADR-028 Accepted 2026-09-04 secret 门禁夹具裁决(#2295):字面量规则的左边界已落地(#2297,只减误报、不放宽放行面);假凭据夹具另加 scripts/verify/secret-fixture-allowlist.json,条目 = 精确 (path, literal) + 强制非空 owner/review/reason禁止目录/glob/包级/regex 豁免,allowlist 自身 fail-closed(字段缺失、指向不存在的路径、同 literal 换路径一律红)。目标:让脱敏测试可重构,且不让生产 secret 更容易漏过。明确否决「改夹具内容绕过门禁」。 Security / Repository governance
ADR-029 Accepted 2026-09-04 前端服务端状态的唯一键形状(#2261 S1):Hub query key 的字面量唯一来源是 app/shared/src/stores/queryKeys.ts,平台包不得私设 key 数组;<family>.root 只用于宽失效、永不直接当某个 query 的 key;集合查询用 .list(...)、单记录用 .detail(id)、子资源必须有工厂;无消费者的工厂不得存在(幽灵键 = 下一次 #2252);失效点只准引用工厂,测试可用字面量钉形状。据此 threads.detail(0 消费者 / 9 个失效点)删除、desktop 私设 ['hub','sessions']['hub','workspace-projects'] 收敛到家族工厂。 Frontend
ADR-030 Accepted 2026-09-04 对外契约四条口径(#2258):① x-agenthub-owner ∈ {Hub, Edge}Runner 退役(edge-server/internal/runners/ 只有 registry,workspace 实现全在 hub-server),4 个 /v1/workspaces/**owner: Hub必须保持 status: planned(否则被拉进 router 比对而红);workspace 元数据/列举归 Hub,将来若真需要 Edge 侧文件内容端点,届时该端点自己标 Edge,不预先在 Edge 建第二套实现。② x-agenthub-phase 只适用于 /v1/** 设计面——声明适用范围而不是批量补 166 个标记,只补真正违反声明的 3 个 /v1。③ Mobile 单一口径 =「装配中的 fixture/边界验证 lane,非 release candidate」,证据锚点是 release.ymlbuild-mobileRELEASE_MOBILE_ENABLED 门控默认 skipped;README 双语与 docs/architecture.md 统一到这句。④ 根 AGENTS.md 下沉已实施:常驻入口从 285 行压到 115 行,只保留硬边界、工作流与 owner 指针;CI 长表、发布 SOP、前端细则和 Renovate 具体策略回归既有 owner 文档,未新增文档层。 API / Docs / Product
ADR-031 Accepted 2026-09-04 分页 clamp 可观测性(#2243 残项):保留「夹到端点自己声明的上限」,不改 400。实测 13 个 list handler 的信封已回传 page.nextCursor + page.hasMore(另两个 clamp 端点是 limit/offset 形态,客户端仍可推进),所以被夹短的页可续取、不是数据丢失;400 会把可满足的请求变成硬失败、零用户收益,并与 repository/pagination_clamp_test.go 钉住的 ClampPageSize 不变量冲突。可观测性用文档化补齐(api/conventions.md + OpenAPI PageSize 描述写明「超过声明上限即夹到该上限,余下部分用 nextCursor/offset 续取」),新增信封字段(跨 ~16 端点的契约变更换近乎零价值)。残项:repository/message.go GetMessagesIncrement 的非正值分支按 paging.go 自己写明的「0 = no explicit limit ⇒ 把 requested 当 def 传」惯例表达,行为不变、消掉最后一处手写分支。 Hub API
ADR-032 Accepted 2026-09-04 CI 反馈回路裁决(#2251)。① 量化目标已达成@agenthub/workbench coverage job 从实测中位 441s(15 次 pre-#2300 run,自测基线而非沿用 7m31s 口径)降到 259s(5 次 post,−41.3%;把 #2300 自己 4 次 pre-merge run 并进来的 n=9 口径 −40.1%),机制在 CI 日志里验证过:vitest tests 桶 796.49s → 110.16s worker-seconds(−86%),达成并发度恒为 3.86(=job 内已无并行度可挖)。② stretch ≤3m 不做:单 runner 下限已是 870/3.86 + 30 = 255s,要到 180s 得再删 35% 测试工作量,那是拿覆盖保障换 KPI。③ workbench coverage 分片(vitest --shard)DEFERRED:在 post-#2300 逐文件成本上重拟合 LPT+4worker 模型(N=1 标定 −2.0%),N=3/N=4 把该 job 降到 1m45s–2m26s,但 PR 墙钟中位只从 289s → 275s(−4.8%),9 次里 4 次收益恰好为 0——FE-only PR 已成 frontend-required(5/9) 与 windows-frontend(4/9) 的双极平局(中位差 20s);同时分片把 FE-only run 叶子数 15→19,而账号级并发上限实测 ~20,两个重叠 FE-only PR 会 30→38(30 那一档本窗口内已饿过一次)。触发重开:该 job 墙钟中位 ≥ ~5m10s(= 交叉点 4m09s + 60s 余量),或 ④(c) 落地之后。④ 真正的极已换人:(a) go-hub-test 分片失衡——本 ADR 同批实施internal/repository 一个包 = hub-server 实测 418.8s race 测试成本的 227.7s(54.4%),它同时是任何包粒度切分的硬下限NR % 2 把它放在 go list 第 15 位(奇数)⇒ shard 2,而 shard 2 在 14/14(post-#2300 窗口 18/18)次 CI run 里都是慢的那半(中位 +119s / +2m02s,最大 +177s / 3m20s)。改成 shard 1 独占该包、shard 2 拿其余 51 个:load 310.5/108.3 → 227.7/191.1(spread 202.3s → 36.5s),不新增 job本 PR 自己的 CI run 就是这次改动的实验,实测已回收go-hub-test (1)=3m49s(2)=3m28s(spread 21s),对照基线 shard1 2m01s–3m00s / shard2 4m41s–5m14s(中位 spread +119s)⇒ 矩阵跨度 −62s,落在预测的 −40…−70s 区间内;含 1 个包的那片反而成了较慢的一片(229s vs 208s),说明「测试二进制 link 成本随包数增长」这一项确实存在、量级约 20s,比 load 失衡小一个数量级,所以按 load 切是对的方向。同一 run 的 go-edge-test 未改:(1) 2m39s / (2) 1m48s,仍不是 backend-required 的极 ⇒ (b) 的「不动」判断被新数据再次支持。硬编码包路径不会静默腐烂——包被改名/移动/拆分则 shard 1 收到 0 个包,既有 0-package 守卫硬报错,verify-ci-gates.py 另加 3 条断言 + 2 发变异自测钉住。(b) go-edge-test 保持轮转:实测 edge 无单一支配包(最重 internal/lifecycle 34.57s = 24.0%),当前 spread 29.1s load(shard1 重,与 CI「edge shard2 在 14/14 次里更快、中位 −46.5s」同向),LPT-2 只省 14.1s load,而 edge-test 从来不是 backend-required 的极(2m39s vs hub 4m51s)⇒ 省下的时间到不了 PR 墙钟;触发重开 = edge-test 成为 backend 极。(c) windows-frontend 的 ~75s/leg 环境准备税 DEFERRED(同样四步在 Ubuntu 上 21.5–24.0s:setup-node +2829s、pnpm action-setup +1922s、pnpm install +2022s、checkout +56s,且 PRE→POST 还在变慢 3m54s→4m07s):只读测量量得出税额、量不出任何修法的效果,需要一次真实 CI 实验;禁止用砍掉 Windows leg 的 Production build 换那 47–50s——desktop-linux-buildworkflow_dispatch only(43 次 run 出现 0 次),Linux frontend-desktop 只跑 typecheck+lint+test 不 build,那条腿是每个 PR 唯一证明 desktop tsc -p tsconfig.app.json && vite build 能过的地方。(d) 两个前端杠杆严格互补、单独都不划算:Windows 税单独修 = +0.0%frontend-required 变成 9/9 的极)、分片单独 = −4.8%、两个一起 = 4m49s → 3m49s(−20.5%)、再加 frontend-desktop 分片重排(164s vs 138s)= 3m29s(−27.3%)⑤ 测量方法学(防下次重推):本机 ARM64 上 go test -short -count=1 -p 1 不带 -race 得到的成本分布与 CI 完全不同(repository 只占 9.9%、internal/middleware 占 31%),带上 -race 才复现 CI 的方向;两个模块的失衡方向都被 28/28 次 CI 观察独立证实 ⇒ 任何 shard 重排必须用 CI 同款 flag(-short -race -count=1)逐包测,命令:go test ./... -count=1 -short -race -p 1 -parallel 1。证据底稿 lane-artifacts/round-73/lane-ci-REPORT.mdlane-ci-post2300-REPORT.md CI / Developer feedback loop
ADR-033 Accepted 2026-09-04;landed #2315 regenerate 的 identity 合同(#2274 B-1):identity = task id(服务端合同 POST /web/agent-tasks/:id/regenerateRegenerateAgentTask(userID, taskID) 不变)。真流取证(live 栈 + 真 OIDC)证明修复前 web 把 block id 剥前缀后当 task id 发——而 block id 实为消息的 client_msg_id(第三个 identity 域),live 恒 404 agent_task_not_found、未登录 demo 对真后端发无凭据请求 401。修法三段:① 生产端补齐——hub 两条 edge 回调路径(stream 投影 + done-final)在 agent 消息 content jsonb 里 stamp agent_task:{"task_id":…},即 shared normalizer 早已解析却 0 生产者的形状(demo fixture 是唯一生产者);非 object content 不 stamp、既有 ref 不覆盖。② transcript 写穿——normalizeHubMessages 把它写成 block.agentTaskIdTextTranscriptBlock 新增可选字段)。③ 双重诚实门——菜单条目与 regenerate effect 同时要求「端口已接线」 block.agentTaskId 存在;web 的 onRegeneratechatActions fail-closed(demo/未登录端口为 undefined ⇒ 入口不渲染、零请求)。禁止:从 message id / client_msg_id 猜 task id、失败后静默 fallback、demo 向真 API 发请求。历史消息无 stamp ⇒ 入口不出现(fail-closed,不做回填迁移)。验收三层:contract(Go 单测 + live API 404/200 对照)、frontend(shared/workbench/web 单测含两个诚实门用例)、real flow(真浏览器点击:PRE 404+失败 toast / POST 200+新任务+成功 toast;demo PRE 401 请求 / POST 零请求)。证据 lane-artifacts/round-74/b1-*-PRE/POST.json + 截图。 Frontend / Hub dispatch
ADR-034 Accepted 2026-09-05;landed #2323 Project lifecycle 只由服务端契约定义,不从显示文案推断。事实:Hub 的 Project/Workspace 当前没有 lifecycle/status 字段或转换 API;GET /web/projects 不接受 status 筛选。裁决:Workbench 不提供 Running/Completed/Archived 项目筛选,也不保留基于 ProjectInfo.status 显示标签的本地分类链;不能为了保留旧 UI 发明后端状态机。项目搜索、选择、CRUD 与 threads/messages 保留,Task、ProjectRun、queue 和 AgentTeam 的真实状态不受影响。后续边界:只有产品确需 Project lifecycle 且 Hub 提供对应 schema/API/转换语义时,才引入唯一映射和类型明确的筛选;在此之前,显示标签不是 lifecycle 事实,也不要求额外的标签类型重构。该裁决取代此前“保留筛选、等待 vocabulary 裁决”的暂缓方案;原始修复与验收记录见 #2316、#2323。 Product / Frontend / Hub
ADR-035 Accepted 2026-09-04;landed #2317 web-v4 键家族收敛(#2261 残项 = ADR-029 的适用范围正式扩展到 Web 自有命名空间)。① 唯一产出点webQueryKeyshubQueryKeys/edgeQueryKeys 并列住在 queryKeys.tsapp/web/src 非测试代码里的键数组字面量 58 → 0键值逐字不变,只搬产出点,所以缓存身份与既有断言键值的测试都不动。② 可空指针原样穿过(`QueryKeyPointer = string null undefined),**禁止**归一化成 ''webHubMessagesFamily.sessionIdOf只判typeof key[2] === 'string',一个 ''会被重连补发当成真会话 id —— 那会把死键 bug 换成更糟的活 bug。**③ 失效必须打在真有生产者的键上**:据此修掉 6 类空转(联系人列表 6 处、新建群聊 1 处、登录后刷新 1 处、realtime 联系人帧 1 处、裸['agent-teams']1 处、以及一条断言了事实错误键的注释)。其中联系人列表有明确用户可见后果:该查询只有staleTime、**没有 refetchInterval**,接受好友请求后列表会一直陈旧到窗口重新聚焦。裸 ['agent-teams']选择 **retarget 而非删除**——审批决定确实会改变 usageBoard 聚合的 runs,原作者意图对、键写错。**④ 无生产者的失效一律删,不留孪生键**(普查app/web+app/workbench+app/shared全量queryKey:生产点;workbench 0 处使用 react-query 键,故无隐藏生产者):Web 无通知查询 ⇒ NOTIFICATION_EVENTS 分支与事件集合整体删除;Web 只有 usageBoard ⇒ TEAM_EVENTS 6 条收敛为 1 条 family root,并删掉**只为拼死键而存在**的 teamId/teamRunId 解析;execution-targets 的 Web 孪生键删除。**⑤ 断言必须看缓存效果、不看调用**:原toHaveBeenCalledWith(['web-v4','execution-targets'])正是让 bug 活下来的护身符(键无生产者,套件却常绿);新增webQueryCacheTargets.test.tsgetQueryState(...).isInvalidated` 为准,7 发变异 6 发翻红,M6(把已删的通知分支放回去)如实登记为不翻红——它证明的是该分支本来就惰性,不是证明删除必要。

Archive

Full ADR bodies are archived in TokenDanceLab/docs under archive/agenthub/repo/docs/adr/. See history.md for the exact archive commit and follow-up tracking.