# FFAI Agent 内核架构（v0.2 final，2026-05-15）

> **状态**：✅ **全部决策已 review 定稿**（G3–G10 架构原则 + Q9–Q34 共 26 项具体决策）
>
> **本文档定位**：**基于 CC / OC / OCW 三家功能清单全量合并 + 去历史包袱**，重新做一遍架构。已替代早期 v0.1 草案（v0.1 从未 commit，已直接删除；下游 reference 文件里残留的 "v0.1 对照"章节是历史背景，待逐文件评估）。
>
> **方法论**：先把三家所有功能展平成"特性候选池"，再按 FFAI 的目标场景（多组织 Web 内置 agent，agent = 首页 = 工作流编排器）做"取/舍/重构"判断；舍的明确写理由（历史包袱 / YAGNI / 不适合 SaaS）。
>
> **依赖输入**：
> - `claude-code-reference.md` —— CC 单租闭源精品
> - `openclaude-reference.md` —— CC fork + 多 provider 散弹（11 vendor × 18 gateway）
> - `openclaw-reference.md` —— 完全不同方向：CLI 宿主 + 多渠道 IM + A2UI + cron 隔离

---

## 0. 设计目标重申（决定取舍的 north star）

| # | 目标 | 含义 |
|---|---|---|
| G1 | **Agent = 工作台首页** | 不是聊天框，是业务工作流的总入口（项目/审批/知识/ADP/外发等都靠 agent 编排） |
| G2 | **多组织内部工具（不是 SaaS）** | 公司内部员工跨组织使用：per-org 隔离（数据/skill/凭据/quota）+ per-user 身份继承 + RBAC + DataScope；用户都是公司员工，互相可信，威胁模型与 SaaS 多租户根本不同（详见 §1.9 / Q22 沙盒决策） |
| G3 | **多端：Web → Desktop → Mobile 分期** | **Phase 1**：Web（主入口）<br>**Phase 2**：Web UI redesign 为双栏 + slide-in artifacts 抽屉（对齐 ChatGPT 空态 hero + Claude artifacts 风格）+ Windows + macOS Desktop App（**Electron**，套同一份 Web UI；Phase 2 起加 thin CLI）<br>**Phase 3**：iOS + Android（**原生 Swift+SwiftUI / Kotlin+Jetpack Compose**，对标 ChatGPT/Claude/Perplexity 头部 AI chat 全原生路线）<br>**关键**：Web/Desktop 共用 React UI；Mobile 独立原生实现（受 G9 指导：业界头部 100% 全原生，AI chat 核心体验跟原生收益高度相关）；所有端共用同一 SDKMessage 协议 |
| G4 | **工具四轨：Service / MCP / CLI / Client** | agent 工具四种实现形态平行注册到同一 ToolRegistry：<br>① **`service:`** —— 直接调 NestJS 业务 Service（项目/审批/知识）<br>② **`mcp:`** —— MCP server（生态兼容、跨 agent 客户端复用）<br>③ **`cli:`** —— API 包装的 CLI 二进制（`git`/`kubectl`/`psql`/`adp-cli`），固定 argv 模板 + 白名单参数 + 服务端 per-tenant Docker 沙盒<br>④ **`client:`** —— **客户端工具**：本地文件 / 剪贴板 / 通知 / 本地 shell，跑在用户的 Desktop/Mobile App 里，通过 HostBridge 反向调用（**只在 Desktop/Mobile 端可用**，Web 端不暴露） |
| G5 | **IM 集成：Teams 优先** | Microsoft Teams 是公司内部通信工具。Phase 1 起 Inbox 抽象保留接口；Phase 2 实现 `TeamsInbox`（Bot Framework + Graph API），@机器人触发 agent / agent 主动推送审批/任务到频道 |
| G6 | **生态兼容** | inbound 预留 Anthropic Messages 兼容（让外部 Claude Code/Cursor 接我们），outbound provider 走 OpenAI 协议 IR |
| G7 | **零技术债倾向** | 单 maintainer 风险高的开源项目不直接依赖；吸收设计，运行时自研 |
| G8 | **API-first 多 surface 平行**（✅ Reviewed 2026-05-15）| **Backend API + SDKMessage 协议是唯一基础**，Web UI 不是。所有 surface（Web/Desktop/Mobile/CLI/Teams）都是后端 API 的平行 client；UI surface 共用 React 渲染层，非 UI surface 各自实现 renderer。**借鉴 OCW** 的 Gateway 中枢 + 同一份 Web UI 跑多原生壳模式；规避 OC 的"Web 子项目独立实现一遍"历史包袱 |
| G9 | **AI 时代代码成本不是约束**（✅ Reviewed 2026-05-15）| 架构 / 技术选型以**产品质量 / 用户体验 / 业界对标**为首要标准，不是开发工作量。如果"原生对用户更好"就选原生，哪怕代码量翻 3-4 倍。例外：违反一致性 / 跨合规 / 团队完全缺乏该领域知识 |
| G10 | **智能模型路由是核心差异化**（✅ Reviewed 2026-05-15）| FFAI 不只是"接一堆模型"，而是 agent 内置 **task-aware model routing**——按 **5 信号源**（① 任务类型 ② 上下文长度 ③ 偏好 ④ 历史 ⑤ 成本）自动选最适模型，跨 provider 故障转移。业界缺口：CC 单 provider 无路由；OC 只做了 sub-agent type 单信号映射（`agentRouting`）；OCW 只做手动 picker。FFAI 是首个把"业务任务感知模型路由"做成企业 ToB 产品级能力的 agent |

---

## 1. 三家功能合并：取 / 舍 / 重构

> 每行 = 一个特性。"取"= 吸收进 FFAI；"重构"= 结构改造后吸收；"舍"= 不做 + 原因。
> CC=Claude Code · OC=OpenClaude · OCW=OpenClaw。

### 1.1 接入层（Surfaces）

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC/OC | CLI / Ink TUI | **舍** | 工作台首页要图形化，CLI 是开发者本地工具形态包袱 |
| CC/OC | VSCode 扩展（HTTP poll bridge） | **架构预留** | day-1 不做，SSE/WS 控制器允许后期挂第二个 surface |
| OC | gRPC + .proto | **舍** | YAGNI；要外部 SDK 时直接发 OpenAPI |
| OC | Python SDK | **舍** | YAGNI |
| CC | WebSocket 远程 + HybridTransport（读 WS / 写 HTTP） | **重构** | 主链路 SSE（NestJS 现状），保留切到 WS 的接口形状 |
| CC | macOS CoreAudio / Linux ALSA 语音 | **舍** | 不是必需能力 |
| OCW | **多渠道 Inbox 抽象** | **取（Day-1 入架构）** | 见下 1.1.x，G5 Teams 优先 |
| OCW | **iOS / Android native（Live Activity / Foreground Service）** | **取（Phase 3）** | G3 移动端走原生 Swift+SwiftUI / Kotlin+Compose，对标 ChatGPT/Claude/Perplexity 头部 AI chat |
| OCW | **Native bridge（同一份 Web UI 跑浏览器/macOS webview/iOS WKWebView/Android WebView）** | **部分取（Desktop 用，Mobile 不用）** | Desktop = Web 的 power user 形态，**Electron 套 Web UI** 共代码；Mobile 是独立场景，走原生独立实现 |
| OCW | A2UI / Canvas（agent 主动渲染 UI，JSONL 增量） | **重构吸收** | FFAI "Display Panel" 升级为 A2UI 协议（见 §4） |

#### 1.1.1 多端壳层（G3 分期，✅ Reviewed 2026-05-15）

| Phase | 端 | 实现 | 共用部分 |
|---|---|---|---|
| **Phase 1** | Web 浏览器 | 无壳，纯 PWA | Lark DS React UI + `packages/agent-renderer-lark/` |
| **Phase 2** | Windows / macOS Desktop | **Electron**（Node 主进程 + Chromium webview 套同一份 React UI；Win/Mac 同源同行为） | 同 Phase 1 UI |
| **Phase 2** | CLI（轻量 surface）| Node + ANSI/cli-table renderer + Node HostBridge | 独立 renderer（不渲染 React） |
| **Phase 3** | iOS | **原生 Swift + SwiftUI/UIKit** | **独立实现**（对标 ChatGPT/Claude/Perplexity） |
| **Phase 3** | Android | **原生 Kotlin + Jetpack Compose** | **独立实现**（同上） |

**为什么 Mobile 不套 webview 而走原生？**（决策依据，2026-05-15 调研）
- 头部 AI chat 产品 100% 全原生：OpenAI / Anthropic / Perplexity 公开 JD 全部要求 Swift/Kotlin 原生，零 RN/Capacitor 字眼，重金招原生工程师（Anthropic Staff iOS $320–405K）
- Notion 正从 Cordova/webview 迁回原生（迁了 6 年）= 前一代教训
- Slack 明确否认 RN/Electron（"Our iOS app is fully native"）
- AI chat 核心体验（流式渲染、长列表、键盘动画、IME 协同、富文本、Live Activity / Dynamic Island、Siri Shortcut）跟原生收益高度相关
- 受 G9 指导：代码成本不是约束，跟头部对齐是产品质量优先

**架构约束（day-1 起就要满足）**：
- Web/Desktop UI 不依赖浏览器独占 API（要在 Electron webview 也跑得通）
- 鉴权用 token-based（cookie 在 webview/native app 跨域有坑），由壳层/native 注入
- 文件上传/通知/剪贴板等"宿主能力"走**抽象接口**（`HostBridge`），Web 走浏览器 API / Electron 主进程走 Node / iOS 走 Swift / Android 走 Kotlin
- SSE 传输需兼容所有 surface（Web `EventSource` / Native URLSession streaming / OkHttp streaming）

#### 1.1.2 IM Inbox（G5 Teams 优先）

| Phase | 渠道 | 实现 | 优先级 |
|---|---|---|---|
| Phase 1 | `WebInbox` | 直接走 Web UI SSE | day-1 |
| **Phase 2** | **`TeamsInbox`** | **Microsoft Bot Framework + Graph API**：@机器人触发 agent / agent 主动推送审批卡片到频道 / DM 1:1 对话 | **G5 明确** |
| Phase 3+ | `FeishuInbox` / `WeComInbox` | 按业务需要扩展 | 后置 |
| 不做 | Discord / WhatsApp / Slack | OCW 强项但不是公司用的工具 | — |

**Teams 集成关键设计点**：
- **Identity 映射**：Teams AAD User ID ↔ FFAI User（用邮箱兜底；首次需要配对挑战）
- **Inbound**：Teams Bot Webhook → `TeamsInbox.normalize()` → AgentMessage → 走 QueryEngine 主循环
- **Outbound**：A2UI 部分组件（Table/Form/Button）有 Teams Adaptive Card 投影，复杂工件回退为"打开 Web 工作台"链接
- **审批场景天然契合**：agent 把审批卡片推到当事人 Teams DM，对方点按钮 → 回调 → tool_result 喂回 agent
- **吸收 OCW**：input provenance（每条输入带 channel/user/message-id，防 prompt injection）

**结论**：v0.2 入口面 = **Web（Phase 1 三栏 → Phase 2 redesign 双栏 + slide-in artifacts 抽屉）+ Electron Desktop（Phase 2）+ Teams Inbox（Phase 2）+ thin CLI（Phase 2）+ 原生 iOS/Android（Phase 3）+ 统一 SDKMessage 协议**。

#### 1.1.3 多 surface 架构原则（G8 落地，✅ Reviewed 2026-05-15）

**核心原则**：API-first，**不是** UI-first。**Backend API + SDKMessage 协议是唯一基础**，Web UI 是其中一个 consumer，不是骨架。

##### 业界先例对比

| 实现 | 中枢 | 主入口 | Web UI 跨端复用 | 反映的教训 |
|---|---|---|---|---|
| **CC** | SDK | CLI Ink | N/A（无 Web） | SDK 作为契约，所有 surface 平等消费——✅ 正确方向 |
| **OC** | SDK | CLI Ink | ❌ Web 独立子项目，跟 CLI 代码完全不共享 | 反例：UI 代码分裂后再合并不动——**FFAI 必须避免** |
| **OCW** | **Gateway HTTP/WS** | 灵活 | ✅ **同一份 Lit+Vite 跑浏览器 / macOS webview / iOS WKWebView / Android WebView** | ✅ **FFAI 直接借鉴此模式** |

##### FFAI 的最终选择（borrowed from OCW）

```
              ┌────────────────────────────────────────────┐
              │  Backend (NestJS) — 中枢                    │
              │  ┌────────────────────────────────────┐    │
              │  │ QueryEngine + ToolRegistry +       │    │
              │  │ SkillRegistry + Compactor + ...    │    │
              │  └────────────┬───────────────────────┘    │
              │               │ SDKMessage 流              │
              │  ┌────────────▼───────────────────────┐    │
              │  │ REST + SSE/WS Gateway              │    │
              │  └────────────┬───────────────────────┘    │
              └───────────────┼────────────────────────────┘
                              │
   ┌────────────┬─────────────┴─────┬───────────────┬──────────┐
   ▼ React 共用 ▼                    ▼  原生独立     ▼  非 UI  ▼
  ┌──────────┐ ┌─────────────┐   ┌───────────┐  ┌──────┐ ┌────────┐
  │ Web       │ │ Desktop      │   │ iOS       │  │ CLI   │ │ Teams  │
  │ (browser) │ │ (Electron    │   │ (Swift +  │  │ (Node │ │ Bot    │
  │           │ │  Chromium    │   │ SwiftUI/  │  │ + ANSI│ │ (Bot   │
  │ Lark DS   │ │  webview)    │   │ UIKit)    │  │ render│ │ Frame  │
  │ + A2UI    │ │ + Node       │   │ + Swift   │  │ + Node│ │ work + │
  │ schema    │ │ Host Bridge  │   │ HostBridge│  │ Host  │ │ Adapt. │
  │ renderer  │ │              │   │           │  │ Bridge│ │ Card)  │
  │           │ │              │   ├───────────┤  │       │ │        │
  │           │ │              │   │ Android   │  │       │ │        │
  │           │ │              │   │ (Kotlin + │  │       │ │        │
  │           │ │              │   │ Compose)  │  │       │ │        │
  │           │ │              │   │ + Kotlin  │  │       │ │        │
  │           │ │              │   │ HostBridge│  │       │ │        │
  └──────────┘ └─────────────┘   └───────────┘  └──────┘ └────────┘
   └─ Web/Desktop 共用      ──┘    └ Mobile 原生独立 ┘  └ 各自实现 ┘
      packages/agent-               独立 A2UI renderer
      renderer-lark                 共用 packages/agent-protocol

  Mobile 走原生（对标 ChatGPT/Claude 100% 原生路线）
  Web/Desktop 共用 React（业界 AI Desktop 70%+ Electron 主流）
  非 UI surface（CLI/Teams）各自渲染 SDKMessage
```

##### 包结构（落地保证 G8 不被违反）

| 包 | 内容 | 消费方 |
|---|---|---|
| `backend/src/agent/` | QueryEngine + Tool + Skill + 业务集成 | — |
| **`packages/agent-protocol/`** | **SDKMessage TS 类型 + HostBridge 接口 + A2UI Lark DS schema —— 唯一类型契约** | 所有 client（TS 用 npm 包；Swift/Kotlin 用 codegen） |
| **`packages/agent-renderer-lark/`** | **Lark DS A2UI 渲染器**（消费 JSON schema → Lark DS 组件树，对标飞书消息卡片 v2 / 蚂蚁 Ant Design X） | Web / Desktop |
| `frontend/` | Lark DS 主工作台 UI（消费 agent-renderer-lark） | Web |
| `desktop/` (Phase 2) | Electron 壳（Node main process）+ Node HostBridge + 同 Web UI 装 Chromium webview | Desktop |
| `cli/` (Phase 2) | Node CLI + ANSI/cli-table renderer + Node HostBridge | CLI |
| `teams-bot/` (Phase 2) | Bot Framework + Adaptive Card renderer + AAD ↔ FFAI Identity 映射 | Teams |
| `mobile-ios/` (Phase 3) | **原生 Swift + SwiftUI/UIKit** + 独立 A2UI Swift renderer + Swift HostBridge | iOS |
| `mobile-android/` (Phase 3) | **原生 Kotlin + Jetpack Compose** + 独立 A2UI Compose renderer + Kotlin HostBridge | Android |

**关键约束（防止 OC 教训重演）**：
- Lark DS 渲染层代码**必须**写在 `packages/agent-renderer-lark/`，不允许在 `frontend/` 单独 fork
- 任何 React 类 surface（Web/Desktop/未来 IDE webview）必须复用 `agent-renderer-lark`
- 原生 surface（iOS/Android）独立实现 renderer，**强制实现 `packages/agent-protocol/` 全套类型**（用 codegen 生成 Swift / Kotlin 类型）
- SDKMessage 协议字段变更必须先动 `packages/agent-protocol/`，CI 卡死下游不跟随的 PR（含 Swift/Kotlin codegen 检查）

##### 协议降级要求（SDKMessage 字段设计原则）

每个 A2UI 组件必须带 `fallback` 字段，保证非图形 surface 能降级渲染：

```typescript
type A2UIComponent = {
  type: 'Table' | 'Chart' | 'Form' | ...
  // 富表达（Web/Desktop/Mobile 用）
  payload: ComponentSpec
  // 纯文本降级（CLI 用 ANSI 表格；Teams 用 Adaptive Card；其他 surface 兜底）
  fallback: { text: string; url?: string }
}
```

例：A2UI `Chart` 在 CLI 里降级为 "📊 销售趋势图（123 数据点）→ https://workspace/artifact/abc"；在 Teams 里降级为 Adaptive Card 的图片块或链接卡。

### 1.2 核心主循环（QueryEngine）

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC/OC | TAOR 主循环（Think → Act → Observe → Repeat） | **取** | 业界共识，v0.1 已采纳 |
| CC | stop_reason 驱动（tool_use / end_turn / max_tokens） | **取** | 简洁、可恢复 |
| CC | StreamingToolExecutor（边流边并发执行工具） | **取** | 是体感快的关键 |
| CC | TrackedTool 状态机（queued/executing/completed/yielded） | **取** | 用于前端进度展示 |
| CC | AsyncGenerator<SDKMessage> 出循环 | **取** | 流式 + 可中断 + 易测试 |
| CC | siblingAbortController（Bash 错误级联取消并发工具） | **取** | 防止"一个失败拖死一批"的反向场景 |
| OCW | **不自实现 loop，宿主 CLI** | **舍** | OCW 这个选择是"个人 gateway"场景的妥协，企业多组织部署必须自实现循环以掌控隔离/审计/quota |
| OCW | per-session 串行化（enqueueCliRun） | **重构** | 不是 CLI，是同会话同时只允许一个 turn 在跑 → 用 Redis lock（多实例部署必需） |
| CC | post_sampling 钩子决定继续/停止 | **取** | 自动重试 / 自动 verify 的钩子位 |
| OCW | **FailoverError 结构化错误** | **取** | provider 切换需要的统一协议；CC/OC 没标准化这一层 |

### 1.3 工具系统（Tool Registry）

> **业务模块接入开发者视角（`@AgentTool` 装饰器 / `AgentToolsModule.forFeature` / 12 条接入标准 / CI 强制项 / inputSchema v1.0 限制）**：
> 见 [`docs/standards/21-agent-business-module-integration.md`](../../standards/21-agent-business-module-integration.md)。
> 本节是 Tool Registry 设计事实源；standards/21 是开发者落地手册。

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC/OC | Tool 接口（name/desc/schema/call/render/permissions/isReadOnly/isConcurrencySafe） | **取** | day-1 照搬 |
| OCW | **ToolDescriptor 元模型** + ToolAvailabilityExpression（allOf/anyOf + 6 signal） | **取** | 把"工具为啥不可用"做成结构化诊断，对多组织灰度上线/feature flag/角色门控特别契合 |
| OCW | Owner（core/plugin/channel/mcp）vs Executor 拆开 | **取** | 同一工具名可被 core 注册 / plugin 执行 / MCP 远端兜底 |
| OCW | 协议投影（toToolProtocolDescriptor） | **取** | 内部 ToolDescriptor 富信息 + 发给 LLM 的精简 schema 解耦 |
| OC | StreamingToolExecutor（流式工具输出） | **取** | 长任务必需 |
| CC/OC | 权限三态（allow/deny/ask） + Permission 规则语法 | **取** | day-1 必备 |
| CC | canUseTool hook + PreToolUse 短路 | **取** | 钩子位（见 §1.5） |
| CC | partitionToolCalls 提前并发分批 | **取** | 性能 |
| CC | 跨进程沙盒（macOS sandbox-exec / Linux landlock） | **重构** | 多组织场景要 per-org Docker 容器或 nsjail（见 §1.9） |
| OC | 49 个内置工具 / CC 43+ 个 | **重构（按域裁剪）** | 见下 §3 工具盘点 |
| MCP / G4 | 工具**四轨**：service / mcp / cli / client | **取（v0.2 更新 v0.1 Q7）** | 见下 §1.3.1-§1.3.3 |

#### 1.3.1 工具四轨执行器（G4 落地）

ToolDescriptor 的 `executor` 字段支持 **4 类**：

| Executor | 跑在哪 | 形态 | 适用 | 沙盒 |
|---|---|---|---|---|
| `service:<NestModule>` | 后端进程内 | 直接调 NestJS 业务 Service | 业务工具（Project/Approval/Knowledge） | 不需要（复用 RBAC/DataScope） |
| `mcp:<server>` | 后端 → MCP server（同机或远程） | 通过 MCP Client 调远程 server | 外部生态工具 / 跨 agent 客户端复用的业务工具 | MCP server 自带鉴权 |
| `cli:<command>` | 后端 → Docker 容器内 | 子进程跑 API 包装的 CLI 二进制 | `git` / `kubectl` / `psql` / `adp-cli` / 数据脚本 / 系统命令 | **per-tenant Docker**（必须） |
| `client:<host_bridge>` | **用户的 Desktop/Mobile App 内** | HostBridge 反向调用（壳层 Node[Electron]/Swift/Kotlin） | **本地文件 / 剪贴板 / 通知 / 本地 shell** | **OS 级沙盒**（macOS sandbox-exec / Windows AppContainer / iOS App Sandbox / Android scoped storage） + 用户授权弹窗 |

**为什么必须四轨**：
- **Service 强项**：业务规则单一来源、复用事务/审计
- **MCP 强项**：标准化、生态兼容（Claude Code / Cursor 都能调）、纯函数语义
- **CLI 强项**：现有工具链零迁移（运维/数据/部署用了几十年）、可在沙盒里跑任意命令
- **Client 强项**：**FFAI 是 agent 不是 SaaS**——agent 需要操作用户本地机器（读 Desktop 文件、看用户剪贴板帮翻译、复制结果到剪贴板、推送系统通知、本地 shell 跑脚本）；这些只能在用户机器上跑，服务端做不到

#### 1.3.2 Client Executor 与 HostBridge

**核心模式**：agent 决策"调用本地文件工具" → 后端 SDKMessage 发 `tool_use` 块 → 前端（壳层）收到 → HostBridge 路由到原生 API → 用户授权弹窗 → 执行 → 结果回传 → tool_result 喂回 LLM。

```typescript
// HostBridge 接口（壳层实现）
interface HostBridge {
  capabilities(): HostCapability[]   // 启动时上报：fs.read / fs.write / clipboard / notify / shell ...

  invoke(tool: string, args: any): Promise<{
    granted: boolean              // 用户是否授权
    result?: any
    error?: string
  }>
}

// 客户端工具示例（仅 Desktop/Mobile 注册）
const tools = [
  { name: 'File.read',    executor: 'client:fs.read',   requires: ['fs.read'] },
  { name: 'File.write',   executor: 'client:fs.write',  requires: ['fs.write'] },
  { name: 'File.list',    executor: 'client:fs.list',   requires: ['fs.read'] },
  { name: 'Clipboard.read',    executor: 'client:clipboard.read' },
  { name: 'Clipboard.write',   executor: 'client:clipboard.write' },
  { name: 'Notify.push',       executor: 'client:notify' },
  { name: 'Shell.exec',   executor: 'client:shell',     requires: ['shell'], requireSandbox: true },
]
```

**安全设计**：
- **Capability 协商**：HostBridge 启动上报支持的能力，Web 端 capabilities 为空 → 这些工具在 ToolRegistry 里通过 `availabilityExpression` 自动不可见
- **首次授权弹窗**：每个能力首次调用必须 OS 级授权（macOS 系统设置 / Windows UAC / iOS Info.plist + 运行时 prompt / Android runtime permissions）
- **细粒度 scope**：`fs.read` 默认仅 `~/FFAI Workspace/` 目录；用户主动选别的目录才扩展。读 / 写 / 列目录在 `path.resolve` 字符串校验之后**再过一次 `fs.realpath`** 二次防符号链接逃逸（pre-existing 链接指向 root 外即拒）
- **Shell.exec** 必须走宿主 OS 沙盒（macOS sandbox-exec / Windows AppContainer），不允许裸跑
- **audit**：所有 client 工具调用上报 TrajectoryEvent，跟服务端工具一视同仁

**HostCapability 枚举 + 各 surface 默认能力集**（事实源：`packages/agent-protocol/src/host-bridge.ts` + `backend/src/modules/agent/tools/host-capability.types.ts` 的 `SURFACE_DEFAULT_CAPABILITIES`）：

| Capability | 含义 | web | desktop | mobile | teams | cli |
|---|---|:-:|:-:|:-:|:-:|:-:|
| `fs.read` / `fs.write` / `fs.list` | 本机文件 read/write/list（scoped to `~/FFAI Workspace/`） | ❌ | ✅ | ❌ | ❌ | ❌ |
| `clipboard.read` / `clipboard.write` | 系统剪贴板读 / 写 | ❌ | ✅ | ❌ | ❌ | ❌ |
| `notify` | 系统通知 | ❌ | ✅ | ❌ | ❌ | ❌ |
| `shell.exec` | 本地命令执行（需 OS 沙盒 + IT 审批） | ❌ | ✅ | ❌ | ❌ | ✅ |
| `shell.openExternal` | 用默认浏览器打开 URL | ✅ | ✅ | ❌ | ❌ | ✅ |

注：mobile / teams 在 Phase 2 仅占位（`SURFACE_DEFAULT_CAPABILITIES.mobile = []`、`teams = []`），后续 PR 真实化时按平台能力补默认值。`ToolDescriptor.availability.surface` 字段独立控制工具对 surface 可见性，与本表交集后决定最终曝光。

**ToolDescriptor 契约字段**（事实源：`backend/src/modules/agent/tools/tool.types.ts`）：

| 字段 | 类型 | 用途 |
|---|---|---|
| `name` | string | LLM/UI 展示用人读名，例 `File.read` |
| `description` | string | LLM tool-use 提示 |
| `inputSchema` | object | 简化 JSON Schema（key → {type, required, description}） |
| `availability.surface` | string[] | 限定可见 surface（`web`/`desktop`/`mobile`/`teams`/`cli`） |
| `availability.permissions` | string[] | RBAC 权限点白名单 |
| `availability.requiredCapabilities` | HostCapability[] | client 工具：客户端必须上报这些能力才可见。任一缺失即对该 surface 隐藏（架构 §1.3.2 "Web 端 capabilities 为空→自动不可见"） |
| `writeAction` | boolean | 写动作。Plan REQUIRED / Permission READ_ONLY 时被屏蔽 |
| `controlTool` | boolean | mode 控制工具本身（EnterPlanMode 等），不受 mode 过滤 |
| `dispatchKey` | string | client 系列工具的派发 key（如 `client:fs.read`）。后端 `name` 是人读名，客户端 dispatcher 用 `dispatchKey` 路由；两边由 `desktop/scripts/check-capabilities-mirror.ts` 在 CI 校验对齐 |

**HostCapability 三处镜像**（PR15.5 monorepo workspace 落地前的过渡期）：
- `packages/agent-protocol/src/host-bridge.ts`（事实源：HostCapability union + DESKTOP_ELECTRON_CAPABILITIES）
- `desktop/src/main.ts`（局部 DESKTOP_ELECTRON_CAPABILITIES，ESM/CJS workaround）
- `backend/src/modules/agent/tools/host-capability.types.ts`（HostCapability 字面复制 + SURFACE_DEFAULT_CAPABILITIES.desktop）

任一漂移由 `desktop && npm run check:caps` 在 CI 上 fail-fast。新增 capability 必须**三处同步改 + 跑 check:caps 验证**。

#### 1.3.3 Tool Exposure 三态（借鉴 Codex，✅ Reviewed 2026-05-15）

ToolDescriptor 的 `exposure` 字段控制工具对**用户 vs LLM** 的可见性（与 `surfaces` 字段控制"哪些端可见"正交）：

| Exposure | 用户 UI 工具列表 | LLM tools schema | 典型场景 |
|---|---|---|---|
| `direct` | ✅ 可见 | ✅ 可调 | 普通工具（`Project.search`、`File.upload` 等）|
| `deferred` | ❌ 默认隐藏 | ❌ 默认不暴露给 LLM | Skill 触发或用户点击后才加载（避免 tool list 污染）|
| `model_only` | ❌ 用户看不到 | ✅ LLM 可调 | 内部规划/反思工具（`_internal_replan` / `_compact_check` / `Scratchpad.write` / `EnterPlanMode` 受限使用）|

**为什么这字段不能省**：FFAI sub-agent 内部工具（如 `Scratchpad.write` / `Compaction.trigger`）不该出现在用户 UI 工具列表里——如果只有二态 `isReadOnly` 表达不了"仅 model 可见"。Codex 已经验证这设计实用。

#### 1.3.4 工具可见性矩阵

| 工具类 | Web | Desktop (Electron) | Mobile (原生 iOS/Android) | Teams Inbox |
|---|---|---|---|---|
| `service:` 业务工具 | ✅ | ✅ | ✅ | ✅ |
| `mcp:` 工具 | ✅ | ✅ | ✅ | ✅ |
| `cli:` 工具（服务端 Docker 跑） | ✅ | ✅ | ✅ | ✅ |
| `client:fs.*` 本地文件 | ❌ | ✅ | ⚠（scoped）| ❌ |
| `client:clipboard.*` | ⚠（浏览器 API 受限）| ✅ | ✅ | ❌ |
| `client:notify` | ⚠（Web Notification）| ✅ | ✅ | ⚠（走 Teams 通知）|
| `client:shell` | ❌ | ✅（沙盒）| ❌ | ❌ |

**MCP 仍是首选**：新增工具默认 MCP；CLI 仅在"已存在成熟 CLI + 无 MCP 实现"时使用；Client 仅在"必须在用户本地执行"时使用。

**CLI 工具的"API 化封装"模式**：

```yaml
# 内部 CLI 工具定义示例
name: "git.log"
executor: "cli:git"
template: ["git", "log", "--oneline", "-n", "{count}", "--", "{path}"]
params:
  count: { type: integer, min: 1, max: 1000 }
  path:  { type: string, pattern: '^[\w./\-]+$' }  # 防注入
sandbox:
  image: "ffai/cli-runner:git"
  workdir: "/repo/{org_id}/{repo_slug}"
  timeout_sec: 30
  readonly: true
```

> 详见 §1.3.1 "为什么必须四轨"。MCP 仍是首选；CLI / Client 在前述场景使用。

### 1.4 扩展机制（Skills / Plugins / Hooks / Commands）

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC/OC/OCW | Skill = markdown + frontmatter | **取** | 业界共识 |
| v0.1 已锁 | Skill 双源（文件系统 + 数据库组织自定义） | **取** | 维持 |
| OCW | Skill `requires/install/allowed-tools/user-invocable` 声明式 | **取** | allowed-tools 限权对多组织敏感工具特别重要 |
| CC/OC | Plugin（聚合 skill+tool+hook+mcp） | **重构** | day-1 不开放第三方 plugin（多组织安全/审计成本高）；plugin 概念内部用于打包 FFAI 自家功能模块 |
| CC | **Hook 11 种事件 / 4 种实现（Command/Prompt/HTTP/Agent）** | **重构** | 收敛为 5 类（OCW 也是 5 类）：PreToolUse / PostToolUse / SessionStart / SessionEnd / UserPromptSubmit；实现限 HTTP Webhook + Agent（不允许跑任意 Command，多组织禁止） |
| CC | asyncRewake（事件驱动重评估） | **取** | 后续上长任务/异步通知场景必需 |
| CC | FileChanged + watchPaths | **舍** | 不操作本地文件 |
| CC | Slash Commands（60+ 内置 + 目录 + 插件 + skill + MCP） | **重构** | 只保留：内置（/help, /reset, /tasks）+ skill 触发（`/<skill-name>`）；不做 plugin/MCP slash |
| CC/OC | Keybindings（17 contexts / 70+ actions / chord） | **舍** | Web 不是终端，浏览器快捷键 1-2 个够用 |
| CC | settings 11 个迁移脚本 | **架构预留** | DB 字段 schema_version + 升级脚本，day-1 起步就建机制 |

### 1.5 Sub-agent / 多 agent

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC/OC | Coordinator + AgentTool（独立 QueryEngine + 工具子集） | **取** | day-1 同进程实现 |
| CC | INTERNAL_WORKER_TOOLS 黑名单（防递归套娃 + 越权） | **取** | 子 agent 工具集 ⊂ 主 agent |
| OC | Scratchpad 跨 worker 共享 | **重构** | 用 PG `AgentScratchpad` 表 per-session，不用文件 |
| OC | task-notification 消息回报 | **取** | 子 agent 进度推到前端 Task 卡片 |
| CC | Team 管理（TeamCreate/Delete/SendMessage） | **舍/延后** | YAGNI，business 场景不明 |
| CC/OC | 同进程 fork + resume | **重构** | day-1 同进程；架构层准备好把子 agent 抽成"可由队列调度"的接口，未来 Temporal worker 接入 |
| OCW | **Cron 隔离 agent**（每次触发起新 session，跑完销毁） | **取** | 业务定时任务（"每天 9 点汇总待审批"）必备；Temporal 已在项目里有 |

### 1.6 上下文管理 / Compaction

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC | 5 层 Compaction（去重 / 工具摘要 / 旧轮摘要 / 系统收敛 / 硬截断） | **取** | v0.1 已设计 |
| CC | contentReplacementState（选择性文本替换） | **取** | 大附件正文摘要、原文落 ArtifactStore |
| CC | 不删 PG（compaction 只影响"喂模型"而非"前端展示"） | **取** | day-1 必须 |
| CC | memdir（MEMORY.md 索引 + 双重截断 200 行/25KB） | **取（day-1 实现）** | per-user + per-org 两层 memory；MEMORY.md 索引模式（context 只塞索引按需展开）是关键；Q14 ✅ Reviewed 2026-05-15 改为 day-1 落 schema + 注入逻辑（不延后），避免 QueryEngine system prompt 拼装逻辑后期重构 |
| CC | CLAUDE.md / AGENTS.md cached on bootstrap | **重构** | 改成"OrganizationProfile" PG 表 + 缓存 |
| OCW | **Trajectory event 流**（traceId/sessionId/runId/source/seq） | **取** | 多组织审计/回放/debug 必备；CC 没标准化这一层 |

### 1.7 持久化

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC/OC | JSONL 本地文件会话 | **舍** | 多组织必须 DB |
| CC/OC | `~/.claude/` settings.json + 多文件合并 | **重构** | per-org config 表 + per-user config 表，覆盖优先级用 SQL 视图 |
| CC | outputFile 解耦内存（任务输出落盘） | **取** | 大输出走对象存储（S3/MinIO），DB 只存引用 |
| OCW | auth-profiles 凭据矩阵 + ref 模式 | **取** | API key/OAuth 不存明文，走 vault；plan/apply 分离 |
| CC | telemetry 属性（userId/sessionId/model/intent） | **取** | 接 OTel |

### 1.8 Provider / Model

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| OC | **Registry**（全局单例 5 大 Map） | **取** | day-1 起就建，避免散落 |
| OC | **ProfileResolver**（用户字符串 → ResolvedProfileRoute） | **重构** | profile 改为 **per-tenant + per-user-override**，不是 per-user |
| OC | DiscoveryService（三层缓存 / TTL / 离线降级） | **重构** | day-1 只对 ollama/openai-compatible 做发现，其他静态；离线降级保留 |
| OC | RouteMetadata | **取** | day-1 简化版 |
| OC | Compatibility + OpenAI Shim | **取** | 这是 OC 的杀器；通过它接 DeepSeek/通义/Ollama 几乎零成本 |
| OC | 11 vendor × 18 gateway 散弹 | **舍** | YAGNI / 历史包袱；**day-1 只接 OpenAI + Anthropic 两家**（Q13 ✅ Reviewed 2026-05-15）；其他通过 LiteLLM 后期兜底 |
| OC | DeepSeek thinking → Claude thinking block 映射 | **架构预留** | provider capability `extendedThinking` 位预留 |
| OCW | Multi-account OAuth 池（多账号轮转 + cooldown） | **架构预留** | SaaS 场景不让 agent 跑用户的 Claude 个人订阅；但企业部署多个 API key 做负载均衡可借鉴 |
| OCW | FailoverError + provider 故障转移链 | **取** | day-1 实现 ProviderChain，按 priority 顺序 fallback |
| v0.1 已锁 | OpenAI 协议作内部 IR | **取** | 维持 |
| OC | `agentRouting` 配置（sub-agent type → model）| **重构扩展** | **极简版本只 1 个信号源**；FFAI ModelRouter 扩展到 5 信号源（task/context/偏好/历史/成本）+ 3 策略（rule/LLM/scope）+ 学习反馈，是 FFAI G10 核心差异化（详见 §1.12）|

### 1.9 沙盒与安全

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC/OC | sandbox-exec / landlock / Docker / Termux | **取（两侧都要）** | service / MCP 工具不需要 OS 沙盒；**CLI 工具必须服务端沙盒**；**Client.shell 工具必须客户端 OS 沙盒** |
| G4 落地（服务端，Q22 ✅ Reviewed 2026-05-15）| **per-user Docker 容器** + **命令白名单/黑名单** + **network egress 限制** | **Day-1 接口必备** | **FFAI 是多组织内部工具不是 SaaS**——威胁模型是"agent 被 prompt injection 跑恶意命令 / 员工误操作"而非"恶意外部租户 kernel 提权"，Docker 已够用，**不用 E2B / Firecracker**（E2B 数据出境合规问题）；`dockerode` 起 short-lived 容器，image-per-tool-domain（`ffai/cli-runner:git` / `:kubectl` / `:psql` / `:python`）；网络默认禁外网按工具白名单放开；接口封装 `SandboxAdapter`，未来如果产品转 ToB SaaS 对外部租户开放，再切 Firecracker |
| G4 落地（客户端） | **OS 级沙盒**（Client executor 专用） | **Phase 2 Desktop 上时必备** | macOS：sandbox-exec + App Sandbox entitlements；Windows：AppContainer + Job Object；iOS：App Sandbox（默认）；Android：scoped storage + SELinux；shell 命令必须走 OS 沙盒 + 二次授权；fs 默认仅 `~/FFAI Workspace/`，扩展目录靠用户在 OS 文件选择器主动加 |
| CC | 权限三态 + 用户确认弹窗 | **取** | 写动作前的 ask 必备 |
| OCW | Approval 多路由（in-app / native notification / DM） | **重构** | day-1 in-app；未来对接企业 IM 通知 |
| OCW | 凭据矩阵（env/exec/secret_input/sibling_ref） | **取** | plan/apply 分离值得 |
| OCW | proxy-capture（开发期流量回放） | **架构预留** | 通过日志中间件实现，不真起代理 |

### 1.10 可观测性

| 来源 | 特性 | 决策 | 理由 |
|---|---|---|---|
| CC | cost-tracker（input/output/cache create/cache read tokens） | **取** | 计费必需 |
| OC | per-vendor 多档计价表 | **取** | 维护成 `ModelPricing` 表 |
| CC | OpenTelemetry meter | **取** | 接现有 OTel |
| OCW | **TrajectoryEvent**（traceId/sessionId/runId/seq）完整事件流 | **取** | per-org 审计 + bug 回放 + agent 行为分析 |
| OCW | 诊断三维度（phase/session/activity） | **取** | "最近哪个 session 卡在哪个 stage" |
| OCW | proxy-capture / console capture | **架构预留** | 用 NestJS interceptor + winston transport |
| CC | Doctor 命令 / ant-trace / perf-issue | **舍** | dev 工具 |

#### 1.10.1 TrajectoryEvent 双职责澄清（借鉴 Codex 三层持久化，✅ Reviewed 2026-05-15）

`AgentTrajectoryEvent` 表**同时承担两个语义角色**，落地时必须分清：

| 角色 | 语义 | 强保证 | 查询 |
|---|---|---|---|
| **Audit Log**（多组织审计要求）| 不可篡改的事实记录："谁在何时调了什么工具" | PG `REVOKE UPDATE, DELETE FROM ffai_app_user`（应用层连接无 update/delete 权限，只 admin DBA 可清理）| admin dashboard 按 traceId/sessionId/userId 钻取 |
| **Debug Stream**（开发审计 / 性能分析）| 完整事件流，可关联 cost/latency 做诊断 | 无写约束（同表，靠 phase/source 字段过滤）| RLS 按 org 隔离 + 按 phase/source 切流 |

**为什么不拆两张表**：Codex 拆了 `message-history`（audit）+ `rollout-trace`（debug reducer），但 FFAI 拆表会导致：
- 双写不一致风险
- Reducer 重放在 PG SQL 已经可做（不需要 Redux 模式）
- 增加运维复杂度

**一张表 + 写约束**就够。**不引入 Codex 的 Reducer 模式**（YAGNI——SQL 聚合查询已足够支撑回放/分析）。

**实施约束**：
- PG migration 写约束 day-1 起；应用层只能 INSERT
- TrajectoryEvent 是**首次出现 schema 不可改**——后续字段加 nullable 列，不允许重命名 / 删除
- admin 删 audit 数据走单独 retention job（PG cron），不在应用代码里

### 1.11 存储后端抽象（Storage Backend，✅ Reviewed 2026-05-15）

> **核心需求**：让用户能把 agent 输入/输出文件落到自己选的位置（OneDrive / 本地 Desktop / 未来其他云盘）。这是 G2 多组织内部工具的能力补充——**用户保留数据主权**，文件不绑在 FFAI 自家存储里。

#### 1.11.1 不做默认 backend（设计权衡）

| 选项 | 决策 |
|---|---|
| **不提供默认 backend** | ✅ **取**：用户没绑定时小工件 inline 显示继续工作；大工件 / 持久化操作报错引导用户去绑 |
| 提供 FFAI 自家 S3/MinIO 作默认 | ❌ 舍：造一套用户不愿用的兜底没价值，且 FFAI 不希望长期承担用户数据存储责任 |

#### 1.11.2 三层 scope 解析（项目 > 用户 > 组织）

```
agent 调 File.upload(path, content)
   ↓
FileService.upload()
   ↓
StorageRegistry.getActiveBackend(ctx)
   ↓
   1. ctx.projectId 存在 → 查 ProjectStorageBinding
   2. fallback 查 UserStorageBinding（用户全产品默认）
   3. fallback 查 OrganizationStorageBinding（管理员预配置兜底）
   4. 都没有 → throw NoStorageBackendError(actionable='open_settings_storage_binding')
              + A2UI 推"请去设置中绑定 OneDrive"卡片
```

**典型使用模式**：
- 用户 onboarding 时绑定 OneDrive 作全产品默认
- 个别敏感项目（如客户对接项目）单独绑定到客户共享 OneDrive 盘 / 本地隔离目录
- 组织管理员可以为整个组织预配置一个默认 backend，新用户没绑时兜底

#### 1.11.3 StorageAdapter 接口

```typescript
interface StorageAdapter {
  readonly kind: 'onedrive' | 'local' | 'gdrive' | 'feishu' | ...
  readonly availableOn: Surface[]   // Local 只在 Desktop/Mobile 可用
  
  list(path?: string): Promise<FileEntry[]>
  read(ref: FileRef): Promise<Buffer>
  write(path: string, content: Buffer): Promise<FileRef>
  delete(ref: FileRef): Promise<void>
  shareLink(ref: FileRef, scope: 'org' | 'public'): Promise<string>
  preview(ref: FileRef): Promise<PreviewUrl>  // Office Online / 缩略图等
}
```

#### 1.11.4 Backend × Surface 可用矩阵

| Backend | Web | Desktop | Mobile | Teams |
|---|---|---|---|---|
| **OneDrive** | ✅ Graph API | ✅ Graph API + 同步可见 | ✅ Graph API | ✅ 附件天然 |
| **Local**（HostBridge）| ❌ 浏览器无 fs | ✅ Phase 2 | ⚠ scoped storage | ❌ |
| 未来：GDrive / 飞书 / WeCom / Box | ✅ | ✅ | ✅ | ⚠ 按渠道 |

**Web 端用户选了 Local 怎么办**：StorageRegistry 检测 surface=web 时自动跳过 Local，按优先级用下一个；如果用户只绑了 Local + 当前在 Web → 报错 + 引导（"该 backend 仅在 Desktop/Mobile 可用"）。

#### 1.11.5 配置入口（三处都做，Q28 ✅）

1. **首次登录引导（onboarding wizard）**：登录后引导绑定第一个 backend
2. **个人设置页**：用户随时改 / 加 / 删
3. **管理员后台预配置**：组织管理员可设组织级兜底 backend，新员工无感知可用

#### 1.11.6 agent 工具家族（File.\*）

agent 看到的是**统一接口**，不感知 backend 是哪个（路由由 FileService 内部解决）：

| 工具 | executor | 说明 |
|---|---|---|
| `File.list(path?)` | `service:FileService.list` | 列当前 backend 当前路径文件 |
| `File.read(ref)` | `service:FileService.read` | 读引用 |
| `File.upload(path, content)` | `service:FileService.upload` | 写文件 |
| `File.search(query)` | `service:FileService.search` | 搜索（OneDrive 自带全文搜，Local 走文件名）|
| `File.shareLink(ref, scope)` | `service:FileService.shareLink` | 生成分享链接 |
| `File.preview(ref)` | `service:FileService.preview` | 拿 Office Online / 缩略图 URL |

agent 写一次代码，用户切 backend 不需要改 agent prompt / skill。

#### 1.11.7 day-1 / Phase 2 / 未来

| Phase | 实现 | 验证 |
|---|---|---|
| **Phase 1（day-1）** | StorageAdapter 抽象 + **OneDrive 实现** + 三层 scope binding + onboarding/settings UI | 用户绑 OneDrive，agent 上传文件能在 OneDrive Web 看到 |
| **Phase 2** | **Local backend**（依赖 G4 Client Executor HostBridge）+ 管理员后台预配置 + Teams 附件联动（用户绑 OneDrive 时 Teams 附件自动落 OneDrive） | Desktop 用户切 Local 跑通 |
| 未来 | Google Drive / 飞书 / WeCom / Box / Dropbox 按需 | — |

---

### 1.12 ModelRouter（G10 核心差异化，✅ Reviewed 2026-05-15）

> **战略定位**：FFAI 把"业务任务感知的模型路由"做成产品级能力——这是企业 ToB agent 场景最稀缺的能力，业界目前**无人**做完整版。

#### 1.12.1 业界现状与缺口

| 产品 | 模型路由实现 |
|---|---|
| **Claude Code** | 单 provider（Anthropic），无路由 |
| **OpenClaude** | **`agentRouting` 配置**：sub-agent type → model 名（如 `Explore → cheap-model`, `Plan → strong-model`）—— **仅 1 个信号源** |
| **OpenClaw** | `flows/model-picker.ts`——**用户手动选 model 的 wizard**，无自动路由 |
| **OpenRouter / LiteLLM** | API 网关层路由（按价格/可用性），**agent 不感知** |
| **Cursor / Copilot** | 黑盒任务类型切换（Composer/autocomplete），**不可配置** |
| **FFAI v0.2** | **5 信号源 + rule-based + LLM-routed + 学习反馈 + per-scope UI** |

FFAI 不只是接一堆模型——而是**根据任务自动选最适模型**，跟踪决策结果，逐步学习优化。

#### 1.12.2 5 个路由信号源（Q30 ✅ 全做）

```
┌──────────────────────────────────────────────────────────┐
│  ModelRouter (L4 服务) —— 综合 5 信号源决策               │
└──────────────────────────────────────────────────────────┘
       ▲          ▲          ▲          ▲          ▲
  ① 任务类型  ② 上下文     ③ 偏好     ④ 历史      ⑤ 成本
  - tool name - context    - per-org  - 成功率     - 月度 quota
  - sub-agent   length     - per-user - latency    - 余额预警
    type      - has-image  - per-     - error 率   - 紧急 vs
  - skill     - history       project              非紧急
    intent     turn depth
  - 复杂度
    classifier
```

#### 1.12.3 三种路由策略（Q29 ✅ 全做）

| 策略 | 何时触发 | 备注 |
|---|---|---|
| **A. Rule-based 静态规则** | 规则明确匹配时（90% 场景）| 可预测、零延迟、可审计 |
| **B. LLM-routed 动态** | 规则无匹配 / `routingStrategy='llm'` 显式开启 | 用便宜小模型分析任务 → 推荐主模型，+200-500ms |
| **C. Per-scope 覆盖** | admin / user / project 手动配规则 | 三层 scope 跟 StorageBinding 一致 |

落地：**默认 A 优先，缺规则 fallback 到 B，per-scope 覆盖优先级最高**。

#### 1.12.4 ModelRouter 接口

```typescript
interface RoutingRequest {
  // ① 任务信号
  taskType: 'tool_call' | 'agent_loop' | 'classification' | 'translation' | 'reasoning' | 'code' | 'vision' | ...
  toolName?: string
  subAgentType?: string         // 借鉴 OC agentRouting
  skillIntent?: string
  
  // ② 上下文信号
  contextTokens: number          // 已积累 history token
  hasImage: boolean
  hasAudio: boolean
  turnDepth: number              // 递归深度
  
  // ③ 偏好信号
  organizationId: string
  userId: string
  projectId?: string
  surface: 'web' | 'desktop' | 'mobile' | 'teams' | 'cli'
  
  // ④ 历史信号（由 ModelRoutingDecision 表分析）
  recentSuccessRate?: number
  recentP50Latency?: number
  
  // ⑤ 成本信号
  monthlyBudgetRemaining?: number
  latencyPriority: 'realtime' | 'interactive' | 'background'
  costPriority: 'budget' | 'standard' | 'premium'
}

interface RoutingDecision {
  primary: { provider: string; model: string }
  fallbacks: { provider: string; model: string }[]   // FailoverChain 联动
  matchSource: 'rule' | 'llm_routed' | 'scope_override' | 'default'
  matchedRuleId?: string
  reasoning?: string                                  // 路由理由（暴露给 admin）
  estimatedCostUsd?: number
}
```

#### 1.12.5 路由学习反馈（Q31 ✅ 逐步做）

```
agent 每次调 ModelRouter → 写 ModelRoutingDecision 表
  ↓
后台 Job 定期分析：
  - 哪些 rule 经常匹配但实际效果不佳？
  - 哪些 LLM-routed 决策被人 override？
  - 哪些 task pattern 在不同 model 上成功率/cost/latency 怎样？
  ↓
分析结果输出：
  - 建议给 admin 调整规则（自动推 issue / dashboard）
  - 二期：自动收紧/放宽规则（带 admin 审批）
```

day-1 只**记数据**（`ModelRoutingDecision` 表），二期上**分析 dashboard**，三期上**自动学习闭环**。

#### 1.12.6 配置入口（三层 scope，跟 StorageBinding 同模式）

| Scope | 配置者 | 典型规则 |
|---|---|---|
| **organization**（admin） | IT/数据治理 | "组织默认成本优先 / 默认走 Sonnet / 禁止用 GPT-4o（数据出境）" |
| **user** | 用户自己 | "我写代码任务请走 GPT-5.4" |
| **project** | 项目负责人 | "保密项目只能走 Anthropic / 仅自部署模型" |

#### 1.12.7 透明度（Q33 ✅ 暴露给管理员）

| 用户类型 | 看到什么 |
|---|---|
| **普通用户** | 默认不显示路由理由；hover 模型名可看简版 reasoning（可选） |
| **管理员** | 完整 `ModelRoutingDecision` dashboard：每次路由的 reasoning / 匹配规则 / cost / latency / success；可按时间/scope/task 钻取 |

审计场景：管理员可查 "上周我组织 agent 调用都走了哪些 model / 总成本 / 哪些规则被 override 最多"——直接生成成本/效果报告。

#### 1.12.8 跟现有架构对接

| 层 | 关系 |
|---|---|
| **L2 Agent Core** | QueryEngine 调 provider 前先调 `ModelRouter.decide(routingRequest)` 拿模型 + fallbacks |
| **L3 Registries** | 新增 `ModelRoutingRuleRegistry`（rules per-org/user/project，热加载）|
| **L4 Adapters** | 新增 `ModelRouter` 服务；输出 `RoutingDecision` 喂给 ProviderRegistry |
| **L4 Async Orchestrator** | `ProviderFailoverChain` 用 `RoutingDecision.fallbacks` 做转移链 |
| **L6 Cross-cutting** | Cost Tracker 复用 `ModelRoutingDecision.actualCost` |
| **Schema** | 新增 `ModelRoutingRule` + `ModelRoutingDecision` 表（见 §5） |

#### 1.12.9 跟 OC `agentRouting` 的关系

OC 的 `agentRouting` 是 **FFAI ModelRouter 的极简子集**——只用了"sub-agent type"1 个信号源。FFAI 兼容这种简单规则配置，但能力远超：

```yaml
# OC 风格规则（FFAI 也支持，作为 sub-agent type 路由）
- pattern: { subAgentType: 'Explore' }
  selectedModel: 'anthropic/claude-haiku-4.5'
  
# FFAI 进阶（多信号组合）
- pattern: 
    taskType: 'vision'
    hasImage: true
  selectedModel: 'openai/gpt-4o'
  fallbacks: ['anthropic/claude-opus-4.7']
  
- pattern:
    contextTokens: { '>': 200_000 }
  selectedModel: 'google/gemini-2.5-pro'  # 长上下文
  
- pattern:
    surface: 'mobile'
    latencyPriority: 'realtime'
  selectedModel: 'anthropic/claude-haiku-4.5'  # 移动端要快
  
- pattern:
    organizationId: 'org_xyz'
    projectId: { tag: 'confidential' }
  selectedModel: 'azure-openai/gpt-5.4'  # 合规：仅 Azure 部署
```

---

### 1.13 OCW 独门特性（重点决策摘要）

> 注：以下特性大部分已在前述章节落地（A2UI → §1.1+§4 / Markdown chunking → §6 / Cron isolated → §1.5 / Trajectory → §1.10 / Secret ref → §1.9）。本节是综述索引。

| 特性 | 决策 | 理由 |
|---|---|---|
| **A2UI / Canvas（agent 主动渲染 UI）** | **取（核心采纳）** | FFAI 的"Display Panel"应该 = A2UI 协议：JSONL 增量推送、声明式组件、agent 通过 snapshot 看自己；这是把 agent 从聊天框升级为"工作流引擎"的关键 |
| **Realtime 转写 + TTS directives** | **舍** | YAGNI |
| **Markdown render-aware chunking** | **取** | 流式 chunk 不切 code fence/table，前端体感关键 |
| **133 bundled extension + 53 skill** | **舍** | 我们不是个人 gateway；skill 自家写 |
| **proxy-capture (开发期 TLS 拦截回放)** | **架构预留** | 用 wiremock-like 方案 |

---

### 1.14 协作模式（CC 两轴正交，Q34 ✅ Reviewed 2026-05-15）

> **核心问题**：业务用户做不同任务（查询 / 审批 / 批量数据 / 探索）希望 agent 表现不一样——有时要看完整 plan 再审、有时要 agent 全自动跑、有时每步问。**怎么把这些表达成系统能力**？

#### 1.14.1 业界两种设计对比

| 设计 | Codex | CC（最终选这个）|
|---|---|---|
| 表达方式 | **一维 4 模式枚举**（PLAN/DEFAULT/EXECUTE/PAIR_PROGRAMMING）| **两根正交轴**（Plan toggle + Permission mode 5 态）|
| 表达力 | 4 个组合 | 10+ 个组合 |
| 业务场景覆盖 | 普通使用 | + CI/CD 无人值守 + 长任务自动编辑 + 探索模式 |

FFAI 选 CC 的两轴正交，因为业务场景多样性更高（多组织 ToB）。

#### 1.14.2 轴 1：Plan vs Execute

通过 `EnterPlanMode` / `ExitPlanMode` 工具切换：

| 状态 | Agent 行为 | 工具集 |
|---|---|---|
| **Plan** | 只规划不执行修改性操作；输出 TODO 列表给用户审 | 工具集过滤为 `isReadOnly=true` 子集（查询/搜索类） |
| **Execute**（默认）| 正常 TAOR 主循环 | 完整工具集 |

#### 1.14.3 轴 2：Permission Mode 5 态

通过 `SetPermissionMode` 工具切换（或 session 创建时指定）：

| Mode | 行为 | 典型场景 |
|---|---|---|
| `default`（默认）| 写操作 ask，读操作直接执行 | 日常正常用 |
| `acceptEdits` | 文件编辑自动通过；其他写操作仍 ask | 长会话编辑文件不被打扰 |
| `bypass`（"YOLO"）| 所有工具自动通过 | 完全信任 agent，批量任务 |
| `scripted` | 按预设 Permission 规则匹配（`Project.search` 允许 / `Project.delete` 禁止）| CI/CD / cron job 无人值守 |
| `ask` | 永远每个工具都 ask | 极敏感任务（合规审计）|

#### 1.14.4 两轴组合 = 实际"协作姿势"

```
                  Plan mode               Execute mode
              ┌──────────────────┬──────────────────────┐
ask           │ 看 plan + 逐步审  │ 对应 Codex            │
              │                   │ PAIR_PROGRAMMING     │
              ├──────────────────┼──────────────────────┤
default       │ 看 plan，写操作   │ 对应 Codex            │
              │ 仍 ask            │ DEFAULT              │
              ├──────────────────┼──────────────────────┤
acceptEdits   │ 看 plan，编辑     │ 长任务自动编辑、     │
              │ 自动接受          │ 其他 ask             │
              ├──────────────────┼──────────────────────┤
bypass        │ 先 plan 再全自动  │ 对应 Codex            │
              │                   │ EXECUTE / YOLO       │
              ├──────────────────┼──────────────────────┤
scripted      │ 罕见组合          │ CI/CD 模式           │
              │                   │ Permission 规则匹配  │
              └──────────────────┴──────────────────────┘
```

#### 1.14.5 多组织安全约束

| 设计点 | 实现 |
|---|---|
| 组织级 mode 白名单 | `OrganizationSettings.allowedPermissionModes`——admin 可禁某些 mode（如禁 `bypass` 防员工误操作）|
| 默认 mode | per-org `defaultPermissionMode`（admin 配） |
| Plan mode 切换审计 | 进/出 Plan 都写 TrajectoryEvent |
| Mode 切换权限 | 普通用户只能切 `default` / `acceptEdits` / `ask`；`bypass` / `scripted` 需要特定 RBAC 权限 |

#### 1.14.6 工具清单（落地 §3）

| 工具 | exposure | 说明 |
|---|---|---|
| `EnterPlanMode` | `direct` | 进入 Plan 模式 |
| `ExitPlanMode` | `direct` | 退出 Plan 模式 |
| `SetPermissionMode` | `direct`（受 RBAC 控）| 切换当前 session permission mode |

#### 1.14.7 Schema 落地（§5）

`AgentSession` 新增字段：
- `planMode: Boolean @default(false)`
- `permissionMode: String @default("default")`

新增 `OrganizationSettings` 行（或合入现有 config 表）：
- `defaultPermissionMode`
- `allowedPermissionModes: String[]`

### 1.15 横切待办（Phase 1 已识别但单独 PRD 跟进）

> 这些是**已识别但未深入设计**的横切关注点。每项有最小实施约束，详细设计在后续单独 PRD/RFC 跟进，**不阻塞 v0.2 release**。

| 关注点 | 最小约束（day-1） | 详细设计触发 |
|---|---|---|
| **i18n / 多语言** | AgentContext 携带 `locale`；SDK message text 块标 locale；agent system prompt 按 locale 切换（基础中文/英文双语） | Phase 2 多 locale 需求或 Teams Inbox 上线（用户跨语言） |
| **Rate Limiting / Quota** | per-user / per-org token quota；超限走 RoutingDecision 降级（fallback 到便宜 model）；硬 429 兜底 | PR3.5 ModelRouter 上时一起做基础限流；学习反馈 PR15.7 上 quota dashboard |
| **错误恢复 / 容错** | QueryEngine 崩溃后 session resume（用 PG message + last `compactedAt` 续）；SSE 断线前端 `lastMessageId` 拉取；PG 写失败 fallback JSONL 文件 | Phase 2 长任务 / cron isolated agent 上时统一收敛设计 |
| **测试策略** | LLM 用 MockProvider；工具用集成测试（PG + 真实 Service）；A2UI 用 schema snapshot 对比；TrajectoryEvent 落库可作为回放数据 | PR3.5 + PR4 上时确立 fixture/snapshot 标准 |
| **启动顺序 / 依赖图** | NestJS 模块 onModuleInit：Registry → Adapter → QueryEngine 顺序；ToolRegistry 装饰器扫描必须先于 ModelRouter 初始化 | 部署文档（独立 doc）|
| **部署模式** | day-1 单一云部署（公司内部 k8s）；多实例靠 Redis lock + PG；不做"私有化下载部署"形态 | 业务真要私有化（如客户 on-prem）再设计 |

每项展开成单独 PRD 时按 `docs/modules/{topic}/01-prd.md` 走标准文档流。本节是**索引 + 最小约束**，不是详细设计。

---

## 2. v0.2 顶层架构图

> **多 surface 部分已 review，定稿 OCW Gateway 中枢模式**（2026-05-15）。详见 §1.1.3。

```
┌──────────────────────────────────────────────────────────────────────────┐
│  L0: Surfaces  ── API-first 多 surface 平行（G8，借鉴 OCW）                │
│                                                                           │
│  ━━ React surfaces（共用 packages/agent-renderer-lark）━━━━━━━━━━━━━━━     │
│  Phase 1                       Phase 2                                   │
│  ┌──────────────────┐         ┌─────────────────────────┐               │
│  │ Web (browser)     │         │ Desktop (Electron)      │               │
│  │ Lark DS + A2UI    │         │ Win/macOS 同 Chromium   │               │
│  │ JSON schema       │         │ + Node HostBridge       │               │
│  │ renderer          │         │ (fs/clip/notify/shell)  │               │
│  │                   │         │ + OS sandbox            │               │
│  └──────────────────┘         └─────────────────────────┘               │
│                                                                           │
│  ━━ 原生 surfaces（独立 renderer，对标 ChatGPT/Claude）━━━━━━━━━━━━━━━━    │
│  Phase 3                                                                  │
│  ┌──────────────────────────────┐  ┌──────────────────────────────┐     │
│  │ iOS Native                    │  │ Android Native                │     │
│  │ Swift + SwiftUI/UIKit         │  │ Kotlin + Jetpack Compose      │     │
│  │ + Swift A2UI renderer         │  │ + Compose A2UI renderer       │     │
│  │ + Swift HostBridge            │  │ + Kotlin HostBridge           │     │
│  │ + Live Activity / Dyn Island  │  │ + Material You + 通知集成     │     │
│  │ + Siri Shortcut               │  │ + Quick Settings tile         │     │
│  └──────────────────────────────┘  └──────────────────────────────┘     │
│                                                                           │
│  ━━ 非 UI surfaces（各自实现 SDKMessage renderer）━━━━━━━━━━━━━━━━━━━     │
│  Phase 2                              Phase 2                            │
│  ┌──────────────────────────┐        ┌──────────────────────────────┐   │
│  │ Teams Bot                 │        │ CLI (thin)                    │   │
│  │ Bot Framework + Graph API │        │ Node + ANSI/cli-table         │   │
│  │ + Adaptive Card renderer  │        │ + Node HostBridge             │   │
│  │ + AAD↔FFAI Identity 映射  │        │ + 用于 SSH/script/CI 场景      │   │
│  └──────────────────────────┘        └──────────────────────────────┘   │
│                                                                           │
│  ━━ 预留 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━     │
│  IDE Adapter / Feishu / WeCom / Inbound Anthropic Messages              │
└─────────────────────────────┬───────────────────────────────────────────┘
                              │ SSE / WS  统一 SDKMessage 协议
                              │ + HostBridge 接口（每个非 Web surface 各自实现：
                              │   Electron-Node / Swift / Kotlin / CLI-Node 各一套）
┌─────────────────────▼──────────────────────────────────────┐
│  L1: Gateway （协议 + 路由 + 适配）                          │
│  - SSE/WS Controller（POST /api/agent/messages → SSE）       │
│  - InboxAdapter 抽象（WebInbox / TeamsInbox / 未来 IMs）     │
│  - 入参归一化（user / org / surface / locale → AgentContext）│
│  - 预留：Inbound Anthropic Messages 适配器                    │
└─────────────────────┬──────────────────────────────────────┘
                      ▼
┌──────────────────────────────────────────────────────────────┐
│  L2: Agent Core （执行单元——所有 agent 机制集中在这）          │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ QueryEngine （per-session TAOR 主循环 / async gen）    │ │
│  │  ┌──────────────────┐  ┌──────────────────────────┐   │ │
│  │  │ Tool Dispatcher  │  │ Sub-Agent Dispatch       │   │ │
│  │  │ (4 executor 路由) │  │ (Agent.spawn = 递归       │   │ │
│  │  │ + Permission 三态 │  │  new QueryEngine 子实例) │   │ │
│  │  └──────────────────┘  └──────────────────────────┘   │ │
│  │  ┌────────────────────────────────────────────────┐   │ │
│  │  │ StreamingToolExecutor（并发 + 互斥 + abort）    │   │ │
│  │  └────────────────────────────────────────────────┘   │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌────────────┐   │ │
│  │  │ Compactor    │  │ Trajectory   │  │ Per-session│   │ │
│  │  │ (5 层)        │  │ Emitter      │  │ Redis lock │   │ │
│  │  └──────────────┘  └──────────────┘  └────────────┘   │ │
│  │  ┌──────────────────┐  ┌──────────────────────────┐   │ │
│  │  │ FailoverErr 触发 │  │ Post-sampling Hooks      │   │ │
│  │  └──────────────────┘  └──────────────────────────┘   │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                              │
│  说明：子 agent ＝ 同类 QueryEngine 实例（递归同层）；主 agent   │
│        await 子 agent 完成；不存在独立 "Coordinator" 层        │
└─────────────────────┬────────────────────────────────────────┘
                      ▼ 通过 Registries 查找 ↓
┌──────────────────────────────────────────────────────────────┐
│  L3: Registries （可发现资源 / 元数据，由 L2 查询）              │
│  ┌──────────────────┐ ┌──────────────────┐ ┌────────────┐   │
│  │ ToolRegistry      │ │ ProviderRegistry │ │ SkillReg   │   │
│  │ + Descriptor      │ │ + Resolver       │ │ (fs + DB)  │   │
│  │ + Availability    │ │ + Discovery      │ │            │   │
│  │ + Surface 矩阵    │ │ + Routing        │ │            │   │
│  └──────────────────┘ └──────────────────┘ └────────────┘   │
│  ┌──────────────────┐ ┌──────────────────────────────────┐  │
│  │ HookRegistry      │ │ StorageBindingRegistry           │  │
│  │ (5 事件 + 4 源)    │ │ (scope 三层: project>user>org)   │  │
│  └──────────────────┘ └──────────────────────────────────┘  │
└─────────────────────┬────────────────────────────────────────┘
                      ▼ 路由到 ↓
┌──────────────────────────────────────────────────────────────┐
│  L4: Adapters + Async Orchestrator                            │
│                                                               │
│  ━━ Executor Adapters（执行实现，被 L2 Tool Dispatcher 路由到）━━ │
│  ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐ ┌─────────┐  │
│  │service:│ │ mcp:   │ │ cli:   │ │ client:  │ │ storage:│  │
│  │NestJS  │ │MCP Cli │ │Docker  │ │HostBridge│ │OneDrive │  │
│  │Service │ │+Servers│ │per-user│ │→ Electron│ │/ Local /│  │
│  │(P/A/K) │ │        │ │sandbox │ │  iOS /   │ │ Future  │  │
│  │        │ │        │ │+白名单 │ │  Android │ │ GDrive  │  │
│  └────────┘ └────────┘ └────────┘ └──────────┘ └─────────┘  │
│  ┌────────────────┐  ┌────────────────────────────────┐      │
│  │ provider:OpenAI│  │ + OpenAI-compatible Shim        │      │
│  │  / Anthropic   │  │   （内部 IR 归一）              │      │
│  └────────────────┘  └────────────────────────────────┘      │
│                                                               │
│  ━━ Async Orchestrator（跨 turn/跨 session 长生命周期管理）━━━━ │
│  ┌────────────────┐ ┌────────────────┐ ┌──────────────────┐ │
│  │ TaskTracker    │ │ Cron Scheduler │ │ ProviderFailover │ │
│  │ (PG 持久化     │ │ + Temporal     │ │ Chain            │ │
│  │  长任务状态)    │ │ worker（隔离   │ │ (priority + cool │ │
│  │                │ │  agent 调度）   │ │  down)           │ │
│  └────────────────┘ └────────────────┘ └──────────────────┘ │
│  ┌──────────────────────────────────────────────────────┐    │
│  │ ★ ModelRouter (G10 核心)                              │    │
│  │   - 5 信号源决策（task / context / 偏好 / 历史 / 成本） │    │
│  │   - 三策略：rule-based / LLM-routed / scope-override   │    │
│  │   - 输出 RoutingDecision → ProviderFailoverChain      │    │
│  │   - 决策落 ModelRoutingDecision 表（学习反馈）         │    │
│  └──────────────────────────────────────────────────────┘    │
└─────────────────────┬────────────────────────────────────────┘
                      │
   ┌──────────────────▼──────────────────┐
   │  L5: Display Channel (A2UI 出口)     │
   │  - Display.render(JSON schema) 增量  │
   │  - Display.snapshot → image 回灌 LLM │
   │  - Capability scoped URL + TTL token │
   │  - 落 AgentArtifact (用户绑定 backend)│
   └─────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│  L6: Cross-cutting （横切关注点，被各层共用，不参与依赖方向）    │
│  Permissions (RBAC + DataScope) · Cost Tracker · OTel ·       │
│  TrajectoryEvent · Memory (per-user + per-org) ·              │
│  Credentials Vault · Settings (per-org/per-user 合并) ·       │
│  Scratchpad (per-session) · History (AgentMessage PG)         │
└──────────────────────────────────────────────────────────────┘

依赖方向：L0 → L1 → L2 → L3 → L4 → L5；L6 横切（不向上依赖）
```

**架构 layering 原则**（受 [`feedback_layering_dependency_direction`](../../../../../.claude/projects/-home-chentao-Code-workspace/memory/feedback_layering_dependency_direction.md) 指导）：
- 每层只依赖下一层（不跨层、不反向）
- 高层易变（surfaces），低层稳定（adapters）
- 横切关注点单独成 L6，不污染主流
- **Sub-agent 跟主 agent 同层（L2）**——递归同类组件必然同层；之前 v0.2 早期版本曾把 "Coordinator" 单独画 L4 是错误的虚构层（混合了"执行流向"和"依赖方向"两种 layering 视角导致）

---

## 3. 工具盘点（FFAI 必备 vs 不必备）

把 CC 43 + OC 49 + OCW 100+ 工具去重后按域聚合，按 FFAI 场景选取：

### 3.1 必备（day-1）
| 工具 | 来源 | FFAI 形态 |
|---|---|---|
| `AskUserQuestion` | CC | 反向提问 + 选项卡片 |
| `Display.render` / `Display.snapshot` | A2UI 协议（OCW） | 主动 UI 渲染 |
| `Agent.spawn` | CC | 子 agent |
| `Skill.invoke` | CC/OC | 调 skill |
| `TodoWrite` / `TaskCreate/Update/List` | CC | 任务跟踪 |
| `ToolSearch` | CC | 大工具集时的相关性筛选 |
| `Project.*` (MCP) | 业务域 | 项目管理 |
| `Approval.*` (MCP) | 业务域 | 审批 |
| `Knowledge.search` (MCP) | 业务域 | 知识检索（替代 ai-assistant） |
| `WebSearch` / `WebFetch` | CC/OC | 联网（org-policy 门控） |
| `EnterPlanMode` / `ExitPlanMode` / `SetPermissionMode` | CC（协作模式两轴）| 切换会话级协作模式（详见 §1.14） |

### 3.2 文件操作（Phase 1 day-1 + Phase 2 扩展 backend）

| 工具 | executor | Phase | 说明 |
|---|---|---|---|
| `File.list` / `File.read` / `File.upload` / `File.search` / `File.shareLink` / `File.preview` | `service:FileService.*` | P1 | 统一接口，内部按用户当前激活 backend 路由（OneDrive day-1 / Local Phase 2 / 未来 GDrive 等）|

详见 §1.11 存储后端抽象。

### 3.3 二期
| 工具 | 来源 | 说明 |
|---|---|---|
| `Monitor` | CC | 长任务监听 |
| `Sleep` | CC | 主动节流 |
| `ScheduleWakeup` / `CronCreate/List/Delete` | CC/OCW | 定时任务 |
| `EnterPlanMode` / `ExitPlanMode` | CC | 复杂任务规划 |
| `SendMessage` | CC | 跨 agent / 跨 session 消息 |
| `ListMCP` / `ReadMCP` | CC/OC | MCP 资源查询 |

### 3.4 二期（Phase 2 / Desktop 上线时）— Client Executor 工具

| 工具 | executor | 说明 |
|---|---|---|
| `File.read` / `.write` / `.list` | `client:fs.*` | 本地文件读写（默认仅 `~/FFAI Workspace/`） |
| `File.diff` | `client:fs.read` + 服务端 diff | 跨版本对比 |
| `Clipboard.read` / `.write` | `client:clipboard.*` | 帮翻译/总结剪贴板内容，结果回灌 |
| `Notify.push` | `client:notify` | 后台任务完成时推送系统通知 |
| `Shell.exec` | `client:shell`（OS 沙盒）| 跑本地脚本——例如帮用户分析本地数据文件、本地 git 操作 |
| `LocalApp.open` | `client:app.open` | 用系统默认应用打开文件 |

> 这些工具**只在 Desktop/Mobile 注册**（通过 ToolDescriptor.availabilityExpression 中 `signal: 'context.surface in [desktop, mobile]'` 控制）；Web 端用户看不到。

### 3.5 服务端 CLI 工具（按需）

| 工具 | executor | 沙盒 image |
|---|---|---|
| `Git.log` / `.diff` / `.show` | `cli:git`（只读）| `ffai/cli-runner:git` |
| `Kubectl.get` | `cli:kubectl`（只读）| `ffai/cli-runner:kubectl` |
| `Psql.query` | `cli:psql`（只读 RO 角色）| `ffai/cli-runner:psql` |
| `AdpCli.*` | `cli:adp-cli` | 内部工具镜像 |

### 3.6 不做（明确舍弃）
| 工具 | 原因 |
|---|---|
| `EnterWorktree` / `ExitWorktree` | 不是 git agent；本地 git 操作走 `Shell.exec` |
| `TeamCreate` / `TeamDelete` | YAGNI |
| Voice / Realtime transcription | OCW 独门，FFAI 不需要 |
| `NotebookEdit` | 不操作 Jupyter |

---

## 4. A2UI 协议（FFAI 关键差异化，吸收自 OCW Canvas + 飞书消息卡片 v2）

> **Q17 ✅ Reviewed 2026-05-15**：单栈方案 = Lark DS + 自定义 JSON schema 渲染层。**业界企业 ToB 主流路线**：Microsoft Adaptive Cards / Slack Block Kit AI / 飞书消息卡片 v2 / 蚂蚁 Ant Design X 全是这条路。**不引入 shadcn/Tailwind**（那是面向开发者工具产品的标准，不是企业 ToB）。
>
> **对标实现**：飞书消息卡片 v2（`schema:"2.0"` + `elements[].tag` + Table/Chart/Button/Markdown 八件套），蚂蚁 `@ant-design/x-card`（JSON 消息流 → 动态卡片）。
>

> **核心 insight**：传统 agent 把 UI 写死成"聊天气泡 + 工具卡片折叠"。OCW 的 Canvas 把 UI 当 agent 的"画布"——agent 边想边推 JSONL 增量，前端边收边渲染，并能把当前画面截屏发回 LLM 形成视觉闭环。这正好契合 FFAI"agent = 工作流编排"的目标。

### 4.1 协议形态

```typescript
// agent 通过 Display.render 推到右侧 Display Panel
type A2UIMessage =
  | { op: 'init'; surfaceId: string; root: ComponentNode }
  | { op: 'append'; surfaceId: string; parentPath: string[]; node: ComponentNode }
  | { op: 'patch'; surfaceId: string; path: string[]; partial: Partial<ComponentNode> }
  | { op: 'data_update'; surfaceId: string; dataPath: string[]; value: any }
  | { op: 'hide'; surfaceId: string }

// 声明式组件（Flutter 风格 / 类似 Anthropic Artifacts）
type ComponentNode =
  | { type: 'Column'; children: ComponentNode[] }
  | { type: 'Row'; children: ComponentNode[] }
  | { type: 'Text'; content: string; style?: TextStyle }
  | { type: 'Table'; columns: ColDef[]; rowsRef: string }  // 指向 data path
  | { type: 'Chart'; spec: VegaLiteSpec; dataRef: string }
  | { type: 'Form'; fields: FieldDef[]; onSubmitTool: string }
  | { type: 'Button'; label: string; onClickTool: string; args: any }
  | { type: 'Diff'; before: string; after: string }
  | { type: 'FileLink'; artifactId: string }
```

### 4.2 Snapshot 回灌（视觉闭环）

```
Agent 调 Display.render(table) → 前端渲染 → 用户截屏？或 agent 主动 Display.snapshot
→ 转 PNG → 作为 image content block 回灌下一轮 LLM
→ Agent "看到自己画的表" → 决定下一步动作
```

**用途**：让 agent 能自检 UI 是否表达清楚、是否触发用户预期。CC/OC 都没有这一层。

### 4.3 安全：Capability-scoped URL

每个 surface 一个 TTL token，泄漏只影响该 surface 而非整个 session（OCW 设计）。

### 4.4 与"工件库（ArtifactStore）"的关系

- A2UI surface = **临时画布**（会话内活动状态）
- Artifact = **持久化工件**（用户维度，跨会话可检索）
- 用户点 "保存到工件库" → 把当前 surface 序列化进 `AgentArtifact` 表

---

## 5. 数据模型（v0.2 完整版，覆盖三家所有持久化点）

```prisma
// prisma/schema/agent.prisma

model AgentSession {
  id              String   @id @default(cuid())
  title           String
  userId          String
  organizationId  String
  projectId       String?  // 会话归项目下（"全部会话"项目容纳无归属会话）
  createdById     String
  schemaVersion   Int      @default(1)  // 配合迁移机制
  // 协作模式两轴（§1.14 / Q34，借鉴 CC）
  planMode        Boolean  @default(false)        // 是否在 Plan mode（受限工具集，只规划不写）
  permissionMode  String   @default("default")    // default | acceptEdits | bypass | scripted | ask
  // 成本累积
  totalCostUsd    Decimal  @default(0)
  totalInputTokens Int     @default(0)
  totalOutputTokens Int    @default(0)
  totalCacheReadTokens Int @default(0)
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
}

model OrganizationAgentSettings {     // 组织级 agent 配置（含协作模式限制）
  id                       String   @id @default(cuid())
  organizationId           String   @unique
  defaultPermissionMode    String   @default("default")
  allowedPermissionModes   String[]                       // admin 可禁某些 mode（如禁 'bypass'）
  defaultStorageBackendKind String?                       // 组织级 storage backend 兜底
  modelRoutingDefaults     Json?                          // 组织级 ModelRouter 偏好
  createdAt                DateTime @default(now())
  updatedAt                DateTime @updatedAt
}

model AgentMessage {
  id          String   @id @default(cuid())
  sessionId   String
  role        String   // user | assistant | system | tool_result
  blocks      Json     // ContentBlock[]
  modelName   String?
  tokens      Int?
  compactedAt DateTime?  // 不删，仅标记"不再喂模型"
  createdAt   DateTime @default(now())
}

model AgentTask {                    // 来自 CC TaskTracker
  id          String   @id @default(cuid())
  sessionId   String
  parentTaskId String?              // sub-agent 树
  type        String   // subagent | long_tool | workflow | cron
  status      String
  description String
  outputRef   String?               // S3 引用
  startedAt   DateTime @default(now())
  endedAt     DateTime?
}

model AgentArtifact {                 // 用户维度，跨会话
  id              String  @id @default(cuid())
  sourceSessionId String?
  userId          String
  organizationId  String
  type            String  // table | chart | form | diff | file | doc | a2ui_surface
  title           String
  storageBackend  String  // 'inline' | 'onedrive' | 'local' | 'gdrive' | ...
  externalRef     Json?   // backend-specific 引用（OneDrive itemId / Local 绝对路径 / 未来 GDrive fileId 等）
  inlinePayload   Json?   // 仅 storageBackend='inline' 时使用，小工件 < 256KB
  tags            String[]
  pinned          Boolean @default(false)
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
}

model ModelRoutingRule {              // G10 模型路由规则（项目 > 用户 > 组织 三层 scope）
  id              String  @id @default(cuid())
  scope           String  // 'organization' | 'user' | 'project'
  scopeId         String
  pattern         Json    // matcher：{ taskType?, toolName?, subAgentType?, contextTokens?{op,value}, hasImage?, ... }
  selectedModel   String  // 'openai/gpt-5.4' | 'anthropic/claude-haiku-4.5' | ...
  fallbacks       String[] // 故障转移链（喂给 ProviderFailoverChain）
  priority        Int     // 同 scope 内多规则匹配时按优先级（高的先匹）
  active          Boolean @default(true)
  reasoning       String? // admin 备注："为啥这条规则存在"
  organizationId  String
  createdById     String
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  @@index([scope, scopeId, priority])
}

model ModelRoutingDecision {         // 每次路由决策的审计 + 学习数据
  id              String  @id @default(cuid())
  sessionId       String
  turnId          String?
  ruleId          String?            // 匹配的 ModelRoutingRule.id（null=LLM-routed 或 default）
  matchSource     String             // 'rule' | 'llm_routed' | 'scope_override' | 'default'
  request         Json               // RoutingRequest 快照（5 信号源完整）
  decision        Json               // RoutingDecision 完整（primary + fallbacks + reasoning）
  actualProvider  String             // 真实跑的（可能 fallback 切了）
  actualModel     String
  actualCostUsd   Decimal?
  actualLatencyMs Int?
  inputTokens     Int?
  outputTokens   Int?
  success         Boolean
  failoverFrom    String?            // 如果触发了 failover
  createdAt       DateTime @default(now())
  @@index([sessionId])
  @@index([createdAt])
  @@index([ruleId])
}

model StorageBinding {                 // 存储后端绑定（项目 > 用户 > 组织 三层 scope）
  id              String  @id @default(cuid())
  scope           String  // 'organization' | 'user' | 'project'
  scopeId         String  // 对应 organizationId / userId / projectId
  kind            String  // 'onedrive' | 'local' | 'gdrive' | 'feishu' | ...
  config          Json    // OneDrive: { rootPath, credentialId }; Local: { rootPath, surface }
  organizationId  String
  createdById     String
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  @@unique([scope, scopeId, kind])
  @@index([scopeId])
}

model AgentMemory {                   // 来自 CC memdir
  id              String  @id @default(cuid())
  scope           String  // user | organization
  scopeId         String
  category        String  // user | feedback | project | reference
  slug            String  // kebab-case
  description     String
  body            String
  links           String[] // [[other-slug]]
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  @@unique([scope, scopeId, slug])
}

model AgentSkill {                    // 来自 OCW skill 声明式
  id              String  @id @default(cuid())
  organizationId  String?            // null = 全局预置
  name            String
  description     String
  whenToUse       String
  bodyMd          String
  requires        Json?              // 依赖工具列表
  allowedTools    String[]           // 限权
  userInvocable   Boolean @default(true)
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  @@unique([organizationId, name])
}

model AgentToolDescriptor {           // 来自 OCW ToolDescriptor
  id              String  @id @default(cuid())
  name            String  @unique
  owner           String  // core | plugin | mcp
  executor        String  // service:<NestModule> | mcp:<server> | cli:<command> | client:<host_bridge_method>
  surfaces        String[] // ['web','desktop','mobile','teams'] — 哪些 surface 可见此工具（控制 client 工具仅 desktop/mobile）
  exposure        String   @default("direct")  // 'direct' | 'deferred' | 'model_only' —— 借鉴 Codex ToolExposure 三态
  availabilityExpr Json   // ToolAvailabilityExpression
  inputSchema     Json
  outputSchema    Json?
  isReadOnly      Boolean
  isConcurrencySafe Boolean
  createdAt       DateTime @default(now())
}

model AgentProviderProfile {          // 来自 OC ProfileResolver
  id              String  @id @default(cuid())
  organizationId  String
  name            String              // 'default' | 'analyst' | 'cheap'
  modelName       String
  providerKey     String              // openai | anthropic | qwen | ...
  routeMetadata   Json
  priority        Int                 // failover 链顺序
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  @@unique([organizationId, name, priority])
}

model AgentCredential {               // 来自 OCW 凭据矩阵
  id              String  @id @default(cuid())
  organizationId  String
  name            String              // 'OPENAI_API_KEY' 等
  refType         String              // env | vault | exec
  refValue        String              // vault://path 或 env name
  policy          Json?               // 例如 'allow_provider=openai'
  createdAt       DateTime @default(now())
  @@unique([organizationId, name])
}

model AgentTrajectoryEvent {          // 来自 OCW Trajectory
  id              String  @id @default(cuid())
  traceId         String
  sessionId       String
  runId           String              // turn id
  source          String              // 'query_engine' | 'tool' | 'provider' | ...
  seq             Int
  phase           String              // 'gather' | 'act' | 'verify'
  payload         Json
  timestamp       DateTime @default(now())
  @@index([sessionId, seq])
  @@index([traceId])
}

model AgentHook {                     // 来自 CC hook
  id              String  @id @default(cuid())
  organizationId  String
  event           String              // PreToolUse | PostToolUse | SessionStart | SessionEnd | UserPromptSubmit
  implementation  String              // 'http_webhook' | 'agent_skill'
  config          Json
  enabled         Boolean @default(true)
  createdAt       DateTime @default(now())
}

model AgentScratchpad {               // 来自 OC scratchpad，sub-agent 共享
  id              String  @id @default(cuid())
  sessionId       String
  key             String
  value           Json
  createdAt       DateTime @default(now())
  @@unique([sessionId, key])
}
```

> 上面所有表的标准字段（id/createdAt/updatedAt/createdById/organizationId）按 `docs/standards/04-database-architecture.md` 补齐。

---

## 6. SDKMessage 协议（合并三家 + 预留 Anthropic 兼容）

```typescript
type SDKMessage =
  // 文本流
  | { type: 'assistant_text'; delta: boolean; content: string }
  | { type: 'thinking'; delta: boolean; content: string }       // Anthropic extended thinking

  // 工具循环（结构同构 Anthropic content block，便于 inbound 适配）
  | { type: 'tool_use'; id: string; name: string; input: any }
  | { type: 'tool_result'; toolUseId: string; output: any; isError?: boolean }

  // 多 agent
  | { type: 'subagent_started'; agentId: string; description: string }
  | { type: 'subagent_completed'; agentId: string; summary: string }
  | { type: 'task_created' | 'task_updated'; task: Task }

  // 显示通道（A2UI）
  | { type: 'a2ui'; message: A2UIMessage }
  | { type: 'artifact_saved'; artifactId: string }

  // 权限 / 交互
  | { type: 'permission_request'; toolUseId: string; question: string; options?: string[] }

  // 控制
  | { type: 'compact_boundary' }
  | { type: 'failover'; from: string; to: string; reason: string }
  | { type: 'result'; subtype: 'success' | 'error'; usage: TokenUsage; cost: number }
```

---

## 7. 与 v0.1 的 delta（变更摘要）

| 维度 | v0.1 | v0.2 |
|---|---|---|
| Inbox 抽象 | 提到但 day-1 不实现 | **正式入架构图作为 L1 一部分** |
| Display Panel | 结构化工件（table/chart/form/diff/file） | **升级为 A2UI 协议**（JSONL 增量 + snapshot 回灌） |
| ToolDescriptor | 简单 Tool 接口 | **加 availabilityExpression + owner/executor 分离 + 协议投影** |
| Hooks | 提到 4 事件保留扩展点 | **明确 5 事件 + 实现限 HTTP webhook + agent skill** |
| Trajectory | 未提 | **新增 AgentTrajectoryEvent 表 + 5 字段 schema** |
| Provider 故障转移 | 未提 | **FailoverError 结构化 + ProviderChain 按 priority** |
| Cron / 后台 agent | 提到 Task Tracker | **明确 Cron Isolated Agent 走 Temporal worker** |
| Provider 数量 | 默认 Codex + ModelProvider 抽象 | **day-1 接 2 家（OpenAI + Anthropic，Q13）+ LiteLLM 后期兜底；舍弃 OC 11×18 散弹** |
| 沙盒 | 提到 Docker per-tenant | **day-1 不做 OS 沙盒**（无 shell 工具）；预留接口 |
| Memory | 未提 | **新增 AgentMemory 表（per-user + per-org，CC memdir 模式）** |
| 凭据 | 未提 | **新增 AgentCredential 表 + ref 模式** |
| Settings | 简单 config | **schemaVersion + 迁移脚本机制 day-1 起步** |
| 端形态 | Web 主入口 | **G3 三期：Web → Electron Desktop → 原生 iOS/Android**，Web/Desktop 共用 React UI；Mobile 独立原生 |
| IM | 飞书/钉钉占位 | **G5：Teams 优先**，Phase 2 实现 TeamsInbox + Bot Framework + Adaptive Card 投影 |
| 工具执行器 | 仅 MCP | **G4：四轨 service/mcp/cli/client**（CLI 服务端 Docker；Client 客户端 OS 沙盒） |
| 沙盒 | day-1 不做 OS 级 | **day-1 接口必备**（CLI 服务端 Docker + Client 客户端 OS 沙盒），首批工具 Phase 2 上 |
| 本地文件 | 不做 | **Client Executor 工具**：Phase 2 Desktop 时通过 HostBridge 实现 |
| 存储后端 | 未提（默认 PG/S3 自家）| **StorageBackend 抽象**：用户绑 OneDrive / Local / 未来 GDrive 等；三层 scope（project>user>org）；FFAI 自己不存用户文件 |
| 模型路由 | 单 provider 单 model | **ModelRouter（G10 核心差异化）**：5 信号源 + 3 策略 + 学习反馈；OC 只做了 sub-agent type 1 信号，FFAI 是首个产品级完整版 |

---

## 8. 落地路线图（v0.2 重排）

| PR | 范围 | 验证 |
|---|---|---|
| **PR1** | UI 骨架 + SSE echo 链路 + `/agent` 路由（Phase 2 redesign 为双栏 + slide-in artifacts 抽屉） | Playwright MCP |
| **PR2** | Prisma schema 全量落地（16 张表）+ AgentGateway Controller + InboxAdapter 抽象（仅 WebInbox 实现） | 集成测试连通 |
| **PR3** | ModelProvider Registry + ProfileResolver + Compatibility Shim + OpenAIProvider + MockProvider | mock 跑 mock LLM 测试 |
| **PR3.5** | **ModelRouter（G10 核心）：5 信号源 + rule-based + LLM-routed + ModelRoutingRule/Decision schema + QueryEngine 集成** | agent 调不同任务自动切模型；决策落 PG 可查 |
| **PR4** | QueryEngine MVP（TAOR + StreamingToolExecutor + 5 层 Compaction + Trajectory event） | 多轮对话 + 强制触发 compaction |
| **PR4.5** | 协作模式两轴（Plan/Execute toggle + Permission mode 5 态）+ EnterPlanMode / ExitPlanMode / SetPermissionMode 工具 + OrganizationAgentSettings 配置 | 用户切 Plan mode 看到工具集受限；admin 可禁 bypass |
| **PR5** | ToolRegistry + Descriptor + AvailabilityExpression + MCP Client + 第一个业务 MCP server（Project） | agent 跑通 "查项目并回显" |
| **PR6** | A2UI 协议 + Display Panel 渲染 + ArtifactStore | agent 画表格 + snapshot 回灌 |
| **PR7** | SkillRegistry（双源） + HookRegistry（5 事件） + Slash Commands 内置三件套 | skill 触发跑通 |
| **PR8** | Sub-agent dispatch（AgentTool → 递归 new QueryEngine 子实例）+ Scratchpad PG 表 | 子 agent 并行研究场景 |
| **PR9** | Memory（AgentMemory 表 + MEMORY.md 索引模式） | per-org / per-user 隔离 |
| **PR10** | Cron Isolated Agent（Temporal worker）+ FailoverError 链路 + Credentials Vault | 跑一个"每天 9 点汇总待审批" |
| **PR10.5** | **StorageBackend 抽象 + OneDriveAdapter + 三层 scope binding（org/user/project）+ onboarding/settings UI + File.* 工具家族** | 用户绑 OneDrive，agent 上传文件能在 OneDrive Web 看到 |
| **PR10.6** | **ModelRouter 配置 UI（管理员后台 + per-user/project 覆盖）+ Routing Decision Dashboard（admin 可看）** | admin 能配规则 / 查路由决策 / 看 cost 报告 |

**Phase 2（Desktop + Teams）**：

| PR | 范围 | 验证 |
|---|---|---|
| **PR11** | Electron Desktop 壳 + HostBridge 接口 + Capability 协商 + Web UI 装壳跑通 | macOS/Win 双端启动登录 |
| **PR12** | Client Executor 第一批工具（`File.read/write/list` + `Notify.push`）+ 授权弹窗 + audit | agent 帮读用户本地 Excel 并总结 |
| **PR13** | `Shell.exec` + OS 沙盒（macOS sandbox-exec / Win AppContainer）+ 二次授权 | agent 跑本地 git/python 脚本 |
| **PR14** | TeamsInbox（Bot Framework webhook + Graph API）+ Identity 映射 + A2UI → Adaptive Card 投影 + Teams 附件自动落用户绑定的 OneDrive | @机器人触发审批流，按钮回调，附件自动入库 |
| **PR14.5** | **LocalStorageAdapter**（复用 G4 Client Executor HostBridge fs.*）+ 管理员后台 StorageBinding 预配置 | 用户在 Desktop 切 Local backend 跑通 |
| **PR15** | CLI Executor + Docker sandbox + 第一批服务端 CLI 工具（git/kubectl/psql 只读） | agent 查 K8s pod 状态 |
| **PR15.5** | `packages/agent-protocol/` + `packages/agent-renderer-lark/` monorepo 抽包 + `frontend/` 改为消费 renderer 包 | 一次重构不破坏 Web 功能 |
| **PR15.6** | thin CLI（Node + ANSI renderer + Node HostBridge）+ A2UI 文本降级 | `ffai ask "..."` 在 SSH 远端可用 |
| **PR15.7** | **ModelRouter 学习反馈 Phase 1**：定期分析 ModelRoutingDecision 表，输出"哪些规则该调整"建议给 admin（推 Gitea issue / dashboard 提示） | admin 收到周报告："规则 R3 触发 100 次但成功率 70%，建议调整" |

**Phase 3（Mobile 原生，Q20 ✅ Reviewed 2026-05-15）**：

> 对标 ChatGPT/Claude/Perplexity 头部 AI chat 全原生路线（受 G9 指导）。`packages/agent-protocol/` 类型用 codegen 生成 Swift / Kotlin。

| PR | 范围 | 验证 |
|---|---|---|
| **PR16** | iOS 原生 app 框架（Swift + SwiftUI/UIKit）+ Swift HostBridge + Swift A2UI renderer（Lark DS iOS 子集）+ AAD 登录 | iOS 装上能登录 + 一句话对话 |
| **PR17** | Android 原生 app 框架（Kotlin + Jetpack Compose）+ Kotlin HostBridge + Compose A2UI renderer + AAD 登录 | Android 装上能登录 + 一句话对话 |
| **PR18** | iOS Live Activity / Dynamic Island / Siri Shortcut（agent 任务进度锁屏显示）| 体验对标 ChatGPT mobile |
| **PR19** | Android Material You + 通知集成 + Quick Settings tile | 体验对标 Claude Android |
| **PR20** | Mobile-specific client tools（相机 OCR / 位置 / 剪贴板等）按需 | — |

---

## 9. 开放问题（**全部 ✅ Reviewed 2026-05-15**）

> v0.2 所有开放问题已 review 完成，下表是定稿决策清单。

### 9.1 架构原则级（G3-G10）

| # | 问题 | 定稿决策 |
|---|---|---|
| G3/G5/G8 多 surface 架构 | API-first 多 surface 平行 vs Web-first | **借鉴 OCW**：Backend API + SDKMessage 协议为唯一基础；React surfaces（Web/Desktop）共用 `packages/agent-renderer-lark/`；Mobile 走原生独立实现；CLI/Teams 独立 renderer |
| G4 工具执行器 | MCP-only vs 多轨 | **四轨**：service / mcp / cli / client，平行注册同一 ToolRegistry |
| 本地文件 + shell 沙盒 | 不做 vs 做 | **做**：Client Executor + 服务端 Docker per-user + 客户端 OS 沙盒 |
| IM 优先级 | 飞书/钉钉 vs Teams | **Teams 优先**（公司内部工具）|
| G9 决策原则 | 工作量 vs 产品质量 | **AI 时代代码成本不是约束**——架构按产品质量 / 业界对标决策，跟头部 AI 产品（ChatGPT/Claude）对齐 |

### 9.2 具体技术决策（Q9–Q34）

| # | 问题 | 定稿 |
|---|---|---|
| Q9 | A2UI 时机 | **day-1 上**（PR6） |
| Q10 | Inbox 抽象 | **day-1 入架构** |
| Q11 | TrajectoryEvent | **day-1 落表**（多组织审计要求）|
| Q12 | Sub-agent | **同进程**；接口预留 Temporal worker |
| Q13 | provider day-1 | **OpenAI + Anthropic 两家**（Qwen/Ollama 后期通过 LiteLLM 兜底） |
| Q14 | Memory | **day-1 实现**（不延后，避免后期重构 system prompt 拼装） |
| Q15 | Hook 实现 | **webhook + agent skill 都做** |
| Q16 | Vault | **PG 加密列**（envelope encryption，FFAI 不是 secrets-heavy） |
| Q17 | A2UI 组件库 | **A 方案：Lark DS + 自定义 JSON schema 渲染层（单栈）**；对标飞书消息卡片 v2 / 蚂蚁 Ant Design X / Microsoft Adaptive Cards |
| Q18 | Inbound Anthropic Messages 适配 | **后期做**，day-1 架构预留（SDKMessage 字段同构 Anthropic content block） |
| Q19 | Desktop 壳 | **Electron**（业界 AI Desktop 70%+ 主流——Claude/Cursor/ChatGPT Win 都用；团队 Node 栈一致；Chromium 跨 OS 行为同源；放弃 Tauri 推荐——之前 Tauri 优势的"体积/启动/内存"对 ToB 企业内部分发不是约束） |
| Q20 | Mobile 壳 | **原生 Swift+SwiftUI / Kotlin+Compose**（对标 ChatGPT/Claude/Perplexity 全原生路线，受 G9 指导） |
| Q21 | Client 工具拆分 | **PR12 轻量（fs/clip/notify/app）+ PR13 Shell 单独 + PR14 专项工具**（"都做"原则）|
| Q22 | CLI 沙盒 | **Docker per-user + 命令白名单 + 网络隔离**；内部多用户场景 Docker 已够，不用 E2B（数据出境）/ Firecracker（过度工程）；SandboxAdapter 接口预留 |
| Q23 | TeamsInbox | **Bot Framework SDK**（@提及/卡片回调原生支持） |
| Q24 | A2UI → Teams Adaptive Card | **基础组件投影**（Text/Table/Form/Button）+ 复杂工件回退 "打开 Web 工作台"链接 |
| Q25 | 默认 backend | **无默认**，用户没绑时报错引导（小工件 inline 继续工作）|
| Q26 | Day-1 backend | **OneDrive**（Local 跟 Phase 2 Desktop 一起）|
| Q27 | Binding scope | **三层 project > user > organization-admin**，用户可全产品默认 + 项目级覆盖 |
| Q28 | 配置入口 | **三处都做**：首次引导 wizard + 个人设置页 + 管理员后台预配置 |
| Q29 | ModelRouter 深度 | **全做**：rule-based + per-scope UI + LLM-routed 自动判断（受 G9 指导 + G10 是核心差异化）|
| Q30 | 路由信号源 | **全套 5 个**：① 任务类型 ② 上下文长度/图 ③ 偏好 ④ 历史 ⑤ 成本 |
| Q31 | 路由学习 | **逐步做**：day-1 落 ModelRoutingDecision 表；PR15.7 上分析报告；二期上自动学习闭环 |
| Q32 | ModelRouter 定位 | **G10 写进设计目标**（核心差异化，不只是 feature） |
| Q33 | 路由理由透明度 | **暴露给管理员**（Routing Decision Dashboard）；普通用户默认不显示 |
| Q34 | 协作模式设计 | **CC 风格两轴正交**：Plan toggle + Permission mode 5 态（default/acceptEdits/bypass/scripted/ask）；表达力 > Codex 一维 4 模式；详见 §1.14 |

### 9.3 设计依据强度

每个定稿都基于以下其中一类依据：
- **业界事实标准**（Q17 Microsoft/飞书路线 / Q20 头部 AI 全原生 / Q19 AI Desktop 70%+ Electron）
- **威胁模型分析**（Q22 多组织内部工具 ≠ SaaS 对外多租户）
- **YAGNI / 渐进式**（Q13 接 2 家 / Q12 同进程 / Q18 后期）
- **G 原则推导**（Q14 G9 优先质量不优先工时 / Q20 同上）
- **多组织企业部署必备**（Q11 TrajectoryEvent 审计）

---

## 10. 不做清单（明确边界，防范围蔓延）

| 不做 | 理由 |
|---|---|
| CLI / TUI / Ink 主入口 | Web 主入口 |
| 自定义 keybinding 系统 | 浏览器原生够 |
| Discord / WhatsApp / Slack Inbox | 公司用 Teams，不做其他社交 IM |
| 服务端文件系统工具 | 本地文件操作通过 **客户端 Client Executor**（Phase 2 Desktop 时上）实现，**不是** 服务端 `Read/Edit/Write` |
| **FFAI 自家文件存储 S3/MinIO** | 用户文件通过 **StorageBackend 抽象**走用户绑定的 OneDrive / Local / 未来其他云盘；FFAI 不存用户文件，只存元数据（AgentArtifact 表）+ 小工件 inline（< 256KB）|
| 反向连 Anthropic CCR | 违反 ToS |
| 自研 LLM 网关替代 LiteLLM | 溢出走 LiteLLM |
| 第三方 plugin 市场 | day-1 不开放，多组织审计成本高 |
| Realtime 语音 / TTS | YAGNI |
| 自动 provider 路由（按价格/延迟） | 简单 priority 链够用，YAGNI |
| 11 vendor × 18 gateway 散弹 | OC 历史包袱 |
| outputStyles / vim mode / voice / moreright | OC/OCW 个人开发者口味，不适合企业 |

---

## 11. 参考文档与历史

- 本文档为 **v0.2 final 定稿**，已**替代并删除**早期 `00-architecture.md` v0.1 草案（v0.1 从未 commit 入库，删除即终稿）。
- 历史脉络保留在本文 §7「与 v0.1 的 delta」表格里，方便追溯每项决策的演进。
- `provider-strategy.md` Q4/Q7/Q8 内容继续生效（文档自身仍标注 v0.1，下次更新时同步升 v1.0）。
- `agent-implementations-comparison.md` 矩阵需在 v0.2 落地后更新 FFAI 列。
- 下游 reference 文档（`claude-code-reference.md` / `openclaw-reference.md` / `openclaude-reference.md` / `codex-reference.md` / `agent-implementations-comparison.md`）里仍有引 `00-architecture.md` 的链接和 "v0.1 9 层对照" 章节——这些是写参考时为快速对齐 v0.1 而做的辅助内容，v0.2 后已过时，**单独排期逐文件评估是否重写或删除**，不在本次范围。

---

> **本文档已 v0.2 final 定稿（2026-05-15）**。PR1 起按 §8 路线图执行。任何决策变更必须先改文档（PR review）再动代码。
