# AgentTeam Product Overview

> Canonical path: `specs/product/overview.md`
> Status: living-spec
> Scope: AgentTeam top-level product behavior, object model, and collaboration shape.

> 目标：定义 AgentTeam 当前的产品行为、对象模型与用户协作方式。本文是产品规范，不再以历史外部实现行为作为默认判据。

## 0. TL;DR

AgentTeam 是一个面向「人类 + AI Agent」协作的类 Slack 系统：

- **核心是消息**：按 Workspace（类似 Slack 的一个团队）隔离；内部有 Channel（群聊）与 DM（私聊线程）。
- **Agent 不是云端跑**：Agent 的 Claude/Codex runtime 在用户机器上运行，由本机 daemon 负责启动/休眠/上报状态；本仓库直接包含一方客户端实现 `desktop/`，其中 `agentteam daemon` 是 canonical 一方入口，machine 接入 / 重连的 CLI 高级路径是一行命令：`agentteam daemon --server-url <url> --api-key <machineApiKey>`；服务端负责编排、消息持久化、通知与 UI。
- **两条平面**：
  - 控制面：Daemon ↔ Server WebSocket（`/api/v1/workspaces/:workspaceId/daemon/connect?key=...`）用于 agent 生命周期、遥测、工作区浏览。
  - 数据面：Runtime → 本地 MCP chat-bridge → Server HTTP（`/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/*`）用于收发消息与历史。

## 0.1 本文定位与实现状态

本文最初是“目标产品设计”，但仓库实现会持续演进。为避免“设计稿”与“代码现状”脱节，本文会明确区分：

- **当前实现（As-Is）**：已在本仓库实现并可通过 workspace-scoped `/api/v1/*` 与 `/socket.io/*` 观察到的行为。
- **规划/未实现（To-Be）**：仍属设计意图，代码尚未实现（或仅实现了一部分）的内容。

标注约定：

- `[已实现]`：与当前代码一致
- `[部分实现]`：主路径已落地，但能力/入口不完整
- `[未实现]`：当前代码不存在

## 0.2 Canonical Product Spec Structure

[`SPECS.md`](../../SPECS.md) 是 AgentTeam 的唯一顶层产品规范索引。本文属于 `product` domain，保留 AgentTeam 顶层产品行为、对象模型与协作形态；长期有效的子域规范统一放在 repo-root `specs/`，并由各 domain `README.md` 维护逻辑顺序。

目录约束：

- `specs/` 是 canonical specs 的唯一目录，按 `specs/{domain}/...` 组织。
- `guardrails/` 是 executable architecture / spec checks 的唯一目录，按 `guardrails/{domain}/...` 组织。
- `docs/organizations/`、`docs/public/`、`docs/research/`、`docs/archive/` 等目录可以保留 guides、research、archive 和 usage material，但不定义默认产品规范入口。
- dated design / RFC 材料只作为历史参考，不作为默认产品规范入口。

如果本文与 canonical 子域规范冲突，以对应的 root `specs/**/*.md` 为准。

## 1. 背景与约束

### 1.1 当前事实来源

以下内容构成当前产品与实现判断依据：

- 本仓库的 canonical product / domain specs
- 仓库内置 desktop client：`desktop/`（AgentTeam 仓库内的一方 desktop client 实现；canonical 客户端入口是 `agentteam daemon`）
- 本仓库当前 public / internal contract 与对应代码实现

历史兼容说明：

- 实现层可能仍保留对旧 daemon 状态目录或旧字段形状的部分兼容，但这些兼容不再定义产品方向。

当前产品决策如下：

- 产品名明确为 AgentTeam
- 密码约束放宽到 6 位
- 体验优化：创建账户后自带 Workspace
- 体验优化：人类端登录后，如果只有 Workspace 的话会直接自动进入该 Workspace，不展示 Workspace 列表页
- 体验优化：Email 验证链接点击后自动尝试递交验证
- 认证治理：部署可用 `AGENTTEAM_PASSWORD_AUTH_ENABLED=false` 关闭本地密码体系；此时必须启用 PingCAP / AgentTeam User Center OIDC，登录页会直接跳转到统一身份登录
- 工程化：当 `AGENTTEAM_MAIL_DRIVER=noop`（本地开发默认）时，不要求 Email 验证（注册会直接视为已验证并自动登录）
- 简化：核心自部署形态不包含订阅升级体系，所有用户默认享有完整功能；billing-enabled 商业部署可启用托管模型 credits 充值和组织 human-seat 购买，分别由 [`specs/billing/hosted-credits-payment.md`](../billing/hosted-credits-payment.md) 与 [`specs/billing/organization-seat-purchases.md`](../billing/organization-seat-purchases.md) 定义，并受当前 billing 配置控制
- 可选治理层：新增 `agentteam-org` provider 与 `/api/orgs/*` Web 管理入口，支持已有 organization 的 member/team/unit/workspace 治理；organization 由用户 onboarding 自动创建或通过邀请/Provider 加入，浏览器用户不自助创建 organization；仍保持 workspace-first 与 `/api/v1/workspaces/:workspaceId/*` path identity 语义不变
- 仓库内置一方 desktop client：`desktop/`（单一产品二进制；`agentteam daemon` 负责一方客户端接入）
- 体验优化：创建/编辑 Agent 时支持自定义模型 ID（"Custom…"选项）
- 模型配置：新建 model profile 必须从空模型选择开始，不得继承当前 AgentTeam 托管模型；用户明确配置的私网模型网关允许执行模型检测和连接测试，但回环、链路本地及元数据地址继续拒绝
- 产品分叉：Web UI 额外提供 `Threads` 侧边栏分组，聚合“我创建 / 我回复 / @我”的 reply thread，并支持对单个 thread 取消订阅；该入口是对现有 thread 数据的附加视图，不改变原有频道未读语义
- Codex runtime 使用规范 runtime ID `codex`，其实现是通过 `codex app-server` 子命令入口启动的长生命周期 JSON-RPC 进程（stdio transport）。它支持原生 thread / turn 协议控制、通过 `thread/rollback` 支持 DM / reply thread rewind、通过 `thread/compact/start` 支持空闲期上下文压缩，并通过 JSON-RPC notification 提供流式输出。旧的一次性 CLI 实现不是 AgentTeam 支持的 runtime。

### 1.2 需求（产品 + 工程）

- solo-maintainer，部署在 AWS，尽可能使用托管服务降低运维复杂度
- 服务端 Go 开发（单体优先，清晰模块边界）
- 可靠、可扩展（但不强制考虑限流、多副本等）

## 2. 产品形态（Product Spec）

### 2.1 核心对象与层级

核心关系分为三条视角：

- **资产视角**：`Role -> Agent`
- **消息视角**：`Workspace -> Member / Channel / DM`
- **运行视角**：`Workspace -> Agent -> Instance`

其中 Role 是可复用、可版本化的角色包；Agent 是 workspace 内可被提及和加入 channel 的协作成员；Instance 是绑定 Machine 的 live runtime。Role 不参与消息路由或 channel membership，Instance 的 session/status/activity/trajectory 由 daemon runtime 管理，不是 TiDB 持久化事实。

对象定义：

- **User**：通过邮箱注册或登录，可属于多个 Workspace。
- **Workspace**：成员、消息、机器和 Agent 的逻辑隔离边界。
- **Role**：有单一 owner 的可复用角色包；官方 preset 是只读模板。包内必需 `IDENTITY.md`，并可包含 Skills、Knowledge、KB 与 Memory grants。Role 不可直接被提及、加入 channel 或运行。
- **Member**：Workspace 内统一消息身份，分为 human 与 agent。
- **Channel**：Workspace 内唯一命名的协作 scope；`#all` 默认存在。
- **DM**：两个 member 之间的私聊 scope。
- **Machine**：运行 AgentTeam Daemon、承载 Agent Instance 的机器。
- **Agent**：由 Role binding 驱动的 workspace worker，拥有稳定 agent handle 与隐藏 member id；本地 recovery memory 使用 `MEMORY.md`、`state/`、`kb/`、`archive/`。
- **Instance**：Agent 在 Machine 上的 live runtime；临时运行状态以 daemon 为 source of truth。

### 2.2 成员与权限模型

Workspace 角色（最小集，后续可扩展）：

- `owner`：创建者；可管理 workspace 设置、成员、机器、agent
- `admin`：[已实现] 当前 public API 提供该角色：可管理 agents/machines/members 等 workspace 运营面，但不因为 workspace 角色自动获得普通 channel 的可见性或 `channel owner` 权限
- `member`：可聊天、创建 channel、加入 public channel、被邀请进入 private channel；默认可以读写 `#all`
- 角色的权威来源就是 human member 自己的 `workspace_role`
- agent member 没有 workspace 管理角色；agent 永远不是 owner
- [已实现] 当前 public API 提供成员角色变更：`PATCH /api/v1/workspaces/:workspaceId/members/:userId`（payload: `{ role }`，仅 owner 可调用；仅允许 `admin|member`；禁止修改 owner）

Channel 访问控制：

- `#all`：所有 member 强制加入
- 其他 channel：
  - 创建时可选 `private | public`，默认 `private`；v1 不支持后续修改 visibility
  - `private`：只有已加入 member 可见；未加入成员完全不可见；workspace owner/admin 与普通 member 一样没有后门可见性
  - `public`：workspace member 可通过搜索/发现加入；未加入前只能看到 channel metadata（如名称、描述），不能看到消息历史、task、成员列表
  - “My Channels” / sidebar 只显示已加入的 channel；public channel 不会自动出现在每个人的列表里
  - Human 可以在 Conversations 二级 sidebar 中创建、命名、选择图标并调整个人频道分类；分类、频道归属和折叠状态按 `(workspace, human member)` 持久化，同一用户可跨 Web/Desktop/设备同步，但绝不共享给工作区内的其他成员
  - 已加入 member 可以继续邀请其他 member
  - 普通 channel 采用单 human owner 模型：只有 `channel owner` 可以修改设置、移除成员、删除 channel、转移 ownership
  - `channel owner` 离开前必须先转移 ownership
  - 显式 add/remove channel member 成功后，channel timeline 会写入居中的灰色 system message 作为审计记录；单成员操作每次实际变化写一条记录，批量 add 对同一请求中的实际变化成员写一条聚合记录；重复 add/remove 若没有改变当前有效成员关系，不重复写记录

Agent 的 channel membership：

- 现有 MCP 工具集 **没有 join/leave channel 的工具**
- 因此实质上 **agent 不能自主加入/退出 channel**；只能由 human 或服务端策略调整其成员关系
- 但服务端实现上先不需要特殊对待，agent 和 human 一样都是 member 身份，保留 agent 也可以加入/退出 channel 的能力；只是 UI 不提供入口，agent 也不主动调用

DM 可见性：

- [已实现] 当前 public `/api/*` 下，DM（无论 human↔human 还是 human↔agent）均仅参与双方可读可写（`GET /api/channels/dm` 只返回参与者 inbox）
- [已实现] 删除 agent 时会从参与者的 Workspace Inbox 物化列表中移除该 agent 参与的 DM；原 DM `scope_messages` 历史不作为 agent 生命周期删除的一部分被物理删除。
- [未实现] 规划：只要 DM 中任一方是 agent，则该 DM 对 workspace 内所有 human member 可读，但仅参与者可写；非参与者不进入 inbox，不产生未读/通知；UI 可提供单独的 “Agent DMs” 视图（读-only）

#### 2.2.1 标识与展示（简化后的定稿）

标识与展示已经从本文抽出为 canonical 子域规范：[`specs/identity/identity.md`](../identity/identity.md)。

本产品文档只保留顶层结论：

- human 默认展示名使用 `displayName`，默认路由 target 使用 `member.id`
- agent 默认展示名使用 `displayName`，默认路由 target 使用 `agents.name`
- service/App 默认展示名使用 `displayName`，默认路由 target 使用安装态公开 handle（当前 App v1 为 slug）
- `users.name`、agent `member.id`、principal id 都属于兼容或 inspect 数据，不是默认人类可见标签
- detail / inspect surface 可以显示 id，但必须显式标注且保持次要
- legacy alias 只允许停留在解析边界，不允许重新泄漏回默认 UI

涉及消息、DM、task、mention、member list、details、diagnostics 的任何新规则，都应直接更新 [`specs/identity/identity.md`](../identity/identity.md) 而不是在本文重新复制一份。

#### 2.2.2 Workspace 加入与邀请

Workspace 的成员关系建议采用**邀请制**：

- 邀请：`owner` 输入被邀请人的邮箱或手机号，服务端按 [`Invite Identity Target Spec`](../identity/invite-identity.md) 严格解析为 email/mobile target，并生成可复制邀请链接；邮箱/手机号只用于后续身份匹配，不表示 AgentTeam 会自动发送邮件或短信
- 接受：
  - 受邀人必须先完成登录；当 invite target 是 email 时必须拥有匹配的 verified email，当 invite target 是 mobile 时必须拥有匹配的 verified mobile
  - 登录后可通过邀请链接接受邀请；owner 复制的 pending invite 链接使用 invite id 进入登录后的接受流程；若用户已登录且 verified email/mobile 匹配有效 invite，站内也会提示其接受该 workspace invite
  - token 链接、pending invite id、站内 pending banner 必须使用同一套 target matcher，不能拆成 email-only 与 mobile-only 接受路径
  - 校验通过后，服务端先确保受邀人加入该 Workspace 所属组织，再在该 Workspace 下创建一个 human member，并写入 `workspace_role=member`
  - `member.id` 由服务端自动生成；`displayName` 可先使用用户资料默认值，后续允许修改
- 幂等：若匹配 invite target 的当前登录用户已是成员，则 accept 返回成功但不重复创建

### 2.3 账号与注册（邮件）

必须支持（当前实现）：

- [已实现] 邮箱注册：`email + password + name`；系统初始化后默认改为邀请制，只有有效 workspace / organization 邀请或注册白名单邮箱可以注册；当 `AGENTTEAM_PASSWORD_AUTH_ENABLED=false` 时，本地密码注册入口关闭
- [已实现] 邮箱验证：配置开启时，验证通过才可访问主要管理/聊天 API（`/api/auth/*` 除外）
- [已实现] 密码登录/修改/刷新/退出：`accessToken` + `refreshToken`（refresh 轮换）；当 `AGENTTEAM_PASSWORD_AUTH_ENABLED=false` 时，`POST /api/auth/login` 与用户资料里的密码修改不可用
- [已实现] 忘记密码 / 重置密码（邮件 token）；当 `AGENTTEAM_PASSWORD_AUTH_ENABLED=false` 时，这两个密码恢复入口不可用
- [已实现] PingCAP / AgentTeam User Center OIDC 登录：当 `AGENTTEAM_PINGCAP_AUTH_ENABLED=true` 时可用；当 `AGENTTEAM_PASSWORD_AUTH_ENABLED=false` 且 `AGENTTEAM_PINGCAP_AUTH_ENABLED=true` 时，登录页不展示密码表单并直接发起 PingCAP 登录
- [已实现] PingCAP / AgentTeam User Center OIDC 登录支持 web 与 desktop client 分离：浏览器登录使用 web client；desktop 使用 PKCE S256 public client 和 loopback redirect（`127.0.0.1` / `::1` 动态端口）
- [已实现] PingCAP OIDC 账号识别支持邮箱或已验证手机号二选一：userinfo 中存在 `email` 时按邮箱关联；邮箱缺失但 `phone_number_verified=true` 且 `phone_number` 有效时，按手机号关联并写入本地 `users.mobile` / `users.mobile_verified_at`

系统初始化（当前实现）：

- [已实现] 首个注册用户会被 bootstrap 为 admin（用于初始化系统），并自动拥有个人 Workspace
- [已实现] 前端登录页不展示公开注册入口；受邀用户通过邀请链接进入创建账号流程

认证开关矩阵（当前实现）：

| `AGENTTEAM_PINGCAP_AUTH_ENABLED` | `AGENTTEAM_PASSWORD_AUTH_ENABLED` | 行为                                                                                           |
| --------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------- |
| `false`                     | `true`                       | 只提供本地密码登录 / 注册 / 密码恢复 / 密码修改                                                |
| `true`                      | `true`                       | 登录页提供本地密码登录，并额外提供 PingCAP 登录按钮；登录后可修改本地密码                      |
| `true`                      | `false`                      | PingCAP-only：登录页直接跳转 PingCAP，本地密码登录 / 注册 / 密码恢复 / 密码修改 API 返回不可用 |
| `false`                     | `false`                      | 配置错误，服务拒绝启动                                                                         |

登录/注册成功后的默认行为（当前实现）：

- [已实现] 确保个人 Workspace + `#all` + human member 存在（幂等）

### 2.4 消息模型与语义

详细的协作路由、mention 语义、受控广播、thread 参与者继承和 task handoff 协作边界，统一由 [`specs/collaboration/collaboration.md`](../collaboration/collaboration.md) 定义；reaction 的产品语义、agent delivery 与 API 统一由 [`specs/message/reaction.md`](../message/reaction.md) 定义；详细的 Task Board / kanban / review / agent task execution 统一由 [`specs/task/board.md`](../task/board.md) 定义；agent output review、review context 与 output target 由 [`specs/agent/output-review.md`](../agent/output-review.md) 定义；workspace task hub 与 channel `List` / `Board` 的 browse/query/pagination contract 统一由 [`specs/task/board-pages.md`](../task/board-pages.md) 定义。本文只保留消息产品形态与协议字段的顶层约束。

消息类型：

- Channel 消息：属于 `#channel`
- DM 消息：属于一个 DM thread；MCP 侧使用 `dm:@<peer-human-member-id>` 或 `dm:@<peer-agent-name>`
- Thread 消息：[已实现] 属于某条消息的回复线程（reply thread）；MCP 侧 target 形如 `#<channel>:<shortid>` / `dm:@<peer>:<shortid>`（`shortid` 为 6-8 位 hex）

消息协议以 target + canonical author profile 表达路由与作者展示：

- `channel_type`: `"channel" | "dm" | "thread"`
- `channel_name`:
  - channel：`channel.slug`，不带 `#`
  - dm：对方公开标识，human 用 `member.id`，agent 用 `agents.name`，不带 `@`
  - thread：[已实现] `thread-<shortid>`（或直接 `<shortid>`；daemon 侧会兼容去掉 `thread-` 前缀）
- `parent_channel_type`：[已实现] thread 场景下必填，`"channel" | "dm"`
- `parent_channel_name`：[已实现] thread 场景下必填；channel thread 为 `channel.slug`，DM thread 为对端公开标识（human 用 `member.id`，agent 用 `agents.name`）
- `authorProfile.actorType`: `"human" | "agent" | "service"`
- `authorProfile.targetId`: 作者路由公开标识（human 用 `member.id`，agent 用 `agents.name`，service 用安装态公开 handle；App v1 为 service member handle，默认等于 App slug）
- `authorProfile.displayName`: 作者默认展示名；UI 不从路由 target 推断展示名
- `authorProfile.serviceKind` / `authorProfile.serviceSlug`: service/app 作者的种类与稳定 slug，用于渲染而不是路由
- `content`: 有界的 UTF-8 Markdown 源文本；wire/storage 仍是 text，不存 rich-text AST。web/app 可在其中引用附件；最大 `32KiB`，按字节，超出会被拒绝
- [已实现] Web 的消息 Markdown 渲染支持 `$…$` / `$$…$$` 以及 `\(…\)` / `\[…\]` LaTeX 公式分隔符；公式只改变展示方式，wire/storage 中仍保留原始 Markdown 文本
- `timestamp`: RFC3339Nano

对人类 Web 产品，不建议直接把 internal payload 原样透出；应在展示层或 public API 中补充：

- 作者的 `displayName`
- DM 对端的 `displayName`
- service/app 作者的种类与稳定 slug（例如 `authorProfile.serviceKind`、`authorProfile.serviceSlug`），用于渲染而不是路由
- 成员类型、头像、状态等人类友好的展示信息

附件（web/app）：

- [已实现] 支持消息附件：
  - 上传：`POST /api/v1/workspaces/:workspaceId/attachments`（`multipart/form-data`：`scopeId` + `files`）
  - 发送：`POST /api/messages` 支持 `attachmentIds?: string[]`，允许 `content=""` 且仅携带附件
  - message 字段：`attachments?: [{ id, filename, contentType?, sizeBytes?, thumbnailUrl? }]`
  - 下载：`GET /api/attachments/:attachmentId?token=<accessToken>`
- [已实现] 正文内附件采用 Markdown 中的 `attachment://<attachmentId>` 引用：图片使用 `![alt](<attachment://...>)`，视频、音频和普通文件使用 `[label](<attachment://...>)`。`content` 中引用出现的位置和顺序是消息排版的事实来源；`attachmentIds` 仍定义本次发送要 claim 的附件集合，并沿用 scope/message 所有权与下载鉴权，不用其数组顺序推断排版
- [已实现] 只有同时属于当前 message sidecar 的附件引用才会被解析。图片按正文位置显示；视频/音频的标准附件链接由支持的客户端根据 sidecar `contentType` 升级为原生受控播放器。只实现附件链接、不实现内嵌播放器的旧客户端继续降级为鉴权下载，不需要新的消息或附件 wire shape
- [已实现] HTML 附件仍持久化为标准 `[file.html](<attachment://...>)`，支持的客户端按 sidecar `contentType=text/html` 识别，并可用 `.html` 文件名作兼容 fallback。在正文引用位置显示小型静态网页预览，点击后用同一份静态内容放大查看；不支持预览或预览失败时保留鉴权下载链接
- [已实现] 第一版 HTML 预览运行在无脚本权限的隔离 sandbox iframe 中，并在装载前阻断脚本、事件处理器、表单提交、页面跳转、嵌套 frame/object 与外网资源。交互式 JavaScript 原型不复用消息内静态预览能力；如未来支持，必须部署在与 AgentTeam 应用隔离的独立 preview origin，并单独定义鉴权、CSP 与宿主通信边界
- [已实现] 没有被 `content` 引用的附件（包括旧消息与仅附件消息）继续显示在正文下方的附件 sidecar；同一附件已被正文引用时不在底部重复展示
- [已实现] 微信小程序的频道、DM 与 reply-thread 共用 composer 在输入框右下角提供图片按钮；点击只打开微信图片库，选择一张受附件大小限制约束的压缩图片后直接完成上传并发送仅附件消息，不改写或清空当前文字草稿。选择取消不产生请求；上传期间禁用重复提交并显示进行中状态；上传失败保留同一临时图片供用户确认重试，消息发送失败则沿用 optimistic message 的点击重试能力
- [已实现] Web 的频道与 reply-thread `Attachments` 面板使用横向铺满的响应式预览流；同一 `message_id` 的附件聚合为一个卡片和预览序列，reply thread 只作为来源标签与筛选范围，不把整个 thread 的附件折叠为一组。卡片点击先打开附件预览，只有显式选择“在对话中查看”才进入来源 scope 并定位 message。HTML、Markdown 与常见源码卡片在接近视口后异步读取有界内容，复用短期 Blob 缓存；HTML 卡片以缩小的桌面视口呈现静态 sandbox 文档，单张图片卡片按自身比例展示，不强制统一高度。
- [已实现] Web 的消息附件与 `Attachments` 面板复用同一套无边框全屏附件查看器；单行 Header 使用类型 Badge + 文件名，并保留关闭、下载等文件操作。频道附件预览的来源信息按需从 Header 打开，不常驻占用预览宽度。
- [已实现] 当前 Web 内嵌视频受 public 单文件 `20 MiB` 上限约束，播放器先通过鉴权接口读取完整响应并建立 Blob URL；它适用于短视频，不是流式媒体传输。首帧快速开始和任意位置 seek 需要后续增加短期授权媒体 URL 与 HTTP Range/`206 Partial Content` 支持
- [已实现] desktop/internal 也支持图片附件；历史外部 daemon/chat-bridge artifact 曾使用过相近 tool 形状，但当前产品规范以本仓 contract 为准
- [已实现] 未发送的附件不会永久保留：后台会定期清理 `message_id IS NULL` 且超过固定 TTL 的上传，避免 TiDB 被“弃发附件”无限堆积；若上传后长时间未发送，需要重新上传
- 冷归档 worker 当前关闭，现有冷对象格式不保留附件。重新启用消息冷归档前，必须先让归档与恢复同时保留 `content` 中的附件 ID、message attachment manifest（至少含 ID、文件名、MIME 与大小）以及附件 blob 的可访问生命周期；不得因为热消息行被淘汰就使正文内嵌媒体或下载链接失效

Reaction（web/app + desktop/internal）：

- [已实现] reaction 是附着在消息上的轻量状态，不会作为独立聊天消息写入 `scope_messages`
- [已实现] Web UI 会在消息行展示 reaction 聚合，并允许当前 actor 直接 add/remove
- [已实现] 当目标消息作者是 agent 时，添加 reaction 还会进入 agent-facing daemon delivery / notification 模型；详细规则见 [`specs/message/reaction.md`](../message/reaction.md)
- [已实现] 当目标消息作者是 human 时，添加 reaction 会额外 fanout 一条 author-targeted realtime/browser notification；它不写入 `scope_messages`，也不新增 human unread 计数或独立 activity ledger

消息序列（当前实现同时服务 desktop/internal 与 web/app）：

- [已实现] desktop/internal：每个 thread（Channel 或 DM）都有单调递增 `seq`（用于 canonical Agent Runtime history 与 daemon 的未读语义）
- [已实现] web/app：额外维护 workspace 级 `global_seq` 用于跨 thread 增量同步（`GET /api/messages/sync?since_seq=...`）与未读角标（热窗口内）
- [已实现] 发送消息后，发送者的 read cursor 会自动推进（至少推进 `last_read_seq`；web 侧同时推进 `last_read_global_seq`）

#### 2.4.1 回复线程（Threads / Reply Threads）

当前 AgentTeam runtime 支持“消息回复线程”（不要与本仓库当前用于 channel/dm 的 child `scopes` rows概念混淆；这里指的是**挂在某条父消息下的子对话**）。

- [已实现] 当前 Web UI 形态：
  - 消息行操作 contract 由 `specs/message/actions.md` 约束：桌面端使用 row-anchored floating toolbar，顶层只保留 `Reply in thread` 与 `More actions`
  - 消息行会在正文下方展示 reaction pills；桌面端 `Add reaction` 使用 Slack 风格的 emoji add-reaction 图标，并通过右上角 toolbar 触发 row-anchored picker dropdown；picker 需要展示完整开源 emoji dataset，而不是手写维护的小子集，并在最上方使用 emoji-only 分类 tabs（搜索框放在 tabs 下方）、支持列表滚动与 tab 高亮联动，并在点击外部区域时直接收起；同时保留对非建议集 emoji / shortcode 的显式 `Add emoji` 提交。现有 reaction pill 还需要承担“看是谁点了这个 reaction”的职责：桌面端 hover pill 显示 reactor 名单，移动端对 pill 长按显示同类名单，而直接点击 pill 继续只做 add/remove 自己的 reaction。底部 composer 输入框旁也应提供 emoji symbol trigger，复用同一套 dataset / tabs / search / `Add emoji` 选择能力；桌面端 message row picker 与底部 composer / thread composer picker 都应给足宽高，避免完整 emoji dataset 被裁切或显得过于局促；移动端 reaction picker 改为从底部弹出的 sheet / drawer，并支持向下拖拽或浏览器返回手势先关闭 picker；它们都属于消息附属状态，不是新的 thread reply
  - 桌面端 thread 入口仍以 thread summary footer 与 `Reply in thread` 为准；移动端 channel 时间线中，点击消息行本体直接打开该消息的 reply thread
  - 小屏视口不再显示 inline message action rail；`Reply in thread`、`Copy markdown`、`Rewind conversation`、`Inspect delivery` 统一收敛到 long-press action sheet；sheet 顶部先显示左上角的小号 `Message actions` 提示，下面一排常用 reaction quick picks，quick-pick 行最右侧的 `more` 图标再以上滑 bottom sheet / drawer 打开完整 reaction picker；所属消息行在 long-press 打开菜单时需要保持与 hover 类似的 active/focus 高亮；移动端进入 thread 只做导航，不自动聚焦 composer，也不触发输入法弹出
  - Thread panel：桌面右侧抽屉（约 400px），移动端全屏；顶部显示父消息，下方显示 replies 列表与输入框
  - Thread panel 的二级 tabs 在 `Pinned` 与 `Review` 之间提供 `Attachments`；该列表只读取当前 reply-thread scope 的附件，不汇总父频道或 sibling threads，并在用户显式打开 tab 后才分页加载
  - thread ref：`#<channel>:<shortid>` 可点击打开 thread；`dm:@<peer>:<shortid>` 当前为置灰不可点击
- [已实现] 本仓在 thread inbox 上有一组 product-specific 增量能力：
  - Thread panel header 支持显式 `Subscribe / Unsubscribe`；Workspace `Inbox` 中 reply-thread row 的右键菜单也提供 `Unsubscribe from this thread`，并复用同一取消订阅 mutation
  - 左侧 Conversations sidebar 的 `Threads` 分组是 IM subsystem 下 subscribed thread scopes 的轻量预览，而不是完整 inbox：它支持用户在 `Settings > Preferences` 里按个人偏好配置“最近活跃时间范围（近 8 小时 / 近 1 天 / 近 1 周）”与“最大展示数量（1-20）”；`All Threads` 页面沿用同一 IM thread surface。Workspace `Inbox` 页面使用 conversations view model 展示全量 inbox，不按 subsystem 隐藏 KB 等非 IM thread。
  - 频道内的 `Threads` Tab 在有界的频道话题候选列表上提供 `All / Subscribed / Unread / Read` 分类；`Subscribed` 只匹配当前 human 的显式 `active / quiet / mute` 订阅状态。话题的可读性、打开或已读状态都不等价于订阅，不能据此伪造订阅状态。
- [已实现] public HTTP API：
  - `POST /api/channels/:channelId/threads`（payload: `{ parentMessageId }`）→ `{ threadChannelId, replyCount, lastReplyAt, participantIds }`
  - `GET /api/channels/:channelId/threads` → `Record<parentMessageId, ThreadSummary>`
  - `GET /api/v1/workspaces/:workspaceId/threads/:threadChannelId/attachments` → 当前 reply thread 的可见附件分页，不包含父频道或 sibling threads
  - 语义补充：即使父消息已被归档出热层 `scope_messages`，只要调用方已经持有精确的 `parentMessageId`，服务端仍应能打开/创建对应 reply thread；这条能力不等价于“web/app 可分页读取冷历史”
- [已实现] Socket.IO：
  - `thread:updated` `{ parentMessageId, threadChannelId, replyCount, lastReplyAt, participantIds }`
- [已实现] product-specific thread inbox API：
  - `GET /api/threads`
  - `POST /api/threads/:threadChannelId/subscribe`
  - `POST /api/threads/:threadChannelId/unsubscribe`
- [实现中] Web 对外显示为 `Messages` 的 workspace 主入口使用内部 `Inbox` surface 和 workspace-scoped `GET /api/v1/workspaces/:workspaceId/conversations` view model，展示 channel、reply thread、human DM、agent DM 以及其它 subsystem 投射到 inbox 的 thread；列表项由后端产出 `/inbox/channel/:parentId/thread/:threadId` 或 `/inbox/dm/:parentId/thread/:threadId`，点击后保持 Inbox 会话列表、父会话与右侧 Thread 同屏，并复用既有 channel/thread/DM timeline 组件。Conversation sidebar 的 thread preview 不复用 Inbox 全量列表，而使用 IM-only subscribed thread surface。
- [已实现] desktop/internal 消息字段：
  - thread 消息应使用 `channel_type="thread"`，并附带 `parent_channel_type/parent_channel_name` 以便 bridge 正确格式化 target

关键隐含约束（来自当前实现）：

- `shortid` 需要能反推出唯一的 `parentMessageId`
- thread 不支持嵌套（thread inside thread）

实现方案（AgentTeam）：

- thread 在实现上是一个“隐藏 channel”（`scopes.type = 'thread'`），slug 固定为 `thread-<shortid>`，不出现在 `/api/channels` 列表，也不允许通过常规 channel API 进行 join/leave/rename/delete
- thread 的消息仍复用既有的 thread child `scopes` / `scope_messages` 模型：每个 thread channel 对应一条 `scopes(type='thread', parent_scope_id=<channelScopeId>, root_message_id=<parentMessageId>)`
- 新增 child `scopes` rows把“thread channel”与“父消息”关联，并维护 thread summary（`reply_count/last_reply_at/participant_ids`），用于：
  - `GET /api/channels/:channelId/threads` 返回 summaries
  - 发送 thread 消息时 emit `thread:updated`
- `shortid` 的实现取舍：为了满足“前缀匹配 parentMessageId”的当前约束，`message.id` 使用 `workspace.global_seq` 的低 32 位作为 8 位 hex 前缀（总长 32 位 hex）。因此 shortid 在协议上仍允许 6-8 位 hex，但 canonical 会规范化为 8 位。

#### 2.4.2 会话 Rewind

- [已实现] public API：`POST /api/messages/:messageId/rewind`
- [已实现] v1 支持三类会话面：
  - DM 主线
  - channel 主时间线
  - reply thread
- [已实现] Web 只在“当前用户自己发送的 human message”上展示 `Rewind`
- [已实现] target message 必须仍在 hot history；rewind 语义是把该会话面恢复到该消息发送前的状态，因此 target 与其后的整个 suffix 都会写成 `rewound` lifecycle tombstone、scrub 派生投影，并从普通读路径消失
- [已实现] 如果 suffix 中包含其他 human 的消息，整个 rewind 会失败；同一用户自己更晚的 human 消息会随 suffix 一起 rewound
- [已实现] 如果 suffix 涉及 agent，服务端会按该 agent 在 suffix 中的 `first-touch` 计算恢复点；只有 rewind-capable runtime 才允许参与；当前支持的有效 runtime 只有 `codex`
- [已实现] AgentTeam 对 rewind 的稳定保证是恢复 runtime 会话面，并将 suffix 写成 `rewound` tombstone/scrub；代码改动、workspace 文件、memory、外部副作用是否会被一并恢复，取决于具体 runtime implementation
- [已实现] rewind 成功后会发送 realtime 事件 `conversation:rewound`；客户端必须 refetch 当前会话、DM/thread summary 与 unread counts；被 rewound 的 suffix 不应再出现在 Web、`/api/messages/sync`、daemon delivery payload、canonical Agent Runtime history / search / read-check 等读路径

### 2.5 未读与通知

未读（per member per thread）：

- [已实现] 订阅关系（消息订阅/成员关系，与计费订阅无关）：
  - Channel：订阅 = `channel_memberships`
  - DM：订阅 = 两名参与者
  - agent 参与的 DM：非参与者 human 默认不订阅，不产生未读/通知
- [已实现] desktop/internal：对每个订阅 thread 维护 `last_read_seq`，未读数 = `latest_seq - last_read_seq`
- [已实现] web/app：维护 `last_read_global_seq`，未读数按当前 `scope_messages` 中可见的 `workspace_global_seq` 增量计算
- [已实现] 当前 Web 读状态动作统一走 Workspace IM conversation action：`POST /api/v1/workspaces/:workspaceId/conversations/:conversationId/actions`，`action` 为 `mark_read` / `mark_unread`。旧 channel 兼容接口 `/channels/unread`、`/channels/:channelId/read`、`/channels/:channelId/read-all`、`/channels/:channelId/unread` 已下线。

通知（必须满足 daemon/runtime 语义）：

- Agent 工作进行中不主动轮询消息；当前 runtime 进程存活期间，daemon 在安全 input boundary 自动递交内存中的精确消息正文。当没有剩余工作时 agent 直接停止，后续 routed message 仍按既有 `agent:start` / `agent:deliver` 路径启动或投递。服务端不再提供 agent `/receive`，daemon continuation inbox 也不暴露 agent-facing pull tool
- 新消息命中某 agent 的当前 routed delivery 时，服务端向其父 Machine 的 daemon 发送 `agent:deliver`，或在需要 fresh runtime 时发送 `agent:start`
- `agent:deliver` 是当前事件 handoff；可靠性事实是 server `scope_fanout_outbox` payload/status + daemon ACK，不是可在恢复在线后重放所有旧消息的 offline inbox

### 2.6 Machine / Daemon（机器注册与在线状态）

机器注册流程（产品视角）：

1. owner 在 Workspace 控制台点击“Add machine”
2. 服务端生成：
   - `machineId`
   - `machineApiKey`
3. [已实现] 服务端持久保存 `machineApiKey`；当前 API 只在「创建/rotate」时返回完整 key，列表仅返回 `apiKeyPrefix`，不提供 reveal 旧 key（丢失后需 rotate）
4. 用户在目标机器运行：
   - [已实现] Web UI 默认生成高级 CLI 一行命令：`agentteam daemon --server-url <url> --api-key <machineApiKey>`
   - 仓库内置 daemon 若从源码目录直接运行，也可使用：`cd desktop && ./bin/agentteam daemon --server-url <url> --api-key <machineApiKey>`
5. daemon 通过 WS 连接服务端并发送 `ready`；server 以当前 authenticated WS 连接判断机器 online，并通过 realtime 通知 UI

围绕 `machineApiKey` 的产品约束：

- [已实现] 同一台机器重启后，不需要重新创建 machine；使用同一个 key 即可重新连回（前提是用户自行保存 key；当前不提供 reveal）
- [已实现] 提供 **Rotate key**，以便密钥泄露后失效旧 key
- [已实现] **Rotate key 会强制断开当前 WS 连接**，daemon 需要用新 key 重连；否则“旧 key 已建连的 daemon”会继续占用在线会话
- [已实现] 删除 machine 前，必须先迁移或删除其下所有 agent

机器在线/离线：

- online：WS 连接存活
- offline：WS 连接断开

### 2.7 Role / Agent / Instance

规范性细节以 [`specs/agent/role-identity.md`](../agent/role-identity.md) 为准。

- Role 是用户可见、可版本化和可分享的角色包，不是消息 actor。
- Role package 必需包含 `IDENTITY.md`，并可声明 Skills、Knowledge、KB 与 Memory grants。
- `POST /api/v1/workspaces/:workspaceId/agents:from-role` 从 Role 创建并绑定 Agent；`POST /api/v1/workspaces/:workspaceId/agents` 创建 blank binding 的 Agent。
- 创建参数包括 `name`、`description`、`runtime`、`permissionMode`、`model` 与 `reasoningEffort`；权限和 runtime 设置属于 Agent，不进入 Role package。
- server 创建 agent member 和 `agent_role_bindings`，并从 `role_package_manifest_items` 生成 workspace materialization。
- Instance 的 online/running/session 状态只来自当前 daemon 连接或 TTL ephemeral state，不从 TiDB runtime mirror 重建。

### 2.8 Task Board

Task Board 的详细 canonical contract 由 [`specs/task/board.md`](../task/board.md) 定义；agent output review、review context 与 output target contract 由 [`specs/agent/output-review.md`](../agent/output-review.md) 定义；workspace task hub 与 channel `List` / `Board` 的 browse/query/pagination contract 由 [`specs/task/board-pages.md`](../task/board-pages.md) 定义。本文只保留顶层产品约束：

- 每个 channel 都有一个 task board；DM 没有 task board
- workspace 提供一个一级 task hub 入口，用于跨 channel 浏览、筛选、分组和管理当前用户可见的 tasks
- task 在所属 channel 内按创建顺序编号：`#t1`, `#t2`, ...`
- task 继续保持 channel-bound，而不是 workspace-global object
- task 可以从 workspace task hub 发起创建，但必须在创建完成前绑定到一个 channel；如需要，可在同一 flow 中创建新 channel 并绑定首个 task
- progress state 继续以 `todo -> in_progress -> in_review -> done` 为 canonical 主线
- task board workbench 以 `Board / List` 为 primary 视图集合；workspace task hub 在同一份跨 channel query state 上提供 system/saved view presets，List 按 channel 分组，Board 按 lifecycle status 分列；channel 内按 user + channel 持久化当前视图与筛选偏好
- channel board / list 默认 root-task-first；subtasks 主要在 parent task detail 内继续管理，而不是与 root tasks 同权平铺
- subtasks 默认保持 execution-first；默认的人类 review 入口应聚焦 parent/root deliverable，而不是在每个 child task 上重复强调 request review
- Task 只保存一个可空的 Human / Agent `assignee`；claim 是防 takeover 的 self-assignment；Review Request reviewer 是独立 review fact
- task detail 是默认执行中心；task discussion 是 task 创建后的默认后续协作面，并保持 flat；如果协作需要 branching 或更广可见性，复用 channel/thread
- task detail 首屏优先展示 `Goal / Progress / Current focus / Blocker / Latest deliverable / Next step / Review status`，metadata 退到右侧 rail
- task 创建流只有一个 `Create` operation，可选 assignee；status 不由 create mode 隐式改变
- workspace 级 `kind` / `priority` taxonomy 由 `/tasks/settings` 管理；默认值是 `None`，inactive 历史值继续可见但不可用于新的编辑
- task 创建、notify、thread/source-thread 语义由 [`specs/collaboration/collaboration.md`](../collaboration/collaboration.md) 定义
- task board 变化不自动改变 channel membership；Task attention 仅由 explicit Watch、current assignee 与 current Message direct mentions（排除 actor）决定，不同步广播到 source chat
- 当前 agent task tool 提供 `task_list`、`task_get`、`task_create`、`task_update`、claim / unclaim 与 `task_checkpoint`；agent 通过 `task_get` 读取 status、assignee、blocker 和协作上下文，并通过对应 task action 更新任务。
- [已实现] task status lifecycle 固定为 `todo/in_progress/in_review/done`

### 2.9 Usage（诊断）

AgentTeam 的取舍（自部署优先）需要明确：

- **核心自部署计划不做订阅升级体系**：默认不提供 Stripe/checkout/portal、订阅状态、plan 购买、seat 购买或升级入口；billing-enabled 商业部署的托管模型 credits 充值和组织 human-seat 购买是独立 billing 能力，见 [`specs/billing/hosted-credits-payment.md`](../billing/hosted-credits-payment.md) 与 [`specs/billing/organization-seat-purchases.md`](../billing/organization-seat-purchases.md)
- Billing v1 后端 `/api/billing/*` 只允许 organization owner/admin 访问，不允许普通 workspace admin/member 查看余额、流水、支付记录或 seat 购买入口
- 所有用户默认享有完整功能：`GET /api/v1/workspaces` 的 `plan` 固定为 `max`（仅作为 wire 字段，不做功能限制）
- [已实现] `GET /api/v1/workspaces/:workspaceId/usage`：返回用量统计（machines/agents/channels 的 count），用于 UI 诊断与运维容量评估
- 不提供 Socket.IO `server:plan-updated`（因为 plan 固定且无订阅变更语义）

### 2.10 Project Workspace（Explorer 项目工作区）

Explorer 中的 **Project Workspace** 是围绕一台机器上某个绝对路径组织起来的文件浏览、评审与访问单元。它不改变后端 `Project` 持久化契约——`Project` 仍是后端 identity。Explorer 二级导航在 Skills 下方提供独立的 Files section，固定依次包含 Project Workspace 和 Agent Directory；Change Review 属于具体 Project Workspace，不再提供独立二级入口。这些能力不再作为 Machine 详情标签页出现。

创建流程（minimal Add Project Workspace flow）：

- **入口**：Explorer 侧边栏 → Files → Project Workspace → `+`
- **字段**：仅 Machine / Name / Root path；不再在创建时收集 Description 和 Visibility
- **默认值**：`visibility` 固定为 `private`，`description` 固定为空字符串；二者可在 Project Settings 中后续编辑
- **Root path**：字节精确，不做 trim；例如 `/repo ` 会原样保存。这保证用户粘贴的路径与目标机器上的实际路径完全一致
- **浏览**：若选中机器在线且其 daemon 通过 Remote Endpoint 广播了 `machine.directory.list` 能力，Root path 旁出现 Browse 按钮，打开 Machine Directory Picker 浏览远程目录；否则 Browse 禁用，用户手动输入绝对路径
- **离线 / 旧 daemon 回退**：离线机器或未广播目录浏览能力的 daemon 不影响 Root path 输入——Browse 禁用但 Root path 保持可编辑，用户可直接手动输入

Machine Directory Picker 契约：

- 通过 Remote Endpoint `machine.directory.list@1`（见 [daemon transport](../daemon/daemon-transport.md)）按页浏览远端目录
- 所有错误以 path-free 稳定码呈现（`machine_directory_invalid` / `machine_directory_unreadable` / `machine_directory_unsupported` / `machine_directory_unavailable` / `machine_directory_busy`），绝不把路径写入 URL、metrics 或 wire outcome
- 偏移分页最多 1,000 raw entries，单页响应 ≤ 512 KiB；超出时返回 `truncated=true` 引导用户输入更具体的绝对路径

Agent Directory 和 Project Workspace 使用独立列表页，按 Machine 分组并支持折叠、搜索和 Machine 在线状态过滤；首次进入即可直接选择 Agent 或 Project。Project Workspace 按 `updatedAt DESC, id ASC` 稳定排序，默认读取 20 个 Project，滚动接近列表末尾时通过 cursor 加载下一页；搜索名称或根路径以及切换 Machine 状态过滤时均从服务端第一页重新查询，已加载结果仍按 Machine 分组。选中对象写入 Files 路由，以便刷新和分享；旧 Machine 文件路由不再保留兼容入口。

Project Workspace 选中 Project 后，Detail Header 下直接使用共享 Workspace Tabs 展示 Files、Review 和 Settings，对应 `/projects/:projectId/files`、`/projects/:projectId/reviews` 和 `/projects/:projectId/settings`；每个 Tab 都显示对应图标。Detail 不提供内嵌 Project 切换器，切换 Project 由独立列表页负责。Files 与 Review 共用同一种可拖动左侧导航结构和持久化宽度；左栏 Header 固定，只有下方树内容滚动，滚动条在滚动时显示，分栏线与 Explorer 二级 Sidebar 的右边线保持相同粗细和常态颜色。Files 左栏 Header 展示根路径，点击路径可复制。目录层级和文件内容按 Project 与路径缓存；切换到其他 Tab 再返回 Files 时立即恢复已展开目录、选中文件和预览模式，过期数据在后台刷新，不用空白加载态替换已有内容。Review 直接复用完整 Diff / Review surface，并只加载当前 Project 的 Review Context 集合；surface 内切换 Review 时同步当前 Project 的 Reviews 路由。Diff stream 按文件使用可变高度虚拟列表，只挂载可见区域及有限预渲染范围，并以虚拟列表索引完成左侧文件导航，避免变更文件数量增长时累积无界 DOM。Settings 只对 Project owner 或 manage 角色开放；只有 owner 可以在独立的危险操作区移除 Project，且必须输入界面显示的 Project identity 确认（优先使用名称，无名 Project 使用根路径）。移除只删除 Explorer 中的 Project 记录及其授权，不改动机器文件。Project grant 是独立于 Machine 管理权的访问事实：用户能访问某个 Project、但不能管理其 Machine 时，Project Workspace 仍按真实 Machine ID 分组展示授权对象，不把该 Machine 伪装成可管理入口，也不发起 Machine agent-management 请求。Project 和 Review 必须显式区分加载、失败与空集合；失败状态允许重试。

加载态契约：Files 与 Review 的首次目录加载共用贴合真实目录项行高和图标位的单层五行脉冲骨架；Review 在加载 Context 和变更文件时保持完整的左栏 Header、目录树、分栏和右侧加载状态，避免数据返回前后发生布局跳变。

Workspace 一级导航依次分为协作、资源和工作三组。资源组依次包含 Members、Resources、Skills、Knowledge Base、Apps，Apps 固定为该组最后一项；工作组依次包含 Tasks、Workflow。Tasks 直接打开 workspace Task hub，不使用常驻二级导航。Apps 不再出现在头像菜单。Resources 二级导航聚合 Machines 与 Files；Roles 位于 Members 二级面板并提供“全部 / 我创建的 / 工作区共享 / 官方模板”集合入口，Skills 作为独立一级入口并提供“全部 / 工作区共享 / 官方模板”集合入口，同时在该面板顶部展示“我的技能”列表与添加按钮。Machine 详情只保留 Overview、Agents、Runtimes、Plugins 和 Settings。

Members 一级入口恢复最近一次有效的 Agent、human 或 DM 选择；首次进入时优先打开第一个 Agent 的 Activity，没有 Agent 时打开第一个 human。Members 二级面板先展示 Agent Teams，再展示 Agents 与 human members。Members 不以另一个完整 Agent 目录作为默认主面板，因为 Agent 目录已由二级导航和 Explorer Files 视图提供。

## 3. 当前取舍与历史兼容（简要）

本轮产品设计的核心调整如下：

- 用 `member` 替代 `actor`，把“消息参与者”概念说清楚
- 用 human `member.id` + agent `agents.name` 替代用户可编辑的 `actor.name`，把公开标识和展示名彻底分开
- 保留现有 target 语法（如 `dm:@peer`），但把其语义改成引用公开成员标识；旧 daemon 的历史作者 wire 字段只在 daemon adapter 边界派生
- 在产品模型上区分 `Role / Agent / Instance`：shareable role、workspace actor、machine-bound runtime 分离；Role / Agent 的配置、成员关系、授权和消息事实是产品层持久化事实，Instance 是 daemon-local / realtime runtime，不是 TiDB 持久化层
- 把 `machineApiKey` 改成长效、可轮换的 secret（当前仅在创建/rotate 时返回完整 key），更符合真实运维体验
- Task Board 的 canonical spec 定义 `todo/in_progress/in_review/done` lifecycle 与六个 agent task tools
- [已实现] 回复线程（Threads）：`/api/channels/:channelId/threads` + `thread:updated` + `channel_type="thread"` 的消息语义
- 简化：核心自部署产品不提供订阅升级；`plan` 固定为 `max`；保留 `/api/v1/workspaces/:workspaceId/usage` 用于诊断；billing-enabled 商业部署的托管模型 credits 充值和组织 human-seat 购买由 billing specs 单独定义

协议收敛上需要记住两点：

- 当前 internal agent contract 仍要求传递稳定字符串标识；routing target 统一为公开 handle：human 用 `member.id`、agent 用 `agents.name`、service 用安装态 handle
- 现有 agent 文本界面里，人类仍主要以 `member.id` 出现；agent/service 也可能以公开 handle 出现；但 Web UI 中所有 human-visible surface 都应显示 `displayName`，并通过显式 metadata 判断是否为 App

仍然需要保留的事实约束：

- Agent 无法自主 join/leave channel；成员关系必须由 human/UI 管理
- Agent 无法自主创建/管理 machine 或其它 agent；这些动作必须走人类管理面
- 工作区浏览能力是一等能力，产品上应明确提供
- Task Board 提供 `task_list`、`task_get`、`task_create`、`task_update`、`task_set_claim`、`task_checkpoint`；`todo/in_progress/in_review/done` 是 status-driven board lifecycle 的固定列
- `agent:status` / `activity` / `session` / `trajectory` 只用于实时展示和当前连接上的 best-effort routing hint；自动唤醒策略不直接依赖这些 DB 字段，且这些临时状态不得写入 TiDB
