# Team 详细设计

> Canonical path: `specs/collaboration/team-design.md`
> Status: living-design
> Scope: Team 的存储、API、mention 解析、消息投递、agent Team Context 注入、Web UI 与测试设计。
> Product spec: [`specs/collaboration/team.md`](./team.md)

## 1. 设计目标

Team 功能在现有 AgentTeam 协作模型中增加一个可被提及的集体 actor。实现以当前 message / scope / delivery / task / workflow 能力为基础，新增 Team 数据、Team mention 解析、成员展开与 agent Team Context 注入。

完整实现后的用户体验：

- 用户在 channel、reply thread、DM、task scope 或 workflow surface 中输入 `@team-handle`。
- Web mention picker 展示 Team 候选项。
- 服务端保存原始消息与 Team resolved mention。
- 标准消息投递链路将 Team 展开为当前协作面可见的 agent assignments。
- Agent 成员按标准 agent message delivery 能力接收，并在 delivery envelope 中获得 Team Context。
- Agent runtime 在 Team-member Mode 中收到 Team Description 与自身 Role Overlay。
- Team 可被 task、review、workflow、permission 与 directory 等相关系统以 actor reference 引用；具体合同由对应 spec 定义。

## 2. 模块归属

| 模块            | 归属路径                                | 职责                                                      |
| --------------- | --------------------------------------- | --------------------------------------------------------- |
| Product spec    | `specs/collaboration/team.md`           | Team 产品语义。                                           |
| 详细设计        | `specs/collaboration/team-design.md`    | Team 实现设计。                                           |
| DTO             | `internal/api/dto/team.go`              | Team API 请求与响应结构。                                 |
| API handler     | `internal/api/team/`                    | Team CRUD、成员管理、模板管理、mention candidate 查询。   |
| Store           | `internal/store/team/`                  | Team / Template 表读写、成员展开、权限查询。              |
| Store model     | `internal/store/model/team.go`          | Team 数据模型结构。                                       |
| Message routing | `internal/store/message/`               | Team mention 解析、Team 成员展开、delivery payload 增强。 |
| Routing syntax  | `internal/core/routing/`                | 复用 canonical mention parser。                           |
| Fanout          | `internal/api/im/fanout/`               | 将 Team Context 带入 agent wake payload。                 |
| DTO converter   | `internal/api/dto/converter/message.go` | ReceiveMessage / AppMessage / ResolvedMention 转换。      |
| Web API client  | `web/src/agentteam/api*`                     | Team API client 与 cache key。                            |
| Web types       | `web/src/types/`                        | Team、TeamMember、Team mention 类型。                     |
| Web UI          | `web/src/agentteam/`                         | Team 管理、mention picker、Team token、Team profile。     |
| TiDB schema     | `internal/tidb/migrations/`             | Team 与 Team Template 表、索引与约束。                    |

## 3. 数据模型

### 3.1 表：`workspace_teams`

`workspace_teams` 存储 workspace 内 Team 的稳定身份与 Team Description。

```sql
CREATE TABLE `workspace_teams` (
  `id` varchar(255) NOT NULL,
  `workspace_id` varchar(255) NOT NULL,
  `handle` varchar(128) NOT NULL,
  `display_name` varchar(255) NOT NULL,
  `description` text NOT NULL,
  `avatar_url` varchar(2048) NOT NULL DEFAULT '',
  `visibility` varchar(32) NOT NULL DEFAULT 'workspace',
  `status` varchar(32) NOT NULL DEFAULT 'active',
  `created_by_member_id` varchar(255) NOT NULL,
  `created_at` timestamp NOT NULL,
  `updated_at` timestamp NOT NULL,
  `archived_at` timestamp NULL DEFAULT NULL,
  `active_handle_key` varchar(512) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `workspace_teams_active_handle_key` (`active_handle_key`),
  KEY `workspace_teams_workspace_status_idx` (`workspace_id`, `status`, `display_name`),
  KEY `workspace_teams_created_by_idx` (`created_by_member_id`),
  CONSTRAINT `fk_workspace_teams_workspace` FOREIGN KEY (`workspace_id`) REFERENCES `workspaces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_workspace_teams_created_by` FOREIGN KEY (`created_by_member_id`) REFERENCES `members` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
```

`active_handle_key` 由 `workspace_id + ':' + lower(handle)` 生成。active Team 的 handle 唯一性通过该列控制；Team 删除后 handle 可重新使用。当前产品模型中 Team 只有一个用户可见名称，`display_name` 作为存储兼容字段必须镜像 `handle`，不得成为第二个可编辑名称。`status` / `archived_at` 保留为存储兼容字段，不作为新的 Team 生命周期主路径。

### 3.2 表：`workspace_team_members`

`workspace_team_members` 存储 Team 的 agent assignment、Role Overlay 与默认响应配置。

```sql
CREATE TABLE `workspace_team_members` (
  `id` varchar(255) NOT NULL,
  `workspace_id` varchar(255) NOT NULL,
  `team_id` varchar(255) NOT NULL,
  `actor_kind` varchar(32) NOT NULL,
  `actor_id` varchar(255) NOT NULL,
  `role_label` varchar(128) NOT NULL DEFAULT '',
  `role_overlay_prompt` longtext NOT NULL,
  `role_overlay_version` bigint NOT NULL DEFAULT 1,
  `wake_on_team_mention` tinyint(1) NOT NULL DEFAULT '1',
  `sort_order` bigint NOT NULL DEFAULT 0,
  `added_by_member_id` varchar(255) NOT NULL,
  `added_at` timestamp NOT NULL,
  `updated_at` timestamp NOT NULL,
  `removed_at` timestamp NULL DEFAULT NULL,
  `active_member_key` varchar(768) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `workspace_team_members_active_key` (`active_member_key`),
  KEY `workspace_team_members_team_idx` (`workspace_id`, `team_id`, `removed_at`, `sort_order`),
  KEY `workspace_team_members_actor_idx` (`workspace_id`, `actor_kind`, `actor_id`, `removed_at`),
  KEY `workspace_team_members_added_by_idx` (`added_by_member_id`),
  CONSTRAINT `fk_workspace_team_members_workspace` FOREIGN KEY (`workspace_id`) REFERENCES `workspaces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_workspace_team_members_team` FOREIGN KEY (`team_id`) REFERENCES `workspace_teams` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_workspace_team_members_added_by` FOREIGN KEY (`added_by_member_id`) REFERENCES `members` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
```

`actor_kind` 当前固定为 `agent`。`actor_id` 引用当前 workspace 中 active agent member 的 `members.id`，并且该 agent 必须属于发起创建/编辑的用户。

`active_member_key` 由 `team_id + ':' + actor_kind + ':' + actor_id` 生成。

Team 不存储成员管理角色。管理权限由 Team creator、workspace owner 与 workspace admin 判定。`role_overlay_prompt` 与 `wake_on_team_mention` 只面向 agent assignment。


### 3.3 表：`workspace_team_templates`

`workspace_team_templates` 存储 workspace 自定义 Team 模板。内置模板来自代码配置，并在响应时投影为当前 workspace 的模板视图，不写入该表。

```sql
CREATE TABLE `workspace_team_templates` (
  `id` varchar(255) NOT NULL,
  `workspace_id` varchar(255) NOT NULL,
  `name` varchar(128) NOT NULL,
  `description` text NOT NULL,
  `visibility` varchar(32) NOT NULL DEFAULT 'workspace',
  `source` varchar(32) NOT NULL DEFAULT 'custom',
  `template_scope` varchar(32) NOT NULL DEFAULT 'workspace',
  `created_by_member_id` varchar(255) NOT NULL,
  `created_from_team_id` varchar(255) DEFAULT NULL,
  `created_at` timestamp NOT NULL,
  `updated_at` timestamp NOT NULL,
  PRIMARY KEY (`id`),
  KEY `workspace_team_templates_workspace_source_idx` (`workspace_id`, `source`, `name`),
  KEY `workspace_team_templates_created_by_idx` (`created_by_member_id`),
  KEY `workspace_team_templates_created_from_team_idx` (`created_from_team_id`),
  CONSTRAINT `fk_workspace_team_templates_workspace` FOREIGN KEY (`workspace_id`) REFERENCES `workspaces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_workspace_team_templates_created_by` FOREIGN KEY (`created_by_member_id`) REFERENCES `members` (`id`),
  CONSTRAINT `fk_workspace_team_templates_created_from_team` FOREIGN KEY (`created_from_team_id`) REFERENCES `workspace_teams` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
```

### 3.4 表：`workspace_team_template_members`

`workspace_team_template_members` 存储模板 agent 槽位。槽位可以绑定默认 agent actor，也可以只表达 agent 角色配置，等用户从模板创建 Team 时再选择具体 agent。

```sql
CREATE TABLE `workspace_team_template_members` (
  `id` varchar(255) NOT NULL,
  `workspace_id` varchar(255) NOT NULL,
  `template_id` varchar(255) NOT NULL,
  `actor_kind` varchar(32) NOT NULL,
  `actor_id` varchar(255) NOT NULL DEFAULT '',
  `role_label` varchar(128) NOT NULL DEFAULT '',
  `role_overlay_prompt` longtext NOT NULL,
  `wake_on_team_mention` tinyint(1) NOT NULL DEFAULT '1',
  `sort_order` bigint NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `workspace_team_template_members_template_idx` (`workspace_id`, `template_id`, `sort_order`),
  CONSTRAINT `fk_workspace_team_template_members_workspace` FOREIGN KEY (`workspace_id`) REFERENCES `workspaces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_workspace_team_template_members_template` FOREIGN KEY (`template_id`) REFERENCES `workspace_team_templates` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
```

### 3.5 Store model

`internal/store/model/team.go`：

```go
type WorkspaceTeam struct {
    ID                  string
    WorkspaceID         string
    Handle              string
    DisplayName         string
    Description         string
    AvatarURL           string
    Visibility          string
    Status              string
    CreatedByMemberID   string
    CreatedAt           time.Time
    UpdatedAt           time.Time
    ArchivedAt          *time.Time
}

type WorkspaceTeamMember struct {
    ID                 string
    WorkspaceID        string
    TeamID             string
    ActorKind          string
    ActorID            string
    RoleLabel          string
    RoleOverlayPrompt  string
    RoleOverlayVersion int64
    WakeOnTeamMention  bool
    SortOrder          int64
    AddedByMemberID    string
    AddedAt            time.Time
    UpdatedAt          time.Time
    RemovedAt          *time.Time
}

type WorkspaceTeamTemplate struct {
    ID                  string
    WorkspaceID         string
    Name                string
    Description         string
    Visibility          string
    Source              string
    Scope               string
    CreatedByMemberID   string
    CreatedFromTeamID   string
    CreatedAt           time.Time
    UpdatedAt           time.Time
}

type WorkspaceTeamTemplateMember struct {
    ID                string
    WorkspaceID       string
    TemplateID        string
    ActorKind         string
    ActorID           string
    RoleLabel         string
    RoleOverlayPrompt string
    WakeOnTeamMention bool
    SortOrder         int64
}
```

Store model struct 保持数据库形状；HTTP JSON wire contract 由 DTO 承担。

## 4. Store 设计

### 4.1 Package

新增 `internal/store/team`。

核心类型：

```go
type Store struct { db coredb.DB }
type TxStore struct { tx coredb.Tx }
```

### 4.2 Agent Runtime directory

The Agent Runtime Team directory is a bounded read with the public shape
`{teams, hasMore, nextCursor}`. It defaults to 20 rows and caps a page at 100.
Rows use the deterministic `displayName ASC, handle ASC, teamId ASC` keyset;
the existing active-membership authorization and compact Team projection stay
unchanged. The optional query narrows the existing handle/display-name/
description search and is bound into the opaque cursor, so changing it cannot
silently reuse a cursor from another result set. Invalid cursors return a
validation error rather than restarting at the first page.

### 4.3 Team CRUD

Store 暴露：

```go
func (s *Store) ListTeamsForUser(ctx context.Context, userID, workspaceID string, filter ListTeamsFilter) ([]TeamView, error)
func (s *Store) GetTeamForUser(ctx context.Context, userID, workspaceID, teamID string) (TeamView, error)
func (s *Store) CreateTeamForUser(ctx context.Context, userID, workspaceID string, params CreateTeamParams) (TeamView, error)
func (s *Store) UpdateTeamForUser(ctx context.Context, userID, workspaceID, teamID string, params UpdateTeamParams) (TeamView, error)
func (s *Store) DeleteTeamForUser(ctx context.Context, userID, workspaceID, teamID string) error
```

### 4.3 Team member CRUD

```go
func (s *Store) ListTeamMembersForUser(ctx context.Context, userID, workspaceID, teamID string) ([]TeamMemberView, error)
func (s *Store) AddTeamMemberForUser(ctx context.Context, userID, workspaceID, teamID string, params AddTeamMemberParams) (TeamMemberView, error)
func (s *Store) UpdateTeamMemberForUser(ctx context.Context, userID, workspaceID, teamID, teamMemberID string, params UpdateTeamMemberParams) (TeamMemberView, error)
func (s *Store) RemoveTeamMemberForUser(ctx context.Context, userID, workspaceID, teamID, teamMemberID string) error
```

### 4.4 Team template CRUD

```go
func (s *Store) ListTeamTemplatesForUser(ctx context.Context, userID, workspaceID string, filter ListTeamTemplateFilter) ([]TeamTemplateView, error)
func (s *Store) CreateTeamTemplateForUser(ctx context.Context, userID, workspaceID string, params CreateTeamTemplateParams) (TeamTemplateView, error)
func (s *Store) SaveTeamTemplateFromTeamForUser(ctx context.Context, userID, workspaceID, teamID string, params CreateTeamTemplateParams) (TeamTemplateView, error)
func (s *Store) UpdateTeamTemplateForUser(ctx context.Context, userID, workspaceID, templateID string, params UpdateTeamTemplateParams) (TeamTemplateView, error)
func (s *Store) DeleteTeamTemplateForUser(ctx context.Context, userID, workspaceID, templateID string) error
func (s *Store) InstantiateTeamTemplateForUser(ctx context.Context, userID, workspaceID, templateID string, params InstantiateTeamTemplateParams) (TeamView, error)
```

Template store 合并内置模板与自定义模板。内置模板不落库；自定义模板写入 `workspace_team_templates` / `workspace_team_template_members`。自定义模板有 `private` 与 `workspace` 两种 `template_scope`：新保存/新建默认 private；历史模板 scope migration 默认 workspace；列表只返回 workspace 模板和当前用户自己的 private 模板。从模板创建 Team 时复用 Team 创建事务，生成普通 Team，并按 agent 槽位绑定创建 Team member。删除模板直接 hard delete 模板蓝图，不影响已经创建的 Team。

### 4.5 Mention resolution store API

消息写入路径使用专门的查询接口：

```go
type TeamMentionMatch struct {
    TeamID      string
    Handle      string
    DisplayName string
    Description string
}

type TeamExpansionEdge struct {
    TeamID               string
    TeamHandle           string
    TeamDisplayName      string
    TeamDescription      string
    ActorKind            string
    ActorID              string
    ActorDisplayName     string
    ActorHandle          string
    TeamMemberID         string
    AgentID              string
    MachineID            string
    AutoWakeEnabled      bool
    RoleLabel            string
    RoleOverlaySnapshot  string
    RoleOverlayVersion   int64
    WakeOnTeamMention    bool
    ExpansionPath        []string
}

func (s *Store) ResolveTeamMentionTokens(ctx context.Context, workspaceID string, tokens []string, hints []TeamMentionHint) ([]TeamMentionMatch, error)
func (s *Store) ExpandTeamMentionsForScope(ctx context.Context, params ExpandTeamMentionsParams) ([]TeamExpansionEdge, error)
```

`ExpandTeamMentionsForScope` 接收当前 scope、发送者、message author、Team IDs，并返回当前协作面可见的 per-team expansion edge。每条 edge 保留来源 Team、TeamMember、Role Overlay、默认响应配置与当前 expansion path。当前产品模型不展开 nested Team。

### 4.6 成员展开规则

展开算法：

1. 根据 mention token 与 hint 解析 Team。
2. 读取 active Team 成员。
3. 丢弃非 agent assignment；正常写路径通过 validation 已阻止这类数据，迁移会清理历史非 agent 行。
4. 对 edge 中的 agent 执行当前协作面可见性过滤。
5. 为 agent edge 补齐 `AgentID`、`MachineID`、`AutoWakeEnabled`。
6. 按 Team 生成本次协作成员列表。
7. 按 Team 与 TeamMember 生成 Team Context snapshot。
8. 在最终 recipient 层按 `actor_kind + actor_id` 去重，并合并多个 Team Context。

展开参数：

| 参数                       | 值  |
| -------------------------- | --- |
| 单条消息最大 Team token 数 | 32  |
| 单条消息最大展开成员数     | 256 |

每个 root/source Team 生成独立 edge，edge 在最终 recipient 层合并。

## 5. API 设计

### 5.1 路由

新增 `internal/api/team`，挂载到 workspace scope：

```text
GET    /api/v1/workspaces/:workspaceId/teams
POST   /api/v1/workspaces/:workspaceId/teams
GET    /api/v1/workspaces/:workspaceId/teams/:teamId
PATCH  /api/v1/workspaces/:workspaceId/teams/:teamId
DELETE /api/v1/workspaces/:workspaceId/teams/:teamId
GET    /api/v1/workspaces/:workspaceId/teams/:teamId/members
POST   /api/v1/workspaces/:workspaceId/teams/:teamId/members
PATCH  /api/v1/workspaces/:workspaceId/teams/:teamId/members/:teamMemberId
DELETE /api/v1/workspaces/:workspaceId/teams/:teamId/members/:teamMemberId
GET    /api/v1/workspaces/:workspaceId/team-templates
POST   /api/v1/workspaces/:workspaceId/team-templates
PATCH  /api/v1/workspaces/:workspaceId/team-templates/:templateId
DELETE /api/v1/workspaces/:workspaceId/team-templates/:templateId
POST   /api/v1/workspaces/:workspaceId/team-templates/:templateId/instantiate
POST   /api/v1/workspaces/:workspaceId/teams/:teamId/template
GET    /api/v1/workspaces/:workspaceId/mention-candidates/teams
```

### 5.2 DTO

`internal/api/dto/team.go`：

```go
type TeamResponse struct {
    ID          string `json:"id"`
    Handle      string `json:"handle"`
    DisplayName string `json:"displayName"`
    Description string `json:"description"`
    AvatarURL   string `json:"avatarUrl,omitempty"`
    Visibility  string `json:"visibility"`
    Status      string `json:"status"`
    CreatedAt   string `json:"createdAt"`
    UpdatedAt   string `json:"updatedAt"`
}

type TeamMemberResponse struct {
    ID                string         `json:"id"`
    TeamID            string         `json:"teamId"`
    ActorKind         string         `json:"actorKind"`
    ActorID           string         `json:"actorId"`
    ActorProfile      *AuthorProfile `json:"actorProfile,omitempty"`
    RoleLabel         string         `json:"roleLabel,omitempty"`
    RoleOverlayPrompt string         `json:"roleOverlayPrompt,omitempty"`
    WakeOnTeamMention bool           `json:"wakeOnTeamMention"`
    SortOrder         int64          `json:"sortOrder"`
    AddedAt           string         `json:"addedAt"`
}

type TeamMentionCandidateResponse struct {
    ID                  string `json:"id"`
    Handle              string `json:"handle"`
    DisplayName         string `json:"displayName"`
    Description         string `json:"description,omitempty"`
    AvatarURL           string `json:"avatarUrl,omitempty"`
    MemberCount         int    `json:"memberCount"`
    DeliveryPreview     bool   `json:"deliveryPreview"`
    ReachableAgentCount int    `json:"reachableAgentCount"`
}

type TeamTemplateResponse struct {
    ID          string                       `json:"id"`
    Name        string                       `json:"name"`
    Description string                       `json:"description"`
    Visibility  string                       `json:"visibility"`
    Source      string                       `json:"source"`
    Scope       string                       `json:"scope"`
    CanManage   bool                         `json:"canManage"`
    Members     []TeamTemplateMemberResponse `json:"members"`
}

type TeamTemplateMemberResponse struct {
    ID                string `json:"id"`
    ActorKind         string `json:"actorKind"`
    ActorID           string `json:"actorId,omitempty"`
    RoleLabel         string `json:"roleLabel,omitempty"`
    RoleOverlayPrompt string `json:"roleOverlayPrompt,omitempty"`
    WakeOnTeamMention bool   `json:"wakeOnTeamMention"`
}
```

请求 DTO：

```go
type CreateTeamRequest struct {
    Handle      string `json:"handle"`
    DisplayName string `json:"displayName"`
    Description string `json:"description,omitempty"`
    Visibility  string `json:"visibility"`
}

type UpdateTeamRequest struct {
    Handle      *string `json:"handle,omitempty"`
    DisplayName *string `json:"displayName,omitempty"`
    Description *string `json:"description,omitempty"`
    Visibility  *string `json:"visibility,omitempty"`
}

type AddTeamMemberRequest struct {
    ActorKind         string `json:"actorKind"`
    ActorID           string `json:"actorId"`
    Role              string `json:"role,omitempty"`
    RoleLabel         string `json:"roleLabel,omitempty"`
    RoleOverlayPrompt string `json:"roleOverlayPrompt,omitempty"`
    WakeOnTeamMention *bool  `json:"wakeOnTeamMention,omitempty"`
    SortOrder         int64  `json:"sortOrder,omitempty"`
}

type UpdateTeamMemberRequest struct {
    Role              *string `json:"role,omitempty"`
    RoleLabel         *string `json:"roleLabel,omitempty"`
    RoleOverlayPrompt *string `json:"roleOverlayPrompt,omitempty"`
    WakeOnTeamMention *bool   `json:"wakeOnTeamMention,omitempty"`
    SortOrder         *int64  `json:"sortOrder,omitempty"`
}

type CreateTeamTemplateMemberRequest struct {
    ActorKind         string `json:"actorKind"`
    ActorID           string `json:"actorId,omitempty"`
    Role              string `json:"role,omitempty"`
    RoleLabel         string `json:"roleLabel,omitempty"`
    RoleOverlayPrompt string `json:"roleOverlayPrompt,omitempty"`
    WakeOnTeamMention *bool  `json:"wakeOnTeamMention,omitempty"`
    SortOrder         int64  `json:"sortOrder,omitempty"`
}

type TeamTemplateMemberBindingRequest struct {
    TemplateMemberID  string  `json:"templateMemberId"`
    ActorID           string  `json:"actorId"`
    Role              *string `json:"role,omitempty"`
    RoleLabel         *string `json:"roleLabel,omitempty"`
    RoleOverlayPrompt *string `json:"roleOverlayPrompt,omitempty"`
    WakeOnTeamMention *bool   `json:"wakeOnTeamMention,omitempty"`
}
```

### 5.3 Validation

| 输入                | 规则                                                                                                |
| ------------------- | --------------------------------------------------------------------------------------------------- |
| `handle`            | Team 名称；trim 后 1-64 字符；小写 kebab / underscore / digit；workspace 内 active 唯一。           |
| `displayName`       | 存储兼容镜像字段；写入时与 `handle` 保持同值，不作为独立输入。                                      |
| `description`       | trim 后 0-2048 Unicode 字符。                                                                       |
| `actorKind`         | Team member 与 template member 当前只接受 `agent`。                                                |
| `actorId`           | 添加到 Team 时必须引用当前 workspace 中由当前用户拥有的 active agent member；模板槽位可为空。        |
| `role`              | Team 本地成员角色；只接受 `leader` 或 `member`，active Team 必须且只能有一个 `leader`。             |
| `roleOverlayPrompt` | agent assignment 可写；trim 后 0-8192 Unicode 字符。                                                |
| `visibility`        | `workspace`、`private`。                                                                            |

## 6. Mention 解析与投递设计

### 6.1 写消息入口

当前消息写入路径保留：

```text
API handler -> internal/service/message -> store/message -> fanout
```

Team 逻辑接入 store/message 的 routing plan 构建过程。

### 6.2 Mention token parser

Team mention 复用 `internal/core/routing.ParseMentionTokens`。parser 输出 token 字符串，Team resolver 与其他 mention resolver 使用同一 token 集合。

### 6.3 Mention hint

Browser composer 选中 Team mention 时发送 hint：

```json
{
  "type": "team",
  "id": "team_...",
  "token": "agentteam-core"
}
```

`MessageMentionHint.Type` 支持 `team`。store 在写入时校验 hint 与实际可解析 Team 一致。

### 6.4 Resolved mention

`ResolvedMention.Type` 支持 `team`：

```json
{
  "token": "agentteam-core",
  "type": "team",
  "id": "team_...",
  "displayName": "agentteam-core",
  "canonicalToken": "agentteam-core"
}
```

Read-time mention resolution 增加 Team 查询。Web 使用该结果渲染 Team mention token。

### 6.5 Routing plan 增强

新增 delivery reason：

```go
const AgentDeliveryReasonTeamMention = "team_mention"
```

`AgentRecipient` 增加 Team Context 列表：

```go
type AgentRecipient struct {
    AgentID         string
    MachineID       string
    AgentMemberID   string
    AutoWakeEnabled bool
    DeliveryReason  string
    MentionToken    string
    TeamContexts    []TeamDeliveryContext
}

type TeamDeliveryContext struct {
    TeamID                    string
    TeamHandle                string
    TeamDisplayName           string
    TeamDescription           string
    TeamMemberID              string
    RoleLabel                 string
    RoleOverlayPromptSnapshot string
    RoleOverlayVersion        int64
    MemberRoster              []TeamDeliveryRosterMember
}

type TeamDeliveryRosterMember struct {
    ActorKind    string
    ActorID      string
    DisplayName  string
    Handle       string
    RoleLabel    string
    DeliveryKind string
    Self         bool
}
```

Merge 规则：

| 场景                                     | 结果                                                                            |
| ---------------------------------------- | ------------------------------------------------------------------------------- |
| 同一 agent 被多个 Team 提及              | 一个 `AgentRecipient`，保留多个 `TeamContexts`。                                |
| 同一 agent 被直接 @agent 且被 @team 展开 | `DeliveryReason` 保留直接 mention 优先级，`TeamContexts` 保留 Team 身份上下文。 |
| 同一 agent 以多个 assignment 路径出现    | 去重后保留各来源 Team Context。                                                 |

### 6.6 非 agent 成员

当前产品模型不支持 human、service 或 nested Team 作为 Team 成员。写路径只接受 agent assignment；升级迁移清理历史非 agent 行；展开路径忽略任何残留非 agent 成员，避免把旧数据带回投递链路。

### 6.7 Scope 可见性过滤

`ExpandTeamMentionsForScope` 使用当前 surface 的可读权限过滤成员：

| Surface          | 可见性过滤输入                                                            |
| ---------------- | ------------------------------------------------------------------------- |
| channel          | `scope_memberships`、channel visibility、owner-private agent authority。  |
| reply thread     | thread scope membership、parent scope 权限、thread participant 规则。     |
| DM               | DM participant 与 DM 可见性策略。                                         |
| task scope       | task scope membership、task 所属 channel 权限、task collaborator policy。 |
| workflow surface | workflow run surface 与其绑定的 scope 权限。                              |

实际首轮 agent 投递对象是 Team 成员与当前协作面可见 actor 的交集，并且仅包含 `wake_on_team_mention=true` 的 agent 成员；其他成员保留在 Team Context roster 中，供后续结构化委托或显式 mention 使用。新建 Team 默认 Leader 为 `true`、其他成员为 `false`。

### 6.8 Fanout payload

`ScopeFanoutPayload` 继续承载 `MessageDelivery`。由于 `AgentRecipient` 包含 `TeamContexts`，outbox JSON 可直接持久化 Team Context snapshot。

Fanout 时：

1. 根据当前 recipient 取出对应 `TeamContexts`。
2. 将当前 recipient 标记写入各 Team Context 的 `MemberRoster.Self`。
3. 构造 `ReceiveMessage`。
4. 将 Team Context 写入 `ReceiveMessage.TeamContexts`。
5. 调用 `NotifyAgentMessage`。

### 6.9 ReceiveMessage DTO

Store model：

```go
type ReceiveMessage struct {
    ...
    TeamContexts []ReceiveTeamContext
}

type ReceiveTeamContext struct {
    TeamID             string
    TeamHandle         string
    TeamDisplayName    string
    TeamDescription    string
    TeamMemberID       string
    RoleLabel          string
    RoleOverlayPrompt  string
    MemberRoster       []ReceiveTeamMemberContext
}

type ReceiveTeamMemberContext struct {
    TeamMemberID string
    ActorKind    string
    ActorID      string
    DisplayName  string
    Handle       string
    RoleLabel    string
    DeliveryKind string
    Self         bool
}
```

Agent DTO：

```go
type ReceiveMessage struct {
    ...
    TeamContexts []ReceiveTeamContext `json:"teamContexts,omitempty"`
}

type ReceiveTeamContext struct {
    TeamID            string                     `json:"teamId"`
    TeamHandle        string                     `json:"teamHandle"`
    TeamDisplayName   string                     `json:"teamDisplayName"`
    TeamDescription   string                     `json:"teamDescription,omitempty"`
    TeamMemberID      string                     `json:"teamMemberId"`
    RoleLabel         string                     `json:"roleLabel,omitempty"`
    RoleOverlayPrompt string                     `json:"roleOverlayPrompt,omitempty"`
    MemberRoster      []ReceiveTeamMemberContext `json:"memberRoster"`
}

type ReceiveTeamMemberContext struct {
    TeamMemberID string `json:"teamMemberId"`
    ActorKind    string `json:"actorKind"`
    ActorID      string `json:"actorId"`
    DisplayName  string `json:"displayName"`
    Handle       string `json:"handle,omitempty"`
    RoleLabel    string `json:"roleLabel,omitempty"`
    DeliveryKind string `json:"deliveryKind"`
    Self         bool   `json:"self,omitempty"`
}
```


### 6.10 相关系统引用边界

Task、review、workflow、permission 等相关系统可以引用 Team actor reference。具体存储字段、状态机、展开时机、通知、权限与审计合同由对应 spec 定义。Team design 提供 Team identity、Team Profile、Team mention、成员展开与 agent Team Context 合同。

## 7. Agent prompt 编译

Daemon prompt compiler 在收到 `teamContexts` 后为当前 turn 注入 Team-member Mode 文本。

编译模板：

```text
# Team Context
You were mentioned as a member of @{{teamHandle}}.

Team id:
{{teamId}}

Team invocation:
You were awakened as this Team member. Other Team members may not have been awakened automatically.

Team duty (from description):
{{teamDescription}}

Your role in this team:
{{roleLabel}}

Role overlay:
{{roleOverlayPrompt}}

Team members in this current AgentTeam surface:
{{memberRoster}}
```

多个 Team Context 按 mention token 在原消息中的出现顺序排列。相同 Team 只注入一次。Team Context 只携带结构化的 Team identity、职责、当前成员角色与 invocation 元数据；prompt compiler 不额外注入通用协作规则或 Team 专属 Skill。

## 8. Web UI 设计

### 8.1 Team list / profile

新增 Team 管理入口：

```text
Workspace sidebar -> Teams
```

页面能力：

- Team directory、搜索、选中状态与空状态。
- Team 创建、编辑与删除。
- 内置模板与自定义模板入口。
- 点击「新建 Team」先打开模板选择小窗，主区域按内置模板、工作区模板、我的模板分组展示模板卡片；右下角保留弱入口「从空白建立」。
- 选择「从空白建立」时走手动创建；选择模板卡片后，把模板成员预填为创建页成员列表里的初始行，并继续填写 Team 资料、成员与成员配置。
- 新建 Team 表单的初始成员区复用 Team 成员页的 agent assignment 列结构；模板成员与手动添加成员都显示在同一个成员列表中；点击列表底部「添加智能体」追加一行，并直接填写 agent、标签、agent Role Overlay 与响应方式。
- 创建提交时，初始成员列表中仍存在但未选择成员对象的行必须阻止创建并提示用户先选择成员对象或移除该行；不可将这类行静默当作不存在。
- 创建提交校验失败或后端创建失败时，错误 Notice 必须自动滚动并聚焦到可见区域，保证用户从长表单底部提交时能立即看到失败原因。
- 将当前 Team 保存为自定义模板；保存弹窗默认仅自己可见，也可以发布为工作区模板；可管理自定义模板的名称、描述、可见范围并直接删除。
- Team Profile。
- Team Description 编辑。
- Team members 管理。
- agent Role Overlay 编辑。
- Settings Danger zone 中删除 Team。

### 8.2 Team mention picker

Composer mention picker 的 candidate source 增加 Team：

```text
People
Agents
Teams
Services
```

Team candidate 展示：

- Team avatar。
- Team 名称。
- 当前协作面可投递摘要：有可唤醒 agent 时显示“将唤醒 N 个智能体”；无可唤醒 agent 时显示“当前频道没有可唤醒的智能体”。
- 无当前协作面 preview 时可回退显示 description 或 member count。

Team candidate 不同时展示 display name 与 `@handle`；当前产品模型中两者是同一个名称。

### 8.3 Team token render

Message renderer 识别 `ResolvedMention{type: "team"}` 并渲染为 Team token。Token 点击打开 Team Profile。

### 8.4 Team member role UI

Team Profile 中成员行展示：

- actor avatar / display name。
- actor kind；当前固定为 `agent`。
- role label。
- Role Overlay 编辑入口。
- `@Team` 首轮响应方式。

## 9. API 与 store 权限

### 9.1 Team 可发现性

Team listing 与 mention candidate 查询按 `visibility` 过滤：

| visibility  | 查询可见性                                          |
| ----------- | --------------------------------------------------- |
| `workspace` | workspace active member 可见。                      |
| `private`   | Team creator、workspace owner/admin 可见。          |

### 9.2 Team 管理权限

| 操作                    | 权限                                |
| ----------------------- | ----------------------------------- |
| 创建 Team               | active workspace human member。     |
| 更新 Team 基本信息      | Team creator、workspace owner/admin。 |
| 编辑 Team Description        | Team creator、workspace owner/admin。 |
| 添加/移除成员           | Team creator、workspace owner/admin。 |
| 编辑 agent Role Overlay | Team creator、workspace owner/admin。 |
| 删除 Team               | Team creator、workspace owner/admin。 |

Team 创建事务只写入用户选择的 agent assignments，不把 human 创建者写入 Team 成员列表。Team 不维护 Team 内 admin/member 角色，也不存在最后 human admin 保护逻辑。

### 9.3 Actor 校验

添加 Team member 时：

- `actor_kind` 必须为 `agent`。
- `actor_id` 必须引用当前 workspace 的 active agent member。
- 用户只能添加自己拥有的 agent。

## 10. 与现有组织 Team 的关系

`organization_teams` 表达 organization 目录与授权编排。`workspace_teams` 表达 workspace 内可 mention 的协作主体。

组织目录 Team 可投影为 workspace Team：

```text
organization_team -> workspace_team
organization_member_team_memberships -> workspace_team_members
```

投影字段：

| organization team                  | workspace team                        |
| ---------------------------------- | ------------------------------------- |
| `slug`                             | `handle`                              |
| `name`                             | `handle`；`display_name` 仅作兼容镜像 |
| `description`                      | `description`                         |
| org agent mapping                   | workspace team agent assignment       |

投影策略由 org provider 控制，workspace Team API 继续面向 workspace 协作主体。

## 11. 迁移设计

新增 TiDB migration：

```text
internal/tidb/migrations/00216_workspace_teams.sql
internal/tidb/migrations/00217_workspace_team_templates.sql
internal/tidb/migrations/00218_workspace_team_member_wake_on_team_mention.sql
internal/tidb/migrations/00221_workspace_team_agent_assignments.sql
```

迁移内容：

1. 创建 `workspace_teams`。
2. 创建 `workspace_team_members`。
3. 创建 `workspace_team_templates`。
4. 创建 `workspace_team_template_members`。
5. 添加必要索引与外键。
6. 添加 `wake_on_team_mention`。
7. 清理历史非 agent Team member/template member 行。
8. 删除 `management_role`，并保持最终 bootstrap schema 与 migration 对齐。

## 12. 测试设计

### 12.1 Store tests

| 测试                               | 覆盖                                                   |
| ---------------------------------- | ------------------------------------------------------ |
| Create/list/get/update/delete Team | Team CRUD、权限、handle 唯一性与删除清理。             |
| Add/update/remove Team member      | agent assignment CRUD、role overlay、默认响应配置。    |
| Team management permission         | creator、workspace owner/admin 权限。                  |
| ResolveTeamMentionTokens           | hint 优先、handle fallback、大小写归一。               |
| ExpandTeamMentionsForScope channel | channel scope 可见性过滤。                             |
| ExpandTeamMentionsForScope thread  | reply thread 可见性过滤。                              |
| ExpandTeamMentionsForScope dedupe  | 多 Team 提及同一 agent 时去重并保留 Team Context。     |
| Team templates lifecycle            | 内置/工作区/我的模板列表、从 Team 保存模板、更新模板 metadata/scope、删除模板、从模板创建普通 Team。 |

### 12.2 Message routing tests

| 测试                                   | 覆盖                                      |
| -------------------------------------- | ----------------------------------------- |
| `@team` creates agent recipients       | AgentRecipient reason 与 TeamContexts。   |
| `@team` persists resolved mention      | AppMessage resolved mention type `team`。 |
| direct agent mention plus team mention | recipient merge 与 TeamContexts 保留。    |
| multiple teams mention same agent      | 多 TeamContexts。                         |
| expansion edge before recipient dedupe | per-team context 与最终 recipient 合并。  |
| invisible team member                  | 当前 surface 可见性过滤。                 |
| member roster in TeamContext           | 本次协作成员列表与 `self` 标记。          |

### 12.3 API tests

| 测试                         | 覆盖                                                        |
| ---------------------------- | ----------------------------------------------------------- |
| Team management auth         | creator、workspace owner/admin 权限。                       |
| Mention candidate query      | visibility 与搜索。                                         |
| Team member actor validation | agent-only actor 校验与 owned-agent 字段约束。              |
| Team template API             | 模板列表、创建、保存、删除与 instantiate 权限。              |
| Prompt length validation     | Team Description 与 agent Role Overlay 上限。                    |

### 12.4 Web tests

| 测试                 | 覆盖                                                  |
| -------------------- | ----------------------------------------------------- |
| Team mention picker  | 搜索、展示、插入 token。                              |
| Team token render    | AppMessage resolved mention 渲染。                    |
| Team Profile         | 成员、Team Description、agent 成员 Role Overlay 编辑入口。 |
| Team templates       | 新建时模板选择小窗、内置/工作区/我的模板分组、右下角弱空白入口、模板成员预填进同一个创建成员列表、预制 agent 提示词填写、创建前成员列表式初始成员配置、保存/管理/直接删除模板。 |
| Channel mention flow | @team 后 agent 成员投递展示。                         |
| Role assignment prompt | 全部待补成员 canonical mention、leader 去重、草稿保留，以及 approved 后按最新成员数据消失。 |

### 12.5 Agent delivery tests

| 测试                         | 覆盖                                           |
| ---------------------------- | ---------------------------------------------- |
| ReceiveMessage TeamContexts  | DTO 转换与 daemon payload。                    |
| ReceiveMessage member roster | 带稳定 TeamMember id 的本次协作成员列表进入 agent payload。 |
| Prompt compile team context  | Team-member Mode prompt 注入。                 |
| Outbox replay                | scope_fanout_outbox replay 保留 TeamContexts。 |

## 13. 实现清单

- 新增 `workspace_teams`、`workspace_team_members`、`workspace_team_templates` 与 `workspace_team_template_members` migration。
- 新增 `internal/store/model/team.go`。
- 新增 `internal/store/team`。
- 新增 `internal/api/dto/team.go`。
- 新增 `internal/api/team` routes 与 handlers。
- Web mention candidate 查询加入 Team。
- Browser `MessageMentionHint` 支持 `type = team`。
- Message resolved mention 支持 `type = team`。
- Message routing 解析 Team mention。
- Team 成员展开接入 routing plan。
- per-team expansion edge 先生成 Team Context，再做最终 recipient 去重。
- `AgentRecipient` 增加 `TeamContexts`。
- Team Context 增加本次协作成员列表。
- scope fanout outbox payload 持久化 TeamContexts。
- `ReceiveMessage` DTO 增加 `teamContexts`。
- Daemon prompt compiler 注入 Team Context。
- Web Team 管理页面、Team Template 选择/保存入口与 Team token 渲染。
- Diagnostics 展示 team mention 展开结果与过滤结果。
- Store / API / routing / fanout / Web / daemon prompt tests。
